Merge "USB: qdss: Fix NULL pointer deference issue during QDSS transfers" into msm-4.9
diff --git a/Documentation/devicetree/bindings/cnss/icnss.txt b/Documentation/devicetree/bindings/cnss/icnss.txt
index 15feda3..c801e848 100644
--- a/Documentation/devicetree/bindings/cnss/icnss.txt
+++ b/Documentation/devicetree/bindings/cnss/icnss.txt
@@ -12,13 +12,22 @@
- reg-names: Names of the memory regions defined in reg entry
- interrupts: Copy engine interrupt table
- qcom,wlan-msa-memory: MSA memory size
+ - clocks: List of clock phandles
+ - clock-names: List of clock names corresponding to the "clocks" property
- iommus: SMMUs and corresponding Stream IDs needed by WLAN
- qcom,wlan-smmu-iova-address: I/O virtual address range as <start length>
format to be used for allocations associated between WLAN and SMMU
Optional properties:
+ - <supply-name>-supply: phandle to the regulator device tree node
+ optional "supply-name" is "vdd-0.8-cx-mx".
+ - qcom,<supply>-config: Specifies voltage levels for supply. Should be
+ specified in pairs (min, max), units uV. There can
+ be optional load in uA and Regulator settle delay in
+ uS.
- qcom,icnss-vadc: VADC handle for vph_pwr read APIs.
- qcom,icnss-adc_tm: VADC handle for vph_pwr notification APIs.
+ - qcom,smmu-s1-bypass: Boolean context flag to set SMMU to S1 bypass
Example:
@@ -26,6 +35,8 @@
compatible = "qcom,icnss";
reg = <0x0a000000 0x1000000>;
reg-names = "membase";
+ clocks = <&clock_gcc clk_aggre2_noc_clk>;
+ clock-names = "smmu_aggre2_noc_clk";
iommus = <&anoc2_smmu 0x1900>,
<&anoc2_smmu 0x1901>;
qcom,wlan-smmu-iova-address = <0 0x10000000>;
@@ -43,4 +54,7 @@
<0 140 0 /* CE10 */ >,
<0 141 0 /* CE11 */ >;
qcom,wlan-msa-memory = <0x200000>;
+ qcom,smmu-s1-bypass;
+ vdd-0.8-cx-mx-supply = <&pm8998_l5>;
+ qcom,vdd-0.8-cx-mx-config = <800000 800000 2400 1000>;
};
diff --git a/Documentation/devicetree/bindings/fb/mdss-pll.txt b/Documentation/devicetree/bindings/fb/mdss-pll.txt
index b028dda..d0d7fff 100644
--- a/Documentation/devicetree/bindings/fb/mdss-pll.txt
+++ b/Documentation/devicetree/bindings/fb/mdss-pll.txt
@@ -15,7 +15,7 @@
"qcom,mdss_hdmi_pll_8996_v2", "qcom,mdss_dsi_pll_8996_v2",
"qcom,mdss_hdmi_pll_8996_v3", "qcom,mdss_hdmi_pll_8996_v3_1p8",
"qcom,mdss_edp_pll_8996_v3", "qcom,mdss_edp_pll_8996_v3_1p8",
- "qcom,mdss_dsi_pll_8998", "qcom,mdss_dp_pll_8998",
+ "qcom,mdss_dsi_pll_10nm", "qcom,mdss_dp_pll_8998",
"qcom,mdss_hdmi_pll_8998"
- cell-index: Specifies the controller used
- reg: offset and length of the register set for the device.
diff --git a/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt
index 058dab1..0295e1b 100644
--- a/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt
+++ b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt
@@ -84,6 +84,7 @@
- qcom,mdss-rot-mode: This is integer value indicates operation mode
of the rotator device
- qcom,mdss-sbuf-headroom: This integer value indicates stream buffer headroom in lines.
+- qcom,mdss-rot-linewidth: This integer value indicates rotator line width supported in pixels.
- cache-slice-names: A set of names that identify the usecase names of a client that uses
cache slice. These strings are used to look up the cache slice
entries by name.
diff --git a/Documentation/devicetree/bindings/misc/qpnp-misc.txt b/Documentation/devicetree/bindings/misc/qpnp-misc.txt
new file mode 100644
index 0000000..a34cbde
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/qpnp-misc.txt
@@ -0,0 +1,25 @@
+QPNP-MISC
+
+QPNP-MISC provides a way to read the PMIC part number and revision.
+
+Required properties:
+- compatible : should be "qcom,qpnp-misc"
+- reg : offset and length of the PMIC peripheral register map.
+
+Optional properties:
+- qcom,pwm-sel: Select PWM source. Possible values:
+ 0: LOW
+ 1: PWM1_in
+ 2: PWM2_in
+ 3: PWM1_in & PWM2_in
+- qcom,enable-gp-driver: Enable the GP driver. Should only be specified
+ if a non-zero PWM source is specified under
+ "qcom,pwm-sel" property.
+
+Example:
+ qcom,misc@900 {
+ compatible = "qcom,qpnp-misc";
+ reg = <0x900 0x100>;
+ qcom,pwm-sel = <2>;
+ qcom,enable-gp-driver;
+ };
diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt
index 485483a..6111c88 100644
--- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt
+++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt
@@ -1,55 +1,81 @@
-* Qualcomm SDHCI controller (sdhci-msm)
+Qualcomm Technologies, Inc. Standard Secure Digital Host Controller (SDHC)
-This file documents differences between the core properties in mmc.txt
-and the properties used by the sdhci-msm driver.
+Secure Digital Host Controller provides standard host interface to SD/MMC/SDIO cards.
Required properties:
-- compatible: Should contain "qcom,sdhci-msm-v4".
-- reg: Base address and length of the register in the following order:
- - Host controller register map (required)
- - SD Core register map (required)
-- interrupts: Should contain an interrupt-specifiers for the interrupts:
- - Host controller interrupt (required)
-- pinctrl-names: Should contain only one value - "default".
-- pinctrl-0: Should specify pin control groups used for this controller.
-- clocks: A list of phandle + clock-specifier pairs for the clocks listed in clock-names.
-- clock-names: Should contain the following:
- "iface" - Main peripheral bus clock (PCLK/HCLK - AHB Bus clock) (required)
- "core" - SDC MMC clock (MCLK) (required)
- "bus" - SDCC bus voter clock (optional)
+ - compatible : should be "qcom,sdhci-msm"
+ - reg : should contain SDHC, SD Core register map.
+ - reg-names : indicates various resources passed to driver (via reg proptery) by name.
+ Required "reg-names" are "hc_mem" and "core_mem"
+ - interrupts : should contain SDHC interrupts.
+ - interrupt-names : indicates interrupts passed to driver (via interrupts property) by name.
+ Required "interrupt-names" are "hc_irq" and "pwr_irq".
+ - <supply-name>-supply: phandle to the regulator device tree node
+ Required "supply-name" are "vdd" and "vdd-io".
+
+Required alias:
+- The slot number is specified via an alias with the following format
+ 'sdhc{n}' where n is the slot number.
+
+Optional Properties:
+ - interrupt-names - "status_irq". This status_irq will be used for card
+ detection.
+ - qcom,bus-width - defines the bus I/O width that controller supports.
+ Units - number of bits. The valid bus-width values are
+ 1, 4 and 8.
+ - qcom,nonremovable - specifies whether the card in slot is
+ hot pluggable or hard wired.
+ - qcom,bus-speed-mode - specifies supported bus speed modes by host.
+ The supported bus speed modes are :
+ "HS200_1p8v" - indicates that host can support HS200 at 1.8v.
+ "HS200_1p2v" - indicates that host can support HS200 at 1.2v.
+ "DDR_1p8v" - indicates that host can support DDR mode at 1.8v.
+ "DDR_1p2v" - indicates that host can support DDR mode at 1.2v.
+
+In the following, <supply> can be vdd (flash core voltage) or vdd-io (I/O voltage).
+ - qcom,<supply>-always-on - specifies whether supply should be kept "on" always.
+ - qcom,<supply>-lpm_sup - specifies whether supply can be kept in low power mode (lpm).
+ - qcom,<supply>-voltage_level - specifies voltage levels for supply. Should be
+ specified in pairs (min, max), units uV.
+ - qcom,<supply>-current_level - specifies load levels for supply in lpm or
+ high power mode (hpm). Should be specified in
+ pairs (lpm, hpm), units uA.
+
+ - gpios - specifies gpios assigned for sdhc slot.
+ - qcom,gpio-names - a list of strings that map in order to the list of gpios
Example:
- sdhc_1: sdhci@f9824900 {
- compatible = "qcom,sdhci-msm-v4";
- reg = <0xf9824900 0x11c>, <0xf9824000 0x800>;
- interrupts = <0 123 0>;
- bus-width = <8>;
- non-removable;
-
- vmmc-supply = <&pm8941_l20>;
- vqmmc-supply = <&pm8941_s3>;
-
- pinctrl-names = "default";
- pinctrl-0 = <&sdc1_clk &sdc1_cmd &sdc1_data>;
-
- clocks = <&gcc GCC_SDCC1_APPS_CLK>, <&gcc GCC_SDCC1_AHB_CLK>;
- clock-names = "core", "iface";
+ aliases {
+ sdhc1 = &sdhc_1;
};
- sdhc_2: sdhci@f98a4900 {
- compatible = "qcom,sdhci-msm-v4";
- reg = <0xf98a4900 0x11c>, <0xf98a4000 0x800>;
- interrupts = <0 125 0>;
- bus-width = <4>;
- cd-gpios = <&msmgpio 62 0x1>;
+ sdhc_1: qcom,sdhc@f9824900 {
+ compatible = "qcom,sdhci-msm";
+ reg = <0xf9824900 0x11c>, <0xf9824000 0x800>;
+ reg-names = "hc_mem", "core_mem";
+ interrupts = <0 123 0>, <0 138 0>;
+ interrupt-names = "hc_irq", "pwr_irq";
- vmmc-supply = <&pm8941_l21>;
- vqmmc-supply = <&pm8941_l13>;
+ vdd-supply = <&pm8941_l21>;
+ vdd-io-supply = <&pm8941_l13>;
+ qcom,vdd-voltage-level = <2950000 2950000>;
+ qcom,vdd-current-level = <9000 800000>;
- pinctrl-names = "default";
- pinctrl-0 = <&sdc2_clk &sdc2_cmd &sdc2_data>;
+ qcom,vdd-io-always-on;
+ qcom,vdd-io-lpm-sup;
+ qcom,vdd-io-voltage-level = <1800000 2950000>;
+ qcom,vdd-io-current-level = <6 22000>;
- clocks = <&gcc GCC_SDCC2_APPS_CLK>, <&gcc GCC_SDCC2_AHB_CLK>;
- clock-names = "core", "iface";
+ qcom,bus-width = <4>;
+ qcom,nonremovable;
+ qcom,bus-speed-mode = "HS200_1p8v", "DDR_1p8v";
+
+ gpios = <&msmgpio 40 0>, /* CLK */
+ <&msmgpio 39 0>, /* CMD */
+ <&msmgpio 38 0>, /* DATA0 */
+ <&msmgpio 37 0>, /* DATA1 */
+ <&msmgpio 36 0>, /* DATA2 */
+ <&msmgpio 35 0>; /* DATA3 */
+ qcom,gpio-names = "CLK", "CMD", "DAT0", "DAT1", "DAT2", "DAT3";
};
diff --git a/Documentation/devicetree/bindings/platform/msm/rmnet_ipa.txt b/Documentation/devicetree/bindings/platform/msm/rmnet_ipa.txt
index c7024e0..d8934c0 100644
--- a/Documentation/devicetree/bindings/platform/msm/rmnet_ipa.txt
+++ b/Documentation/devicetree/bindings/platform/msm/rmnet_ipa.txt
@@ -10,9 +10,13 @@
- qcom,ipa-loaduC: indicate that ipa uC should be loaded
- qcom,ipa-advertise-sg-support: determine how to respond to a query
regarding scatter-gather capability
+- qcom,ipa-napi-enable: Boolean context flag to indicate whether
+ to enable napi framework or not
+- qcom,wan-rx-desc-size: size of WAN rx desc fifo ring, default is 256
Example:
qcom,rmnet-ipa {
compatible = "qcom,rmnet-ipa";
+ qcom,wan-rx-desc-size = <256>;
}
diff --git a/Documentation/devicetree/bindings/platform/msm/rmnet_ipa3.txt b/Documentation/devicetree/bindings/platform/msm/rmnet_ipa3.txt
index 3f55312..e9575f1 100644
--- a/Documentation/devicetree/bindings/platform/msm/rmnet_ipa3.txt
+++ b/Documentation/devicetree/bindings/platform/msm/rmnet_ipa3.txt
@@ -10,9 +10,13 @@
- qcom,ipa-loaduC: indicate that ipa uC should be loaded
- qcom,ipa-advertise-sg-support: determine how to respond to a query
regarding scatter-gather capability
+- qcom,ipa-napi-enable: Boolean context flag to indicate whether
+ to enable napi framework or not
+- qcom,wan-rx-desc-size: size of WAN rx desc fifo ring, default is 256
Example:
qcom,rmnet-ipa3 {
compatible = "qcom,rmnet-ipa3";
+ qcom,wan-rx-desc-size = <256>;
}
diff --git a/Documentation/devicetree/bindings/soc/qcom/qpnp-pbs.txt b/Documentation/devicetree/bindings/soc/qcom/qpnp-pbs.txt
new file mode 100644
index 0000000..d7aefbf
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qpnp-pbs.txt
@@ -0,0 +1,30 @@
+QPNP PBS
+
+QPNP (Qualcomm Technologies, Inc. Plug N Play) PBS is programmable boot sequence
+and this driver is for helping the client drivers triggering such sequence
+to be configured in PMIC.
+
+This document describes the bindings for QPNP PBS driver.
+
+=======================
+Required Node Structure
+=======================
+
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: should be "qcom,qpnp-pbs".
+
+- reg
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Base address of the PBS registers.
+
+
+=======
+Example
+=======
+ pm660l_pbs: qcom,pbs@7300 {
+ compatible = "qcom,qpnp-pbs";
+ reg = <0x7300 0x100>;
+ };
diff --git a/Documentation/devicetree/bindings/thermal/qcom-bcl.txt b/Documentation/devicetree/bindings/thermal/qcom-bcl.txt
new file mode 100644
index 0000000..449cbad
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/qcom-bcl.txt
@@ -0,0 +1,44 @@
+===============================================================================
+BCL PMIC Peripheral driver:
+===============================================================================
+Qualcomm Technologies, Inc's PMIC has battery current limiting peripheral, which can monitor for
+high battery current and low battery voltage in the hardware. The BCL
+peripheral driver interacts with the PMIC peripheral using the SPMI driver
+interface. The hardware can take threshold for notifying for high battery
+current or low battery voltage events.
+
+Required Parameters:
+- compatible: must be
+ 'qcom,msm-bcl-lmh' for bcl peripheral with LMH DCVSh interface.
+- reg: <a b> where 'a' is the starting register address of the PMIC
+ peripheral and 'b' is the size of the peripheral address space.
+ If the BCL inhibit current derating feature is enabled, this must also
+ have the PON spare registers as well. Example: <a b c d> where
+ c is the first PON spare register that will be written and d is the
+ size of the registers space needed to be written. Certain version
+ of PMIC, can send interrupt to LMH hardware driver directly. In that
+ case the shadow peripheral address space should be mentioned along
+ with the bcl peripherals address.
+- interrupts: <a b c> Where 'a' is the SLAVE ID of the PMIC, 'b' is
+ the peripheral ID and 'c' is the interrupt number in PMIC.
+- interrupt-names: user defined names for the interrupts. These
+ interrupt names will be used by the drivers to identify the
+ interrupts, instead of specifying the ID's. bcl driver will
+ accept these five standard interrupts.
+ "bcl-low-vbat"
+ "bcl-very-low-vbat"
+ "bcl-crit-low-vbat"
+ "bcl-high-ibat"
+ "bcl-very-high-ibat"
+
+
+Optional Parameters:
+
+ bcl@4200 {
+ compatible = "qcom,msm-bcl";
+ reg = <0x4200 0xFF 0x88e 0x2>;
+ interrupts = <0x2 0x42 0x0>,
+ <0x2 0x42 0x1>;
+ interrupt-names = "bcl-high-ibat-int",
+ "bcl-low-vbat-int";
+ };
diff --git a/Documentation/devicetree/bindings/thermal/qcom-lmh-dcvs.txt b/Documentation/devicetree/bindings/thermal/qcom-lmh-dcvs.txt
new file mode 100644
index 0000000..080d4da
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/qcom-lmh-dcvs.txt
@@ -0,0 +1,41 @@
+Limits Management Hardware - DCVS
+
+The LMH-DCVS block is a hardware IP for every CPU cluster, to handle quick
+changes in thermal limits. The hardware responds to thermal variation amongst
+the CPUs in the cluster by requesting limits on the clock frequency and
+voltage on the OSM hardware.
+
+The LMH DCVS driver exports a virtual sensor that can be used to set the
+thermal limits on the hardware. LMH DCVS driver can be a platform CPU Cooling
+device, which registers with the CPU cooling device interface. All CPU device
+nodes should reference the corresponding LMH DCVS hardware in device tree.
+CPUs referencing the same LMH DCVS node will be associated with the
+corresponding cooling device as related CPUs.
+
+Properties:
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: shall be "qcom,msm-hw-limits"
+- interrupts:
+ Usage: required
+ Value type: <interrupt_type interrupt_number interrupt_trigger_type>
+ Definition: Should specify interrupt information about the debug
+ interrupt generated by the LMH DCVSh hardware. LMH
+ DCVSh hardware will generate this interrupt whenever
+ it makes a new cpu DCVS decision.
+
+Example:
+
+ lmh_dcvs0: qcom,limits-dcvs@0 {
+ compatible = "qcom,msm-hw-limits";
+ interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ CPU0: cpu@0 {
+ device_type = "cpu";
+ compatible = "arm,armv8";
+ reg = <0x0 0x0>;
+ qcom,lmh-dcvs = <&lmh_dcvs0>;;
+ };
diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt
index 4a81034..609d853 100644
--- a/Documentation/devicetree/bindings/usb/dwc3.txt
+++ b/Documentation/devicetree/bindings/usb/dwc3.txt
@@ -56,6 +56,8 @@
fladj_30mhz_sdbnd signal is invalid or incorrect.
- snps,disable-clk-gating: If present, disable controller's internal clock
gating. Default it is enabled.
+ - snps,xhci-imod-value: Interrupt moderation interval for host mode
+ (in increments of 250nsec).
This is usually a subnode to DWC3 glue to which it is connected.
@@ -65,4 +67,5 @@
interrupts = <0 92 4>
usb-phy = <&usb2_phy>, <&usb3,phy>;
tx-fifo-resize;
+ snps,xhci-imod-value = <4000>;
};
diff --git a/Documentation/devicetree/bindings/usb/msm-ssusb.txt b/Documentation/devicetree/bindings/usb/msm-ssusb.txt
index 18056ee..bc66690 100644
--- a/Documentation/devicetree/bindings/usb/msm-ssusb.txt
+++ b/Documentation/devicetree/bindings/usb/msm-ssusb.txt
@@ -66,6 +66,7 @@
event buffers. 1 event buffer is needed per h/w accelerated endpoint.
- 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,smmu-s1-bypass: If present, configure SMMU to bypass stage 1 translation.
Sub nodes:
- Sub node for "DWC3- USB3 controller".
diff --git a/arch/arm/include/asm/dma-iommu.h b/arch/arm/include/asm/dma-iommu.h
index 2ef282f..b4e74af 100644
--- a/arch/arm/include/asm/dma-iommu.h
+++ b/arch/arm/include/asm/dma-iommu.h
@@ -24,6 +24,8 @@ struct dma_iommu_mapping {
struct kref kref;
};
+#ifdef CONFIG_ARM_DMA_USE_IOMMU
+
struct dma_iommu_mapping *
arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, u64 size);
@@ -33,5 +35,29 @@ int arm_iommu_attach_device(struct device *dev,
struct dma_iommu_mapping *mapping);
void arm_iommu_detach_device(struct device *dev);
+#else /* !CONFIG_ARM_DMA_USE_IOMMU */
+
+static inline struct dma_iommu_mapping *
+arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, size_t size)
+{
+ return NULL;
+}
+
+static inline void arm_iommu_release_mapping(struct dma_iommu_mapping *mapping)
+{
+}
+
+static inline int arm_iommu_attach_device(struct device *dev,
+ struct dma_iommu_mapping *mapping)
+{
+ return -ENODEV;
+}
+
+static inline void arm_iommu_detach_device(struct device *dev)
+{
+}
+
+#endif /* CONFIG_ARM_DMA_USE_IOMMU */
+
#endif /* __KERNEL__ */
#endif
diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index e46907c..33f3cc6 100644
--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -1925,7 +1925,11 @@ static dma_addr_t arm_coherent_iommu_map_page(struct device *dev, struct page *p
{
struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
dma_addr_t dma_addr;
- int ret, prot, len = PAGE_ALIGN(size + offset);
+ int ret, prot, len, start_offset, map_offset;
+
+ map_offset = offset & ~PAGE_MASK;
+ start_offset = offset & PAGE_MASK;
+ len = PAGE_ALIGN(map_offset + size);
dma_addr = __alloc_iova(mapping, len);
if (dma_addr == DMA_ERROR_CODE)
@@ -1933,11 +1937,12 @@ static dma_addr_t arm_coherent_iommu_map_page(struct device *dev, struct page *p
prot = __dma_direction_to_prot(dir);
- ret = iommu_map(mapping->domain, dma_addr, page_to_phys(page), len, prot);
+ ret = iommu_map(mapping->domain, dma_addr, page_to_phys(page) +
+ start_offset, len, prot);
if (ret < 0)
goto fail;
- return dma_addr + offset;
+ return dma_addr + map_offset;
fail:
__free_iova(mapping, dma_addr, len);
return DMA_ERROR_CODE;
diff --git a/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-ascent-2800mah.dtsi b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-ascent-2800mah.dtsi
new file mode 100644
index 0000000..a83d860
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-ascent-2800mah.dtsi
@@ -0,0 +1,81 @@
+/* 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.
+ */
+
+qcom,ascent_2800mah {
+ /* #Ascent_860_82912_0000_2800mAh_averaged_MasterSlave_Jan11th2017*/
+ qcom,max-voltage-uv = <4350000>;
+ qcom,fg-cc-cv-threshold-mv = <4340>;
+ qcom,fastchg-current-ma = <2800>;
+ qcom,batt-id-kohm = <20>;
+ qcom,battery-beta = <3450>;
+ qcom,battery-type = "ascent_2800mah_averaged_masterslave_jan11th2017";
+ qcom,checksum = <0x0110>;
+ qcom,gui-version = "PMI8998GUI - 2.0.0.54";
+ qcom,fg-profile-data = [
+ 21 21 F5 0D
+ 82 0B 6E 05
+ 0C 1D 5F FA
+ 74 06 97 01
+ 0E 18 F7 22
+ A8 45 B1 52
+ 76 00 00 00
+ 0E 00 00 00
+ 00 00 3D C4
+ 6E CD 2A CB
+ 21 00 08 00
+ 28 D3 2E E5
+ 0E 06 BA F3
+ 59 E3 22 12
+ 08 E5 54 32
+ 22 06 09 20
+ 27 00 14 00
+ 4B 20 F6 04
+ CF 0A 04 06
+ 25 1D B7 FA
+ DD F4 BB 06
+ FE 18 E1 22
+ 73 45 32 53
+ 5F 00 00 00
+ 0E 00 00 00
+ 00 00 D5 D5
+ 9C CC 8E D3
+ 1A 00 00 00
+ 6E EA 2E E5
+ 6E 06 A9 00
+ 6D F5 73 0B
+ 2A 02 61 1B
+ B1 33 CC FF
+ 07 10 00 00
+ 14 0B 99 45
+ 1A 00 40 00
+ 7D 01 0A FA
+ FF 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ ];
+};
diff --git a/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-ascent-3450mah.dtsi b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-ascent-3450mah.dtsi
new file mode 100644
index 0000000..c7cecbc
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-ascent-3450mah.dtsi
@@ -0,0 +1,81 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+qcom,ascent_3450mah {
+ /* Ascent_with_connector_3450mAh_averaged_MasterSlave_Jan6th2017 */
+ qcom,max-voltage-uv = <4350000>;
+ qcom,fg-cc-cv-threshold-mv = <4340>;
+ qcom,fastchg-current-ma = <3450>;
+ qcom,batt-id-kohm = <60>;
+ qcom,battery-beta = <3435>;
+ qcom,battery-type = "ascent_3450mah_averaged_masterslave_jan6th2017";
+ qcom,checksum = <0x96AC>;
+ qcom,gui-version = "PMI8998GUI - 2.0.0.54";
+ qcom,fg-profile-data = [
+ 9C 1F 85 05
+ 82 0A 73 FC
+ 2B 1D 72 EA
+ EE 03 66 0C
+ C8 17 F4 22
+ E0 45 1F 52
+ 5C 00 00 00
+ 10 00 00 00
+ 00 00 4A C4
+ C7 BC 48 C2
+ 0F 00 08 00
+ E1 DA 5D ED
+ 8D FD B2 F3
+ 96 E2 A7 12
+ 7E F4 0E 3B
+ 24 06 09 20
+ 27 00 14 00
+ 83 1F EE 05
+ 1F 0A 45 FD
+ 6B 1D 53 E5
+ EC 0B 31 14
+ 44 18 49 23
+ 18 45 A6 53
+ 55 00 00 00
+ 0E 00 00 00
+ 00 00 61 CC
+ B7 C3 0F BC
+ 0F 00 00 00
+ 92 00 5D ED
+ E3 06 E0 00
+ 75 FD 9C 03
+ 47 DB B3 22
+ CB 33 CC FF
+ 07 10 00 00
+ 99 0D 99 45
+ 0F 00 40 00
+ AB 01 0A FA
+ FF 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ ];
+};
diff --git a/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-demo-6000mah.dtsi b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-demo-6000mah.dtsi
new file mode 100644
index 0000000..1e8cd16
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-demo-6000mah.dtsi
@@ -0,0 +1,78 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+qcom,demo_6000mah {
+ qcom,max-voltage-uv = <4350000>;
+ qcom,fg-cc-cv-threshold-mv = <4340>;
+ qcom,fastchg-current-ma = <6000>;
+ qcom,batt-id-kohm = <75>;
+ qcom,battery-beta = <3435>;
+ qcom,battery-type = "Demo_battery_6000mah";
+ qcom,fg-profile-data = [
+ 2C 1F 3F FC
+ E9 03 A1 FD
+ 58 1D FD F5
+ 27 12 2C 14
+ 3F 18 FF 22
+ 9B 45 A3 52
+ 55 00 00 00
+ 0E 00 00 00
+ 00 00 1C AC
+ F7 CD 71 B5
+ 1A 00 0C 00
+ 3C EB 54 E4
+ EC 05 7F FA
+ 76 05 F5 02
+ CA F3 82 3A
+ 2A 09 40 40
+ 07 00 05 00
+ 58 1F 42 06
+ 85 03 35 F4
+ 4D 1D 37 F2
+ 23 0A 79 15
+ B7 18 32 23
+ 26 45 72 53
+ 55 00 00 00
+ 0D 00 00 00
+ 00 00 13 CC
+ 03 00 98 BD
+ 16 00 00 00
+ 3C EB 54 E4
+ 9F FC A3 F3
+ 0F FC DF FA
+ FF E5 A9 23
+ CB 33 08 33
+ 07 10 00 00
+ 81 0D 99 45
+ 16 00 19 00
+ 75 01 0A FA
+ FF 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ ];
+};
diff --git a/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-itech-3000mah.dtsi b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-itech-3000mah.dtsi
new file mode 100644
index 0000000..3888047
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-itech-3000mah.dtsi
@@ -0,0 +1,81 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+qcom,itech_3000mah {
+ /* #Itech_B00826LF_3000mAh_ver1660_averaged_MasterSlave_Jan10th2017*/
+ qcom,max-voltage-uv = <4350000>;
+ qcom,fg-cc-cv-threshold-mv = <4340>;
+ qcom,fastchg-current-ma = <2000>;
+ qcom,batt-id-kohm = <100>;
+ qcom,battery-beta = <3435>;
+ qcom,battery-type = "itech_b00826lf_3000mah_ver1660_jan10th2017";
+ qcom,checksum = <0xFB8F>;
+ qcom,gui-version = "PMI8998GUI - 2.0.0.54";
+ qcom,fg-profile-data = [
+ A4 1F 6E 05
+ 9C 0A 2B FC
+ 32 1D 23 E5
+ 60 0B 1B 15
+ AD 17 8C 22
+ EA 3C 89 4A
+ 5B 00 00 00
+ 12 00 00 00
+ 00 00 62 C2
+ 0C CD D8 C2
+ 19 00 08 00
+ 85 EA C7 EC
+ E2 05 2F 01
+ 9B F5 12 12
+ 5E 05 88 3B
+ 22 06 09 20
+ 27 00 14 00
+ 7D 1F DD 05
+ 3F 0A E5 FC
+ 72 1D E3 F5
+ 6F 12 C0 1D
+ 88 18 FB 22
+ 8D 45 C6 52
+ 54 00 00 00
+ 0F 00 00 00
+ 00 00 BD CD
+ 55 C2 5D C5
+ 14 00 00 00
+ 7E 00 C7 EC
+ 60 06 BB 00
+ 59 06 61 03
+ D9 FC 75 1B
+ B3 33 CC FF
+ 07 10 00 00
+ 3E 0B 99 45
+ 14 00 40 00
+ AE 01 0A FA
+ FF 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ ];
+};
diff --git a/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-qrd-skuk-4v4-3000mah.dtsi b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-qrd-skuk-4v4-3000mah.dtsi
new file mode 100644
index 0000000..11600ef
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/fg-gen3-batterydata-qrd-skuk-4v4-3000mah.dtsi
@@ -0,0 +1,81 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+qcom,qrd_msm8998_skuk_3000mah {
+ /* QRD8997_ST1031GA_3000mAh_averaged_MasterSlave_Jan10th2017 */
+ qcom,max-voltage-uv = <4400000>;
+ qcom,fg-cc-cv-threshold-mv = <4390>;
+ qcom,fastchg-current-ma = <3000>;
+ qcom,batt-id-kohm = <68>;
+ qcom,battery-beta = <3380>;
+ qcom,battery-type = "qrd8997_st1031ga_3000mah";
+ qcom,checksum = <0xD299>;
+ qcom,gui-version = "PMI8998GUI - 2.0.0.54";
+ qcom,fg-profile-data = [
+ 70 1F B1 05
+ 6F 0A A1 FC
+ 8C 1D D7 FD
+ C4 12 AC 1D
+ 7E 18 01 23
+ 8C 45 B6 52
+ 55 00 00 00
+ 0F 00 00 00
+ 00 00 92 C5
+ 95 CD A0 CA
+ 1F 00 08 00
+ 9F E3 C3 EC
+ F7 FC 25 F3
+ 02 01 FF 12
+ 29 DC 1D 3A
+ 1C 06 09 20
+ 27 00 14 00
+ AC 1F B4 05
+ 57 0A EF FC
+ 6A 1D E9 E2
+ 11 0B BB 14
+ 40 19 DC 22
+ 79 45 03 53
+ 53 00 00 00
+ 0E 00 00 00
+ 00 00 05 CC
+ 3A BB 24 CA
+ 1C 00 00 00
+ 56 F2 C3 EC
+ A6 06 A2 F2
+ 9A 06 CC 01
+ 8C EA CF 1A
+ BA 33 CC FF
+ 07 10 00 00
+ 3A 0C 66 46
+ 1C 00 40 00
+ 98 01 0A FA
+ FF 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ ];
+};
diff --git a/arch/arm64/boot/dts/qcom/msm-arm-smmu-sdm845.dtsi b/arch/arm64/boot/dts/qcom/msm-arm-smmu-sdm845.dtsi
index a94a716..44d6f18 100644
--- a/arch/arm64/boot/dts/qcom/msm-arm-smmu-sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/msm-arm-smmu-sdm845.dtsi
@@ -212,18 +212,18 @@
};
};
- iommu_test_device {
+ kgsl_iommu_test_device {
compatible = "iommu-debug-test";
/*
- * 42 shouldn't be used by anyone on the mmss_smmu. We just
- * need _something_ here to get this node recognized by the
- * SMMU driver. Our test uses ATOS, which doesn't use SIDs
+ * 0x7 isn't a valid sid, but should pass the sid sanity check.
+ * We just need _something_ here to get this node recognized by
+ * the SMMU driver. Our test uses ATOS, which doesn't use SIDs
* anyways, so using a dummy value is ok.
*/
- iommus = <&kgsl_smmu 0x3>;
+ iommus = <&kgsl_smmu 0x7>;
};
- iommu_test_device2 {
+ apps_iommu_test_device {
compatible = "iommu-debug-test";
/*
* This SID belongs to PCIE. We can't use a fake SID for
diff --git a/arch/arm64/boot/dts/qcom/pm8998.dtsi b/arch/arm64/boot/dts/qcom/pm8998.dtsi
index 5290f46..7c496f1 100644
--- a/arch/arm64/boot/dts/qcom/pm8998.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm8998.dtsi
@@ -12,6 +12,7 @@
#include <dt-bindings/spmi/spmi.h>
#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/msm/power-on.h>
&spmi_bus {
qcom,pm8998@0 {
@@ -42,10 +43,6 @@
qcom,pon-type = <0>;
qcom,pull-up = <1>;
linux,code = <116>;
- qcom,support-reset = <1>;
- qcom,s1-timer = <10256>;
- qcom,s2-timer = <2000>;
- qcom,s2-type = <1>;
};
qcom,pon_2 {
@@ -60,7 +57,7 @@
qcom,pull-up = <1>;
qcom,s1-timer = <6720>;
qcom,s2-timer = <2000>;
- qcom,s2-type = <7>;
+ qcom,s2-type = <PON_POWER_OFF_DVDD_HARD_RESET>;
qcom,use-bark;
};
};
diff --git a/arch/arm64/boot/dts/qcom/pmi8998.dtsi b/arch/arm64/boot/dts/qcom/pmi8998.dtsi
index 1f27b21..539685a 100644
--- a/arch/arm64/boot/dts/qcom/pmi8998.dtsi
+++ b/arch/arm64/boot/dts/qcom/pmi8998.dtsi
@@ -63,6 +63,175 @@
qcom,gpios-disallowed = <4 7 13>;
};
+ qcom,qpnp-qnovo@1500 {
+ compatible = "qcom,qpnp-qnovo";
+ reg = <0x1500 0x100>;
+ interrupts = <0x2 0x15 0x0 IRQ_TYPE_NONE>;
+ interrupt-names = "ptrain-done";
+ qcom,pmic-revid = <&pmi8998_revid>;
+ };
+
+ pmi8998_charger: qcom,qpnp-smb2 {
+ compatible = "qcom,qpnp-smb2";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ qcom,pmic-revid = <&pmi8998_revid>;
+
+ io-channels = <&pmi8998_rradc 8>,
+ <&pmi8998_rradc 10>,
+ <&pmi8998_rradc 3>,
+ <&pmi8998_rradc 4>;
+ io-channel-names = "charger_temp",
+ "charger_temp_max",
+ "usbin_i",
+ "usbin_v";
+
+ qcom,boost-threshold-ua = <100000>;
+ qcom,wipower-max-uw = <5000000>;
+
+ qcom,thermal-mitigation
+ = <3000000 1500000 1000000 500000>;
+
+ qcom,chgr@1000 {
+ reg = <0x1000 0x100>;
+ interrupts =
+ <0x2 0x10 0x0 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x10 0x1 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x10 0x2 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x10 0x3 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x10 0x4 IRQ_TYPE_EDGE_RISING>;
+
+ interrupt-names = "chg-error",
+ "chg-state-change",
+ "step-chg-state-change",
+ "step-chg-soc-update-fail",
+ "step-chg-soc-update-request";
+ };
+
+ qcom,otg@1100 {
+ reg = <0x1100 0x100>;
+ interrupts = <0x2 0x11 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x11 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x11 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x11 0x3 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "otg-fail",
+ "otg-overcurrent",
+ "otg-oc-dis-sw-sts",
+ "testmode-change-detect";
+ };
+
+ qcom,bat-if@1200 {
+ reg = <0x1200 0x100>;
+ interrupts =
+ <0x2 0x12 0x0 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x12 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x12 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x12 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x12 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x12 0x5 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "bat-temp",
+ "bat-ocp",
+ "bat-ov",
+ "bat-low",
+ "bat-therm-or-id-missing",
+ "bat-terminal-missing";
+ };
+
+ qcom,usb-chgpth@1300 {
+ reg = <0x1300 0x100>;
+ interrupts =
+ <0x2 0x13 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x13 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x13 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x13 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x13 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x13 0x5 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x13 0x6 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x13 0x7 IRQ_TYPE_EDGE_RISING>;
+
+ interrupt-names = "usbin-collapse",
+ "usbin-lt-3p6v",
+ "usbin-uv",
+ "usbin-ov",
+ "usbin-plugin",
+ "usbin-src-change",
+ "usbin-icl-change",
+ "type-c-change";
+ };
+
+ qcom,dc-chgpth@1400 {
+ reg = <0x1400 0x100>;
+ interrupts =
+ <0x2 0x14 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x14 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x14 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x14 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x14 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x14 0x5 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x14 0x6 IRQ_TYPE_EDGE_RISING>;
+
+ interrupt-names = "dcin-collapse",
+ "dcin-lt-3p6v",
+ "dcin-uv",
+ "dcin-ov",
+ "dcin-plugin",
+ "div2-en-dg",
+ "dcin-icl-change";
+ };
+
+ qcom,chgr-misc@1600 {
+ reg = <0x1600 0x100>;
+ interrupts =
+ <0x2 0x16 0x0 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x16 0x1 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x16 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x16 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x16 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x16 0x5 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x16 0x6 IRQ_TYPE_EDGE_FALLING>,
+ <0x2 0x16 0x7 IRQ_TYPE_EDGE_BOTH>;
+
+ interrupt-names = "wdog-snarl",
+ "wdog-bark",
+ "aicl-fail",
+ "aicl-done",
+ "high-duty-cycle",
+ "input-current-limiting",
+ "temperature-change",
+ "switcher-power-ok";
+ };
+ };
+
+ pmi8998_pdphy: qcom,usb-pdphy@1700 {
+ compatible = "qcom,qpnp-pdphy";
+ reg = <0x1700 0x100>;
+ vdd-pdphy-supply = <&pm8998_l24>;
+ vbus-supply = <&smb2_vbus>;
+ vconn-supply = <&smb2_vconn>;
+ interrupts = <0x2 0x17 0x0 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x17 0x1 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x17 0x2 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x17 0x3 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x17 0x4 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x17 0x5 IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x17 0x6 IRQ_TYPE_EDGE_RISING>;
+
+ interrupt-names = "sig-tx",
+ "sig-rx",
+ "msg-tx",
+ "msg-rx",
+ "msg-tx-failed",
+ "msg-tx-discarded",
+ "msg-rx-discarded";
+
+ qcom,default-sink-caps = <5000 3000>, /* 5V @ 3A */
+ <9000 3000>, /* 9V @ 3A */
+ <12000 2250>; /* 12V @ 2.25A */
+ };
+
pmi8998_rradc: rradc@4500 {
compatible = "qcom,rradc";
reg = <0x4500 0x100>;
@@ -71,6 +240,70 @@
#io-channel-cells = <1>;
qcom,pmic-revid = <&pmi8998_revid>;
};
+
+ pmi8998_fg: qpnp,fg {
+ compatible = "qcom,fg-gen3";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ qcom,pmic-revid = <&pmi8998_revid>;
+ io-channels = <&pmi8998_rradc 0>;
+ io-channel-names = "rradc_batt_id";
+ qcom,rradc-base = <0x4500>;
+ qcom,fg-esr-timer-awake = <96>;
+ qcom,fg-esr-timer-asleep = <256>;
+ qcom,cycle-counter-en;
+ status = "okay";
+
+ qcom,fg-batt-soc@4000 {
+ status = "okay";
+ reg = <0x4000 0x100>;
+ interrupts = <0x2 0x40 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x40 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x40 0x2
+ IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x40 0x3
+ IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x40 0x4 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x40 0x5
+ IRQ_TYPE_EDGE_RISING>,
+ <0x2 0x40 0x6 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x40 0x7 IRQ_TYPE_EDGE_BOTH>;
+ interrupt-names = "soc-update",
+ "soc-ready",
+ "bsoc-delta",
+ "msoc-delta",
+ "msoc-low",
+ "msoc-empty",
+ "msoc-high",
+ "msoc-full";
+ };
+
+ qcom,fg-batt-info@4100 {
+ status = "okay";
+ reg = <0x4100 0x100>;
+ interrupts = <0x2 0x41 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x41 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x41 0x2 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x41 0x3 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x41 0x6 IRQ_TYPE_EDGE_BOTH>;
+ interrupt-names = "vbatt-pred-delta",
+ "vbatt-low",
+ "esr-delta",
+ "batt-missing",
+ "batt-temp-delta";
+ };
+
+ qcom,fg-memif@4400 {
+ status = "okay";
+ reg = <0x4400 0x100>;
+ interrupts = <0x2 0x44 0x0 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x44 0x1 IRQ_TYPE_EDGE_BOTH>,
+ <0x2 0x44 0x2 IRQ_TYPE_EDGE_BOTH>;
+ interrupt-names = "ima-rdy",
+ "mem-xcp",
+ "dma-grant";
+ };
+ };
};
qcom,pmi8998@3 {
diff --git a/arch/arm64/boot/dts/qcom/sdm845-cdp.dtsi b/arch/arm64/boot/dts/qcom/sdm845-cdp.dtsi
index 06f620b..27a95ae 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-cdp.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-cdp.dtsi
@@ -118,3 +118,19 @@
status = "ok";
};
+
+&pmi8998_flash2 {
+ pinctrl-names = "led_enable", "led_disable";
+ pinctrl-0 = <&flash_led3_front_en>;
+ pinctrl-1 = <&flash_led3_front_dis>;
+};
+
+&pmi8998_torch2 {
+ pinctrl-names = "led_enable", "led_disable";
+ pinctrl-0 = <&flash_led3_front_en>;
+ pinctrl-1 = <&flash_led3_front_dis>;
+};
+
+&pmi8998_charger {
+ qcom,batteryless-platform;
+};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-coresight.dtsi b/arch/arm64/boot/dts/qcom/sdm845-coresight.dtsi
index e7ff343..a3adcec 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-coresight.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-coresight.dtsi
@@ -476,6 +476,16 @@
};
port@2 {
+ reg = <2>;
+ funnel_in2_in_funnel_modem: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&funnel_modem_out_funnel_in2>;
+ };
+
+ };
+
+ port@3 {
reg = <5>;
funnel_in2_in_funnel_apss_merg: endpoint {
slave-mode;
@@ -495,12 +505,17 @@
coresight-name = "coresight-tpda";
qcom,tpda-atid = <65>;
- qcom,bc-elem-size = <13 32>;
- qcom,tc-elem-size = <7 32>,
+ qcom,bc-elem-size = <10 32>,
<13 32>;
- qcom,dsb-elem-size = <13 32>;
- qcom,cmb-elem-size = <7 32>,
- <8 32>,
+ qcom,tc-elem-size = <13 32>;
+ qcom,dsb-elem-size = <0 32>,
+ <2 32>,
+ <3 32>,
+ <10 32>,
+ <11 32>,
+ <13 32>;
+ qcom,cmb-elem-size = <3 64>,
+ <7 64>,
<13 64>;
clocks = <&clock_gcc RPMH_QDSS_CLK>,
@@ -520,6 +535,33 @@
};
port@1 {
+ reg = <0>;
+ tpda_in_tpdm_center: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpdm_center_out_tpda>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+ tpda_in_funnel_dl_mm: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&funnel_dl_mm_out_tpda>;
+ };
+ };
+
+ port@3 {
+ reg = <3>;
+ tpda_in_funnel_ddr_0: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&funnel_ddr_0_out_tpda>;
+ };
+ };
+
+ port@4 {
reg = <7>;
tpda_in_tpdm_vsense: endpoint {
slave-mode;
@@ -528,16 +570,25 @@
};
};
- port@2 {
- reg = <8>;
- tpda_in_tpdm_dcc: endpoint {
+ port@5 {
+ reg = <10>;
+ tpda_in_tpdm_qm: endpoint {
slave-mode;
remote-endpoint =
- <&tpdm_dcc_out_tpda>;
+ <&tpdm_qm_out_tpda>;
};
};
- port@3 {
+ port@6 {
+ reg = <11>;
+ tpda_in_tpdm_north: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpdm_north_out_tpda>;
+ };
+ };
+
+ port@7 {
reg = <13>;
tpda_in_tpdm_pimem: endpoint {
slave-mode;
@@ -548,6 +599,423 @@
};
};
+ funnel_modem: funnel@6832000 {
+ compatible = "arm,primecell";
+ arm,primecell-periphid = <0x0003b908>;
+
+ reg = <0x6832000 0x1000>;
+ reg-names = "funnel-base";
+
+ coresight-name = "coresight-funnel-modem";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "apb_pclk", "core_a_clk";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ funnel_modem_out_funnel_in2: endpoint {
+ remote-endpoint =
+ <&funnel_in2_in_funnel_modem>;
+ };
+ };
+
+ port@1 {
+ reg = <0>;
+ funnel_modem_in_tpda_modem: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpda_modem_out_funnel_modem>;
+ };
+ };
+ };
+ };
+
+ tpda_modem: tpda@6831000 {
+ compatible = "qcom,coresight-tpda";
+ reg = <0x6831000 0x1000>;
+ reg-names = "tpda-base";
+
+ coresight-name = "coresight-tpda-modem";
+
+ qcom,tpda-atid = <67>;
+ qcom,dsb-elem-size = <0 32>;
+ qcom,cmb-elem-size = <0 64>;
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ tpda_modem_out_funnel_modem: endpoint {
+ remote-endpoint =
+ <&funnel_modem_in_tpda_modem>;
+ };
+ };
+
+ port@1 {
+ reg = <0>;
+ tpda_modem_in_tpdm_modem: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpdm_modem_out_tpda_modem>;
+ };
+ };
+ };
+ };
+
+ tpdm_modem: tpdm@6830000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x6830000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-modem";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_modem_out_tpda_modem: endpoint {
+ remote-endpoint = <&tpda_modem_in_tpdm_modem>;
+ };
+ };
+ };
+
+ tpdm_center: tpdm@6c28000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x6c28000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-center";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_center_out_tpda: endpoint {
+ remote-endpoint = <&tpda_in_tpdm_center>;
+ };
+ };
+ };
+
+ tpdm_north: tpdm@6a24000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x6a24000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-north";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_north_out_tpda: endpoint {
+ remote-endpoint = <&tpda_in_tpdm_north>;
+ };
+ };
+ };
+
+ tpdm_qm: tpdm@69d0000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x69d0000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-qm";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_qm_out_tpda: endpoint {
+ remote-endpoint = <&tpda_in_tpdm_qm>;
+ };
+ };
+ };
+
+ tpda_apss: tpda@7862000 {
+ compatible = "qcom,coresight-tpda";
+ reg = <0x7862000 0x1000>;
+ reg-names = "tpda-base";
+
+ coresight-name = "coresight-tpda-apss";
+
+ qcom,tpda-atid = <66>;
+ qcom,dsb-elem-size = <0 32>;
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ tpda_apss_out_funnel_apss_merg: endpoint {
+ remote-endpoint =
+ <&funnel_apss_merg_in_tpda_apss>;
+ };
+ };
+
+ port@1 {
+ reg = <0>;
+ tpda_apss_in_tpdm_apss: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpdm_apss_out_tpda_apss>;
+ };
+ };
+ };
+ };
+
+ tpdm_apss: tpdm@7860000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x7860000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-apss";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_apss_out_tpda_apss: endpoint {
+ remote-endpoint = <&tpda_apss_in_tpdm_apss>;
+ };
+ };
+ };
+
+ tpda_llm_silver: tpda@78c0000 {
+ compatible = "qcom,coresight-tpda";
+ reg = <0x78c0000 0x1000>;
+ reg-names = "tpda-base";
+
+ coresight-name = "coresight-tpda-llm-silver";
+
+ qcom,tpda-atid = <72>;
+ qcom,cmb-elem-size = <0 64>;
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ tpda_llm_silver_out_funnel_apss_merg: endpoint {
+ remote-endpoint =
+ <&funnel_apss_merg_in_tpda_llm_silver>;
+ };
+ };
+
+ port@1 {
+ reg = <0>;
+ tpda_llm_silver_in_tpdm_llm_silver: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpdm_llm_silver_out_tpda_llm_silver>;
+ };
+ };
+ };
+ };
+
+ tpdm_llm_silver: tpdm@78a0000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x78a0000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-llm-silver";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_llm_silver_out_tpda_llm_silver: endpoint {
+ remote-endpoint =
+ <&tpda_llm_silver_in_tpdm_llm_silver>;
+ };
+ };
+ };
+
+ tpda_llm_gold: tpda@78d0000 {
+ compatible = "qcom,coresight-tpda";
+ reg = <0x78d0000 0x1000>;
+ reg-names = "tpda-base";
+
+ coresight-name = "coresight-tpda-llm-gold";
+
+ qcom,tpda-atid = <73>;
+ qcom,cmb-elem-size = <0 64>;
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ port@0 {
+ reg = <0>;
+ tpda_llm_gold_out_funnel_apss_merg: endpoint {
+ remote-endpoint =
+ <&funnel_apss_merg_in_tpda_llm_gold>;
+ };
+ };
+
+ port@1 {
+ reg = <0>;
+ tpda_llm_gold_in_tpdm_llm_gold: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpdm_llm_gold_out_tpda_llm_gold>;
+ };
+ };
+ };
+ };
+
+ tpdm_llm_gold: tpdm@78b0000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x78b0000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-llm-gold";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_llm_gold_out_tpda_llm_gold: endpoint {
+ remote-endpoint =
+ <&tpda_llm_gold_in_tpdm_llm_gold>;
+ };
+ };
+ };
+
+ funnel_dl_mm: funnel@6c0b000 {
+ compatible = "arm,primecell";
+ arm,primecell-periphid = <0x0003b908>;
+
+ reg = <0x6c0b000 0x1000>;
+ reg-names = "funnel-base";
+
+ coresight-name = "coresight-funnel-dl-mm";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "apb_pclk", "core_a_clk";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ funnel_dl_mm_out_tpda: endpoint {
+ remote-endpoint =
+ <&tpda_in_funnel_dl_mm>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ funnel_dl_mm_in_tpdm_mm: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpdm_mm_out_funnel_dl_mm>;
+ };
+ };
+ };
+ };
+
+ tpdm_mm: tpdm@6c08000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x6c08000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-mm";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_mm_out_funnel_dl_mm: endpoint {
+ remote-endpoint = <&funnel_dl_mm_in_tpdm_mm>;
+ };
+ };
+ };
+
+ funnel_ddr_0: funnel@69e2000 {
+ compatible = "arm,primecell";
+ arm,primecell-periphid = <0x0003b908>;
+
+ reg = <0x69e2000 0x1000>;
+ reg-names = "funnel-base";
+
+ coresight-name = "coresight-funnel-ddr-0";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "apb_pclk", "core_a_clk";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ funnel_ddr_0_out_tpda: endpoint {
+ remote-endpoint =
+ <&tpda_in_funnel_ddr_0>;
+ };
+ };
+
+ port@1 {
+ reg = <0>;
+ funnel_ddr_0_in_tpdm_ddr: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpdm_ddr_out_funnel_ddr_0>;
+ };
+ };
+ };
+ };
+
+ tpdm_ddr: tpdm@69e0000 {
+ compatible = "qcom,coresight-tpdm";
+ reg = <0x69e0000 0x1000>;
+ reg-names = "tpdm-base";
+
+ coresight-name = "coresight-tpdm-ddr";
+
+ clocks = <&clock_gcc RPMH_QDSS_CLK>,
+ <&clock_gcc RPMH_QDSS_A_CLK>;
+ clock-names = "core_clk", "core_a_clk";
+
+ port {
+ tpdm_ddr_out_funnel_ddr_0: endpoint {
+ remote-endpoint = <&funnel_ddr_0_in_tpdm_ddr>;
+ };
+ };
+ };
+
tpdm_pimem: tpdm@6850000 {
compatible = "qcom,coresight-tpdm";
reg = <0x6850000 0x1000>;
@@ -566,25 +1034,6 @@
};
};
-
- tpdm_dcc: tpdm@6870000 {
- compatible = "qcom,coresight-tpdm";
- reg = <0x6870000 0x1000>;
- reg-names = "tpdm-base";
-
- coresight-name = "coresight-tpdm-dcc";
-
- clocks = <&clock_gcc RPMH_QDSS_CLK>,
- <&clock_gcc RPMH_QDSS_A_CLK>;
- clock-names = "core_clk", "core_a_clk";
-
- port {
- tpdm_dcc_out_tpda: endpoint {
- remote-endpoint = <&tpda_in_tpdm_dcc>;
- };
- };
- };
-
tpdm_vsense: tpdm@6840000 {
compatible = "qcom,coresight-tpdm";
reg = <0x6840000 0x1000>;
@@ -1129,13 +1578,40 @@
};
port@2 {
- reg = <1>;
+ reg = <2>;
funnel_apss_merg_in_tpda_olc: endpoint {
slave-mode;
remote-endpoint =
<&tpda_olc_out_funnel_apss_merg>;
};
};
+
+ port@3 {
+ reg = <4>;
+ funnel_apss_merg_in_tpda_apss: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpda_apss_out_funnel_apss_merg>;
+ };
+ };
+
+ port@4 {
+ reg = <5>;
+ funnel_apss_merg_in_tpda_llm_silver: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpda_llm_silver_out_funnel_apss_merg>;
+ };
+ };
+
+ port@5 {
+ reg = <6>;
+ funnel_apss_merg_in_tpda_llm_gold: endpoint {
+ slave-mode;
+ remote-endpoint =
+ <&tpda_llm_gold_out_funnel_apss_merg>;
+ };
+ };
};
};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi b/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi
new file mode 100644
index 0000000..a6efb50
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi
@@ -0,0 +1,287 @@
+/* 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.
+ */
+
+&soc {
+
+ msm_bus: qcom,kgsl-busmon{
+ label = "kgsl-busmon";
+ compatible = "qcom,kgsl-busmon";
+ };
+
+ gpubw: qcom,gpubw {
+ compatible = "qcom,devbw";
+ governor = "bw_vbif";
+ qcom,src-dst-ports = <26 512>;
+ /*
+ * active-only flag is used while registering the bus
+ * governor.It helps release the bus vote when the CPU
+ * subsystem is inactiv3
+ */
+ qcom,active-only;
+ qcom,bw-tbl =
+ < 0 /* off */ >,
+ < 762 /* 100 MHz */ >,
+ < 1144 /* 150 MHz */ >,
+ < 1525 /* 200 MHz */ >,
+ < 2288 /* 300 MHz */ >,
+ < 3143 /* 412 MHz */ >,
+ < 4173 /* 547 MHz */ >,
+ < 5195 /* 681 MHz */ >,
+ < 5859 /* 768 MHz */ >,
+ < 7759 /* 1017 MHz */ >,
+ < 9887 /* 1296 MHz */ >,
+ < 11863 /* 1555 MHz */ >,
+ < 13763 /* 1804 MHz */ >;
+ };
+
+ msm_gpu: qcom,kgsl-3d0@5000000 {
+ label = "kgsl-3d0";
+ compatible = "qcom,kgsl-3d0", "qcom,kgsl-3d";
+ status = "ok";
+ reg = <0x5000000 0x40000>;
+ reg-names = "kgsl_3d0_reg_memory";
+ interrupts = <0 300 0>;
+ interrupt-names = "kgsl_3d0_irq";
+ qcom,id = <0>;
+
+ qcom,chipid = <0x06030000>;
+
+ qcom,initial-pwrlevel = <2>;
+
+ qcom,gpu-quirk-hfi-use-reg;
+ qcom,gpu-quirk-two-pass-use-wfi;
+
+ qcom,idle-timeout = <100000000>; //msecs
+ qcom,no-nap;
+
+ qcom,highest-bank-bit = <15>;
+
+ qcom,min-access-length = <32>;
+
+ qcom,ubwc-mode = <2>;
+
+ qcom,snapshot-size = <1048576>; //bytes
+
+ qcom,gpu-qdss-stm = <0x161c0000 0x40000>; // base addr, size
+
+ qcom,tsens-name = "tsens_tz_sensor12";
+
+ clocks = <&clock_gfx GPU_CC_GX_GFX3D_CLK>,
+ <&clock_gcc GCC_GPU_CFG_AHB_CLK>,
+ <&clock_gpucc GPU_CC_CXO_CLK>,
+ <&clock_gcc GCC_DDRSS_GPU_AXI_CLK>,
+ <&clock_gcc GCC_GPU_MEMNOC_GFX_CLK>;
+
+ clock-names = "core_clk", "iface_clk", "rbbmtimer_clk",
+ "mem_clk", "mem_iface_clk";
+
+ qcom,isense-clk-on-level = <1>;
+
+ /* Bus Scale Settings */
+ qcom,gpubw-dev = <&gpubw>;
+ qcom,bus-control;
+ qcom,msm-bus,name = "grp3d";
+ qcom,msm-bus,num-cases = <13>;
+ qcom,msm-bus,num-paths = <1>;
+ qcom,msm-bus,vectors-KBps =
+ <26 512 0 0>,
+
+ <26 512 0 800000>, // 1 bus=100
+ <26 512 0 1200000>, // 2 bus=150
+ <26 512 0 1600000>, // 3 bus=200
+ <26 512 0 2400000>, // 4 bus=300
+ <26 512 0 3296000>, // 5 bus=412
+ <26 512 0 4376000>, // 6 bus=547
+ <26 512 0 5448000>, // 7 bus=681
+ <26 512 0 6144000>, // 8 bus=768
+ <26 512 0 8136000>, // 9 bus=1017
+ <26 512 0 10368000>, // 10 bus=1296
+ <26 512 0 12440000>, // 11 bus=1555
+ <26 512 0 14432000>; // 12 bus=1804
+
+ /* GDSC regulator names */
+ regulator-names = "vddcx", "vdd";
+ /* GDSC oxili regulators */
+ vddcx-supply = <&gpu_cx_gdsc>;
+ vdd-supply = <&gpu_gx_gdsc>;
+
+ /* GPU related llc slices */
+ cache-slice-names = "gpu", "gpuhtw";
+ cache-slices = <&llcc 12>, <&llcc 11>;
+
+ /* GPU Mempools */
+ qcom,gpu-mempools {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "qcom,gpu-mempools";
+
+ /* 4K Page Pool configuration */
+ qcom,gpu-mempool@0 {
+ reg = <0>;
+ qcom,mempool-page-size = <4096>;
+ qcom,mempool-reserved = <2048>;
+ qcom,mempool-allocate;
+ };
+ /* 8K Page Pool configuration */
+ qcom,gpu-mempool@1 {
+ reg = <1>;
+ qcom,mempool-page-size = <8192>;
+ qcom,mempool-reserved = <1024>;
+ qcom,mempool-allocate;
+ };
+ /* 64K Page Pool configuration */
+ qcom,gpu-mempool@2 {
+ reg = <2>;
+ qcom,mempool-page-size = <65536>;
+ qcom,mempool-reserved = <256>;
+ };
+ /* 1M Page Pool configuration */
+ qcom,gpu-mempool@3 {
+ reg = <3>;
+ qcom,mempool-page-size = <1048576>;
+ qcom,mempool-reserved = <32>;
+ };
+ };
+
+ /* Power levels */
+ qcom,gpu-pwrlevels {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ compatible = "qcom,gpu-pwrlevels";
+
+ qcom,gpu-pwrlevel@0 {
+ reg = <0>;
+ qcom,gpu-freq = <548000000>;
+ qcom,bus-freq = <12>;
+ qcom,bus-min = <11>;
+ qcom,bus-max = <12>;
+ };
+
+
+ qcom,gpu-pwrlevel@1 {
+ reg = <1>;
+ qcom,gpu-freq = <425000000>;
+ qcom,bus-freq = <7>;
+ qcom,bus-min = <6>;
+ qcom,bus-max = <8>;
+ };
+
+ qcom,gpu-pwrlevel@2 {
+ reg = <2>;
+ qcom,gpu-freq = <280000000>;
+ qcom,bus-freq = <4>;
+ qcom,bus-min = <3>;
+ qcom,bus-max = <5>;
+ };
+
+ qcom,gpu-pwrlevel@3 {
+ reg = <3>;
+ qcom,gpu-freq = <27000000>;
+ qcom,bus-freq = <0>;
+ qcom,bus-min = <0>;
+ qcom,bus-max = <0>;
+ };
+ };
+
+ };
+
+ kgsl_msm_iommu: qcom,kgsl-iommu {
+ compatible = "qcom,kgsl-smmu-v2";
+
+ reg = <0x05040000 0x10000>;
+ qcom,protect = <0x40000 0x10000>;
+ qcom,micro-mmu-control = <0x6000>;
+
+ clocks =<&clock_gcc GCC_GPU_CFG_AHB_CLK>,
+ <&clock_gcc GCC_DDRSS_GPU_AXI_CLK>,
+ <&clock_gcc GCC_GPU_MEMNOC_GFX_CLK>;
+
+ clock-names = "iface_clk", "mem_clk", "mem_iface_clk";
+
+ qcom,secure_align_mask = <0xfff>;
+ qcom,global_pt;
+
+ gfx3d_user: gfx3d_user {
+ compatible = "qcom,smmu-kgsl-cb";
+ label = "gfx3d_user";
+ iommus = <&kgsl_smmu 0>;
+ qcom,gpu-offset = <0x48000>;
+ };
+
+ gfx3d_secure: gfx3d_secure {
+ compatible = "qcom,smmu-kgsl-cb";
+ iommus = <&kgsl_smmu 2>;
+ };
+ };
+
+ gmu: qcom,gmu {
+ label = "kgsl-gmu";
+ compatible = "qcom,gpu-gmu";
+
+ reg = <0x506a000 0x26000>, <0xb200000 0x300000>;
+ reg-names = "kgsl_gmu_reg", "kgsl_gmu_pdc_reg";
+
+ interrupts = <0 304 0>, <0 305 0>;
+ interrupt-names = "kgsl_hfi_irq", "kgsl_gmu_irq";
+
+ qcom,msm-bus,name = "cnoc";
+ qcom,msm-bus,num-cases = <2>;
+ qcom,msm-bus,num-paths = <1>;
+ qcom,msm-bus,vectors-KBps =
+ <26 10036 0 0>, // CNOC off
+ <26 10036 0 100>; // CNOC on
+
+ regulator-names = "vddcx", "vdd";
+ vddcx-supply = <&gpu_cx_gdsc>;
+ vdd-supply = <&gpu_gx_gdsc>;
+
+
+ clocks = <&clock_gpucc GPU_CC_CX_GMU_CLK>,
+ <&clock_gcc GCC_GPU_CFG_AHB_CLK>,
+ <&clock_gpucc GPU_CC_CXO_CLK>,
+ <&clock_gcc GCC_DDRSS_GPU_AXI_CLK>,
+ <&clock_gcc GCC_GPU_MEMNOC_GFX_CLK>;
+
+ clock-names = "gmu_clk", "ahb_clk", "cxo_clk",
+ "axi_clk", "memnoc_clk";
+
+ qcom,gmu-pwrlevels {
+ compatible = "qcom,gmu-pwrlevels";
+
+ qcom,gmu-pwrlevel@0 {
+ reg = <0>;
+ qcom,gmu-freq = <400000000>;
+ };
+
+ qcom,gmu-pwrlevel@1 {
+ reg = <1>;
+ qcom,gmu-freq = <19200000>;
+ };
+
+ qcom,gmu-pwrlevel@2 {
+ reg = <2>;
+ qcom,gmu-freq = <0>;
+ };
+ };
+
+ gmu_user: gmu_user {
+ compatible = "qcom,smmu-gmu-user-cb";
+ iommus = <&kgsl_smmu 4>;
+ };
+
+ gmu_kernel: gmu_kernel {
+ compatible = "qcom,smmu-gmu-kernel-cb";
+ iommus = <&kgsl_smmu 5>;
+ };
+ };
+};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-mtp.dtsi b/arch/arm64/boot/dts/qcom/sdm845-mtp.dtsi
index 734b6a9..28afd55 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-mtp.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-mtp.dtsi
@@ -103,3 +103,28 @@
status = "ok";
};
+
+&pmi8998_flash2 {
+ pinctrl-names = "led_enable", "led_disable";
+ pinctrl-0 = <&flash_led3_front_en>;
+ pinctrl-1 = <&flash_led3_front_dis>;
+};
+
+&pmi8998_torch2 {
+ pinctrl-names = "led_enable", "led_disable";
+ pinctrl-0 = <&flash_led3_front_en>;
+ pinctrl-1 = <&flash_led3_front_dis>;
+};
+
+/{
+ mtp_batterydata: qcom,battery-data {
+ qcom,batt-id-range-pct = <15>;
+ #include "fg-gen3-batterydata-itech-3000mah.dtsi"
+ #include "fg-gen3-batterydata-ascent-3450mah.dtsi"
+ #include "fg-gen3-batterydata-demo-6000mah.dtsi"
+ };
+};
+
+&pmi8998_fg {
+ qcom,battery-data = <&mtp_batterydata>;
+};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-pinctrl.dtsi b/arch/arm64/boot/dts/qcom/sdm845-pinctrl.dtsi
index 19c6543..f300684 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-pinctrl.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-pinctrl.dtsi
@@ -66,6 +66,23 @@
};
};
+ flash_led3_front {
+ flash_led3_front_en: flash_led3_front_en {
+ mux {
+ pins = "gpio21";
+ drive_strength = <2>;
+ output-high;
+ };
+ };
+
+ flash_led3_front_dis: flash_led3_front_dis {
+ mux {
+ pins = "gpio21";
+ drive_strength = <2>;
+ output-low;
+ };
+ };
+ };
wcd9xxx_intr {
wcd_intr_default: wcd_intr_default{
diff --git a/arch/arm64/boot/dts/qcom/sdm845-qrd.dtsi b/arch/arm64/boot/dts/qcom/sdm845-qrd.dtsi
index 6ea92ee..ba725cb 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-qrd.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-qrd.dtsi
@@ -9,3 +9,15 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
+
+/{
+ qrd_batterydata: qcom,battery-data {
+ qcom,batt-id-range-pct = <15>;
+ #include "fg-gen3-batterydata-itech-3000mah.dtsi"
+ #include "fg-gen3-batterydata-ascent-3450mah.dtsi"
+ };
+};
+
+&pmi8998_fg {
+ qcom,battery-data = <&qrd_batterydata>;
+};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-regulator.dtsi b/arch/arm64/boot/dts/qcom/sdm845-regulator.dtsi
index ca325c0..aadc17e 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-regulator.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-regulator.dtsi
@@ -391,7 +391,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l1: regulator-l1 {
regulator-name = "pm8998_l1";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -409,7 +409,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 30000>;
pm8998_l2: regulator-l2 {
regulator-name = "pm8998_l2";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -427,7 +427,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l3: regulator-l3 {
regulator-name = "pm8998_l3";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -458,7 +458,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l5: regulator-l5 {
regulator-name = "pm8998_l5";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -476,7 +476,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l6: regulator-l6 {
regulator-name = "pm8998_l6";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -494,7 +494,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l7: regulator-l7 {
regulator-name = "pm8998_l7";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -512,7 +512,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l8: regulator-l8 {
regulator-name = "pm8998_l8";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -530,7 +530,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l9: regulator-l9 {
regulator-name = "pm8998_l9";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -548,7 +548,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l10: regulator-l10 {
regulator-name = "pm8998_l10";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -566,7 +566,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l11: regulator-l11 {
regulator-name = "pm8998_l11";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -584,7 +584,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l12: regulator-l12 {
regulator-name = "pm8998_l12";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -638,7 +638,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l15: regulator-l15 {
regulator-name = "pm8998_l15";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -656,7 +656,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l16: regulator-l16 {
regulator-name = "pm8998_l16";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -674,7 +674,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l17: regulator-l17 {
regulator-name = "pm8998_l17";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -692,7 +692,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l18: regulator-l18 {
regulator-name = "pm8998_l18";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -710,7 +710,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l19: regulator-l19 {
regulator-name = "pm8998_l19";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -782,7 +782,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l23: regulator-l23 {
regulator-name = "pm8998_l23";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -819,7 +819,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l25: regulator-l25 {
regulator-name = "pm8998_l25";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -837,7 +837,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l26: regulator-l26 {
regulator-name = "pm8998_l26";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -868,7 +868,7 @@
qcom,supported-modes =
<RPMH_REGULATOR_MODE_LDO_LPM
RPMH_REGULATOR_MODE_LDO_HPM>;
- qcom,mode-threshold-currents = <0 10000>;
+ qcom,mode-threshold-currents = <0 1>;
pm8998_l28: regulator-l28 {
regulator-name = "pm8998_l28";
qcom,set = <RPMH_REGULATOR_SET_ALL>;
@@ -955,3 +955,13 @@
};
};
};
+
+&pmi8998_charger {
+ smb2_vbus: qcom,smb2-vbus {
+ regulator-name = "smb2-vbus";
+ };
+
+ smb2_vconn: qcom,smb2-vconn {
+ regulator-name = "smb2-vconn";
+ };
+};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-rumi.dtsi b/arch/arm64/boot/dts/qcom/sdm845-rumi.dtsi
index 80f34bf..5625531 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-rumi.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-rumi.dtsi
@@ -147,3 +147,7 @@
spm-level = <0>;
status = "ok";
};
+
+&pmi8998_charger {
+ qcom,suspend-input;
+};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi b/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
index ab4c253..d99e6de 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
@@ -13,7 +13,7 @@
&soc {
mdss_mdp: qcom,mdss_mdp@ae00000 {
compatible = "qcom,sde-kms";
- reg = <0x0ae00000 0x81a24>,
+ reg = <0x0ae00000 0x81d40>,
<0x0aeb0000 0x2008>;
reg-names = "mdp_phys",
"vbif_phys";
@@ -357,18 +357,17 @@
reg-names = "dsi_phy";
gdsc-supply = <&mdss_core_gdsc>;
vdda-1p2-supply = <&pm8998_l26>;
- qcom,platform-strength-ctrl = [ff 06
- ff 06
- ff 06
- ff 00];
- qcom,platform-regulator-settings = [1d
- 1d 1d 1d 1d];
- qcom,platform-lane-config = [00 00 10 0f
- 00 00 10 0f
- 00 00 10 0f
- 00 00 10 0f
- 00 00 10 8f];
-
+ qcom,platform-strength-ctrl = [55 03
+ 55 03
+ 55 03
+ 55 03
+ 55 00];
+ qcom,platform-lane-config = [00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 80];
+ qcom,platform-regulator-settings = [1d 1d 1d 1d 1d];
qcom,phy-supply-entries {
#address-cells = <1>;
#size-cells = <0>;
@@ -392,18 +391,17 @@
reg-names = "dsi_phy";
gdsc-supply = <&mdss_core_gdsc>;
vdda-1p2-supply = <&pm8998_l26>;
- qcom,platform-strength-ctrl = [ff 06
- ff 06
- ff 06
- ff 00];
- qcom,platform-regulator-settings = [1d
- 1d 1d 1d 1d];
- qcom,platform-lane-config = [00 00 10 0f
- 00 00 10 0f
- 00 00 10 0f
- 00 00 10 0f
- 00 00 10 8f];
-
+ qcom,platform-strength-ctrl = [55 03
+ 55 03
+ 55 03
+ 55 03
+ 55 00];
+ qcom,platform-regulator-settings = [1d 1d 1d 1d 1d];
+ qcom,platform-lane-config = [00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 00
+ 00 00 00 80];
qcom,phy-supply-entries {
#address-cells = <1>;
#size-cells = <0>;
diff --git a/arch/arm64/boot/dts/qcom/sdm845-sim.dtsi b/arch/arm64/boot/dts/qcom/sdm845-sim.dtsi
index 0f94d812..a03148d 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-sim.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-sim.dtsi
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -9,3 +9,7 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
+
+&pmi8998_charger {
+ qcom,suspend-input;
+};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-usb.dtsi b/arch/arm64/boot/dts/qcom/sdm845-usb.dtsi
index 7f090ad..2e66657 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-usb.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-usb.dtsi
@@ -20,6 +20,7 @@
<0x088ee000 0x400>;
reg-names = "core_base", "ahb2phy_base";
iommus = <&apps_smmu 0x740>;
+ qcom,smmu-s1-bypass;
#address-cells = <1>;
#size-cells = <1>;
ranges;
@@ -31,7 +32,7 @@
qcom,usb-dbm = <&dbm_1p5>;
qcom,dwc-usb3-msm-tx-fifo-size = <21288>;
qcom,num-gsi-evt-buffs = <0x3>;
- extcon = <0>, <0>, <&eud>;
+ extcon = <&pmi8998_pdphy>, <&pmi8998_pdphy>, <&eud>;
clocks = <&clock_gcc GCC_USB30_PRIM_MASTER_CLK>,
<&clock_gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>,
@@ -76,16 +77,26 @@
vdda33-supply = <&pm8998_l24>;
qcom,vdd-voltage-level = <0 880000 880000>;
qcom,qusb-phy-init-seq =
- /* <value reg_offset> */
- <0x03 0x04 /* PLL_ANALOG_CONTROLS_TWO */
- 0x7c 0x18c /* PLL_CLOCK_INVERTERS */
- 0x80 0x2c /* PLL_CMODE */
- 0x0a 0x184 /* PLL_LOCK_DELAY */
- 0x19 0xb4 /* PLL_DIGITAL_TIMERS_TWO */
- 0xa5 0x240 /* TUNE1 */
- 0x09 0x244 /* TUNE2 */
- 0x00 0x220 /* IMP_CTRL1 */
- 0x58 0x224>; /* IMP_CTRL2 */
+ /* <value reg_offset> */
+ <0x23 0x210 /* PWR_CTRL1 */
+ 0x03 0x04 /* PLL_ANALOG_CONTROLS_TWO */
+ 0x7c 0x18c /* PLL_CLOCK_INVERTERS */
+ 0x80 0x2c /* PLL_CMODE */
+ 0x0a 0x184 /* PLL_LOCK_DELAY */
+ 0x19 0xb4 /* PLL_DIGITAL_TIMERS_TWO */
+ 0x40 0x194 /* PLL_BIAS_CONTROL_1 */
+ 0x20 0x198 /* PLL_BIAS_CONTROL_2 */
+ 0x21 0x214 /* PWR_CTRL2 */
+ 0x00 0x220 /* IMP_CTRL1 */
+ 0x58 0x224 /* IMP_CTRL2 */
+ 0x32 0x240 /* TUNE1 */
+ 0x29 0x244 /* TUNE2 */
+ 0xca 0x248 /* TUNE3 */
+ 0x04 0x24c /* TUNE4 */
+ 0x00 0x250 /* TUNE5 */
+ 0x00 0x23c /* CHG_CTRL2 */
+ 0x22 0x210>; /* PWR_CTRL1 */
+
phy_type= "utmi";
clocks = <&clock_rpmh RPMH_CXO_CLK>,
<&clock_gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>;
@@ -112,6 +123,7 @@
<0x088ee000 0x400>;
reg-names = "core_base", "ahb2phy_base";
iommus = <&apps_smmu 0x760>;
+ qcom,smmu-s1-bypass;
#address-cells = <1>;
#size-cells = <1>;
ranges;
@@ -165,16 +177,26 @@
vdda33-supply = <&pm8998_l24>;
qcom,vdd-voltage-level = <0 880000 880000>;
qcom,qusb-phy-init-seq =
- /* <value reg_offset> */
- <0x03 0x04 /* PLL_ANALOG_CONTROLS_TWO */
- 0x7c 0x18c /* PLL_CLOCK_INVERTERS */
- 0x80 0x2c /* PLL_CMODE */
- 0x0a 0x184 /* PLL_LOCK_DELAY */
- 0x19 0xb4 /* PLL_DIGITAL_TIMERS_TWO */
- 0xa5 0x240 /* TUNE1 */
- 0x09 0x244 /* TUNE2 */
- 0x00 0x220 /* IMP_CTRL1 */
- 0x58 0x224>; /* IMP_CTRL2 */
+ /* <value reg_offset> */
+ <0x23 0x210 /* PWR_CTRL1 */
+ 0x03 0x04 /* PLL_ANALOG_CONTROLS_TWO */
+ 0x7c 0x18c /* PLL_CLOCK_INVERTERS */
+ 0x80 0x2c /* PLL_CMODE */
+ 0x0a 0x184 /* PLL_LOCK_DELAY */
+ 0x19 0xb4 /* PLL_DIGITAL_TIMERS_TWO */
+ 0x40 0x194 /* PLL_BIAS_CONTROL_1 */
+ 0x20 0x198 /* PLL_BIAS_CONTROL_2 */
+ 0x21 0x214 /* PWR_CTRL2 */
+ 0x00 0x220 /* IMP_CTRL1 */
+ 0x58 0x224 /* IMP_CTRL2 */
+ 0x32 0x240 /* TUNE1 */
+ 0x29 0x244 /* TUNE2 */
+ 0xca 0x248 /* TUNE3 */
+ 0x04 0x24c /* TUNE4 */
+ 0x00 0x250 /* TUNE5 */
+ 0x00 0x23c /* CHG_CTRL2 */
+ 0x22 0x210>; /* PWR_CTRL1 */
+
phy_type= "utmi";
clocks = <&clock_rpmh RPMH_CXO_CLK>,
<&clock_gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>;
diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
index 18f0186..f591cca 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -68,7 +68,7 @@
};
};
- CPU1: cpu@1 {
+ CPU1: cpu@100 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x100>;
@@ -76,24 +76,24 @@
efficiency = <1024>;
cache-size = <0x8000>;
cpu-release-addr = <0x0 0x90000000>;
- next-level-cache = <&L2_1>;
- L2_1: l2-cache {
+ next-level-cache = <&L2_100>;
+ L2_100: l2-cache {
compatible = "arm,arch-cache";
cache-size = <0x20000>;
cache-level = <2>;
next-level-cache = <&L3_0>;
};
- L1_I_1: l1-icache {
+ L1_I_100: l1-icache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x9000>;
};
- L1_D_1: l1-dcache {
+ L1_D_100: l1-dcache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x9000>;
};
};
- CPU2: cpu@2 {
+ CPU2: cpu@200 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x200>;
@@ -101,24 +101,24 @@
efficiency = <1024>;
cache-size = <0x8000>;
cpu-release-addr = <0x0 0x90000000>;
- next-level-cache = <&L2_2>;
- L2_2: l2-cache {
+ next-level-cache = <&L2_200>;
+ L2_200: l2-cache {
compatible = "arm,arch-cache";
cache-size = <0x20000>;
cache-level = <2>;
next-level-cache = <&L3_0>;
};
- L1_I_2: l1-icache {
+ L1_I_200: l1-icache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x9000>;
};
- L1_D_2: l1-dcache {
+ L1_D_200: l1-dcache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x9000>;
};
};
- CPU3: cpu@3 {
+ CPU3: cpu@300 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x300>;
@@ -126,24 +126,24 @@
efficiency = <1024>;
cache-size = <0x8000>;
cpu-release-addr = <0x0 0x90000000>;
- next-level-cache = <&L2_3>;
- L2_3: l2-cache {
+ next-level-cache = <&L2_300>;
+ L2_300: l2-cache {
compatible = "arm,arch-cache";
cache-size = <0x20000>;
cache-level = <2>;
next-level-cache = <&L3_0>;
};
- L1_I_3: l1-icache {
+ L1_I_300: l1-icache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x9000>;
};
- L1_D_3: l1-dcache {
+ L1_D_300: l1-dcache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x9000>;
};
};
- CPU4: cpu@100 {
+ CPU4: cpu@400 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x400>;
@@ -151,24 +151,24 @@
efficiency = <1740>;
cache-size = <0x20000>;
cpu-release-addr = <0x0 0x90000000>;
- next-level-cache = <&L2_4>;
- L2_4: l2-cache {
+ next-level-cache = <&L2_400>;
+ L2_400: l2-cache {
compatible = "arm,arch-cache";
cache-size = <0x40000>;
cache-level = <2>;
next-level-cache = <&L3_0>;
};
- L1_I_100: l1-icache {
+ L1_I_400: l1-icache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x12000>;
};
- L1_D_100: l1-dcache {
+ L1_D_400: l1-dcache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x12000>;
};
};
- CPU5: cpu@101 {
+ CPU5: cpu@500 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x500>;
@@ -176,24 +176,24 @@
efficiency = <1740>;
cache-size = <0x20000>;
cpu-release-addr = <0x0 0x90000000>;
- next-level-cache = <&L2_5>;
- L2_5: l2-cache {
+ next-level-cache = <&L2_500>;
+ L2_500: l2-cache {
compatible = "arm,arch-cache";
cache-size = <0x40000>;
cache-level = <2>;
next-level-cache = <&L3_0>;
};
- L1_I_101: l1-icache {
+ L1_I_500: l1-icache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x12000>;
};
- L1_D_101: l1-dcache {
+ L1_D_500: l1-dcache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x12000>;
};
};
- CPU6: cpu@102 {
+ CPU6: cpu@600 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x600>;
@@ -201,24 +201,24 @@
efficiency = <1740>;
cache-size = <0x20000>;
cpu-release-addr = <0x0 0x90000000>;
- next-level-cache = <&L2_6>;
- L2_6: l2-cache {
+ next-level-cache = <&L2_600>;
+ L2_600: l2-cache {
compatible = "arm,arch-cache";
cache-size = <0x40000>;
cache-level = <2>;
next-level-cache = <&L3_0>;
};
- L1_I_102: l1-icache {
+ L1_I_600: l1-icache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x12000>;
};
- L1_D_102: l1-dcache {
+ L1_D_600: l1-dcache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x12000>;
};
};
- CPU7: cpu@103 {
+ CPU7: cpu@700 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0 0x700>;
@@ -226,18 +226,18 @@
efficiency = <1740>;
cache-size = <0x20000>;
cpu-release-addr = <0x0 0x90000000>;
- next-level-cache = <&L2_7>;
- L2_7: l2-cache {
+ next-level-cache = <&L2_700>;
+ L2_700: l2-cache {
compatible = "arm,arch-cache";
cache-size = <0x40000>;
cache-level = <2>;
next-level-cache = <&L3_0>;
};
- L1_I_103: l1-icache {
+ L1_I_700: l1-icache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x12000>;
};
- L1_D_103: l1-dcache {
+ L1_D_700: l1-dcache {
compatible = "arm,arch-cache";
qcom,dump-size = <0x12000>;
};
@@ -308,37 +308,61 @@
pil_modem_mem: modem_region@8b000000 {
compatible = "removed-dma-pool";
no-map;
- reg = <0 0x8b000000 0 0x6e00000>;
+ reg = <0 0x8b000000 0 0x7300000>;
};
- pil_video_mem: pil_video_region@91e00000 {
+ pil_video_mem: pil_video_region@92300000 {
compatible = "removed-dma-pool";
no-map;
- reg = <0 0x91e00000 0 0x500000>;
+ reg = <0 0x92300000 0 0x500000>;
};
- pil_cdsp_mem: cdsp_regions@92300000 {
+ pil_cdsp_mem: cdsp_regions@92800000 {
compatible = "removed-dma-pool";
no-map;
- reg = <0 0x92300000 0 0x800000>;
+ reg = <0 0x92800000 0 0x800000>;
};
- pil_adsp_mem: pil_adsp_region@92b00000 {
+ pil_adsp_mem: pil_adsp_region@93000000 {
compatible = "removed-dma-pool";
no-map;
- reg = <0 0x92b00000 0 0x1a00000>;
+ reg = <0 0x93000000 0 0x1a00000>;
};
- pil_slpi_mem: pil_slpi_region@94500000 {
+ pil_mba_mem: pil_mba_region@0x94a00000 {
compatible = "removed-dma-pool";
no-map;
- reg = <0 0x94500000 0 0xf00000>;
+ reg = <0 0x94a00000 0 0x200000>;
};
- pil_spss_mem: spss_region@95400000 {
+ pil_slpi_mem: pil_slpi_region@94c00000 {
compatible = "removed-dma-pool";
no-map;
- reg = <0 0x95400000 0 0x700000>;
+ reg = <0 0x94c00000 0 0x1400000>;
+ };
+
+ pil_ipa_fw_mem: pil_ipa_fw_region@96000000 {
+ compatible = "removed-dma-pool";
+ no-map;
+ reg = <0 0x96000000 0 0x10000>;
+ };
+
+ pil_ipa_gsi_mem: pil_ipa_gsi_region@96010000 {
+ compatible = "removed-dma-pool";
+ no-map;
+ reg = <0 0x96010000 0 0x5000>;
+ };
+
+ pil_gpu_mem: pil_gpu_region@96015000 {
+ compatible = "removed-dma-pool";
+ no-map;
+ reg = <0 0x96015000 0 0x1000>;
+ };
+
+ pil_spss_mem: spss_region@96100000 {
+ compatible = "removed-dma-pool";
+ no-map;
+ reg = <0 0x96100000 0 0x100000>;
};
adsp_mem: adsp_region {
@@ -346,7 +370,7 @@
alloc-ranges = <0 0x00000000 0 0xffffffff>;
reusable;
alignment = <0 0x400000>;
- size = <0 0x800000>;
+ size = <0 0xc00000>;
};
qseecom_mem: qseecom_region {
@@ -707,6 +731,12 @@
< 1651200 960000 >;
};
+ cpu_pmu: cpu-pmu {
+ compatible = "arm,armv8-pmuv3";
+ qcom,irq-is-percpu;
+ interrupts = <1 5 4>;
+ };
+
clock_gcc: qcom,gcc@100000 {
compatible = "qcom,gcc-sdm845", "syscon";
reg = <0x100000 0x1f0000>;
@@ -750,6 +780,7 @@
reg = <0x5090000 0x9000>;
reg-names = "cc_base";
vdd_cx-supply = <&pm8998_s9_level>;
+ qcom,gpu_cc_gmu_clk_src-opp-handle = <&gmu>;
#clock-cells = <1>;
#reset-cells = <1>;
};
@@ -760,6 +791,7 @@
reg-names = "cc_base";
vdd_gfx-supply = <&pm8005_s1_level>;
vdd_mx-supply = <&pm8998_s6_level>;
+ qcom,gpu_cc_gx_gfx3d_clk_src-opp-handle = <&msm_gpu>;
#clock-cells = <1>;
#reset-cells = <1>;
};
@@ -1329,6 +1361,11 @@
qcom,rtb-size = <0x100000>;
};
+ qcom,msm-cdsp-loader {
+ compatible = "qcom,cdsp-loader";
+ qcom,proc-img-to-load = "cdsp";
+ };
+
qcom,msm_fastrpc {
compatible = "qcom,msm-fastrpc-compute";
@@ -1438,31 +1475,31 @@
qcom,dump-id = <0x60>;
};
qcom,l1_i_cache1 {
- qcom,dump-node = <&L1_I_1>;
+ qcom,dump-node = <&L1_I_100>;
qcom,dump-id = <0x61>;
};
qcom,l1_i_cache2 {
- qcom,dump-node = <&L1_I_2>;
+ qcom,dump-node = <&L1_I_200>;
qcom,dump-id = <0x62>;
};
qcom,l1_i_cache3 {
- qcom,dump-node = <&L1_I_3>;
+ qcom,dump-node = <&L1_I_300>;
qcom,dump-id = <0x63>;
};
qcom,l1_i_cache100 {
- qcom,dump-node = <&L1_I_100>;
+ qcom,dump-node = <&L1_I_400>;
qcom,dump-id = <0x64>;
};
qcom,l1_i_cache101 {
- qcom,dump-node = <&L1_I_101>;
+ qcom,dump-node = <&L1_I_500>;
qcom,dump-id = <0x65>;
};
qcom,l1_i_cache102 {
- qcom,dump-node = <&L1_I_102>;
+ qcom,dump-node = <&L1_I_600>;
qcom,dump-id = <0x66>;
};
qcom,l1_i_cache103 {
- qcom,dump-node = <&L1_I_103>;
+ qcom,dump-node = <&L1_I_700>;
qcom,dump-id = <0x67>;
};
qcom,l1_d_cache0 {
@@ -1470,31 +1507,31 @@
qcom,dump-id = <0x80>;
};
qcom,l1_d_cache1 {
- qcom,dump-node = <&L1_D_1>;
+ qcom,dump-node = <&L1_D_100>;
qcom,dump-id = <0x81>;
};
qcom,l1_d_cache2 {
- qcom,dump-node = <&L1_D_2>;
+ qcom,dump-node = <&L1_D_200>;
qcom,dump-id = <0x82>;
};
qcom,l1_d_cache3 {
- qcom,dump-node = <&L1_D_3>;
+ qcom,dump-node = <&L1_D_300>;
qcom,dump-id = <0x83>;
};
qcom,l1_d_cache100 {
- qcom,dump-node = <&L1_D_100>;
+ qcom,dump-node = <&L1_D_400>;
qcom,dump-id = <0x84>;
};
qcom,l1_d_cache101 {
- qcom,dump-node = <&L1_D_101>;
+ qcom,dump-node = <&L1_D_500>;
qcom,dump-id = <0x85>;
};
qcom,l1_d_cache102 {
- qcom,dump-node = <&L1_D_102>;
+ qcom,dump-node = <&L1_D_600>;
qcom,dump-id = <0x86>;
};
qcom,l1_d_cache103 {
- qcom,dump-node = <&L1_D_103>;
+ qcom,dump-node = <&L1_D_700>;
qcom,dump-id = <0x87>;
};
qcom,llcc1_d_cache {
@@ -2114,6 +2151,9 @@
};
&gpu_gx_gdsc {
+ clock-names = "core_root_clk";
+ clocks = <&clock_gfx GPU_CC_GX_GFX3D_CLK_SRC>;
+ qcom,force-enable-root-clk;
parent-supply = <&pm8005_s1_level>;
status = "ok";
};
@@ -2144,3 +2184,4 @@
#include "sdm845-pm.dtsi"
#include "sdm845-pinctrl.dtsi"
#include "sdm845-audio.dtsi"
+#include "sdm845-gpu.dtsi"
diff --git a/arch/arm64/configs/sdm845-perf_defconfig b/arch/arm64/configs/sdm845-perf_defconfig
index 4a13b7a..9552dc1 100644
--- a/arch/arm64/configs/sdm845-perf_defconfig
+++ b/arch/arm64/configs/sdm845-perf_defconfig
@@ -408,6 +408,7 @@
CONFIG_MSM_CLK_RPMH=y
CONFIG_CLOCK_CPU_OSM=y
CONFIG_MSM_GPUCC_SDM845=y
+CONFIG_QCOM_MDSS_PLL=y
CONFIG_REMOTE_SPINLOCK_MSM=y
CONFIG_MSM_QMP=y
CONFIG_IOMMU_IO_PGTABLE_FAST=y
diff --git a/arch/arm64/configs/sdm845_defconfig b/arch/arm64/configs/sdm845_defconfig
index f0f6cd9..5f22fed 100644
--- a/arch/arm64/configs/sdm845_defconfig
+++ b/arch/arm64/configs/sdm845_defconfig
@@ -275,8 +275,6 @@
# CONFIG_SERIO_SERPORT is not set
# CONFIG_VT is not set
# CONFIG_LEGACY_PTYS is not set
-CONFIG_SERIAL_MSM=y
-CONFIG_SERIAL_MSM_CONSOLE=y
CONFIG_DIAG_CHAR=y
CONFIG_HVC_DCC=y
CONFIG_HVC_DCC_SERIALIZE_SMP=y
@@ -427,6 +425,7 @@
CONFIG_MSM_CLK_RPMH=y
CONFIG_CLOCK_CPU_OSM=y
CONFIG_MSM_GPUCC_SDM845=y
+CONFIG_QCOM_MDSS_PLL=y
CONFIG_REMOTE_SPINLOCK_MSM=y
CONFIG_MSM_QMP=y
CONFIG_IOMMU_IO_PGTABLE_FAST=y
diff --git a/arch/arm64/kernel/perf_event.c b/arch/arm64/kernel/perf_event.c
index 2f8d275..852548c 100644
--- a/arch/arm64/kernel/perf_event.c
+++ b/arch/arm64/kernel/perf_event.c
@@ -867,8 +867,6 @@ static int armv8pmu_set_event_filter(struct hw_perf_event *event,
{
unsigned long config_base = 0;
- if (attr->exclude_idle)
- return -EPERM;
if (is_kernel_in_hyp_mode() &&
attr->exclude_kernel != attr->exclude_hv)
return -EINVAL;
@@ -975,11 +973,74 @@ static void __armv8pmu_probe_pmu(void *info)
ARRAY_SIZE(pmceid));
}
+static void armv8pmu_idle_update(struct arm_pmu *cpu_pmu)
+{
+ struct pmu_hw_events *hw_events;
+ struct perf_event *event;
+ int idx;
+
+ if (!cpu_pmu)
+ return;
+
+ hw_events = this_cpu_ptr(cpu_pmu->hw_events);
+
+ if (!hw_events)
+ return;
+
+ for (idx = 0; idx < cpu_pmu->num_events; ++idx) {
+
+ if (!test_bit(idx, hw_events->used_mask))
+ continue;
+
+ event = hw_events->events[idx];
+
+ if (!event || !event->attr.exclude_idle ||
+ event->state != PERF_EVENT_STATE_ACTIVE)
+ continue;
+
+ cpu_pmu->pmu.read(event);
+ }
+}
+
+struct arm_pmu_and_idle_nb {
+ struct arm_pmu *cpu_pmu;
+ struct notifier_block perf_cpu_idle_nb;
+};
+
+static int perf_cpu_idle_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct arm_pmu_and_idle_nb *pmu_nb = container_of(nb,
+ struct arm_pmu_and_idle_nb, perf_cpu_idle_nb);
+
+ if (action == IDLE_START)
+ armv8pmu_idle_update(pmu_nb->cpu_pmu);
+
+ return NOTIFY_OK;
+}
+
static int armv8pmu_probe_pmu(struct arm_pmu *cpu_pmu)
{
- return smp_call_function_any(&cpu_pmu->supported_cpus,
+ int ret;
+ struct arm_pmu_and_idle_nb *pmu_idle_nb;
+
+ pmu_idle_nb = devm_kzalloc(&cpu_pmu->plat_device->dev,
+ sizeof(*pmu_idle_nb), GFP_KERNEL);
+ if (!pmu_idle_nb)
+ return -ENOMEM;
+
+ pmu_idle_nb->cpu_pmu = cpu_pmu;
+ pmu_idle_nb->perf_cpu_idle_nb.notifier_call = perf_cpu_idle_notifier;
+ idle_notifier_register(&pmu_idle_nb->perf_cpu_idle_nb);
+
+ ret = smp_call_function_any(&cpu_pmu->supported_cpus,
__armv8pmu_probe_pmu,
cpu_pmu, 1);
+
+ if (ret)
+ idle_notifier_unregister(&pmu_idle_nb->perf_cpu_idle_nb);
+
+ return ret;
}
static void armv8_pmu_init(struct arm_pmu *cpu_pmu)
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 8eb0d14..0c4a5ee 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -84,6 +84,16 @@ void arch_cpu_idle(void)
trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, smp_processor_id());
}
+void arch_cpu_idle_enter(void)
+{
+ idle_notifier_call_chain(IDLE_START);
+}
+
+void arch_cpu_idle_exit(void)
+{
+ idle_notifier_call_chain(IDLE_END);
+}
+
#ifdef CONFIG_HOTPLUG_CPU
void arch_cpu_idle_dead(void)
{
diff --git a/arch/arm64/mm/dma-mapping.c b/arch/arm64/mm/dma-mapping.c
index 860c3b6..40e775a 100644
--- a/arch/arm64/mm/dma-mapping.c
+++ b/arch/arm64/mm/dma-mapping.c
@@ -1743,7 +1743,11 @@ static dma_addr_t arm_coherent_iommu_map_page(struct device *dev,
{
struct dma_iommu_mapping *mapping = dev->archdata.mapping;
dma_addr_t dma_addr;
- int ret, prot, len = PAGE_ALIGN(size + offset);
+ int ret, prot, len, start_offset, map_offset;
+
+ map_offset = offset & ~PAGE_MASK;
+ start_offset = offset & PAGE_MASK;
+ len = PAGE_ALIGN(map_offset + size);
dma_addr = __alloc_iova(mapping, len);
if (dma_addr == DMA_ERROR_CODE)
@@ -1753,12 +1757,12 @@ static dma_addr_t arm_coherent_iommu_map_page(struct device *dev,
prot = __get_iommu_pgprot(attrs, prot,
is_dma_coherent(dev, attrs));
- ret = iommu_map(mapping->domain, dma_addr, page_to_phys(page), len,
- prot);
+ ret = iommu_map(mapping->domain, dma_addr, page_to_phys(page) +
+ start_offset, len, prot);
if (ret < 0)
goto fail;
- return dma_addr + offset;
+ return dma_addr + map_offset;
fail:
__free_iova(mapping, dma_addr, len);
return DMA_ERROR_CODE;
@@ -1897,7 +1901,11 @@ arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, size_t size)
if (!mapping)
goto err;
- mapping->bitmap = kzalloc(bitmap_size, GFP_KERNEL);
+ mapping->bitmap = kzalloc(bitmap_size, GFP_KERNEL | __GFP_NOWARN |
+ __GFP_NORETRY);
+ if (!mapping->bitmap)
+ mapping->bitmap = vzalloc(bitmap_size);
+
if (!mapping->bitmap)
goto err2;
@@ -1912,7 +1920,7 @@ arm_iommu_create_mapping(struct bus_type *bus, dma_addr_t base, size_t size)
kref_init(&mapping->kref);
return mapping;
err3:
- kfree(mapping->bitmap);
+ kvfree(mapping->bitmap);
err2:
kfree(mapping);
err:
@@ -1926,7 +1934,7 @@ static void release_iommu_mapping(struct kref *kref)
container_of(kref, struct dma_iommu_mapping, kref);
iommu_domain_free(mapping->domain);
- kfree(mapping->bitmap);
+ kvfree(mapping->bitmap);
kfree(mapping);
}
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index cf874a1..7226dd3 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -224,3 +224,5 @@
Support for the graphics clock controller on Qualcomm Technologies, Inc.
sdm845 devices.
Say Y if you want to support graphics controller devices.
+
+source "drivers/clk/qcom/mdss/Kconfig"
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 6e13562..1d042cd 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -38,3 +38,5 @@
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o
obj-$(CONFIG_MSM_VIDEOCC_SDM845) += videocc-sdm845.o
+
+obj-y += mdss/
diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h
index 0c0ddf9..3a38d37 100644
--- a/drivers/clk/qcom/clk-rcg.h
+++ b/drivers/clk/qcom/clk-rcg.h
@@ -161,7 +161,7 @@ extern const struct clk_ops clk_dyn_rcg_ops;
* @current_freq: last cached frequency when using branches with shared RCGs
* @enable_safe_config: When set, the RCG is parked at CXO when it's disabled
* @clkr: regmap clock handle
- *
+ * @flags: additional flag parameters for the RCG
*/
struct clk_rcg2 {
u32 cmd_rcgr;
@@ -172,6 +172,8 @@ struct clk_rcg2 {
unsigned long current_freq;
bool enable_safe_config;
struct clk_regmap clkr;
+ u8 flags;
+#define FORCE_ENABLE_RCG BIT(0)
};
#define to_clk_rcg2(_hw) container_of(to_clk_regmap(_hw), struct clk_rcg2, clkr)
diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c
index a13a45e..8484b57 100644
--- a/drivers/clk/qcom/clk-rcg2.c
+++ b/drivers/clk/qcom/clk-rcg2.c
@@ -164,6 +164,47 @@ static void clk_rcg2_clear_force_enable(struct clk_hw *hw)
CMD_ROOT_EN, 0);
}
+static int prepare_enable_rcg_srcs(struct clk *curr, struct clk *new)
+{
+ int rc = 0;
+
+ rc = clk_prepare(curr);
+ if (rc)
+ return rc;
+
+ rc = clk_prepare(new);
+ if (rc)
+ goto err_new_src_prepare;
+
+ rc = clk_enable(curr);
+ if (rc)
+ goto err_curr_src_enable;
+
+ rc = clk_enable(new);
+ if (rc)
+ goto err_new_src_enable;
+
+ return rc;
+
+err_new_src_enable:
+ clk_disable(curr);
+err_curr_src_enable:
+ clk_unprepare(new);
+err_new_src_prepare:
+ clk_unprepare(curr);
+
+ return rc;
+}
+
+static void disable_unprepare_rcg_srcs(struct clk *curr, struct clk *new)
+{
+ clk_disable(new);
+ clk_disable(curr);
+
+ clk_unprepare(new);
+ clk_unprepare(curr);
+}
+
/*
* Calculate m/n:d rate
*
@@ -378,7 +419,8 @@ static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate)
{
struct clk_rcg2 *rcg = to_clk_rcg2(hw);
const struct freq_tbl *f;
- int ret;
+ int ret, curr_src_index, new_src_index;
+ struct clk_hw *curr_src = NULL, *new_src = NULL;
f = qcom_find_freq(rcg->freq_tbl, rate);
if (!f)
@@ -393,10 +435,38 @@ static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate)
return 0;
}
+ if (rcg->flags & FORCE_ENABLE_RCG) {
+ if (!rcg->current_freq)
+ rcg->current_freq = cxo_f.freq;
+
+ if (rcg->current_freq == cxo_f.freq)
+ curr_src_index = 0;
+ else {
+ f = qcom_find_freq(rcg->freq_tbl, rcg->current_freq);
+ curr_src_index = qcom_find_src_index(hw,
+ rcg->parent_map, f->src);
+ }
+
+ new_src_index = qcom_find_src_index(hw, rcg->parent_map,
+ f->src);
+
+ curr_src = clk_hw_get_parent_by_index(hw, curr_src_index);
+ new_src = clk_hw_get_parent_by_index(hw, new_src_index);
+
+ /* The RCG could currently be disabled. Enable its parents. */
+ ret = prepare_enable_rcg_srcs(curr_src->clk, new_src->clk);
+ clk_rcg2_set_force_enable(hw);
+ }
+
ret = clk_rcg2_configure(rcg, f);
if (ret)
return ret;
+ if (rcg->flags & FORCE_ENABLE_RCG) {
+ clk_rcg2_clear_force_enable(hw);
+ disable_unprepare_rcg_srcs(curr_src->clk, new_src->clk);
+ }
+
/* Update current frequency with the requested frequency. */
rcg->current_freq = rate;
return ret;
@@ -420,6 +490,11 @@ static int clk_rcg2_enable(struct clk_hw *hw)
unsigned long rate;
const struct freq_tbl *f;
+ if (rcg->flags & FORCE_ENABLE_RCG) {
+ clk_rcg2_set_force_enable(hw);
+ return 0;
+ }
+
if (!rcg->enable_safe_config)
return 0;
@@ -456,6 +531,11 @@ static void clk_rcg2_disable(struct clk_hw *hw)
{
struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+ if (rcg->flags & FORCE_ENABLE_RCG) {
+ clk_rcg2_clear_force_enable(hw);
+ return;
+ }
+
if (!rcg->enable_safe_config)
return;
/*
diff --git a/drivers/clk/qcom/gpucc-sdm845.c b/drivers/clk/qcom/gpucc-sdm845.c
index a5a7488..a95deff 100644
--- a/drivers/clk/qcom/gpucc-sdm845.c
+++ b/drivers/clk/qcom/gpucc-sdm845.c
@@ -211,9 +211,9 @@ static struct clk_rcg2 gpu_cc_gx_gfx3d_clk_src = {
.cmd_rcgr = 0x101c,
.mnd_width = 0,
.hid_width = 5,
- .enable_safe_config = true,
.parent_map = gpu_cc_parent_map_1,
.freq_tbl = ftbl_gpu_cc_gx_gfx3d_clk_src,
+ .flags = FORCE_ENABLE_RCG,
.clkr.hw.init = &(struct clk_init_data){
.name = "gpu_cc_gx_gfx3d_clk_src",
.parent_names = gpu_cc_parent_names_1,
diff --git a/drivers/clk/qcom/mdss/Kconfig b/drivers/clk/qcom/mdss/Kconfig
index 229780e..7213e37 100644
--- a/drivers/clk/qcom/mdss/Kconfig
+++ b/drivers/clk/qcom/mdss/Kconfig
@@ -1,5 +1,6 @@
-config MSM_MDSS_PLL
+config QCOM_MDSS_PLL
bool "MDSS pll programming"
+ depends on COMMON_CLK_QCOM
---help---
It provides support for DSI, eDP and HDMI interface pll programming on MDSS
hardware. It also handles the pll specific resources and turn them on/off when
diff --git a/drivers/clk/qcom/mdss/Makefile b/drivers/clk/qcom/mdss/Makefile
index 64c7609..d183393 100644
--- a/drivers/clk/qcom/mdss/Makefile
+++ b/drivers/clk/qcom/mdss/Makefile
@@ -1,9 +1,3 @@
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-pll-util.o
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-pll.o
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-dsi-pll-8996.o
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-dsi-pll-8996-util.o
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-dsi-pll-8998.o
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-dp-pll-8998.o
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-dp-pll-8998-util.o
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-hdmi-pll-8996.o
-obj-$(CONFIG_MSM_MDSS_PLL) += mdss-hdmi-pll-8998.o
+obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-pll-util.o
+obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-pll.o
+obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-dsi-pll-10nm.o
diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-8998.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c
similarity index 62%
rename from drivers/clk/qcom/mdss/mdss-dsi-pll-8998.c
rename to drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c
index 8c6bc2c..6ce0d76 100644
--- a/drivers/clk/qcom/mdss/mdss-dsi-pll-8998.c
+++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c
@@ -17,14 +17,9 @@
#include <linux/err.h>
#include <linux/iopoll.h>
#include <linux/delay.h>
-#include <linux/clk/msm-clk-provider.h>
-#include <linux/clk/msm-clk.h>
-#include <linux/clk/msm-clock-generic.h>
-#include <dt-bindings/clock/msm-clocks-8998.h>
-
-#include "mdss-pll.h"
#include "mdss-dsi-pll.h"
#include "mdss-pll.h"
+#include <dt-bindings/clock/mdss-10nm-pll-clk.h>
#define VCO_DELAY_USEC 1
@@ -128,14 +123,14 @@ struct dsi_pll_config {
u32 refclk_cycles;
};
-struct dsi_pll_8998 {
+struct dsi_pll_10nm {
struct mdss_pll_resources *rsc;
struct dsi_pll_config pll_configuration;
struct dsi_pll_regs reg_setup;
};
static struct mdss_pll_resources *pll_rsc_db[DSI_PLL_MAX];
-static struct dsi_pll_8998 plls[DSI_PLL_MAX];
+static struct dsi_pll_10nm plls[DSI_PLL_MAX];
static void dsi_pll_config_slave(struct mdss_pll_resources *rsc)
{
@@ -166,7 +161,7 @@ static void dsi_pll_config_slave(struct mdss_pll_resources *rsc)
pr_debug("Slave PLL %s\n", rsc->slave ? "configured" : "absent");
}
-static void dsi_pll_setup_config(struct dsi_pll_8998 *pll,
+static void dsi_pll_setup_config(struct dsi_pll_10nm *pll,
struct mdss_pll_resources *rsc)
{
struct dsi_pll_config *config = &pll->pll_configuration;
@@ -198,14 +193,14 @@ static void dsi_pll_setup_config(struct dsi_pll_8998 *pll,
dsi_pll_config_slave(rsc);
}
-static void dsi_pll_calc_dec_frac(struct dsi_pll_8998 *pll,
+static void dsi_pll_calc_dec_frac(struct dsi_pll_10nm *pll,
struct mdss_pll_resources *rsc)
{
struct dsi_pll_config *config = &pll->pll_configuration;
struct dsi_pll_regs *regs = &pll->reg_setup;
u64 target_freq;
u64 fref = rsc->vco_ref_clk_rate;
- u32 computed_output_div, div_log;
+ u32 computed_output_div, div_log = 0;
u64 pll_freq;
u64 divider;
u64 dec, dec_multiple;
@@ -262,7 +257,7 @@ static void dsi_pll_calc_dec_frac(struct dsi_pll_8998 *pll,
regs->frac_div_start_high = (frac & 0x30000) >> 16;
}
-static void dsi_pll_calc_ssc(struct dsi_pll_8998 *pll,
+static void dsi_pll_calc_ssc(struct dsi_pll_10nm *pll,
struct mdss_pll_resources *rsc)
{
struct dsi_pll_config *config = &pll->pll_configuration;
@@ -307,7 +302,7 @@ static void dsi_pll_calc_ssc(struct dsi_pll_8998 *pll,
ssc_per, (u32)ssc_step_size, config->ssc_adj_per);
}
-static void dsi_pll_ssc_commit(struct dsi_pll_8998 *pll,
+static void dsi_pll_ssc_commit(struct dsi_pll_10nm *pll,
struct mdss_pll_resources *rsc)
{
void __iomem *pll_base = rsc->pll_base;
@@ -333,7 +328,7 @@ static void dsi_pll_ssc_commit(struct dsi_pll_8998 *pll,
}
}
-static void dsi_pll_config_hzindep_reg(struct dsi_pll_8998 *pll,
+static void dsi_pll_config_hzindep_reg(struct dsi_pll_10nm *pll,
struct mdss_pll_resources *rsc)
{
void __iomem *pll_base = rsc->pll_base;
@@ -357,7 +352,7 @@ static void dsi_pll_config_hzindep_reg(struct dsi_pll_8998 *pll,
MDSS_PLL_REG_W(pll_base, PLL_PLL_LOCK_OVERRIDE, 0x80);
}
-static void dsi_pll_commit(struct dsi_pll_8998 *pll,
+static void dsi_pll_commit(struct dsi_pll_10nm *pll,
struct mdss_pll_resources *rsc)
{
void __iomem *pll_base = rsc->pll_base;
@@ -378,12 +373,13 @@ static void dsi_pll_commit(struct dsi_pll_8998 *pll,
}
-static int vco_8998_set_rate(struct clk *c, unsigned long rate)
+static int vco_10nm_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
{
int rc;
- struct dsi_pll_vco_clk *vco = to_vco_clk(c);
+ struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw);
struct mdss_pll_resources *rsc = vco->priv;
- struct dsi_pll_8998 *pll;
+ struct dsi_pll_10nm *pll;
if (!rsc) {
pr_err("pll resource not found\n");
@@ -431,7 +427,7 @@ static int vco_8998_set_rate(struct clk *c, unsigned long rate)
return 0;
}
-static int dsi_pll_8998_lock_status(struct mdss_pll_resources *pll)
+static int dsi_pll_10nm_lock_status(struct mdss_pll_resources *pll)
{
int rc;
u32 status;
@@ -487,7 +483,7 @@ static int dsi_pll_enable(struct dsi_pll_vco_clk *vco)
wmb();
/* Check for PLL lock */
- rc = dsi_pll_8998_lock_status(rsc);
+ rc = dsi_pll_10nm_lock_status(rsc);
if (rc) {
pr_err("PLL(%d) lock failed\n", rsc->index);
goto error;
@@ -532,9 +528,25 @@ static void dsi_pll_disable(struct dsi_pll_vco_clk *vco)
rsc->pll_on = false;
}
-static void vco_8998_unprepare(struct clk *c)
+long vco_10nm_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
{
- struct dsi_pll_vco_clk *vco = to_vco_clk(c);
+ unsigned long rrate = rate;
+ struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw);
+
+ if (rate < vco->min_rate)
+ rrate = vco->min_rate;
+ if (rate > vco->max_rate)
+ rrate = vco->max_rate;
+
+ *parent_rate = rrate;
+
+ return rrate;
+}
+
+static void vco_10nm_unprepare(struct clk_hw *hw)
+{
+ struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw);
struct mdss_pll_resources *pll = vco->priv;
if (!pll) {
@@ -542,15 +554,15 @@ static void vco_8998_unprepare(struct clk *c)
return;
}
- pll->vco_cached_rate = c->rate;
+ pll->vco_cached_rate = clk_hw_get_rate(hw);
dsi_pll_disable(vco);
mdss_pll_resource_enable(pll, false);
}
-static int vco_8998_prepare(struct clk *c)
+static int vco_10nm_prepare(struct clk_hw *hw)
{
int rc = 0;
- struct dsi_pll_vco_clk *vco = to_vco_clk(c);
+ struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw);
struct mdss_pll_resources *pll = vco->priv;
if (!pll) {
@@ -566,8 +578,9 @@ static int vco_8998_prepare(struct clk *c)
}
if ((pll->vco_cached_rate != 0) &&
- (pll->vco_cached_rate == c->rate)) {
- rc = c->ops->set_rate(c, pll->vco_cached_rate);
+ (pll->vco_cached_rate == clk_hw_get_rate(hw))) {
+ rc = hw->init->ops->set_rate(hw, pll->vco_cached_rate,
+ pll->vco_cached_rate);
if (rc) {
pr_err("pll(%d) set_rate failed, rc=%d\n",
pll->index, rc);
@@ -586,9 +599,10 @@ static int vco_8998_prepare(struct clk *c)
return rc;
}
-static unsigned long dsi_pll_get_vco_rate(struct clk *c)
+static unsigned long vco_10nm_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
{
- struct dsi_pll_vco_clk *vco = to_vco_clk(c);
+ struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw);
struct mdss_pll_resources *pll = vco->priv;
int rc;
u64 ref_clk = vco->ref_clk_rate;
@@ -642,46 +656,11 @@ static unsigned long dsi_pll_get_vco_rate(struct clk *c)
return (unsigned long)vco_rate;
}
-enum handoff vco_8998_handoff(struct clk *c)
-{
- enum handoff ret = HANDOFF_DISABLED_CLK;
- int rc;
- struct dsi_pll_vco_clk *vco = to_vco_clk(c);
- struct mdss_pll_resources *pll = vco->priv;
- u32 status;
-
- if (!pll) {
- pr_err("Unable to find pll resource\n");
- return HANDOFF_DISABLED_CLK;
- }
-
- rc = mdss_pll_resource_enable(pll, true);
- if (rc) {
- pr_err("failed to enable pll(%d) resources, rc=%d\n",
- pll->index, rc);
- return ret;
- }
-
- status = MDSS_PLL_REG_R(pll->pll_base, PLL_COMMON_STATUS_ONE);
- if (status & BIT(0)) {
- pll->handoff_resources = true;
- pll->pll_on = true;
- c->rate = dsi_pll_get_vco_rate(c);
- ret = HANDOFF_ENABLED_CLK;
- } else {
- (void)mdss_pll_resource_enable(pll, false);
- ret = HANDOFF_DISABLED_CLK;
- }
-
- return ret;
-}
-
-static int pixel_clk_get_div(struct div_clk *clk)
+static int pixel_clk_get_div(void *context, unsigned int reg, unsigned int *div)
{
int rc;
- struct mdss_pll_resources *pll = clk->priv;
+ struct mdss_pll_resources *pll = context;
u32 reg_val;
- int div;
rc = mdss_pll_resource_enable(pll, true);
if (rc) {
@@ -690,11 +669,16 @@ static int pixel_clk_get_div(struct div_clk *clk)
}
reg_val = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0);
- div = (reg_val & 0xF0) >> 4;
+ *div = (reg_val & 0xF0) >> 4;
+
+ if (*div == 0)
+ *div = 1;
+ else
+ *div -= 1;
(void)mdss_pll_resource_enable(pll, false);
- return div;
+ return rc;
}
static void pixel_clk_set_div_sub(struct mdss_pll_resources *pll, int div)
@@ -707,16 +691,18 @@ static void pixel_clk_set_div_sub(struct mdss_pll_resources *pll, int div)
MDSS_PLL_REG_W(pll->phy_base, PHY_CMN_CLK_CFG0, reg_val);
}
-static int pixel_clk_set_div(struct div_clk *clk, int div)
+static int pixel_clk_set_div(void *context, unsigned int reg, unsigned int div)
{
int rc;
- struct mdss_pll_resources *pll = clk->priv;
+ struct mdss_pll_resources *pll = context;
rc = mdss_pll_resource_enable(pll, true);
if (rc) {
pr_err("Failed to enable dsi pll resources, rc=%d\n", rc);
return rc;
}
+ /* In common clock framework the divider value provided is one less */
+ div++;
pixel_clk_set_div_sub(pll, div);
if (pll->slave)
@@ -727,12 +713,11 @@ static int pixel_clk_set_div(struct div_clk *clk, int div)
return 0;
}
-static int bit_clk_get_div(struct div_clk *clk)
+static int bit_clk_get_div(void *context, unsigned int reg, unsigned int *div)
{
int rc;
- struct mdss_pll_resources *pll = clk->priv;
+ struct mdss_pll_resources *pll = context;
u32 reg_val;
- int div;
rc = mdss_pll_resource_enable(pll, true);
if (rc) {
@@ -741,11 +726,17 @@ static int bit_clk_get_div(struct div_clk *clk)
}
reg_val = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0);
- div = (reg_val & 0x0F);
+ *div = (reg_val & 0x0F);
+
+ /* Common clock framework will add one to divider value sent */
+ if (*div == 0)
+ *div = 1;
+ else
+ *div -= 1;
(void)mdss_pll_resource_enable(pll, false);
- return div;
+ return rc;
}
static void bit_clk_set_div_sub(struct mdss_pll_resources *rsc, int div)
@@ -758,10 +749,10 @@ static void bit_clk_set_div_sub(struct mdss_pll_resources *rsc, int div)
MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CLK_CFG0, reg_val);
}
-static int bit_clk_set_div(struct div_clk *clk, int div)
+static int bit_clk_set_div(void *context, unsigned int reg, unsigned int div)
{
int rc;
- struct mdss_pll_resources *rsc = clk->priv;
+ struct mdss_pll_resources *rsc = context;
struct dsi_pll_8998 *pll;
if (!rsc) {
@@ -780,6 +771,7 @@ static int bit_clk_set_div(struct div_clk *clk, int div)
pr_err("Failed to enable dsi pll resources, rc=%d\n", rc);
return rc;
}
+ div++;
bit_clk_set_div_sub(rsc, div);
/* For slave PLL, this divider always should be set to 1 */
@@ -791,12 +783,12 @@ static int bit_clk_set_div(struct div_clk *clk, int div)
return rc;
}
-static int post_vco_clk_get_div(struct div_clk *clk)
+static int post_vco_clk_get_div(void *context, unsigned int reg,
+ unsigned int *div)
{
int rc;
- struct mdss_pll_resources *pll = clk->priv;
+ struct mdss_pll_resources *pll = context;
u32 reg_val;
- int div;
rc = mdss_pll_resource_enable(pll, true);
if (rc) {
@@ -808,15 +800,20 @@ static int post_vco_clk_get_div(struct div_clk *clk)
reg_val &= 0x3;
if (reg_val == 2)
- div = 1;
+ *div = 1;
else if (reg_val == 3)
- div = 4;
+ *div = 4;
else
- div = 1;
+ *div = 1;
+
+ if (*div == 0)
+ *div = 1;
+ else
+ *div -= 1;
(void)mdss_pll_resource_enable(pll, false);
- return div;
+ return rc;
}
static int post_vco_clk_set_div_sub(struct mdss_pll_resources *pll, int div)
@@ -842,10 +839,11 @@ static int post_vco_clk_set_div_sub(struct mdss_pll_resources *pll, int div)
return rc;
}
-static int post_vco_clk_set_div(struct div_clk *clk, int div)
+static int post_vco_clk_set_div(void *context, unsigned int reg,
+ unsigned int div)
{
int rc = 0;
- struct mdss_pll_resources *pll = clk->priv;
+ struct mdss_pll_resources *pll = context;
rc = mdss_pll_resource_enable(pll, true);
if (rc) {
@@ -853,6 +851,8 @@ static int post_vco_clk_set_div(struct div_clk *clk, int div)
return rc;
}
+ div++;
+
rc = post_vco_clk_set_div_sub(pll, div);
if (!rc && pll->slave)
rc = post_vco_clk_set_div_sub(pll->slave, div);
@@ -862,12 +862,12 @@ static int post_vco_clk_set_div(struct div_clk *clk, int div)
return rc;
}
-static int post_bit_clk_get_div(struct div_clk *clk)
+static int post_bit_clk_get_div(void *context, unsigned int reg,
+ unsigned int *div)
{
int rc;
- struct mdss_pll_resources *pll = clk->priv;
+ struct mdss_pll_resources *pll = context;
u32 reg_val;
- int div;
rc = mdss_pll_resource_enable(pll, true);
if (rc) {
@@ -879,15 +879,20 @@ static int post_bit_clk_get_div(struct div_clk *clk)
reg_val &= 0x3;
if (reg_val == 0)
- div = 1;
+ *div = 1;
else if (reg_val == 1)
- div = 2;
+ *div = 2;
else
- div = 1;
+ *div = 1;
+
+ if (*div == 0)
+ *div = 1;
+ else
+ *div -= 1;
(void)mdss_pll_resource_enable(pll, false);
- return div;
+ return rc;
}
static int post_bit_clk_set_div_sub(struct mdss_pll_resources *pll, int div)
@@ -913,10 +918,11 @@ static int post_bit_clk_set_div_sub(struct mdss_pll_resources *pll, int div)
return rc;
}
-static int post_bit_clk_set_div(struct div_clk *clk, int div)
+static int post_bit_clk_set_div(void *context, unsigned int reg,
+ unsigned int div)
{
int rc = 0;
- struct mdss_pll_resources *pll = clk->priv;
+ struct mdss_pll_resources *pll = context;
rc = mdss_pll_resource_enable(pll, true);
if (rc) {
@@ -924,6 +930,8 @@ static int post_bit_clk_set_div(struct div_clk *clk, int div)
return rc;
}
+ div++;
+
rc = post_bit_clk_set_div_sub(pll, div);
if (!rc && pll->slave)
rc = post_bit_clk_set_div_sub(pll->slave, div);
@@ -933,57 +941,44 @@ static int post_bit_clk_set_div(struct div_clk *clk, int div)
return rc;
}
-long vco_8998_round_rate(struct clk *c, unsigned long rate)
-{
- unsigned long rrate = rate;
- struct dsi_pll_vco_clk *vco = to_vco_clk(c);
-
- if (rate < vco->min_rate)
- rrate = vco->min_rate;
- if (rate > vco->max_rate)
- rrate = vco->max_rate;
-
- return rrate;
-}
-
-/* clk ops that require runtime fixup */
-static const struct clk_ops clk_ops_gen_mux_dsi;
-static const struct clk_ops clk_ops_bitclk_src_c;
-static const struct clk_ops clk_ops_post_vco_div_c;
-static const struct clk_ops clk_ops_post_bit_div_c;
-static const struct clk_ops clk_ops_pclk_src_c;
-
-static struct clk_div_ops clk_post_vco_div_ops = {
- .set_div = post_vco_clk_set_div,
- .get_div = post_vco_clk_get_div,
+static struct regmap_config dsi_pll_10nm_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x7c0,
};
-static struct clk_div_ops clk_post_bit_div_ops = {
- .set_div = post_bit_clk_set_div,
- .get_div = post_bit_clk_get_div,
+static struct regmap_bus post_vco_regmap_bus = {
+ .reg_write = post_vco_clk_set_div,
+ .reg_read = post_vco_clk_get_div,
};
-static struct clk_div_ops pixel_clk_div_ops = {
- .set_div = pixel_clk_set_div,
- .get_div = pixel_clk_get_div,
+static struct regmap_bus post_bit_regmap_bus = {
+ .reg_write = post_bit_clk_set_div,
+ .reg_read = post_bit_clk_get_div,
};
-static struct clk_div_ops clk_bitclk_src_ops = {
- .set_div = bit_clk_set_div,
- .get_div = bit_clk_get_div,
+static struct regmap_bus pclk_src_regmap_bus = {
+ .reg_write = pixel_clk_set_div,
+ .reg_read = pixel_clk_get_div,
};
-static const struct clk_ops clk_ops_vco_8998 = {
- .set_rate = vco_8998_set_rate,
- .round_rate = vco_8998_round_rate,
- .handoff = vco_8998_handoff,
- .prepare = vco_8998_prepare,
- .unprepare = vco_8998_unprepare,
+static struct regmap_bus bitclk_src_regmap_bus = {
+ .reg_write = bit_clk_set_div,
+ .reg_read = bit_clk_get_div,
};
-static struct clk_mux_ops mdss_mux_ops = {
- .set_mux_sel = mdss_set_mux_sel,
- .get_mux_sel = mdss_get_mux_sel,
+static const struct clk_ops clk_ops_vco_10nm = {
+ .recalc_rate = vco_10nm_recalc_rate,
+ .set_rate = vco_10nm_set_rate,
+ .round_rate = vco_10nm_round_rate,
+ .prepare = vco_10nm_prepare,
+ .unprepare = vco_10nm_unprepare,
+};
+
+static struct regmap_bus mdss_mux_regmap_bus = {
+ .reg_write = mdss_set_mux_sel,
+ .reg_read = mdss_get_mux_sel,
};
/*
@@ -1039,303 +1034,296 @@ static struct dsi_pll_vco_clk dsi0pll_vco_clk = {
.ref_clk_rate = 19200000UL,
.min_rate = 1500000000UL,
.max_rate = 3500000000UL,
- .c = {
- .dbg_name = "dsi0pll_vco_clk",
- .ops = &clk_ops_vco_8998,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_vco_clk.c),
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_vco_clk",
+ .parent_names = (const char *[]){"xo_board"},
+ .num_parents = 1,
+ .ops = &clk_ops_vco_10nm,
+ .flags = CLK_GET_RATE_NOCACHE,
},
};
-static struct div_clk dsi0pll_bitclk_src = {
- .data = {
- .div = 1,
- .min_div = 1,
- .max_div = 15,
- },
- .ops = &clk_bitclk_src_ops,
- .c = {
- .parent = &dsi0pll_vco_clk.c,
- .dbg_name = "dsi0pll_bitclk_src",
- .ops = &clk_ops_bitclk_src_c,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_bitclk_src.c),
- }
-};
-
-static struct div_clk dsi0pll_post_vco_div = {
- .data = {
- .div = 1,
- .min_div = 1,
- .max_div = 4,
- },
- .ops = &clk_post_vco_div_ops,
- .c = {
- .parent = &dsi0pll_vco_clk.c,
- .dbg_name = "dsi0pll_post_vco_div",
- .ops = &clk_ops_post_vco_div_c,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_post_vco_div.c),
- }
-};
-
-static struct div_clk dsi0pll_post_bit_div = {
- .data = {
- .div = 1,
- .min_div = 1,
- .max_div = 2,
- },
- .ops = &clk_post_bit_div_ops,
- .c = {
- .parent = &dsi0pll_bitclk_src.c,
- .dbg_name = "dsi0pll_post_bit_div",
- .ops = &clk_ops_post_bit_div_c,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_post_bit_div.c),
- }
-};
-
-static struct mux_clk dsi0pll_pclk_src_mux = {
- .num_parents = 2,
- .parents = (struct clk_src[]) {
- {&dsi0pll_post_bit_div.c, 0},
- {&dsi0pll_post_vco_div.c, 1},
- },
- .ops = &mdss_mux_ops,
- .c = {
- .parent = &dsi0pll_post_bit_div.c,
- .dbg_name = "dsi0pll_pclk_src_mux",
- .ops = &clk_ops_gen_mux,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_pclk_src_mux.c),
- }
-};
-
-static struct div_clk dsi0pll_pclk_src = {
- .data = {
- .div = 1,
- .min_div = 1,
- .max_div = 15,
- },
- .ops = &pixel_clk_div_ops,
- .c = {
- .parent = &dsi0pll_pclk_src_mux.c,
- .dbg_name = "dsi0pll_pclk_src",
- .ops = &clk_ops_pclk_src_c,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_pclk_src.c),
- },
-};
-
-static struct mux_clk dsi0pll_pclk_mux = {
- .num_parents = 1,
- .parents = (struct clk_src[]) {
- {&dsi0pll_pclk_src.c, 0},
- },
- .ops = &mdss_mux_ops,
- .c = {
- .parent = &dsi0pll_pclk_src.c,
- .dbg_name = "dsi0pll_pclk_mux",
- .ops = &clk_ops_gen_mux_dsi,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_pclk_mux.c),
- }
-};
-
-static struct div_clk dsi0pll_byteclk_src = {
- .data = {
- .div = 8,
- .min_div = 8,
- .max_div = 8,
- },
- .c = {
- .parent = &dsi0pll_bitclk_src.c,
- .dbg_name = "dsi0pll_byteclk_src",
- .ops = &clk_ops_div,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_byteclk_src.c),
- },
-};
-
-static struct mux_clk dsi0pll_byteclk_mux = {
- .num_parents = 1,
- .parents = (struct clk_src[]) {
- {&dsi0pll_byteclk_src.c, 0},
- },
- .ops = &mdss_mux_ops,
- .c = {
- .parent = &dsi0pll_byteclk_src.c,
- .dbg_name = "dsi0pll_byteclk_mux",
- .ops = &clk_ops_gen_mux_dsi,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi0pll_byteclk_mux.c),
- }
-};
-
static struct dsi_pll_vco_clk dsi1pll_vco_clk = {
.ref_clk_rate = 19200000UL,
.min_rate = 1500000000UL,
.max_rate = 3500000000UL,
- .c = {
- .dbg_name = "dsi1pll_vco_clk",
- .ops = &clk_ops_vco_8998,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_vco_clk.c),
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_vco_clk",
+ .parent_names = (const char *[]){"xo_board"},
+ .num_parents = 1,
+ .ops = &clk_ops_vco_10nm,
+ .flags = CLK_GET_RATE_NOCACHE,
},
};
-static struct div_clk dsi1pll_bitclk_src = {
- .data = {
- .div = 1,
- .min_div = 1,
- .max_div = 15,
- },
- .ops = &clk_bitclk_src_ops,
- .c = {
- .parent = &dsi1pll_vco_clk.c,
- .dbg_name = "dsi1pll_bitclk_src",
- .ops = &clk_ops_bitclk_src_c,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_bitclk_src.c),
- }
-};
-
-static struct div_clk dsi1pll_post_vco_div = {
- .data = {
- .div = 1,
- .min_div = 1,
- .max_div = 4,
- },
- .ops = &clk_post_vco_div_ops,
- .c = {
- .parent = &dsi1pll_vco_clk.c,
- .dbg_name = "dsi1pll_post_vco_div",
- .ops = &clk_ops_post_vco_div_c,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_post_vco_div.c),
- }
-};
-
-static struct div_clk dsi1pll_post_bit_div = {
- .data = {
- .div = 1,
- .min_div = 1,
- .max_div = 2,
- },
- .ops = &clk_post_bit_div_ops,
- .c = {
- .parent = &dsi1pll_bitclk_src.c,
- .dbg_name = "dsi1pll_post_bit_div",
- .ops = &clk_ops_post_bit_div_c,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_post_bit_div.c),
- }
-};
-
-static struct mux_clk dsi1pll_pclk_src_mux = {
- .num_parents = 2,
- .parents = (struct clk_src[]) {
- {&dsi1pll_post_bit_div.c, 0},
- {&dsi1pll_post_vco_div.c, 1},
- },
- .ops = &mdss_mux_ops,
- .c = {
- .parent = &dsi1pll_post_bit_div.c,
- .dbg_name = "dsi1pll_pclk_src_mux",
- .ops = &clk_ops_gen_mux,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_pclk_src_mux.c),
- }
-};
-
-static struct div_clk dsi1pll_pclk_src = {
- .data = {
- .div = 1,
- .min_div = 1,
- .max_div = 15,
- },
- .ops = &pixel_clk_div_ops,
- .c = {
- .parent = &dsi1pll_pclk_src_mux.c,
- .dbg_name = "dsi1pll_pclk_src",
- .ops = &clk_ops_pclk_src_c,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_pclk_src.c),
+static struct clk_regmap_div dsi0pll_bitclk_src = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_bitclk_src",
+ .parent_names = (const char *[]){"dsi0pll_vco_clk"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_div_ops,
+ },
},
};
-static struct mux_clk dsi1pll_pclk_mux = {
- .num_parents = 1,
- .parents = (struct clk_src[]) {
- {&dsi1pll_pclk_src.c, 0},
- },
- .ops = &mdss_mux_ops,
- .c = {
- .parent = &dsi1pll_pclk_src.c,
- .dbg_name = "dsi1pll_pclk_mux",
- .ops = &clk_ops_gen_mux_dsi,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_pclk_mux.c),
- }
-};
-
-static struct div_clk dsi1pll_byteclk_src = {
- .data = {
- .div = 8,
- .min_div = 8,
- .max_div = 8,
- },
- .c = {
- .parent = &dsi1pll_bitclk_src.c,
- .dbg_name = "dsi1pll_byteclk_src",
- .ops = &clk_ops_div,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_byteclk_src.c),
+static struct clk_regmap_div dsi1pll_bitclk_src = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_bitclk_src",
+ .parent_names = (const char *[]){"dsi1pll_vco_clk"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_div_ops,
+ },
},
};
-static struct mux_clk dsi1pll_byteclk_mux = {
- .num_parents = 1,
- .parents = (struct clk_src[]) {
- {&dsi1pll_byteclk_src.c, 0},
+static struct clk_regmap_div dsi0pll_post_vco_div = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_post_vco_div",
+ .parent_names = (const char *[]){"dsi0pll_vco_clk"},
+ .num_parents = 1,
+ .flags = CLK_GET_RATE_NOCACHE,
+ .ops = &clk_regmap_div_ops,
+ },
},
- .ops = &mdss_mux_ops,
- .c = {
- .parent = &dsi1pll_byteclk_src.c,
- .dbg_name = "dsi1pll_byteclk_mux",
- .ops = &clk_ops_gen_mux_dsi,
- .flags = CLKFLAG_NO_RATE_CACHE,
- CLK_INIT(dsi1pll_byteclk_mux.c),
- }
};
-static struct clk_lookup mdss_dsi_pll0cc_8998[] = {
- CLK_LIST(dsi0pll_byteclk_mux),
- CLK_LIST(dsi0pll_byteclk_src),
- CLK_LIST(dsi0pll_pclk_mux),
- CLK_LIST(dsi0pll_pclk_src),
- CLK_LIST(dsi0pll_pclk_src_mux),
- CLK_LIST(dsi0pll_post_bit_div),
- CLK_LIST(dsi0pll_post_vco_div),
- CLK_LIST(dsi0pll_bitclk_src),
- CLK_LIST(dsi0pll_vco_clk),
-};
-static struct clk_lookup mdss_dsi_pll1cc_8998[] = {
- CLK_LIST(dsi1pll_byteclk_mux),
- CLK_LIST(dsi1pll_byteclk_src),
- CLK_LIST(dsi1pll_pclk_mux),
- CLK_LIST(dsi1pll_pclk_src),
- CLK_LIST(dsi1pll_pclk_src_mux),
- CLK_LIST(dsi1pll_post_bit_div),
- CLK_LIST(dsi1pll_post_vco_div),
- CLK_LIST(dsi1pll_bitclk_src),
- CLK_LIST(dsi1pll_vco_clk),
+static struct clk_regmap_div dsi1pll_post_vco_div = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_post_vco_div",
+ .parent_names = (const char *[]){"dsi1pll_vco_clk"},
+ .num_parents = 1,
+ .flags = CLK_GET_RATE_NOCACHE,
+ .ops = &clk_regmap_div_ops,
+ },
+ },
};
-int dsi_pll_clock_register_8998(struct platform_device *pdev,
+static struct clk_fixed_factor dsi0pll_byteclk_src = {
+ .div = 8,
+ .mult = 1,
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_byteclk_src",
+ .parent_names = (const char *[]){"dsi0pll_bitclk_src"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_fixed_factor_ops,
+ },
+};
+
+static struct clk_fixed_factor dsi1pll_byteclk_src = {
+ .div = 8,
+ .mult = 1,
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_byteclk_src",
+ .parent_names = (const char *[]){"dsi1pll_bitclk_src"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_fixed_factor_ops,
+ },
+};
+
+static struct clk_regmap_div dsi0pll_post_bit_div = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_post_bit_div",
+ .parent_names = (const char *[]){"dsi0pll_bitclk_src"},
+ .num_parents = 1,
+ .flags = CLK_GET_RATE_NOCACHE,
+ .ops = &clk_regmap_div_ops,
+ },
+ },
+};
+
+static struct clk_regmap_div dsi1pll_post_bit_div = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_post_bit_div",
+ .parent_names = (const char *[]){"dsi1pll_bitclk_src"},
+ .num_parents = 1,
+ .flags = CLK_GET_RATE_NOCACHE,
+ .ops = &clk_regmap_div_ops,
+ },
+ },
+};
+
+static struct clk_regmap_mux dsi0pll_byteclk_mux = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_byteclk_mux",
+ .parent_names = (const char *[]){"dsi0pll_byteclk_src"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_mux_closest_ops,
+ },
+ },
+};
+
+static struct clk_regmap_mux dsi1pll_byteclk_mux = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_byteclk_mux",
+ .parent_names = (const char *[]){"dsi1pll_byteclk_src"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_mux_closest_ops,
+ },
+ },
+};
+
+static struct clk_regmap_mux dsi0pll_pclk_src_mux = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_pclk_src_mux",
+ .parent_names = (const char *[]){"dsi0pll_post_bit_div",
+ "dsi0pll_post_bit_div"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_mux_closest_ops,
+ },
+ },
+};
+
+static struct clk_regmap_mux dsi1pll_pclk_src_mux = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_pclk_src_mux",
+ .parent_names = (const char *[]){"dsi1pll_post_bit_div",
+ "dsi1pll_post_bit_div"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_mux_closest_ops,
+ },
+ },
+};
+
+static struct clk_regmap_div dsi0pll_pclk_src = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_pclk_src",
+ .parent_names = (const char *[]){
+ "dsi0pll_pclk_src_mux"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_div_ops,
+ },
+ },
+};
+
+static struct clk_regmap_div dsi1pll_pclk_src = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_pclk_src",
+ .parent_names = (const char *[]){
+ "dsi1pll_pclk_src_mux"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_div_ops,
+ },
+ },
+};
+
+static struct clk_regmap_mux dsi0pll_pclk_mux = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi0pll_pclk_mux",
+ .parent_names = (const char *[]){"dsi0pll_pclk_src"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_mux_closest_ops,
+ },
+ },
+};
+
+static struct clk_regmap_mux dsi1pll_pclk_mux = {
+ .reg = 0x48,
+ .shift = 0,
+ .width = 4,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "dsi1pll_pclk_mux",
+ .parent_names = (const char *[]){"dsi1pll_pclk_src"},
+ .num_parents = 1,
+ .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT),
+ .ops = &clk_regmap_mux_closest_ops,
+ },
+ },
+};
+
+static struct clk_hw *mdss_dsi_pllcc_10nm[] = {
+ [VCO_CLK_0] = &dsi0pll_vco_clk.hw,
+ [BITCLK_SRC_0_CLK] = &dsi0pll_bitclk_src.clkr.hw,
+ [BYTECLK_SRC_0_CLK] = &dsi0pll_byteclk_src.hw,
+ [POST_BIT_DIV_0_CLK] = &dsi0pll_post_bit_div.clkr.hw,
+ [POST_VCO_DIV_0_CLK] = &dsi0pll_post_vco_div.clkr.hw,
+ [BYTECLK_MUX_0_CLK] = &dsi0pll_byteclk_mux.clkr.hw,
+ [PCLK_SRC_MUX_0_CLK] = &dsi0pll_pclk_src_mux.clkr.hw,
+ [PCLK_SRC_0_CLK] = &dsi0pll_pclk_src.clkr.hw,
+ [PCLK_MUX_0_CLK] = &dsi0pll_pclk_mux.clkr.hw,
+ [VCO_CLK_1] = &dsi1pll_vco_clk.hw,
+ [BITCLK_SRC_1_CLK] = &dsi1pll_bitclk_src.clkr.hw,
+ [BYTECLK_SRC_1_CLK] = &dsi1pll_byteclk_src.hw,
+ [POST_BIT_DIV_1_CLK] = &dsi1pll_post_bit_div.clkr.hw,
+ [POST_VCO_DIV_1_CLK] = &dsi1pll_post_vco_div.clkr.hw,
+ [BYTECLK_MUX_1_CLK] = &dsi1pll_byteclk_mux.clkr.hw,
+ [PCLK_SRC_MUX_1_CLK] = &dsi1pll_pclk_src_mux.clkr.hw,
+ [PCLK_SRC_1_CLK] = &dsi1pll_pclk_src.clkr.hw,
+ [PCLK_MUX_1_CLK] = &dsi1pll_pclk_mux.clkr.hw,
+
+};
+
+int dsi_pll_clock_register_10nm(struct platform_device *pdev,
struct mdss_pll_resources *pll_res)
{
- int rc = 0, ndx;
+ int rc = 0, ndx, i;
+ struct clk *clk;
+ struct clk_onecell_data *clk_data;
+ int num_clks = ARRAY_SIZE(mdss_dsi_pllcc_10nm);
+ struct regmap *rmap;
if (!pdev || !pdev->dev.of_node ||
!pll_res || !pll_res->pll_base || !pll_res->phy_base) {
@@ -1353,62 +1341,120 @@ int dsi_pll_clock_register_8998(struct platform_device *pdev,
pll_rsc_db[ndx] = pll_res;
pll_res->priv = &plls[ndx];
plls[ndx].rsc = pll_res;
-
- /* runtime fixup of all div and mux clock ops */
- clk_ops_gen_mux_dsi = clk_ops_gen_mux;
- clk_ops_gen_mux_dsi.round_rate = parent_round_rate;
- clk_ops_gen_mux_dsi.set_rate = parent_set_rate;
-
- clk_ops_bitclk_src_c = clk_ops_div;
- clk_ops_bitclk_src_c.prepare = mdss_pll_div_prepare;
-
- /*
- * Set the ops for the two dividers in the pixel clock tree to the
- * slave_div to ensure that a set rate on this divider clock will not
- * be propagated to it's parent. This is needed ensure that when we set
- * the rate for pixel clock, the vco is not reconfigured
- */
- clk_ops_post_vco_div_c = clk_ops_slave_div;
- clk_ops_post_vco_div_c.prepare = mdss_pll_div_prepare;
-
- clk_ops_post_bit_div_c = clk_ops_slave_div;
- clk_ops_post_bit_div_c.prepare = mdss_pll_div_prepare;
-
- clk_ops_pclk_src_c = clk_ops_div;
- clk_ops_pclk_src_c.prepare = mdss_pll_div_prepare;
-
pll_res->vco_delay = VCO_DELAY_USEC;
- if (ndx == 0) {
- dsi0pll_byteclk_mux.priv = pll_res;
- dsi0pll_byteclk_src.priv = pll_res;
- dsi0pll_pclk_mux.priv = pll_res;
- dsi0pll_pclk_src.priv = pll_res;
- dsi0pll_pclk_src_mux.priv = pll_res;
- dsi0pll_post_bit_div.priv = pll_res;
- dsi0pll_post_vco_div.priv = pll_res;
- dsi0pll_bitclk_src.priv = pll_res;
- dsi0pll_vco_clk.priv = pll_res;
- rc = of_msm_clock_register(pdev->dev.of_node,
- mdss_dsi_pll0cc_8998,
- ARRAY_SIZE(mdss_dsi_pll0cc_8998));
- } else {
- dsi1pll_byteclk_mux.priv = pll_res;
- dsi1pll_byteclk_src.priv = pll_res;
- dsi1pll_pclk_mux.priv = pll_res;
- dsi1pll_pclk_src.priv = pll_res;
- dsi1pll_pclk_src_mux.priv = pll_res;
- dsi1pll_post_bit_div.priv = pll_res;
- dsi1pll_post_vco_div.priv = pll_res;
- dsi1pll_bitclk_src.priv = pll_res;
- dsi1pll_vco_clk.priv = pll_res;
+ clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data),
+ GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
- rc = of_msm_clock_register(pdev->dev.of_node,
- mdss_dsi_pll1cc_8998,
- ARRAY_SIZE(mdss_dsi_pll1cc_8998));
+ clk_data->clks = devm_kzalloc(&pdev->dev, (num_clks *
+ sizeof(struct clk *)), GFP_KERNEL);
+ if (!clk_data->clks) {
+ devm_kfree(&pdev->dev, clk_data);
+ return -ENOMEM;
}
- if (rc)
- pr_err("dsi%dpll clock register failed, rc=%d\n", ndx, rc);
+ clk_data->clk_num = num_clks;
+ /* Establish client data */
+ if (ndx == 0) {
+ rmap = devm_regmap_init(&pdev->dev, &post_vco_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi0pll_post_vco_div.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &post_bit_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi0pll_post_bit_div.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &bitclk_src_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi0pll_bitclk_src.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &pclk_src_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi0pll_pclk_src.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi0pll_pclk_mux.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi0pll_pclk_src_mux.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi0pll_byteclk_mux.clkr.regmap = rmap;
+
+ for (i = VCO_CLK_0; i <= PCLK_MUX_0_CLK; i++) {
+ clk = devm_clk_register(&pdev->dev,
+ mdss_dsi_pllcc_10nm[i]);
+ if (IS_ERR(clk)) {
+ pr_err("clk registration failed for DSI clock:%d\n",
+ pll_res->index);
+ rc = -EINVAL;
+ goto clk_register_fail;
+ }
+ clk_data->clks[i] = clk;
+
+ }
+
+ rc = of_clk_add_provider(pdev->dev.of_node,
+ of_clk_src_onecell_get, clk_data);
+
+
+ } else {
+ rmap = devm_regmap_init(&pdev->dev, &post_vco_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi1pll_post_vco_div.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &post_bit_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi1pll_post_bit_div.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &bitclk_src_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi1pll_bitclk_src.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &pclk_src_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi1pll_pclk_src.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi1pll_pclk_mux.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi1pll_pclk_src_mux.clkr.regmap = rmap;
+
+ rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus,
+ pll_res, &dsi_pll_10nm_config);
+ dsi1pll_byteclk_mux.clkr.regmap = rmap;
+
+ for (i = VCO_CLK_1; i <= PCLK_MUX_1_CLK; i++) {
+ clk = devm_clk_register(&pdev->dev,
+ mdss_dsi_pllcc_10nm[i]);
+ if (IS_ERR(clk)) {
+ pr_err("clk registration failed for DSI clock:%d\n",
+ pll_res->index);
+ rc = -EINVAL;
+ goto clk_register_fail;
+ }
+ clk_data->clks[i] = clk;
+
+ }
+
+ rc = of_clk_add_provider(pdev->dev.of_node,
+ of_clk_src_onecell_get, clk_data);
+ }
+ if (!rc) {
+ pr_info("Registered DSI PLL ndx=%d, clocks successfully", ndx);
+
+ return rc;
+ }
+clk_register_fail:
+ devm_kfree(&pdev->dev, clk_data->clks);
+ devm_kfree(&pdev->dev, clk_data);
return rc;
}
diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll.h b/drivers/clk/qcom/mdss/mdss-dsi-pll.h
index 286c99e..7fc38a2 100644
--- a/drivers/clk/qcom/mdss/mdss-dsi-pll.h
+++ b/drivers/clk/qcom/mdss/mdss-dsi-pll.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -13,6 +13,8 @@
#ifndef __MDSS_DSI_PLL_H
#define __MDSS_DSI_PLL_H
+#include <linux/clk-provider.h>
+#include "mdss-pll.h"
#define MAX_DSI_PLL_EN_SEQS 10
#define DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG (0x0020)
@@ -31,6 +33,7 @@ struct lpfr_cfg {
};
struct dsi_pll_vco_clk {
+ struct clk_hw hw;
unsigned long ref_clk_rate;
unsigned long min_rate;
unsigned long max_rate;
@@ -38,73 +41,16 @@ struct dsi_pll_vco_clk {
struct lpfr_cfg *lpfr_lut;
u32 lpfr_lut_size;
void *priv;
-
- struct clk c;
-
int (*pll_enable_seqs[MAX_DSI_PLL_EN_SEQS])
(struct mdss_pll_resources *dsi_pll_Res);
};
-static inline struct dsi_pll_vco_clk *to_vco_clk(struct clk *clk)
+int dsi_pll_clock_register_10nm(struct platform_device *pdev,
+ struct mdss_pll_resources *pll_res);
+
+static inline struct dsi_pll_vco_clk *to_vco_clk_hw(struct clk_hw *hw)
{
- return container_of(clk, struct dsi_pll_vco_clk, c);
+ return container_of(hw, struct dsi_pll_vco_clk, hw);
}
-int dsi_pll_clock_register_hpm(struct platform_device *pdev,
- struct mdss_pll_resources *pll_res);
-int dsi_pll_clock_register_20nm(struct platform_device *pdev,
- struct mdss_pll_resources *pll_res);
-int dsi_pll_clock_register_lpm(struct platform_device *pdev,
- struct mdss_pll_resources *pll_res);
-int dsi_pll_clock_register_8996(struct platform_device *pdev,
- struct mdss_pll_resources *pll_res);
-int dsi_pll_clock_register_8998(struct platform_device *pdev,
- struct mdss_pll_resources *pll_res);
-
-int set_byte_mux_sel(struct mux_clk *clk, int sel);
-int get_byte_mux_sel(struct mux_clk *clk);
-int dsi_pll_mux_prepare(struct clk *c);
-int fixed_4div_set_div(struct div_clk *clk, int div);
-int fixed_4div_get_div(struct div_clk *clk);
-int digital_set_div(struct div_clk *clk, int div);
-int digital_get_div(struct div_clk *clk);
-int analog_set_div(struct div_clk *clk, int div);
-int analog_get_div(struct div_clk *clk);
-int dsi_pll_lock_status(struct mdss_pll_resources *dsi_pll_res);
-int vco_set_rate(struct dsi_pll_vco_clk *vco, unsigned long rate);
-unsigned long vco_get_rate(struct clk *c);
-long vco_round_rate(struct clk *c, unsigned long rate);
-enum handoff vco_handoff(struct clk *c);
-int vco_prepare(struct clk *c);
-void vco_unprepare(struct clk *c);
-
-/* APIs for 20nm PHY PLL */
-int pll_20nm_vco_set_rate(struct dsi_pll_vco_clk *vco, unsigned long rate);
-int shadow_pll_20nm_vco_set_rate(struct dsi_pll_vco_clk *vco,
- unsigned long rate);
-long pll_20nm_vco_round_rate(struct clk *c, unsigned long rate);
-enum handoff pll_20nm_vco_handoff(struct clk *c);
-int pll_20nm_vco_prepare(struct clk *c);
-void pll_20nm_vco_unprepare(struct clk *c);
-int pll_20nm_vco_enable_seq(struct mdss_pll_resources *dsi_pll_res);
-
-int set_bypass_lp_div_mux_sel(struct mux_clk *clk, int sel);
-int set_shadow_bypass_lp_div_mux_sel(struct mux_clk *clk, int sel);
-int get_bypass_lp_div_mux_sel(struct mux_clk *clk);
-int fixed_hr_oclk2_set_div(struct div_clk *clk, int div);
-int shadow_fixed_hr_oclk2_set_div(struct div_clk *clk, int div);
-int fixed_hr_oclk2_get_div(struct div_clk *clk);
-int hr_oclk3_set_div(struct div_clk *clk, int div);
-int shadow_hr_oclk3_set_div(struct div_clk *clk, int div);
-int hr_oclk3_get_div(struct div_clk *clk);
-int ndiv_set_div(struct div_clk *clk, int div);
-int shadow_ndiv_set_div(struct div_clk *clk, int div);
-int ndiv_get_div(struct div_clk *clk);
-void __dsi_pll_disable(void __iomem *pll_base);
-
-int set_mdss_pixel_mux_sel(struct mux_clk *clk, int sel);
-int get_mdss_pixel_mux_sel(struct mux_clk *clk);
-int set_mdss_byte_mux_sel(struct mux_clk *clk, int sel);
-int get_mdss_byte_mux_sel(struct mux_clk *clk);
-
#endif
diff --git a/drivers/clk/qcom/mdss/mdss-pll-util.c b/drivers/clk/qcom/mdss/mdss-pll-util.c
index 690c53f..4d79772 100644
--- a/drivers/clk/qcom/mdss/mdss-pll-util.c
+++ b/drivers/clk/qcom/mdss/mdss-pll-util.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -16,7 +16,6 @@
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/string.h>
-#include <linux/clk/msm-clock-generic.h>
#include <linux/of_address.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
diff --git a/drivers/clk/qcom/mdss/mdss-pll.c b/drivers/clk/qcom/mdss/mdss-pll.c
index c22fa80..0a0d303 100644
--- a/drivers/clk/qcom/mdss/mdss-pll.c
+++ b/drivers/clk/qcom/mdss/mdss-pll.c
@@ -19,12 +19,8 @@
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
-#include <linux/clk/msm-clock-generic.h>
-
#include "mdss-pll.h"
#include "mdss-dsi-pll.h"
-#include "mdss-hdmi-pll.h"
-#include "mdss-dp-pll.h"
int mdss_pll_resource_enable(struct mdss_pll_resources *pll_res, bool enable)
{
@@ -128,32 +124,10 @@ static int mdss_pll_resource_parse(struct platform_device *pdev,
goto err;
}
- if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_8996")) {
- pll_res->pll_interface_type = MDSS_DSI_PLL_8996;
- pll_res->target_id = MDSS_PLL_TARGET_8996;
- pll_res->revision = 1;
- } else if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_8996_v2")) {
- pll_res->pll_interface_type = MDSS_DSI_PLL_8996;
- pll_res->target_id = MDSS_PLL_TARGET_8996;
- pll_res->revision = 2;
- } else if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_8998")) {
- pll_res->pll_interface_type = MDSS_DSI_PLL_8998;
- } else if (!strcmp(compatible_stream, "qcom,mdss_dp_pll_8998")) {
- pll_res->pll_interface_type = MDSS_DP_PLL_8998;
- } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8996")) {
- pll_res->pll_interface_type = MDSS_HDMI_PLL_8996;
- } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8996_v2")) {
- pll_res->pll_interface_type = MDSS_HDMI_PLL_8996_V2;
- } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8996_v3")) {
- pll_res->pll_interface_type = MDSS_HDMI_PLL_8996_V3;
- } else if (!strcmp(compatible_stream,
- "qcom,mdss_hdmi_pll_8996_v3_1p8")) {
- pll_res->pll_interface_type = MDSS_HDMI_PLL_8996_V3_1_8;
- } else if (!strcmp(compatible_stream, "qcom,mdss_hdmi_pll_8998")) {
- pll_res->pll_interface_type = MDSS_HDMI_PLL_8998;
- } else {
+ if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_10nm"))
+ pll_res->pll_interface_type = MDSS_DSI_PLL_10NM;
+ else
goto err;
- }
return rc;
@@ -174,29 +148,8 @@ static int mdss_pll_clock_register(struct platform_device *pdev,
}
switch (pll_res->pll_interface_type) {
- case MDSS_DSI_PLL_8996:
- rc = dsi_pll_clock_register_8996(pdev, pll_res);
- break;
- case MDSS_DSI_PLL_8998:
- rc = dsi_pll_clock_register_8998(pdev, pll_res);
- case MDSS_DP_PLL_8998:
- rc = dp_pll_clock_register_8998(pdev, pll_res);
- break;
- case MDSS_HDMI_PLL_8996:
- rc = hdmi_8996_v1_pll_clock_register(pdev, pll_res);
- break;
- case MDSS_HDMI_PLL_8996_V2:
- rc = hdmi_8996_v2_pll_clock_register(pdev, pll_res);
- break;
- case MDSS_HDMI_PLL_8996_V3:
- rc = hdmi_8996_v3_pll_clock_register(pdev, pll_res);
- break;
- case MDSS_HDMI_PLL_8996_V3_1_8:
- rc = hdmi_8996_v3_1p8_pll_clock_register(pdev, pll_res);
- break;
- case MDSS_HDMI_PLL_8998:
- rc = hdmi_8998_pll_clock_register(pdev, pll_res);
- break;
+ case MDSS_DSI_PLL_10NM:
+ rc = dsi_pll_clock_register_10nm(pdev, pll_res);
case MDSS_UNKNOWN_PLL:
default:
rc = -EINVAL;
@@ -392,15 +345,7 @@ static int mdss_pll_remove(struct platform_device *pdev)
}
static const struct of_device_id mdss_pll_dt_match[] = {
- {.compatible = "qcom,mdss_dsi_pll_8996"},
- {.compatible = "qcom,mdss_dsi_pll_8996_v2"},
- {.compatible = "qcom,mdss_dsi_pll_8998"},
- {.compatible = "qcom,mdss_hdmi_pll_8996"},
- {.compatible = "qcom,mdss_hdmi_pll_8996_v2"},
- {.compatible = "qcom,mdss_hdmi_pll_8996_v3"},
- {.compatible = "qcom,mdss_hdmi_pll_8996_v3_1p8"},
- {.compatible = "qcom,mdss_dp_pll_8998"},
- {.compatible = "qcom,mdss_hdmi_pll_8998"},
+ {.compatible = "qcom,mdss_dsi_pll_10nm"},
{}
};
diff --git a/drivers/clk/qcom/mdss/mdss-pll.h b/drivers/clk/qcom/mdss/mdss-pll.h
index 48dddf6..28b7ca6 100644
--- a/drivers/clk/qcom/mdss/mdss-pll.h
+++ b/drivers/clk/qcom/mdss/mdss-pll.h
@@ -12,10 +12,16 @@
#ifndef __MDSS_PLL_H
#define __MDSS_PLL_H
-
-#include <linux/mdss_io_util.h>
-#include <linux/clk/msm-clock-generic.h>
+#include <linux/sde_io_util.h>
+#include <linux/clk-provider.h>
#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/regmap.h>
+#include "../clk-regmap.h"
+#include "../clk-regmap-divider.h"
+#include "../clk-regmap-mux.h"
+
#define MDSS_PLL_REG_W(base, offset, data) \
writel_relaxed((data), (base) + (offset))
@@ -30,14 +36,7 @@
(base) + (offset))
enum {
- MDSS_DSI_PLL_8996,
- MDSS_DSI_PLL_8998,
- MDSS_DP_PLL_8998,
- MDSS_HDMI_PLL_8996,
- MDSS_HDMI_PLL_8996_V2,
- MDSS_HDMI_PLL_8996_V3,
- MDSS_HDMI_PLL_8996_V3_1_8,
- MDSS_HDMI_PLL_8998,
+ MDSS_DSI_PLL_10NM,
MDSS_UNKNOWN_PLL,
};
@@ -200,20 +199,24 @@ static inline bool is_gdsc_disabled(struct mdss_pll_resources *pll_res)
(!(readl_relaxed(pll_res->gdsc_base) & BIT(0)))) ? false : true;
}
-static inline int mdss_pll_div_prepare(struct clk *c)
+static inline int mdss_pll_div_prepare(struct clk_hw *hw)
{
- struct div_clk *div = to_div_clk(c);
+ struct clk_hw *parent_hw = clk_hw_get_parent(hw);
/* Restore the divider's value */
- return div->ops->set_div(div, div->data.div);
+ return hw->init->ops->set_rate(hw, clk_hw_get_rate(hw),
+ clk_hw_get_rate(parent_hw));
}
-static inline int mdss_set_mux_sel(struct mux_clk *clk, int sel)
+static inline int mdss_set_mux_sel(void *context, unsigned int reg,
+ unsigned int val)
{
return 0;
}
-static inline int mdss_get_mux_sel(struct mux_clk *clk)
+static inline int mdss_get_mux_sel(void *context, unsigned int reg,
+ unsigned int *val)
{
+ *val = 0;
return 0;
}
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c
index 2fcf10b..cc87775 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c
@@ -503,7 +503,7 @@ static int dsi_display_core_clk_enable(struct dsi_core_clks *clks,
goto error_disable_master;
}
}
-
+ return rc;
error_disable_master:
(void)dsi_core_clk_stop(m_clks);
diff --git a/drivers/gpu/drm/msm/sde/sde_ad4.h b/drivers/gpu/drm/msm/sde/sde_ad4.h
index 5ed7ae2..4a664a8 100644
--- a/drivers/gpu/drm/msm/sde/sde_ad4.h
+++ b/drivers/gpu/drm/msm/sde/sde_ad4.h
@@ -52,6 +52,14 @@ enum ad_property {
};
/**
+ * enum ad_intr_resp_property - ad4 interrupt response enum
+ */
+enum ad_intr_resp_property {
+ AD4_BACKLIGHT,
+ AD4_RESPMAX,
+};
+
+/**
* struct sde_ad_hw_cfg - structure for setting the ad properties
* @prop: enum of ad property
* @hw_cfg: payload for the prop being set.
@@ -76,4 +84,13 @@ int sde_validate_dspp_ad4(struct sde_hw_dspp *dspp, u32 *prop);
* @cfg: pointer to struct sde_ad_hw_cfg
*/
void sde_setup_dspp_ad4(struct sde_hw_dspp *dspp, void *cfg);
+
+/**
+ * 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
+ */
+void sde_read_intr_resp_ad4(struct sde_hw_dspp *dspp, u32 event, u32 *resp);
+
#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 cb6917a..79b39bd 100644
--- a/drivers/gpu/drm/msm/sde/sde_color_processing.c
+++ b/drivers/gpu/drm/msm/sde/sde_color_processing.c
@@ -22,6 +22,8 @@
#include "sde_hw_dspp.h"
#include "sde_hw_lm.h"
#include "sde_ad4.h"
+#include "sde_hw_interrupts.h"
+#include "sde_core_irq.h"
struct sde_cp_node {
u32 property_id;
@@ -35,6 +37,7 @@ struct sde_cp_node {
struct list_head dirty_list;
bool is_dspp_feature;
u32 prop_blob_sz;
+ struct sde_irq_callback *irq;
};
struct sde_cp_prop_attach {
@@ -67,6 +70,8 @@ static void sde_cp_update_list(struct sde_cp_node *prop_node,
static int sde_cp_ad_validate_prop(struct sde_cp_node *prop_node,
struct sde_crtc *crtc);
+static void sde_cp_notify_ad_event(struct drm_crtc *crtc_drm, void *arg);
+
#define setup_dspp_prop_install_funcs(func) \
do { \
func[SDE_DSPP_PCC] = dspp_pcc_install_property; \
@@ -1316,3 +1321,122 @@ static int sde_cp_ad_validate_prop(struct sde_cp_node *prop_node,
}
return ret;
}
+
+static void sde_cp_ad_interrupt_cb(void *arg, int irq_idx)
+{
+ struct sde_crtc *crtc = arg;
+
+ sde_crtc_event_queue(&crtc->base, sde_cp_notify_ad_event, NULL);
+}
+
+static void sde_cp_notify_ad_event(struct drm_crtc *crtc_drm, void *arg)
+{
+ uint32_t bl = 0;
+ struct sde_hw_mixer *hw_lm = NULL;
+ struct sde_hw_dspp *hw_dspp = NULL;
+ u32 num_mixers;
+ struct sde_crtc *crtc;
+ struct drm_event event;
+ int i;
+
+ crtc = to_sde_crtc(crtc_drm);
+ num_mixers = crtc->num_mixers;
+ if (!num_mixers)
+ return;
+
+ for (i = 0; i < num_mixers; i++) {
+ hw_lm = crtc->mixers[i].hw_lm;
+ hw_dspp = crtc->mixers[i].hw_dspp;
+ if (!hw_lm->cfg.right_mixer)
+ break;
+ }
+
+ if (!hw_dspp)
+ return;
+
+ hw_dspp->ops.ad_read_intr_resp(hw_dspp, AD4_BACKLIGHT, &bl);
+ event.length = sizeof(u32);
+ event.type = DRM_EVENT_AD_BACKLIGHT;
+ msm_send_crtc_notification(&crtc->base, &event, (u8 *)&bl);
+}
+
+int sde_cp_ad_interrupt(struct drm_crtc *crtc_drm, bool en,
+ struct sde_irq_callback *ad_irq)
+{
+ struct sde_kms *kms = NULL;
+ u32 num_mixers;
+ struct sde_hw_mixer *hw_lm;
+ struct sde_hw_dspp *hw_dspp = NULL;
+ struct sde_crtc *crtc;
+ int i;
+ int irq_idx, ret;
+ struct sde_cp_node prop_node;
+
+ if (!crtc_drm || !ad_irq) {
+ DRM_ERROR("invalid crtc %pK irq %pK\n", crtc_drm, ad_irq);
+ return -EINVAL;
+ }
+
+ crtc = to_sde_crtc(crtc_drm);
+ if (!crtc) {
+ DRM_ERROR("invalid sde_crtc %pK\n", crtc);
+ return -EINVAL;
+ }
+
+ mutex_lock(&crtc->crtc_lock);
+ kms = get_kms(crtc_drm);
+ num_mixers = crtc->num_mixers;
+
+ memset(&prop_node, 0, sizeof(prop_node));
+ prop_node.feature = SDE_CP_CRTC_DSPP_AD_BACKLIGHT;
+ ret = sde_cp_ad_validate_prop(&prop_node, crtc);
+ if (ret) {
+ DRM_ERROR("Ad not supported ret %d\n", ret);
+ goto exit;
+ }
+
+ for (i = 0; i < num_mixers; i++) {
+ hw_lm = crtc->mixers[i].hw_lm;
+ hw_dspp = crtc->mixers[i].hw_dspp;
+ if (!hw_lm->cfg.right_mixer)
+ break;
+ }
+
+ if (!hw_dspp) {
+ DRM_ERROR("invalid dspp\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ irq_idx = sde_core_irq_idx_lookup(kms, SDE_IRQ_TYPE_AD4_BL_DONE,
+ hw_dspp->idx);
+ if (irq_idx < 0) {
+ DRM_ERROR("failed to get the irq idx ret %d\n", irq_idx);
+ ret = irq_idx;
+ goto exit;
+ }
+
+ if (!en) {
+ sde_core_irq_disable(kms, &irq_idx, 1);
+ sde_core_irq_unregister_callback(kms, irq_idx, ad_irq);
+ ret = 0;
+ goto exit;
+ }
+
+ INIT_LIST_HEAD(&ad_irq->list);
+ ad_irq->arg = crtc;
+ ad_irq->func = sde_cp_ad_interrupt_cb;
+ ret = sde_core_irq_register_callback(kms, irq_idx, ad_irq);
+ if (ret) {
+ DRM_ERROR("failed to register the callback ret %d\n", ret);
+ goto exit;
+ }
+ ret = sde_core_irq_enable(kms, &irq_idx, 1);
+ if (ret) {
+ DRM_ERROR("failed to enable irq ret %d\n", ret);
+ sde_core_irq_unregister_callback(kms, irq_idx, ad_irq);
+ }
+exit:
+ mutex_unlock(&crtc->crtc_lock);
+ return ret;
+}
diff --git a/drivers/gpu/drm/msm/sde/sde_color_processing.h b/drivers/gpu/drm/msm/sde/sde_color_processing.h
index 9fa63f8..e78f690 100644
--- a/drivers/gpu/drm/msm/sde/sde_color_processing.h
+++ b/drivers/gpu/drm/msm/sde/sde_color_processing.h
@@ -15,6 +15,8 @@
#define _SDE_COLOR_PROCESSING_H
#include <drm/drm_crtc.h>
+struct sde_irq_callback;
+
/*
* PA MEMORY COLOR types
* @MEMCOLOR_SKIN Skin memory color type
@@ -92,4 +94,13 @@ void sde_cp_crtc_suspend(struct drm_crtc *crtc);
* @crtc: Pointer to crtc.
*/
void sde_cp_crtc_resume(struct drm_crtc *crtc);
+
+/**
+ * sde_cp_ad_interrupt: Api to enable/disable ad interrupt
+ * @crtc: Pointer to crtc.
+ * @en: Variable to enable/disable interrupt.
+ * @irq: Pointer to irq callback
+ */
+int sde_cp_ad_interrupt(struct drm_crtc *crtc, bool en,
+ struct sde_irq_callback *irq);
#endif /*_SDE_COLOR_PROCESSING_H */
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index acb5695..cec8792 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -45,6 +45,16 @@ struct sde_crtc_irq_info {
struct list_head list;
};
+struct sde_crtc_custom_events {
+ u32 event;
+ int (*func)(struct drm_crtc *crtc, bool en,
+ struct sde_irq_callback *irq);
+};
+
+static struct sde_crtc_custom_events custom_events[] = {
+ {DRM_EVENT_AD_BACKLIGHT, sde_cp_ad_interrupt}
+};
+
/* default input fence timeout, in ms */
#define SDE_CRTC_INPUT_FENCE_TIMEOUT 2000
@@ -107,6 +117,374 @@ static inline int _sde_crtc_power_enable(struct sde_crtc *sde_crtc, bool enable)
enable);
}
+/**
+ * _sde_crtc_rp_to_crtc - get crtc from resource pool object
+ * @rp: Pointer to resource pool
+ * return: Pointer to drm crtc if success; null otherwise
+ */
+static struct drm_crtc *_sde_crtc_rp_to_crtc(struct sde_crtc_respool *rp)
+{
+ if (!rp)
+ return NULL;
+
+ return container_of(rp, struct sde_crtc_state, rp)->base.crtc;
+}
+
+/**
+ * _sde_crtc_rp_reclaim - reclaim unused, or all if forced, resources in pool
+ * @rp: Pointer to resource pool
+ * @force: True to reclaim all resources; otherwise, reclaim only unused ones
+ * return: None
+ */
+static void _sde_crtc_rp_reclaim(struct sde_crtc_respool *rp, bool force)
+{
+ struct sde_crtc_res *res, *next;
+ struct drm_crtc *crtc;
+
+ crtc = _sde_crtc_rp_to_crtc(rp);
+ if (!crtc) {
+ SDE_ERROR("invalid crtc\n");
+ return;
+ }
+
+ SDE_DEBUG("crtc%d.%u %s\n", crtc->base.id, rp->sequence_id,
+ force ? "destroy" : "free_unused");
+
+ list_for_each_entry_safe(res, next, &rp->res_list, list) {
+ if (!force && !(res->flags & SDE_CRTC_RES_FLAG_FREE))
+ continue;
+ SDE_DEBUG("crtc%d.%u reclaim res:0x%x/0x%llx/%pK/%d\n",
+ crtc->base.id, rp->sequence_id,
+ res->type, res->tag, res->val,
+ atomic_read(&res->refcount));
+ list_del(&res->list);
+ if (res->ops.put)
+ res->ops.put(res->val);
+ kfree(res);
+ }
+}
+
+/**
+ * _sde_crtc_rp_free_unused - free unused resource in pool
+ * @rp: Pointer to resource pool
+ * return: none
+ */
+static void _sde_crtc_rp_free_unused(struct sde_crtc_respool *rp)
+{
+ _sde_crtc_rp_reclaim(rp, false);
+}
+
+/**
+ * _sde_crtc_rp_destroy - destroy resource pool
+ * @rp: Pointer to resource pool
+ * return: None
+ */
+static void _sde_crtc_rp_destroy(struct sde_crtc_respool *rp)
+{
+ _sde_crtc_rp_reclaim(rp, true);
+}
+
+/**
+ * _sde_crtc_hw_blk_get - get callback for hardware block
+ * @val: Resource handle
+ * @type: Resource type
+ * @tag: Search tag for given resource
+ * return: Resource handle
+ */
+static void *_sde_crtc_hw_blk_get(void *val, u32 type, u64 tag)
+{
+ SDE_DEBUG("res:%d/0x%llx/%pK\n", type, tag, val);
+ return sde_hw_blk_get(val, type, tag);
+}
+
+/**
+ * _sde_crtc_hw_blk_put - put callback for hardware block
+ * @val: Resource handle
+ * return: None
+ */
+static void _sde_crtc_hw_blk_put(void *val)
+{
+ SDE_DEBUG("res://%pK\n", val);
+ sde_hw_blk_put(val);
+}
+
+/**
+ * _sde_crtc_rp_duplicate - duplicate resource pool and reset reference count
+ * @rp: Pointer to original resource pool
+ * @dup_rp: Pointer to duplicated resource pool
+ * return: None
+ */
+static void _sde_crtc_rp_duplicate(struct sde_crtc_respool *rp,
+ struct sde_crtc_respool *dup_rp)
+{
+ struct sde_crtc_res *res, *dup_res;
+ struct drm_crtc *crtc;
+
+ if (!rp || !dup_rp) {
+ SDE_ERROR("invalid resource pool\n");
+ return;
+ }
+
+ crtc = _sde_crtc_rp_to_crtc(rp);
+ if (!crtc) {
+ SDE_ERROR("invalid crtc\n");
+ return;
+ }
+
+ SDE_DEBUG("crtc%d.%u duplicate\n", crtc->base.id, rp->sequence_id);
+
+ dup_rp->sequence_id = rp->sequence_id + 1;
+ INIT_LIST_HEAD(&dup_rp->res_list);
+ dup_rp->ops = rp->ops;
+ list_for_each_entry(res, &rp->res_list, list) {
+ dup_res = kzalloc(sizeof(struct sde_crtc_res), GFP_KERNEL);
+ if (!dup_res)
+ return;
+ INIT_LIST_HEAD(&dup_res->list);
+ atomic_set(&dup_res->refcount, 0);
+ dup_res->type = res->type;
+ dup_res->tag = res->tag;
+ dup_res->val = res->val;
+ dup_res->ops = res->ops;
+ dup_res->flags = SDE_CRTC_RES_FLAG_FREE;
+ SDE_DEBUG("crtc%d.%u dup res:0x%x/0x%llx/%pK/%d\n",
+ crtc->base.id, dup_rp->sequence_id,
+ dup_res->type, dup_res->tag, dup_res->val,
+ atomic_read(&dup_res->refcount));
+ list_add_tail(&dup_res->list, &dup_rp->res_list);
+ if (dup_res->ops.get)
+ dup_res->ops.get(dup_res->val, 0, -1);
+ }
+}
+
+/**
+ * _sde_crtc_rp_reset - reset resource pool after allocation
+ * @rp: Pointer to original resource pool
+ * return: None
+ */
+static void _sde_crtc_rp_reset(struct sde_crtc_respool *rp)
+{
+ if (!rp) {
+ SDE_ERROR("invalid resource pool\n");
+ return;
+ }
+
+ rp->sequence_id = 0;
+ INIT_LIST_HEAD(&rp->res_list);
+ rp->ops.get = _sde_crtc_hw_blk_get;
+ rp->ops.put = _sde_crtc_hw_blk_put;
+}
+
+/**
+ * _sde_crtc_rp_add - add given resource to resource pool
+ * @rp: Pointer to original resource pool
+ * @type: Resource type
+ * @tag: Search tag for given resource
+ * @val: Resource handle
+ * @ops: Resource callback operations
+ * return: 0 if success; error code otherwise
+ */
+static int _sde_crtc_rp_add(struct sde_crtc_respool *rp, u32 type, u64 tag,
+ void *val, struct sde_crtc_res_ops *ops)
+{
+ struct sde_crtc_res *res;
+ struct drm_crtc *crtc;
+
+ if (!rp || !ops) {
+ SDE_ERROR("invalid resource pool/ops\n");
+ return -EINVAL;
+ }
+
+ crtc = _sde_crtc_rp_to_crtc(rp);
+ if (!crtc) {
+ SDE_ERROR("invalid crtc\n");
+ return -EINVAL;
+ }
+
+ list_for_each_entry(res, &rp->res_list, list) {
+ if (res->type != type || res->tag != tag)
+ continue;
+ SDE_ERROR("crtc%d.%u already exist res:0x%x/0x%llx/%pK/%d\n",
+ crtc->base.id, rp->sequence_id,
+ res->type, res->tag, res->val,
+ atomic_read(&res->refcount));
+ return -EEXIST;
+ }
+ res = kzalloc(sizeof(struct sde_crtc_res), GFP_KERNEL);
+ if (!res)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&res->list);
+ atomic_set(&res->refcount, 1);
+ res->type = type;
+ res->tag = tag;
+ res->val = val;
+ res->ops = *ops;
+ list_add_tail(&res->list, &rp->res_list);
+ SDE_DEBUG("crtc%d.%u added res:0x%x/0x%llx\n",
+ crtc->base.id, rp->sequence_id, type, tag);
+ return 0;
+}
+
+/**
+ * _sde_crtc_rp_get - lookup the resource from given resource pool and obtain
+ * if available; otherwise, obtain resource from global pool
+ * @rp: Pointer to original resource pool
+ * @type: Resource type
+ * @tag: Search tag for given resource
+ * return: Resource handle if success; pointer error or null otherwise
+ */
+static void *_sde_crtc_rp_get(struct sde_crtc_respool *rp, u32 type, u64 tag)
+{
+ struct sde_crtc_res *res;
+ void *val = NULL;
+ int rc;
+ struct drm_crtc *crtc;
+
+ if (!rp) {
+ SDE_ERROR("invalid resource pool\n");
+ return NULL;
+ }
+
+ crtc = _sde_crtc_rp_to_crtc(rp);
+ if (!crtc) {
+ SDE_ERROR("invalid crtc\n");
+ return NULL;
+ }
+
+ list_for_each_entry(res, &rp->res_list, list) {
+ if (res->type != type || res->tag != tag)
+ continue;
+ SDE_DEBUG("crtc%d.%u found res:0x%x/0x%llx/%pK/%d\n",
+ crtc->base.id, rp->sequence_id,
+ res->type, res->tag, res->val,
+ atomic_read(&res->refcount));
+ atomic_inc(&res->refcount);
+ res->flags &= ~SDE_CRTC_RES_FLAG_FREE;
+ return res->val;
+ }
+ list_for_each_entry(res, &rp->res_list, list) {
+ if (res->type != type || !(res->flags & SDE_CRTC_RES_FLAG_FREE))
+ continue;
+ SDE_DEBUG("crtc%d.%u retag res:0x%x/0x%llx/%pK/%d\n",
+ crtc->base.id, rp->sequence_id,
+ res->type, res->tag, res->val,
+ atomic_read(&res->refcount));
+ atomic_inc(&res->refcount);
+ res->tag = tag;
+ res->flags &= ~SDE_CRTC_RES_FLAG_FREE;
+ return res->val;
+ }
+ if (rp->ops.get)
+ val = rp->ops.get(NULL, type, -1);
+ if (IS_ERR_OR_NULL(val)) {
+ SDE_ERROR("crtc%d.%u failed to get res:0x%x//\n",
+ crtc->base.id, rp->sequence_id, type);
+ return NULL;
+ }
+ rc = _sde_crtc_rp_add(rp, type, tag, val, &rp->ops);
+ if (rc) {
+ SDE_ERROR("crtc%d.%u failed to add res:0x%x/0x%llx\n",
+ crtc->base.id, rp->sequence_id, type, tag);
+ if (rp->ops.put)
+ rp->ops.put(val);
+ val = NULL;
+ }
+ return val;
+}
+
+/**
+ * _sde_crtc_rp_put - return given resource to resource pool
+ * @rp: Pointer to original resource pool
+ * @type: Resource type
+ * @tag: Search tag for given resource
+ * return: None
+ */
+static void _sde_crtc_rp_put(struct sde_crtc_respool *rp, u32 type, u64 tag)
+{
+ struct sde_crtc_res *res, *next;
+ struct drm_crtc *crtc;
+
+ if (!rp) {
+ SDE_ERROR("invalid resource pool\n");
+ return;
+ }
+
+ crtc = _sde_crtc_rp_to_crtc(rp);
+ if (!crtc) {
+ SDE_ERROR("invalid crtc\n");
+ return;
+ }
+
+ list_for_each_entry_safe(res, next, &rp->res_list, list) {
+ if (res->type != type || res->tag != tag)
+ continue;
+ SDE_DEBUG("crtc%d.%u found res:0x%x/0x%llx/%pK/%d\n",
+ crtc->base.id, rp->sequence_id,
+ res->type, res->tag, res->val,
+ atomic_read(&res->refcount));
+ if (res->flags & SDE_CRTC_RES_FLAG_FREE)
+ SDE_ERROR(
+ "crtc%d.%u already free res:0x%x/0x%llx/%pK/%d\n",
+ crtc->base.id, rp->sequence_id,
+ res->type, res->tag, res->val,
+ atomic_read(&res->refcount));
+ else if (atomic_dec_return(&res->refcount) == 0)
+ res->flags |= SDE_CRTC_RES_FLAG_FREE;
+
+ return;
+ }
+ SDE_ERROR("crtc%d.%u not found res:0x%x/0x%llx\n",
+ crtc->base.id, rp->sequence_id, type, tag);
+}
+
+int sde_crtc_res_add(struct drm_crtc_state *state, u32 type, u64 tag,
+ void *val, struct sde_crtc_res_ops *ops)
+{
+ struct sde_crtc_respool *rp;
+
+ if (!state) {
+ SDE_ERROR("invalid parameters\n");
+ return -EINVAL;
+ }
+
+ rp = &to_sde_crtc_state(state)->rp;
+ return _sde_crtc_rp_add(rp, type, tag, val, ops);
+}
+
+void *sde_crtc_res_get(struct drm_crtc_state *state, u32 type, u64 tag)
+{
+ struct sde_crtc_respool *rp;
+ void *val;
+
+ if (!state) {
+ SDE_ERROR("invalid parameters\n");
+ return NULL;
+ }
+
+ rp = &to_sde_crtc_state(state)->rp;
+ val = _sde_crtc_rp_get(rp, type, tag);
+ if (IS_ERR(val)) {
+ SDE_ERROR("failed to get res type:0x%x:0x%llx\n",
+ type, tag);
+ return NULL;
+ }
+
+ return val;
+}
+
+void sde_crtc_res_put(struct drm_crtc_state *state, u32 type, u64 tag)
+{
+ struct sde_crtc_respool *rp;
+
+ if (!state) {
+ SDE_ERROR("invalid parameters\n");
+ return;
+ }
+
+ rp = &to_sde_crtc_state(state)->rp;
+ _sde_crtc_rp_put(rp, type, tag);
+}
+
static void _sde_crtc_deinit_events(struct sde_crtc *sde_crtc)
{
if (!sde_crtc)
@@ -1061,6 +1439,8 @@ static void sde_crtc_destroy_state(struct drm_crtc *crtc,
SDE_DEBUG("crtc%d\n", crtc->base.id);
+ _sde_crtc_rp_destroy(&cstate->rp);
+
__drm_atomic_helper_crtc_destroy_state(state);
/* destroy value helper */
@@ -1252,6 +1632,8 @@ static struct drm_crtc_state *sde_crtc_duplicate_state(struct drm_crtc *crtc)
/* duplicate base helper */
__drm_atomic_helper_crtc_duplicate_state(crtc, &cstate->base);
+ _sde_crtc_rp_duplicate(&old_cstate->rp, &cstate->rp);
+
return &cstate->base;
}
@@ -1294,6 +1676,8 @@ static void sde_crtc_reset(struct drm_crtc *crtc)
_sde_crtc_set_input_fence_timeout(cstate);
+ _sde_crtc_rp_reset(&cstate->rp);
+
cstate->base.crtc = crtc;
crtc->state = &cstate->base;
}
@@ -1477,14 +1861,15 @@ static int sde_crtc_atomic_check(struct drm_crtc *crtc,
return -EINVAL;
}
+ sde_crtc = to_sde_crtc(crtc);
+ cstate = to_sde_crtc_state(state);
+
if (!state->enable || !state->active) {
SDE_DEBUG("crtc%d -> enable %d, active %d, skip atomic_check\n",
crtc->base.id, state->enable, state->active);
- return 0;
+ goto end;
}
- sde_crtc = to_sde_crtc(crtc);
- cstate = to_sde_crtc_state(state);
mode = &state->adjusted_mode;
SDE_DEBUG("%s: check", sde_crtc->name);
@@ -1692,6 +2077,7 @@ static int sde_crtc_atomic_check(struct drm_crtc *crtc,
end:
+ _sde_crtc_rp_free_unused(&cstate->rp);
return rc;
}
@@ -1897,6 +2283,9 @@ static int sde_crtc_atomic_set_property(struct drm_crtc *crtc,
}
if (ret)
DRM_ERROR("failed to set the property\n");
+
+ SDE_DEBUG("crtc%d %s[%d] <= 0x%llx ret=%d\n", crtc->base.id,
+ property->name, property->base.id, val, ret);
}
return ret;
@@ -2199,6 +2588,7 @@ static int sde_crtc_debugfs_state_show(struct seq_file *s, void *v)
{
struct drm_crtc *crtc = (struct drm_crtc *) s->private;
struct sde_crtc_state *cstate = to_sde_crtc_state(crtc->state);
+ struct sde_crtc_res *res;
seq_printf(s, "num_connectors: %d\n", cstate->num_connectors);
seq_printf(s, "client type: %d\n", sde_crtc_get_client_type(crtc));
@@ -2208,6 +2598,13 @@ static int sde_crtc_debugfs_state_show(struct seq_file *s, void *v)
seq_printf(s, "max_per_pipe_ib: %llu\n",
cstate->cur_perf.max_per_pipe_ib);
+ seq_printf(s, "rp.%d: ", cstate->rp.sequence_id);
+ list_for_each_entry(res, &cstate->rp.res_list, list)
+ seq_printf(s, "0x%x/0x%llx/%pK/%d ",
+ res->type, res->tag, res->val,
+ atomic_read(&res->refcount));
+ seq_puts(s, "\n");
+
return 0;
}
DEFINE_SDE_DEBUGFS_SEQ_FOPS(sde_crtc_debugfs_state);
@@ -2273,7 +2670,6 @@ static int _sde_crtc_init_debugfs(struct drm_crtc *crtc)
static void _sde_crtc_destroy_debugfs(struct drm_crtc *crtc)
{
- return 0;
}
#endif /* CONFIG_DEBUG_FS */
@@ -2322,21 +2718,22 @@ static void _sde_crtc_event_cb(struct kthread_work *work)
}
event = container_of(work, struct sde_crtc_event, kt_work);
- if (event->cb_func)
- event->cb_func(event->usr);
/* set sde_crtc to NULL for static work structures */
sde_crtc = event->sde_crtc;
if (!sde_crtc)
return;
+ if (event->cb_func)
+ event->cb_func(&sde_crtc->base, event->usr);
+
spin_lock_irqsave(&sde_crtc->event_lock, irq_flags);
list_add_tail(&event->list, &sde_crtc->event_free_list);
spin_unlock_irqrestore(&sde_crtc->event_lock, irq_flags);
}
int sde_crtc_event_queue(struct drm_crtc *crtc,
- void (*func)(void *usr), void *usr)
+ void (*func)(struct drm_crtc *crtc, void *usr), void *usr)
{
unsigned long irq_flags;
struct sde_crtc *sde_crtc;
@@ -2346,6 +2743,8 @@ int sde_crtc_event_queue(struct drm_crtc *crtc,
return -EINVAL;
sde_crtc = to_sde_crtc(crtc);
+ if (!sde_crtc->event_thread)
+ return -EINVAL;
/*
* Obtain an event struct from the private cache. This event
* queue may be called from ISR contexts, so use a private
@@ -2474,8 +2873,128 @@ struct drm_crtc *sde_crtc_init(struct drm_device *dev, struct drm_plane *plane)
return crtc;
}
-int sde_crtc_register_custom_event(struct sde_kms *kms,
- struct drm_crtc *crtc_drm, u32 event, bool val)
+static int _sde_crtc_event_enable(struct sde_kms *kms,
+ struct drm_crtc *crtc_drm, u32 event)
{
- return -EINVAL;
+ struct sde_crtc *crtc = NULL;
+ struct sde_crtc_irq_info *node;
+ struct msm_drm_private *priv;
+ unsigned long flags;
+ bool found = false;
+ int ret, i = 0;
+
+ crtc = to_sde_crtc(crtc_drm);
+ spin_lock_irqsave(&crtc->spin_lock, flags);
+ list_for_each_entry(node, &crtc->user_event_list, list) {
+ if (node->event == event) {
+ found = true;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&crtc->spin_lock, flags);
+
+ /* event already enabled */
+ if (found)
+ return 0;
+
+ node = NULL;
+ for (i = 0; i < ARRAY_SIZE(custom_events); i++) {
+ if (custom_events[i].event == event &&
+ custom_events[i].func) {
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
+ if (!node)
+ return -ENOMEM;
+ node->event = event;
+ INIT_LIST_HEAD(&node->list);
+ node->func = custom_events[i].func;
+ node->event = event;
+ break;
+ }
+ }
+
+ if (!node) {
+ SDE_ERROR("unsupported event %x\n", event);
+ return -EINVAL;
+ }
+
+ priv = kms->dev->dev_private;
+ ret = 0;
+ if (crtc_drm->enabled) {
+ sde_power_resource_enable(&priv->phandle, kms->core_client,
+ true);
+ ret = node->func(crtc_drm, true, &node->irq);
+ sde_power_resource_enable(&priv->phandle, kms->core_client,
+ false);
+ }
+
+ if (!ret) {
+ spin_lock_irqsave(&crtc->spin_lock, flags);
+ list_add_tail(&node->list, &crtc->user_event_list);
+ spin_unlock_irqrestore(&crtc->spin_lock, flags);
+ } else {
+ kfree(node);
+ }
+
+ return ret;
+}
+
+static int _sde_crtc_event_disable(struct sde_kms *kms,
+ struct drm_crtc *crtc_drm, u32 event)
+{
+ struct sde_crtc *crtc = NULL;
+ struct sde_crtc_irq_info *node = NULL;
+ struct msm_drm_private *priv;
+ unsigned long flags;
+ bool found = false;
+ int ret;
+
+ crtc = to_sde_crtc(crtc_drm);
+ spin_lock_irqsave(&crtc->spin_lock, flags);
+ list_for_each_entry(node, &crtc->user_event_list, list) {
+ if (node->event == event) {
+ list_del(&node->list);
+ found = true;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&crtc->spin_lock, flags);
+
+ /* event already disabled */
+ if (!found)
+ return 0;
+
+ /**
+ * crtc is disabled interrupts are cleared remove from the list,
+ * no need to disable/de-register.
+ */
+ if (!crtc_drm->enabled) {
+ kfree(node);
+ return 0;
+ }
+ priv = kms->dev->dev_private;
+ sde_power_resource_enable(&priv->phandle, kms->core_client, true);
+ ret = node->func(crtc_drm, false, &node->irq);
+ sde_power_resource_enable(&priv->phandle, kms->core_client, false);
+ return ret;
+}
+
+int sde_crtc_register_custom_event(struct sde_kms *kms,
+ struct drm_crtc *crtc_drm, u32 event, bool en)
+{
+ struct sde_crtc *crtc = NULL;
+ int ret;
+
+ crtc = to_sde_crtc(crtc_drm);
+ if (!crtc || !kms || !kms->dev) {
+ DRM_ERROR("invalid sde_crtc %pK kms %pK dev %pK\n", crtc,
+ kms, ((kms) ? (kms->dev) : NULL));
+ return -EINVAL;
+ }
+
+ if (en)
+ ret = _sde_crtc_event_enable(kms, crtc_drm, event);
+ else
+ ret = _sde_crtc_event_disable(kms, crtc_drm, event);
+
+ return ret;
}
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h
index 5934405..19ae27f 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.h
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.h
@@ -25,6 +25,7 @@
#include "sde_fence.h"
#include "sde_kms.h"
#include "sde_core_perf.h"
+#include "sde_hw_blk.h"
#define SDE_CRTC_NAME_SIZE 12
@@ -92,7 +93,7 @@ struct sde_crtc_event {
struct kthread_work kt_work;
void *sde_crtc;
- void (*cb_func)(void *usr);
+ void (*cb_func)(struct drm_crtc *crtc, void *usr);
void *usr;
};
@@ -191,6 +192,56 @@ struct sde_crtc {
#define to_sde_crtc(x) container_of(x, struct sde_crtc, base)
/**
+ * struct sde_crtc_res_ops - common operations for crtc resources
+ * @get: get given resource
+ * @put: put given resource
+ */
+struct sde_crtc_res_ops {
+ void *(*get)(void *val, u32 type, u64 tag);
+ void (*put)(void *val);
+};
+
+/* crtc resource type (0x0-0xffff reserved for hw block type */
+#define SDE_CRTC_RES_ROT_OUT_FBO 0x10000
+#define SDE_CRTC_RES_ROT_OUT_FB 0x10001
+#define SDE_CRTC_RES_ROT_PLANE 0x10002
+#define SDE_CRTC_RES_ROT_IN_FB 0x10003
+
+#define SDE_CRTC_RES_FLAG_FREE BIT(0)
+
+/**
+ * struct sde_crtc_res - definition of crtc resources
+ * @list: list of crtc resource
+ * @type: crtc resource type
+ * @tag: unique identifier per type
+ * @refcount: reference/usage count
+ * @ops: callback operations
+ * @val: resource handle associated with type/tag
+ * @flags: customization flags
+ */
+struct sde_crtc_res {
+ struct list_head list;
+ u32 type;
+ u64 tag;
+ atomic_t refcount;
+ struct sde_crtc_res_ops ops;
+ void *val;
+ u32 flags;
+};
+
+/**
+ * sde_crtc_respool - crtc resource pool
+ * @sequence_id: sequence identifier, incremented per state duplication
+ * @res_list: list of resource managed by this resource pool
+ * @ops: resource operations for parent resource pool
+ */
+struct sde_crtc_respool {
+ u32 sequence_id;
+ struct list_head res_list;
+ struct sde_crtc_res_ops ops;
+};
+
+/**
* struct sde_crtc_state - sde container for atomic crtc state
* @base: Base drm crtc state structure
* @connectors : Currently associated drm connectors
@@ -226,6 +277,8 @@ struct sde_crtc_state {
struct sde_core_perf_params new_perf;
struct sde_ctl_sbuf_cfg sbuf_cfg;
u64 sbuf_prefill_line;
+
+ struct sde_crtc_respool rp;
};
#define to_sde_crtc_state(x) \
@@ -368,6 +421,36 @@ static inline bool sde_crtc_is_enabled(struct drm_crtc *crtc)
* Returns: Zero on success
*/
int sde_crtc_event_queue(struct drm_crtc *crtc,
- void (*func)(void *usr), void *usr);
+ void (*func)(struct drm_crtc *crtc, void *usr), void *usr);
+
+/**
+ * sde_crtc_res_add - add given resource to resource pool in crtc state
+ * @state: Pointer to drm crtc state
+ * @type: Resource type
+ * @tag: Search tag for given resource
+ * @val: Resource handle
+ * @ops: Resource callback operations
+ * return: 0 if success; error code otherwise
+ */
+int sde_crtc_res_add(struct drm_crtc_state *state, u32 type, u64 tag,
+ void *val, struct sde_crtc_res_ops *ops);
+
+/**
+ * sde_crtc_res_get - get given resource from resource pool in crtc state
+ * @state: Pointer to drm crtc state
+ * @type: Resource type
+ * @tag: Search tag for given resource
+ * return: Resource handle if success; pointer error or null otherwise
+ */
+void *sde_crtc_res_get(struct drm_crtc_state *state, u32 type, u64 tag);
+
+/**
+ * sde_crtc_res_put - return given resource to resource pool in crtc state
+ * @state: Pointer to drm crtc state
+ * @type: Resource type
+ * @tag: Search tag for given resource
+ * return: None
+ */
+void sde_crtc_res_put(struct drm_crtc_state *state, u32 type, u64 tag);
#endif /* _SDE_CRTC_H_ */
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index 7ab4f8d..7137aaa 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -32,7 +32,6 @@
#include "sde_hw_ctl.h"
#include "sde_formats.h"
#include "sde_encoder_phys.h"
-#include "sde_color_processing.h"
#include "sde_power_handle.h"
#include "sde_hw_dsc.h"
@@ -1369,6 +1368,7 @@ int sde_encoder_helper_hw_release(struct sde_encoder_phys *phys_enc,
return 0;
}
+#ifdef CONFIG_DEBUG_FS
static int _sde_encoder_status_show(struct seq_file *s, void *data)
{
struct sde_encoder_virt *sde_enc;
@@ -1414,7 +1414,6 @@ static int _sde_encoder_status_show(struct seq_file *s, void *data)
return 0;
}
-#ifdef CONFIG_DEBUG_FS
static int _sde_encoder_debugfs_status_open(struct inode *inode,
struct file *file)
{
@@ -1590,7 +1589,7 @@ static int _sde_encoder_init_debugfs(struct drm_encoder *drm_enc)
return 0;
}
-static _sde_encoder_destroy_debugfs(struct drm_encoder *drm_enc)
+static void _sde_encoder_destroy_debugfs(struct drm_encoder *drm_enc)
{
}
#endif
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
index 39dfd5d..29f00f7 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
@@ -337,23 +337,40 @@ static void sde_encoder_phys_vid_vblank_irq(void *arg, int irq_idx)
{
struct sde_encoder_phys_vid *vid_enc = arg;
struct sde_encoder_phys *phys_enc;
+ struct sde_hw_ctl *hw_ctl;
unsigned long lock_flags;
- int new_cnt;
+ u32 flush_register = 0;
+ int new_cnt = -1, old_cnt = -1;
if (!vid_enc)
return;
phys_enc = &vid_enc->base;
+ hw_ctl = phys_enc->hw_ctl;
+
if (phys_enc->parent_ops.handle_vblank_virt)
phys_enc->parent_ops.handle_vblank_virt(phys_enc->parent,
phys_enc);
+ old_cnt = atomic_read(&phys_enc->pending_kickoff_cnt);
+
+ /*
+ * only decrement the pending flush count if we've actually flushed
+ * hardware. due to sw irq latency, vblank may have already happened
+ * so we need to double-check with hw that it accepted the flush bits
+ */
spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);
- new_cnt = atomic_add_unless(&phys_enc->pending_kickoff_cnt, -1, 0);
- SDE_EVT32_IRQ(DRMID(phys_enc->parent), vid_enc->hw_intf->idx - INTF_0,
- new_cnt);
+ if (hw_ctl && hw_ctl->ops.get_flush_register)
+ flush_register = hw_ctl->ops.get_flush_register(hw_ctl);
+
+ if (flush_register == 0)
+ new_cnt = atomic_add_unless(&phys_enc->pending_kickoff_cnt,
+ -1, 0);
spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
+ SDE_EVT32_IRQ(DRMID(phys_enc->parent), vid_enc->hw_intf->idx - INTF_0,
+ old_cnt, new_cnt, flush_register);
+
/* Signal any waiting atomic commit thread */
wake_up_all(&phys_enc->pending_kickoff_wq);
}
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_ad4.c b/drivers/gpu/drm/msm/sde/sde_hw_ad4.c
index 78fa634..7d2f67d 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_ad4.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_ad4.c
@@ -881,3 +881,20 @@ static int ad4_backlight_setup(struct sde_hw_dspp *dspp,
(*val & (BIT(16) - 1)));
return 0;
}
+
+void sde_read_intr_resp_ad4(struct sde_hw_dspp *dspp, u32 event, u32 *resp)
+{
+ if (!dspp || !resp) {
+ DRM_ERROR("invalid params dspp %pK resp %pK\n", dspp, resp);
+ return;
+ }
+
+ switch (event) {
+ case AD4_BACKLIGHT:
+ *resp = SDE_REG_READ(&dspp->hw,
+ dspp->cap->sblk->ad.base + 0x48);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_blk.c b/drivers/gpu/drm/msm/sde/sde_hw_blk.c
index 5ac017c..f59864d 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_blk.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_blk.c
@@ -29,9 +29,11 @@ static LIST_HEAD(sde_hw_blk_list);
* sde_hw_blk_init - initialize hw block object
* @type: hw block type - enum sde_hw_blk_type
* @id: instance id of the hw block
+ * @ops: Pointer to block operations
* return: 0 if success; error code otherwise
*/
-int sde_hw_blk_init(struct sde_hw_blk *hw_blk, u32 type, int id)
+int sde_hw_blk_init(struct sde_hw_blk *hw_blk, u32 type, int id,
+ struct sde_hw_blk_ops *ops)
{
if (!hw_blk) {
pr_err("invalid parameters\n");
@@ -42,7 +44,9 @@ int sde_hw_blk_init(struct sde_hw_blk *hw_blk, u32 type, int id)
hw_blk->type = type;
hw_blk->id = id;
atomic_set(&hw_blk->refcount, 0);
- INIT_LIST_HEAD(&hw_blk->attach_list);
+
+ if (ops)
+ hw_blk->ops = *ops;
mutex_lock(&sde_hw_blk_lock);
list_add(&hw_blk->list, &sde_hw_blk_list);
@@ -58,8 +62,6 @@ int sde_hw_blk_init(struct sde_hw_blk *hw_blk, u32 type, int id)
*/
void sde_hw_blk_destroy(struct sde_hw_blk *hw_blk)
{
- struct sde_hw_blk_attachment *curr, *next;
-
if (!hw_blk) {
pr_err("invalid parameters\n");
return;
@@ -69,14 +71,6 @@ void sde_hw_blk_destroy(struct sde_hw_blk *hw_blk)
pr_err("hw_blk:%d.%d invalid refcount\n", hw_blk->type,
hw_blk->id);
- list_for_each_entry_safe(curr, next, &hw_blk->attach_list, list) {
- pr_err("hw_blk:%d.%d tag:0x%x/0x%llx still attached\n",
- hw_blk->type, hw_blk->id,
- curr->tag, (u64) curr->value);
- list_del_init(&curr->list);
- kfree(curr);
- }
-
mutex_lock(&sde_hw_blk_lock);
list_del(&hw_blk->list);
mutex_unlock(&sde_hw_blk_lock);
@@ -92,6 +86,7 @@ void sde_hw_blk_destroy(struct sde_hw_blk *hw_blk)
struct sde_hw_blk *sde_hw_blk_get(struct sde_hw_blk *hw_blk, u32 type, int id)
{
struct sde_hw_blk *curr;
+ int rc, refcount;
if (!hw_blk) {
mutex_lock(&sde_hw_blk_lock);
@@ -108,16 +103,28 @@ struct sde_hw_blk *sde_hw_blk_get(struct sde_hw_blk *hw_blk, u32 type, int id)
mutex_unlock(&sde_hw_blk_lock);
}
- if (hw_blk) {
- int refcount = atomic_inc_return(&hw_blk->refcount);
-
- pr_debug("hw_blk:%d.%d refcount:%d\n", hw_blk->type,
- hw_blk->id, refcount);
- } else {
- pr_err("no hw_blk:%d\n", type);
+ if (!hw_blk) {
+ pr_debug("no hw_blk:%d\n", type);
+ return NULL;
}
+ refcount = atomic_inc_return(&hw_blk->refcount);
+
+ if (refcount == 1 && hw_blk->ops.start) {
+ rc = hw_blk->ops.start(hw_blk);
+ if (rc) {
+ pr_err("failed to start hw_blk:%d rc:%d\n", type, rc);
+ goto error_start;
+ }
+ }
+
+ pr_debug("hw_blk:%d.%d refcount:%d\n", hw_blk->type,
+ hw_blk->id, refcount);
return hw_blk;
+
+error_start:
+ sde_hw_blk_put(hw_blk);
+ return ERR_PTR(rc);
}
/**
@@ -125,11 +132,8 @@ struct sde_hw_blk *sde_hw_blk_get(struct sde_hw_blk *hw_blk, u32 type, int id)
* @hw_blk: hw block to be freed
* @free_blk: function to be called when reference count goes to zero
*/
-void sde_hw_blk_put(struct sde_hw_blk *hw_blk,
- void (*free_blk)(struct sde_hw_blk *))
+void sde_hw_blk_put(struct sde_hw_blk *hw_blk)
{
- struct sde_hw_blk_attachment *curr, *next;
-
if (!hw_blk) {
pr_err("invalid parameters\n");
return;
@@ -146,122 +150,6 @@ void sde_hw_blk_put(struct sde_hw_blk *hw_blk,
if (atomic_dec_return(&hw_blk->refcount))
return;
- if (free_blk)
- free_blk(hw_blk);
-
- /* report any residual attachments */
- list_for_each_entry_safe(curr, next, &hw_blk->attach_list, list) {
- pr_err("hw_blk:%d.%d tag:0x%x/0x%llx still attached\n",
- hw_blk->type, hw_blk->id,
- curr->tag, (u64) curr->value);
- list_del_init(&curr->list);
- kfree(curr);
- }
-}
-
-/**
- * sde_hw_blk_lookup_blk - lookup hardware block that matches tag/value/type
- * tuple and increment reference count
- * @tag: search tag
- * @value: value associated with search tag
- * @type: hardware block type
- * return: Pointer to hardware block
- */
-struct sde_hw_blk *sde_hw_blk_lookup_blk(u32 tag, void *value, u32 type)
-{
- struct sde_hw_blk *hw_blk = NULL, *curr;
- struct sde_hw_blk_attachment *attach;
-
- pr_debug("hw_blk:%d tag:0x%x/0x%llx\n", type, tag, (u64) value);
-
- mutex_lock(&sde_hw_blk_lock);
- list_for_each_entry(curr, &sde_hw_blk_list, list) {
- if ((curr->type != type) || !atomic_read(&curr->refcount))
- continue;
-
- list_for_each_entry(attach, &curr->attach_list, list) {
- if ((attach->tag != tag) || (attach->value != value))
- continue;
-
- hw_blk = curr;
- break;
- }
-
- if (hw_blk)
- break;
- }
- mutex_unlock(&sde_hw_blk_lock);
-
- if (hw_blk)
- sde_hw_blk_get(hw_blk, 0, -1);
-
- return hw_blk;
-}
-
-/**
- * sde_hw_blk_attach - attach given tag/value pair to hardware block
- * and increment reference count
- * @hw_blk: Pointer hardware block
- * @tag: search tag
- * @value: value associated with search tag
- * return: 0 if success; error code otherwise
- */
-int sde_hw_blk_attach(struct sde_hw_blk *hw_blk, u32 tag, void *value)
-{
- struct sde_hw_blk_attachment *attach;
-
- if (!hw_blk) {
- pr_err("invalid parameters\n");
- return -EINVAL;
- }
-
- pr_debug("hw_blk:%d.%d tag:0x%x/0x%llx\n", hw_blk->type, hw_blk->id,
- tag, (u64) value);
-
- attach = kzalloc(sizeof(struct sde_hw_blk_attachment), GFP_KERNEL);
- if (!attach)
- return -ENOMEM;
-
- INIT_LIST_HEAD(&attach->list);
- attach->tag = tag;
- attach->value = value;
- /* always add to the front so latest shows up first in search */
- list_add(&attach->list, &hw_blk->attach_list);
- sde_hw_blk_get(hw_blk, 0, -1);
-
- return 0;
-}
-
-/**
- * sde_hw_blk_detach - detach given tag/value pair from hardware block
- * and decrement reference count
- * @hw_blk: Pointer hardware block
- * @tag: search tag
- * @value: value associated with search tag
- * return: none
- */
-void sde_hw_blk_detach(struct sde_hw_blk *hw_blk, u32 tag, void *value)
-{
- struct sde_hw_blk_attachment *curr, *next;
-
- if (!hw_blk) {
- pr_err("invalid parameters\n");
- return;
- }
-
- pr_debug("hw_blk:%d.%d tag:0x%x/0x%llx\n", hw_blk->type, hw_blk->id,
- tag, (u64) value);
-
- list_for_each_entry_safe(curr, next, &hw_blk->attach_list, list) {
- if ((curr->tag != tag) || (curr->value != value))
- continue;
-
- list_del_init(&curr->list);
- kfree(curr);
- sde_hw_blk_put(hw_blk, NULL);
- return;
- }
-
- pr_err("hw_blk:%d.%d tag:0x%x/0x%llx not found\n", hw_blk->type,
- hw_blk->id, tag, (u64) value);
+ if (hw_blk->ops.stop)
+ hw_blk->ops.stop(hw_blk);
}
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_blk.h b/drivers/gpu/drm/msm/sde/sde_hw_blk.h
index ea4ba08..d979091 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_blk.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_blk.h
@@ -17,16 +17,16 @@
#include <linux/list.h>
#include <linux/atomic.h>
+struct sde_hw_blk;
+
/**
- * struct sde_hw_blk_attachment - hardware block attachment
- * @list: list of attachment
- * @tag: search tag
- * @value: value associated with the given tag
+ * struct sde_hw_blk_ops - common hardware block operations
+ * @start: start operation on first get
+ * @stop: stop operation on last put
*/
-struct sde_hw_blk_attachment {
- struct list_head list;
- u32 tag;
- void *value;
+struct sde_hw_blk_ops {
+ int (*start)(struct sde_hw_blk *);
+ void (*stop)(struct sde_hw_blk *);
};
/**
@@ -35,53 +35,19 @@ struct sde_hw_blk_attachment {
* @type: hardware block type
* @id: instance id
* @refcount: reference/usage count
- * @attachment_list: list of attachment
*/
struct sde_hw_blk {
struct list_head list;
u32 type;
int id;
atomic_t refcount;
- struct list_head attach_list;
+ struct sde_hw_blk_ops ops;
};
-int sde_hw_blk_init(struct sde_hw_blk *hw_blk, u32 type, int id);
+int sde_hw_blk_init(struct sde_hw_blk *hw_blk, u32 type, int id,
+ struct sde_hw_blk_ops *ops);
void sde_hw_blk_destroy(struct sde_hw_blk *hw_blk);
struct sde_hw_blk *sde_hw_blk_get(struct sde_hw_blk *hw_blk, u32 type, int id);
-void sde_hw_blk_put(struct sde_hw_blk *hw_blk,
- void (*blk_free)(struct sde_hw_blk *));
-
-struct sde_hw_blk *sde_hw_blk_lookup_blk(u32 tag, void *value, u32 type);
-int sde_hw_blk_attach(struct sde_hw_blk *hw_blk, u32 tag, void *value);
-void sde_hw_blk_detach(struct sde_hw_blk *hw_blk, u32 tag, void *value);
-
-/**
- * sde_hw_blk_lookup_value - return value associated with the given tag
- * @hw_blk: Pointer to hardware block
- * @tag: tag to find
- * @idx: index if more than one value found, with 0 being first
- * return: value associated with the given tag
- */
-static inline void *sde_hw_blk_lookup_value(struct sde_hw_blk *hw_blk,
- u32 tag, u32 idx)
-{
- struct sde_hw_blk_attachment *attach;
-
- if (!hw_blk)
- return NULL;
-
- list_for_each_entry(attach, &hw_blk->attach_list, list) {
- if (attach->tag != tag)
- continue;
-
- if (idx == 0)
- return attach->value;
-
- idx--;
- }
-
- return NULL;
-}
-
+void sde_hw_blk_put(struct sde_hw_blk *hw_blk);
#endif /*_SDE_HW_BLK_H */
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_ctl.c b/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
index f7bdc96..82f1c09 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_ctl.c
@@ -107,6 +107,12 @@ static inline void sde_hw_ctl_trigger_flush(struct sde_hw_ctl *ctx)
SDE_REG_WRITE(&ctx->hw, CTL_FLUSH, ctx->pending_flush_mask);
}
+static inline u32 sde_hw_ctl_get_flush_register(struct sde_hw_ctl *ctx)
+{
+ struct sde_hw_blk_reg_map *c = &ctx->hw;
+
+ return SDE_REG_READ(c, CTL_FLUSH);
+}
static inline uint32_t sde_hw_ctl_get_bitmask_sspp(struct sde_hw_ctl *ctx,
enum sde_sspp sspp)
@@ -529,6 +535,7 @@ static void _setup_ctl_ops(struct sde_hw_ctl_ops *ops,
ops->update_pending_flush = sde_hw_ctl_update_pending_flush;
ops->get_pending_flush = sde_hw_ctl_get_pending_flush;
ops->trigger_flush = sde_hw_ctl_trigger_flush;
+ ops->get_flush_register = sde_hw_ctl_get_flush_register;
ops->trigger_start = sde_hw_ctl_trigger_start;
ops->setup_intf_cfg = sde_hw_ctl_intf_cfg;
ops->reset = sde_hw_ctl_reset_control;
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_ctl.h b/drivers/gpu/drm/msm/sde/sde_hw_ctl.h
index a4e3bfe..7ae43b7 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_ctl.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_ctl.h
@@ -127,6 +127,13 @@ struct sde_hw_ctl_ops {
void (*trigger_flush)(struct sde_hw_ctl *ctx);
/**
+ * Read the value of the flush register
+ * @ctx : ctl path ctx pointer
+ * @Return: value of the ctl flush register.
+ */
+ u32 (*get_flush_register)(struct sde_hw_ctl *ctx);
+
+ /**
* Setup ctl_path interface config
* @ctx
* @cfg : interface config structure pointer
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_dspp.c b/drivers/gpu/drm/msm/sde/sde_hw_dspp.c
index 51680d3..f1b9c32 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_dspp.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_dspp.c
@@ -101,6 +101,8 @@ static void _setup_dspp_ops(struct sde_hw_dspp *c, unsigned long features)
if (c->cap->sblk->ad.version ==
SDE_COLOR_PROCESS_VER(4, 0)) {
c->ops.setup_ad = sde_setup_dspp_ad4;
+ c->ops.ad_read_intr_resp =
+ sde_read_intr_resp_ad4;
c->ops.validate_ad = sde_validate_dspp_ad4;
}
break;
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_dspp.h b/drivers/gpu/drm/msm/sde/sde_hw_dspp.h
index 455daa4..6020476 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_dspp.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_dspp.h
@@ -153,6 +153,15 @@ struct sde_hw_dspp_ops {
* @cfg: Pointer to ad configuration
*/
void (*setup_ad)(struct sde_hw_dspp *ctx, void *cfg);
+
+ /**
+ * 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.
+ */
+ void (*ad_read_intr_resp)(struct sde_hw_dspp *ctx, u32 event,
+ u32 *resp);
+
};
/**
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_interrupts.c b/drivers/gpu/drm/msm/sde/sde_hw_interrupts.c
index e68e3c9..47fb07f 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_interrupts.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_interrupts.c
@@ -29,6 +29,11 @@
#define MDP_INTF_2_OFF 0x6C000
#define MDP_INTF_3_OFF 0x6C800
#define MDP_INTF_4_OFF 0x6D000
+#define MDP_AD4_0_OFF 0x7D000
+#define MDP_AD4_1_OFF 0x7E000
+#define MDP_AD4_INTR_EN_OFF 0x41c
+#define MDP_AD4_INTR_CLEAR_OFF 0x424
+#define MDP_AD4_INTR_STATUS_OFF 0x420
/**
* WB interrupt status bit definitions
@@ -155,6 +160,14 @@
#define SDE_INTR_PROG_LINE BIT(8)
/**
+ * AD4 interrupt status bit definitions
+ */
+#define SDE_INTR_BRIGHTPR_UPDATED BIT(4)
+#define SDE_INTR_DARKENH_UPDATED BIT(3)
+#define SDE_INTR_STREN_OUTROI_UPDATED BIT(2)
+#define SDE_INTR_STREN_INROI_UPDATED BIT(1)
+#define SDE_INTR_BACKLIGHT_UPDATED BIT(0)
+/**
* struct sde_intr_reg - array of SDE register sets
* @clr_off: offset to CLEAR reg
* @en_off: offset to ENABLE reg
@@ -223,6 +236,16 @@ static const struct sde_intr_reg sde_intr_set[] = {
MDP_INTF_4_OFF+INTF_INTR_CLEAR,
MDP_INTF_4_OFF+INTF_INTR_EN,
MDP_INTF_4_OFF+INTF_INTR_STATUS
+ },
+ {
+ MDP_AD4_0_OFF + MDP_AD4_INTR_CLEAR_OFF,
+ MDP_AD4_0_OFF + MDP_AD4_INTR_EN_OFF,
+ MDP_AD4_0_OFF + MDP_AD4_INTR_STATUS_OFF,
+ },
+ {
+ MDP_AD4_1_OFF + MDP_AD4_INTR_CLEAR_OFF,
+ MDP_AD4_1_OFF + MDP_AD4_INTR_EN_OFF,
+ MDP_AD4_1_OFF + MDP_AD4_INTR_STATUS_OFF,
}
};
@@ -648,6 +671,10 @@ static const struct sde_irq_type sde_irq_map[] = {
{ SDE_IRQ_TYPE_RESERVED, 0, 0, 7},
{ SDE_IRQ_TYPE_RESERVED, 0, 0, 7},
{ SDE_IRQ_TYPE_RESERVED, 0, 0, 7},
+
+ /* irq_idx: 256-257 */
+ { SDE_IRQ_TYPE_AD4_BL_DONE, DSPP_0, SDE_INTR_BACKLIGHT_UPDATED, 8},
+ { SDE_IRQ_TYPE_AD4_BL_DONE, DSPP_1, SDE_INTR_BACKLIGHT_UPDATED, 9}
};
static int sde_hw_intr_irqidx_lookup(enum sde_intr_type intr_type,
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_interrupts.h b/drivers/gpu/drm/msm/sde/sde_hw_interrupts.h
index 261ef64..7805df1 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_interrupts.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_interrupts.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -54,6 +54,7 @@
* @SDE_IRQ_TYPE_SFI_CMD_2_IN: DSI CMD2 static frame INTR into static
* @SDE_IRQ_TYPE_SFI_CMD_2_OUT: DSI CMD2 static frame INTR out-of static
* @SDE_IRQ_TYPE_PROG_LINE: Programmable Line interrupt
+ * @SDE_IRQ_TYPE_AD4_BL_DONE: AD4 backlight
* @SDE_IRQ_TYPE_RESERVED: Reserved for expansion
*/
enum sde_intr_type {
@@ -82,6 +83,7 @@ enum sde_intr_type {
SDE_IRQ_TYPE_SFI_CMD_2_IN,
SDE_IRQ_TYPE_SFI_CMD_2_OUT,
SDE_IRQ_TYPE_PROG_LINE,
+ SDE_IRQ_TYPE_AD4_BL_DONE,
SDE_IRQ_TYPE_RESERVED,
};
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_rot.c b/drivers/gpu/drm/msm/sde/sde_hw_rot.c
index f79dc08..01fe3c8 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_rot.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_rot.c
@@ -120,37 +120,37 @@ static int sde_hw_rot_to_v4l2_pixfmt(u32 drm_pixfmt, u64 drm_modifier,
return -EINVAL;
switch (drm_pixfmt) {
- case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
if (SDE_MODIFIER_IS_UBWC(drm_modifier))
*pixfmt = SDE_PIX_FMT_RGB_565_UBWC;
else
*pixfmt = SDE_PIX_FMT_RGB_565;
break;
- case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_BGRA8888:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_ARGB_8888_TILE;
else
*pixfmt = SDE_PIX_FMT_ARGB_8888;
break;
- case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_BGRX8888:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_XRGB_8888_TILE;
else
*pixfmt = SDE_PIX_FMT_XRGB_8888;
break;
- case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_RGBA8888:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_ABGR_8888_TILE;
else
*pixfmt = SDE_PIX_FMT_ABGR_8888;
break;
- case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBX8888:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_XBGR_8888_TILE;
else
*pixfmt = SDE_PIX_FMT_XBGR_8888;
break;
- case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_ABGR8888:
if (SDE_MODIFIER_IS_UBWC(drm_modifier))
*pixfmt = SDE_PIX_FMT_RGBA_8888_UBWC;
else if (SDE_MODIFIER_IS_TILE(drm_modifier))
@@ -158,7 +158,7 @@ static int sde_hw_rot_to_v4l2_pixfmt(u32 drm_pixfmt, u64 drm_modifier,
else
*pixfmt = SDE_PIX_FMT_RGBA_8888;
break;
- case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_XBGR8888:
if (SDE_MODIFIER_IS_UBWC(drm_modifier))
*pixfmt = SDE_PIX_FMT_RGBX_8888_UBWC;
else if (SDE_MODIFIER_IS_TILE(drm_modifier))
@@ -166,13 +166,13 @@ static int sde_hw_rot_to_v4l2_pixfmt(u32 drm_pixfmt, u64 drm_modifier,
else
*pixfmt = SDE_PIX_FMT_RGBX_8888;
break;
- case DRM_FORMAT_BGRA8888:
+ case DRM_FORMAT_ARGB8888:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_BGRA_8888_TILE;
else
*pixfmt = SDE_PIX_FMT_BGRA_8888;
break;
- case DRM_FORMAT_BGRX8888:
+ case DRM_FORMAT_XRGB8888:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_BGRX_8888_TILE;
else
@@ -220,43 +220,43 @@ static int sde_hw_rot_to_v4l2_pixfmt(u32 drm_pixfmt, u64 drm_modifier,
else
*pixfmt = SDE_PIX_FMT_Y_CRCB_H2V2;
break;
- case DRM_FORMAT_ARGB2101010:
+ case DRM_FORMAT_BGRA1010102:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_ARGB_2101010_TILE;
else
*pixfmt = SDE_PIX_FMT_ARGB_2101010;
break;
- case DRM_FORMAT_XRGB2101010:
+ case DRM_FORMAT_BGRX1010102:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_XRGB_2101010_TILE;
else
*pixfmt = SDE_PIX_FMT_XRGB_2101010;
break;
- case DRM_FORMAT_ABGR2101010:
+ case DRM_FORMAT_RGBA1010102:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_ABGR_2101010_TILE;
else
*pixfmt = SDE_PIX_FMT_ABGR_2101010;
break;
- case DRM_FORMAT_XBGR2101010:
+ case DRM_FORMAT_RGBX1010102:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_XBGR_2101010_TILE;
else
*pixfmt = SDE_PIX_FMT_XBGR_2101010;
break;
- case DRM_FORMAT_BGRA1010102:
+ case DRM_FORMAT_ARGB2101010:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_BGRA_1010102_TILE;
else
*pixfmt = SDE_PIX_FMT_BGRA_1010102;
break;
- case DRM_FORMAT_BGRX1010102:
+ case DRM_FORMAT_XRGB2101010:
if (SDE_MODIFIER_IS_TILE(drm_modifier))
*pixfmt = SDE_PIX_FMT_BGRX_1010102_TILE;
else
*pixfmt = SDE_PIX_FMT_BGRX_1010102;
break;
- case DRM_FORMAT_RGBA1010102:
+ case DRM_FORMAT_ABGR2101010:
if (SDE_MODIFIER_IS_UBWC(drm_modifier))
*pixfmt = SDE_PIX_FMT_RGBA_1010102_UBWC;
else if (SDE_MODIFIER_IS_TILE(drm_modifier))
@@ -264,7 +264,7 @@ static int sde_hw_rot_to_v4l2_pixfmt(u32 drm_pixfmt, u64 drm_modifier,
else
*pixfmt = SDE_PIX_FMT_RGBA_1010102;
break;
- case DRM_FORMAT_RGBX1010102:
+ case DRM_FORMAT_XBGR2101010:
if (SDE_MODIFIER_IS_UBWC(drm_modifier))
*pixfmt = SDE_PIX_FMT_RGBX_1010102_UBWC;
else if (SDE_MODIFIER_IS_TILE(drm_modifier))
@@ -298,28 +298,28 @@ static int sde_hw_rot_to_drm_pixfmt(u32 pixfmt, u32 *drm_pixfmt,
switch (pixfmt) {
case SDE_PIX_FMT_RGB_565:
- *drm_pixfmt = DRM_FORMAT_RGB565;
+ *drm_pixfmt = DRM_FORMAT_BGR565;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_RGB_565_UBWC:
- *drm_pixfmt = DRM_FORMAT_RGB565;
+ *drm_pixfmt = DRM_FORMAT_BGR565;
*drm_modifier = DRM_FORMAT_MOD_QCOM_COMPRESSED |
DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_RGBA_8888:
- *drm_pixfmt = DRM_FORMAT_RGBA8888;
+ *drm_pixfmt = DRM_FORMAT_ABGR8888;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_RGBX_8888:
- *drm_pixfmt = DRM_FORMAT_RGBX8888;
+ *drm_pixfmt = DRM_FORMAT_XBGR8888;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_BGRA_8888:
- *drm_pixfmt = DRM_FORMAT_BGRA8888;
+ *drm_pixfmt = DRM_FORMAT_ARGB8888;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_BGRX_8888:
- *drm_pixfmt = DRM_FORMAT_BGRX8888;
+ *drm_pixfmt = DRM_FORMAT_XRGB8888;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_Y_CBCR_H2V2_UBWC:
@@ -332,12 +332,12 @@ static int sde_hw_rot_to_drm_pixfmt(u32 pixfmt, u32 *drm_pixfmt,
*drm_modifier = 0;
break;
case SDE_PIX_FMT_RGBA_8888_UBWC:
- *drm_pixfmt = DRM_FORMAT_RGBA8888;
+ *drm_pixfmt = DRM_FORMAT_ABGR8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_COMPRESSED |
DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_RGBX_8888_UBWC:
- *drm_pixfmt = DRM_FORMAT_RGBX8888;
+ *drm_pixfmt = DRM_FORMAT_XBGR8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_COMPRESSED |
DRM_FORMAT_MOD_QCOM_TILE;
break;
@@ -346,59 +346,59 @@ static int sde_hw_rot_to_drm_pixfmt(u32 pixfmt, u32 *drm_pixfmt,
*drm_modifier = 0;
break;
case SDE_PIX_FMT_ARGB_8888:
- *drm_pixfmt = DRM_FORMAT_ARGB8888;
+ *drm_pixfmt = DRM_FORMAT_BGRA8888;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_XRGB_8888:
- *drm_pixfmt = DRM_FORMAT_XRGB8888;
+ *drm_pixfmt = DRM_FORMAT_BGRX8888;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_ABGR_8888:
- *drm_pixfmt = DRM_FORMAT_ABGR8888;
+ *drm_pixfmt = DRM_FORMAT_RGBA8888;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_XBGR_8888:
- *drm_pixfmt = DRM_FORMAT_XBGR8888;
+ *drm_pixfmt = DRM_FORMAT_RGBX8888;
*drm_modifier = 0;
break;
case SDE_PIX_FMT_ARGB_2101010:
- *drm_pixfmt = DRM_FORMAT_ARGB2101010;
- *drm_modifier = 0;
- break;
- case SDE_PIX_FMT_XRGB_2101010:
- *drm_pixfmt = DRM_FORMAT_XRGB2101010;
- *drm_modifier = 0;
- break;
- case SDE_PIX_FMT_ABGR_2101010:
- *drm_pixfmt = DRM_FORMAT_ABGR2101010;
- *drm_modifier = 0;
- break;
- case SDE_PIX_FMT_XBGR_2101010:
- *drm_pixfmt = DRM_FORMAT_XBGR2101010;
- *drm_modifier = 0;
- break;
- case SDE_PIX_FMT_BGRA_1010102:
*drm_pixfmt = DRM_FORMAT_BGRA1010102;
*drm_modifier = 0;
break;
- case SDE_PIX_FMT_BGRX_1010102:
+ case SDE_PIX_FMT_XRGB_2101010:
*drm_pixfmt = DRM_FORMAT_BGRX1010102;
*drm_modifier = 0;
break;
+ case SDE_PIX_FMT_ABGR_2101010:
+ *drm_pixfmt = DRM_FORMAT_RGBA1010102;
+ *drm_modifier = 0;
+ break;
+ case SDE_PIX_FMT_XBGR_2101010:
+ *drm_pixfmt = DRM_FORMAT_RGBX1010102;
+ *drm_modifier = 0;
+ break;
+ case SDE_PIX_FMT_BGRA_1010102:
+ *drm_pixfmt = DRM_FORMAT_ARGB2101010;
+ *drm_modifier = 0;
+ break;
+ case SDE_PIX_FMT_BGRX_1010102:
+ *drm_pixfmt = DRM_FORMAT_XRGB2101010;
+ *drm_modifier = 0;
+ break;
case SDE_PIX_FMT_RGBA_8888_TILE:
- *drm_pixfmt = DRM_FORMAT_RGBA8888;
+ *drm_pixfmt = DRM_FORMAT_ABGR8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_RGBX_8888_TILE:
- *drm_pixfmt = DRM_FORMAT_RGBX8888;
+ *drm_pixfmt = DRM_FORMAT_XBGR8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_BGRA_8888_TILE:
- *drm_pixfmt = DRM_FORMAT_BGRA8888;
+ *drm_pixfmt = DRM_FORMAT_ARGB8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_BGRX_8888_TILE:
- *drm_pixfmt = DRM_FORMAT_BGRX8888;
+ *drm_pixfmt = DRM_FORMAT_XRGB8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_Y_CRCB_H2V2_TILE:
@@ -410,45 +410,55 @@ static int sde_hw_rot_to_drm_pixfmt(u32 pixfmt, u32 *drm_pixfmt,
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_ARGB_8888_TILE:
- *drm_pixfmt = DRM_FORMAT_ARGB8888;
+ *drm_pixfmt = DRM_FORMAT_BGRA8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_XRGB_8888_TILE:
- *drm_pixfmt = DRM_FORMAT_XRGB8888;
+ *drm_pixfmt = DRM_FORMAT_BGRX8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_ABGR_8888_TILE:
- *drm_pixfmt = DRM_FORMAT_ABGR8888;
+ *drm_pixfmt = DRM_FORMAT_RGBA8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_XBGR_8888_TILE:
- *drm_pixfmt = DRM_FORMAT_XBGR8888;
+ *drm_pixfmt = DRM_FORMAT_RGBX8888;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
case SDE_PIX_FMT_ARGB_2101010_TILE:
- *drm_pixfmt = DRM_FORMAT_ARGB2101010;
- *drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
- break;
- case SDE_PIX_FMT_XRGB_2101010_TILE:
- *drm_pixfmt = DRM_FORMAT_XRGB2101010;
- *drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
- break;
- case SDE_PIX_FMT_ABGR_2101010_TILE:
- *drm_pixfmt = DRM_FORMAT_ABGR2101010;
- *drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
- break;
- case SDE_PIX_FMT_XBGR_2101010_TILE:
- *drm_pixfmt = DRM_FORMAT_XBGR2101010;
- *drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
- break;
- case SDE_PIX_FMT_BGRA_1010102_TILE:
*drm_pixfmt = DRM_FORMAT_BGRA1010102;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
- case SDE_PIX_FMT_BGRX_1010102_TILE:
+ case SDE_PIX_FMT_XRGB_2101010_TILE:
*drm_pixfmt = DRM_FORMAT_BGRX1010102;
*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
break;
+ case SDE_PIX_FMT_ABGR_2101010_TILE:
+ *drm_pixfmt = DRM_FORMAT_RGBA1010102;
+ *drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
+ break;
+ case SDE_PIX_FMT_XBGR_2101010_TILE:
+ *drm_pixfmt = DRM_FORMAT_RGBX1010102;
+ *drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
+ break;
+ case SDE_PIX_FMT_BGRA_1010102_TILE:
+ *drm_pixfmt = DRM_FORMAT_ARGB2101010;
+ *drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
+ break;
+ case SDE_PIX_FMT_BGRX_1010102_TILE:
+ *drm_pixfmt = DRM_FORMAT_XRGB2101010;
+ *drm_modifier = DRM_FORMAT_MOD_QCOM_TILE;
+ break;
+ case SDE_PIX_FMT_RGBA_1010102_UBWC:
+ *drm_pixfmt = DRM_FORMAT_ABGR2101010;
+ *drm_modifier = DRM_FORMAT_MOD_QCOM_COMPRESSED |
+ DRM_FORMAT_MOD_QCOM_TILE;
+ break;
+ case SDE_PIX_FMT_RGBX_1010102_UBWC:
+ *drm_pixfmt = DRM_FORMAT_XBGR2101010;
+ *drm_modifier = DRM_FORMAT_MOD_QCOM_COMPRESSED |
+ DRM_FORMAT_MOD_QCOM_TILE;
+ break;
case SDE_PIX_FMT_Y_CBCR_H2V2_P010:
*drm_pixfmt = DRM_FORMAT_NV12;
*drm_modifier = DRM_FORMAT_MOD_QCOM_DX;
@@ -779,6 +789,25 @@ static size_t sde_hw_rot_get_cache_size(struct sde_hw_rot *hw)
}
/**
+ * sde_hw_rot_get_maxlinewidth - get maximum line width of rotator
+ * @hw: Pointer to rotator hardware driver
+ * return: maximum line width
+ */
+static int sde_hw_rot_get_maxlinewidth(struct sde_hw_rot *hw)
+{
+ struct platform_device *pdev;
+
+ if (!hw || !hw->caps || !hw->caps->pdev) {
+ SDE_ERROR("invalid rotator hw\n");
+ return 0;
+ }
+
+ pdev = hw->caps->pdev;
+
+ return sde_rotator_inline_get_maxlinewidth(pdev);
+}
+
+/**
* _setup_rot_ops - setup rotator operations
* @ops: Pointer to operation table
* @features: available feature bitmask
@@ -790,64 +819,7 @@ static void _setup_rot_ops(struct sde_hw_rot_ops *ops, unsigned long features)
ops->get_format_caps = sde_hw_rot_get_format_caps;
ops->get_downscale_caps = sde_hw_rot_get_downscale_caps;
ops->get_cache_size = sde_hw_rot_get_cache_size;
-}
-
-/**
- * sde_hw_rot_init - create/initialize given rotator instance
- * @idx: index of given rotator
- * @addr: i/o address mapping
- * @m: Pointer to mdss catalog
- * return: Pointer to hardware rotator driver of the given instance
- */
-struct sde_hw_rot *sde_hw_rot_init(enum sde_rot idx,
- void __iomem *addr,
- struct sde_mdss_cfg *m)
-{
- struct sde_hw_rot *c;
- struct sde_rot_cfg *cfg;
- int rc;
-
- c = kzalloc(sizeof(*c), GFP_KERNEL);
- if (!c)
- return ERR_PTR(-ENOMEM);
-
- cfg = _rot_offset(idx, m, addr, &c->hw);
- if (IS_ERR(cfg)) {
- WARN(1, "Unable to find rot idx=%d\n", idx);
- kfree(c);
- return ERR_PTR(-EINVAL);
- }
-
- /* Assign ops */
- c->idx = idx;
- c->caps = cfg;
- _setup_rot_ops(&c->ops, c->caps->features);
-
- rc = sde_hw_blk_init(&c->base, SDE_HW_BLK_ROT, idx);
- if (rc) {
- SDE_ERROR("failed to init hw blk %d\n", rc);
- goto blk_init_error;
- }
-
- return c;
-
-blk_init_error:
- kzfree(c);
-
- return ERR_PTR(rc);
-}
-
-/**
- * sde_hw_rot_destroy - destroy given hardware rotator driver
- * @hw_rot: Pointer to hardware rotator driver
- * return: none
- */
-void sde_hw_rot_destroy(struct sde_hw_rot *hw_rot)
-{
- sde_hw_blk_destroy(&hw_rot->base);
- kfree(hw_rot->downscale_caps);
- kfree(hw_rot->format_caps);
- kfree(hw_rot);
+ ops->get_maxlinewidth = sde_hw_rot_get_maxlinewidth;
}
/**
@@ -881,26 +853,81 @@ static int sde_hw_rot_blk_start(struct sde_hw_blk *hw_blk)
return rc;
}
+static struct sde_hw_blk_ops sde_hw_rot_ops = {
+ .start = sde_hw_rot_blk_start,
+ .stop = sde_hw_rot_blk_stop,
+};
+
+/**
+ * sde_hw_rot_init - create/initialize given rotator instance
+ * @idx: index of given rotator
+ * @addr: i/o address mapping
+ * @m: Pointer to mdss catalog
+ * return: Pointer to hardware rotator driver of the given instance
+ */
+struct sde_hw_rot *sde_hw_rot_init(enum sde_rot idx,
+ void __iomem *addr,
+ struct sde_mdss_cfg *m)
+{
+ struct sde_hw_rot *c;
+ struct sde_rot_cfg *cfg;
+ int rc;
+
+ c = kzalloc(sizeof(*c), GFP_KERNEL);
+ if (!c)
+ return ERR_PTR(-ENOMEM);
+
+ cfg = _rot_offset(idx, m, addr, &c->hw);
+ if (IS_ERR(cfg)) {
+ WARN(1, "Unable to find rot idx=%d\n", idx);
+ kfree(c);
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* Assign ops */
+ c->idx = idx;
+ c->caps = cfg;
+ _setup_rot_ops(&c->ops, c->caps->features);
+
+ rc = sde_hw_blk_init(&c->base, SDE_HW_BLK_ROT, idx,
+ &sde_hw_rot_ops);
+ if (rc) {
+ SDE_ERROR("failed to init hw blk %d\n", rc);
+ goto blk_init_error;
+ }
+
+ return c;
+
+blk_init_error:
+ kzfree(c);
+
+ return ERR_PTR(rc);
+}
+
+/**
+ * sde_hw_rot_destroy - destroy given hardware rotator driver
+ * @hw_rot: Pointer to hardware rotator driver
+ * return: none
+ */
+void sde_hw_rot_destroy(struct sde_hw_rot *hw_rot)
+{
+ sde_hw_blk_destroy(&hw_rot->base);
+ kfree(hw_rot->downscale_caps);
+ kfree(hw_rot->format_caps);
+ kfree(hw_rot);
+}
+
struct sde_hw_rot *sde_hw_rot_get(struct sde_hw_rot *hw_rot)
{
struct sde_hw_blk *hw_blk = sde_hw_blk_get(hw_rot ? &hw_rot->base :
NULL, SDE_HW_BLK_ROT, -1);
- int rc = 0;
- if (!hw_rot && hw_blk)
- rc = sde_hw_rot_blk_start(hw_blk);
-
- if (rc) {
- sde_hw_blk_put(hw_blk, NULL);
- return NULL;
- }
-
- return hw_blk ? to_sde_hw_rot(hw_blk) : NULL;
+ return IS_ERR_OR_NULL(hw_blk) ? NULL : to_sde_hw_rot(hw_blk);
}
void sde_hw_rot_put(struct sde_hw_rot *hw_rot)
{
struct sde_hw_blk *hw_blk = hw_rot ? &hw_rot->base : NULL;
- sde_hw_blk_put(hw_blk, sde_hw_rot_blk_stop);
+ sde_hw_blk_put(hw_blk);
}
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_rot.h b/drivers/gpu/drm/msm/sde/sde_hw_rot.h
index 949f9bd..a4f5b49 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_rot.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_rot.h
@@ -20,12 +20,6 @@
struct sde_hw_rot;
-/* tags for attachment */
-#define SDE_TAG_ROT_OUT_FBO 0x1000
-#define SDE_TAG_ROT_OUT_FB 0x1001
-#define SDE_TAG_ROT_PLANE 0x1002
-#define SDE_TAG_ROT_IN_FB 0x1003
-
/**
* enum sde_hw_rot_cmd_type - type of rotator hardware command
* @SDE_HW_ROT_CMD_VALDIATE: validate rotator command; do not commit
@@ -124,6 +118,7 @@ struct sde_hw_rot_ops {
struct sde_hw_rot *hw);
const char *(*get_downscale_caps)(struct sde_hw_rot *hw);
size_t (*get_cache_size)(struct sde_hw_rot *hw);
+ int (*get_maxlinewidth)(struct sde_hw_rot *hw);
};
/**
diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c
index b6a9f42..8bc6a2b 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.c
+++ b/drivers/gpu/drm/msm/sde/sde_kms.c
@@ -324,18 +324,6 @@ static int _sde_debugfs_init(struct sde_kms *sde_kms)
static void _sde_debugfs_destroy(struct sde_kms *sde_kms)
{
- return 0;
-}
-
-static void sde_debugfs_danger_destroy(struct sde_kms *sde_kms,
- struct dentry *parent)
-{
-}
-
-static int sde_debugfs_danger_init(struct sde_kms *sde_kms,
- struct dentry *parent)
-{
- return 0;
}
#endif
diff --git a/drivers/gpu/drm/msm/sde/sde_plane.c b/drivers/gpu/drm/msm/sde/sde_plane.c
index 0be17e4..6e1fe33 100644
--- a/drivers/gpu/drm/msm/sde/sde_plane.c
+++ b/drivers/gpu/drm/msm/sde/sde_plane.c
@@ -1220,6 +1220,40 @@ static int _sde_plane_color_fill(struct sde_plane *psde,
}
/**
+ * _sde_plane_fb_get/put - framebuffer callback for crtc res ops
+ */
+static void *_sde_plane_fb_get(void *fb, u32 type, u64 tag)
+{
+ drm_framebuffer_reference(fb);
+ return fb;
+}
+static void _sde_plane_fb_put(void *fb)
+{
+ drm_framebuffer_unreference(fb);
+}
+static struct sde_crtc_res_ops fb_res_ops = {
+ .put = _sde_plane_fb_put,
+ .get = _sde_plane_fb_get,
+};
+
+/**
+ * _sde_plane_fbo_get/put - framebuffer object callback for crtc res ops
+ */
+static void *_sde_plane_fbo_get(void *fbo, u32 type, u64 tag)
+{
+ sde_kms_fbo_reference(fbo);
+ return fbo;
+}
+static void _sde_plane_fbo_put(void *fbo)
+{
+ sde_kms_fbo_unreference(fbo);
+}
+static struct sde_crtc_res_ops fbo_res_ops = {
+ .put = _sde_plane_fbo_put,
+ .get = _sde_plane_fbo_get,
+};
+
+/**
* sde_plane_rot_calc_prefill - calculate rotator start prefill
* @plane: Pointer to drm plane
* return: prefill time in line
@@ -1294,17 +1328,26 @@ static void sde_plane_rot_calc_cfg(struct drm_plane *plane,
struct sde_plane_state *pstate;
struct sde_plane_rot_state *rstate;
struct sde_hw_blk *hw_blk;
- struct sde_hw_blk_attachment *attach;
+ struct drm_crtc_state *cstate;
struct drm_rect *in_rot, *out_rot;
+ struct drm_plane *attached_plane;
u32 dst_x, dst_y, dst_w, dst_h;
int found = 0;
int xpos = 0;
+ int ret;
if (!plane || !state || !state->state) {
SDE_ERROR("invalid parameters\n");
return;
}
+ cstate = _sde_plane_get_crtc_state(state);
+ if (IS_ERR_OR_NULL(cstate)) {
+ ret = PTR_ERR(cstate);
+ SDE_ERROR("invalid crtc state %d\n", ret);
+ return;
+ }
+
pstate = to_sde_plane_state(state);
rstate = &pstate->rot;
@@ -1341,24 +1384,12 @@ static void sde_plane_rot_calc_cfg(struct drm_plane *plane,
hw_blk = &rstate->rot_hw->base;
/* enumerating over all planes attached to the same rotator */
- list_for_each_entry(attach, &hw_blk->attach_list, list) {
- struct drm_plane *attached_plane;
+ drm_atomic_crtc_state_for_each_plane(attached_plane, cstate) {
struct drm_plane_state *attached_state;
struct sde_plane_state *attached_pstate;
struct sde_plane_rot_state *attached_rstate;
struct drm_rect attached_out_rect;
- if (attach->tag != SDE_TAG_ROT_PLANE)
- continue;
-
- attached_plane = attach->value;
-
- found++;
-
- /* skip itself */
- if (attached_plane == plane)
- continue;
-
attached_state = drm_atomic_get_existing_plane_state(
state->state, attached_plane);
@@ -1368,6 +1399,15 @@ static void sde_plane_rot_calc_cfg(struct drm_plane *plane,
attached_pstate = to_sde_plane_state(attached_state);
attached_rstate = &attached_pstate->rot;
+ if (attached_rstate->rot_hw != rstate->rot_hw)
+ continue;
+
+ found++;
+
+ /* skip itself */
+ if (attached_plane == plane)
+ continue;
+
/* find bounding rotator source roi */
if (attached_state->src_x < in_rot->x1)
in_rot->x1 = attached_state->src_x;
@@ -1628,6 +1668,7 @@ static int sde_plane_rot_prepare_fb(struct drm_plane *plane,
struct drm_framebuffer *fb = new_state->fb;
struct sde_plane_state *new_pstate = to_sde_plane_state(new_state);
struct sde_plane_rot_state *new_rstate = &new_pstate->rot;
+ struct drm_crtc_state *cstate;
int ret;
SDE_DEBUG("plane%d.%d FB[%u] sbuf:%d rot:%d crtc:%d\n",
@@ -1639,6 +1680,13 @@ static int sde_plane_rot_prepare_fb(struct drm_plane *plane,
if (!new_rstate->out_sbuf || !new_rstate->rot_hw)
return 0;
+ cstate = _sde_plane_get_crtc_state(new_state);
+ if (IS_ERR(cstate)) {
+ ret = PTR_ERR(cstate);
+ SDE_ERROR("invalid crtc state %d\n", ret);
+ return ret;
+ }
+
/* need to re-calc based on all newly validated plane states */
sde_plane_rot_calc_cfg(plane, new_state);
@@ -1647,26 +1695,25 @@ static int sde_plane_rot_prepare_fb(struct drm_plane *plane,
struct sde_kms_fbo *fbo;
struct drm_framebuffer *fb;
- fbo = sde_hw_blk_lookup_value(&new_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FBO, 0);
- fb = sde_hw_blk_lookup_value(&new_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FB, 0);
+ fbo = sde_crtc_res_get(cstate, SDE_CRTC_RES_ROT_OUT_FBO,
+ (u64) &new_rstate->rot_hw->base);
+ fb = sde_crtc_res_get(cstate, SDE_CRTC_RES_ROT_OUT_FB,
+ (u64) &new_rstate->rot_hw->base);
if (fb && fbo) {
SDE_DEBUG("plane%d.%d get fb/fbo\n", plane->base.id,
new_rstate->sequence_id);
-
- new_rstate->out_fbo = fbo;
- sde_kms_fbo_reference(new_rstate->out_fbo);
- sde_hw_blk_attach(&new_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FBO,
- new_rstate->out_fbo);
-
- new_rstate->out_fb = fb;
- drm_framebuffer_reference(new_rstate->out_fb);
- sde_hw_blk_attach(&new_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FB,
- new_rstate->out_fb);
+ } else if (fbo) {
+ sde_crtc_res_put(cstate, SDE_CRTC_RES_ROT_OUT_FBO,
+ (u64) &new_rstate->rot_hw->base);
+ fbo = NULL;
+ } else if (fb) {
+ sde_crtc_res_put(cstate, SDE_CRTC_RES_ROT_OUT_FB,
+ (u64) &new_rstate->rot_hw->base);
+ fb = NULL;
}
+
+ new_rstate->out_fbo = fbo;
+ new_rstate->out_fb = fb;
}
/* release buffer if output format configuration changes */
@@ -1682,13 +1729,11 @@ static int sde_plane_rot_prepare_fb(struct drm_plane *plane,
SDE_DEBUG("plane%d.%d release fb/fbo\n", plane->base.id,
new_rstate->sequence_id);
- sde_hw_blk_detach(&new_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FB, new_rstate->out_fb);
- drm_framebuffer_unreference(new_rstate->out_fb);
+ sde_crtc_res_put(cstate, SDE_CRTC_RES_ROT_OUT_FB,
+ (u64) &new_rstate->rot_hw->base);
new_rstate->out_fb = NULL;
- sde_hw_blk_detach(&new_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FBO, new_rstate->out_fbo);
- sde_kms_fbo_unreference(new_rstate->out_fbo);
+ sde_crtc_res_put(cstate, SDE_CRTC_RES_ROT_OUT_FBO,
+ (u64) &new_rstate->rot_hw->base);
new_rstate->out_fbo = NULL;
}
@@ -1716,8 +1761,13 @@ static int sde_plane_rot_prepare_fb(struct drm_plane *plane,
goto error_create_fbo;
}
- sde_hw_blk_attach(&new_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FBO, new_rstate->out_fbo);
+ ret = sde_crtc_res_add(cstate, SDE_CRTC_RES_ROT_OUT_FBO,
+ (u64) &new_rstate->rot_hw->base,
+ new_rstate->out_fbo, &fbo_res_ops);
+ if (ret) {
+ SDE_ERROR("failed to add crtc resource\n");
+ goto error_create_fbo_res;
+ }
new_rstate->out_fb = sde_kms_fbo_create_fb(plane->dev,
new_rstate->out_fbo);
@@ -1727,8 +1777,13 @@ static int sde_plane_rot_prepare_fb(struct drm_plane *plane,
goto error_create_fb;
}
- sde_hw_blk_attach(&new_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FB, new_rstate->out_fb);
+ ret = sde_crtc_res_add(cstate, SDE_CRTC_RES_ROT_OUT_FB,
+ (u64) &new_rstate->rot_hw->base,
+ new_rstate->out_fb, &fb_res_ops);
+ if (ret) {
+ SDE_ERROR("failed to add crtc resource %d\n", ret);
+ goto error_create_fb_res;
+ }
}
/* prepare rotator input buffer */
@@ -1756,14 +1811,14 @@ static int sde_plane_rot_prepare_fb(struct drm_plane *plane,
error_prepare_output_buffer:
msm_framebuffer_cleanup(new_state->fb, new_rstate->mmu_id);
error_prepare_input_buffer:
- sde_hw_blk_detach(&new_rstate->rot_hw->base, SDE_TAG_ROT_OUT_FB,
- new_rstate->out_fb);
- drm_framebuffer_unreference(new_rstate->out_fb);
+ sde_crtc_res_put(cstate, SDE_CRTC_RES_ROT_OUT_FB,
+ (u64) &new_rstate->rot_hw->base);
+error_create_fb_res:
new_rstate->out_fb = NULL;
error_create_fb:
- sde_hw_blk_detach(&new_rstate->rot_hw->base, SDE_TAG_ROT_OUT_FBO,
- new_rstate->out_fbo);
- sde_kms_fbo_unreference(new_rstate->out_fbo);
+ sde_crtc_res_put(cstate, SDE_CRTC_RES_ROT_OUT_FBO,
+ (u64) &new_rstate->rot_hw->base);
+error_create_fbo_res:
new_rstate->out_fbo = NULL;
error_create_fbo:
return ret;
@@ -1782,6 +1837,7 @@ static void sde_plane_rot_cleanup_fb(struct drm_plane *plane,
struct sde_plane_state *old_pstate = to_sde_plane_state(old_state);
struct sde_plane_rot_state *old_rstate = &old_pstate->rot;
struct sde_hw_rot_cmd *cmd = &old_rstate->rot_cmd;
+ struct drm_crtc_state *cstate;
int ret;
SDE_DEBUG("plane%d.%d FB[%u] sbuf:%d rot:%d crtc:%d\n", plane->base.id,
@@ -1792,6 +1848,13 @@ static void sde_plane_rot_cleanup_fb(struct drm_plane *plane,
if (!old_rstate->out_sbuf || !old_rstate->rot_hw)
return;
+ cstate = _sde_plane_get_crtc_state(old_state);
+ if (IS_ERR(cstate)) {
+ ret = PTR_ERR(cstate);
+ SDE_ERROR("invalid crtc state %d\n", ret);
+ return;
+ }
+
if (sde_plane_crtc_enabled(old_state)) {
ret = old_rstate->rot_hw->ops.commit(old_rstate->rot_hw, cmd,
SDE_HW_ROT_CMD_CLEANUP);
@@ -1803,15 +1866,11 @@ static void sde_plane_rot_cleanup_fb(struct drm_plane *plane,
if (old_rstate->out_fb) {
msm_framebuffer_cleanup(old_rstate->out_fb,
old_rstate->mmu_id);
- sde_hw_blk_detach(&old_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FB,
- old_rstate->out_fb);
- drm_framebuffer_unreference(old_rstate->out_fb);
+ sde_crtc_res_put(cstate, SDE_CRTC_RES_ROT_OUT_FB,
+ (u64) &old_rstate->rot_hw->base);
old_rstate->out_fb = NULL;
- sde_hw_blk_detach(&old_rstate->rot_hw->base,
- SDE_TAG_ROT_OUT_FBO,
- old_rstate->out_fbo);
- sde_kms_fbo_unreference(old_rstate->out_fbo);
+ sde_crtc_res_put(cstate, SDE_CRTC_RES_ROT_OUT_FBO,
+ (u64) &old_rstate->rot_hw->base);
old_rstate->out_fbo = NULL;
}
@@ -1831,6 +1890,7 @@ static int sde_plane_rot_atomic_check(struct drm_plane *plane,
struct sde_plane *psde;
struct sde_plane_state *pstate, *old_pstate;
struct sde_plane_rot_state *rstate, *old_rstate;
+ struct drm_crtc_state *cstate;
struct sde_hw_blk *hw_blk;
int i, ret = 0;
@@ -1845,6 +1905,14 @@ static int sde_plane_rot_atomic_check(struct drm_plane *plane,
rstate = &pstate->rot;
old_rstate = &old_pstate->rot;
+ /* cstate will be null if crtc is disconnected from plane */
+ cstate = _sde_plane_get_crtc_state(state);
+ if (IS_ERR(cstate)) {
+ ret = PTR_ERR(cstate);
+ SDE_ERROR("invalid crtc state %d\n", ret);
+ return ret;
+ }
+
SDE_DEBUG("plane%d.%d FB[%u] sbuf:%d rot:%d crtc:%d\n", plane->base.id,
rstate->sequence_id, state->fb ? state->fb->base.id : 0,
!!rstate->out_sbuf, !!rstate->rot_hw,
@@ -1852,70 +1920,40 @@ static int sde_plane_rot_atomic_check(struct drm_plane *plane,
rstate->in_rotation = drm_rotation_simplify(
sde_plane_get_property(pstate, PLANE_PROP_ROTATION),
- DRM_ROTATE_90 | DRM_REFLECT_X | DRM_REFLECT_Y);
+ DRM_ROTATE_0 | DRM_ROTATE_90 |
+ DRM_REFLECT_X | DRM_REFLECT_Y);
rstate->rot90 = rstate->in_rotation & DRM_ROTATE_90 ? true : false;
rstate->hflip = rstate->in_rotation & DRM_REFLECT_X ? true : false;
rstate->vflip = rstate->in_rotation & DRM_REFLECT_Y ? true : false;
rstate->out_sbuf = psde->sbuf_mode || rstate->rot90;
- if ((!sde_plane_enabled(state) || !rstate->out_sbuf) &&
- rstate->rot_hw) {
-
- SDE_DEBUG("plane%d.%d release rotator\n",
+ if (sde_plane_enabled(state) && rstate->out_sbuf) {
+ SDE_DEBUG("plane%d.%d acquire rotator\n",
plane->base.id, rstate->sequence_id);
- sde_hw_blk_detach(&rstate->rot_hw->base, SDE_TAG_ROT_IN_FB,
- rstate->in_fb);
- rstate->in_fb = NULL;
- sde_hw_blk_detach(&rstate->rot_hw->base, SDE_TAG_ROT_PLANE,
- plane);
- sde_hw_rot_put(rstate->rot_hw);
- rstate->rot_hw = NULL;
-
- } else if (sde_plane_enabled(state) && rstate->out_sbuf &&
- !rstate->rot_hw) {
-
- SDE_DEBUG("plane%d.%d allocate rotator\n",
- plane->base.id, rstate->sequence_id);
-
- hw_blk = sde_hw_blk_lookup_blk(SDE_TAG_ROT_IN_FB, state->fb,
- SDE_HW_BLK_ROT);
- if (hw_blk)
- rstate->rot_hw = to_sde_hw_rot(hw_blk);
- else
- rstate->rot_hw = sde_hw_rot_get(NULL);
-
- if (!rstate->rot_hw) {
+ hw_blk = sde_crtc_res_get(cstate, SDE_HW_BLK_ROT,
+ (u64) state->fb);
+ if (!hw_blk) {
SDE_ERROR("plane%d no available rotator\n",
plane->base.id);
return -EINVAL;
}
+ rstate->rot_hw = to_sde_hw_rot(hw_blk);
+
if (!rstate->rot_hw->ops.commit) {
SDE_ERROR("plane%d invalid rotator ops\n",
plane->base.id);
- sde_hw_rot_put(rstate->rot_hw);
+ sde_crtc_res_put(cstate,
+ SDE_HW_BLK_ROT, (u64) state->fb);
rstate->rot_hw = NULL;
return -EINVAL;
}
rstate->in_fb = state->fb;
- sde_hw_blk_attach(&rstate->rot_hw->base, SDE_TAG_ROT_IN_FB,
- rstate->in_fb);
- sde_hw_blk_attach(&rstate->rot_hw->base, SDE_TAG_ROT_PLANE,
- plane);
-
- } else if (sde_plane_enabled(state) && rstate->out_sbuf &&
- (rstate->in_fb != state->fb)) {
-
- SDE_DEBUG("plane%d.%d update fb\n",
- plane->base.id, rstate->sequence_id);
-
- sde_hw_blk_detach(&rstate->rot_hw->base, SDE_TAG_ROT_IN_FB,
- rstate->in_fb);
- rstate->in_fb = state->fb;
- sde_hw_blk_attach(&rstate->rot_hw->base, SDE_TAG_ROT_IN_FB,
- rstate->in_fb);
+ } else {
+ rstate->in_fb = NULL;
+ rstate->rot_hw = NULL;
}
if (sde_plane_enabled(state) && rstate->out_sbuf && rstate->rot_hw) {
@@ -2008,16 +2046,6 @@ static void sde_plane_rot_destroy_state(struct drm_plane *plane,
rstate->sequence_id,
!!rstate->out_sbuf, !!rstate->rot_hw,
sde_plane_crtc_enabled(state));
-
- if (rstate->rot_hw) {
- sde_hw_blk_detach(&rstate->rot_hw->base, SDE_TAG_ROT_IN_FB,
- rstate->in_fb);
- rstate->in_fb = NULL;
- sde_hw_blk_detach(&rstate->rot_hw->base, SDE_TAG_ROT_PLANE,
- plane);
- sde_hw_rot_put(rstate->rot_hw);
- rstate->rot_hw = NULL;
- }
}
/**
@@ -2031,6 +2059,8 @@ static int sde_plane_rot_duplicate_state(struct drm_plane *plane,
{
struct sde_plane_state *pstate = to_sde_plane_state(new_state);
struct sde_plane_rot_state *rstate = &pstate->rot;
+ struct drm_crtc_state *cstate;
+ int ret;
rstate->sequence_id++;
@@ -2038,14 +2068,19 @@ static int sde_plane_rot_duplicate_state(struct drm_plane *plane,
rstate->sequence_id,
!!rstate->out_sbuf, !!rstate->rot_hw);
- if (rstate->rot_hw) {
- sde_hw_blk_attach(&rstate->rot_hw->base, SDE_TAG_ROT_IN_FB,
- rstate->in_fb);
- sde_hw_blk_attach(&rstate->rot_hw->base, SDE_TAG_ROT_PLANE,
- plane);
- sde_hw_rot_get(rstate->rot_hw);
+ cstate = _sde_plane_get_crtc_state(new_state);
+ if (IS_ERR(cstate)) {
+ ret = PTR_ERR(cstate);
+ SDE_ERROR("invalid crtc state %d\n", ret);
+ return -EINVAL;
}
+ if (rstate->rot_hw && cstate)
+ sde_crtc_res_get(cstate, SDE_HW_BLK_ROT, (u64) rstate->in_fb);
+ else if (rstate->rot_hw && !cstate)
+ SDE_ERROR("plane%d.%d zombie rotator hw\n",
+ plane->base.id, rstate->sequence_id);
+
rstate->out_fb = NULL;
rstate->out_fbo = NULL;
@@ -2108,6 +2143,10 @@ static void sde_plane_rot_install_caps(struct drm_plane *plane)
sde_kms_info_add_keyint(info, "cache_size",
rot_hw->ops.get_cache_size(rot_hw));
+ if (rot_hw->ops.get_maxlinewidth)
+ sde_kms_info_add_keyint(info, "max_linewidth",
+ rot_hw->ops.get_maxlinewidth(rot_hw));
+
msm_property_set_blob(&psde->property_info, &psde->blob_rot_caps,
info->data, info->len, PLANE_PROP_ROT_CAPS_V1);
@@ -2126,7 +2165,8 @@ static void sde_plane_rot_install_properties(struct drm_plane *plane,
struct sde_mdss_cfg *catalog)
{
struct sde_plane *psde = to_sde_plane(plane);
- unsigned long supported_rotations = DRM_REFLECT_X | DRM_REFLECT_Y;
+ unsigned long supported_rotations = DRM_ROTATE_0 | DRM_REFLECT_X |
+ DRM_REFLECT_Y;
if (!plane || !psde) {
SDE_ERROR("invalid plane\n");
@@ -3531,10 +3571,6 @@ sde_plane_duplicate_state(struct drm_plane *plane)
msm_property_duplicate_state(&psde->property_info, old_state, pstate,
pstate->property_values, pstate->property_blobs);
- /* add ref count for frame buffer */
- if (pstate->base.fb)
- drm_framebuffer_reference(pstate->base.fb);
-
/* clear out any input fence */
pstate->input_fence = 0;
input_fence_default = msm_property_get_default(
@@ -3545,6 +3581,8 @@ sde_plane_duplicate_state(struct drm_plane *plane)
pstate->dirty = 0x0;
pstate->pending = false;
+ __drm_atomic_helper_plane_duplicate_state(plane, &pstate->base);
+
sde_plane_rot_duplicate_state(plane, &pstate->base);
return &pstate->base;
diff --git a/drivers/gpu/drm/msm/sde_dbg.c b/drivers/gpu/drm/msm/sde_dbg.c
index 9977d10..40e02b8 100644
--- a/drivers/gpu/drm/msm/sde_dbg.c
+++ b/drivers/gpu/drm/msm/sde_dbg.c
@@ -2916,7 +2916,7 @@ int sde_dbg_debugfs_register(struct dentry *debugfs_root)
return 0;
}
-#if defined(CONFIG_DEBUG_FS)
+#ifdef CONFIG_DEBUG_FS
static void _sde_dbg_debugfs_destroy(void)
{
debugfs_remove_recursive(sde_dbg_base.root);
diff --git a/drivers/gpu/msm/a6xx_reg.h b/drivers/gpu/msm/a6xx_reg.h
index 2709aca..218c6e7 100644
--- a/drivers/gpu/msm/a6xx_reg.h
+++ b/drivers/gpu/msm/a6xx_reg.h
@@ -537,6 +537,8 @@
#define A6XX_UCHE_GMEM_RANGE_MAX_HI 0xE0E
#define A6XX_UCHE_CACHE_WAYS 0xE17
#define A6XX_UCHE_FILTER_CNTL 0xE18
+#define A6XX_UCHE_CLIENT_PF 0xE19
+#define A6XX_UCHE_CLIENT_PF_CLIENT_ID_MASK 0x7
#define A6XX_UCHE_PERFCTR_UCHE_SEL_0 0xE1C
#define A6XX_UCHE_PERFCTR_UCHE_SEL_1 0xE1D
#define A6XX_UCHE_PERFCTR_UCHE_SEL_2 0xE1E
diff --git a/drivers/gpu/msm/adreno.c b/drivers/gpu/msm/adreno.c
index 1c37978..68d7653 100644
--- a/drivers/gpu/msm/adreno.c
+++ b/drivers/gpu/msm/adreno.c
@@ -3063,6 +3063,7 @@ static const struct kgsl_functable adreno_functable = {
.regulator_disable_poll = adreno_regulator_disable_poll,
.clk_set_options = adreno_clk_set_options,
.gpu_model = adreno_gpu_model,
+ .stop_fault_timer = adreno_dispatcher_stop_fault_timer,
};
static struct platform_driver adreno_platform_driver = {
diff --git a/drivers/gpu/msm/adreno.h b/drivers/gpu/msm/adreno.h
index e23d6a0..75d5587 100644
--- a/drivers/gpu/msm/adreno.h
+++ b/drivers/gpu/msm/adreno.h
@@ -856,6 +856,8 @@ struct adreno_gpudev {
unsigned int arg1, unsigned int arg2);
bool (*hw_isidle)(struct adreno_device *);
int (*wait_for_gmu_idle)(struct adreno_device *);
+ const char *(*iommu_fault_block)(struct adreno_device *adreno_dev,
+ unsigned int fsynr1);
};
/**
diff --git a/drivers/gpu/msm/adreno_a6xx.c b/drivers/gpu/msm/adreno_a6xx.c
index 0211a17..49d784c 100644
--- a/drivers/gpu/msm/adreno_a6xx.c
+++ b/drivers/gpu/msm/adreno_a6xx.c
@@ -285,6 +285,8 @@ static void a6xx_start(struct adreno_device *adreno_dev)
kgsl_regwrite(device, A6XX_RBBM_INTERFACE_HANG_INT_CNTL,
(1 << 30) | 0x4000);
+ kgsl_regwrite(device, A6XX_UCHE_CLIENT_PF, 1);
+
/* Set TWOPASSUSEWFI in A6XX_PC_DBG_ECO_CNTL if requested */
if (ADRENO_QUIRK(adreno_dev, ADRENO_QUIRK_TWO_PASS_USE_WFI))
kgsl_regrmw(device, A6XX_PC_DBG_ECO_CNTL, 0, (1 << 8));
@@ -899,6 +901,72 @@ static int a6xx_hm_disable(struct adreno_device *adreno_dev)
}
/*
+ * a6xx_hm_sptprac_enable() - Turn on HM and SPTPRAC
+ * @device: Pointer to KGSL device
+ */
+static int a6xx_hm_sptprac_enable(struct kgsl_device *device)
+{
+ int ret = 0;
+ struct gmu_device *gmu = &device->gmu;
+
+ /* If GMU does not control HM we must */
+ if (gmu->idle_level < GPU_HW_IFPC) {
+ ret = a6xx_hm_enable(ADRENO_DEVICE(device));
+ if (ret) {
+ dev_err(&gmu->pdev->dev, "Failed to power on GPU HM\n");
+ return ret;
+ }
+ }
+
+ /* If GMU does not control SPTPRAC we must */
+ if (gmu->idle_level < GPU_HW_SPTP_PC) {
+ ret = a6xx_sptprac_enable(ADRENO_DEVICE(device));
+ if (ret) {
+ a6xx_hm_disable(ADRENO_DEVICE(device));
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * a6xx_hm_sptprac_disable() - Turn off SPTPRAC and HM
+ * @device: Pointer to KGSL device
+ */
+static int a6xx_hm_sptprac_disable(struct kgsl_device *device)
+{
+ int ret = 0;
+ struct gmu_device *gmu = &device->gmu;
+
+ /* If GMU does not control SPTPRAC we must */
+ if (gmu->idle_level < GPU_HW_SPTP_PC)
+ a6xx_sptprac_disable(ADRENO_DEVICE(device));
+
+ /* If GMU does not control HM we must */
+ if (gmu->idle_level < GPU_HW_IFPC) {
+ ret = a6xx_hm_disable(ADRENO_DEVICE(device));
+ if (ret)
+ dev_err(&gmu->pdev->dev, "Failed to power off GPU HM\n");
+ }
+
+ return ret;
+}
+
+/*
+ * a6xx_hm_sptprac_control() - Turn HM and SPTPRAC on or off
+ * @device: Pointer to KGSL device
+ * @on: True to turn on or false to turn off
+ */
+static int a6xx_hm_sptprac_control(struct kgsl_device *device, bool on)
+{
+ if (on)
+ return a6xx_hm_sptprac_enable(device);
+ else
+ return a6xx_hm_sptprac_disable(device);
+}
+
+/*
* a6xx_gfx_rail_on() - request GMU to power GPU at given OPP.
* @device: Pointer to KGSL device
*
@@ -976,7 +1044,7 @@ static int a6xx_rpmh_power_on_gpu(struct kgsl_device *device)
{
struct gmu_device *gmu = &device->gmu;
struct device *dev = &gmu->pdev->dev;
- int ret;
+ int ret = 0;
if (device->state != KGSL_STATE_INIT &&
device->state != KGSL_STATE_SUSPEND) {
@@ -1002,26 +1070,11 @@ static int a6xx_rpmh_power_on_gpu(struct kgsl_device *device)
0xFFFFFFFF))
goto error_rsc;
- /* If GMU does not control HM we must */
- if (gmu->idle_level < GPU_HW_IFPC) {
- ret = a6xx_hm_enable(ADRENO_DEVICE(device));
- if (ret) {
- dev_err(dev, "Failed to power on GPU HM\n");
- return ret;
- }
- }
-
- /* If GMU does not control SPTP we must */
- if (gmu->idle_level < GPU_HW_SPTP_PC) {
- ret = a6xx_sptprac_enable(ADRENO_DEVICE(device));
- if (ret) {
- a6xx_hm_disable(ADRENO_DEVICE(device));
- return ret;
- }
- }
+ /* Turn on the HM and SPTP head switches */
+ ret = a6xx_hm_sptprac_control(device, true);
}
- return 0;
+ return ret;
error_rsc:
dev_err(dev, "GPU RSC sequence stuck in waking up GPU\n");
@@ -1031,19 +1084,10 @@ static int a6xx_rpmh_power_on_gpu(struct kgsl_device *device)
static int a6xx_rpmh_power_off_gpu(struct kgsl_device *device)
{
struct gmu_device *gmu = &device->gmu;
- struct device *dev = &gmu->pdev->dev;
- int val, ret;
+ int val, ret = 0;
- /* If GMU does not control SPTP we must */
- if (gmu->idle_level < GPU_HW_SPTP_PC)
- a6xx_sptprac_disable(ADRENO_DEVICE(device));
-
- /* If GMU does not control HM we must */
- if (gmu->idle_level < GPU_HW_IFPC) {
- ret = a6xx_hm_disable(ADRENO_DEVICE(device));
- if (ret)
- dev_err(dev, "Failed to power off GPU HM\n");
- }
+ /* Turn off the SPTP and HM head switches */
+ ret = a6xx_hm_sptprac_control(device, false);
/* RSC sleep sequence */
_regwrite(gmu->pdc_reg_virt, PDC_GPU_TIMESTAMP_UNIT1_EN_DRV0, 1);
@@ -1069,7 +1113,7 @@ static int a6xx_rpmh_power_off_gpu(struct kgsl_device *device)
/* FIXME: v2 has different procedure to trigger sequence */
- return 0;
+ return ret;
}
/*
@@ -1083,30 +1127,16 @@ static int a6xx_gmu_fw_start(struct kgsl_device *device,
struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
struct gmu_device *gmu = &device->gmu;
struct gmu_memdesc *mem_addr = gmu->hfi_mem;
- struct device *dev = &gmu->pdev->dev;
int ret, i;
a6xx_gmu_power_config(device);
- /* If GMU does not control HM then we must */
- if (gmu->idle_level < GPU_HW_IFPC) {
- ret = a6xx_hm_enable(adreno_dev);
- if (ret) {
- dev_err(dev, "Failed to power on GPU HM\n");
- return ret;
- }
- }
-
- /* If GMU does not control SPTP then we must */
- if (gmu->idle_level < GPU_HW_SPTP_PC) {
- ret = a6xx_sptprac_enable(adreno_dev);
- if (ret) {
- a6xx_hm_disable(adreno_dev);
- return ret;
- }
- }
-
if (boot_state == GMU_COLD_BOOT || boot_state == GMU_RESET) {
+ /* Turn on the HM and SPTP head switches */
+ ret = a6xx_hm_sptprac_control(device, true);
+ if (ret)
+ return ret;
+
/* Turn on TCM retention */
kgsl_gmu_regwrite(device, A6XX_GMU_GENERAL_7, 1);
@@ -1494,6 +1524,46 @@ static void a6xx_llc_enable_overrides(struct adreno_device *adreno_dev)
iounmap(gpu_cx_reg);
}
+static const char *fault_block[8] = {
+ [0] = "CP",
+ [1] = "UCHE",
+ [2] = "VFD",
+ [3] = "UCHE",
+ [4] = "CCU",
+ [5] = "unknown",
+ [6] = "CDP Prefetch",
+ [7] = "GPMU",
+};
+
+static const char *uche_client[8] = {
+ [0] = "VFD",
+ [1] = "SP",
+ [2] = "VSC",
+ [3] = "VPC",
+ [4] = "HLSQ",
+ [5] = "PC",
+ [6] = "LRZ",
+ [7] = "unknown",
+};
+
+static const char *a6xx_iommu_fault_block(struct adreno_device *adreno_dev,
+ unsigned int fsynr1)
+{
+ struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
+ unsigned int client_id;
+ unsigned int uche_client_id;
+
+ client_id = fsynr1 & 0xff;
+
+ if (client_id >= ARRAY_SIZE(fault_block))
+ return "unknown";
+ else if (client_id != 3)
+ return fault_block[client_id];
+
+ kgsl_regread(device, A6XX_UCHE_CLIENT_PF, &uche_client_id);
+ return uche_client[uche_client_id & A6XX_UCHE_CLIENT_PF_CLIENT_ID_MASK];
+}
+
#define A6XX_INT_MASK \
((1 << A6XX_INT_CP_AHB_ERROR) | \
(1 << A6XX_INT_ATB_ASYNCFIFO_OVERFLOW) | \
@@ -2050,5 +2120,6 @@ struct adreno_gpudev adreno_a6xx_gpudev = {
.oob_clear = a6xx_oob_clear,
.rpmh_gpu_pwrctrl = a6xx_rpmh_gpu_pwrctrl,
.hw_isidle = a6xx_hw_isidle, /* Replaced by NULL if GMU is disabled */
- .wait_for_gmu_idle = a6xx_wait_for_gmu_idle
+ .wait_for_gmu_idle = a6xx_wait_for_gmu_idle,
+ .iommu_fault_block = a6xx_iommu_fault_block,
};
diff --git a/drivers/gpu/msm/adreno_a6xx_snapshot.c b/drivers/gpu/msm/adreno_a6xx_snapshot.c
index f2a7963..e501a68 100644
--- a/drivers/gpu/msm/adreno_a6xx_snapshot.c
+++ b/drivers/gpu/msm/adreno_a6xx_snapshot.c
@@ -1166,6 +1166,28 @@ static void a6xx_snapshot_gmu(struct kgsl_device *device,
snapshot, a6xx_snapshot_dump_gmu_registers, &gmu_regs);
}
+/* a6xx_snapshot_sqe() - Dump SQE data in snapshot */
+static size_t a6xx_snapshot_sqe(struct kgsl_device *device, u8 *buf,
+ size_t remain, void *priv)
+{
+ struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
+ struct kgsl_snapshot_debug *header = (struct kgsl_snapshot_debug *)buf;
+ unsigned int *data = (unsigned int *)(buf + sizeof(*header));
+ struct adreno_firmware *fw = ADRENO_FW(adreno_dev, ADRENO_FW_SQE);
+
+ if (remain < DEBUG_SECTION_SZ(1)) {
+ SNAPSHOT_ERR_NOMEM(device, "SQE VERSION DEBUG");
+ return 0;
+ }
+
+ /* Dump the SQE firmware version */
+ header->type = SNAPSHOT_DEBUG_SQE_VERSION;
+ header->size = 1;
+ *data = fw->version;
+
+ return DEBUG_SECTION_SZ(1);
+}
+
static void _a6xx_do_crashdump(struct kgsl_device *device)
{
unsigned long wait_time;
@@ -1255,6 +1277,10 @@ void a6xx_snapshot(struct adreno_device *adreno_dev,
snapshot, adreno_snapshot_cp_roq,
&snap_data->sect_sizes->roq);
+ /* SQE Firmware */
+ kgsl_snapshot_add_section(device, KGSL_SNAPSHOT_SECTION_DEBUG,
+ snapshot, a6xx_snapshot_sqe, NULL);
+
/* Mempool debug data */
a6xx_snapshot_mempool(device, snapshot);
diff --git a/drivers/gpu/msm/adreno_dispatch.c b/drivers/gpu/msm/adreno_dispatch.c
index 4d1f1ad..ed5b714 100644
--- a/drivers/gpu/msm/adreno_dispatch.c
+++ b/drivers/gpu/msm/adreno_dispatch.c
@@ -208,6 +208,9 @@ static inline bool _isidle(struct adreno_device *adreno_dev)
if (!kgsl_state_is_awake(KGSL_DEVICE(adreno_dev)))
goto ret;
+ if (adreno_rb_empty(adreno_dev->cur_rb))
+ goto ret;
+
/* only check rbbm status to determine if GPU is idle */
adreno_readreg(adreno_dev, ADRENO_REG_RBBM_STATUS, ®_rbbm_status);
@@ -2055,6 +2058,18 @@ static int dispatcher_do_fault(struct adreno_device *adreno_dev)
return 0;
/*
+ * In the very unlikely case that the power is off, do nothing - the
+ * state will be reset on power up and everybody will be happy
+ */
+
+ if (!kgsl_state_is_awake(device) && (fault & ADRENO_SOFT_FAULT)) {
+ /* Clear the existing register values */
+ memset(adreno_ft_regs_val, 0,
+ adreno_ft_regs_num * sizeof(unsigned int));
+ return 0;
+ }
+
+ /*
* On A5xx and A6xx, read RBBM_STATUS3:SMMU_STALLED_ON_FAULT (BIT 24)
* to tell if this function was entered after a pagefault. If so, only
* proceed if the fault handler has already run in the IRQ thread,
@@ -2509,7 +2524,7 @@ static void adreno_dispatcher_fault_timer(unsigned long data)
if (!fault_detect_read_compare(adreno_dev)) {
adreno_set_gpu_fault(adreno_dev, ADRENO_SOFT_FAULT);
adreno_dispatcher_schedule(KGSL_DEVICE(adreno_dev));
- } else {
+ } else if (dispatcher->inflight > 0) {
mod_timer(&dispatcher->fault_timer,
jiffies + msecs_to_jiffies(_fault_timer_interval));
}
@@ -2554,6 +2569,20 @@ void adreno_dispatcher_stop(struct adreno_device *adreno_dev)
}
/**
+ * adreno_dispatcher_stop_fault_timer() - stop the dispatcher fault timer
+ * @device: pointer to the KGSL device structure
+ *
+ * Stop the dispatcher fault timer
+ */
+void adreno_dispatcher_stop_fault_timer(struct kgsl_device *device)
+{
+ struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
+ struct adreno_dispatcher *dispatcher = &adreno_dev->dispatcher;
+
+ del_timer_sync(&dispatcher->fault_timer);
+}
+
+/**
* adreno_dispatcher_close() - close the dispatcher
* @adreno_dev: pointer to the adreno device structure
*
diff --git a/drivers/gpu/msm/adreno_dispatch.h b/drivers/gpu/msm/adreno_dispatch.h
index cb9106f..72545db 100644
--- a/drivers/gpu/msm/adreno_dispatch.h
+++ b/drivers/gpu/msm/adreno_dispatch.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2008-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
@@ -108,6 +108,7 @@ void adreno_dispatcher_close(struct adreno_device *adreno_dev);
int adreno_dispatcher_idle(struct adreno_device *adreno_dev);
void adreno_dispatcher_irq_fault(struct adreno_device *adreno_dev);
void adreno_dispatcher_stop(struct adreno_device *adreno_dev);
+void adreno_dispatcher_stop_fault_timer(struct kgsl_device *device);
int adreno_dispatcher_queue_cmds(struct kgsl_device_private *dev_priv,
struct kgsl_context *context, struct kgsl_drawobj *drawobj[],
diff --git a/drivers/gpu/msm/kgsl_device.h b/drivers/gpu/msm/kgsl_device.h
index b4725c1..bf31c00 100644
--- a/drivers/gpu/msm/kgsl_device.h
+++ b/drivers/gpu/msm/kgsl_device.h
@@ -178,6 +178,7 @@ struct kgsl_functable {
const char *name, struct clk *clk);
void (*gpu_model)(struct kgsl_device *device, char *str,
size_t bufsz);
+ void (*stop_fault_timer)(struct kgsl_device *device);
};
struct kgsl_ioctl {
diff --git a/drivers/gpu/msm/kgsl_iommu.c b/drivers/gpu/msm/kgsl_iommu.c
index 0325db8..86d4d61 100644
--- a/drivers/gpu/msm/kgsl_iommu.c
+++ b/drivers/gpu/msm/kgsl_iommu.c
@@ -797,6 +797,7 @@ static int kgsl_iommu_fault_handler(struct iommu_domain *domain,
int write;
struct kgsl_device *device;
struct adreno_device *adreno_dev;
+ struct adreno_gpudev *gpudev;
unsigned int no_page_fault_log = 0;
unsigned int curr_context_id = 0;
struct kgsl_context *context;
@@ -813,6 +814,7 @@ static int kgsl_iommu_fault_handler(struct iommu_domain *domain,
ctx = &iommu->ctx[KGSL_IOMMU_CONTEXT_USER];
device = KGSL_MMU_DEVICE(mmu);
adreno_dev = ADRENO_DEVICE(device);
+ gpudev = ADRENO_GPU_DEVICE(adreno_dev);
if (pt->name == KGSL_MMU_SECURE_PT)
ctx = &iommu->ctx[KGSL_IOMMU_CONTEXT_SECURE];
@@ -886,6 +888,16 @@ static int kgsl_iommu_fault_handler(struct iommu_domain *domain,
ctx->name, ptbase, contextidr,
write ? "write" : "read", fault_type);
+ if (gpudev->iommu_fault_block) {
+ unsigned int fsynr1;
+
+ fsynr1 = KGSL_IOMMU_GET_CTX_REG(ctx, FSYNR1);
+ KGSL_MEM_CRIT(ctx->kgsldev,
+ "FAULTING BLOCK: %s\n",
+ gpudev->iommu_fault_block(adreno_dev,
+ fsynr1));
+ }
+
/* Don't print the debug if this is a permissions fault */
if (!(flags & IOMMU_FAULT_PERMISSION)) {
_check_if_freed(ctx, addr, ptname);
diff --git a/drivers/gpu/msm/kgsl_pwrctrl.c b/drivers/gpu/msm/kgsl_pwrctrl.c
index 4d38794..b3e2b6a 100644
--- a/drivers/gpu/msm/kgsl_pwrctrl.c
+++ b/drivers/gpu/msm/kgsl_pwrctrl.c
@@ -2621,6 +2621,7 @@ _nap(struct kgsl_device *device)
return -EBUSY;
}
+ device->ftbl->stop_fault_timer(device);
kgsl_pwrscale_midframe_timer_cancel(device);
/*
diff --git a/drivers/gpu/msm/kgsl_snapshot.h b/drivers/gpu/msm/kgsl_snapshot.h
index d2ff8f1..340a7db 100644
--- a/drivers/gpu/msm/kgsl_snapshot.h
+++ b/drivers/gpu/msm/kgsl_snapshot.h
@@ -225,6 +225,7 @@ struct kgsl_snapshot_istore {
#define SNAPSHOT_DEBUG_CP_ROQ 10
#define SNAPSHOT_DEBUG_SHADER_MEMORY 11
#define SNAPSHOT_DEBUG_CP_MERCIU 12
+#define SNAPSHOT_DEBUG_SQE_VERSION 14
struct kgsl_snapshot_debug {
int type; /* Type identifier for the attached tata */
diff --git a/drivers/iommu/dma-mapping-fast.c b/drivers/iommu/dma-mapping-fast.c
index 34c7381..aded314 100644
--- a/drivers/iommu/dma-mapping-fast.c
+++ b/drivers/iommu/dma-mapping-fast.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -17,7 +17,8 @@
#include <linux/vmalloc.h>
#include <asm/cacheflush.h>
#include <asm/dma-iommu.h>
-
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
/* some redundant definitions... :( TODO: move to io-pgtable-fast.h */
#define FAST_PAGE_SHIFT 12
@@ -633,7 +634,7 @@ static void __fast_smmu_mapped_over_stale(struct dma_fast_smmu_mapping *fast,
dev_err(fast->dev, "Mapped over stale tlb at %pa\n", &iova);
dev_err(fast->dev, "bitmap (failure at idx %lu):\n", bitmap_idx);
dev_err(fast->dev, "ptep: %p pmds: %p diff: %lu\n", ptep,
- fast->pgtbl_pmds, ptep - fast->pgtbl_pmds);
+ fast->pgtbl_pmds, bitmap_idx);
print_hex_dump(KERN_ERR, "bmap: ", DUMP_PREFIX_ADDRESS,
32, 8, fast->bitmap, fast->bitmap_size, false);
}
@@ -683,7 +684,7 @@ static const struct dma_map_ops fast_smmu_dma_ops = {
* fast_smmu_attach_device function.
*/
static struct dma_fast_smmu_mapping *__fast_smmu_create_mapping_sized(
- dma_addr_t base, size_t size)
+ dma_addr_t base, u64 size)
{
struct dma_fast_smmu_mapping *fast;
@@ -696,7 +697,11 @@ static struct dma_fast_smmu_mapping *__fast_smmu_create_mapping_sized(
fast->num_4k_pages = size >> FAST_PAGE_SHIFT;
fast->bitmap_size = BITS_TO_LONGS(fast->num_4k_pages) * sizeof(long);
- fast->bitmap = kzalloc(fast->bitmap_size, GFP_KERNEL);
+ fast->bitmap = kzalloc(fast->bitmap_size, GFP_KERNEL | __GFP_NOWARN |
+ __GFP_NORETRY);
+ if (!fast->bitmap)
+ fast->bitmap = vzalloc(fast->bitmap_size);
+
if (!fast->bitmap)
goto err2;
@@ -726,7 +731,7 @@ int fast_smmu_attach_device(struct device *dev,
int atomic_domain = 1;
struct iommu_domain *domain = mapping->domain;
struct iommu_pgtbl_info info;
- size_t size = mapping->bits << PAGE_SHIFT;
+ u64 size = (u64)mapping->bits << PAGE_SHIFT;
if (mapping->base + size > (SZ_1G * 4ULL))
return -EINVAL;
@@ -780,7 +785,7 @@ void fast_smmu_detach_device(struct device *dev,
dev->archdata.mapping = NULL;
set_dma_ops(dev, NULL);
- kfree(mapping->fast->bitmap);
+ kvfree(mapping->fast->bitmap);
kfree(mapping->fast);
}
EXPORT_SYMBOL(fast_smmu_detach_device);
diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 393e20c4..f7739ae 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -78,7 +78,7 @@
/* Calculate the block/page mapping size at level l for pagetable in d. */
#define ARM_LPAE_BLOCK_SIZE(l,d) \
- (1 << (ilog2(sizeof(arm_lpae_iopte)) + \
+ (1ULL << (ilog2(sizeof(arm_lpae_iopte)) + \
((ARM_LPAE_MAX_LEVELS - (l)) * (d)->bits_per_level)))
/* Page table bits */
diff --git a/drivers/iommu/io-pgtable-fast.c b/drivers/iommu/io-pgtable-fast.c
index 85fe317..9b13fce 100644
--- a/drivers/iommu/io-pgtable-fast.c
+++ b/drivers/iommu/io-pgtable-fast.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -20,6 +20,7 @@
#include <linux/types.h>
#include <linux/io-pgtable-fast.h>
#include <asm/cacheflush.h>
+#include <linux/vmalloc.h>
#include "io-pgtable.h"
@@ -268,11 +269,18 @@ static size_t av8l_fast_unmap(struct io_pgtable_ops *ops, unsigned long iova,
return size;
}
+#if defined(CONFIG_ARM64)
+#define FAST_PGDNDX(va) (((va) & 0x7fc0000000) >> 27)
+#elif defined(CONFIG_ARM)
+#define FAST_PGDNDX(va) (((va) & 0xc0000000) >> 27)
+#endif
+
static phys_addr_t av8l_fast_iova_to_phys(struct io_pgtable_ops *ops,
unsigned long iova)
{
struct av8l_fast_io_pgtable *data = iof_pgtable_ops_to_data(ops);
av8l_fast_iopte pte, *pgdp, *pudp, *pmdp;
+ unsigned long pgd;
phys_addr_t phys;
const unsigned long pts = AV8L_FAST_PTE_TYPE_SHIFT;
const unsigned long ptm = AV8L_FAST_PTE_TYPE_MASK;
@@ -282,8 +290,9 @@ static phys_addr_t av8l_fast_iova_to_phys(struct io_pgtable_ops *ops,
/* TODO: clean up some of these magic numbers... */
- pgdp = (av8l_fast_iopte *)
- (((unsigned long)data->pgd) | ((iova & 0x7fc0000000) >> 27));
+ pgd = (unsigned long)data->pgd | FAST_PGDNDX(iova);
+ pgdp = (av8l_fast_iopte *)pgd;
+
pte = *pgdp;
if (((pte >> pts) & ptm) != ptt)
return 0;
@@ -345,7 +354,12 @@ av8l_fast_prepopulate_pgtables(struct av8l_fast_io_pgtable *data,
int i, j, pg = 0;
struct page **pages, *page;
- pages = kmalloc(sizeof(*pages) * NUM_PGTBL_PAGES, GFP_KERNEL);
+ pages = kmalloc(sizeof(*pages) * NUM_PGTBL_PAGES, __GFP_NOWARN |
+ __GFP_NORETRY);
+
+ if (!pages)
+ pages = vmalloc(sizeof(*pages) * NUM_PGTBL_PAGES);
+
if (!pages)
return -ENOMEM;
@@ -414,7 +428,7 @@ av8l_fast_prepopulate_pgtables(struct av8l_fast_io_pgtable *data,
for (i = 0; i < pg; ++i)
__free_page(pages[i]);
err_free_pages_arr:
- kfree(pages);
+ kvfree(pages);
return -ENOMEM;
}
@@ -473,6 +487,9 @@ av8l_fast_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
reg |= (64ULL - cfg->ias) << AV8L_FAST_TCR_T0SZ_SHIFT;
reg |= AV8L_FAST_TCR_EPD1_FAULT << AV8L_FAST_TCR_EPD1_SHIFT;
+#if defined(CONFIG_ARM)
+ reg |= ARM_32_LPAE_TCR_EAE;
+#endif
cfg->av8l_fast_cfg.tcr = reg;
/* MAIRs */
@@ -512,7 +529,7 @@ static void av8l_fast_free_pgtable(struct io_pgtable *iop)
vunmap(data->pmds);
for (i = 0; i < NUM_PGTBL_PAGES; ++i)
__free_page(data->pages[i]);
- kfree(data->pages);
+ kvfree(data->pages);
kfree(data);
}
@@ -560,7 +577,7 @@ static bool av8l_fast_range_has_specific_mapping(struct io_pgtable_ops *ops,
const phys_addr_t phys_start,
const size_t size)
{
- unsigned long iova = iova_start;
+ u64 iova = iova_start;
phys_addr_t phys = phys_start;
while (iova < (iova_start + size)) {
@@ -576,11 +593,12 @@ static bool av8l_fast_range_has_specific_mapping(struct io_pgtable_ops *ops,
static int __init av8l_fast_positive_testing(void)
{
int failed = 0;
- unsigned long iova;
+ u64 iova;
struct io_pgtable_ops *ops;
struct io_pgtable_cfg cfg;
struct av8l_fast_io_pgtable *data;
av8l_fast_iopte *pmds;
+ u64 max = SZ_1G * 4ULL - 1;
cfg = (struct io_pgtable_cfg) {
.quirks = 0,
@@ -600,19 +618,18 @@ static int __init av8l_fast_positive_testing(void)
pmds = data->pmds;
/* map the entire 4GB VA space with 4K map calls */
- for (iova = 0; iova < SZ_1G * 4UL; iova += SZ_4K) {
+ for (iova = 0; iova < max; iova += SZ_4K) {
if (WARN_ON(ops->map(ops, iova, iova, SZ_4K, IOMMU_READ))) {
failed++;
continue;
}
}
-
if (WARN_ON(!av8l_fast_range_has_specific_mapping(ops, 0, 0,
- SZ_1G * 4UL)))
+ max)))
failed++;
/* unmap it all */
- for (iova = 0; iova < SZ_1G * 4UL; iova += SZ_4K) {
+ for (iova = 0; iova < max; iova += SZ_4K) {
if (WARN_ON(ops->unmap(ops, iova, SZ_4K) != SZ_4K))
failed++;
}
@@ -621,7 +638,7 @@ static int __init av8l_fast_positive_testing(void)
av8l_fast_clear_stale_ptes(pmds, false);
/* map the entire 4GB VA space with 8K map calls */
- for (iova = 0; iova < SZ_1G * 4UL; iova += SZ_8K) {
+ for (iova = 0; iova < max; iova += SZ_8K) {
if (WARN_ON(ops->map(ops, iova, iova, SZ_8K, IOMMU_READ))) {
failed++;
continue;
@@ -629,11 +646,11 @@ static int __init av8l_fast_positive_testing(void)
}
if (WARN_ON(!av8l_fast_range_has_specific_mapping(ops, 0, 0,
- SZ_1G * 4UL)))
+ max)))
failed++;
/* unmap it all with 8K unmap calls */
- for (iova = 0; iova < SZ_1G * 4UL; iova += SZ_8K) {
+ for (iova = 0; iova < max; iova += SZ_8K) {
if (WARN_ON(ops->unmap(ops, iova, SZ_8K) != SZ_8K))
failed++;
}
@@ -642,7 +659,7 @@ static int __init av8l_fast_positive_testing(void)
av8l_fast_clear_stale_ptes(pmds, false);
/* map the entire 4GB VA space with 16K map calls */
- for (iova = 0; iova < SZ_1G * 4UL; iova += SZ_16K) {
+ for (iova = 0; iova < max; iova += SZ_16K) {
if (WARN_ON(ops->map(ops, iova, iova, SZ_16K, IOMMU_READ))) {
failed++;
continue;
@@ -650,11 +667,11 @@ static int __init av8l_fast_positive_testing(void)
}
if (WARN_ON(!av8l_fast_range_has_specific_mapping(ops, 0, 0,
- SZ_1G * 4UL)))
+ max)))
failed++;
/* unmap it all */
- for (iova = 0; iova < SZ_1G * 4UL; iova += SZ_16K) {
+ for (iova = 0; iova < max; iova += SZ_16K) {
if (WARN_ON(ops->unmap(ops, iova, SZ_16K) != SZ_16K))
failed++;
}
@@ -663,7 +680,7 @@ static int __init av8l_fast_positive_testing(void)
av8l_fast_clear_stale_ptes(pmds, false);
/* map the entire 4GB VA space with 64K map calls */
- for (iova = 0; iova < SZ_1G * 4UL; iova += SZ_64K) {
+ for (iova = 0; iova < max; iova += SZ_64K) {
if (WARN_ON(ops->map(ops, iova, iova, SZ_64K, IOMMU_READ))) {
failed++;
continue;
@@ -671,11 +688,11 @@ static int __init av8l_fast_positive_testing(void)
}
if (WARN_ON(!av8l_fast_range_has_specific_mapping(ops, 0, 0,
- SZ_1G * 4UL)))
+ max)))
failed++;
/* unmap it all at once */
- if (WARN_ON(ops->unmap(ops, 0, SZ_1G * 4UL) != SZ_1G * 4UL))
+ if (WARN_ON(ops->unmap(ops, 0, max) != max))
failed++;
free_io_pgtable_ops(ops);
diff --git a/drivers/iommu/iommu-debug.c b/drivers/iommu/iommu-debug.c
index 45ffb40..5730126 100644
--- a/drivers/iommu/iommu-debug.c
+++ b/drivers/iommu/iommu-debug.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -822,7 +822,7 @@ static int iommu_debug_profiling_fast_dma_api_show(struct seq_file *s,
if (!virt)
goto out;
- mapping = arm_iommu_create_mapping(&platform_bus_type, 0, SZ_1G * 4UL);
+ mapping = arm_iommu_create_mapping(&platform_bus_type, 0, SZ_1G * 4ULL);
if (!mapping) {
seq_puts(s, "fast_smmu_create_mapping failed\n");
goto out_kfree;
@@ -922,8 +922,8 @@ static const struct file_operations iommu_debug_profiling_fast_dma_api_fops = {
static int __tlb_stress_sweep(struct device *dev, struct seq_file *s)
{
int i, ret = 0;
- unsigned long iova;
- const unsigned long max = SZ_1G * 4UL;
+ u64 iova;
+ const u64 max = SZ_1G * 4ULL - 1;
void *virt;
phys_addr_t phys;
dma_addr_t dma_addr;
@@ -995,8 +995,8 @@ static int __tlb_stress_sweep(struct device *dev, struct seq_file *s)
}
/* we're all full again. unmap everything. */
- for (dma_addr = 0; dma_addr < max; dma_addr += SZ_8K)
- dma_unmap_single(dev, dma_addr, SZ_8K, DMA_TO_DEVICE);
+ for (iova = 0; iova < max; iova += SZ_8K)
+ dma_unmap_single(dev, (dma_addr_t)iova, SZ_8K, DMA_TO_DEVICE);
out:
free_pages((unsigned long)virt, get_order(SZ_8K));
@@ -1029,7 +1029,7 @@ static int __rand_va_sweep(struct device *dev, struct seq_file *s,
const size_t size)
{
u64 iova;
- const unsigned long max = SZ_1G * 4UL;
+ const u64 max = SZ_1G * 4ULL - 1;
int i, remapped, unmapped, ret = 0;
void *virt;
dma_addr_t dma_addr, dma_addr2;
@@ -1061,9 +1061,9 @@ static int __rand_va_sweep(struct device *dev, struct seq_file *s,
fib_init(&fib);
for (iova = get_next_fib(&fib) * size;
iova < max - size;
- iova = get_next_fib(&fib) * size) {
- dma_addr = iova;
- dma_addr2 = max - size - iova;
+ iova = (u64)get_next_fib(&fib) * size) {
+ dma_addr = (dma_addr_t)(iova);
+ dma_addr2 = (dma_addr_t)((max + 1) - size - iova);
if (dma_addr == dma_addr2) {
WARN(1,
"%s test needs update! The random number sequence is folding in on itself and should be changed.\n",
@@ -1089,8 +1089,8 @@ static int __rand_va_sweep(struct device *dev, struct seq_file *s,
ret = -EINVAL;
}
- for (dma_addr = 0; dma_addr < max; dma_addr += size)
- dma_unmap_single(dev, dma_addr, size, DMA_TO_DEVICE);
+ for (iova = 0; iova < max; iova += size)
+ dma_unmap_single(dev, (dma_addr_t)iova, size, DMA_TO_DEVICE);
out:
free_pages((unsigned long)virt, get_order(size));
@@ -1118,10 +1118,11 @@ static int __check_mapping(struct device *dev, struct iommu_domain *domain,
static int __full_va_sweep(struct device *dev, struct seq_file *s,
const size_t size, struct iommu_domain *domain)
{
- unsigned long iova;
+ u64 iova;
dma_addr_t dma_addr;
void *virt;
phys_addr_t phys;
+ const u64 max = SZ_1G * 4ULL - 1;
int ret = 0, i;
virt = (void *)__get_free_pages(GFP_KERNEL, get_order(size));
@@ -1136,7 +1137,7 @@ static int __full_va_sweep(struct device *dev, struct seq_file *s,
}
phys = virt_to_phys(virt);
- for (iova = 0, i = 0; iova < SZ_1G * 4UL; iova += size, ++i) {
+ for (iova = 0, i = 0; iova < max; iova += size, ++i) {
unsigned long expected = iova;
dma_addr = dma_map_single(dev, virt, size, DMA_TO_DEVICE);
@@ -1184,8 +1185,8 @@ static int __full_va_sweep(struct device *dev, struct seq_file *s,
}
out:
- for (dma_addr = 0; dma_addr < SZ_1G * 4UL; dma_addr += size)
- dma_unmap_single(dev, dma_addr, size, DMA_TO_DEVICE);
+ for (iova = 0; iova < max; iova += size)
+ dma_unmap_single(dev, (dma_addr_t)iova, size, DMA_TO_DEVICE);
free_pages((unsigned long)virt, get_order(size));
return ret;
@@ -1374,7 +1375,8 @@ static int __apply_to_new_mapping(struct seq_file *s,
int ret = -EINVAL, fast = 1;
phys_addr_t pt_phys;
- mapping = arm_iommu_create_mapping(&platform_bus_type, 0, SZ_1G * 4UL);
+ mapping = arm_iommu_create_mapping(&platform_bus_type, 0,
+ (SZ_1G * 4ULL));
if (!mapping)
goto out;
@@ -1443,7 +1445,9 @@ static int iommu_debug_functional_arm_dma_api_show(struct seq_file *s,
size_t sizes[] = {SZ_4K, SZ_64K, SZ_2M, SZ_1M * 12, 0};
int ret = -EINVAL;
- mapping = arm_iommu_create_mapping(&platform_bus_type, 0, SZ_1G * 4UL);
+ /* Make the size equal to MAX_ULONG */
+ mapping = arm_iommu_create_mapping(&platform_bus_type, 0,
+ (SZ_1G * 4ULL - 1));
if (!mapping)
goto out;
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_core.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_core.h
index 980e4af..819f57b 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_core.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_core.h
@@ -459,6 +459,7 @@ struct sde_rot_mgr {
bool input);
int (*ops_hw_get_downscale_caps)(struct sde_rot_mgr *mgr, char *caps,
int len);
+ int (*ops_hw_get_maxlinewidth)(struct sde_rot_mgr *mgr);
void *hw_data;
};
@@ -490,6 +491,14 @@ static inline int sde_rotator_get_downscale_caps(struct sde_rot_mgr *mgr,
return 0;
}
+static inline int sde_rotator_get_maxlinewidth(struct sde_rot_mgr *mgr)
+{
+ if (mgr && mgr->ops_hw_get_maxlinewidth)
+ return mgr->ops_hw_get_maxlinewidth(mgr);
+
+ return 2048;
+}
+
static inline int __compare_session_item_rect(
struct sde_rotation_buf_info *s_rect,
struct sde_rect *i_rect, uint32_t i_fmt, bool src)
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c
index c061446..1c94632 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_dev.c
@@ -1315,6 +1315,35 @@ int sde_rotator_inline_get_downscale_caps(struct platform_device *pdev,
EXPORT_SYMBOL(sde_rotator_inline_get_downscale_caps);
/*
+ * sde_rotator_inline_get_maxlinewidth - get maximum line width of rotator
+ * @pdev: Pointer to platform device
+ * return: maximum line width
+ */
+int sde_rotator_inline_get_maxlinewidth(struct platform_device *pdev)
+{
+ struct sde_rotator_device *rot_dev;
+ int maxlinewidth;
+
+ if (!pdev) {
+ SDEROT_ERR("invalid platform device\n");
+ return -EINVAL;
+ }
+
+ rot_dev = (struct sde_rotator_device *)platform_get_drvdata(pdev);
+ if (!rot_dev || !rot_dev->mgr) {
+ SDEROT_ERR("invalid rotator device\n");
+ return -EINVAL;
+ }
+
+ sde_rot_mgr_lock(rot_dev->mgr);
+ maxlinewidth = sde_rotator_get_maxlinewidth(rot_dev->mgr);
+ sde_rot_mgr_unlock(rot_dev->mgr);
+
+ return maxlinewidth;
+}
+EXPORT_SYMBOL(sde_rotator_inline_get_maxlinewidth);
+
+/*
* sde_rotator_inline_get_pixfmt_caps - get pixel format capability
* @pdev: Pointer to platform device
* @pixfmt: array of pixel format buffer
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_inline.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_inline.h
index ec89785..27fd0c3 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_inline.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_inline.h
@@ -104,6 +104,7 @@ int sde_rotator_inline_get_dst_pixfmt(struct platform_device *pdev,
u32 src_pixfmt, u32 *dst_pixfmt);
int sde_rotator_inline_get_downscale_caps(struct platform_device *pdev,
char *downscale_caps, int len);
+int sde_rotator_inline_get_maxlinewidth(struct platform_device *pdev);
int sde_rotator_inline_get_pixfmt_caps(struct platform_device *pdev,
bool input, u32 *pixfmt, int len);
int sde_rotator_inline_commit(void *handle, struct sde_rotator_inline_cmd *cmd,
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
index 9071361..a152573 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
@@ -55,6 +55,8 @@
#define DEFAULT_UBWC_MALSIZE 1
#define DEFAULT_UBWC_SWIZZLE 1
+#define DEFAULT_MAXLINEWIDTH 4096
+
/* Macro for constructing the REGDMA command */
#define SDE_REGDMA_WRITE(p, off, data) \
do { \
@@ -2457,11 +2459,24 @@ static int sde_hw_rotator_validate_entry(struct sde_rot_mgr *mgr,
struct sde_rot_entry *entry)
{
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
+ struct sde_hw_rotator *hw_data;
int ret = 0;
u16 src_w, src_h, dst_w, dst_h;
struct sde_rotation_item *item = &entry->item;
struct sde_mdp_format_params *fmt;
+ if (!mgr || !entry || !mgr->hw_data) {
+ SDEROT_ERR("invalid parameters\n");
+ return -EINVAL;
+ }
+
+ hw_data = mgr->hw_data;
+
+ if (hw_data->maxlinewidth < item->src_rect.w) {
+ SDEROT_ERR("invalid src width %u\n", item->src_rect.w);
+ return -EINVAL;
+ }
+
src_w = item->src_rect.w;
src_h = item->src_rect.h;
@@ -2756,6 +2771,25 @@ static int sde_hw_rotator_get_downscale_caps(struct sde_rot_mgr *mgr,
}
/*
+ * sde_hw_rotator_get_maxlinewidth - get maximum line width supported
+ * @mgr: Pointer to rotator manager
+ * return: maximum line width supported by hardware
+ */
+static int sde_hw_rotator_get_maxlinewidth(struct sde_rot_mgr *mgr)
+{
+ struct sde_hw_rotator *rot;
+
+ if (!mgr || !mgr->hw_data) {
+ SDEROT_ERR("null parameters\n");
+ return -EINVAL;
+ }
+
+ rot = mgr->hw_data;
+
+ return rot->maxlinewidth;
+}
+
+/*
* sde_hw_rotator_parse_dt - parse r3 specific device tree settings
* @hw_data: Pointer to rotator hw
* @dev: Pointer to platform device
@@ -2824,6 +2858,16 @@ static int sde_hw_rotator_parse_dt(struct sde_hw_rotator *hw_data,
hw_data->sbuf_headroom = data;
}
+ ret = of_property_read_u32(dev->dev.of_node,
+ "qcom,mdss-rot-linewidth", &data);
+ if (ret) {
+ ret = 0;
+ hw_data->maxlinewidth = DEFAULT_MAXLINEWIDTH;
+ } else {
+ SDEROT_DBG("set mdss-rot-linewidth to %d\n", data);
+ hw_data->maxlinewidth = data;
+ }
+
return ret;
}
@@ -2871,6 +2915,7 @@ int sde_rotator_r3_init(struct sde_rot_mgr *mgr)
mgr->ops_hw_pre_pmevent = sde_hw_rotator_pre_pmevent;
mgr->ops_hw_post_pmevent = sde_hw_rotator_post_pmevent;
mgr->ops_hw_get_downscale_caps = sde_hw_rotator_get_downscale_caps;
+ mgr->ops_hw_get_maxlinewidth = sde_hw_rotator_get_maxlinewidth;
ret = sde_hw_rotator_parse_dt(mgr->hw_data, mgr->pdev);
if (ret)
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h
index d1607d9..22eaa3f 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h
@@ -262,6 +262,7 @@ struct sde_hw_rotator_resource_info {
* @outpixfmts: array of supported output pixel formats in fourcc
* @num_outpixfmt: size of the supported output pixel formats array
* @downscale_caps: capability string of scaling
+ * @maxlinewidth: maximum line width supported
*/
struct sde_hw_rotator {
/* base */
@@ -322,6 +323,7 @@ struct sde_hw_rotator {
u32 *outpixfmts;
u32 num_outpixfmt;
const char *downscale_caps;
+ u32 maxlinewidth;
};
/**
diff --git a/drivers/media/platform/msm/vidc/msm_smem.c b/drivers/media/platform/msm/vidc/msm_smem.c
index a949c55..19a1e3f 100644
--- a/drivers/media/platform/msm/vidc/msm_smem.c
+++ b/drivers/media/platform/msm/vidc/msm_smem.c
@@ -202,8 +202,8 @@ static int ion_user_to_kernel(struct smem_client *client, int fd, u32 offset,
unsigned long align = SZ_4K;
unsigned long ion_flags = 0;
-#ifndef CONFIG_ION
- hndl = ion_import_dma_buf(client->clnt, fd);
+#ifdef CONFIG_ION
+ hndl = ion_import_dma_buf_fd(client->clnt, fd);
#endif
dprintk(VIDC_DBG, "%s ion handle: %pK\n", __func__, hndl);
if (IS_ERR_OR_NULL(hndl)) {
@@ -476,8 +476,8 @@ bool msm_smem_compare_buffers(void *clt, int fd, void *priv)
clt, priv);
return false;
}
-#ifndef CONFIG_ION
- handle = ion_import_dma_buf(client->clnt, fd);
+#ifdef CONFIG_ION
+ handle = ion_import_dma_buf_fd(client->clnt, fd);
#endif
ret = handle == priv;
(!IS_ERR_OR_NULL(handle)) ? ion_free(client->clnt, handle) : 0;
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 9c9d130..d593315 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -780,6 +780,15 @@
help
Memory time statistics exported to /sys/kernel/memory_state_time
+config QPNP_MISC
+ tristate "QPNP Misc Peripheral"
+ depends on MFD_SPMI_PMIC
+ help
+ Say 'y' here to include support for the QTI QPNP MISC
+ peripheral. The MISC peripheral holds the USB ID interrupt
+ and the driver provides an API to check if this interrupt
+ is available on the current PMIC chip.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 19f9e1d..dd12e9a 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -53,6 +53,7 @@
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_PANEL) += panel.o
+obj-$(CONFIG_QPNP_MISC) += qpnp-misc.o
obj-y += qcom/
obj-$(CONFIG_MEMORY_STATE_TIME) += memory_state_time.o
diff --git a/drivers/misc/qpnp-misc.c b/drivers/misc/qpnp-misc.c
new file mode 100644
index 0000000..3c11de0
--- /dev/null
+++ b/drivers/misc/qpnp-misc.c
@@ -0,0 +1,352 @@
+/* Copyright (c) 2013-2014,2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/qpnp/qpnp-misc.h>
+
+#define QPNP_MISC_DEV_NAME "qcom,qpnp-misc"
+
+#define REG_DIG_MAJOR_REV 0x01
+#define REG_SUBTYPE 0x05
+#define REG_PWM_SEL 0x49
+#define REG_GP_DRIVER_EN 0x4C
+
+#define PWM_SEL_MAX 0x03
+#define GP_DRIVER_EN_BIT BIT(0)
+
+static DEFINE_MUTEX(qpnp_misc_dev_list_mutex);
+static LIST_HEAD(qpnp_misc_dev_list);
+
+struct qpnp_misc_version {
+ u8 subtype;
+ u8 dig_major_rev;
+};
+
+/**
+ * struct qpnp_misc_dev - holds controller device specific information
+ * @list: Doubly-linked list parameter linking to other
+ * qpnp_misc devices.
+ * @mutex: Mutex lock that is used to ensure mutual
+ * exclusion between probing and accessing misc
+ * driver information
+ * @dev: Device pointer to the misc device
+ * @regmap: Regmap pointer to the misc device
+ * @version: struct that holds the subtype and dig_major_rev
+ * of the chip.
+ */
+struct qpnp_misc_dev {
+ struct list_head list;
+ struct mutex mutex;
+ struct device *dev;
+ struct regmap *regmap;
+ struct qpnp_misc_version version;
+
+ u32 base;
+ u8 pwm_sel;
+ bool enable_gp_driver;
+};
+
+static const struct of_device_id qpnp_misc_match_table[] = {
+ { .compatible = QPNP_MISC_DEV_NAME },
+ {}
+};
+
+enum qpnp_misc_version_name {
+ INVALID,
+ PM8941,
+ PM8226,
+ PMA8084,
+ PMDCALIFORNIUM,
+};
+
+static struct qpnp_misc_version irq_support_version[] = {
+ {0x00, 0x00}, /* INVALID */
+ {0x01, 0x02}, /* PM8941 */
+ {0x07, 0x00}, /* PM8226 */
+ {0x09, 0x00}, /* PMA8084 */
+ {0x16, 0x00}, /* PMDCALIFORNIUM */
+};
+
+static int qpnp_write_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 val)
+{
+ int rc;
+
+ rc = regmap_write(mdev->regmap, mdev->base + addr, val);
+ if (rc)
+ pr_err("regmap write failed rc=%d\n", rc);
+
+ return rc;
+}
+
+static int qpnp_read_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 *val)
+{
+ unsigned int temp;
+ int rc;
+
+ rc = regmap_read(mdev->regmap, mdev->base + addr, &temp);
+ if (rc) {
+ pr_err("regmap read failed rc=%d\n", rc);
+ return rc;
+ }
+
+ *val = (u8)temp;
+ return rc;
+}
+
+static int get_qpnp_misc_version_name(struct qpnp_misc_dev *dev)
+{
+ int i;
+
+ for (i = 1; i < ARRAY_SIZE(irq_support_version); i++)
+ if (dev->version.subtype == irq_support_version[i].subtype &&
+ dev->version.dig_major_rev >=
+ irq_support_version[i].dig_major_rev)
+ return i;
+
+ return INVALID;
+}
+
+static bool __misc_irqs_available(struct qpnp_misc_dev *dev)
+{
+ int version_name = get_qpnp_misc_version_name(dev);
+
+ if (version_name == INVALID)
+ return 0;
+ return 1;
+}
+
+int qpnp_misc_read_reg(struct device_node *node, u16 addr, u8 *val)
+{
+ struct qpnp_misc_dev *mdev = NULL;
+ struct qpnp_misc_dev *mdev_found = NULL;
+ int rc;
+ u8 temp;
+
+ if (IS_ERR_OR_NULL(node)) {
+ pr_err("Invalid device node pointer\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&qpnp_misc_dev_list_mutex);
+ list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
+ if (mdev->dev->of_node == node) {
+ mdev_found = mdev;
+ break;
+ }
+ }
+ mutex_unlock(&qpnp_misc_dev_list_mutex);
+
+ if (!mdev_found) {
+ /*
+ * No MISC device was found. This API should only
+ * be called by drivers which have specified the
+ * misc phandle in their device tree node.
+ */
+ pr_err("no probed misc device found\n");
+ return -EPROBE_DEFER;
+ }
+
+ rc = qpnp_read_byte(mdev, addr, &temp);
+ if (rc < 0) {
+ dev_err(mdev->dev, "Failed to read addr %x, rc=%d\n", addr, rc);
+ return rc;
+ }
+
+ *val = temp;
+ return 0;
+}
+
+int qpnp_misc_irqs_available(struct device *consumer_dev)
+{
+ struct device_node *misc_node = NULL;
+ struct qpnp_misc_dev *mdev = NULL;
+ struct qpnp_misc_dev *mdev_found = NULL;
+
+ if (IS_ERR_OR_NULL(consumer_dev)) {
+ pr_err("Invalid consumer device pointer\n");
+ return -EINVAL;
+ }
+
+ misc_node = of_parse_phandle(consumer_dev->of_node, "qcom,misc-ref", 0);
+ if (!misc_node) {
+ pr_debug("Could not find qcom,misc-ref property in %s\n",
+ consumer_dev->of_node->full_name);
+ return 0;
+ }
+
+ mutex_lock(&qpnp_misc_dev_list_mutex);
+ list_for_each_entry(mdev, &qpnp_misc_dev_list, list) {
+ if (mdev->dev->of_node == misc_node) {
+ mdev_found = mdev;
+ break;
+ }
+ }
+ mutex_unlock(&qpnp_misc_dev_list_mutex);
+
+ if (!mdev_found) {
+ /*
+ * No MISC device was found. This API should only
+ * be called by drivers which have specified the
+ * misc phandle in their device tree node.
+ */
+ pr_err("no probed misc device found\n");
+ return -EPROBE_DEFER;
+ }
+
+ return __misc_irqs_available(mdev_found);
+}
+
+static int qpnp_misc_dt_init(struct qpnp_misc_dev *mdev)
+{
+ struct device_node *node = mdev->dev->of_node;
+ u32 val;
+ int rc;
+
+ rc = of_property_read_u32(node, "reg", &mdev->base);
+ if (rc < 0 || !mdev->base) {
+ dev_err(mdev->dev, "Base address not defined or invalid\n");
+ return -EINVAL;
+ }
+
+ if (!of_property_read_u32(node, "qcom,pwm-sel", &val)) {
+ if (val > PWM_SEL_MAX) {
+ dev_err(mdev->dev, "Invalid value for pwm-sel\n");
+ return -EINVAL;
+ }
+ mdev->pwm_sel = (u8)val;
+ }
+ mdev->enable_gp_driver = of_property_read_bool(node,
+ "qcom,enable-gp-driver");
+
+ WARN((mdev->pwm_sel > 0 && !mdev->enable_gp_driver),
+ "Setting PWM source without enabling gp driver\n");
+ WARN((mdev->pwm_sel == 0 && mdev->enable_gp_driver),
+ "Enabling gp driver without setting PWM source\n");
+
+ return 0;
+}
+
+static int qpnp_misc_config(struct qpnp_misc_dev *mdev)
+{
+ int rc, version_name;
+
+ version_name = get_qpnp_misc_version_name(mdev);
+
+ switch (version_name) {
+ case PMDCALIFORNIUM:
+ if (mdev->pwm_sel > 0 && mdev->enable_gp_driver) {
+ rc = qpnp_write_byte(mdev, REG_PWM_SEL, mdev->pwm_sel);
+ if (rc < 0) {
+ dev_err(mdev->dev,
+ "Failed to write PWM_SEL reg\n");
+ return rc;
+ }
+
+ rc = qpnp_write_byte(mdev, REG_GP_DRIVER_EN,
+ GP_DRIVER_EN_BIT);
+ if (rc < 0) {
+ dev_err(mdev->dev,
+ "Failed to write GP_DRIVER_EN reg\n");
+ return rc;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int qpnp_misc_probe(struct platform_device *pdev)
+{
+ struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL);
+ int rc;
+
+ mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
+ if (!mdev)
+ return -ENOMEM;
+
+ mdev->dev = &pdev->dev;
+ mdev->regmap = dev_get_regmap(mdev->dev->parent, NULL);
+ if (!mdev->regmap) {
+ dev_err(mdev->dev, "Parent regmap is unavailable\n");
+ return -ENXIO;
+ }
+
+ rc = qpnp_misc_dt_init(mdev);
+ if (rc < 0) {
+ dev_err(mdev->dev,
+ "Error reading device tree properties, rc=%d\n", rc);
+ return rc;
+ }
+
+
+ rc = qpnp_read_byte(mdev, REG_SUBTYPE, &mdev->version.subtype);
+ if (rc < 0) {
+ dev_err(mdev->dev, "Failed to read subtype, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = qpnp_read_byte(mdev, REG_DIG_MAJOR_REV,
+ &mdev->version.dig_major_rev);
+ if (rc < 0) {
+ dev_err(mdev->dev, "Failed to read dig_major_rev, rc=%d\n", rc);
+ return rc;
+ }
+
+ mutex_lock(&qpnp_misc_dev_list_mutex);
+ list_add_tail(&mdev->list, &qpnp_misc_dev_list);
+ mutex_unlock(&qpnp_misc_dev_list_mutex);
+
+ rc = qpnp_misc_config(mdev);
+ if (rc < 0) {
+ dev_err(mdev->dev,
+ "Error configuring module registers, rc=%d\n", rc);
+ return rc;
+ }
+
+ dev_info(mdev->dev, "probe successful\n");
+ return 0;
+}
+
+static struct platform_driver qpnp_misc_driver = {
+ .probe = qpnp_misc_probe,
+ .driver = {
+ .name = QPNP_MISC_DEV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = qpnp_misc_match_table,
+ },
+};
+
+static int __init qpnp_misc_init(void)
+{
+ return platform_driver_register(&qpnp_misc_driver);
+}
+
+static void __exit qpnp_misc_exit(void)
+{
+ return platform_driver_unregister(&qpnp_misc_driver);
+}
+
+subsys_initcall(qpnp_misc_init);
+module_exit(qpnp_misc_exit);
+
+MODULE_DESCRIPTION(QPNP_MISC_DEV_NAME);
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" QPNP_MISC_DEV_NAME);
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index 817fcf8..ace47ae 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -2324,10 +2324,7 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
err = mmc_blk_reset(md, card->host, type);
if (!err)
break;
- if (err == -ENODEV ||
- mmc_packed_cmd(mq_rq->cmd_type))
- goto cmd_abort;
- /* Fall through */
+ goto cmd_abort;
}
case MMC_BLK_ECC_ERR:
if (brq->data.blocks > 1) {
@@ -2843,6 +2840,13 @@ static const struct mmc_fixup blk_fixups[] =
MMC_QUIRK_LONG_READ_TIME),
/*
+ * Some Samsung MMC cards need longer data read timeout than
+ * indicated in CSD.
+ */
+ MMC_FIXUP("Q7XSAB", CID_MANFID_SAMSUNG, 0x100, add_quirk_mmc,
+ MMC_QUIRK_LONG_READ_TIME),
+
+ /*
* On these Samsung MoviNAND parts, performing secure erase or
* secure trim can result in unrecoverable corruption due to a
* firmware bug.
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index e7af954..e19d912 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -1089,6 +1089,9 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort)
if (pm)
pm_runtime_get_sync(mmc_dev(host));
+ if (host->ops->enable && !stop && host->claim_cnt == 1)
+ host->ops->enable(host);
+
return stop;
}
EXPORT_SYMBOL(__mmc_claim_host);
@@ -1106,6 +1109,9 @@ void mmc_release_host(struct mmc_host *host)
WARN_ON(!host->claimed);
+ if (host->ops->disable && host->claim_cnt == 1)
+ host->ops->disable(host);
+
spin_lock_irqsave(&host->lock, flags);
if (--host->claim_cnt) {
/* Release for nested claim */
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5274f50..defb66e 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -396,18 +396,26 @@
If unsure, say N.
config MMC_SDHCI_MSM
- tristate "Qualcomm SDHCI Controller Support"
- depends on ARCH_QCOM || (ARM && COMPILE_TEST)
+ tristate "Qualcomm Technologies, Inc. SDHCI Controller Support"
+ depends on ARCH_QCOM || ARCH_MSM || (ARM && COMPILE_TEST)
depends on MMC_SDHCI_PLTFM
help
This selects the Secure Digital Host Controller Interface (SDHCI)
- support present in Qualcomm SOCs. The controller supports
- SD/MMC/SDIO devices.
+ support present in Qualcomm Technologies, Inc. SOCs. The controller
+ supports SD/MMC/SDIO devices.
If you have a controller with this interface, say Y or M here.
If unsure, say N.
+config MMC_MSM
+ tristate "Qualcomm SDCC Controller Support"
+ depends on MMC && (ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50)
+ help
+ This provides support for the SD/MMC cell found in the
+ MSM and QSD SOCs from Qualcomm. The controller also has
+ support for SDIO devices.
+
config MMC_MXC
tristate "Freescale i.MX21/27/31 or MPC512x Multimedia Card support"
depends on ARCH_MXC || PPC_MPC512x
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e2bdaaf..ef56624 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -71,8 +71,8 @@
obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o
obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o
obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o
-obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o
obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o
+obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o
obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32) += sdhci-pic32.o
obj-$(CONFIG_MMC_SDHCI_BRCMSTB) += sdhci-brcmstb.o
diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c
index 90ed2e1..b2fdb19 100644
--- a/drivers/mmc/host/sdhci-msm.c
+++ b/drivers/mmc/host/sdhci-msm.c
@@ -1,7 +1,8 @@
/*
- * drivers/mmc/host/sdhci-msm.c - Qualcomm SDHCI Platform driver
+ * drivers/mmc/host/sdhci-msm.c - Qualcomm Technologies, Inc. MSM SDHCI Platform
+ * driver source file
*
- * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2012-2013, 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
@@ -15,106 +16,239 @@
*/
#include <linux/module.h>
-#include <linux/of_device.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/gfp.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+#include <linux/io.h>
#include <linux/delay.h>
-#include <linux/mmc/mmc.h>
+#include <linux/scatterlist.h>
#include <linux/slab.h>
+#include <linux/mmc/mmc.h>
+#include <linux/msm-bus.h>
#include "sdhci-pltfm.h"
-#define CORE_MCI_VERSION 0x50
-#define CORE_VERSION_MAJOR_SHIFT 28
-#define CORE_VERSION_MAJOR_MASK (0xf << CORE_VERSION_MAJOR_SHIFT)
-#define CORE_VERSION_MINOR_MASK 0xff
-
+#define SDHCI_VER_100 0x2B
#define CORE_HC_MODE 0x78
#define HC_MODE_EN 0x1
-#define CORE_POWER 0x0
-#define CORE_SW_RST BIT(7)
-#define CORE_PWRCTL_STATUS 0xdc
-#define CORE_PWRCTL_MASK 0xe0
-#define CORE_PWRCTL_CLEAR 0xe4
-#define CORE_PWRCTL_CTL 0xe8
-#define CORE_PWRCTL_BUS_OFF BIT(0)
-#define CORE_PWRCTL_BUS_ON BIT(1)
-#define CORE_PWRCTL_IO_LOW BIT(2)
-#define CORE_PWRCTL_IO_HIGH BIT(3)
-#define CORE_PWRCTL_BUS_SUCCESS BIT(0)
-#define CORE_PWRCTL_IO_SUCCESS BIT(2)
-#define REQ_BUS_OFF BIT(0)
-#define REQ_BUS_ON BIT(1)
-#define REQ_IO_LOW BIT(2)
-#define REQ_IO_HIGH BIT(3)
-#define INT_MASK 0xf
+#define CORE_POWER 0x0
+#define CORE_SW_RST (1 << 7)
+
+#define CORE_PWRCTL_STATUS 0xDC
+#define CORE_PWRCTL_MASK 0xE0
+#define CORE_PWRCTL_CLEAR 0xE4
+#define CORE_PWRCTL_CTL 0xE8
+
+#define CORE_PWRCTL_BUS_OFF 0x01
+#define CORE_PWRCTL_BUS_ON (1 << 1)
+#define CORE_PWRCTL_IO_LOW (1 << 2)
+#define CORE_PWRCTL_IO_HIGH (1 << 3)
+
+#define CORE_PWRCTL_BUS_SUCCESS 0x01
+#define CORE_PWRCTL_BUS_FAIL (1 << 1)
+#define CORE_PWRCTL_IO_SUCCESS (1 << 2)
+#define CORE_PWRCTL_IO_FAIL (1 << 3)
+
+#define INT_MASK 0xF
#define MAX_PHASES 16
-#define CORE_DLL_LOCK BIT(7)
-#define CORE_DLL_EN BIT(16)
-#define CORE_CDR_EN BIT(17)
-#define CORE_CK_OUT_EN BIT(18)
-#define CORE_CDR_EXT_EN BIT(19)
-#define CORE_DLL_PDN BIT(29)
-#define CORE_DLL_RST BIT(30)
+
+#define CORE_DLL_LOCK (1 << 7)
+#define CORE_DLL_EN (1 << 16)
+#define CORE_CDR_EN (1 << 17)
+#define CORE_CK_OUT_EN (1 << 18)
+#define CORE_CDR_EXT_EN (1 << 19)
+#define CORE_DLL_PDN (1 << 29)
+#define CORE_DLL_RST (1 << 30)
#define CORE_DLL_CONFIG 0x100
+#define CORE_DLL_TEST_CTL 0x104
#define CORE_DLL_STATUS 0x108
-#define CORE_VENDOR_SPEC 0x10c
-#define CORE_CLK_PWRSAVE BIT(1)
+#define CORE_VENDOR_SPEC 0x10C
+#define CORE_CLK_PWRSAVE (1 << 1)
+#define CORE_IO_PAD_PWR_SWITCH (1 << 16)
-#define CORE_VENDOR_SPEC_CAPABILITIES0 0x11c
+/* 8KB descriptors */
+#define SDHCI_MSM_MAX_SEGMENTS (1 << 13)
+#define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */
-#define CDR_SELEXT_SHIFT 20
-#define CDR_SELEXT_MASK (0xf << CDR_SELEXT_SHIFT)
-#define CMUX_SHIFT_PHASE_SHIFT 24
-#define CMUX_SHIFT_PHASE_MASK (7 << CMUX_SHIFT_PHASE_SHIFT)
-
-struct sdhci_msm_host {
- struct platform_device *pdev;
- void __iomem *core_mem; /* MSM SDCC mapped address */
- int pwr_irq; /* power irq */
- struct clk *clk; /* main SD/MMC bus clock */
- struct clk *pclk; /* SDHC peripheral bus clock */
- struct clk *bus_clk; /* SDHC bus voter clock */
- struct mmc_host *mmc;
+static const u32 tuning_block_64[] = {
+ 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE,
+ 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777,
+ 0xF0FFF0FF, 0x3CCCFC0F, 0xCFCC33CC, 0xEEFFEFFF,
+ 0xFDFFFDFF, 0xFFBFFFDF, 0xFFF7FFBB, 0xDE7B7FF7
};
-/* Platform specific tuning */
-static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host, u8 poll)
+static const u32 tuning_block_128[] = {
+ 0xFF00FFFF, 0x0000FFFF, 0xCCCCFFFF, 0xCCCC33CC,
+ 0xCC3333CC, 0xFFFFCCCC, 0xFFFFEEFF, 0xFFEEEEFF,
+ 0xFFDDFFFF, 0xDDDDFFFF, 0xBBFFFFFF, 0xBBFFFFFF,
+ 0xFFFFFFBB, 0xFFFFFF77, 0x77FF7777, 0xFFEEDDBB,
+ 0x00FFFFFF, 0x00FFFFFF, 0xCCFFFF00, 0xCC33CCCC,
+ 0x3333CCCC, 0xFFCCCCCC, 0xFFEEFFFF, 0xEEEEFFFF,
+ 0xDDFFFFFF, 0xDDFFFFFF, 0xFFFFFFDD, 0xFFFFFFBB,
+ 0xFFFFBBBB, 0xFFFF77FF, 0xFF7777FF, 0xEEDDBB77
+};
+
+/* This structure keeps information per regulator */
+struct sdhci_msm_reg_data {
+ /* voltage regulator handle */
+ struct regulator *reg;
+ /* regulator name */
+ const char *name;
+ /* voltage level to be set */
+ u32 low_vol_level;
+ u32 high_vol_level;
+ /* Load values for low power and high power mode */
+ u32 lpm_uA;
+ u32 hpm_uA;
+
+ /* is this regulator enabled? */
+ bool is_enabled;
+ /* is this regulator needs to be always on? */
+ bool is_always_on;
+ /* is low power mode setting required for this regulator? */
+ bool lpm_sup;
+ bool set_voltage_sup;
+};
+
+/*
+ * This structure keeps information for all the
+ * regulators required for a SDCC slot.
+ */
+struct sdhci_msm_slot_reg_data {
+ /* keeps VDD/VCC regulator info */
+ struct sdhci_msm_reg_data *vdd_data;
+ /* keeps VDD IO regulator info */
+ struct sdhci_msm_reg_data *vdd_io_data;
+};
+
+struct sdhci_msm_gpio {
+ u32 no;
+ const char *name;
+ bool is_enabled;
+};
+
+struct sdhci_msm_gpio_data {
+ struct sdhci_msm_gpio *gpio;
+ u8 size;
+};
+
+struct sdhci_msm_pin_data {
+ /*
+ * = 1 if controller pins are using gpios
+ * = 0 if controller has dedicated MSM pads
+ */
+ bool cfg_sts;
+ struct sdhci_msm_gpio_data *gpio_data;
+};
+
+struct sdhci_msm_bus_voting_data {
+ struct msm_bus_scale_pdata *bus_pdata;
+ unsigned int *bw_vecs;
+ unsigned int bw_vecs_size;
+};
+
+struct sdhci_msm_pltfm_data {
+ /* Supported UHS-I Modes */
+ u32 caps;
+
+ /* More capabilities */
+ u32 caps2;
+
+ unsigned long mmc_bus_width;
+ u32 max_clk;
+ struct sdhci_msm_slot_reg_data *vreg_data;
+ bool nonremovable;
+ struct sdhci_msm_pin_data *pin_data;
+ u32 cpu_dma_latency_us;
+ struct sdhci_msm_bus_voting_data *voting_data;
+};
+
+struct sdhci_msm_bus_vote {
+ uint32_t client_handle;
+ uint32_t curr_vote;
+ int min_bw_vote;
+ int max_bw_vote;
+ bool is_max_bw_needed;
+ struct delayed_work vote_work;
+ struct device_attribute max_bus_bw;
+};
+
+struct sdhci_msm_host {
+ struct platform_device *pdev;
+ void __iomem *core_mem; /* MSM SDCC mapped address */
+ struct clk *clk; /* main SD/MMC bus clock */
+ struct clk *pclk; /* SDHC peripheral bus clock */
+ struct clk *bus_clk; /* SDHC bus voter clock */
+ atomic_t clks_on; /* Set if clocks are enabled */
+ struct sdhci_msm_pltfm_data *pdata;
+ struct mmc_host *mmc;
+ struct sdhci_pltfm_data sdhci_msm_pdata;
+ wait_queue_head_t pwr_irq_wait;
+ struct sdhci_msm_bus_vote msm_bus_vote;
+};
+
+enum vdd_io_level {
+ /* set vdd_io_data->low_vol_level */
+ VDD_IO_LOW,
+ /* set vdd_io_data->high_vol_level */
+ VDD_IO_HIGH,
+ /*
+ * set whatever there in voltage_level (third argument) of
+ * sdhci_msm_set_vdd_io_vol() function.
+ */
+ VDD_IO_SET_LEVEL,
+};
+
+/* MSM platform specific tuning */
+static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host,
+ u8 poll)
{
+ int rc = 0;
u32 wait_cnt = 50;
- u8 ck_out_en;
+ u8 ck_out_en = 0;
struct mmc_host *mmc = host->mmc;
- /* Poll for CK_OUT_EN bit. max. poll time = 50us */
+ /* poll for CK_OUT_EN bit. max. poll time = 50us */
ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) &
CORE_CK_OUT_EN);
while (ck_out_en != poll) {
if (--wait_cnt == 0) {
- dev_err(mmc_dev(mmc), "%s: CK_OUT_EN bit is not %d\n",
- mmc_hostname(mmc), poll);
- return -ETIMEDOUT;
+ pr_err("%s: %s: CK_OUT_EN bit is not %d\n",
+ mmc_hostname(mmc), __func__, poll);
+ rc = -ETIMEDOUT;
+ goto out;
}
udelay(1);
- ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) &
- CORE_CK_OUT_EN);
+ ck_out_en = !!(readl_relaxed(host->ioaddr +
+ CORE_DLL_CONFIG) & CORE_CK_OUT_EN);
}
-
- return 0;
+out:
+ return rc;
}
static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase)
{
- int rc;
- static const u8 grey_coded_phase_table[] = {
- 0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4,
- 0xc, 0xd, 0xf, 0xe, 0xa, 0xb, 0x9, 0x8
- };
+ int rc = 0;
+ u8 grey_coded_phase_table[] = {0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4,
+ 0xC, 0xD, 0xF, 0xE, 0xA, 0xB, 0x9,
+ 0x8};
unsigned long flags;
u32 config;
struct mmc_host *mmc = host->mmc;
+ pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__);
spin_lock_irqsave(&host->lock, flags);
config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
@@ -131,10 +265,10 @@ static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase)
* Write the selected DLL clock output phase (0 ... 15)
* to CDR_SELEXT bit field of DLL_CONFIG register.
*/
- config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
- config &= ~CDR_SELEXT_MASK;
- config |= grey_coded_phase_table[phase] << CDR_SELEXT_SHIFT;
- writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
+ writel_relaxed(((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
+ & ~(0xF << 20))
+ | (grey_coded_phase_table[phase] << 20)),
+ host->ioaddr + CORE_DLL_CONFIG);
/* Set CK_OUT_EN bit of DLL_CONFIG register to 1. */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
@@ -152,10 +286,11 @@ static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase)
goto out;
err_out:
- dev_err(mmc_dev(mmc), "%s: Failed to set DLL phase: %d\n",
- mmc_hostname(mmc), phase);
+ pr_err("%s: %s: Failed to set DLL phase: %d\n",
+ mmc_hostname(mmc), __func__, phase);
out:
spin_unlock_irqrestore(&host->lock, flags);
+ pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__);
return rc;
}
@@ -170,19 +305,20 @@ static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase)
*/
static int msm_find_most_appropriate_phase(struct sdhci_host *host,
- u8 *phase_table, u8 total_phases)
+ u8 *phase_table, u8 total_phases)
{
int ret;
u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} };
- u8 phases_per_row[MAX_PHASES] = { 0 };
+ u8 phases_per_row[MAX_PHASES] = {0};
int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0;
int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0;
bool phase_0_found = false, phase_15_found = false;
struct mmc_host *mmc = host->mmc;
+ pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__);
if (!total_phases || (total_phases > MAX_PHASES)) {
- dev_err(mmc_dev(mmc), "%s: Invalid argument: total_phases=%d\n",
- mmc_hostname(mmc), total_phases);
+ pr_err("%s: %s: invalid argument: total_phases=%d\n",
+ mmc_hostname(mmc), __func__, total_phases);
return -EINVAL;
}
@@ -240,7 +376,7 @@ static int msm_find_most_appropriate_phase(struct sdhci_host *host,
i = phases_15;
for (cnt = 0; cnt < phases_0; cnt++) {
ranges[phase_15_raw_index][i] =
- ranges[phase_0_raw_index][cnt];
+ ranges[phase_0_raw_index][cnt];
if (++i >= MAX_PHASES)
break;
}
@@ -256,24 +392,25 @@ static int msm_find_most_appropriate_phase(struct sdhci_host *host,
}
}
- i = (curr_max * 3) / 4;
+ i = ((curr_max * 3) / 4);
if (i)
i--;
- ret = ranges[selected_row_index][i];
+ ret = (int)ranges[selected_row_index][i];
if (ret >= MAX_PHASES) {
ret = -EINVAL;
- dev_err(mmc_dev(mmc), "%s: Invalid phase selected=%d\n",
- mmc_hostname(mmc), ret);
+ pr_err("%s: %s: invalid phase selected=%d\n",
+ mmc_hostname(mmc), __func__, ret);
}
+ pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__);
return ret;
}
static inline void msm_cm_dll_set_freq(struct sdhci_host *host)
{
- u32 mclk_freq = 0, config;
+ u32 mclk_freq = 0;
/* Program the MCLK value to MCLK_FREQ bit field */
if (host->clock <= 112000000)
@@ -293,28 +430,31 @@ static inline void msm_cm_dll_set_freq(struct sdhci_host *host)
else if (host->clock <= 200000000)
mclk_freq = 7;
- config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG);
- config &= ~CMUX_SHIFT_PHASE_MASK;
- config |= mclk_freq << CMUX_SHIFT_PHASE_SHIFT;
- writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG);
+ writel_relaxed(((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
+ & ~(7 << 24)) | (mclk_freq << 24)),
+ host->ioaddr + CORE_DLL_CONFIG);
}
-/* Initialize the DLL (Programmable Delay Line) */
+/* Initialize the DLL (Programmable Delay Line ) */
static int msm_init_cm_dll(struct sdhci_host *host)
{
struct mmc_host *mmc = host->mmc;
- int wait_cnt = 50;
+ int rc = 0;
unsigned long flags;
+ u32 wait_cnt;
+ pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__);
spin_lock_irqsave(&host->lock, flags);
/*
* Make sure that clock is always enabled when DLL
* tuning is in progress. Keeping PWRSAVE ON may
- * turn off the clock.
+ * turn off the clock. So let's disable the PWRSAVE
+ * here and re-enable it once tuning is completed.
*/
writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC)
- & ~CORE_CLK_PWRSAVE), host->ioaddr + CORE_VENDOR_SPEC);
+ & ~CORE_CLK_PWRSAVE),
+ host->ioaddr + CORE_VENDOR_SPEC);
/* Write 1 to DLL_RST bit of DLL_CONFIG register */
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
@@ -341,69 +481,107 @@ static int msm_init_cm_dll(struct sdhci_host *host)
writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG)
| CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG);
+ wait_cnt = 50;
/* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */
while (!(readl_relaxed(host->ioaddr + CORE_DLL_STATUS) &
- CORE_DLL_LOCK)) {
+ CORE_DLL_LOCK)) {
/* max. wait for 50us sec for LOCK bit to be set */
if (--wait_cnt == 0) {
- dev_err(mmc_dev(mmc), "%s: DLL failed to LOCK\n",
- mmc_hostname(mmc));
- spin_unlock_irqrestore(&host->lock, flags);
- return -ETIMEDOUT;
+ pr_err("%s: %s: DLL failed to LOCK\n",
+ mmc_hostname(mmc), __func__);
+ rc = -ETIMEDOUT;
+ goto out;
}
+ /* wait for 1us before polling again */
udelay(1);
}
+out:
+ /* re-enable PWRSAVE */
+ writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) |
+ CORE_CLK_PWRSAVE),
+ host->ioaddr + CORE_VENDOR_SPEC);
spin_unlock_irqrestore(&host->lock, flags);
- return 0;
+ pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__);
+ return rc;
}
-static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode)
+int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode)
{
- int tuning_seq_cnt = 3;
- u8 phase, tuned_phases[16], tuned_phase_cnt = 0;
+ unsigned long flags;
+ u8 phase, *data_buf, tuned_phases[16], tuned_phase_cnt = 0;
+ const u32 *tuning_block_pattern = tuning_block_64;
+ int size = sizeof(tuning_block_64); /* Tuning pattern size in bytes */
int rc;
struct mmc_host *mmc = host->mmc;
- struct mmc_ios ios = host->mmc->ios;
- /*
- * Tuning is required for SDR104, HS200 and HS400 cards and
- * if clock frequency is greater than 100MHz in these modes.
- */
- if (host->clock <= 100 * 1000 * 1000 ||
- !((ios.timing == MMC_TIMING_MMC_HS200) ||
- (ios.timing == MMC_TIMING_UHS_SDR104)))
- return 0;
+ pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__);
+ /* Tuning is only required for SDR104 modes */
+ spin_lock_irqsave(&host->lock, flags);
-retry:
- /* First of all reset the tuning block */
+ if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) &&
+ (mmc->ios.bus_width == MMC_BUS_WIDTH_8)) {
+ tuning_block_pattern = tuning_block_128;
+ size = sizeof(tuning_block_128);
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ /* first of all reset the tuning block */
rc = msm_init_cm_dll(host);
if (rc)
- return rc;
+ goto out;
+
+ data_buf = kmalloc(size, GFP_KERNEL);
+ if (!data_buf) {
+ rc = -ENOMEM;
+ goto out;
+ }
phase = 0;
do {
- /* Set the phase in delay line hw block */
+ struct mmc_command cmd = {0};
+ struct mmc_data data = {0};
+ struct mmc_request mrq = {
+ .cmd = &cmd,
+ .data = &data
+ };
+ struct scatterlist sg;
+
+ /* set the phase in delay line hw block */
rc = msm_config_cm_dll_phase(host, phase);
if (rc)
- return rc;
+ goto kfree;
- rc = mmc_send_tuning(mmc, opcode, NULL);
- if (!rc) {
- /* Tuning is successful at this tuning point */
+ cmd.opcode = opcode;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ data.blksz = size;
+ data.blocks = 1;
+ data.flags = MMC_DATA_READ;
+ data.timeout_ns = 1000 * 1000 * 1000; /* 1 sec */
+
+ data.sg = &sg;
+ data.sg_len = 1;
+ sg_init_one(&sg, data_buf, size);
+ memset(data_buf, 0, size);
+ mmc_wait_for_req(mmc, &mrq);
+
+ if (!cmd.error && !data.error &&
+ !memcmp(data_buf, tuning_block_pattern, size)) {
+ /* tuning is successful at this tuning point */
tuned_phases[tuned_phase_cnt++] = phase;
- dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n",
- mmc_hostname(mmc), phase);
+ pr_debug("%s: %s: found good phase = %d\n",
+ mmc_hostname(mmc), __func__, phase);
}
- } while (++phase < ARRAY_SIZE(tuned_phases));
+ } while (++phase < 16);
if (tuned_phase_cnt) {
rc = msm_find_most_appropriate_phase(host, tuned_phases,
- tuned_phase_cnt);
+ tuned_phase_cnt);
if (rc < 0)
- return rc;
+ goto kfree;
else
- phase = rc;
+ phase = (u8)rc;
/*
* Finally set the selected phase in delay
@@ -411,121 +589,1023 @@ static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode)
*/
rc = msm_config_cm_dll_phase(host, phase);
if (rc)
- return rc;
- dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n",
- mmc_hostname(mmc), phase);
+ goto kfree;
+ pr_debug("%s: %s: finally setting the tuning phase to %d\n",
+ mmc_hostname(mmc), __func__, phase);
} else {
- if (--tuning_seq_cnt)
- goto retry;
- /* Tuning failed */
- dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n",
- mmc_hostname(mmc));
- rc = -EIO;
+ /* tuning failed */
+ pr_err("%s: %s: no tuning point found\n",
+ mmc_hostname(mmc), __func__);
+ rc = -EAGAIN;
}
+kfree:
+ kfree(data_buf);
+out:
+ pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__);
return rc;
}
-static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host,
- unsigned int uhs)
+static int sdhci_msm_setup_gpio(struct sdhci_msm_pltfm_data *pdata, bool enable)
{
- struct mmc_host *mmc = host->mmc;
- u16 ctrl_2;
+ struct sdhci_msm_gpio_data *curr;
+ int i, ret = 0;
- ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2);
- /* Select Bus Speed Mode for host */
- ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
- switch (uhs) {
- case MMC_TIMING_UHS_SDR12:
- ctrl_2 |= SDHCI_CTRL_UHS_SDR12;
- break;
- case MMC_TIMING_UHS_SDR25:
- ctrl_2 |= SDHCI_CTRL_UHS_SDR25;
- break;
- case MMC_TIMING_UHS_SDR50:
- ctrl_2 |= SDHCI_CTRL_UHS_SDR50;
- break;
- case MMC_TIMING_MMC_HS200:
- case MMC_TIMING_UHS_SDR104:
- ctrl_2 |= SDHCI_CTRL_UHS_SDR104;
- break;
- case MMC_TIMING_UHS_DDR50:
- case MMC_TIMING_MMC_DDR52:
- ctrl_2 |= SDHCI_CTRL_UHS_DDR50;
- break;
+ curr = pdata->pin_data->gpio_data;
+ for (i = 0; i < curr->size; i++) {
+ if (!gpio_is_valid(curr->gpio[i].no)) {
+ ret = -EINVAL;
+ pr_err("%s: Invalid gpio = %d\n", __func__,
+ curr->gpio[i].no);
+ goto free_gpios;
+ }
+ if (enable) {
+ ret = gpio_request(curr->gpio[i].no,
+ curr->gpio[i].name);
+ if (ret) {
+ pr_err("%s: gpio_request(%d, %s) failed %d\n",
+ __func__, curr->gpio[i].no,
+ curr->gpio[i].name, ret);
+ goto free_gpios;
+ }
+ curr->gpio[i].is_enabled = true;
+ } else {
+ gpio_free(curr->gpio[i].no);
+ curr->gpio[i].is_enabled = false;
+ }
}
+ return ret;
- /*
- * When clock frequency is less than 100MHz, the feedback clock must be
- * provided and DLL must not be used so that tuning can be skipped. To
- * provide feedback clock, the mode selection can be any value less
- * than 3'b011 in bits [2:0] of HOST CONTROL2 register.
- */
- if (host->clock <= 100000000 &&
- (uhs == MMC_TIMING_MMC_HS400 ||
- uhs == MMC_TIMING_MMC_HS200 ||
- uhs == MMC_TIMING_UHS_SDR104))
- ctrl_2 &= ~SDHCI_CTRL_UHS_MASK;
-
- dev_dbg(mmc_dev(mmc), "%s: clock=%u uhs=%u ctrl_2=0x%x\n",
- mmc_hostname(host->mmc), host->clock, uhs, ctrl_2);
- sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+free_gpios:
+ for (i--; i >= 0; i--) {
+ gpio_free(curr->gpio[i].no);
+ curr->gpio[i].is_enabled = false;
+ }
+ return ret;
}
-static void sdhci_msm_voltage_switch(struct sdhci_host *host)
+static int sdhci_msm_setup_pins(struct sdhci_msm_pltfm_data *pdata, bool enable)
+{
+ int ret = 0;
+
+ if (!pdata->pin_data || (pdata->pin_data->cfg_sts == enable))
+ return 0;
+
+ ret = sdhci_msm_setup_gpio(pdata, enable);
+ if (!ret)
+ pdata->pin_data->cfg_sts = enable;
+
+ return ret;
+}
+
+static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name,
+ u32 **out, int *len, u32 size)
+{
+ int ret = 0;
+ struct device_node *np = dev->of_node;
+ size_t sz;
+ u32 *arr = NULL;
+
+ if (!of_get_property(np, prop_name, len)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ sz = *len = *len / sizeof(*arr);
+ if (sz <= 0 || (size > 0 && (sz != size))) {
+ dev_err(dev, "%s invalid size\n", prop_name);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ arr = devm_kzalloc(dev, sz * sizeof(*arr), GFP_KERNEL);
+ if (!arr) {
+ dev_err(dev, "%s failed allocating memory\n", prop_name);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = of_property_read_u32_array(np, prop_name, arr, sz);
+ if (ret < 0) {
+ dev_err(dev, "%s failed reading array %d\n", prop_name, ret);
+ goto out;
+ }
+ *out = arr;
+out:
+ if (ret)
+ *len = 0;
+ return ret;
+}
+
+#define MAX_PROP_SIZE 32
+static int sdhci_msm_dt_parse_vreg_info(struct device *dev,
+ struct sdhci_msm_reg_data **vreg_data, const char *vreg_name)
+{
+ int len, ret = 0;
+ const __be32 *prop;
+ char prop_name[MAX_PROP_SIZE];
+ struct sdhci_msm_reg_data *vreg;
+ struct device_node *np = dev->of_node;
+
+ snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", vreg_name);
+ if (!of_parse_phandle(np, prop_name, 0)) {
+ dev_err(dev, "No vreg data found for %s\n", vreg_name);
+ ret = -EINVAL;
+ return ret;
+ }
+
+ vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
+ if (!vreg) {
+ dev_err(dev, "No memory for vreg: %s\n", vreg_name);
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ vreg->name = vreg_name;
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,%s-always-on", vreg_name);
+ if (of_get_property(np, prop_name, NULL))
+ vreg->is_always_on = true;
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,%s-lpm-sup", vreg_name);
+ if (of_get_property(np, prop_name, NULL))
+ vreg->lpm_sup = true;
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,%s-voltage-level", vreg_name);
+ prop = of_get_property(np, prop_name, &len);
+ if (!prop || (len != (2 * sizeof(__be32)))) {
+ dev_warn(dev, "%s %s property\n",
+ prop ? "invalid format" : "no", prop_name);
+ } else {
+ vreg->low_vol_level = be32_to_cpup(&prop[0]);
+ vreg->high_vol_level = be32_to_cpup(&prop[1]);
+ }
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,%s-current-level", vreg_name);
+ prop = of_get_property(np, prop_name, &len);
+ if (!prop || (len != (2 * sizeof(__be32)))) {
+ dev_warn(dev, "%s %s property\n",
+ prop ? "invalid format" : "no", prop_name);
+ } else {
+ vreg->lpm_uA = be32_to_cpup(&prop[0]);
+ vreg->hpm_uA = be32_to_cpup(&prop[1]);
+ }
+
+ *vreg_data = vreg;
+ dev_dbg(dev, "%s: %s %s vol=[%d %d]uV, curr=[%d %d]uA\n",
+ vreg->name, vreg->is_always_on ? "always_on," : "",
+ vreg->lpm_sup ? "lpm_sup," : "", vreg->low_vol_level,
+ vreg->high_vol_level, vreg->lpm_uA, vreg->hpm_uA);
+
+ return ret;
+}
+
+#define GPIO_NAME_MAX_LEN 32
+static int sdhci_msm_dt_parse_gpio_info(struct device *dev,
+ struct sdhci_msm_pltfm_data *pdata)
+{
+ int ret = 0, cnt, i;
+ struct sdhci_msm_pin_data *pin_data;
+ struct device_node *np = dev->of_node;
+
+ pin_data = devm_kzalloc(dev, sizeof(*pin_data), GFP_KERNEL);
+ if (!pin_data) {
+ dev_err(dev, "No memory for pin_data\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ cnt = of_gpio_count(np);
+ if (cnt > 0) {
+ pin_data->gpio_data = devm_kzalloc(dev,
+ sizeof(struct sdhci_msm_gpio_data), GFP_KERNEL);
+ if (!pin_data->gpio_data) {
+ dev_err(dev, "No memory for gpio_data\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+ pin_data->gpio_data->size = cnt;
+ pin_data->gpio_data->gpio = devm_kzalloc(dev, cnt *
+ sizeof(struct sdhci_msm_gpio), GFP_KERNEL);
+
+ if (!pin_data->gpio_data->gpio) {
+ dev_err(dev, "No memory for gpio\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (i = 0; i < cnt; i++) {
+ const char *name = NULL;
+ char result[GPIO_NAME_MAX_LEN];
+ pin_data->gpio_data->gpio[i].no = of_get_gpio(np, i);
+ of_property_read_string_index(np,
+ "qcom,gpio-names", i, &name);
+
+ snprintf(result, GPIO_NAME_MAX_LEN, "%s-%s",
+ dev_name(dev), name ? name : "?");
+ pin_data->gpio_data->gpio[i].name = result;
+ dev_dbg(dev, "%s: gpio[%s] = %d\n", __func__,
+ pin_data->gpio_data->gpio[i].name,
+ pin_data->gpio_data->gpio[i].no);
+ pdata->pin_data = pin_data;
+ }
+ }
+
+out:
+ if (ret)
+ dev_err(dev, "%s failed with err %d\n", __func__, ret);
+ return ret;
+}
+
+/* Parse platform data */
+static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev)
+{
+ struct sdhci_msm_pltfm_data *pdata = NULL;
+ struct device_node *np = dev->of_node;
+ u32 bus_width = 0;
+ u32 cpu_dma_latency;
+ int len, i;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(dev, "failed to allocate memory for platform data\n");
+ goto out;
+ }
+
+ of_property_read_u32(np, "qcom,bus-width", &bus_width);
+ if (bus_width == 8)
+ pdata->mmc_bus_width = MMC_CAP_8_BIT_DATA;
+ else if (bus_width == 4)
+ pdata->mmc_bus_width = MMC_CAP_4_BIT_DATA;
+ else {
+ dev_notice(dev, "invalid bus-width, default to 1-bit mode\n");
+ pdata->mmc_bus_width = 0;
+ }
+
+ if (!of_property_read_u32(np, "qcom,cpu-dma-latency-us",
+ &cpu_dma_latency))
+ pdata->cpu_dma_latency_us = cpu_dma_latency;
+
+ pdata->vreg_data = devm_kzalloc(dev, sizeof(struct
+ sdhci_msm_slot_reg_data),
+ GFP_KERNEL);
+ if (!pdata->vreg_data) {
+ dev_err(dev, "failed to allocate memory for vreg data\n");
+ goto out;
+ }
+
+ if (sdhci_msm_dt_parse_vreg_info(dev, &pdata->vreg_data->vdd_data,
+ "vdd")) {
+ dev_err(dev, "failed parsing vdd data\n");
+ goto out;
+ }
+ if (sdhci_msm_dt_parse_vreg_info(dev,
+ &pdata->vreg_data->vdd_io_data,
+ "vdd-io")) {
+ dev_err(dev, "failed parsing vdd-io data\n");
+ goto out;
+ }
+
+ if (sdhci_msm_dt_parse_gpio_info(dev, pdata)) {
+ dev_err(dev, "failed parsing gpio data\n");
+ goto out;
+ }
+
+ of_property_read_u32(np, "qcom,max-clk-rate", &pdata->max_clk);
+
+ len = of_property_count_strings(np, "qcom,bus-speed-mode");
+
+ for (i = 0; i < len; i++) {
+ const char *name = NULL;
+
+ of_property_read_string_index(np,
+ "qcom,bus-speed-mode", i, &name);
+ if (!name)
+ continue;
+
+ if (!strncmp(name, "HS200_1p8v", sizeof("HS200_1p8v")))
+ pdata->caps2 |= MMC_CAP2_HS200_1_8V_SDR;
+ else if (!strncmp(name, "HS200_1p2v", sizeof("HS200_1p2v")))
+ pdata->caps2 |= MMC_CAP2_HS200_1_2V_SDR;
+ else if (!strncmp(name, "DDR_1p8v", sizeof("DDR_1p8v")))
+ pdata->caps |= MMC_CAP_1_8V_DDR
+ | MMC_CAP_UHS_DDR50;
+ else if (!strncmp(name, "DDR_1p2v", sizeof("DDR_1p2v")))
+ pdata->caps |= MMC_CAP_1_2V_DDR
+ | MMC_CAP_UHS_DDR50;
+ }
+
+ if (of_get_property(np, "qcom,nonremovable", NULL))
+ pdata->nonremovable = true;
+
+ return pdata;
+out:
+ return NULL;
+}
+
+/* Returns required bandwidth in Bytes per Sec */
+static unsigned int sdhci_get_bw_required(struct sdhci_host *host,
+ struct mmc_ios *ios)
+{
+ unsigned int bw;
+
+ bw = host->clock;
+ /*
+ * For DDR mode, SDCC controller clock will be at
+ * the double rate than the actual clock that goes to card.
+ */
+ if (ios->bus_width == MMC_BUS_WIDTH_4)
+ bw /= 2;
+ else if (ios->bus_width == MMC_BUS_WIDTH_1)
+ bw /= 8;
+
+ return bw;
+}
+
+static int sdhci_msm_bus_get_vote_for_bw(struct sdhci_msm_host *host,
+ unsigned int bw)
+{
+ unsigned int *table = host->pdata->voting_data->bw_vecs;
+ unsigned int size = host->pdata->voting_data->bw_vecs_size;
+ int i;
+
+ if (host->msm_bus_vote.is_max_bw_needed && bw)
+ return host->msm_bus_vote.max_bw_vote;
+
+ for (i = 0; i < size; i++) {
+ if (bw <= table[i])
+ break;
+ }
+
+ if (i && (i == size))
+ i--;
+
+ return i;
+}
+
+/*
+ * This function must be called with host lock acquired.
+ * Caller of this function should also ensure that msm bus client
+ * handle is not null.
+ */
+static inline int sdhci_msm_bus_set_vote(struct sdhci_msm_host *msm_host,
+ int vote,
+ unsigned long flags)
+{
+ struct sdhci_host *host = platform_get_drvdata(msm_host->pdev);
+ int rc = 0;
+
+ if (vote != msm_host->msm_bus_vote.curr_vote) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ rc = msm_bus_scale_client_update_request(
+ msm_host->msm_bus_vote.client_handle, vote);
+ spin_lock_irqsave(&host->lock, flags);
+ if (rc) {
+ pr_err("%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n",
+ mmc_hostname(host->mmc),
+ msm_host->msm_bus_vote.client_handle, vote, rc);
+ goto out;
+ }
+ msm_host->msm_bus_vote.curr_vote = vote;
+ }
+out:
+ return rc;
+}
+
+/*
+ * Internal work. Work to set 0 bandwidth for msm bus.
+ */
+static void sdhci_msm_bus_work(struct work_struct *work)
+{
+ struct sdhci_msm_host *msm_host;
+ struct sdhci_host *host;
+ unsigned long flags;
+
+ msm_host = container_of(work, struct sdhci_msm_host,
+ msm_bus_vote.vote_work.work);
+ host = platform_get_drvdata(msm_host->pdev);
+
+ if (!msm_host->msm_bus_vote.client_handle)
+ return;
+
+ spin_lock_irqsave(&host->lock, flags);
+ /* don't vote for 0 bandwidth if any request is in progress */
+ if (!host->mrq) {
+ sdhci_msm_bus_set_vote(msm_host,
+ msm_host->msm_bus_vote.min_bw_vote, flags);
+ } else
+ pr_warning("%s: %s: Transfer in progress. skipping bus voting to 0 bandwidth\n",
+ mmc_hostname(host->mmc), __func__);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+/*
+ * This function cancels any scheduled delayed work and sets the bus
+ * vote based on bw (bandwidth) argument.
+ */
+static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host,
+ unsigned int bw)
+{
+ int vote;
+ unsigned long flags;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
+
+ cancel_delayed_work_sync(&msm_host->msm_bus_vote.vote_work);
+ spin_lock_irqsave(&host->lock, flags);
+ vote = sdhci_msm_bus_get_vote_for_bw(msm_host, bw);
+ sdhci_msm_bus_set_vote(msm_host, vote, flags);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+#define MSM_MMC_BUS_VOTING_DELAY 200 /* msecs */
+
+/* This function queues a work which will set the bandwidth requiement to 0 */
+static void sdhci_msm_bus_queue_work(struct sdhci_host *host)
+{
+ unsigned long flags;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (msm_host->msm_bus_vote.min_bw_vote !=
+ msm_host->msm_bus_vote.curr_vote)
+ queue_delayed_work(system_wq,
+ &msm_host->msm_bus_vote.vote_work,
+ msecs_to_jiffies(MSM_MMC_BUS_VOTING_DELAY));
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static int sdhci_msm_bus_register(struct sdhci_msm_host *host,
+ struct platform_device *pdev)
+{
+ int rc = 0;
+ struct msm_bus_scale_pdata *bus_pdata;
+
+ struct sdhci_msm_bus_voting_data *data;
+ struct device *dev = &pdev->dev;
+
+ data = devm_kzalloc(dev,
+ sizeof(struct sdhci_msm_bus_voting_data), GFP_KERNEL);
+ if (!data) {
+ dev_err(&pdev->dev,
+ "%s: failed to allocate memory\n", __func__);
+ rc = -ENOMEM;
+ goto out;
+ }
+ data->bus_pdata = msm_bus_cl_get_pdata(pdev);
+ if (data->bus_pdata) {
+ rc = sdhci_msm_dt_get_array(dev, "qcom,bus-bw-vectors-bps",
+ &data->bw_vecs, &data->bw_vecs_size, 0);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "%s: Failed to get bus-bw-vectors-bps\n",
+ __func__);
+ goto out;
+ }
+ host->pdata->voting_data = data;
+ }
+ if (host->pdata->voting_data &&
+ host->pdata->voting_data->bus_pdata &&
+ host->pdata->voting_data->bw_vecs &&
+ host->pdata->voting_data->bw_vecs_size) {
+
+ bus_pdata = host->pdata->voting_data->bus_pdata;
+ host->msm_bus_vote.client_handle =
+ msm_bus_scale_register_client(bus_pdata);
+ if (!host->msm_bus_vote.client_handle) {
+ dev_err(&pdev->dev, "msm_bus_scale_register_client()\n");
+ rc = -EFAULT;
+ goto out;
+ }
+ /* cache the vote index for minimum and maximum bandwidth */
+ host->msm_bus_vote.min_bw_vote =
+ sdhci_msm_bus_get_vote_for_bw(host, 0);
+ host->msm_bus_vote.max_bw_vote =
+ sdhci_msm_bus_get_vote_for_bw(host, UINT_MAX);
+ } else {
+ devm_kfree(dev, data);
+ }
+
+out:
+ return rc;
+}
+
+static void sdhci_msm_bus_unregister(struct sdhci_msm_host *host)
+{
+ if (host->msm_bus_vote.client_handle)
+ msm_bus_scale_unregister_client(
+ host->msm_bus_vote.client_handle);
+}
+
+static void sdhci_msm_bus_voting(struct sdhci_host *host, u32 enable)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
- struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
- u32 irq_status, irq_ack = 0;
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
+ struct mmc_ios *ios = &host->mmc->ios;
+ unsigned int bw;
- irq_status = readl_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS);
- irq_status &= INT_MASK;
+ if (!msm_host->msm_bus_vote.client_handle)
+ return;
- writel_relaxed(irq_status, msm_host->core_mem + CORE_PWRCTL_CLEAR);
+ bw = sdhci_get_bw_required(host, ios);
+ if (enable)
+ sdhci_msm_bus_cancel_work_and_set_vote(host, bw);
+ else
+ sdhci_msm_bus_queue_work(host);
+}
- if (irq_status & (CORE_PWRCTL_BUS_ON | CORE_PWRCTL_BUS_OFF))
- irq_ack |= CORE_PWRCTL_BUS_SUCCESS;
- if (irq_status & (CORE_PWRCTL_IO_LOW | CORE_PWRCTL_IO_HIGH))
- irq_ack |= CORE_PWRCTL_IO_SUCCESS;
+/* Regulator utility functions */
+static int sdhci_msm_vreg_init_reg(struct device *dev,
+ struct sdhci_msm_reg_data *vreg)
+{
+ int ret = 0;
+
+ /* check if regulator is already initialized? */
+ if (vreg->reg)
+ goto out;
+
+ /* Get the regulator handle */
+ vreg->reg = devm_regulator_get(dev, vreg->name);
+ if (IS_ERR(vreg->reg)) {
+ ret = PTR_ERR(vreg->reg);
+ pr_err("%s: devm_regulator_get(%s) failed. ret=%d\n",
+ __func__, vreg->name, ret);
+ goto out;
+ }
+
+ /* sanity check */
+ if (!vreg->high_vol_level || !vreg->hpm_uA) {
+ pr_err("%s: %s invalid constraints specified\n",
+ __func__, vreg->name);
+ ret = -EINVAL;
+ }
+
+out:
+ return ret;
+}
+
+static void sdhci_msm_vreg_deinit_reg(struct sdhci_msm_reg_data *vreg)
+{
+ if (vreg->reg)
+ devm_regulator_put(vreg->reg);
+}
+
+static int sdhci_msm_vreg_set_optimum_mode(struct sdhci_msm_reg_data
+ *vreg, int uA_load)
+{
+ int ret = 0;
+
+ /*
+ * regulators that do not support regulator_set_voltage also
+ * do not support regulator_set_optimum_mode
+ */
+ if (vreg->set_voltage_sup) {
+ ret = regulator_set_load(vreg->reg, uA_load);
+ if (ret < 0)
+ pr_err("%s: regulator_set_load(reg=%s,uA_load=%d) failed. ret=%d\n",
+ __func__, vreg->name, uA_load, ret);
+ else
+ /*
+ * regulator_set_load() can return non zero
+ * value even for success case.
+ */
+ ret = 0;
+ }
+ return ret;
+}
+
+static int sdhci_msm_vreg_set_voltage(struct sdhci_msm_reg_data *vreg,
+ int min_uV, int max_uV)
+{
+ int ret = 0;
+
+ ret = regulator_set_voltage(vreg->reg, min_uV, max_uV);
+ if (ret) {
+ pr_err("%s: regulator_set_voltage(%s)failed. min_uV=%d,max_uV=%d,ret=%d\n",
+ __func__, vreg->name, min_uV, max_uV, ret);
+ }
+
+ return ret;
+}
+
+static int sdhci_msm_vreg_enable(struct sdhci_msm_reg_data *vreg)
+{
+ int ret = 0;
+
+ /* Put regulator in HPM (high power mode) */
+ ret = sdhci_msm_vreg_set_optimum_mode(vreg, vreg->hpm_uA);
+ if (ret < 0)
+ return ret;
+
+ if (!vreg->is_enabled) {
+ /* Set voltage level */
+ ret = sdhci_msm_vreg_set_voltage(vreg, vreg->high_vol_level,
+ vreg->high_vol_level);
+ if (ret)
+ return ret;
+ }
+ ret = regulator_enable(vreg->reg);
+ if (ret) {
+ pr_err("%s: regulator_enable(%s) failed. ret=%d\n",
+ __func__, vreg->name, ret);
+ return ret;
+ }
+ vreg->is_enabled = true;
+ return ret;
+}
+
+static int sdhci_msm_vreg_disable(struct sdhci_msm_reg_data *vreg)
+{
+ int ret = 0;
+
+ /* Never disable regulator marked as always_on */
+ if (vreg->is_enabled && !vreg->is_always_on) {
+ ret = regulator_disable(vreg->reg);
+ if (ret) {
+ pr_err("%s: regulator_disable(%s) failed. ret=%d\n",
+ __func__, vreg->name, ret);
+ goto out;
+ }
+ vreg->is_enabled = false;
+
+ ret = sdhci_msm_vreg_set_optimum_mode(vreg, 0);
+ if (ret < 0)
+ goto out;
+
+ /* Set min. voltage level to 0 */
+ ret = sdhci_msm_vreg_set_voltage(vreg, 0, vreg->high_vol_level);
+ if (ret)
+ goto out;
+ } else if (vreg->is_enabled && vreg->is_always_on) {
+ if (vreg->lpm_sup) {
+ /* Put always_on regulator in LPM (low power mode) */
+ ret = sdhci_msm_vreg_set_optimum_mode(vreg,
+ vreg->lpm_uA);
+ if (ret < 0)
+ goto out;
+ }
+ }
+out:
+ return ret;
+}
+
+static int sdhci_msm_setup_vreg(struct sdhci_msm_pltfm_data *pdata,
+ bool enable, bool is_init)
+{
+ int ret = 0, i;
+ struct sdhci_msm_slot_reg_data *curr_slot;
+ struct sdhci_msm_reg_data *vreg_table[2];
+
+ curr_slot = pdata->vreg_data;
+ if (!curr_slot) {
+ pr_debug("%s: vreg info unavailable,assuming the slot is powered by always on domain\n",
+ __func__);
+ goto out;
+ }
+
+ vreg_table[0] = curr_slot->vdd_data;
+ vreg_table[1] = curr_slot->vdd_io_data;
+
+ for (i = 0; i < ARRAY_SIZE(vreg_table); i++) {
+ if (vreg_table[i]) {
+ if (enable)
+ ret = sdhci_msm_vreg_enable(vreg_table[i]);
+ else
+ ret = sdhci_msm_vreg_disable(vreg_table[i]);
+ if (ret)
+ goto out;
+ }
+ }
+out:
+ return ret;
+}
+
+/*
+ * Reset vreg by ensuring it is off during probe. A call
+ * to enable vreg is needed to balance disable vreg
+ */
+static int sdhci_msm_vreg_reset(struct sdhci_msm_pltfm_data *pdata)
+{
+ int ret;
+
+ ret = sdhci_msm_setup_vreg(pdata, 1, true);
+ if (ret)
+ return ret;
+ ret = sdhci_msm_setup_vreg(pdata, 0, true);
+ return ret;
+}
+
+/* This init function should be called only once for each SDHC slot */
+static int sdhci_msm_vreg_init(struct device *dev,
+ struct sdhci_msm_pltfm_data *pdata,
+ bool is_init)
+{
+ int ret = 0;
+ struct sdhci_msm_slot_reg_data *curr_slot;
+ struct sdhci_msm_reg_data *curr_vdd_reg, *curr_vdd_io_reg;
+
+ curr_slot = pdata->vreg_data;
+ if (!curr_slot)
+ goto out;
+
+ curr_vdd_reg = curr_slot->vdd_data;
+ curr_vdd_io_reg = curr_slot->vdd_io_data;
+
+ if (!is_init)
+ /* Deregister all regulators from regulator framework */
+ goto vdd_io_reg_deinit;
/*
- * The driver has to acknowledge the interrupt, switch voltages and
- * report back if it succeded or not to this register. The voltage
- * switches are handled by the sdhci core, so just report success.
+ * Get the regulator handle from voltage regulator framework
+ * and then try to set the voltage level for the regulator
*/
- writel_relaxed(irq_ack, msm_host->core_mem + CORE_PWRCTL_CTL);
+ if (curr_vdd_reg) {
+ ret = sdhci_msm_vreg_init_reg(dev, curr_vdd_reg);
+ if (ret)
+ goto out;
+ }
+ if (curr_vdd_io_reg) {
+ ret = sdhci_msm_vreg_init_reg(dev, curr_vdd_io_reg);
+ if (ret)
+ goto vdd_reg_deinit;
+ }
+ ret = sdhci_msm_vreg_reset(pdata);
+ if (ret)
+ dev_err(dev, "vreg reset failed (%d)\n", ret);
+ goto out;
+
+vdd_io_reg_deinit:
+ if (curr_vdd_io_reg)
+ sdhci_msm_vreg_deinit_reg(curr_vdd_io_reg);
+vdd_reg_deinit:
+ if (curr_vdd_reg)
+ sdhci_msm_vreg_deinit_reg(curr_vdd_reg);
+out:
+ return ret;
+}
+
+
+static int sdhci_msm_set_vdd_io_vol(struct sdhci_msm_pltfm_data *pdata,
+ enum vdd_io_level level,
+ unsigned int voltage_level)
+{
+ int ret = 0;
+ int set_level;
+ struct sdhci_msm_reg_data *vdd_io_reg;
+
+ if (!pdata->vreg_data)
+ return ret;
+
+ vdd_io_reg = pdata->vreg_data->vdd_io_data;
+ if (vdd_io_reg && vdd_io_reg->is_enabled) {
+ switch (level) {
+ case VDD_IO_LOW:
+ set_level = vdd_io_reg->low_vol_level;
+ break;
+ case VDD_IO_HIGH:
+ set_level = vdd_io_reg->high_vol_level;
+ break;
+ case VDD_IO_SET_LEVEL:
+ set_level = voltage_level;
+ break;
+ default:
+ pr_err("%s: invalid argument level = %d",
+ __func__, level);
+ ret = -EINVAL;
+ return ret;
+ }
+ ret = sdhci_msm_vreg_set_voltage(vdd_io_reg, set_level,
+ set_level);
+ }
+ return ret;
}
static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data)
{
struct sdhci_host *host = (struct sdhci_host *)data;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
+ u8 irq_status = 0;
+ u8 irq_ack = 0;
+ int ret = 0;
- sdhci_msm_voltage_switch(host);
+ irq_status = readb_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS);
+ pr_debug("%s: Received IRQ(%d), status=0x%x\n",
+ mmc_hostname(msm_host->mmc), irq, irq_status);
+ /* Clear the interrupt */
+ writeb_relaxed(irq_status, (msm_host->core_mem + CORE_PWRCTL_CLEAR));
+ /*
+ * SDHC has core_mem and hc_mem device memory and these memory
+ * addresses do not fall within 1KB region. Hence, any update to
+ * core_mem address space would require an mb() to ensure this gets
+ * completed before its next update to registers within hc_mem.
+ */
+ mb();
+
+ /* Handle BUS ON/OFF*/
+ if (irq_status & CORE_PWRCTL_BUS_ON) {
+ ret = sdhci_msm_setup_vreg(msm_host->pdata, true, false);
+ if (!ret)
+ ret = sdhci_msm_setup_pins(msm_host->pdata, true);
+ if (ret)
+ irq_ack |= CORE_PWRCTL_BUS_FAIL;
+ else
+ irq_ack |= CORE_PWRCTL_BUS_SUCCESS;
+ }
+ if (irq_status & CORE_PWRCTL_BUS_OFF) {
+ ret = sdhci_msm_setup_vreg(msm_host->pdata, false, false);
+ if (!ret)
+ ret = sdhci_msm_setup_pins(msm_host->pdata, false);
+ if (ret)
+ irq_ack |= CORE_PWRCTL_BUS_FAIL;
+ else
+ irq_ack |= CORE_PWRCTL_BUS_SUCCESS;
+ }
+ /* Handle IO LOW/HIGH */
+ if (irq_status & CORE_PWRCTL_IO_LOW) {
+ /* Switch voltage Low */
+ ret = sdhci_msm_set_vdd_io_vol(msm_host->pdata, VDD_IO_LOW, 0);
+ if (ret)
+ irq_ack |= CORE_PWRCTL_IO_FAIL;
+ else
+ irq_ack |= CORE_PWRCTL_IO_SUCCESS;
+ }
+ if (irq_status & CORE_PWRCTL_IO_HIGH) {
+ /* Switch voltage High */
+ ret = sdhci_msm_set_vdd_io_vol(msm_host->pdata, VDD_IO_HIGH, 0);
+ if (ret)
+ irq_ack |= CORE_PWRCTL_IO_FAIL;
+ else
+ irq_ack |= CORE_PWRCTL_IO_SUCCESS;
+ }
+
+ /* ACK status to the core */
+ writeb_relaxed(irq_ack, (msm_host->core_mem + CORE_PWRCTL_CTL));
+ /*
+ * SDHC has core_mem and hc_mem device memory and these memory
+ * addresses do not fall within 1KB region. Hence, any update to
+ * core_mem address space would require an mb() to ensure this gets
+ * completed before its next update to registers within hc_mem.
+ */
+ mb();
+
+ if (irq_status & CORE_PWRCTL_IO_HIGH)
+ writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) &
+ ~CORE_IO_PAD_PWR_SWITCH),
+ host->ioaddr + CORE_VENDOR_SPEC);
+ if (irq_status & CORE_PWRCTL_IO_LOW)
+ writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) |
+ CORE_IO_PAD_PWR_SWITCH),
+ host->ioaddr + CORE_VENDOR_SPEC);
+ mb();
+
+ pr_debug("%s: Handled IRQ(%d), ret=%d, ack=0x%x\n",
+ mmc_hostname(msm_host->mmc), irq, ret, irq_ack);
+ wake_up_interruptible(&msm_host->pwr_irq_wait);
return IRQ_HANDLED;
}
-static const struct of_device_id sdhci_msm_dt_match[] = {
- { .compatible = "qcom,sdhci-msm-v4" },
- {},
-};
+static ssize_t
+show_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
-MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match);
+ return snprintf(buf, PAGE_SIZE, "%u\n",
+ msm_host->msm_bus_vote.is_max_bw_needed);
+}
-static const struct sdhci_ops sdhci_msm_ops = {
+static ssize_t
+store_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
+ uint32_t value;
+ unsigned long flags;
+
+ if (!kstrtou32(buf, 0, &value)) {
+ spin_lock_irqsave(&host->lock, flags);
+ msm_host->msm_bus_vote.is_max_bw_needed = !!value;
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+ return count;
+}
+
+static void sdhci_msm_check_power_status(struct sdhci_host *host)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
+ int ret = 0;
+
+ pr_debug("%s: %s: power status before waiting 0x%x\n",
+ mmc_hostname(host->mmc), __func__,
+ readb_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL));
+
+ ret = wait_event_interruptible(msm_host->pwr_irq_wait,
+ (readb_relaxed(msm_host->core_mem +
+ CORE_PWRCTL_CTL)) != 0x0);
+ if (ret)
+ pr_warning("%s: %s: returned due to error %d\n",
+ mmc_hostname(host->mmc), __func__, ret);
+ pr_debug("%s: %s: ret %d power status after handling power IRQ 0x%x\n",
+ mmc_hostname(host->mmc), __func__, ret,
+ readb_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL));
+}
+
+static void sdhci_msm_toggle_cdr(struct sdhci_host *host, bool enable)
+{
+ if (enable)
+ writel_relaxed((readl_relaxed(host->ioaddr +
+ CORE_DLL_CONFIG) | CORE_CDR_EN),
+ host->ioaddr + CORE_DLL_CONFIG);
+ else
+ writel_relaxed((readl_relaxed(host->ioaddr +
+ CORE_DLL_CONFIG) & ~CORE_CDR_EN),
+ host->ioaddr + CORE_DLL_CONFIG);
+}
+
+static unsigned int sdhci_msm_max_segs(void)
+{
+ return SDHCI_MSM_MAX_SEGMENTS;
+}
+
+void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ int rc;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
+ unsigned long flags;
+
+ if (clock && !atomic_read(&msm_host->clks_on)) {
+ pr_debug("%s: request to enable clock at rate %u\n",
+ mmc_hostname(host->mmc), clock);
+ if (!IS_ERR_OR_NULL(msm_host->bus_clk)) {
+ rc = clk_prepare_enable(msm_host->bus_clk);
+ if (rc) {
+ pr_err("%s: %s: failed to enable the bus-clock with error %d\n",
+ mmc_hostname(host->mmc), __func__, rc);
+ goto out;
+ }
+ }
+ if (!IS_ERR(msm_host->pclk)) {
+ rc = clk_prepare_enable(msm_host->pclk);
+ if (rc) {
+ pr_err("%s: %s: failed to enable the pclk with error %d\n",
+ mmc_hostname(host->mmc), __func__, rc);
+ goto disable_bus_clk;
+ }
+ }
+ rc = clk_prepare_enable(msm_host->clk);
+ if (rc) {
+ pr_err("%s: %s: failed to enable the host-clk with error %d\n",
+ mmc_hostname(host->mmc), __func__, rc);
+ goto disable_pclk;
+ }
+ mb();
+ atomic_set(&msm_host->clks_on, 1);
+
+ } else if (!clock && atomic_read(&msm_host->clks_on)) {
+ pr_debug("%s: request to disable clocks\n",
+ mmc_hostname(host->mmc));
+ sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
+ mb();
+ clk_disable_unprepare(msm_host->clk);
+ if (!IS_ERR(msm_host->pclk))
+ clk_disable_unprepare(msm_host->pclk);
+ if (!IS_ERR_OR_NULL(msm_host->bus_clk))
+ clk_disable_unprepare(msm_host->bus_clk);
+ atomic_set(&msm_host->clks_on, 0);
+ }
+ spin_lock_irqsave(&host->lock, flags);
+ host->clock = clock;
+ spin_unlock_irqrestore(&host->lock, flags);
+ goto out;
+disable_pclk:
+ if (!IS_ERR_OR_NULL(msm_host->pclk))
+ clk_disable_unprepare(msm_host->pclk);
+disable_bus_clk:
+ if (!IS_ERR_OR_NULL(msm_host->bus_clk))
+ clk_disable_unprepare(msm_host->bus_clk);
+out:
+ return;
+}
+
+static struct sdhci_ops sdhci_msm_ops = {
+ .check_power_status = sdhci_msm_check_power_status,
.platform_execute_tuning = sdhci_msm_execute_tuning,
- .reset = sdhci_reset,
- .set_clock = sdhci_set_clock,
- .set_bus_width = sdhci_set_bus_width,
- .set_uhs_signaling = sdhci_msm_set_uhs_signaling,
- .voltage_switch = sdhci_msm_voltage_switch,
-};
-
-static const struct sdhci_pltfm_data sdhci_msm_pdata = {
- .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION |
- SDHCI_QUIRK_NO_CARD_NO_RESET |
- SDHCI_QUIRK_SINGLE_POWER_WRITE,
- .ops = &sdhci_msm_ops,
+ .toggle_cdr = sdhci_msm_toggle_cdr,
+ .get_max_segments = sdhci_msm_max_segs,
+ .set_clock = sdhci_msm_set_clock,
+ .platform_bus_voting = sdhci_msm_bus_voting,
};
static int sdhci_msm_probe(struct platform_device *pdev)
@@ -533,30 +1613,48 @@ static int sdhci_msm_probe(struct platform_device *pdev)
struct sdhci_host *host;
struct sdhci_pltfm_host *pltfm_host;
struct sdhci_msm_host *msm_host;
- struct resource *core_memres;
- int ret;
- u16 host_version, core_minor;
- u32 core_version, caps;
- u8 core_major;
+ struct resource *core_memres = NULL;
+ int ret = 0, pwr_irq = 0, dead = 0;
+ u32 host_version;
- host = sdhci_pltfm_init(pdev, &sdhci_msm_pdata, sizeof(*msm_host));
- if (IS_ERR(host))
- return PTR_ERR(host);
+ pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__);
+ msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host),
+ GFP_KERNEL);
+ if (!msm_host) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ init_waitqueue_head(&msm_host->pwr_irq_wait);
+
+ msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops;
+ host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0);
+ if (IS_ERR(host)) {
+ ret = PTR_ERR(host);
+ goto out;
+ }
pltfm_host = sdhci_priv(host);
- msm_host = sdhci_pltfm_priv(pltfm_host);
+ pltfm_host->priv = msm_host;
msm_host->mmc = host->mmc;
msm_host->pdev = pdev;
- ret = mmc_of_parse(host->mmc);
- if (ret)
+ /* Extract platform data */
+ if (pdev->dev.of_node) {
+ msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev);
+ if (!msm_host->pdata) {
+ dev_err(&pdev->dev, "DT parsing error\n");
+ goto pltfm_free;
+ }
+ } else {
+ dev_err(&pdev->dev, "No device tree node\n");
goto pltfm_free;
+ }
- sdhci_get_of_property(pdev);
+ /* Setup Clocks */
/* Setup SDCC bus voter clock. */
- msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus");
- if (!IS_ERR(msm_host->bus_clk)) {
+ msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk");
+ if (!IS_ERR_OR_NULL(msm_host->bus_clk)) {
/* Vote for max. clk rate for max. performance */
ret = clk_set_rate(msm_host->bus_clk, INT_MAX);
if (ret)
@@ -567,113 +1665,168 @@ static int sdhci_msm_probe(struct platform_device *pdev)
}
/* Setup main peripheral bus clock */
- msm_host->pclk = devm_clk_get(&pdev->dev, "iface");
- if (IS_ERR(msm_host->pclk)) {
- ret = PTR_ERR(msm_host->pclk);
- dev_err(&pdev->dev, "Peripheral clk setup failed (%d)\n", ret);
- goto bus_clk_disable;
+ msm_host->pclk = devm_clk_get(&pdev->dev, "iface_clk");
+ if (!IS_ERR(msm_host->pclk)) {
+ ret = clk_prepare_enable(msm_host->pclk);
+ if (ret)
+ goto bus_clk_disable;
}
- ret = clk_prepare_enable(msm_host->pclk);
- if (ret)
- goto bus_clk_disable;
-
/* Setup SDC MMC clock */
- msm_host->clk = devm_clk_get(&pdev->dev, "core");
+ msm_host->clk = devm_clk_get(&pdev->dev, "core_clk");
if (IS_ERR(msm_host->clk)) {
ret = PTR_ERR(msm_host->clk);
- dev_err(&pdev->dev, "SDC MMC clk setup failed (%d)\n", ret);
goto pclk_disable;
}
- /* Vote for maximum clock rate for maximum performance */
- ret = clk_set_rate(msm_host->clk, INT_MAX);
- if (ret)
- dev_warn(&pdev->dev, "core clock boost failed\n");
-
ret = clk_prepare_enable(msm_host->clk);
if (ret)
goto pclk_disable;
- core_memres = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- msm_host->core_mem = devm_ioremap_resource(&pdev->dev, core_memres);
-
- if (IS_ERR(msm_host->core_mem)) {
- dev_err(&pdev->dev, "Failed to remap registers\n");
- ret = PTR_ERR(msm_host->core_mem);
+ atomic_set(&msm_host->clks_on, 1);
+ /* Setup regulators */
+ ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true);
+ if (ret) {
+ dev_err(&pdev->dev, "Regulator setup failed (%d)\n", ret);
goto clk_disable;
}
/* Reset the core and Enable SDHC mode */
- writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) |
- CORE_SW_RST, msm_host->core_mem + CORE_POWER);
+ core_memres = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "core_mem");
+ msm_host->core_mem = devm_ioremap(&pdev->dev, core_memres->start,
+ resource_size(core_memres));
- /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */
- usleep_range(1000, 5000);
- if (readl(msm_host->core_mem + CORE_POWER) & CORE_SW_RST) {
- dev_err(&pdev->dev, "Stuck in reset\n");
- ret = -ETIMEDOUT;
- goto clk_disable;
+ if (!msm_host->core_mem) {
+ dev_err(&pdev->dev, "Failed to remap registers\n");
+ ret = -ENOMEM;
+ goto vreg_deinit;
}
+ /* Set SW_RST bit in POWER register (Offset 0x0) */
+ writel_relaxed(CORE_SW_RST, msm_host->core_mem + CORE_POWER);
/* Set HC_MODE_EN bit in HC_MODE register */
writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE));
- host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION));
+ /*
+ * Following are the deviations from SDHC spec v3.0 -
+ * 1. Card detection is handled using separate GPIO.
+ * 2. Bus power control is handled by interacting with PMIC.
+ */
+ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
+ host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE;
+
+ host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION));
dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n",
host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >>
- SDHCI_VENDOR_VER_SHIFT));
-
- core_version = readl_relaxed(msm_host->core_mem + CORE_MCI_VERSION);
- core_major = (core_version & CORE_VERSION_MAJOR_MASK) >>
- CORE_VERSION_MAJOR_SHIFT;
- core_minor = core_version & CORE_VERSION_MINOR_MASK;
- dev_dbg(&pdev->dev, "MCI Version: 0x%08x, major: 0x%04x, minor: 0x%02x\n",
- core_version, core_major, core_minor);
-
- /*
- * Support for some capabilities is not advertised by newer
- * controller versions and must be explicitly enabled.
- */
- if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) {
- caps = readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES);
- caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT;
- writel_relaxed(caps, host->ioaddr +
- CORE_VENDOR_SPEC_CAPABILITIES0);
+ SDHCI_VENDOR_VER_SHIFT));
+ if (((host_version & SDHCI_VENDOR_VER_MASK) >>
+ SDHCI_VENDOR_VER_SHIFT) == SDHCI_VER_100) {
+ /*
+ * Add 40us delay in interrupt handler when
+ * operating at initialization frequency(400KHz).
+ */
+ host->quirks2 |= SDHCI_QUIRK2_SLOW_INT_CLR;
+ /*
+ * Set Software Reset for DAT line in Software
+ * Reset Register (Bit 2).
+ */
+ host->quirks2 |= SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT;
}
- /* Setup IRQ for handling power/voltage tasks with PMIC */
- msm_host->pwr_irq = platform_get_irq_byname(pdev, "pwr_irq");
- if (msm_host->pwr_irq < 0) {
- dev_err(&pdev->dev, "Get pwr_irq failed (%d)\n",
- msm_host->pwr_irq);
- ret = msm_host->pwr_irq;
- goto clk_disable;
+ /* Setup PWRCTL irq */
+ pwr_irq = platform_get_irq_byname(pdev, "pwr_irq");
+ if (pwr_irq < 0) {
+ dev_err(&pdev->dev, "Failed to get pwr_irq by name (%d)\n",
+ pwr_irq);
+ goto vreg_deinit;
}
-
- ret = devm_request_threaded_irq(&pdev->dev, msm_host->pwr_irq, NULL,
+ ret = devm_request_threaded_irq(&pdev->dev, pwr_irq, NULL,
sdhci_msm_pwr_irq, IRQF_ONESHOT,
dev_name(&pdev->dev), host);
if (ret) {
- dev_err(&pdev->dev, "Request IRQ failed (%d)\n", ret);
- goto clk_disable;
+ dev_err(&pdev->dev, "Request threaded irq(%d) failed (%d)\n",
+ pwr_irq, ret);
+ goto vreg_deinit;
}
- ret = sdhci_add_host(host);
+ /* Enable pwr irq interrupts */
+ writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK));
+
+#ifdef CONFIG_MMC_CLKGATE
+ /* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */
+ msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY;
+#endif
+
+ /* Set host capabilities */
+ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width;
+ msm_host->mmc->caps |= msm_host->pdata->caps;
+ msm_host->mmc->caps |= MMC_CAP_HW_RESET;
+ msm_host->mmc->caps2 |= msm_host->pdata->caps2;
+ msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR;
+ msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL;
+
+ if (msm_host->pdata->nonremovable)
+ msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE;
+
+ host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us;
+
+ ret = sdhci_msm_bus_register(msm_host, pdev);
if (ret)
- goto clk_disable;
+ goto vreg_deinit;
- return 0;
+ if (msm_host->msm_bus_vote.client_handle)
+ INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work,
+ sdhci_msm_bus_work);
+ ret = sdhci_add_host(host);
+ if (ret) {
+ dev_err(&pdev->dev, "Add host failed (%d)\n", ret);
+ goto bus_unregister;
+ }
+
+ /* Set core clk rate, optionally override from dts */
+ if (msm_host->pdata->max_clk)
+ host->max_clk = msm_host->pdata->max_clk;
+ ret = clk_set_rate(msm_host->clk, host->max_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "MClk rate set failed (%d)\n", ret);
+ goto remove_host;
+ }
+
+ msm_host->msm_bus_vote.max_bus_bw.show = show_sdhci_max_bus_bw;
+ msm_host->msm_bus_vote.max_bus_bw.store = store_sdhci_max_bus_bw;
+ sysfs_attr_init(&msm_host->msm_bus_vote.max_bus_bw.attr);
+ msm_host->msm_bus_vote.max_bus_bw.attr.name = "max_bus_bw";
+ msm_host->msm_bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR;
+ ret = device_create_file(&pdev->dev,
+ &msm_host->msm_bus_vote.max_bus_bw);
+ if (ret)
+ goto remove_host;
+
+ /* Successful initialization */
+ goto out;
+
+remove_host:
+ dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff);
+ sdhci_remove_host(host, dead);
+bus_unregister:
+ sdhci_msm_bus_unregister(msm_host);
+vreg_deinit:
+ sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
clk_disable:
- clk_disable_unprepare(msm_host->clk);
+ if (!IS_ERR(msm_host->clk))
+ clk_disable_unprepare(msm_host->clk);
pclk_disable:
- clk_disable_unprepare(msm_host->pclk);
+ if (!IS_ERR(msm_host->pclk))
+ clk_disable_unprepare(msm_host->pclk);
bus_clk_disable:
- if (!IS_ERR(msm_host->bus_clk))
+ if (!IS_ERR_OR_NULL(msm_host->bus_clk))
clk_disable_unprepare(msm_host->bus_clk);
pltfm_free:
sdhci_pltfm_free(pdev);
+out:
+ pr_debug("%s: Exit %s\n", dev_name(&pdev->dev), __func__);
return ret;
}
@@ -681,29 +1834,43 @@ static int sdhci_msm_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
- struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
+ struct sdhci_msm_host *msm_host = pltfm_host->priv;
+ struct sdhci_msm_pltfm_data *pdata = msm_host->pdata;
int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) ==
- 0xffffffff);
+ 0xffffffff);
+ pr_debug("%s: %s\n", dev_name(&pdev->dev), __func__);
+ device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw);
sdhci_remove_host(host, dead);
- clk_disable_unprepare(msm_host->clk);
- clk_disable_unprepare(msm_host->pclk);
- if (!IS_ERR(msm_host->bus_clk))
- clk_disable_unprepare(msm_host->bus_clk);
sdhci_pltfm_free(pdev);
+ sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false);
+
+ if (pdata->pin_data)
+ sdhci_msm_setup_gpio(pdata, false);
+
+ if (msm_host->msm_bus_vote.client_handle) {
+ sdhci_msm_bus_cancel_work_and_set_vote(host, 0);
+ sdhci_msm_bus_unregister(msm_host);
+ }
return 0;
}
+static const struct of_device_id sdhci_msm_dt_match[] = {
+ {.compatible = "qcom,sdhci-msm"},
+};
+MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match);
+
static struct platform_driver sdhci_msm_driver = {
- .probe = sdhci_msm_probe,
- .remove = sdhci_msm_remove,
- .driver = {
- .name = "sdhci_msm",
- .of_match_table = sdhci_msm_dt_match,
+ .probe = sdhci_msm_probe,
+ .remove = sdhci_msm_remove,
+ .driver = {
+ .name = "sdhci_msm",
+ .owner = THIS_MODULE,
+ .of_match_table = sdhci_msm_dt_match,
},
};
module_platform_driver(sdhci_msm_driver);
-MODULE_DESCRIPTION("Qualcomm Secure Digital Host Controller Interface driver");
+MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Secure Digital Host Controller Interface driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h
index 3280f20..33b4fa6 100644
--- a/drivers/mmc/host/sdhci-pltfm.h
+++ b/drivers/mmc/host/sdhci-pltfm.h
@@ -23,6 +23,7 @@ struct sdhci_pltfm_data {
struct sdhci_pltfm_host {
struct clk *clk;
+ void *priv; /* to handle quirks across io-accessor calls */
/* migrate from sdhci_of_host */
unsigned int clock;
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index a983ba0..e1b54fc 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -748,6 +748,17 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd)
}
}
+static void sdhci_set_blk_size_reg(struct sdhci_host *host, unsigned int blksz,
+ unsigned int sdma_boundary)
+{
+ if (host->flags & SDHCI_USE_ADMA)
+ sdhci_writew(host, SDHCI_MAKE_BLKSZ(0, blksz),
+ SDHCI_BLOCK_SIZE);
+ else
+ sdhci_writew(host, SDHCI_MAKE_BLKSZ(sdma_boundary, blksz),
+ SDHCI_BLOCK_SIZE);
+}
+
static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
{
u8 ctrl;
@@ -880,8 +891,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd)
sdhci_set_transfer_irqs(host);
/* Set the DMA boundary value and block size */
- sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG,
- data->blksz), SDHCI_BLOCK_SIZE);
+ sdhci_set_blk_size_reg(host, data->blksz, SDHCI_DEFAULT_BOUNDARY_ARG);
sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
}
@@ -1350,7 +1360,8 @@ void sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
host->mmc->actual_clock = 0;
- sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
+ if (host->clock)
+ sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
if (clock == 0)
return;
@@ -1479,6 +1490,32 @@ EXPORT_SYMBOL_GPL(sdhci_set_power);
* *
\*****************************************************************************/
+static int sdhci_enable(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (host->cpu_dma_latency_us)
+ pm_qos_update_request(&host->pm_qos_req_dma,
+ host->cpu_dma_latency_us);
+ if (host->ops->platform_bus_voting)
+ host->ops->platform_bus_voting(host, 1);
+
+ return 0;
+}
+
+static int sdhci_disable(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ if (host->cpu_dma_latency_us)
+ pm_qos_update_request(&host->pm_qos_req_dma,
+ PM_QOS_DEFAULT_VALUE);
+ if (host->ops->platform_bus_voting)
+ host->ops->platform_bus_voting(host, 0);
+
+ return 0;
+}
+
static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdhci_host *host;
@@ -1581,22 +1618,16 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
return;
}
- /*
- * Reset the chip on each power off.
- * Should clear out any weird states.
- */
- if (ios->power_mode == MMC_POWER_OFF) {
- sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
- sdhci_reinit(host);
- }
-
if (host->version >= SDHCI_SPEC_300 &&
(ios->power_mode == MMC_POWER_UP) &&
!(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN))
sdhci_enable_preset_value(host, false);
+ spin_lock_irqsave(&host->lock, flags);
if (!ios->clock || ios->clock != host->clock) {
+ spin_unlock_irqrestore(&host->lock, flags);
host->ops->set_clock(host, ios->clock);
+ spin_lock_irqsave(&host->lock, flags);
host->clock = ios->clock;
if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK &&
@@ -1611,11 +1642,13 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
host->mmc->max_busy_timeout /= host->timeout_clk;
}
}
+ spin_unlock_irqrestore(&host->lock, flags);
if (host->ops->set_power)
host->ops->set_power(host, ios->power_mode, ios->vdd);
else
- sdhci_set_power(host, ios->power_mode, ios->vdd);
+ if (ios->power_mode & (MMC_POWER_UP | MMC_POWER_ON))
+ sdhci_set_power(host, ios->power_mode, ios->vdd);
if (host->ops->platform_send_init_74_clocks)
host->ops->platform_send_init_74_clocks(host, ios->power_mode);
@@ -1714,6 +1747,7 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
} else
sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+ spin_unlock_irqrestore(&host->lock, flags);
/*
* Some (ENE) controllers go apeshit on some ios operation,
* signalling timeout and CRC errors even on CMD0. Resetting
@@ -1722,8 +1756,19 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
if (host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS)
sdhci_do_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA);
+ /*
+ * Reset the chip on each power off.
+ * Should clear out any weird states.
+ */
+ if (ios->power_mode == MMC_POWER_OFF) {
+ sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE);
+ sdhci_reinit(host);
+ sdhci_set_power(host, ios->power_mode, ios->vdd);
+ }
+ if (!ios->clock)
+ sdhci_set_clock(host, ios->clock);
+
mmiowb();
- spin_unlock_irqrestore(&host->lock, flags);
}
static int sdhci_get_cd(struct mmc_host *mmc)
@@ -2056,14 +2101,11 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
*/
if (cmd.opcode == MMC_SEND_TUNING_BLOCK_HS200) {
if (mmc->ios.bus_width == MMC_BUS_WIDTH_8)
- sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 128),
- SDHCI_BLOCK_SIZE);
+ sdhci_set_blk_size_reg(host, 128, 7);
else if (mmc->ios.bus_width == MMC_BUS_WIDTH_4)
- sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64),
- SDHCI_BLOCK_SIZE);
+ sdhci_set_blk_size_reg(host, 64, 7);
} else {
- sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64),
- SDHCI_BLOCK_SIZE);
+ sdhci_set_blk_size_reg(host, 64, 7);
}
/*
@@ -2290,6 +2332,8 @@ static const struct mmc_host_ops sdhci_ops = {
.select_drive_strength = sdhci_select_drive_strength,
.card_event = sdhci_card_event,
.card_busy = sdhci_card_busy,
+ .enable = sdhci_enable,
+ .disable = sdhci_disable,
};
/*****************************************************************************\
@@ -2362,6 +2406,9 @@ static bool sdhci_request_done(struct sdhci_host *host)
sdhci_do_reset(host, SDHCI_RESET_DATA);
host->pending_reset = false;
+ } else {
+ if (host->quirks2 & SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT)
+ sdhci_reset(host, SDHCI_RESET_DATA);
}
if (!sdhci_has_requests(host))
@@ -2708,11 +2755,19 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
result = IRQ_WAKE_THREAD;
}
- if (intmask & SDHCI_INT_CMD_MASK)
+ if (intmask & SDHCI_INT_CMD_MASK) {
+ if ((host->quirks2 & SDHCI_QUIRK2_SLOW_INT_CLR) &&
+ (host->clock <= 400000))
+ udelay(40);
sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK);
+ }
- if (intmask & SDHCI_INT_DATA_MASK)
+ if (intmask & SDHCI_INT_DATA_MASK) {
+ if ((host->quirks2 & SDHCI_QUIRK2_SLOW_INT_CLR) &&
+ (host->clock <= 400000))
+ udelay(40);
sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK);
+ }
if (intmask & SDHCI_INT_BUS_POWER)
pr_err("%s: Card is consuming too much power!\n",
@@ -3595,6 +3650,10 @@ int __sdhci_add_host(struct sdhci_host *host)
mmiowb();
+ if (host->cpu_dma_latency_us)
+ pm_qos_add_request(&host->pm_qos_req_dma,
+ PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE);
+
ret = mmc_add_host(mmc);
if (ret)
goto unled;
@@ -3666,7 +3725,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)
sdhci_disable_card_detection(host);
- mmc_remove_host(mmc);
+ if (host->cpu_dma_latency_us)
+ pm_qos_remove_request(&host->pm_qos_req_dma);
+ mmc_remove_host(host->mmc);
sdhci_led_unregister(host);
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 2570455..a8d4cfa 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -17,6 +17,7 @@
#include <linux/compiler.h>
#include <linux/types.h>
#include <linux/io.h>
+#include <linux/pm_qos.h>
#include <linux/mmc/host.h>
@@ -425,6 +426,17 @@ struct sdhci_host {
#define SDHCI_QUIRK2_ACMD23_BROKEN (1<<14)
/* Broken Clock divider zero in controller */
#define SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN (1<<15)
+/*
+ * Read Transfer Active/ Write Transfer Active may be not
+ * de-asserted after end of transaction. Issue reset for DAT line.
+ */
+#define SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT (1<<17)
+/*
+ * Slow interrupt clearance at 400KHz may cause
+ * host controller driver interrupt handler to
+ * be called twice.
+*/
+#define SDHCI_QUIRK2_SLOW_INT_CLR (1<<18)
int irq; /* Device IRQ */
void __iomem *ioaddr; /* Mapped address */
@@ -474,6 +486,7 @@ struct sdhci_host {
bool pending_reset; /* Cmd/data reset is pending */
struct mmc_request *mrqs_done[SDHCI_MAX_MRQS]; /* Requests done */
+ struct mmc_request *mrq; /* Current request */
struct mmc_command *cmd; /* Current command */
struct mmc_command *data_cmd; /* Current data command */
struct mmc_data *data; /* Current data request */
@@ -525,6 +538,9 @@ struct sdhci_host {
#define SDHCI_TUNING_MODE_2 1
#define SDHCI_TUNING_MODE_3 2
+ unsigned int cpu_dma_latency_us;
+ struct pm_qos_request pm_qos_req_dma;
+
unsigned long private[0] ____cacheline_aligned;
};
@@ -558,7 +574,11 @@ struct sdhci_ops {
void (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs);
void (*hw_reset)(struct sdhci_host *host);
void (*adma_workaround)(struct sdhci_host *host, u32 intmask);
+ unsigned int (*get_max_segments)(void);
void (*card_event)(struct sdhci_host *host);
+ void (*platform_bus_voting)(struct sdhci_host *host, u32 enable);
+ void (*toggle_cdr)(struct sdhci_host *host, bool enable);
+ void (*check_power_status)(struct sdhci_host *host);
void (*voltage_switch)(struct sdhci_host *host);
int (*select_drive_strength)(struct sdhci_host *host,
struct mmc_card *card,
diff --git a/drivers/net/wireless/Makefile b/drivers/net/wireless/Makefile
index f00d429..91594de 100644
--- a/drivers/net/wireless/Makefile
+++ b/drivers/net/wireless/Makefile
@@ -25,3 +25,5 @@
obj-$(CONFIG_USB_NET_RNDIS_WLAN) += rndis_wlan.o
obj-$(CONFIG_MAC80211_HWSIM) += mac80211_hwsim.o
+
+obj-$(CONFIG_WCNSS_MEM_PRE_ALLOC) += cnss_prealloc/
diff --git a/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c b/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c
index 636f466..61de231 100644
--- a/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c
+++ b/drivers/net/wireless/cnss_prealloc/cnss_prealloc.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012,2014-2016 The Linux Foundation. All rights reserved.
+/* Copyright (c) 2012,2014-2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -207,6 +207,8 @@ void wcnss_prealloc_check_memory_leak(void)
print_stack_trace(&wcnss_allocs[i].trace, 1);
}
}
+#else
+void wcnss_prealloc_check_memory_leak(void) {}
#endif
int wcnss_pre_alloc_reset(void)
diff --git a/drivers/platform/msm/gsi/gsi.c b/drivers/platform/msm/gsi/gsi.c
index b7685cb..5fdb4e9 100644
--- a/drivers/platform/msm/gsi/gsi.c
+++ b/drivers/platform/msm/gsi/gsi.c
@@ -2854,10 +2854,8 @@ static int msm_gsi_probe(struct platform_device *pdev)
gsi_ctx->ipc_logbuf = ipc_log_context_create(GSI_IPC_LOG_PAGES,
"gsi", 0);
- if (gsi_ctx->ipc_logbuf == NULL) {
- GSIERR("failed to get ipc_logbuf\n");
- return -ENOMEM;
- }
+ if (gsi_ctx->ipc_logbuf == NULL)
+ GSIERR("failed to create IPC log, continue...\n");
gsi_ctx->dev = dev;
init_completion(&gsi_ctx->gen_ee_cmd_compl);
diff --git a/drivers/platform/msm/ipa/ipa_clients/ipa_uc_offload.c b/drivers/platform/msm/ipa/ipa_clients/ipa_uc_offload.c
index 51c930a..ae06d54 100644
--- a/drivers/platform/msm/ipa/ipa_clients/ipa_uc_offload.c
+++ b/drivers/platform/msm/ipa/ipa_clients/ipa_uc_offload.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015, 2016 The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -131,23 +131,23 @@ static int ipa_uc_offload_ntn_reg_intf(
IPA_UC_OFFLOAD_DBG("register interface for netdev %s\n",
inp->netdev_name);
memset(¶m, 0, sizeof(param));
- param.name = IPA_RM_RESOURCE_ODU_ADAPT_PROD;
+ param.name = IPA_RM_RESOURCE_ETHERNET_PROD;
param.reg_params.user_data = ntn_ctx;
param.reg_params.notify_cb = ipa_uc_offload_rm_notify;
param.floor_voltage = IPA_VOLTAGE_SVS;
ret = ipa_rm_create_resource(¶m);
if (ret) {
- IPA_UC_OFFLOAD_ERR("fail to create ODU_ADAPT_PROD resource\n");
+ IPA_UC_OFFLOAD_ERR("fail to create ETHERNET_PROD resource\n");
return -EFAULT;
}
memset(¶m, 0, sizeof(param));
- param.name = IPA_RM_RESOURCE_ODU_ADAPT_CONS;
+ param.name = IPA_RM_RESOURCE_ETHERNET_CONS;
param.request_resource = ipa_uc_ntn_cons_request;
param.release_resource = ipa_uc_ntn_cons_release;
ret = ipa_rm_create_resource(¶m);
if (ret) {
- IPA_UC_OFFLOAD_ERR("fail to create ODU_ADAPT_CONS resource\n");
+ IPA_UC_OFFLOAD_ERR("fail to create ETHERNET_CONS resource\n");
goto fail_create_rm_cons;
}
@@ -177,13 +177,13 @@ static int ipa_uc_offload_ntn_reg_intf(
memset(tx_prop, 0, sizeof(tx_prop));
tx_prop[0].ip = IPA_IP_v4;
- tx_prop[0].dst_pipe = IPA_CLIENT_ODU_TETH_CONS;
+ tx_prop[0].dst_pipe = IPA_CLIENT_ETHERNET_CONS;
tx_prop[0].hdr_l2_type = inp->hdr_info[0].hdr_type;
memcpy(tx_prop[0].hdr_name, hdr->hdr[IPA_IP_v4].name,
sizeof(tx_prop[0].hdr_name));
tx_prop[1].ip = IPA_IP_v6;
- tx_prop[1].dst_pipe = IPA_CLIENT_ODU_TETH_CONS;
+ tx_prop[1].dst_pipe = IPA_CLIENT_ETHERNET_CONS;
tx_prop[1].hdr_l2_type = inp->hdr_info[1].hdr_type;
memcpy(tx_prop[1].hdr_name, hdr->hdr[IPA_IP_v6].name,
sizeof(tx_prop[1].hdr_name));
@@ -194,7 +194,7 @@ static int ipa_uc_offload_ntn_reg_intf(
memset(rx_prop, 0, sizeof(rx_prop));
rx_prop[0].ip = IPA_IP_v4;
- rx_prop[0].src_pipe = IPA_CLIENT_ODU_PROD;
+ rx_prop[0].src_pipe = IPA_CLIENT_ETHERNET_PROD;
rx_prop[0].hdr_l2_type = inp->hdr_info[0].hdr_type;
if (inp->is_meta_data_valid) {
rx_prop[0].attrib.attrib_mask |= IPA_FLT_META_DATA;
@@ -203,7 +203,7 @@ static int ipa_uc_offload_ntn_reg_intf(
}
rx_prop[1].ip = IPA_IP_v6;
- rx_prop[1].src_pipe = IPA_CLIENT_ODU_PROD;
+ rx_prop[1].src_pipe = IPA_CLIENT_ETHERNET_PROD;
rx_prop[1].hdr_l2_type = inp->hdr_info[1].hdr_type;
if (inp->is_meta_data_valid) {
rx_prop[1].attrib.attrib_mask |= IPA_FLT_META_DATA;
@@ -229,9 +229,9 @@ static int ipa_uc_offload_ntn_reg_intf(
fail:
kfree(hdr);
fail_alloc:
- ipa_rm_delete_resource(IPA_RM_RESOURCE_ODU_ADAPT_CONS);
+ ipa_rm_delete_resource(IPA_RM_RESOURCE_ETHERNET_CONS);
fail_create_rm_cons:
- ipa_rm_delete_resource(IPA_RM_RESOURCE_ODU_ADAPT_PROD);
+ ipa_rm_delete_resource(IPA_RM_RESOURCE_ETHERNET_PROD);
return ret;
}
@@ -349,18 +349,18 @@ int ipa_uc_ntn_conn_pipes(struct ipa_ntn_conn_in_params *inp,
return -EINVAL;
}
- result = ipa_rm_add_dependency(IPA_RM_RESOURCE_ODU_ADAPT_PROD,
+ result = ipa_rm_add_dependency(IPA_RM_RESOURCE_ETHERNET_PROD,
IPA_RM_RESOURCE_APPS_CONS);
if (result) {
IPA_UC_OFFLOAD_ERR("fail to add rm dependency: %d\n", result);
return result;
}
- result = ipa_rm_request_resource(IPA_RM_RESOURCE_ODU_ADAPT_PROD);
+ result = ipa_rm_request_resource(IPA_RM_RESOURCE_ETHERNET_PROD);
if (result == -EINPROGRESS) {
if (wait_for_completion_timeout(&ntn_ctx->ntn_completion,
10*HZ) == 0) {
- IPA_UC_OFFLOAD_ERR("ODU PROD resource req time out\n");
+ IPA_UC_OFFLOAD_ERR("ETH_PROD resource req time out\n");
result = -EFAULT;
goto fail;
}
@@ -384,7 +384,7 @@ int ipa_uc_ntn_conn_pipes(struct ipa_ntn_conn_in_params *inp,
return 0;
fail:
- ipa_rm_delete_dependency(IPA_RM_RESOURCE_ODU_ADAPT_PROD,
+ ipa_rm_delete_dependency(IPA_RM_RESOURCE_ETHERNET_PROD,
IPA_RM_RESOURCE_APPS_CONS);
return result;
}
@@ -448,10 +448,10 @@ int ipa_set_perf_profile(struct ipa_perf_profile *profile)
rm_profile.max_supported_bandwidth_mbps =
profile->max_supported_bw_mbps;
- if (profile->client == IPA_CLIENT_ODU_PROD) {
- resource_name = IPA_RM_RESOURCE_ODU_ADAPT_PROD;
- } else if (profile->client == IPA_CLIENT_ODU_TETH_CONS) {
- resource_name = IPA_RM_RESOURCE_ODU_ADAPT_CONS;
+ if (profile->client == IPA_CLIENT_ETHERNET_PROD) {
+ resource_name = IPA_RM_RESOURCE_ETHERNET_PROD;
+ } else if (profile->client == IPA_CLIENT_ETHERNET_CONS) {
+ resource_name = IPA_RM_RESOURCE_ETHERNET_CONS;
} else {
IPA_UC_OFFLOAD_ERR("not supported\n");
return -EINVAL;
@@ -473,22 +473,22 @@ static int ipa_uc_ntn_disconn_pipes(struct ipa_uc_offload_ctx *ntn_ctx)
ntn_ctx->state = IPA_UC_OFFLOAD_STATE_DOWN;
- ret = ipa_rm_release_resource(IPA_RM_RESOURCE_ODU_ADAPT_PROD);
+ ret = ipa_rm_release_resource(IPA_RM_RESOURCE_ETHERNET_PROD);
if (ret) {
- IPA_UC_OFFLOAD_ERR("fail to release ODU_ADAPT_PROD res: %d\n",
+ IPA_UC_OFFLOAD_ERR("fail to release ETHERNET_PROD res: %d\n",
ret);
return -EFAULT;
}
- ret = ipa_rm_delete_dependency(IPA_RM_RESOURCE_ODU_ADAPT_PROD,
+ ret = ipa_rm_delete_dependency(IPA_RM_RESOURCE_ETHERNET_PROD,
IPA_RM_RESOURCE_APPS_CONS);
if (ret) {
- IPA_UC_OFFLOAD_ERR("fail to del dep ODU->APPS, %d\n", ret);
+ IPA_UC_OFFLOAD_ERR("fail to del dep ETH_PROD->APPS, %d\n", ret);
return -EFAULT;
}
- ipa_ep_idx_ul = ipa_get_ep_mapping(IPA_CLIENT_ODU_PROD);
- ipa_ep_idx_dl = ipa_get_ep_mapping(IPA_CLIENT_ODU_TETH_CONS);
+ ipa_ep_idx_ul = ipa_get_ep_mapping(IPA_CLIENT_ETHERNET_PROD);
+ ipa_ep_idx_dl = ipa_get_ep_mapping(IPA_CLIENT_ETHERNET_CONS);
ret = ipa_tear_down_uc_offload_pipes(ipa_ep_idx_ul, ipa_ep_idx_dl);
if (ret) {
IPA_UC_OFFLOAD_ERR("fail to tear down ntn offload pipes, %d\n",
@@ -541,13 +541,13 @@ static int ipa_uc_ntn_cleanup(struct ipa_uc_offload_ctx *ntn_ctx)
int len, result = 0;
struct ipa_ioc_del_hdr *hdr;
- if (ipa_rm_delete_resource(IPA_RM_RESOURCE_ODU_ADAPT_PROD)) {
- IPA_UC_OFFLOAD_ERR("fail to delete ODU_ADAPT_PROD resource\n");
+ if (ipa_rm_delete_resource(IPA_RM_RESOURCE_ETHERNET_PROD)) {
+ IPA_UC_OFFLOAD_ERR("fail to delete ETHERNET_PROD resource\n");
return -EFAULT;
}
- if (ipa_rm_delete_resource(IPA_RM_RESOURCE_ODU_ADAPT_CONS)) {
- IPA_UC_OFFLOAD_ERR("fail to delete ODU_ADAPT_CONS resource\n");
+ if (ipa_rm_delete_resource(IPA_RM_RESOURCE_ETHERNET_CONS)) {
+ IPA_UC_OFFLOAD_ERR("fail to delete ETHERNET_CONS resource\n");
return -EFAULT;
}
diff --git a/drivers/platform/msm/ipa/ipa_rm.c b/drivers/platform/msm/ipa/ipa_rm.c
index 1431dcf..ea91b13 100644
--- a/drivers/platform/msm/ipa/ipa_rm.c
+++ b/drivers/platform/msm/ipa/ipa_rm.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -28,6 +28,7 @@ static const char *resource_name_to_str[IPA_RM_RESOURCE_MAX] = {
__stringify(IPA_RM_RESOURCE_WLAN_PROD),
__stringify(IPA_RM_RESOURCE_ODU_ADAPT_PROD),
__stringify(IPA_RM_RESOURCE_MHI_PROD),
+ __stringify(IPA_RM_RESOURCE_ETHERNET_PROD),
__stringify(IPA_RM_RESOURCE_Q6_CONS),
__stringify(IPA_RM_RESOURCE_USB_CONS),
__stringify(IPA_RM_RESOURCE_USB_DPL_CONS),
@@ -36,6 +37,7 @@ static const char *resource_name_to_str[IPA_RM_RESOURCE_MAX] = {
__stringify(IPA_RM_RESOURCE_APPS_CONS),
__stringify(IPA_RM_RESOURCE_ODU_ADAPT_CONS),
__stringify(IPA_RM_RESOURCE_MHI_CONS),
+ __stringify(IPA_RM_RESOURCE_ETHERNET_CONS),
};
struct ipa_rm_profile_vote_type {
diff --git a/drivers/platform/msm/ipa/ipa_rm_resource.c b/drivers/platform/msm/ipa/ipa_rm_resource.c
index 6657bd9..9e74a3f 100644
--- a/drivers/platform/msm/ipa/ipa_rm_resource.c
+++ b/drivers/platform/msm/ipa/ipa_rm_resource.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -38,6 +38,7 @@ int ipa_rm_prod_index(enum ipa_rm_resource_name resource_name)
case IPA_RM_RESOURCE_WLAN_PROD:
case IPA_RM_RESOURCE_ODU_ADAPT_PROD:
case IPA_RM_RESOURCE_MHI_PROD:
+ case IPA_RM_RESOURCE_ETHERNET_PROD:
break;
default:
result = IPA_RM_INDEX_INVALID;
@@ -69,6 +70,7 @@ int ipa_rm_cons_index(enum ipa_rm_resource_name resource_name)
case IPA_RM_RESOURCE_ODU_ADAPT_CONS:
case IPA_RM_RESOURCE_MHI_CONS:
case IPA_RM_RESOURCE_USB_DPL_CONS:
+ case IPA_RM_RESOURCE_ETHERNET_CONS:
break;
default:
result = IPA_RM_INDEX_INVALID;
diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_dp.c b/drivers/platform/msm/ipa/ipa_v2/ipa_dp.c
index 964d6c8..3dca3e6 100644
--- a/drivers/platform/msm/ipa/ipa_v2/ipa_dp.c
+++ b/drivers/platform/msm/ipa/ipa_v2/ipa_dp.c
@@ -18,6 +18,7 @@
#include "ipa_i.h"
#include "ipa_trace.h"
+#define IPA_WAN_AGGR_PKT_CNT 5
#define IPA_LAST_DESC_CNT 0xFFFF
#define POLLING_INACTIVITY_RX 40
#define POLLING_INACTIVITY_TX 40
@@ -1099,16 +1100,18 @@ int ipa2_rx_poll(u32 clnt_hdl, int weight)
break;
ipa_wq_rx_common(ep->sys, iov.size);
- cnt += 5;
+ cnt += IPA_WAN_AGGR_PKT_CNT;
};
- if (cnt == 0) {
+ if (cnt == 0 || cnt < weight) {
ep->inactive_cycles++;
ep->client_notify(ep->priv, IPA_CLIENT_COMP_NAPI, 0);
if (ep->inactive_cycles > 3 || ep->sys->len == 0) {
ep->switch_to_intr = true;
delay = 0;
+ } else if (cnt < weight) {
+ delay = 0;
}
queue_delayed_work(ep->sys->wq,
&ep->sys->switch_to_intr_work, msecs_to_jiffies(delay));
@@ -3176,14 +3179,9 @@ static int ipa_assign_policy_v2(struct ipa_sys_connect_params *in,
sys->repl_hdlr =
ipa_replenish_rx_cache;
}
- if (in->napi_enabled) {
- sys->rx_pool_sz =
- IPA_WAN_NAPI_CONS_RX_POOL_SZ;
- if (in->recycle_enabled) {
- sys->repl_hdlr =
- ipa_replenish_rx_cache_recycle;
- }
- }
+ if (in->napi_enabled && in->recycle_enabled)
+ sys->repl_hdlr =
+ ipa_replenish_rx_cache_recycle;
sys->ep->wakelock_client =
IPA_WAKELOCK_REF_CLIENT_WAN_RX;
in->ipa_ep_cfg.aggr.aggr_sw_eof_active
diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_i.h b/drivers/platform/msm/ipa/ipa_v2/ipa_i.h
index 672c620..cd575fe 100644
--- a/drivers/platform/msm/ipa/ipa_v2/ipa_i.h
+++ b/drivers/platform/msm/ipa/ipa_v2/ipa_i.h
@@ -51,8 +51,6 @@
#define IPA_UC_FINISH_MAX 6
#define IPA_UC_WAIT_MIN_SLEEP 1000
#define IPA_UC_WAII_MAX_SLEEP 1200
-#define IPA_WAN_NAPI_CONS_RX_POOL_SZ (IPA_GENERIC_RX_POOL_SZ*3)
-#define IPA_WAN_CONS_DESC_FIFO_SZ (IPA_SYS_DESC_FIFO_SZ*3)
#define IPA_MAX_STATUS_STAT_NUM 30
diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_utils.c b/drivers/platform/msm/ipa/ipa_v2/ipa_utils.c
index 78d67a5..a50665c 100644
--- a/drivers/platform/msm/ipa/ipa_v2/ipa_utils.c
+++ b/drivers/platform/msm/ipa/ipa_v2/ipa_utils.c
@@ -94,6 +94,7 @@ static const int ep_mapping[3][IPA_CLIENT_MAX] = {
[IPA_1_1][IPA_CLIENT_Q6_LAN_PROD] = 5,
[IPA_1_1][IPA_CLIENT_Q6_WAN_PROD] = -1,
[IPA_1_1][IPA_CLIENT_Q6_CMD_PROD] = -1,
+ [IPA_1_1][IPA_CLIENT_ETHERNET_PROD] = -1,
[IPA_1_1][IPA_CLIENT_HSIC1_CONS] = 14,
[IPA_1_1][IPA_CLIENT_WLAN1_CONS] = -1,
@@ -119,6 +120,7 @@ static const int ep_mapping[3][IPA_CLIENT_MAX] = {
[IPA_1_1][IPA_CLIENT_MHI_CONS] = -1,
[IPA_1_1][IPA_CLIENT_Q6_LAN_CONS] = 4,
[IPA_1_1][IPA_CLIENT_Q6_WAN_CONS] = -1,
+ [IPA_1_1][IPA_CLIENT_ETHERNET_CONS] = -1,
[IPA_2_0][IPA_CLIENT_HSIC1_PROD] = 12,
@@ -148,6 +150,7 @@ static const int ep_mapping[3][IPA_CLIENT_MAX] = {
= 12,
[IPA_2_0][IPA_CLIENT_MEMCPY_DMA_ASYNC_PROD]
= 19,
+ [IPA_2_0][IPA_CLIENT_ETHERNET_PROD] = 12,
/* Only for test purpose */
[IPA_2_0][IPA_CLIENT_TEST_PROD] = 19,
[IPA_2_0][IPA_CLIENT_TEST1_PROD] = 19,
@@ -188,6 +191,7 @@ static const int ep_mapping[3][IPA_CLIENT_MAX] = {
= 16,
[IPA_2_0][IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS]
= 10,
+ [IPA_2_0][IPA_CLIENT_ETHERNET_CONS] = 1,
/* Only for test purpose */
[IPA_2_0][IPA_CLIENT_TEST_CONS] = 1,
[IPA_2_0][IPA_CLIENT_TEST1_CONS] = 1,
@@ -223,6 +227,7 @@ static const int ep_mapping[3][IPA_CLIENT_MAX] = {
= -1,
[IPA_2_6L][IPA_CLIENT_MEMCPY_DMA_ASYNC_PROD]
= -1,
+ [IPA_2_6L][IPA_CLIENT_ETHERNET_PROD] = -1,
/* Only for test purpose */
[IPA_2_6L][IPA_CLIENT_TEST_PROD] = 11,
[IPA_2_6L][IPA_CLIENT_TEST1_PROD] = 11,
@@ -263,6 +268,7 @@ static const int ep_mapping[3][IPA_CLIENT_MAX] = {
= -1,
[IPA_2_6L][IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS]
= -1,
+ [IPA_2_6L][IPA_CLIENT_ETHERNET_CONS] = -1,
/* Only for test purpose */
[IPA_2_6L][IPA_CLIENT_TEST_CONS] = 15,
[IPA_2_6L][IPA_CLIENT_TEST1_CONS] = 15,
@@ -457,6 +463,9 @@ int ipa_get_clients_from_rm_resource(
clients->names[i++] = IPA_CLIENT_ODU_EMB_CONS;
clients->names[i++] = IPA_CLIENT_ODU_TETH_CONS;
break;
+ case IPA_RM_RESOURCE_ETHERNET_CONS:
+ clients->names[i++] = IPA_CLIENT_ETHERNET_CONS;
+ break;
case IPA_RM_RESOURCE_USB_PROD:
clients->names[i++] = IPA_CLIENT_USB_PROD;
break;
@@ -468,6 +477,10 @@ int ipa_get_clients_from_rm_resource(
break;
case IPA_RM_RESOURCE_ODU_ADAPT_PROD:
clients->names[i++] = IPA_CLIENT_ODU_PROD;
+ break;
+ case IPA_RM_RESOURCE_ETHERNET_PROD:
+ clients->names[i++] = IPA_CLIENT_ETHERNET_PROD;
+ break;
default:
break;
}
@@ -507,7 +520,8 @@ bool ipa_should_pipe_be_suspended(enum ipa_client_type client)
client == IPA_CLIENT_WLAN3_CONS ||
client == IPA_CLIENT_WLAN4_CONS ||
client == IPA_CLIENT_ODU_EMB_CONS ||
- client == IPA_CLIENT_ODU_TETH_CONS)
+ client == IPA_CLIENT_ODU_TETH_CONS ||
+ client == IPA_CLIENT_ETHERNET_CONS)
return true;
return false;
@@ -3630,7 +3644,8 @@ int ipa2_write_qmap_id(struct ipa_ioc_write_qmapid *param_in)
meta.qmap_id = param_in->qmap_id;
if (param_in->client == IPA_CLIENT_USB_PROD ||
param_in->client == IPA_CLIENT_HSIC1_PROD ||
- param_in->client == IPA_CLIENT_ODU_PROD) {
+ param_in->client == IPA_CLIENT_ODU_PROD ||
+ param_in->client == IPA_CLIENT_ETHERNET_PROD) {
result = ipa2_cfg_ep_metadata(ipa_ep_idx, &meta);
} else if (param_in->client == IPA_CLIENT_WLAN1_PROD) {
ipa_ctx->ep[ipa_ep_idx].cfg.meta = meta;
diff --git a/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c b/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c
index db732c5..0af9387 100644
--- a/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c
+++ b/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c
@@ -64,6 +64,7 @@
#define IPA_UEVENT_NUM_EVNP 4 /* number of event pointers */
#define NAPI_WEIGHT 60
+#define IPA_WWAN_CONS_DESC_FIFO_SZ 1024
static struct net_device *ipa_netdevs[IPA_WWAN_DEVICE_COUNT];
static struct ipa_sys_connect_params apps_to_ipa_ep_cfg, ipa_to_apps_ep_cfg;
@@ -102,6 +103,7 @@ struct ipa_rmnet_plat_drv_res {
bool ipa_loaduC;
bool ipa_advertise_sg_support;
bool ipa_napi_enable;
+ u32 wan_rx_desc_size;
};
static struct ipa_rmnet_plat_drv_res ipa_rmnet_res;
@@ -1310,10 +1312,8 @@ static int handle_ingress_format(struct net_device *dev,
ipa_to_apps_ep_cfg.priv = dev;
ipa_to_apps_ep_cfg.napi_enabled = ipa_rmnet_res.ipa_napi_enable;
- if (ipa_to_apps_ep_cfg.napi_enabled)
- ipa_to_apps_ep_cfg.desc_fifo_sz = IPA_WAN_CONS_DESC_FIFO_SZ;
- else
- ipa_to_apps_ep_cfg.desc_fifo_sz = IPA_SYS_DESC_FIFO_SZ;
+ ipa_to_apps_ep_cfg.desc_fifo_sz =
+ ipa_rmnet_res.wan_rx_desc_size * sizeof(struct sps_iovec);
mutex_lock(&ipa_to_apps_pipe_handle_guard);
if (atomic_read(&is_ssr)) {
@@ -1944,6 +1944,9 @@ static struct notifier_block ssr_notifier = {
static int get_ipa_rmnet_dts_configuration(struct platform_device *pdev,
struct ipa_rmnet_plat_drv_res *ipa_rmnet_drv_res)
{
+ int result;
+
+ ipa_rmnet_drv_res->wan_rx_desc_size = IPA_WWAN_CONS_DESC_FIFO_SZ;
ipa_rmnet_drv_res->ipa_rmnet_ssr =
of_property_read_bool(pdev->dev.of_node,
"qcom,rmnet-ipa-ssr");
@@ -1966,6 +1969,18 @@ static int get_ipa_rmnet_dts_configuration(struct platform_device *pdev,
"qcom,ipa-napi-enable");
pr_info("IPA Napi Enable = %s\n",
ipa_rmnet_drv_res->ipa_napi_enable ? "True" : "False");
+
+ /* Get IPA WAN RX desc fifo size */
+ result = of_property_read_u32(pdev->dev.of_node,
+ "qcom,wan-rx-desc-size",
+ &ipa_rmnet_drv_res->wan_rx_desc_size);
+ if (result)
+ pr_info("using default for wan-rx-desc-size = %u\n",
+ ipa_rmnet_drv_res->wan_rx_desc_size);
+ else
+ IPAWANDBG(": found ipa_drv_res->wan-rx-desc-size = %u\n",
+ ipa_rmnet_drv_res->wan_rx_desc_size);
+
return 0;
}
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c b/drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c
index 3fb767c..a414029 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_debugfs.c
@@ -1213,12 +1213,7 @@ static ssize_t ipa3_read_ntn(struct file *file, char __user *ubuf,
"TX bamFifoUsageLow=%u\n"
"TX bamUtilCount=%u\n"
"TX num_db=%u\n"
- "TX num_unexpected_db=%u\n"
- "TX num_bam_int_handled=%u\n"
- "TX num_bam_int_in_non_running_state=%u\n"
- "TX num_qmb_int_handled=%u\n"
- "TX num_bam_int_handled_while_wait_for_bam=%u\n"
- "TX num_bam_int_handled_while_not_in_bam=%u\n",
+ "TX num_qmb_int_handled=%u\n",
TX_STATS(num_pkts_processed),
TX_STATS(tail_ptr_val),
TX_STATS(num_db_fired),
@@ -1233,12 +1228,7 @@ static ssize_t ipa3_read_ntn(struct file *file, char __user *ubuf,
TX_STATS(bam_stats.bamFifoUsageLow),
TX_STATS(bam_stats.bamUtilCount),
TX_STATS(num_db),
- TX_STATS(num_unexpected_db),
- TX_STATS(num_bam_int_handled),
- TX_STATS(num_bam_int_in_non_running_state),
- TX_STATS(num_qmb_int_handled),
- TX_STATS(num_bam_int_handled_while_wait_for_bam),
- TX_STATS(num_bam_int_handled_while_not_in_bam));
+ TX_STATS(num_qmb_int_handled));
cnt += nbytes;
nbytes = scnprintf(dbg_buff + cnt, IPA_MAX_MSG_LEN - cnt,
"RX max_outstanding_pkts=%u\n"
@@ -1254,12 +1244,7 @@ static ssize_t ipa3_read_ntn(struct file *file, char __user *ubuf,
"RX bamFifoUsageHigh=%u\n"
"RX bamFifoUsageLow=%u\n"
"RX bamUtilCount=%u\n"
- "RX num_bam_int_handled=%u\n"
- "RX num_db=%u\n"
- "RX num_unexpected_db=%u\n"
- "RX num_pkts_in_dis_uninit_state=%u\n"
- "num_ic_inj_vdev_change=%u\n"
- "num_ic_inj_fw_desc_change=%u\n",
+ "RX num_db=%u\n",
RX_STATS(max_outstanding_pkts),
RX_STATS(num_pkts_processed),
RX_STATS(rx_ring_rp_value),
@@ -1273,12 +1258,7 @@ static ssize_t ipa3_read_ntn(struct file *file, char __user *ubuf,
RX_STATS(bam_stats.bamFifoUsageHigh),
RX_STATS(bam_stats.bamFifoUsageLow),
RX_STATS(bam_stats.bamUtilCount),
- RX_STATS(num_bam_int_handled),
- RX_STATS(num_db),
- RX_STATS(num_unexpected_db),
- RX_STATS(num_pkts_in_dis_uninit_state),
- RX_STATS(num_bam_int_handled_while_not_in_bam),
- RX_STATS(num_bam_int_handled_while_in_bam_state));
+ RX_STATS(num_db));
cnt += nbytes;
} else {
nbytes = scnprintf(dbg_buff, IPA_MAX_MSG_LEN,
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c b/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c
index 3e4bd79..62e68dd 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c
@@ -21,6 +21,7 @@
#include "ipahal/ipahal.h"
#include "ipahal/ipahal_fltrt.h"
+#define IPA_WAN_AGGR_PKT_CNT 5
#define IPA_LAST_DESC_CNT 0xFFFF
#define POLLING_INACTIVITY_RX 40
#define POLLING_MIN_SLEEP_RX 1010
@@ -60,7 +61,6 @@
#define IPA_ODU_RX_POOL_SZ 64
#define IPA_SIZE_DL_CSUM_META_TRAILER 8
-#define IPA_GSI_EVT_RING_LEN 4096
#define IPA_GSI_MAX_CH_LOW_WEIGHT 15
#define IPA_GSI_EVT_RING_INT_MODT (32 * 1) /* 1ms under 32KHz clock */
@@ -73,12 +73,6 @@
#define IPA_TX_SEND_COMPL_NOP_DELAY_NS (2 * 1000 * 1000)
-/*
- * The transport descriptor size was changed to GSI_CHAN_RE_SIZE_16B, but
- * IPA users still use sps_iovec size as FIFO element size.
- */
-#define IPA_FIFO_ELEMENT_SIZE 8
-
static struct sk_buff *ipa3_get_skb_ipa_rx(unsigned int len, gfp_t flags);
static void ipa3_replenish_wlan_rx_cache(struct ipa3_sys_context *sys);
static void ipa3_replenish_rx_cache(struct ipa3_sys_context *sys);
@@ -421,7 +415,6 @@ int ipa3_send(struct ipa3_sys_context *sys,
}
kfree(gsi_xfer_elem_array);
- kfree(gsi_xfer_elem_array);
spin_unlock_bh(&sys->spinlock);
/* set the timer for sending the NOP descriptor */
@@ -2677,8 +2670,7 @@ static void ipa3_free_rx_wrapper(struct ipa3_rx_pkt_wrapper *rk_pkt)
static int ipa3_assign_policy(struct ipa_sys_connect_params *in,
struct ipa3_sys_context *sys)
{
- if (in->client == IPA_CLIENT_APPS_CMD_PROD ||
- in->client == IPA_CLIENT_APPS_WAN_PROD) {
+ if (in->client == IPA_CLIENT_APPS_CMD_PROD) {
sys->policy = IPA_POLICY_INTR_MODE;
sys->use_comm_evt_ring = false;
return 0;
@@ -2743,9 +2735,6 @@ static int ipa3_assign_policy(struct ipa_sys_connect_params *in,
sys->repl_hdlr =
ipa3_replenish_rx_cache;
}
- if (in->napi_enabled)
- sys->rx_pool_sz =
- IPA_WAN_NAPI_CONS_RX_POOL_SZ;
if (in->napi_enabled && in->recycle_enabled)
sys->repl_hdlr =
ipa3_replenish_rx_cache_recycle;
@@ -3442,7 +3431,13 @@ static int ipa_gsi_setup_channel(struct ipa_sys_connect_params *in,
gsi_evt_ring_props.re_size =
GSI_EVT_RING_RE_SIZE_16B;
+ /*
+ * GSI ring length is calculated based on the desc_fifo_sz
+ * which was meant to define the BAM desc fifo. GSI descriptors
+ * are 16B as opposed to 8B for BAM.
+ */
gsi_evt_ring_props.ring_len = 2 * in->desc_fifo_sz;
+
gsi_evt_ring_props.ring_base_vaddr =
dma_alloc_coherent(ipa3_ctx->pdev,
gsi_evt_ring_props.ring_len,
@@ -3687,16 +3682,18 @@ int ipa3_rx_poll(u32 clnt_hdl, int weight)
break;
ipa3_wq_rx_common(ep->sys, mem_info.size);
- cnt += 5;
+ cnt += IPA_WAN_AGGR_PKT_CNT;
};
- if (cnt == 0) {
+ if (cnt == 0 || cnt < weight) {
ep->inactive_cycles++;
ep->client_notify(ep->priv, IPA_CLIENT_COMP_NAPI, 0);
if (ep->inactive_cycles > 3 || ep->sys->len == 0) {
ep->switch_to_intr = true;
delay = 0;
+ } else if (cnt < weight) {
+ delay = 0;
}
queue_delayed_work(ep->sys->wq,
&ep->sys->switch_to_intr_work, msecs_to_jiffies(delay));
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_i.h b/drivers/platform/msm/ipa/ipa_v3/ipa_i.h
index 7419a64..90577c0 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_i.h
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_i.h
@@ -54,8 +54,11 @@
#define IPA_UC_FINISH_MAX 6
#define IPA_UC_WAIT_MIN_SLEEP 1000
#define IPA_UC_WAII_MAX_SLEEP 1200
-#define IPA_WAN_NAPI_CONS_RX_POOL_SZ (IPA_GENERIC_RX_POOL_SZ*3)
-#define IPA_WAN_CONS_DESC_FIFO_SZ (IPA_SYS_DESC_FIFO_SZ*3)
+/*
+ * The transport descriptor size was changed to GSI_CHAN_RE_SIZE_16B, but
+ * IPA users still use sps_iovec size as FIFO element size.
+ */
+#define IPA_FIFO_ELEMENT_SIZE 8
#define IPA_MAX_STATUS_STAT_NUM 30
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_uc_ntn.c b/drivers/platform/msm/ipa/ipa_v3/ipa_uc_ntn.c
index 30243da..ce47623 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_uc_ntn.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_uc_ntn.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -117,12 +117,7 @@ int ipa3_get_ntn_stats(struct Ipa3HwStatsNTNInfoData_t *stats)
TX_STATS(bam_stats.bamFifoUsageLow);
TX_STATS(bam_stats.bamUtilCount);
TX_STATS(num_db);
- TX_STATS(num_unexpected_db);
- TX_STATS(num_bam_int_handled);
- TX_STATS(num_bam_int_in_non_running_state);
TX_STATS(num_qmb_int_handled);
- TX_STATS(num_bam_int_handled_while_wait_for_bam);
- TX_STATS(num_bam_int_handled_while_not_in_bam);
RX_STATS(max_outstanding_pkts);
RX_STATS(num_pkts_processed);
@@ -137,12 +132,7 @@ int ipa3_get_ntn_stats(struct Ipa3HwStatsNTNInfoData_t *stats)
RX_STATS(bam_stats.bamFifoUsageHigh);
RX_STATS(bam_stats.bamFifoUsageLow);
RX_STATS(bam_stats.bamUtilCount);
- RX_STATS(num_bam_int_handled);
RX_STATS(num_db);
- RX_STATS(num_unexpected_db);
- RX_STATS(num_pkts_in_dis_uninit_state);
- RX_STATS(num_bam_int_handled_while_not_in_bam);
- RX_STATS(num_bam_int_handled_while_in_bam_state);
IPA_ACTIVE_CLIENTS_DEC_SIMPLE();
@@ -253,7 +243,8 @@ int ipa3_setup_uc_ntn_pipes(struct ipa_ntn_conn_in_params *in,
ep_dl = &ipa3_ctx->ep[ipa_ep_idx_dl];
if (ep_ul->valid || ep_dl->valid) {
- IPAERR("EP already allocated.\n");
+ IPAERR("EP already allocated ul:%d dl:%d\n",
+ ep_ul->valid, ep_dl->valid);
return -EFAULT;
}
@@ -398,7 +389,7 @@ int ipa3_tear_down_uc_offload_pipes(int ipa_ep_idx_ul,
goto fail;
}
ipa3_delete_dflt_flt_rules(ipa_ep_idx_ul);
- memset(&ipa3_ctx->ep[ipa_ep_idx_dl], 0, sizeof(struct ipa3_ep_context));
+ memset(&ipa3_ctx->ep[ipa_ep_idx_ul], 0, sizeof(struct ipa3_ep_context));
IPADBG("ul client (ep: %d) disconnected\n", ipa_ep_idx_ul);
fail:
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_uc_offload_i.h b/drivers/platform/msm/ipa/ipa_v3/ipa_uc_offload_i.h
index 946fc7e..79f0973 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_uc_offload_i.h
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_uc_offload_i.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -388,18 +388,9 @@ union Ipa3HwNTNErrorEventData_t {
*@num_pkts_processed: Number of packets processed - cumulative
*@rx_ring_rp_value: Read pointer last advertized to the WLAN FW
*
- *@ntn_ch_err_type: Information about the channel error (if
- * available)
*@rx_ind_ring_stats:
*@bam_stats:
- *@num_bam_int_handled: Number of Bam Interrupts handled by FW
*@num_db: Number of times the doorbell was rung
- *@num_unexpected_db: Number of unexpected doorbells
- *@num_pkts_in_dis_uninit_state:
- *@num_bam_int_handled_while_not_in_bam: Number of Bam
- * Interrupts handled by FW
- *@num_bam_int_handled_while_in_bam_state: Number of Bam
- * Interrupts handled by FW
*/
struct NTN3RxInfoData_t {
u32 max_outstanding_pkts;
@@ -407,17 +398,12 @@ struct NTN3RxInfoData_t {
u32 rx_ring_rp_value;
struct IpaHwRingStats_t rx_ind_ring_stats;
struct IpaHwBamStats_t bam_stats;
- u32 num_bam_int_handled;
u32 num_db;
- u32 num_unexpected_db;
- u32 num_pkts_in_dis_uninit_state;
- u32 num_bam_int_handled_while_not_in_bam;
- u32 num_bam_int_handled_while_in_bam_state;
} __packed;
/**
- * struct NTNTxInfoData_t - Structure holding the NTN Tx channel
+ * struct NTN3TxInfoData_t - Structure holding the NTN Tx channel
* Ensure that this is always word aligned
*
*@num_pkts_processed: Number of packets processed - cumulative
@@ -427,27 +413,16 @@ struct NTN3RxInfoData_t {
*@tx_comp_ring_stats:
*@bam_stats:
*@num_db: Number of times the doorbell was rung
- *@num_unexpected_db: Number of unexpected doorbells
- *@num_bam_int_handled: Number of Bam Interrupts handled by FW
- *@num_bam_int_in_non_running_state: Number of Bam interrupts
- * while not in Running state
*@num_qmb_int_handled: Number of QMB interrupts handled
- *@num_bam_int_handled_while_wait_for_bam: Number of times the
- * Imm Cmd is injected due to fw_desc change
*/
-struct NTNTxInfoData_t {
+struct NTN3TxInfoData_t {
u32 num_pkts_processed;
u32 tail_ptr_val;
u32 num_db_fired;
struct IpaHwRingStats_t tx_comp_ring_stats;
struct IpaHwBamStats_t bam_stats;
u32 num_db;
- u32 num_unexpected_db;
- u32 num_bam_int_handled;
- u32 num_bam_int_in_non_running_state;
u32 num_qmb_int_handled;
- u32 num_bam_int_handled_while_wait_for_bam;
- u32 num_bam_int_handled_while_not_in_bam;
} __packed;
@@ -458,7 +433,7 @@ struct NTNTxInfoData_t {
*/
struct Ipa3HwStatsNTNInfoData_t {
struct NTN3RxInfoData_t rx_ch_stats[IPA_UC_MAX_NTN_RX_CHANNELS];
- struct NTNTxInfoData_t tx_ch_stats[IPA_UC_MAX_NTN_TX_CHANNELS];
+ struct NTN3TxInfoData_t tx_ch_stats[IPA_UC_MAX_NTN_TX_CHANNELS];
} __packed;
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c b/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c
index 6cfe25d..bc9f693 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c
@@ -392,6 +392,11 @@ static const struct ipa_ep_configuration ipa3_ep_mapping
IPA_DPS_HPS_SEQ_TYPE_DMA_ONLY,
QMB_MASTER_SELECT_PCIE,
{ 13, 10, 8, 16, IPA_EE_AP } },
+ [IPA_3_0][IPA_CLIENT_ETHERNET_PROD] = {
+ 2, IPA_v3_0_GROUP_UL, true,
+ IPA_DPS_HPS_SEQ_TYPE_2ND_PKT_PROCESS_PASS_NO_DEC_UCP,
+ QMB_MASTER_SELECT_DDR,
+ {2, 0, 8, 16, IPA_EE_UC} },
/* Only for test purpose */
[IPA_3_0][IPA_CLIENT_TEST_PROD] = {
1, IPA_v3_0_GROUP_UL, true,
@@ -517,6 +522,11 @@ static const struct ipa_ep_configuration ipa3_ep_mapping
QMB_MASTER_SELECT_PCIE,
{ 29, 14, 8, 8, IPA_EE_AP } },
[IPA_3_0][IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS] = IPA_CLIENT_NOT_USED,
+ [IPA_3_0][IPA_CLIENT_ETHERNET_CONS] = {
+ 24, IPA_v3_0_GROUP_DL, false,
+ IPA_DPS_HPS_SEQ_TYPE_INVALID,
+ QMB_MASTER_SELECT_DDR,
+ {24, 3, 8, 8, IPA_EE_UC} },
/* Only for test purpose */
[IPA_3_0][IPA_CLIENT_TEST_CONS] = {
26, IPA_v3_0_GROUP_DL, false,
@@ -604,6 +614,7 @@ static const struct ipa_ep_configuration ipa3_ep_mapping
[IPA_3_5][IPA_CLIENT_Q6_DECOMP2_PROD] = IPA_CLIENT_NOT_USED,
[IPA_3_5][IPA_CLIENT_MEMCPY_DMA_SYNC_PROD] = IPA_CLIENT_NOT_USED,
[IPA_3_5][IPA_CLIENT_MEMCPY_DMA_ASYNC_PROD] = IPA_CLIENT_NOT_USED,
+ [IPA_3_5][IPA_CLIENT_ETHERNET_PROD] = IPA_CLIENT_NOT_USED,
/* Only for test purpose */
[IPA_3_5][IPA_CLIENT_TEST_PROD] = {
0, IPA_v3_5_GROUP_UL_DL, true,
@@ -701,6 +712,7 @@ static const struct ipa_ep_configuration ipa3_ep_mapping
[IPA_3_5][IPA_CLIENT_MEMCPY_DMA_SYNC_CONS] = IPA_CLIENT_NOT_USED,
[IPA_3_5][IPA_CLIENT_MEMCPY_DMA_ASYNC_CONS] = IPA_CLIENT_NOT_USED,
[IPA_3_5][IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS] = IPA_CLIENT_NOT_USED,
+ [IPA_3_5][IPA_CLIENT_ETHERNET_CONS] = IPA_CLIENT_NOT_USED,
/* Only for test purpose */
/* MBIM aggregation test pipes should have the same QMB as USB_CONS */
[IPA_3_5][IPA_CLIENT_TEST_CONS] = {
@@ -792,6 +804,7 @@ static const struct ipa_ep_configuration ipa3_ep_mapping
IPA_DPS_HPS_SEQ_TYPE_DMA_ONLY,
QMB_MASTER_SELECT_DDR,
{ 8, 9, 8, 16, IPA_EE_AP } },
+ [IPA_3_5_MHI][IPA_CLIENT_ETHERNET_PROD] = IPA_CLIENT_NOT_USED,
/* Only for test purpose */
[IPA_3_5_MHI][IPA_CLIENT_TEST_PROD] = {
0, IPA_v3_5_MHI_GROUP_DDR, true,
@@ -889,6 +902,7 @@ static const struct ipa_ep_configuration ipa3_ep_mapping
QMB_MASTER_SELECT_PCIE,
{ 19, 13, 8, 8, IPA_EE_AP } },
[IPA_3_5_MHI][IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS] = IPA_CLIENT_NOT_USED,
+ [IPA_3_5_MHI][IPA_CLIENT_ETHERNET_CONS] = IPA_CLIENT_NOT_USED,
/* Only for test purpose */
[IPA_3_5_MHI][IPA_CLIENT_TEST_CONS] = {
15, IPA_v3_5_MHI_GROUP_PCIE, false,
@@ -975,6 +989,7 @@ static const struct ipa_ep_configuration ipa3_ep_mapping
[IPA_3_5_1][IPA_CLIENT_Q6_DECOMP2_PROD] = IPA_CLIENT_NOT_USED,
[IPA_3_5_1][IPA_CLIENT_MEMCPY_DMA_SYNC_PROD] = IPA_CLIENT_NOT_USED,
[IPA_3_5_1][IPA_CLIENT_MEMCPY_DMA_ASYNC_PROD] = IPA_CLIENT_NOT_USED,
+ [IPA_3_5_1][IPA_CLIENT_ETHERNET_PROD] = IPA_CLIENT_NOT_USED,
/* Only for test purpose */
[IPA_3_5_1][IPA_CLIENT_TEST_PROD] = {
0, IPA_v3_5_GROUP_UL_DL, true,
@@ -1068,6 +1083,7 @@ static const struct ipa_ep_configuration ipa3_ep_mapping
[IPA_3_5_1][IPA_CLIENT_MEMCPY_DMA_SYNC_CONS] = IPA_CLIENT_NOT_USED,
[IPA_3_5_1][IPA_CLIENT_MEMCPY_DMA_ASYNC_CONS] = IPA_CLIENT_NOT_USED,
[IPA_3_5_1][IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS] = IPA_CLIENT_NOT_USED,
+ [IPA_3_5_1][IPA_CLIENT_ETHERNET_CONS] = IPA_CLIENT_NOT_USED,
/* Only for test purpose */
[IPA_3_5_1][IPA_CLIENT_TEST_CONS] = {
17, IPA_v3_5_GROUP_UL_DL,
@@ -1231,6 +1247,9 @@ int ipa3_get_clients_from_rm_resource(
clients->names[i++] = IPA_CLIENT_ODU_EMB_CONS;
clients->names[i++] = IPA_CLIENT_ODU_TETH_CONS;
break;
+ case IPA_RM_RESOURCE_ETHERNET_CONS:
+ clients->names[i++] = IPA_CLIENT_ETHERNET_CONS;
+ break;
case IPA_RM_RESOURCE_USB_PROD:
clients->names[i++] = IPA_CLIENT_USB_PROD;
break;
@@ -1242,6 +1261,10 @@ int ipa3_get_clients_from_rm_resource(
break;
case IPA_RM_RESOURCE_ODU_ADAPT_PROD:
clients->names[i++] = IPA_CLIENT_ODU_PROD;
+ break;
+ case IPA_RM_RESOURCE_ETHERNET_PROD:
+ clients->names[i++] = IPA_CLIENT_ETHERNET_PROD;
+ break;
default:
break;
}
@@ -1282,7 +1305,8 @@ bool ipa3_should_pipe_be_suspended(enum ipa_client_type client)
client == IPA_CLIENT_WLAN3_CONS ||
client == IPA_CLIENT_WLAN4_CONS ||
client == IPA_CLIENT_ODU_EMB_CONS ||
- client == IPA_CLIENT_ODU_TETH_CONS)
+ client == IPA_CLIENT_ODU_TETH_CONS ||
+ client == IPA_CLIENT_ETHERNET_CONS)
return true;
return false;
@@ -2742,7 +2766,8 @@ int ipa3_write_qmap_id(struct ipa_ioc_write_qmapid *param_in)
meta.qmap_id = param_in->qmap_id;
if (param_in->client == IPA_CLIENT_USB_PROD ||
param_in->client == IPA_CLIENT_HSIC1_PROD ||
- param_in->client == IPA_CLIENT_ODU_PROD) {
+ param_in->client == IPA_CLIENT_ODU_PROD ||
+ param_in->client == IPA_CLIENT_ETHERNET_PROD) {
result = ipa3_cfg_ep_metadata(ipa_ep_idx, &meta);
} else if (param_in->client == IPA_CLIENT_WLAN1_PROD) {
ipa3_ctx->ep[ipa_ep_idx].cfg.meta = meta;
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipahal/ipahal_reg.c b/drivers/platform/msm/ipa/ipa_v3/ipahal/ipahal_reg.c
index 3c8688e..78fd90b 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipahal/ipahal_reg.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipahal/ipahal_reg.c
@@ -81,6 +81,9 @@ static const char *ipareg_name_to_str[IPA_REG_MAX] = {
__stringify(IPA_QSB_MAX_WRITES),
__stringify(IPA_QSB_MAX_READS),
__stringify(IPA_TX_CFG),
+ __stringify(IPA_IDLE_INDICATION_CFG),
+ __stringify(IPA_DPS_SEQUENCER_FIRST),
+ __stringify(IPA_HPS_SEQUENCER_FIRST),
};
static void ipareg_construct_dummy(enum ipahal_reg_name reg,
diff --git a/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c b/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
index cf9775b..9e04518 100644
--- a/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
+++ b/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
@@ -66,6 +66,7 @@
((rmnet_ipa3_ctx && rmnet_ipa3_ctx->wwan_priv) ? \
rmnet_ipa3_ctx->wwan_priv->net : NULL)
+#define IPA_WWAN_CONS_DESC_FIFO_SZ 256
static int ipa3_wwan_add_ul_flt_rule_to_ipa(void);
static int ipa3_wwan_del_ul_flt_rule_to_ipa(void);
@@ -90,6 +91,7 @@ struct ipa3_rmnet_plat_drv_res {
bool ipa_loaduC;
bool ipa_advertise_sg_support;
bool ipa_napi_enable;
+ u32 wan_rx_desc_size;
};
/**
@@ -1297,7 +1299,7 @@ static int handle3_ingress_format(struct net_device *dev,
ipa_wan_ep_cfg->ipa_ep_cfg.aggr.aggr_pkt_limit =
in->u.ingress_format.agg_count;
- if (ipa_wan_ep_cfg->napi_enabled) {
+ if (ipa3_rmnet_res.ipa_napi_enable) {
ipa_wan_ep_cfg->recycle_enabled = true;
ep_cfg = (struct rmnet_phys_ep_conf_s *)
rcu_dereference(dev->rx_handler_data);
@@ -1325,10 +1327,8 @@ static int handle3_ingress_format(struct net_device *dev,
ipa_wan_ep_cfg->priv = dev;
ipa_wan_ep_cfg->napi_enabled = ipa3_rmnet_res.ipa_napi_enable;
- if (ipa_wan_ep_cfg->napi_enabled)
- ipa_wan_ep_cfg->desc_fifo_sz = IPA_WAN_CONS_DESC_FIFO_SZ;
- else
- ipa_wan_ep_cfg->desc_fifo_sz = IPA_SYS_DESC_FIFO_SZ;
+ ipa_wan_ep_cfg->desc_fifo_sz =
+ ipa3_rmnet_res.wan_rx_desc_size * IPA_FIFO_ELEMENT_SIZE;
mutex_lock(&rmnet_ipa3_ctx->pipe_handle_guard);
@@ -2012,6 +2012,9 @@ static struct notifier_block ipa3_ssr_notifier = {
static int get_ipa_rmnet_dts_configuration(struct platform_device *pdev,
struct ipa3_rmnet_plat_drv_res *ipa_rmnet_drv_res)
{
+ int result;
+
+ ipa_rmnet_drv_res->wan_rx_desc_size = IPA_WWAN_CONS_DESC_FIFO_SZ;
ipa_rmnet_drv_res->ipa_rmnet_ssr =
of_property_read_bool(pdev->dev.of_node,
"qcom,rmnet-ipa-ssr");
@@ -2034,6 +2037,18 @@ static int get_ipa_rmnet_dts_configuration(struct platform_device *pdev,
"qcom,ipa-napi-enable");
pr_info("IPA Napi Enable = %s\n",
ipa_rmnet_drv_res->ipa_napi_enable ? "True" : "False");
+
+ /* Get IPA WAN RX desc fifo size */
+ result = of_property_read_u32(pdev->dev.of_node,
+ "qcom,wan-rx-desc-size",
+ &ipa_rmnet_drv_res->wan_rx_desc_size);
+ if (result)
+ pr_info("using default for wan-rx-desc-size = %u\n",
+ ipa_rmnet_drv_res->wan_rx_desc_size);
+ else
+ IPAWANDBG(": found ipa_drv_res->wan-rx-desc-size = %u\n",
+ ipa_rmnet_drv_res->wan_rx_desc_size);
+
return 0;
}
diff --git a/drivers/scsi/ufs/ufs_quirks.c b/drivers/scsi/ufs/ufs_quirks.c
index b22a4c4..3210d60 100644
--- a/drivers/scsi/ufs/ufs_quirks.c
+++ b/drivers/scsi/ufs/ufs_quirks.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -30,6 +30,20 @@ static struct ufs_card_fix ufs_fixups[] = {
UFS_FIX(UFS_VENDOR_SKHYNIX, UFS_ANY_MODEL,
UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME),
UFS_FIX(UFS_VENDOR_SKHYNIX, UFS_ANY_MODEL, UFS_DEVICE_NO_VCCQ),
+ UFS_FIX(UFS_VENDOR_SKHYNIX, "hB8aL1",
+ UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH),
+ UFS_FIX(UFS_VENDOR_SKHYNIX, "hC8aL1",
+ UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH),
+ UFS_FIX(UFS_VENDOR_SKHYNIX, "hD8aL1",
+ UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH),
+ UFS_FIX(UFS_VENDOR_SKHYNIX, "hC8aM1",
+ UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH),
+ UFS_FIX(UFS_VENDOR_SKHYNIX, "h08aM1",
+ UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH),
+ UFS_FIX(UFS_VENDOR_SKHYNIX, "hC8GL1",
+ UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH),
+ UFS_FIX(UFS_VENDOR_SKHYNIX, "hC8HL1",
+ UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH),
END_FIX
};
diff --git a/drivers/scsi/ufs/ufs_quirks.h b/drivers/scsi/ufs/ufs_quirks.h
index f7182ed..e7a59d4 100644
--- a/drivers/scsi/ufs/ufs_quirks.h
+++ b/drivers/scsi/ufs/ufs_quirks.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -130,6 +130,14 @@ struct ufs_card_fix {
*/
#define UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME (1 << 7)
+/*
+ * Some UFS devices may stop responding after switching from HS-G1 to HS-G3.
+ * Also, it is found that these devices work fine if we do 2 steps switch:
+ * HS-G1 to HS-G2 followed by HS-G2 to HS-G3. Enabling this quirk for such
+ * device would apply this 2 steps gear switch workaround.
+ */
+#define UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH (1 << 8)
+
struct ufs_hba;
void ufs_advertise_fixup_device(struct ufs_hba *hba);
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 8772bcb..7b91717 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -524,7 +524,7 @@ static int ufshcd_reset_device(struct ufs_hba *hba)
/* replace non-printable or non-ASCII characters with spaces */
static inline void ufshcd_remove_non_printable(char *val)
{
- if (!val)
+ if (!val || !*val)
return;
if (*val < 0x20 || *val > 0x7e)
@@ -3670,7 +3670,7 @@ int ufshcd_read_string_desc(struct ufs_hba *hba, int desc_index, u8 *buf,
goto out;
}
- buff_ascii = kmalloc(ascii_len, GFP_KERNEL);
+ buff_ascii = kzalloc(ascii_len, GFP_KERNEL);
if (!buff_ascii) {
dev_err(hba->dev, "%s: Failed allocating %d bytes\n",
__func__, ascii_len);
@@ -4308,15 +4308,25 @@ static int __ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
* mode hence full reinit is required to move link to HS speeds.
*/
if (ret || hba->full_init_linereset) {
+ int err;
+
hba->full_init_linereset = false;
ufshcd_update_error_stats(hba, UFS_ERR_HIBERN8_ENTER);
dev_err(hba->dev, "%s: hibern8 enter failed. ret = %d",
__func__, ret);
/*
- * If link recovery fails then return error so that caller
- * don't retry the hibern8 enter again.
+ * If link recovery fails then return error code (-ENOLINK)
+ * returned ufshcd_link_recovery().
+ * If link recovery succeeds then return -EAGAIN to attempt
+ * hibern8 enter retry again.
*/
- ret = ufshcd_link_recovery(hba);
+ err = ufshcd_link_recovery(hba);
+ if (err) {
+ dev_err(hba->dev, "%s: link recovery failed", __func__);
+ ret = err;
+ } else {
+ ret = -EAGAIN;
+ }
} else {
dev_dbg(hba->dev, "%s: Hibern8 Enter at %lld us", __func__,
ktime_to_us(ktime_get()));
@@ -4333,8 +4343,8 @@ int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
ret = __ufshcd_uic_hibern8_enter(hba);
if (!ret)
goto out;
- /* Unable to recover the link, so no point proceeding */
- if (ret == -ENOLINK)
+ else if (ret != -EAGAIN)
+ /* Unable to recover the link, so no point proceeding */
BUG();
}
out:
@@ -4872,12 +4882,9 @@ static int ufshcd_link_startup(struct ufs_hba *hba)
ret = ufshcd_make_hba_operational(hba);
out:
- if (ret) {
+ if (ret)
dev_err(hba->dev, "link startup failed %d\n", ret);
- ufshcd_print_host_state(hba);
- ufshcd_print_pwr_info(hba);
- ufshcd_print_host_regs(hba);
- }
+
return ret;
}
@@ -9370,6 +9377,32 @@ static int ufshcd_scale_gear(struct ufs_hba *hba, bool scale_up)
if (scale_up) {
memcpy(&new_pwr_info, &hba->clk_scaling.saved_pwr_info.info,
sizeof(struct ufs_pa_layer_attr));
+ /*
+ * Some UFS devices may stop responding after switching from
+ * HS-G1 to HS-G3. Also, it is found that these devices work
+ * fine if we do 2 steps switch: HS-G1 to HS-G2 followed by
+ * HS-G2 to HS-G3. If UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH
+ * quirk is enabled for such devices, this 2 steps gear switch
+ * workaround will be applied.
+ */
+ if ((hba->dev_info.quirks &
+ UFS_DEVICE_QUIRK_HS_G1_TO_HS_G3_SWITCH)
+ && (hba->pwr_info.gear_tx == UFS_HS_G1)
+ && (new_pwr_info.gear_tx == UFS_HS_G3)) {
+ /* scale up to G2 first */
+ new_pwr_info.gear_tx = UFS_HS_G2;
+ new_pwr_info.gear_rx = UFS_HS_G2;
+ ret = ufshcd_change_power_mode(hba, &new_pwr_info);
+ if (ret)
+ goto out;
+
+ /* scale up to G3 now */
+ new_pwr_info.gear_tx = UFS_HS_G3;
+ new_pwr_info.gear_rx = UFS_HS_G3;
+ ret = ufshcd_change_power_mode(hba, &new_pwr_info);
+ if (ret)
+ goto out;
+ }
} else {
memcpy(&new_pwr_info, &hba->pwr_info,
sizeof(struct ufs_pa_layer_attr));
@@ -9389,10 +9422,10 @@ static int ufshcd_scale_gear(struct ufs_hba *hba, bool scale_up)
new_pwr_info.pwr_rx = FASTAUTO_MODE;
}
}
+ ret = ufshcd_change_power_mode(hba, &new_pwr_info);
}
- ret = ufshcd_change_power_mode(hba, &new_pwr_info);
-
+out:
if (ret)
dev_err(hba->dev, "%s: failed err %d, old gear: (tx %d rx %d), new gear: (tx %d rx %d), scale_up = %d",
__func__, ret,
@@ -9455,10 +9488,29 @@ static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up)
goto clk_scaling_unprepare;
}
+ /*
+ * If auto hibern8 is supported then put the link in
+ * hibern8 manually, this is to avoid auto hibern8
+ * racing during clock frequency scaling sequence.
+ */
+ if (ufshcd_is_auto_hibern8_supported(hba)) {
+ ret = ufshcd_uic_hibern8_enter(hba);
+ if (ret)
+ /* link will be bad state so no need to scale_up_gear */
+ return ret;
+ }
+
ret = ufshcd_scale_clks(hba, scale_up);
if (ret)
goto scale_up_gear;
+ if (ufshcd_is_auto_hibern8_supported(hba)) {
+ ret = ufshcd_uic_hibern8_exit(hba);
+ if (ret)
+ /* link will be bad state so no need to scale_up_gear */
+ return ret;
+ }
+
/* scale up the gear after scaling up clocks */
if (scale_up) {
ret = ufshcd_scale_gear(hba, true);
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index cf715e5..0f8d9b6 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -214,6 +214,15 @@
deadlocks. It does not run during the bootup process, so it will
not catch any early lockups.
+config QPNP_PBS
+ tristate "PBS trigger support for QPNP PMIC"
+ depends on SPMI
+ help
+ This driver supports configuring software PBS trigger event through PBS
+ RAM on Qualcomm Technologies, Inc. QPNP PMICs. This module provides
+ the APIs to the client drivers that wants to send the PBS trigger
+ event to the PBS RAM.
+
config QCOM_MEMORY_DUMP_V2
bool "QCOM Memory Dump V2 Support"
help
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 45384668..00a1284 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -3,6 +3,7 @@
obj-$(CONFIG_QCOM_LLCC) += llcc-core.o llcc-slice.o
obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.o
obj-$(CONFIG_QCOM_LLCC_AMON) += llcc-amon.o
+obj-$(CONFIG_QPNP_PBS) += qpnp-pbs.o
obj-$(CONFIG_QCOM_PM) += spm.o
obj-$(CONFIG_QCOM_SMD) += smd.o
obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
diff --git a/drivers/soc/qcom/icnss.c b/drivers/soc/qcom/icnss.c
index 722127d..0b35caa 100644
--- a/drivers/soc/qcom/icnss.c
+++ b/drivers/soc/qcom/icnss.c
@@ -48,6 +48,11 @@
#include <soc/qcom/socinfo.h>
#include <soc/qcom/ramdump.h>
+#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
+#include <net/cnss_prealloc.h>
+#endif
+
+
#include "wlan_firmware_service_v01.h"
#ifdef CONFIG_ICNSS_DEBUG
@@ -62,7 +67,7 @@ module_param(qmi_timeout, ulong, 0600);
#define WLFW_CLIENT_ID 0x4b4e454c
#define MAX_PROP_SIZE 32
#define NUM_LOG_PAGES 10
-#define NUM_REG_LOG_PAGES 4
+#define NUM_LOG_LONG_PAGES 4
#define ICNSS_MAGIC 0x5abc5abc
#define ICNSS_SERVICE_LOCATION_CLIENT_NAME "ICNSS-WLAN"
@@ -77,14 +82,10 @@ module_param(qmi_timeout, ulong, 0600);
ipc_log_string(icnss_ipc_log_context, _x); \
} while (0)
-#ifdef CONFIG_ICNSS_DEBUG
#define icnss_ipc_log_long_string(_x...) do { \
if (icnss_ipc_log_long_context) \
ipc_log_string(icnss_ipc_log_long_context, _x); \
} while (0)
-#else
-#define icnss_ipc_log_long_string(_x...)
-#endif
#define icnss_pr_err(_fmt, ...) do { \
pr_err(_fmt, ##__VA_ARGS__); \
@@ -110,28 +111,25 @@ module_param(qmi_timeout, ulong, 0600);
##__VA_ARGS__); \
} while (0)
-#define icnss_reg_dbg(_fmt, ...) do { \
+#define icnss_pr_vdbg(_fmt, ...) do { \
pr_debug(_fmt, ##__VA_ARGS__); \
- icnss_ipc_log_long_string("REG: " pr_fmt(_fmt), \
+ icnss_ipc_log_long_string("DBG: " pr_fmt(_fmt), \
##__VA_ARGS__); \
} while (0)
#ifdef CONFIG_ICNSS_DEBUG
#define ICNSS_ASSERT(_condition) do { \
if (!(_condition)) { \
- icnss_pr_err("ASSERT at line %d\n", \
- __LINE__); \
+ icnss_pr_err("ASSERT at line %d\n", __LINE__); \
BUG_ON(1); \
} \
} while (0)
+
+bool ignore_qmi_timeout;
+#define ICNSS_QMI_ASSERT() ICNSS_ASSERT(ignore_qmi_timeout)
#else
-#define ICNSS_ASSERT(_condition) do { \
- if (!(_condition)) { \
- icnss_pr_err("ASSERT at line %d\n", \
- __LINE__); \
- WARN_ON(1); \
- } \
- } while (0)
+#define ICNSS_ASSERT(_condition) do { } while (0)
+#define ICNSS_QMI_ASSERT() do { } while (0)
#endif
enum icnss_debug_quirks {
@@ -156,10 +154,7 @@ uint64_t dynamic_feature_mask = QMI_WLFW_FW_REJUVENATE_V01;
module_param(dynamic_feature_mask, ullong, 0600);
void *icnss_ipc_log_context;
-
-#ifdef CONFIG_ICNSS_DEBUG
void *icnss_ipc_log_long_context;
-#endif
#define ICNSS_EVENT_PENDING 2989
@@ -181,6 +176,7 @@ enum icnss_driver_event_type {
struct icnss_event_pd_service_down_data {
bool crashed;
bool fw_rejuvenate;
+ bool wdog_bite;
};
struct icnss_driver_event {
@@ -205,6 +201,7 @@ enum icnss_driver_state {
ICNSS_PD_RESTART,
ICNSS_MSA0_ASSIGNED,
ICNSS_WLFW_EXISTS,
+ ICNSS_WDOG_BITE,
};
struct ce_irq_list {
@@ -212,6 +209,38 @@ struct ce_irq_list {
irqreturn_t (*handler)(int, void *);
};
+struct icnss_vreg_info {
+ struct regulator *reg;
+ const char *name;
+ u32 min_v;
+ u32 max_v;
+ u32 load_ua;
+ unsigned long settle_delay;
+ bool required;
+};
+
+struct icnss_clk_info {
+ struct clk *handle;
+ const char *name;
+ u32 freq;
+ bool required;
+};
+
+static struct icnss_vreg_info icnss_vreg_info[] = {
+ {NULL, "vdd-0.8-cx-mx", 800000, 800000, 0, 0, false},
+ {NULL, "vdd-1.8-xo", 1800000, 1800000, 0, 0, false},
+ {NULL, "vdd-1.3-rfa", 1304000, 1304000, 0, 0, false},
+ {NULL, "vdd-3.3-ch0", 3312000, 3312000, 0, 0, false},
+};
+
+#define ICNSS_VREG_INFO_SIZE ARRAY_SIZE(icnss_vreg_info)
+
+static struct icnss_clk_info icnss_clk_info[] = {
+ {NULL, "cxo_ref_clk_pin", 0, false},
+};
+
+#define ICNSS_CLK_INFO_SIZE ARRAY_SIZE(icnss_clk_info)
+
struct icnss_stats {
struct {
uint32_t posted;
@@ -265,6 +294,7 @@ struct icnss_stats {
uint32_t rejuvenate_ack_req;
uint32_t rejuvenate_ack_resp;
uint32_t rejuvenate_ack_err;
+ uint32_t trigger_recovery;
};
#define MAX_NO_OF_MAC_ADDR 4
@@ -284,6 +314,8 @@ static struct icnss_priv {
struct platform_device *pdev;
struct icnss_driver_ops *ops;
struct ce_irq_list ce_irq_list[ICNSS_MAX_IRQ_REGISTRATIONS];
+ struct icnss_vreg_info vreg_info[ICNSS_VREG_INFO_SIZE];
+ struct icnss_clk_info clk_info[ICNSS_CLK_INFO_SIZE];
u32 ce_irqs[ICNSS_MAX_IRQ_REGISTRATIONS];
phys_addr_t mem_base_pa;
void __iomem *mem_base_va;
@@ -310,8 +342,9 @@ static struct icnss_priv {
u32 pwr_pin_result;
u32 phy_io_pin_result;
u32 rf_pin_result;
+ uint32_t nr_mem_region;
struct icnss_mem_region_info
- icnss_mem_region[QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01];
+ mem_region[QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01];
struct dentry *root_dentry;
spinlock_t on_off_lock;
struct icnss_stats stats;
@@ -334,14 +367,24 @@ static struct icnss_priv {
struct ramdump_device *msa0_dump_dev;
bool is_wlan_mac_set;
struct icnss_wlan_mac_addr wlan_mac_addr;
+ bool bypass_s1_smmu;
} *penv;
+#ifdef CONFIG_ICNSS_DEBUG
+static void icnss_ignore_qmi_timeout(bool ignore)
+{
+ ignore_qmi_timeout = ignore;
+}
+#else
+static void icnss_ignore_qmi_timeout(bool ignore) { }
+#endif
+
static void icnss_pm_stay_awake(struct icnss_priv *priv)
{
if (atomic_inc_return(&priv->pm_count) != 1)
return;
- icnss_pr_dbg("PM stay awake, state: 0x%lx, count: %d\n", priv->state,
+ icnss_pr_vdbg("PM stay awake, state: 0x%lx, count: %d\n", priv->state,
atomic_read(&priv->pm_count));
pm_stay_awake(&priv->pdev->dev);
@@ -358,7 +401,7 @@ static void icnss_pm_relax(struct icnss_priv *priv)
if (r != 0)
return;
- icnss_pr_dbg("PM relax, state: 0x%lx, count: %d\n", priv->state,
+ icnss_pr_vdbg("PM relax, state: 0x%lx, count: %d\n", priv->state,
atomic_read(&priv->pm_count));
pm_relax(&priv->pdev->dev);
@@ -680,41 +723,220 @@ static int icnss_qmi_pin_connect_result_ind(void *msg, unsigned int msg_len)
return ret;
}
+static int icnss_vreg_on(struct icnss_priv *priv)
+{
+ int ret = 0;
+ struct icnss_vreg_info *vreg_info;
+ int i;
+
+ for (i = 0; i < ICNSS_VREG_INFO_SIZE; i++) {
+ vreg_info = &priv->vreg_info[i];
+
+ if (!vreg_info->reg)
+ continue;
+
+ icnss_pr_vdbg("Regulator %s being enabled\n", vreg_info->name);
+
+ ret = regulator_set_voltage(vreg_info->reg, vreg_info->min_v,
+ vreg_info->max_v);
+ if (ret) {
+ icnss_pr_err("Regulator %s, can't set voltage: min_v: %u, max_v: %u, ret: %d\n",
+ vreg_info->name, vreg_info->min_v,
+ vreg_info->max_v, ret);
+ break;
+ }
+
+ if (vreg_info->load_ua) {
+ ret = regulator_set_load(vreg_info->reg,
+ vreg_info->load_ua);
+ if (ret < 0) {
+ icnss_pr_err("Regulator %s, can't set load: %u, ret: %d\n",
+ vreg_info->name,
+ vreg_info->load_ua, ret);
+ break;
+ }
+ }
+
+ ret = regulator_enable(vreg_info->reg);
+ if (ret) {
+ icnss_pr_err("Regulator %s, can't enable: %d\n",
+ vreg_info->name, ret);
+ break;
+ }
+
+ if (vreg_info->settle_delay)
+ udelay(vreg_info->settle_delay);
+ }
+
+ if (!ret)
+ return 0;
+
+ for (; i >= 0; i--) {
+ vreg_info = &priv->vreg_info[i];
+
+ if (!vreg_info->reg)
+ continue;
+
+ regulator_disable(vreg_info->reg);
+ regulator_set_load(vreg_info->reg, 0);
+ regulator_set_voltage(vreg_info->reg, 0, vreg_info->max_v);
+ }
+
+ return ret;
+}
+
+static int icnss_vreg_off(struct icnss_priv *priv)
+{
+ int ret = 0;
+ struct icnss_vreg_info *vreg_info;
+ int i;
+
+ for (i = ICNSS_VREG_INFO_SIZE - 1; i >= 0; i--) {
+ vreg_info = &priv->vreg_info[i];
+
+ if (!vreg_info->reg)
+ continue;
+
+ icnss_pr_vdbg("Regulator %s being disabled\n", vreg_info->name);
+
+ ret = regulator_disable(vreg_info->reg);
+ if (ret)
+ icnss_pr_err("Regulator %s, can't disable: %d\n",
+ vreg_info->name, ret);
+
+ ret = regulator_set_load(vreg_info->reg, 0);
+ if (ret < 0)
+ icnss_pr_err("Regulator %s, can't set load: %d\n",
+ vreg_info->name, ret);
+
+ ret = regulator_set_voltage(vreg_info->reg, 0,
+ vreg_info->max_v);
+ if (ret)
+ icnss_pr_err("Regulator %s, can't set voltage: %d\n",
+ vreg_info->name, ret);
+ }
+
+ return ret;
+}
+
+static int icnss_clk_init(struct icnss_priv *priv)
+{
+ struct icnss_clk_info *clk_info;
+ int i;
+ int ret = 0;
+
+ for (i = 0; i < ICNSS_CLK_INFO_SIZE; i++) {
+ clk_info = &priv->clk_info[i];
+
+ if (!clk_info->handle)
+ continue;
+
+ icnss_pr_vdbg("Clock %s being enabled\n", clk_info->name);
+
+ if (clk_info->freq) {
+ ret = clk_set_rate(clk_info->handle, clk_info->freq);
+
+ if (ret) {
+ icnss_pr_err("Clock %s, can't set frequency: %u, ret: %d\n",
+ clk_info->name, clk_info->freq,
+ ret);
+ break;
+ }
+ }
+
+ ret = clk_prepare_enable(clk_info->handle);
+ if (ret) {
+ icnss_pr_err("Clock %s, can't enable: %d\n",
+ clk_info->name, ret);
+ break;
+ }
+ }
+
+ if (ret == 0)
+ return 0;
+
+ for (; i >= 0; i--) {
+ clk_info = &priv->clk_info[i];
+
+ if (!clk_info->handle)
+ continue;
+
+ clk_disable_unprepare(clk_info->handle);
+ }
+
+ return ret;
+}
+
+static int icnss_clk_deinit(struct icnss_priv *priv)
+{
+ struct icnss_clk_info *clk_info;
+ int i;
+
+ for (i = 0; i < ICNSS_CLK_INFO_SIZE; i++) {
+ clk_info = &priv->clk_info[i];
+
+ if (!clk_info->handle)
+ continue;
+
+ icnss_pr_vdbg("Clock %s being disabled\n", clk_info->name);
+
+ clk_disable_unprepare(clk_info->handle);
+ }
+
+ return 0;
+}
+
static int icnss_hw_power_on(struct icnss_priv *priv)
{
int ret = 0;
- unsigned long flags;
icnss_pr_dbg("HW Power on: state: 0x%lx\n", priv->state);
- spin_lock_irqsave(&priv->on_off_lock, flags);
+ spin_lock(&priv->on_off_lock);
if (test_bit(ICNSS_POWER_ON, &priv->state)) {
- spin_unlock_irqrestore(&priv->on_off_lock, flags);
+ spin_unlock(&priv->on_off_lock);
return ret;
}
set_bit(ICNSS_POWER_ON, &priv->state);
- spin_unlock_irqrestore(&priv->on_off_lock, flags);
+ spin_unlock(&priv->on_off_lock);
+ ret = icnss_vreg_on(priv);
+ if (ret)
+ goto out;
+
+ ret = icnss_clk_init(priv);
+ if (ret)
+ goto vreg_off;
+
+ return ret;
+
+vreg_off:
+ icnss_vreg_off(priv);
+out:
+ clear_bit(ICNSS_POWER_ON, &priv->state);
return ret;
}
static int icnss_hw_power_off(struct icnss_priv *priv)
{
int ret = 0;
- unsigned long flags;
if (test_bit(HW_ALWAYS_ON, &quirks))
return 0;
icnss_pr_dbg("HW Power off: 0x%lx\n", priv->state);
- spin_lock_irqsave(&priv->on_off_lock, flags);
+ spin_lock(&priv->on_off_lock);
if (!test_bit(ICNSS_POWER_ON, &priv->state)) {
- spin_unlock_irqrestore(&priv->on_off_lock, flags);
+ spin_unlock(&priv->on_off_lock);
return ret;
}
clear_bit(ICNSS_POWER_ON, &priv->state);
- spin_unlock_irqrestore(&priv->on_off_lock, flags);
+ spin_unlock(&priv->on_off_lock);
+
+ icnss_clk_deinit(priv);
+
+ ret = icnss_vreg_off(priv);
return ret;
}
@@ -760,7 +982,7 @@ int icnss_power_off(struct device *dev)
}
EXPORT_SYMBOL(icnss_power_off);
-static int icnss_map_msa_permissions(struct icnss_priv *priv, u32 index)
+static int icnss_map_msa_permissions(struct icnss_mem_region_info *mem_region)
{
int ret = 0;
phys_addr_t addr;
@@ -773,10 +995,10 @@ static int icnss_map_msa_permissions(struct icnss_priv *priv, u32 index)
int source_nelems = sizeof(source_vmlist)/sizeof(u32);
int dest_nelems = 0;
- addr = priv->icnss_mem_region[index].reg_addr;
- size = priv->icnss_mem_region[index].size;
+ addr = mem_region->reg_addr;
+ size = mem_region->size;
- if (!priv->icnss_mem_region[index].secure_flag) {
+ if (!mem_region->secure_flag) {
dest_vmids[2] = VMID_WLAN_CE;
dest_nelems = 3;
} else {
@@ -786,19 +1008,20 @@ static int icnss_map_msa_permissions(struct icnss_priv *priv, u32 index)
ret = hyp_assign_phys(addr, size, source_vmlist, source_nelems,
dest_vmids, dest_perms, dest_nelems);
if (ret) {
- icnss_pr_err("Region %u hyp_assign_phys failed IPA=%pa size=%u err=%d\n",
- index, &addr, size, ret);
+ icnss_pr_err("Hyperviser map failed for PA=%pa size=%u err=%d\n",
+ &addr, size, ret);
goto out;
}
- icnss_pr_dbg("Hypervisor map for region %u: source=%x, dest_nelems=%d, dest[0]=%x, dest[1]=%x, dest[2]=%x\n",
- index, source_vmlist[0], dest_nelems,
- dest_vmids[0], dest_vmids[1], dest_vmids[2]);
+
+ icnss_pr_dbg("Hypervisor map for source=%x, dest_nelems=%d, dest[0]=%x, dest[1]=%x, dest[2]=%x\n",
+ source_vmlist[0], dest_nelems, dest_vmids[0],
+ dest_vmids[1], dest_vmids[2]);
out:
return ret;
}
-static int icnss_unmap_msa_permissions(struct icnss_priv *priv, u32 index)
+static int icnss_unmap_msa_permissions(struct icnss_mem_region_info *mem_region)
{
int ret = 0;
phys_addr_t addr;
@@ -809,9 +1032,10 @@ static int icnss_unmap_msa_permissions(struct icnss_priv *priv, u32 index)
int source_nelems = 0;
int dest_nelems = sizeof(dest_vmids)/sizeof(u32);
- addr = priv->icnss_mem_region[index].reg_addr;
- size = priv->icnss_mem_region[index].size;
- if (!priv->icnss_mem_region[index].secure_flag) {
+ addr = mem_region->reg_addr;
+ size = mem_region->size;
+
+ if (!mem_region->secure_flag) {
source_vmlist[2] = VMID_WLAN_CE;
source_nelems = 3;
} else {
@@ -822,14 +1046,13 @@ static int icnss_unmap_msa_permissions(struct icnss_priv *priv, u32 index)
ret = hyp_assign_phys(addr, size, source_vmlist, source_nelems,
dest_vmids, dest_perms, dest_nelems);
if (ret) {
- icnss_pr_err("Region %u hyp_assign_phys failed IPA=%pa size=%u err=%d\n",
- index, &addr, size, ret);
+ icnss_pr_err("Hyperviser unmap failed for PA=%pa size=%u err=%d\n",
+ &addr, size, ret);
goto out;
}
- icnss_pr_dbg("hypervisor unmap for region %u, source_nelems=%d, source[0]=%x, source[1]=%x, source[2]=%x, dest=%x\n",
- index, source_nelems,
- source_vmlist[0], source_vmlist[1], source_vmlist[2],
- dest_vmids[0]);
+ icnss_pr_dbg("Hypervisor unmap for source_nelems=%d, source[0]=%x, source[1]=%x, source[2]=%x, dest=%x\n",
+ source_nelems, source_vmlist[0], source_vmlist[1],
+ source_vmlist[2], dest_vmids[0]);
out:
return ret;
}
@@ -837,34 +1060,37 @@ static int icnss_unmap_msa_permissions(struct icnss_priv *priv, u32 index)
static int icnss_setup_msa_permissions(struct icnss_priv *priv)
{
int ret;
+ int i;
if (test_bit(ICNSS_MSA0_ASSIGNED, &priv->state))
return 0;
- ret = icnss_map_msa_permissions(priv, 0);
- if (ret)
- return ret;
+ for (i = 0; i < priv->nr_mem_region; i++) {
- ret = icnss_map_msa_permissions(priv, 1);
- if (ret)
- goto err_map_msa;
+ ret = icnss_map_msa_permissions(&priv->mem_region[i]);
+ if (ret)
+ goto err_unmap;
+ }
set_bit(ICNSS_MSA0_ASSIGNED, &priv->state);
- return ret;
+ return 0;
-err_map_msa:
- icnss_unmap_msa_permissions(priv, 0);
+err_unmap:
+ for (i--; i >= 0; i--)
+ icnss_unmap_msa_permissions(&priv->mem_region[i]);
return ret;
}
static void icnss_remove_msa_permissions(struct icnss_priv *priv)
{
+ int i;
+
if (!test_bit(ICNSS_MSA0_ASSIGNED, &priv->state))
return;
- icnss_unmap_msa_permissions(priv, 0);
- icnss_unmap_msa_permissions(priv, 1);
+ for (i = 0; i < priv->nr_mem_region; i++)
+ icnss_unmap_msa_permissions(&priv->mem_region[i]);
clear_bit(ICNSS_MSA0_ASSIGNED, &priv->state);
}
@@ -915,7 +1141,7 @@ static int wlfw_msa_mem_info_send_sync_msg(void)
icnss_pr_dbg("Receive mem_region_info_len: %d\n",
resp.mem_region_info_len);
- if (resp.mem_region_info_len > 2) {
+ if (resp.mem_region_info_len > QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01) {
icnss_pr_err("Invalid memory region length received: %d\n",
resp.mem_region_info_len);
ret = -EINVAL;
@@ -923,24 +1149,25 @@ static int wlfw_msa_mem_info_send_sync_msg(void)
}
penv->stats.msa_info_resp++;
+ penv->nr_mem_region = resp.mem_region_info_len;
for (i = 0; i < resp.mem_region_info_len; i++) {
- penv->icnss_mem_region[i].reg_addr =
+ penv->mem_region[i].reg_addr =
resp.mem_region_info[i].region_addr;
- penv->icnss_mem_region[i].size =
+ penv->mem_region[i].size =
resp.mem_region_info[i].size;
- penv->icnss_mem_region[i].secure_flag =
+ penv->mem_region[i].secure_flag =
resp.mem_region_info[i].secure_flag;
icnss_pr_dbg("Memory Region: %d Addr: 0x%llx Size: 0x%x Flag: 0x%08x\n",
- i, penv->icnss_mem_region[i].reg_addr,
- penv->icnss_mem_region[i].size,
- penv->icnss_mem_region[i].secure_flag);
+ i, penv->mem_region[i].reg_addr,
+ penv->mem_region[i].size,
+ penv->mem_region[i].secure_flag);
}
return 0;
out:
penv->stats.msa_info_err++;
- ICNSS_ASSERT(false);
+ ICNSS_QMI_ASSERT();
return ret;
}
@@ -988,7 +1215,7 @@ static int wlfw_msa_ready_send_sync_msg(void)
out:
penv->stats.msa_ready_err++;
- ICNSS_ASSERT(false);
+ ICNSS_QMI_ASSERT();
return ret;
}
@@ -1051,7 +1278,7 @@ static int wlfw_ind_register_send_sync_msg(void)
out:
penv->stats.ind_register_err++;
- ICNSS_ASSERT(false);
+ ICNSS_QMI_ASSERT();
return ret;
}
@@ -1120,7 +1347,7 @@ static int wlfw_cap_send_sync_msg(void)
out:
penv->stats.cap_err++;
- ICNSS_ASSERT(false);
+ ICNSS_QMI_ASSERT();
return ret;
}
@@ -1181,7 +1408,7 @@ static int wlfw_wlan_mode_send_sync_msg(enum wlfw_driver_mode_enum_v01 mode)
out:
penv->stats.mode_req_err++;
- ICNSS_ASSERT(false);
+ ICNSS_QMI_ASSERT();
return ret;
}
@@ -1231,7 +1458,7 @@ static int wlfw_wlan_cfg_send_sync_msg(struct wlfw_wlan_cfg_req_msg_v01 *data)
out:
penv->stats.cfg_req_err++;
- ICNSS_ASSERT(false);
+ ICNSS_QMI_ASSERT();
return ret;
}
@@ -1284,7 +1511,7 @@ static int wlfw_ini_send_sync_msg(uint8_t fw_log_mode)
out:
penv->stats.ini_req_err++;
- ICNSS_ASSERT(false);
+ ICNSS_QMI_ASSERT();
return ret;
}
@@ -1339,7 +1566,7 @@ static int wlfw_athdiag_read_send_sync_msg(struct icnss_priv *priv,
goto out;
}
- if (!resp->data_valid || resp->data_len <= data_len) {
+ if (!resp->data_valid || resp->data_len < data_len) {
icnss_pr_err("Athdiag read data is invalid, data_valid = %u, data_len = %u\n",
resp->data_valid, resp->data_len);
ret = -EINVAL;
@@ -1450,7 +1677,7 @@ static int wlfw_rejuvenate_ack_send_sync_msg(struct icnss_priv *priv)
out:
priv->stats.rejuvenate_ack_err++;
- ICNSS_ASSERT(false);
+ ICNSS_QMI_ASSERT();
return ret;
}
@@ -1524,7 +1751,7 @@ static void icnss_qmi_wlfw_clnt_notify_work(struct work_struct *work)
if (!penv || !penv->wlfw_clnt)
return;
- icnss_pr_dbg("Receiving Event in work queue context\n");
+ icnss_pr_vdbg("Receiving Event in work queue context\n");
do {
} while ((ret = qmi_recv_msg(penv->wlfw_clnt)) == 0);
@@ -1532,13 +1759,13 @@ static void icnss_qmi_wlfw_clnt_notify_work(struct work_struct *work)
if (ret != -ENOMSG)
icnss_pr_err("Error receiving message: %d\n", ret);
- icnss_pr_dbg("Receiving Event completed\n");
+ icnss_pr_vdbg("Receiving Event completed\n");
}
static void icnss_qmi_wlfw_clnt_notify(struct qmi_handle *handle,
enum qmi_event_type event, void *notify_priv)
{
- icnss_pr_dbg("QMI client notify: %d\n", event);
+ icnss_pr_vdbg("QMI client notify: %d\n", event);
if (!penv || !penv->wlfw_clnt)
return;
@@ -1553,11 +1780,29 @@ static void icnss_qmi_wlfw_clnt_notify(struct qmi_handle *handle,
}
}
+static int icnss_call_driver_uevent(struct icnss_priv *priv,
+ enum icnss_uevent uevent, void *data)
+{
+ struct icnss_uevent_data uevent_data;
+
+ if (!priv->ops || !priv->ops->uevent)
+ return 0;
+
+ icnss_pr_dbg("Calling driver uevent state: 0x%lx, uevent: %d\n",
+ priv->state, uevent);
+
+ uevent_data.uevent = uevent;
+ uevent_data.data = data;
+
+ return priv->ops->uevent(&priv->pdev->dev, &uevent_data);
+}
+
static void icnss_qmi_wlfw_clnt_ind(struct qmi_handle *handle,
unsigned int msg_id, void *msg,
unsigned int msg_len, void *ind_cb_priv)
{
struct icnss_event_pd_service_down_data *event_data;
+ struct icnss_uevent_fw_down_data fw_down_data;
if (!penv)
return;
@@ -1582,11 +1827,16 @@ static void icnss_qmi_wlfw_clnt_ind(struct qmi_handle *handle,
case QMI_WLFW_REJUVENATE_IND_V01:
icnss_pr_dbg("Received Rejuvenate Indication msg_id 0x%x, state: 0x%lx\n",
msg_id, penv->state);
+
+ icnss_ignore_qmi_timeout(true);
event_data = kzalloc(sizeof(*event_data), GFP_KERNEL);
if (event_data == NULL)
return;
event_data->crashed = true;
event_data->fw_rejuvenate = true;
+ fw_down_data.crashed = true;
+ icnss_call_driver_uevent(penv, ICNSS_UEVENT_FW_DOWN,
+ &fw_down_data);
icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN,
0, event_data);
break;
@@ -1707,6 +1957,9 @@ static int icnss_call_driver_probe(struct icnss_priv *priv)
if (!priv->ops || !priv->ops->probe)
return 0;
+ if (test_bit(ICNSS_DRIVER_PROBED, &priv->state))
+ return -EINVAL;
+
icnss_pr_dbg("Calling driver probe state: 0x%lx\n", priv->state);
icnss_hw_power_on(priv);
@@ -1715,6 +1968,8 @@ static int icnss_call_driver_probe(struct icnss_priv *priv)
if (ret < 0) {
icnss_pr_err("Driver probe failed: %d, state: 0x%lx\n",
ret, priv->state);
+ wcnss_prealloc_check_memory_leak();
+ wcnss_pre_alloc_reset();
goto out;
}
@@ -1727,17 +1982,39 @@ static int icnss_call_driver_probe(struct icnss_priv *priv)
return ret;
}
+static int icnss_call_driver_shutdown(struct icnss_priv *priv)
+{
+ if (!test_bit(ICNSS_DRIVER_PROBED, &penv->state))
+ goto out;
+
+ if (!priv->ops || !priv->ops->shutdown)
+ goto out;
+
+ icnss_pr_dbg("Calling driver shutdown state: 0x%lx\n", priv->state);
+
+ priv->ops->shutdown(&priv->pdev->dev);
+
+out:
+ return 0;
+}
+
static int icnss_pd_restart_complete(struct icnss_priv *priv)
{
int ret;
- clear_bit(ICNSS_PD_RESTART, &priv->state);
icnss_pm_relax(priv);
+ if (test_bit(ICNSS_WDOG_BITE, &priv->state)) {
+ icnss_call_driver_shutdown(priv);
+ clear_bit(ICNSS_WDOG_BITE, &priv->state);
+ }
+
+ clear_bit(ICNSS_PD_RESTART, &priv->state);
+
if (!priv->ops || !priv->ops->reinit)
goto out;
- if (!test_bit(ICNSS_DRIVER_PROBED, &penv->state))
+ if (!test_bit(ICNSS_DRIVER_PROBED, &priv->state))
goto call_probe;
icnss_pr_dbg("Calling driver reinit state: 0x%lx\n", priv->state);
@@ -1774,6 +2051,8 @@ static int icnss_driver_event_fw_ready_ind(void *data)
set_bit(ICNSS_FW_READY, &penv->state);
+ icnss_call_driver_uevent(penv, ICNSS_UEVENT_FW_READY, NULL);
+
icnss_pr_info("WLAN FW is ready: 0x%lx\n", penv->state);
icnss_hw_power_off(penv);
@@ -1820,6 +2099,8 @@ static int icnss_driver_event_register_driver(void *data)
if (ret) {
icnss_pr_err("Driver probe failed: %d, state: 0x%lx\n",
ret, penv->state);
+ wcnss_prealloc_check_memory_leak();
+ wcnss_pre_alloc_reset();
goto power_off;
}
@@ -1845,6 +2126,8 @@ static int icnss_driver_event_unregister_driver(void *data)
penv->ops->remove(&penv->pdev->dev);
clear_bit(ICNSS_DRIVER_PROBED, &penv->state);
+ wcnss_prealloc_check_memory_leak();
+ wcnss_pre_alloc_reset();
penv->ops = NULL;
@@ -1869,27 +2152,39 @@ static int icnss_call_driver_remove(struct icnss_priv *priv)
penv->ops->remove(&priv->pdev->dev);
clear_bit(ICNSS_DRIVER_PROBED, &priv->state);
+ wcnss_prealloc_check_memory_leak();
+ wcnss_pre_alloc_reset();
+
+ icnss_hw_power_off(penv);
return 0;
}
-static int icnss_call_driver_shutdown(struct icnss_priv *priv)
+static int icnss_fw_crashed(struct icnss_priv *priv,
+ struct icnss_event_pd_service_down_data *event_data)
{
- icnss_pr_dbg("Calling driver shutdown state: 0x%lx\n", priv->state);
+ icnss_pr_dbg("FW crashed, state: 0x%lx, wdog_bite: %d\n",
+ priv->state, event_data->wdog_bite);
set_bit(ICNSS_PD_RESTART, &priv->state);
clear_bit(ICNSS_FW_READY, &priv->state);
icnss_pm_stay_awake(priv);
- if (!test_bit(ICNSS_DRIVER_PROBED, &penv->state))
- return 0;
+ if (test_bit(ICNSS_DRIVER_PROBED, &priv->state))
+ icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_CRASHED, NULL);
- if (!priv->ops || !priv->ops->shutdown)
- return 0;
+ if (event_data->wdog_bite) {
+ set_bit(ICNSS_WDOG_BITE, &priv->state);
+ goto out;
+ }
- priv->ops->shutdown(&priv->pdev->dev);
+ icnss_call_driver_shutdown(priv);
+ if (event_data->fw_rejuvenate)
+ wlfw_rejuvenate_ack_send_sync_msg(priv);
+
+out:
return 0;
}
@@ -1900,7 +2195,7 @@ static int icnss_driver_event_pd_service_down(struct icnss_priv *priv,
struct icnss_event_pd_service_down_data *event_data = data;
if (!test_bit(ICNSS_WLFW_EXISTS, &priv->state))
- return 0;
+ goto out;
if (test_bit(ICNSS_PD_RESTART, &priv->state)) {
icnss_pr_err("PD Down while recovery inprogress, crashed: %d, state: 0x%lx\n",
@@ -1910,18 +2205,15 @@ static int icnss_driver_event_pd_service_down(struct icnss_priv *priv,
}
if (event_data->crashed)
- icnss_call_driver_shutdown(priv);
+ icnss_fw_crashed(priv, event_data);
else
icnss_call_driver_remove(priv);
- if (event_data->fw_rejuvenate)
- wlfw_rejuvenate_ack_send_sync_msg(priv);
-
out:
- ret = icnss_hw_power_off(priv);
-
kfree(data);
+ icnss_ignore_qmi_timeout(false);
+
return ret;
}
@@ -2046,8 +2338,9 @@ static int icnss_modem_notifier_nb(struct notifier_block *nb,
struct notif_data *notif = data;
struct icnss_priv *priv = container_of(nb, struct icnss_priv,
modem_ssr_nb);
+ struct icnss_uevent_fw_down_data fw_down_data;
- icnss_pr_dbg("Modem-Notify: event %lu\n", code);
+ icnss_pr_vdbg("Modem-Notify: event %lu\n", code);
if (code == SUBSYS_AFTER_SHUTDOWN &&
notif->crashed == CRASH_STATUS_ERR_FATAL) {
@@ -2063,7 +2356,10 @@ static int icnss_modem_notifier_nb(struct notifier_block *nb,
if (test_bit(ICNSS_PDR_ENABLED, &priv->state))
return NOTIFY_OK;
- icnss_pr_info("Modem went down, state: %lx\n", priv->state);
+ icnss_pr_info("Modem went down, state: 0x%lx, crashed: %d\n",
+ priv->state, notif->crashed);
+
+ icnss_ignore_qmi_timeout(true);
event_data = kzalloc(sizeof(*event_data), GFP_KERNEL);
@@ -2072,6 +2368,12 @@ static int icnss_modem_notifier_nb(struct notifier_block *nb,
event_data->crashed = notif->crashed;
+ if (notif->crashed == CRASH_STATUS_WDOG_BITE)
+ event_data->wdog_bite = true;
+
+ fw_down_data.crashed = !!notif->crashed;
+ icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_DOWN, &fw_down_data);
+
icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN,
ICNSS_EVENT_SYNC, event_data);
@@ -2135,31 +2437,47 @@ static int icnss_service_notifier_notify(struct notifier_block *nb,
service_notifier_nb);
enum pd_subsys_state *state = data;
struct icnss_event_pd_service_down_data *event_data;
+ struct icnss_uevent_fw_down_data fw_down_data;
- switch (notification) {
- case SERVREG_NOTIF_SERVICE_STATE_DOWN_V01:
- icnss_pr_info("Service down, data: 0x%p, state: 0x%lx\n", data,
- priv->state);
- event_data = kzalloc(sizeof(*event_data), GFP_KERNEL);
+ icnss_pr_dbg("PD service notification: 0x%lx state: 0x%lx\n",
+ notification, priv->state);
- if (event_data == NULL)
- return notifier_from_errno(-ENOMEM);
+ if (notification != SERVREG_NOTIF_SERVICE_STATE_DOWN_V01)
+ goto done;
- if (state == NULL || *state != ROOT_PD_SHUTDOWN)
- event_data->crashed = true;
+ event_data = kzalloc(sizeof(*event_data), GFP_KERNEL);
- icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN,
- ICNSS_EVENT_SYNC, event_data);
- break;
- case SERVREG_NOTIF_SERVICE_STATE_UP_V01:
- icnss_pr_dbg("Service up, state: 0x%lx\n", priv->state);
- break;
- default:
- icnss_pr_dbg("Service state Unknown, notification: 0x%lx, state: 0x%lx\n",
- notification, priv->state);
- return NOTIFY_DONE;
+ if (event_data == NULL)
+ return notifier_from_errno(-ENOMEM);
+
+ if (state == NULL) {
+ event_data->crashed = true;
+ goto event_post;
}
+ icnss_pr_info("PD service down, pd_state: %d, state: 0x%lx\n",
+ *state, priv->state);
+
+ switch (*state) {
+ case ROOT_PD_WDOG_BITE:
+ event_data->crashed = true;
+ event_data->wdog_bite = true;
+ break;
+ case ROOT_PD_SHUTDOWN:
+ break;
+ default:
+ event_data->crashed = true;
+ break;
+ }
+
+event_post:
+ icnss_ignore_qmi_timeout(true);
+
+ fw_down_data.crashed = event_data->crashed;
+ icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_DOWN, &fw_down_data);
+ icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN,
+ ICNSS_EVENT_SYNC, event_data);
+done:
return NOTIFY_OK;
}
@@ -2265,7 +2583,7 @@ static int icnss_pd_restart_enable(struct icnss_priv *priv)
return 0;
out:
- icnss_pr_err("PD restart not enabled: %d\n", ret);
+ icnss_pr_err("Failed to enable PD restart: %d\n", ret);
return ret;
}
@@ -2375,7 +2693,7 @@ int icnss_ce_request_irq(unsigned int ce_id,
goto out;
}
- icnss_pr_dbg("CE request IRQ: %d, state: 0x%lx\n", ce_id, penv->state);
+ icnss_pr_vdbg("CE request IRQ: %d, state: 0x%lx\n", ce_id, penv->state);
if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) {
icnss_pr_err("Invalid CE ID, ce_id: %d\n", ce_id);
@@ -2401,7 +2719,7 @@ int icnss_ce_request_irq(unsigned int ce_id,
irq_entry->irq = irq;
irq_entry->handler = handler;
- icnss_pr_dbg("IRQ requested: %d, ce_id: %d\n", irq, ce_id);
+ icnss_pr_vdbg("IRQ requested: %d, ce_id: %d\n", irq, ce_id);
penv->stats.ce_irqs[ce_id].request++;
out:
@@ -2420,7 +2738,7 @@ int icnss_ce_free_irq(unsigned int ce_id, void *ctx)
goto out;
}
- icnss_pr_dbg("CE free IRQ: %d, state: 0x%lx\n", ce_id, penv->state);
+ icnss_pr_vdbg("CE free IRQ: %d, state: 0x%lx\n", ce_id, penv->state);
if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) {
icnss_pr_err("Invalid CE ID to free, ce_id: %d\n", ce_id);
@@ -2454,7 +2772,7 @@ void icnss_enable_irq(unsigned int ce_id)
return;
}
- icnss_pr_dbg("Enable IRQ: ce_id: %d, state: 0x%lx\n", ce_id,
+ icnss_pr_vdbg("Enable IRQ: ce_id: %d, state: 0x%lx\n", ce_id,
penv->state);
if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) {
@@ -2478,7 +2796,7 @@ void icnss_disable_irq(unsigned int ce_id)
return;
}
- icnss_pr_dbg("Disable IRQ: ce_id: %d, state: 0x%lx\n", ce_id,
+ icnss_pr_vdbg("Disable IRQ: ce_id: %d, state: 0x%lx\n", ce_id,
penv->state);
if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) {
@@ -2878,12 +3196,25 @@ int icnss_trigger_recovery(struct device *dev)
goto out;
}
- if (!priv->service_notifier[0].handle) {
- icnss_pr_err("Invalid handle during recovery\n");
+ if (!test_bit(ICNSS_PDR_ENABLED, &priv->state)) {
+ icnss_pr_err("PD restart not enabled to trigger recovery: state: 0x%lx\n",
+ priv->state);
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (!priv->service_notifier || !priv->service_notifier[0].handle) {
+ icnss_pr_err("Invalid handle during recovery, state: 0x%lx\n",
+ priv->state);
ret = -EINVAL;
goto out;
}
+ WARN_ON(1);
+ icnss_pr_warn("Initiate PD restart at WLAN FW, state: 0x%lx\n",
+ priv->state);
+ priv->stats.trigger_recovery++;
+
/*
* Initiate PDR, required only for the first instance
*/
@@ -2914,13 +3245,15 @@ static int icnss_smmu_init(struct icnss_priv *priv)
goto map_fail;
}
- ret = iommu_domain_set_attr(mapping->domain,
- DOMAIN_ATTR_ATOMIC,
- &atomic_ctx);
- if (ret < 0) {
- icnss_pr_err("Set atomic_ctx attribute failed, err = %d\n",
- ret);
- goto set_attr_fail;
+ if (!priv->bypass_s1_smmu) {
+ ret = iommu_domain_set_attr(mapping->domain,
+ DOMAIN_ATTR_ATOMIC,
+ &atomic_ctx);
+ if (ret < 0) {
+ icnss_pr_err("Set atomic_ctx attribute failed, err = %d\n",
+ ret);
+ goto set_attr_fail;
+ }
}
ret = iommu_domain_set_attr(mapping->domain,
@@ -2959,6 +3292,114 @@ static void icnss_smmu_deinit(struct icnss_priv *priv)
priv->smmu_mapping = NULL;
}
+static int icnss_get_vreg_info(struct device *dev,
+ struct icnss_vreg_info *vreg_info)
+{
+ int ret = 0;
+ char prop_name[MAX_PROP_SIZE];
+ struct regulator *reg;
+ const __be32 *prop;
+ int len = 0;
+ int i;
+
+ reg = devm_regulator_get_optional(dev, vreg_info->name);
+ if (PTR_ERR(reg) == -EPROBE_DEFER) {
+ icnss_pr_err("EPROBE_DEFER for regulator: %s\n",
+ vreg_info->name);
+ ret = PTR_ERR(reg);
+ goto out;
+ }
+
+ if (IS_ERR(reg)) {
+ ret = PTR_ERR(reg);
+
+ if (vreg_info->required) {
+ icnss_pr_err("Regulator %s doesn't exist: %d\n",
+ vreg_info->name, ret);
+ goto out;
+ } else {
+ icnss_pr_dbg("Optional regulator %s doesn't exist: %d\n",
+ vreg_info->name, ret);
+ goto done;
+ }
+ }
+
+ vreg_info->reg = reg;
+
+ snprintf(prop_name, MAX_PROP_SIZE,
+ "qcom,%s-config", vreg_info->name);
+
+ prop = of_get_property(dev->of_node, prop_name, &len);
+
+ icnss_pr_dbg("Got regulator config, prop: %s, len: %d\n",
+ prop_name, len);
+
+ if (!prop || len < (2 * sizeof(__be32))) {
+ icnss_pr_dbg("Property %s %s\n", prop_name,
+ prop ? "invalid format" : "doesn't exist");
+ goto done;
+ }
+
+ for (i = 0; (i * sizeof(__be32)) < len; i++) {
+ switch (i) {
+ case 0:
+ vreg_info->min_v = be32_to_cpup(&prop[0]);
+ break;
+ case 1:
+ vreg_info->max_v = be32_to_cpup(&prop[1]);
+ break;
+ case 2:
+ vreg_info->load_ua = be32_to_cpup(&prop[2]);
+ break;
+ case 3:
+ vreg_info->settle_delay = be32_to_cpup(&prop[3]);
+ break;
+ default:
+ icnss_pr_dbg("Property %s, ignoring value at %d\n",
+ prop_name, i);
+ break;
+ }
+ }
+
+done:
+ icnss_pr_dbg("Regulator: %s, min_v: %u, max_v: %u, load: %u, delay: %lu\n",
+ vreg_info->name, vreg_info->min_v, vreg_info->max_v,
+ vreg_info->load_ua, vreg_info->settle_delay);
+
+ return 0;
+
+out:
+ return ret;
+}
+
+static int icnss_get_clk_info(struct device *dev,
+ struct icnss_clk_info *clk_info)
+{
+ struct clk *handle;
+ int ret = 0;
+
+ handle = devm_clk_get(dev, clk_info->name);
+ if (IS_ERR(handle)) {
+ ret = PTR_ERR(handle);
+ if (clk_info->required) {
+ icnss_pr_err("Clock %s isn't available: %d\n",
+ clk_info->name, ret);
+ goto out;
+ } else {
+ icnss_pr_dbg("Ignoring clock %s: %d\n", clk_info->name,
+ ret);
+ ret = 0;
+ goto out;
+ }
+ }
+
+ icnss_pr_dbg("Clock: %s, freq: %u\n", clk_info->name, clk_info->freq);
+
+ clk_info->handle = handle;
+out:
+ return ret;
+}
+
static int icnss_fw_debug_show(struct seq_file *s, void *data)
{
struct icnss_priv *priv = s->private;
@@ -2969,6 +3410,7 @@ static int icnss_fw_debug_show(struct seq_file *s, void *data)
seq_puts(s, " VAL: 0 (Test mode disable)\n");
seq_puts(s, " VAL: 1 (WLAN FW test)\n");
seq_puts(s, " VAL: 2 (CCPM test)\n");
+ seq_puts(s, " VAL: 3 (Trigger Recovery)\n");
seq_puts(s, "\nCMD: dynamic_feature_mask\n");
seq_puts(s, " VAL: (64 bit feature mask)\n");
@@ -3223,6 +3665,9 @@ static int icnss_stats_show_state(struct seq_file *s, struct icnss_priv *priv)
case ICNSS_WLFW_EXISTS:
seq_puts(s, "WLAN FW EXISTS");
continue;
+ case ICNSS_WDOG_BITE:
+ seq_puts(s, "MODEM WDOG BITE");
+ continue;
}
seq_printf(s, "UNKNOWN-%d", i);
@@ -3321,6 +3766,7 @@ static int icnss_stats_show(struct seq_file *s, void *data)
ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_req);
ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_resp);
ICNSS_STATS_DUMP(s, priv, rejuvenate_ack_err);
+ ICNSS_STATS_DUMP(s, priv, trigger_recovery);
seq_puts(s, "\n<------------------ PM stats ------------------->\n");
ICNSS_STATS_DUMP(s, priv, pm_suspend);
@@ -3666,6 +4112,26 @@ static int icnss_probe(struct platform_device *pdev)
if (ret == -EPROBE_DEFER)
goto out;
+ memcpy(priv->vreg_info, icnss_vreg_info, sizeof(icnss_vreg_info));
+ for (i = 0; i < ICNSS_VREG_INFO_SIZE; i++) {
+ ret = icnss_get_vreg_info(dev, &priv->vreg_info[i]);
+
+ if (ret)
+ goto out;
+ }
+
+ memcpy(priv->clk_info, icnss_clk_info, sizeof(icnss_clk_info));
+ for (i = 0; i < ICNSS_CLK_INFO_SIZE; i++) {
+ ret = icnss_get_clk_info(dev, &priv->clk_info[i]);
+ if (ret)
+ goto out;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "qcom,smmu-s1-bypass"))
+ priv->bypass_s1_smmu = true;
+
+ icnss_pr_dbg("SMMU S1 BYPASS = %d\n", priv->bypass_s1_smmu);
+
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "membase");
if (!res) {
icnss_pr_err("Memory base not found in DT\n");
@@ -3830,7 +4296,7 @@ static int icnss_pm_suspend(struct device *dev)
return -EINVAL;
}
- icnss_pr_dbg("PM Suspend, state: 0x%lx\n", priv->state);
+ icnss_pr_vdbg("PM Suspend, state: 0x%lx\n", priv->state);
if (!priv->ops || !priv->ops->pm_suspend ||
!test_bit(ICNSS_DRIVER_PROBED, &priv->state))
@@ -3859,7 +4325,7 @@ static int icnss_pm_resume(struct device *dev)
return -EINVAL;
}
- icnss_pr_dbg("PM resume, state: 0x%lx\n", priv->state);
+ icnss_pr_vdbg("PM resume, state: 0x%lx\n", priv->state);
if (!priv->ops || !priv->ops->pm_resume ||
!test_bit(ICNSS_DRIVER_PROBED, &priv->state))
@@ -3888,7 +4354,7 @@ static int icnss_pm_suspend_noirq(struct device *dev)
return -EINVAL;
}
- icnss_pr_dbg("PM suspend_noirq, state: 0x%lx\n", priv->state);
+ icnss_pr_vdbg("PM suspend_noirq, state: 0x%lx\n", priv->state);
if (!priv->ops || !priv->ops->suspend_noirq ||
!test_bit(ICNSS_DRIVER_PROBED, &priv->state))
@@ -3917,7 +4383,7 @@ static int icnss_pm_resume_noirq(struct device *dev)
return -EINVAL;
}
- icnss_pr_dbg("PM resume_noirq, state: 0x%lx\n", priv->state);
+ icnss_pr_vdbg("PM resume_noirq, state: 0x%lx\n", priv->state);
if (!priv->ops || !priv->ops->resume_noirq ||
!test_bit(ICNSS_DRIVER_PROBED, &priv->state))
@@ -3961,26 +4427,6 @@ static struct platform_driver icnss_driver = {
},
};
-#ifdef CONFIG_ICNSS_DEBUG
-static void __init icnss_ipc_log_long_context_init(void)
-{
- icnss_ipc_log_long_context = ipc_log_context_create(NUM_REG_LOG_PAGES,
- "icnss_long", 0);
- if (!icnss_ipc_log_long_context)
- icnss_pr_err("Unable to create register log context\n");
-}
-
-static void __exit icnss_ipc_log_long_context_destroy(void)
-{
- ipc_log_context_destroy(icnss_ipc_log_long_context);
- icnss_ipc_log_long_context = NULL;
-}
-#else
-
-static void __init icnss_ipc_log_long_context_init(void) { }
-static void __exit icnss_ipc_log_long_context_destroy(void) { }
-#endif
-
static int __init icnss_initialize(void)
{
icnss_ipc_log_context = ipc_log_context_create(NUM_LOG_PAGES,
@@ -3988,7 +4434,10 @@ static int __init icnss_initialize(void)
if (!icnss_ipc_log_context)
icnss_pr_err("Unable to create log context\n");
- icnss_ipc_log_long_context_init();
+ icnss_ipc_log_long_context = ipc_log_context_create(NUM_LOG_LONG_PAGES,
+ "icnss_long", 0);
+ if (!icnss_ipc_log_long_context)
+ icnss_pr_err("Unable to create log long context\n");
return platform_driver_register(&icnss_driver);
}
@@ -3998,8 +4447,8 @@ static void __exit icnss_exit(void)
platform_driver_unregister(&icnss_driver);
ipc_log_context_destroy(icnss_ipc_log_context);
icnss_ipc_log_context = NULL;
-
- icnss_ipc_log_long_context_destroy();
+ ipc_log_context_destroy(icnss_ipc_log_long_context);
+ icnss_ipc_log_long_context = NULL;
}
diff --git a/drivers/soc/qcom/msm_bus/msm_bus_fabric_rpmh.c b/drivers/soc/qcom/msm_bus/msm_bus_fabric_rpmh.c
index 51e03c3..beb5c2b 100644
--- a/drivers/soc/qcom/msm_bus/msm_bus_fabric_rpmh.c
+++ b/drivers/soc/qcom/msm_bus/msm_bus_fabric_rpmh.c
@@ -1531,21 +1531,22 @@ static int msm_bus_device_probe(struct platform_device *pdev)
goto exit_device_probe;
}
}
- if (pdata->info[i].node_info->is_bcm_dev)
+ if (pdata->info[i].node_info->is_bcm_dev) {
ret = msm_bus_bcm_init(node_dev, &pdata->info[i]);
if (ret) {
MSM_BUS_ERR("%s: Error intializing bcm %d",
__func__, pdata->info[i].node_info->id);
goto exit_device_probe;
}
- if (pdata->info[i].node_info->is_rsc_dev)
+ }
+ if (pdata->info[i].node_info->is_rsc_dev) {
ret = msm_bus_rsc_init(pdev, node_dev, &pdata->info[i]);
if (ret) {
MSM_BUS_ERR("%s: Error intializing rsc %d",
__func__, pdata->info[i].node_info->id);
goto exit_device_probe;
}
-
+ }
}
ret = bus_for_each_dev(&msm_bus_type, NULL, NULL,
diff --git a/drivers/soc/qcom/qpnp-pbs.c b/drivers/soc/qcom/qpnp-pbs.c
new file mode 100644
index 0000000..287c8a2
--- /dev/null
+++ b/drivers/soc/qcom/qpnp-pbs.c
@@ -0,0 +1,361 @@
+/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "PBS: %s: " fmt, __func__
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/qpnp/qpnp-pbs.h>
+
+#define QPNP_PBS_DEV_NAME "qcom,qpnp-pbs"
+
+#define PBS_CLIENT_TRIG_CTL 0x42
+#define PBS_CLIENT_SW_TRIG_BIT BIT(7)
+#define PBS_CLIENT_SCRATCH1 0x50
+#define PBS_CLIENT_SCRATCH2 0x51
+
+static LIST_HEAD(pbs_dev_list);
+static DEFINE_MUTEX(pbs_list_lock);
+
+struct qpnp_pbs {
+ struct platform_device *pdev;
+ struct device *dev;
+ struct device_node *dev_node;
+ struct regmap *regmap;
+ struct mutex pbs_lock;
+ struct list_head link;
+
+ u32 base;
+};
+
+static int qpnp_pbs_read(struct qpnp_pbs *pbs, u32 address,
+ u8 *val, int count)
+{
+ int rc = 0;
+ struct platform_device *pdev = pbs->pdev;
+
+ rc = regmap_bulk_read(pbs->regmap, address, val, count);
+ if (rc)
+ pr_err("Failed to read address=0x%02x sid=0x%02x rc=%d\n",
+ address, to_spmi_device(pdev->dev.parent)->usid, rc);
+
+ return rc;
+}
+
+static int qpnp_pbs_write(struct qpnp_pbs *pbs, u16 address,
+ u8 *val, int count)
+{
+ int rc = 0;
+ struct platform_device *pdev = pbs->pdev;
+
+ rc = regmap_bulk_write(pbs->regmap, address, val, count);
+ if (rc < 0)
+ pr_err("Failed to write address =0x%02x sid=0x%02x rc=%d\n",
+ address, to_spmi_device(pdev->dev.parent)->usid, rc);
+ else
+ pr_debug("Wrote 0x%02X to addr 0x%04x\n", *val, address);
+
+ return rc;
+}
+
+static int qpnp_pbs_masked_write(struct qpnp_pbs *pbs, u16 address,
+ u8 mask, u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(pbs->regmap, address, mask, val);
+ if (rc < 0)
+ pr_err("Failed to write address 0x%04X, rc = %d\n",
+ address, rc);
+ else
+ pr_debug("Wrote 0x%02X to addr 0x%04X\n",
+ val, address);
+
+ return rc;
+}
+
+static struct qpnp_pbs *get_pbs_client_node(struct device_node *dev_node)
+{
+ struct qpnp_pbs *pbs;
+
+ mutex_lock(&pbs_list_lock);
+ list_for_each_entry(pbs, &pbs_dev_list, link) {
+ if (dev_node == pbs->dev_node) {
+ mutex_unlock(&pbs_list_lock);
+ return pbs;
+ }
+ }
+
+ mutex_unlock(&pbs_list_lock);
+ return ERR_PTR(-EINVAL);
+}
+
+static int qpnp_pbs_wait_for_ack(struct qpnp_pbs *pbs, u8 bit_pos)
+{
+ int rc = 0;
+ u16 retries = 2000, dly = 1000;
+ u8 val;
+
+ while (retries--) {
+ rc = qpnp_pbs_read(pbs, pbs->base +
+ PBS_CLIENT_SCRATCH2, &val, 1);
+ if (rc < 0) {
+ pr_err("Failed to read register %x rc = %d\n",
+ PBS_CLIENT_SCRATCH2, rc);
+ return rc;
+ }
+
+ if (val == 0xFF) {
+ /* PBS error - clear SCRATCH2 register */
+ rc = qpnp_pbs_write(pbs, pbs->base +
+ PBS_CLIENT_SCRATCH2, 0, 1);
+ if (rc < 0) {
+ pr_err("Failed to clear register %x rc=%d\n",
+ PBS_CLIENT_SCRATCH2, rc);
+ return rc;
+ }
+
+ pr_err("NACK from PBS for bit %d\n", bit_pos);
+ return -EINVAL;
+ }
+
+ if (val & BIT(bit_pos)) {
+ pr_debug("PBS sequence for bit %d executed!\n",
+ bit_pos);
+ break;
+ }
+
+ usleep_range(dly, dly + 100);
+ }
+
+ if (!retries) {
+ pr_err("Timeout for PBS ACK/NACK for bit %d\n", bit_pos);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/**
+ * qpnp_pbs_trigger_event - Trigger the PBS RAM sequence
+ *
+ * Returns = 0 If the PBS RAM sequence executed successfully.
+ *
+ * Returns < 0 for errors.
+ *
+ * This function is used to trigger the PBS RAM sequence to be
+ * executed by the client driver.
+ *
+ * The PBS trigger sequence involves
+ * 1. setting the PBS sequence bit in PBS_CLIENT_SCRATCH1
+ * 2. Initiating the SW PBS trigger
+ * 3. Checking the equivalent bit in PBS_CLIENT_SCRATCH2 for the
+ * completion of the sequence.
+ * 4. If PBS_CLIENT_SCRATCH2 == 0xFF, the PBS sequence failed to execute
+ */
+int qpnp_pbs_trigger_event(struct device_node *dev_node, u8 bitmap)
+{
+ struct qpnp_pbs *pbs;
+ int rc = 0;
+ u16 bit_pos = 0;
+ u8 val, mask = 0;
+
+ if (!dev_node)
+ return -EINVAL;
+
+ if (!bitmap) {
+ pr_err("Invalid bitmap passed by client\n");
+ return -EINVAL;
+ }
+
+ pbs = get_pbs_client_node(dev_node);
+ if (IS_ERR_OR_NULL(pbs)) {
+ pr_err("Unable to find the PBS dev_node\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&pbs->pbs_lock);
+ rc = qpnp_pbs_read(pbs, pbs->base + PBS_CLIENT_SCRATCH2, &val, 1);
+ if (rc < 0) {
+ pr_err("read register %x failed rc = %d\n",
+ PBS_CLIENT_SCRATCH2, rc);
+ goto out;
+ }
+
+ if (val == 0xFF) {
+ /* PBS error - clear SCRATCH2 register */
+ rc = qpnp_pbs_write(pbs, pbs->base + PBS_CLIENT_SCRATCH2, 0, 1);
+ if (rc < 0) {
+ pr_err("Failed to clear register %x rc=%d\n",
+ PBS_CLIENT_SCRATCH2, rc);
+ goto out;
+ }
+ }
+
+ for (bit_pos = 0; bit_pos < 8; bit_pos++) {
+ if (bitmap & BIT(bit_pos)) {
+ /*
+ * Clear the PBS sequence bit position in
+ * PBS_CLIENT_SCRATCH2 mask register.
+ */
+ rc = qpnp_pbs_masked_write(pbs, pbs->base +
+ PBS_CLIENT_SCRATCH2, BIT(bit_pos), 0);
+ if (rc < 0) {
+ pr_err("Failed to clear %x reg bit rc=%d\n",
+ PBS_CLIENT_SCRATCH2, rc);
+ goto error;
+ }
+
+ /*
+ * Set the PBS sequence bit position in
+ * PBS_CLIENT_SCRATCH1 register.
+ */
+ val = mask = BIT(bit_pos);
+ rc = qpnp_pbs_masked_write(pbs, pbs->base +
+ PBS_CLIENT_SCRATCH1, mask, val);
+ if (rc < 0) {
+ pr_err("Failed to set %x reg bit rc=%d\n",
+ PBS_CLIENT_SCRATCH1, rc);
+ goto error;
+ }
+
+ /* Initiate the SW trigger */
+ val = mask = PBS_CLIENT_SW_TRIG_BIT;
+ rc = qpnp_pbs_masked_write(pbs, pbs->base +
+ PBS_CLIENT_TRIG_CTL, mask, val);
+ if (rc < 0) {
+ pr_err("Failed to write register %x rc=%d\n",
+ PBS_CLIENT_TRIG_CTL, rc);
+ goto error;
+ }
+
+ rc = qpnp_pbs_wait_for_ack(pbs, bit_pos);
+ if (rc < 0) {
+ pr_err("Error during wait_for_ack\n");
+ goto error;
+ }
+
+ /*
+ * Clear the PBS sequence bit position in
+ * PBS_CLIENT_SCRATCH1 register.
+ */
+ rc = qpnp_pbs_masked_write(pbs, pbs->base +
+ PBS_CLIENT_SCRATCH1, BIT(bit_pos), 0);
+ if (rc < 0) {
+ pr_err("Failed to clear %x reg bit rc=%d\n",
+ PBS_CLIENT_SCRATCH1, rc);
+ goto error;
+ }
+
+ /*
+ * Clear the PBS sequence bit position in
+ * PBS_CLIENT_SCRATCH2 mask register.
+ */
+ rc = qpnp_pbs_masked_write(pbs, pbs->base +
+ PBS_CLIENT_SCRATCH2, BIT(bit_pos), 0);
+ if (rc < 0) {
+ pr_err("Failed to clear %x reg bit rc=%d\n",
+ PBS_CLIENT_SCRATCH2, rc);
+ goto error;
+ }
+
+ }
+ }
+
+error:
+ /* Clear all the requested bitmap */
+ rc = qpnp_pbs_masked_write(pbs, pbs->base + PBS_CLIENT_SCRATCH1,
+ bitmap, 0);
+ if (rc < 0)
+ pr_err("Failed to clear %x reg bit rc=%d\n",
+ PBS_CLIENT_SCRATCH1, rc);
+out:
+ mutex_unlock(&pbs->pbs_lock);
+
+ return rc;
+}
+EXPORT_SYMBOL(qpnp_pbs_trigger_event);
+
+static int qpnp_pbs_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ u32 val = 0;
+ struct qpnp_pbs *pbs;
+
+ pbs = devm_kzalloc(&pdev->dev, sizeof(*pbs), GFP_KERNEL);
+ if (!pbs)
+ return -ENOMEM;
+
+ pbs->pdev = pdev;
+ pbs->dev = &pdev->dev;
+ pbs->dev_node = pdev->dev.of_node;
+ pbs->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!pbs->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &val);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ return rc;
+ }
+
+ pbs->base = val;
+ mutex_init(&pbs->pbs_lock);
+
+ dev_set_drvdata(&pdev->dev, pbs);
+
+ mutex_lock(&pbs_list_lock);
+ list_add(&pbs->link, &pbs_dev_list);
+ mutex_unlock(&pbs_list_lock);
+
+ return 0;
+}
+
+static const struct of_device_id qpnp_pbs_match_table[] = {
+ { .compatible = QPNP_PBS_DEV_NAME },
+ {}
+};
+
+static struct platform_driver qpnp_pbs_driver = {
+ .driver = {
+ .name = QPNP_PBS_DEV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = qpnp_pbs_match_table,
+ },
+ .probe = qpnp_pbs_probe,
+};
+
+static int __init qpnp_pbs_init(void)
+{
+ return platform_driver_register(&qpnp_pbs_driver);
+}
+arch_initcall(qpnp_pbs_init);
+
+static void __exit qpnp_pbs_exit(void)
+{
+ return platform_driver_unregister(&qpnp_pbs_driver);
+}
+module_exit(qpnp_pbs_exit);
+
+MODULE_DESCRIPTION("QPNP PBS DRIVER");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" QPNP_PBS_DEV_NAME);
diff --git a/drivers/soc/qcom/secure_buffer.c b/drivers/soc/qcom/secure_buffer.c
index b2627f2..f1e7347 100644
--- a/drivers/soc/qcom/secure_buffer.c
+++ b/drivers/soc/qcom/secure_buffer.c
@@ -365,28 +365,19 @@ int hyp_assign_phys(phys_addr_t addr, u64 size, u32 *source_vm_list,
int source_nelems, int *dest_vmids,
int *dest_perms, int dest_nelems)
{
- struct sg_table *table;
+ struct sg_table table;
int ret;
- table = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
- if (!table)
- return -ENOMEM;
- ret = sg_alloc_table(table, 1, GFP_KERNEL);
+ ret = sg_alloc_table(&table, 1, GFP_KERNEL);
if (ret)
- goto err1;
+ return ret;
- sg_set_page(table->sgl, phys_to_page(addr), size, 0);
+ sg_set_page(table.sgl, phys_to_page(addr), size, 0);
- ret = hyp_assign_table(table, source_vm_list, source_nelems, dest_vmids,
- dest_perms, dest_nelems);
- if (ret)
- goto err2;
+ ret = hyp_assign_table(&table, source_vm_list, source_nelems,
+ dest_vmids, dest_perms, dest_nelems);
- return ret;
-err2:
- sg_free_table(table);
-err1:
- kfree(table);
+ sg_free_table(&table);
return ret;
}
diff --git a/drivers/spi/spi-geni-qcom.c b/drivers/spi/spi-geni-qcom.c
index 4c86197..403c799 100644
--- a/drivers/spi/spi-geni-qcom.c
+++ b/drivers/spi/spi-geni-qcom.c
@@ -119,30 +119,30 @@ static int do_spi_clk_cfg(u32 speed_hz, struct spi_geni_master *mas)
int div = 0;
int idx;
struct se_geni_rsc *rsc = &mas->spi_rsc;
- int ret = 0;
u32 clk_sel = geni_read_reg(mas->base, SE_GENI_CLK_SEL);
u32 m_clk_cfg = geni_read_reg(mas->base, GENI_SER_M_CLK_CFG);
+ int ret;
clk_sel &= ~CLK_SEL_MSK;
m_clk_cfg &= ~CLK_DIV_MSK;
idx = get_sclk(speed_hz, &sclk_freq);
- if (idx < 0) {
- ret = -EINVAL;
- goto spi_clk_cfg_exit;
- }
- div = (sclk_freq / (SPI_OVERSAMPLING / speed_hz));
+ if (idx < 0)
+ return -EINVAL;
+
+ div = ((sclk_freq / SPI_OVERSAMPLING) / speed_hz);
+ if (!div)
+ return -EINVAL;
clk_sel |= (idx & CLK_SEL_MSK);
m_clk_cfg |= ((div << CLK_DIV_SHFT) | SER_CLK_EN);
ret = clk_set_rate(rsc->se_clk, sclk_freq);
if (ret)
- goto spi_clk_cfg_exit;
+ return ret;
geni_write_reg(clk_sel, mas->base, SE_GENI_CLK_SEL);
geni_write_reg(m_clk_cfg, mas->base, GENI_SER_M_CLK_CFG);
-spi_clk_cfg_exit:
- return ret;
+ return 0;
}
static void spi_setup_word_len(struct spi_geni_master *mas, u32 mode,
@@ -195,7 +195,8 @@ static int spi_geni_prepare_message(struct spi_master *spi_mas,
ret = do_spi_clk_cfg(mas->cur_speed_hz, mas);
if (ret) {
- dev_err(&spi_mas->dev, "Err setting clks ret %d\n", ret);
+ dev_err(&spi_mas->dev, "Err setting clks ret(%d) for %d\n",
+ ret, mas->cur_speed_hz);
goto prepare_message_exit;
}
spi_setup_word_len(mas, spi_slv->mode, spi_slv->bits_per_word);
diff --git a/drivers/thermal/msm-tsens.c b/drivers/thermal/msm-tsens.c
index 5b4bb7a..2013e7e 100644
--- a/drivers/thermal/msm-tsens.c
+++ b/drivers/thermal/msm-tsens.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
- * it under the term_tm of the GNU General Public License version 2 and
+ * 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,
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
index be32e5a..473d15a 100644
--- a/drivers/thermal/qcom/Kconfig
+++ b/drivers/thermal/qcom/Kconfig
@@ -9,3 +9,24 @@
thermal zone device via the mode file results in disabling the sensor.
Also able to set threshold temperature for both hot and cold and update
when a threshold is reached.
+
+config MSM_BCL_PERIPHERAL_CTL
+ bool "BCL driver to control the PMIC BCL peripheral"
+ depends on SPMI && THERMAL_OF
+ help
+ Say Y here to enable this BCL PMIC peripheral driver. This driver
+ provides routines to configure and monitor the BCL
+ PMIC peripheral. This driver registers the battery current and
+ voltage sensors with the thermal core framework and can take
+ threshold input and notify the thermal core when the threshold is
+ reached.
+
+config QTI_THERMAL_LIMITS_DCVS
+ bool "QTI LMH DCVS Driver"
+ depends on THERMAL_OF
+ help
+ This enables the driver for Limits Management Hardware - DCVS block
+ for the application processors. The h/w block that is available for
+ each cluster can be used to perform quick thermal mitigations by
+ tracking temperatures of the CPUs and taking thermal action in the
+ hardware without s/w intervention.
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
index 2cc2193..d1a53b0 100644
--- a/drivers/thermal/qcom/Makefile
+++ b/drivers/thermal/qcom/Makefile
@@ -1,2 +1,4 @@
obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-8996.o
+obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o
+obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o
diff --git a/drivers/thermal/qcom/bcl_peripheral.c b/drivers/thermal/qcom/bcl_peripheral.c
new file mode 100644
index 0000000..55ff770
--- /dev/null
+++ b/drivers/thermal/qcom/bcl_peripheral.c
@@ -0,0 +1,787 @@
+/*
+ * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/thermal.h>
+
+#include "../thermal_core.h"
+
+#define BCL_DRIVER_NAME "bcl_peripheral"
+#define BCL_VBAT_INT "bcl-low-vbat"
+#define BCL_VLOW_VBAT_INT "bcl-very-low-vbat"
+#define BCL_CLOW_VBAT_INT "bcl-crit-low-vbat"
+#define BCL_IBAT_INT "bcl-high-ibat"
+#define BCL_VHIGH_IBAT_INT "bcl-very-high-ibat"
+#define BCL_MONITOR_EN 0x46
+#define BCL_VBAT_MIN 0x5C
+#define BCL_IBAT_MAX 0x5D
+#define BCL_MAX_MIN_CLR 0x48
+#define BCL_IBAT_MAX_CLR 3
+#define BCL_VBAT_MIN_CLR 2
+#define BCL_VBAT_ADC_LOW 0x72
+#define BCL_VBAT_COMP_LOW 0x75
+#define BCL_VBAT_COMP_TLOW 0x76
+#define BCL_IBAT_HIGH 0x78
+#define BCL_IBAT_TOO_HIGH 0x79
+#define BCL_LMH_CFG 0xA3
+#define BCL_CFG 0x6A
+#define LMH_INT_POL_HIGH 0x12
+#define LMH_INT_EN 0x15
+#define BCL_VBAT_SCALING 39000
+#define BCL_IBAT_SCALING 80
+#define BCL_LMH_CFG_VAL 0x3
+#define BCL_CFG_VAL 0x81
+#define LMH_INT_VAL 0x7
+#define BCL_READ_RETRY_LIMIT 3
+#define VAL_CP_REG_BUF_LEN 3
+#define VAL_REG_BUF_OFFSET 0
+#define VAL_CP_REG_BUF_OFFSET 2
+#define BCL_STD_VBAT_NR 9
+#define BCL_VBAT_NO_READING 127
+
+enum bcl_dev_type {
+ BCL_HIGH_IBAT,
+ BCL_VHIGH_IBAT,
+ BCL_LOW_VBAT,
+ BCL_VLOW_VBAT,
+ BCL_CLOW_VBAT,
+ BCL_SOC_MONITOR,
+ BCL_TYPE_MAX,
+};
+
+struct bcl_peripheral_data {
+ int irq_num;
+ long int trip_temp;
+ int trip_val;
+ int last_val;
+ struct mutex state_trans_lock;
+ bool irq_enabled;
+ struct thermal_zone_of_device_ops ops;
+ struct thermal_zone_device *tz_dev;
+};
+
+struct bcl_device {
+ struct regmap *regmap;
+ uint16_t fg_bcl_addr;
+ uint16_t fg_lmh_addr;
+ struct notifier_block psy_nb;
+ struct work_struct soc_eval_work;
+ struct bcl_peripheral_data param[BCL_TYPE_MAX];
+};
+
+static struct bcl_device *bcl_perph;
+static int vbat_low[BCL_STD_VBAT_NR] = {
+ 2400, 2500, 2600, 2700, 2800, 2900,
+ 3000, 3100, 3200};
+
+static int bcl_read_multi_register(int16_t reg_offset, uint8_t *data, int len)
+{
+ int ret = 0;
+
+ if (!bcl_perph) {
+ pr_err("BCL device not initialized\n");
+ return -EINVAL;
+ }
+ ret = regmap_bulk_read(bcl_perph->regmap,
+ (bcl_perph->fg_bcl_addr + reg_offset),
+ data, len);
+ if (ret < 0) {
+ pr_err("Error reading register %d. err:%d", reg_offset, ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int bcl_write_general_register(int16_t reg_offset,
+ uint16_t base, uint8_t data)
+{
+ int ret = 0;
+ uint8_t *write_buf = &data;
+
+ if (!bcl_perph) {
+ pr_err("BCL device not initialized\n");
+ return -EINVAL;
+ }
+ ret = regmap_write(bcl_perph->regmap, (base + reg_offset), *write_buf);
+ if (ret < 0) {
+ pr_err("Error reading register %d. err:%d", reg_offset, ret);
+ return ret;
+ }
+ pr_debug("wrote 0x%02x to 0x%04x\n", data, base + reg_offset);
+
+ return ret;
+}
+
+static int bcl_write_register(int16_t reg_offset, uint8_t data)
+{
+ return bcl_write_general_register(reg_offset,
+ bcl_perph->fg_bcl_addr, data);
+}
+
+static void convert_vbat_to_adc_val(int *val)
+{
+ *val = (*val * 1000) / BCL_VBAT_SCALING;
+}
+
+static void convert_adc_to_vbat_val(int *val)
+{
+ *val = *val * BCL_VBAT_SCALING / 1000;
+}
+
+static void convert_ibat_to_adc_val(int *val)
+{
+ *val = *val / BCL_IBAT_SCALING;
+}
+
+static void convert_adc_to_ibat_val(int *val)
+{
+ *val = *val * BCL_IBAT_SCALING;
+}
+
+static int bcl_set_ibat(void *data, int low, int high)
+{
+ int ret = 0, ibat_ua, thresh_value;
+ int8_t val = 0;
+ int16_t addr;
+ struct bcl_peripheral_data *bat_data =
+ (struct bcl_peripheral_data *)data;
+
+ thresh_value = high;
+ if (bat_data->trip_temp == thresh_value)
+ return 0;
+
+ mutex_lock(&bat_data->state_trans_lock);
+ if (bat_data->irq_num && bat_data->irq_enabled) {
+ disable_irq_nosync(bat_data->irq_num);
+ bat_data->irq_enabled = false;
+ }
+ if (thresh_value == INT_MAX) {
+ bat_data->trip_temp = thresh_value;
+ goto set_trip_exit;
+ }
+
+ ibat_ua = thresh_value;
+ convert_ibat_to_adc_val(&thresh_value);
+ val = (int8_t)thresh_value;
+ if (&bcl_perph->param[BCL_HIGH_IBAT] == bat_data) {
+ addr = BCL_IBAT_HIGH;
+ pr_debug("ibat high threshold:%d mA ADC:0x%02x\n",
+ ibat_ua, val);
+ } else if (&bcl_perph->param[BCL_VHIGH_IBAT] == bat_data) {
+ addr = BCL_IBAT_TOO_HIGH;
+ pr_debug("ibat too high threshold:%d mA ADC:0x%02x\n",
+ ibat_ua, val);
+ } else {
+ goto set_trip_exit;
+ }
+ ret = bcl_write_register(addr, val);
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ goto set_trip_exit;
+ }
+ bat_data->trip_temp = ibat_ua;
+
+ if (bat_data->irq_num && !bat_data->irq_enabled) {
+ enable_irq(bat_data->irq_num);
+ bat_data->irq_enabled = true;
+ }
+
+set_trip_exit:
+ mutex_unlock(&bat_data->state_trans_lock);
+
+ return ret;
+}
+
+static int bcl_set_vbat(void *data, int low, int high)
+{
+ int ret = 0, vbat_uv, vbat_idx, thresh_value;
+ int8_t val = 0;
+ struct bcl_peripheral_data *bat_data =
+ (struct bcl_peripheral_data *)data;
+ uint16_t addr;
+
+ thresh_value = low;
+ if (bat_data->trip_temp == thresh_value)
+ return 0;
+
+ mutex_lock(&bat_data->state_trans_lock);
+
+ if (bat_data->irq_num && bat_data->irq_enabled) {
+ disable_irq_nosync(bat_data->irq_num);
+ bat_data->irq_enabled = false;
+ }
+ if (thresh_value == INT_MIN) {
+ bat_data->trip_temp = thresh_value;
+ goto set_trip_exit;
+ }
+ vbat_uv = thresh_value;
+ convert_vbat_to_adc_val(&thresh_value);
+ val = (int8_t)thresh_value;
+ /*
+ * very low and critical low trip can support only standard
+ * trip thresholds
+ */
+ if (&bcl_perph->param[BCL_LOW_VBAT] == bat_data) {
+ addr = BCL_VBAT_ADC_LOW;
+ pr_debug("vbat low threshold:%d mv ADC:0x%02x\n",
+ vbat_uv, val);
+ } else if (&bcl_perph->param[BCL_VLOW_VBAT] == bat_data) {
+ /*
+ * Scan the standard voltage table, sorted in ascending order
+ * and find the closest threshold that is lower or equal to
+ * the requested value. Passive trip supports thresholds
+ * indexed from 1...BCL_STD_VBAT_NR in the voltage table.
+ */
+ for (vbat_idx = 2; vbat_idx < BCL_STD_VBAT_NR;
+ vbat_idx++) {
+ if (vbat_uv > vbat_low[vbat_idx])
+ continue;
+ break;
+ }
+ addr = BCL_VBAT_COMP_LOW;
+ val = vbat_idx - 2;
+ vbat_uv = vbat_low[vbat_idx - 1];
+ pr_debug("vbat too low threshold:%d mv ADC:0x%02x\n",
+ vbat_uv, val);
+ } else if (&bcl_perph->param[BCL_CLOW_VBAT] == bat_data) {
+ /* Hot trip supports thresholds indexed from
+ * 0...BCL_STD_VBAT_NR-1 in the voltage table.
+ */
+ for (vbat_idx = 1; vbat_idx < (BCL_STD_VBAT_NR - 1);
+ vbat_idx++) {
+ if (vbat_uv > vbat_low[vbat_idx])
+ continue;
+ break;
+ }
+ addr = BCL_VBAT_COMP_TLOW;
+ val = vbat_idx - 1;
+ vbat_uv = vbat_low[vbat_idx - 1];
+ pr_debug("vbat critic low threshold:%d mv ADC:0x%02x\n",
+ vbat_uv, val);
+ } else {
+ goto set_trip_exit;
+ }
+
+ ret = bcl_write_register(addr, val);
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ goto set_trip_exit;
+ }
+ bat_data->trip_temp = vbat_uv;
+ if (bat_data->irq_num && !bat_data->irq_enabled) {
+ enable_irq(bat_data->irq_num);
+ bat_data->irq_enabled = true;
+ }
+
+set_trip_exit:
+ mutex_unlock(&bat_data->state_trans_lock);
+ return ret;
+}
+
+static int bcl_clear_vbat_min(void)
+{
+ int ret = 0;
+
+ ret = bcl_write_register(BCL_MAX_MIN_CLR,
+ BIT(BCL_VBAT_MIN_CLR));
+ if (ret)
+ pr_err("Error in clearing vbat min reg. err:%d", ret);
+
+ return ret;
+}
+
+static int bcl_clear_ibat_max(void)
+{
+ int ret = 0;
+
+ ret = bcl_write_register(BCL_MAX_MIN_CLR,
+ BIT(BCL_IBAT_MAX_CLR));
+ if (ret)
+ pr_err("Error in clearing ibat max reg. err:%d", ret);
+
+ return ret;
+}
+
+static int bcl_read_ibat(void *data, int *adc_value)
+{
+ int ret = 0, timeout = 0;
+ int8_t val[VAL_CP_REG_BUF_LEN] = {0};
+ struct bcl_peripheral_data *bat_data =
+ (struct bcl_peripheral_data *)data;
+
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ do {
+ ret = bcl_read_multi_register(BCL_IBAT_MAX, val,
+ VAL_CP_REG_BUF_LEN);
+ if (ret) {
+ pr_err("BCL register read error. err:%d\n", ret);
+ goto bcl_read_exit;
+ }
+ } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
+ && timeout++ < BCL_READ_RETRY_LIMIT);
+ if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
+ ret = -ENODEV;
+ *adc_value = bat_data->last_val;
+ goto bcl_read_exit;
+ }
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ if (*adc_value == 0) {
+ /*
+ * The sensor sometime can read a value 0 if there is
+ * consequtive reads
+ */
+ *adc_value = bat_data->last_val;
+ } else {
+ convert_adc_to_ibat_val(adc_value);
+ bat_data->last_val = *adc_value;
+ }
+ pr_debug("ibat:%d mA\n", bat_data->last_val);
+
+bcl_read_exit:
+ return ret;
+}
+
+static int bcl_read_ibat_and_clear(void *data, int *adc_value)
+{
+ int ret = 0;
+
+ ret = bcl_read_ibat(data, adc_value);
+ if (ret)
+ return ret;
+ return bcl_clear_ibat_max();
+}
+
+static int bcl_read_vbat(void *data, int *adc_value)
+{
+ int ret = 0, timeout = 0;
+ int8_t val[VAL_CP_REG_BUF_LEN] = {0};
+ struct bcl_peripheral_data *bat_data =
+ (struct bcl_peripheral_data *)data;
+
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ do {
+ ret = bcl_read_multi_register(BCL_VBAT_MIN, val,
+ VAL_CP_REG_BUF_LEN);
+ if (ret) {
+ pr_err("BCL register read error. err:%d\n", ret);
+ goto bcl_read_exit;
+ }
+ } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]
+ && timeout++ < BCL_READ_RETRY_LIMIT);
+ if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) {
+ ret = -ENODEV;
+ goto bcl_read_exit;
+ }
+ *adc_value = (int)val[VAL_REG_BUF_OFFSET];
+ if (*adc_value == BCL_VBAT_NO_READING) {
+ *adc_value = bat_data->last_val;
+ } else {
+ convert_adc_to_vbat_val(adc_value);
+ bat_data->last_val = *adc_value;
+ }
+ pr_debug("vbat:%d mv\n", bat_data->last_val);
+
+bcl_read_exit:
+ return ret;
+}
+
+static int bcl_read_vbat_and_clear(void *data, int *adc_value)
+{
+ int ret;
+
+ ret = bcl_read_vbat(data, adc_value);
+ if (ret)
+ return ret;
+ return bcl_clear_vbat_min();
+}
+
+static irqreturn_t bcl_handle_ibat(int irq, void *data)
+{
+ struct bcl_peripheral_data *perph_data =
+ (struct bcl_peripheral_data *)data;
+
+ mutex_lock(&perph_data->state_trans_lock);
+ if (!perph_data->irq_enabled) {
+ WARN_ON(1);
+ disable_irq_nosync(irq);
+ perph_data->irq_enabled = false;
+ goto exit_intr;
+ }
+ mutex_unlock(&perph_data->state_trans_lock);
+ of_thermal_handle_trip(perph_data->tz_dev);
+
+ return IRQ_HANDLED;
+
+exit_intr:
+ mutex_unlock(&perph_data->state_trans_lock);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bcl_handle_vbat(int irq, void *data)
+{
+ struct bcl_peripheral_data *perph_data =
+ (struct bcl_peripheral_data *)data;
+
+ mutex_lock(&perph_data->state_trans_lock);
+ if (!perph_data->irq_enabled) {
+ WARN_ON(1);
+ disable_irq_nosync(irq);
+ perph_data->irq_enabled = false;
+ goto exit_intr;
+ }
+ mutex_unlock(&perph_data->state_trans_lock);
+ of_thermal_handle_trip(perph_data->tz_dev);
+
+ return IRQ_HANDLED;
+
+exit_intr:
+ mutex_unlock(&perph_data->state_trans_lock);
+ return IRQ_HANDLED;
+}
+
+static int bcl_get_devicetree_data(struct platform_device *pdev)
+{
+ int ret = 0;
+ const __be32 *prop = NULL;
+ struct device_node *dev_node = pdev->dev.of_node;
+
+ prop = of_get_address(dev_node, 0, NULL, NULL);
+ if (prop) {
+ bcl_perph->fg_bcl_addr = be32_to_cpu(*prop);
+ pr_debug("fg_user_adc@%04x\n", bcl_perph->fg_bcl_addr);
+ } else {
+ dev_err(&pdev->dev, "No fg_user_adc registers found\n");
+ return -ENODEV;
+ }
+
+ prop = of_get_address(dev_node, 1, NULL, NULL);
+ if (prop) {
+ bcl_perph->fg_lmh_addr = be32_to_cpu(*prop);
+ pr_debug("fg_lmh@%04x\n", bcl_perph->fg_lmh_addr);
+ } else {
+ dev_err(&pdev->dev, "No fg_lmh registers found\n");
+ return -ENODEV;
+ }
+
+ return ret;
+}
+
+static int bcl_set_soc(void *data, int low, int high)
+{
+ struct bcl_peripheral_data *bat_data =
+ (struct bcl_peripheral_data *)data;
+
+ if (low == bat_data->trip_temp)
+ return 0;
+
+ mutex_lock(&bat_data->state_trans_lock);
+ pr_debug("low soc threshold:%d\n", low);
+ bat_data->trip_temp = low;
+ if (low == INT_MIN) {
+ bat_data->irq_enabled = false;
+ goto unlock_and_exit;
+ }
+ bat_data->irq_enabled = true;
+ schedule_work(&bcl_perph->soc_eval_work);
+
+unlock_and_exit:
+ mutex_unlock(&bat_data->state_trans_lock);
+ return 0;
+}
+
+static int bcl_read_soc(void *data, int *val)
+{
+ static struct power_supply *batt_psy;
+ union power_supply_propval ret = {0,};
+ int err = 0;
+
+ *val = 100;
+ if (!batt_psy)
+ batt_psy = power_supply_get_by_name("battery");
+ if (batt_psy) {
+ err = power_supply_get_property(batt_psy,
+ POWER_SUPPLY_PROP_CAPACITY, &ret);
+ if (err) {
+ pr_err("battery percentage read error:%d\n",
+ err);
+ return err;
+ }
+ *val = ret.intval;
+ }
+ pr_debug("soc:%d\n", *val);
+
+ return err;
+}
+
+static void bcl_evaluate_soc(struct work_struct *work)
+{
+ int battery_percentage;
+ struct bcl_peripheral_data *perph_data =
+ &bcl_perph->param[BCL_SOC_MONITOR];
+
+ if (bcl_read_soc((void *)perph_data, &battery_percentage))
+ return;
+
+ mutex_lock(&perph_data->state_trans_lock);
+ if (!perph_data->irq_enabled)
+ goto eval_exit;
+ if (battery_percentage > perph_data->trip_temp)
+ goto eval_exit;
+
+ perph_data->trip_val = battery_percentage;
+ mutex_unlock(&perph_data->state_trans_lock);
+ of_thermal_handle_trip(perph_data->tz_dev);
+
+ return;
+eval_exit:
+ mutex_unlock(&perph_data->state_trans_lock);
+}
+
+static int battery_supply_callback(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct power_supply *psy = data;
+
+ if (strcmp(psy->desc->name, "battery"))
+ return NOTIFY_OK;
+ schedule_work(&bcl_perph->soc_eval_work);
+
+ return NOTIFY_OK;
+}
+
+static void bcl_fetch_trip(struct platform_device *pdev, const char *int_name,
+ struct bcl_peripheral_data *data,
+ irqreturn_t (*handle)(int, void *))
+{
+ int ret = 0, irq_num = 0;
+
+ /*
+ * Allow flexibility for the HLOS to set the trip temperature for
+ * all the thresholds but handle the interrupt for only one vbat
+ * and ibat interrupt. The LMH-DCVSh will handle and mitigate for the
+ * rest of the ibat/vbat interrupts.
+ */
+ if (!handle) {
+ mutex_lock(&data->state_trans_lock);
+ data->irq_num = 0;
+ data->irq_enabled = false;
+ mutex_unlock(&data->state_trans_lock);
+ return;
+ }
+
+ irq_num = platform_get_irq_byname(pdev, int_name);
+ if (irq_num) {
+ mutex_lock(&data->state_trans_lock);
+ ret = devm_request_threaded_irq(&pdev->dev,
+ irq_num, NULL, handle,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ int_name, data);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Error requesting trip irq. err:%d",
+ ret);
+ mutex_unlock(&data->state_trans_lock);
+ return;
+ }
+ disable_irq_nosync(irq_num);
+ data->irq_num = irq_num;
+ data->irq_enabled = false;
+ mutex_unlock(&data->state_trans_lock);
+ }
+}
+
+static void bcl_probe_soc(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct bcl_peripheral_data *soc_data;
+
+ soc_data = &bcl_perph->param[BCL_SOC_MONITOR];
+ mutex_init(&soc_data->state_trans_lock);
+ soc_data->ops.get_temp = bcl_read_soc;
+ soc_data->ops.set_trips = bcl_set_soc;
+ INIT_WORK(&bcl_perph->soc_eval_work, bcl_evaluate_soc);
+ bcl_perph->psy_nb.notifier_call = battery_supply_callback;
+ ret = power_supply_reg_notifier(&bcl_perph->psy_nb);
+ if (ret < 0) {
+ pr_err("Unable to register soc notifier. err:%d\n", ret);
+ return;
+ }
+ soc_data->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
+ BCL_SOC_MONITOR, soc_data, &soc_data->ops);
+ if (IS_ERR(soc_data->tz_dev)) {
+ pr_err("vbat register failed. err:%ld\n",
+ PTR_ERR(soc_data->tz_dev));
+ return;
+ }
+ thermal_zone_device_update(soc_data->tz_dev, THERMAL_DEVICE_UP);
+ schedule_work(&bcl_perph->soc_eval_work);
+}
+
+static void bcl_vbat_init(struct platform_device *pdev,
+ struct bcl_peripheral_data *vbat, enum bcl_dev_type type)
+{
+ mutex_init(&vbat->state_trans_lock);
+ switch (type) {
+ case BCL_LOW_VBAT:
+ bcl_fetch_trip(pdev, BCL_VBAT_INT, vbat, bcl_handle_vbat);
+ break;
+ case BCL_VLOW_VBAT:
+ bcl_fetch_trip(pdev, BCL_VLOW_VBAT_INT, vbat, NULL);
+ break;
+ case BCL_CLOW_VBAT:
+ bcl_fetch_trip(pdev, BCL_CLOW_VBAT_INT, vbat, NULL);
+ break;
+ default:
+ return;
+ }
+ vbat->ops.get_temp = bcl_read_vbat_and_clear;
+ vbat->ops.set_trips = bcl_set_vbat;
+ vbat->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
+ type, vbat, &vbat->ops);
+ if (IS_ERR(vbat->tz_dev)) {
+ pr_err("vbat register failed. err:%ld\n",
+ PTR_ERR(vbat->tz_dev));
+ return;
+ }
+ thermal_zone_device_update(vbat->tz_dev, THERMAL_DEVICE_UP);
+}
+
+static void bcl_probe_vbat(struct platform_device *pdev)
+{
+ bcl_vbat_init(pdev, &bcl_perph->param[BCL_LOW_VBAT], BCL_LOW_VBAT);
+ bcl_vbat_init(pdev, &bcl_perph->param[BCL_VLOW_VBAT], BCL_VLOW_VBAT);
+ bcl_vbat_init(pdev, &bcl_perph->param[BCL_CLOW_VBAT], BCL_CLOW_VBAT);
+}
+
+static void bcl_ibat_init(struct platform_device *pdev,
+ struct bcl_peripheral_data *ibat, enum bcl_dev_type type)
+{
+ mutex_init(&ibat->state_trans_lock);
+ if (type == BCL_HIGH_IBAT)
+ bcl_fetch_trip(pdev, BCL_IBAT_INT, ibat, bcl_handle_ibat);
+ else
+ bcl_fetch_trip(pdev, BCL_VHIGH_IBAT_INT, ibat, NULL);
+ ibat->ops.get_temp = bcl_read_ibat_and_clear;
+ ibat->ops.set_trips = bcl_set_ibat;
+ ibat->tz_dev = thermal_zone_of_sensor_register(&pdev->dev,
+ type, ibat, &ibat->ops);
+ if (IS_ERR(ibat->tz_dev)) {
+ pr_err("ibat register failed. err:%ld\n",
+ PTR_ERR(ibat->tz_dev));
+ return;
+ }
+ thermal_zone_device_update(ibat->tz_dev, THERMAL_DEVICE_UP);
+}
+
+static void bcl_probe_ibat(struct platform_device *pdev)
+{
+ bcl_ibat_init(pdev, &bcl_perph->param[BCL_HIGH_IBAT], BCL_HIGH_IBAT);
+ bcl_ibat_init(pdev, &bcl_perph->param[BCL_VHIGH_IBAT], BCL_VHIGH_IBAT);
+}
+
+static void bcl_configure_lmh_peripheral(void)
+{
+ bcl_write_register(BCL_LMH_CFG, BCL_LMH_CFG_VAL);
+ bcl_write_register(BCL_CFG, BCL_CFG_VAL);
+ bcl_write_general_register(LMH_INT_POL_HIGH,
+ bcl_perph->fg_lmh_addr, LMH_INT_VAL);
+ bcl_write_general_register(LMH_INT_EN,
+ bcl_perph->fg_lmh_addr, LMH_INT_VAL);
+}
+
+static int bcl_remove(struct platform_device *pdev)
+{
+ int i = 0;
+
+ for (; i < BCL_TYPE_MAX; i++) {
+ if (!bcl_perph->param[i].tz_dev)
+ continue;
+ if (i == BCL_SOC_MONITOR) {
+ power_supply_unreg_notifier(&bcl_perph->psy_nb);
+ flush_work(&bcl_perph->soc_eval_work);
+ }
+ thermal_zone_of_sensor_unregister(&pdev->dev,
+ bcl_perph->param[i].tz_dev);
+ }
+ bcl_perph = NULL;
+
+ return 0;
+}
+
+static int bcl_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ bcl_perph = devm_kzalloc(&pdev->dev, sizeof(*bcl_perph), GFP_KERNEL);
+ if (!bcl_perph)
+ return -ENOMEM;
+
+ bcl_perph->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!bcl_perph->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ bcl_get_devicetree_data(pdev);
+ bcl_probe_ibat(pdev);
+ bcl_probe_vbat(pdev);
+ bcl_probe_soc(pdev);
+ bcl_configure_lmh_peripheral();
+
+ dev_set_drvdata(&pdev->dev, bcl_perph);
+ ret = bcl_write_register(BCL_MONITOR_EN, BIT(7));
+ if (ret) {
+ pr_err("Error accessing BCL peripheral. err:%d\n", ret);
+ goto bcl_probe_exit;
+ }
+
+ return 0;
+
+bcl_probe_exit:
+ bcl_remove(pdev);
+ return ret;
+}
+
+static const struct of_device_id bcl_match[] = {
+ {
+ .compatible = "qcom,msm-bcl-lmh",
+ },
+ {},
+};
+
+static struct platform_driver bcl_driver = {
+ .probe = bcl_probe,
+ .remove = bcl_remove,
+ .driver = {
+ .name = BCL_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = bcl_match,
+ },
+};
+
+builtin_platform_driver(bcl_driver);
diff --git a/drivers/thermal/qcom/msm_lmh_dcvs.c b/drivers/thermal/qcom/msm_lmh_dcvs.c
new file mode 100644
index 0000000..c93d650
--- /dev/null
+++ b/drivers/thermal/qcom/msm_lmh_dcvs.c
@@ -0,0 +1,518 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/pm_opp.h>
+#include <linux/cpu_cooling.h>
+#include <linux/atomic.h>
+
+#include <asm/smp_plat.h>
+#include <asm/cacheflush.h>
+
+#include <soc/qcom/scm.h>
+
+#include "../thermal_core.h"
+
+#define LIMITS_DCVSH 0x10
+#define LIMITS_PROFILE_CHANGE 0x01
+#define LIMITS_NODE_DCVS 0x44435653
+
+#define LIMITS_SUB_FN_THERMAL 0x54484D4C
+#define LIMITS_SUB_FN_CRNT 0x43524E54
+#define LIMITS_SUB_FN_REL 0x52454C00
+#define LIMITS_SUB_FN_BCL 0x42434C00
+#define LIMITS_SUB_FN_GENERAL 0x47454E00
+
+#define LIMITS_ALGO_MODE_ENABLE 0x454E424C
+
+#define LIMITS_HI_THRESHOLD 0x48494748
+#define LIMITS_LOW_THRESHOLD 0x4C4F5700
+#define LIMITS_ARM_THRESHOLD 0x41524D00
+
+#define LIMITS_CLUSTER_0 0x6370302D
+#define LIMITS_CLUSTER_1 0x6370312D
+
+#define LIMITS_DOMAIN_MAX 0x444D4158
+#define LIMITS_DOMAIN_MIN 0x444D494E
+
+#define LIMITS_TEMP_DEFAULT 75000
+#define LIMITS_LOW_THRESHOLD_OFFSET 500
+#define LIMITS_POLLING_DELAY_MS 10
+#define LIMITS_CLUSTER_0_REQ 0x179C1B04
+#define LIMITS_CLUSTER_1_REQ 0x179C3B04
+#define LIMITS_CLUSTER_0_INT_CLR 0x179CE808
+#define LIMITS_CLUSTER_1_INT_CLR 0x179CC808
+#define LIMITS_CLUSTER_0_MIN_FREQ 0x17D78BC0
+#define LIMITS_CLUSTER_1_MIN_FREQ 0x17D70BC0
+#define dcvsh_get_frequency(_val, _max) do { \
+ _max = (_val) & 0x3FF; \
+ _max *= 19200; \
+} while (0)
+#define FREQ_KHZ_TO_HZ(_val) ((_val) * 1000)
+#define FREQ_HZ_TO_KHZ(_val) ((_val) / 1000)
+
+enum lmh_hw_trips {
+ LIMITS_TRIP_ARM,
+ LIMITS_TRIP_HI,
+ LIMITS_TRIP_MAX,
+};
+
+struct limits_dcvs_hw {
+ char sensor_name[THERMAL_NAME_LENGTH];
+ uint32_t affinity;
+ uint32_t temp_limits[LIMITS_TRIP_MAX];
+ int irq_num;
+ void *osm_hw_reg;
+ void *int_clr_reg;
+ void *min_freq_reg;
+ cpumask_t core_map;
+ struct timer_list poll_timer;
+ unsigned long max_freq;
+ unsigned long min_freq;
+ unsigned long hw_freq_limit;
+ struct list_head list;
+ atomic_t is_irq_enabled;
+};
+
+LIST_HEAD(lmh_dcvs_hw_list);
+
+static int limits_dcvs_get_freq_limits(uint32_t cpu, unsigned long *max_freq,
+ unsigned long *min_freq)
+{
+ unsigned long freq_ceil = UINT_MAX, freq_floor = 0;
+ struct device *cpu_dev = NULL;
+ int ret = 0;
+
+ cpu_dev = get_cpu_device(cpu);
+ if (!cpu_dev) {
+ pr_err("Error in get CPU%d device\n", cpu);
+ return -ENODEV;
+ }
+
+ rcu_read_lock();
+ dev_pm_opp_find_freq_floor(cpu_dev, &freq_ceil);
+ dev_pm_opp_find_freq_ceil(cpu_dev, &freq_floor);
+ rcu_read_unlock();
+
+ *max_freq = freq_ceil / 1000;
+ *min_freq = freq_floor / 1000;
+
+ return ret;
+}
+
+static unsigned long limits_mitigation_notify(struct limits_dcvs_hw *hw)
+{
+ uint32_t val = 0;
+ struct device *cpu_dev = NULL;
+ unsigned long freq_val, max_limit = 0;
+ struct dev_pm_opp *opp_entry;
+
+ val = readl_relaxed(hw->osm_hw_reg);
+ dcvsh_get_frequency(val, max_limit);
+ cpu_dev = get_cpu_device(cpumask_first(&hw->core_map));
+ if (!cpu_dev) {
+ pr_err("Error in get CPU%d device\n",
+ cpumask_first(&hw->core_map));
+ goto notify_exit;
+ }
+
+ freq_val = FREQ_KHZ_TO_HZ(max_limit);
+ rcu_read_lock();
+ opp_entry = dev_pm_opp_find_freq_floor(cpu_dev, &freq_val);
+ /*
+ * Hardware mitigation frequency can be lower than the lowest
+ * possible CPU frequency. In that case freq floor call will
+ * fail with -ERANGE and we need to match to the lowest
+ * frequency using freq_ceil.
+ */
+ if (IS_ERR(opp_entry) && PTR_ERR(opp_entry) == -ERANGE) {
+ opp_entry = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_val);
+ if (IS_ERR(opp_entry))
+ dev_err(cpu_dev, "frequency:%lu. opp error:%ld\n",
+ freq_val, PTR_ERR(opp_entry));
+ }
+ rcu_read_unlock();
+ max_limit = FREQ_HZ_TO_KHZ(freq_val);
+
+ sched_update_cpu_freq_min_max(&hw->core_map, 0, max_limit);
+
+notify_exit:
+ hw->hw_freq_limit = max_limit;
+ return max_limit;
+}
+
+static void limits_dcvs_poll(unsigned long data)
+{
+ unsigned long max_limit = 0;
+ struct limits_dcvs_hw *hw = (struct limits_dcvs_hw *)data;
+
+ if (hw->max_freq == UINT_MAX)
+ limits_dcvs_get_freq_limits(cpumask_first(&hw->core_map),
+ &hw->max_freq, &hw->min_freq);
+ max_limit = limits_mitigation_notify(hw);
+ if (max_limit >= hw->max_freq) {
+ del_timer(&hw->poll_timer);
+ writel_relaxed(0xFF, hw->int_clr_reg);
+ atomic_set(&hw->is_irq_enabled, 1);
+ enable_irq(hw->irq_num);
+ } else {
+ mod_timer(&hw->poll_timer, jiffies + msecs_to_jiffies(
+ LIMITS_POLLING_DELAY_MS));
+ }
+}
+
+static void lmh_dcvs_notify(struct limits_dcvs_hw *hw)
+{
+ if (atomic_dec_and_test(&hw->is_irq_enabled)) {
+ disable_irq_nosync(hw->irq_num);
+ limits_mitigation_notify(hw);
+ mod_timer(&hw->poll_timer, jiffies + msecs_to_jiffies(
+ LIMITS_POLLING_DELAY_MS));
+ }
+}
+
+static irqreturn_t lmh_dcvs_handle_isr(int irq, void *data)
+{
+ struct limits_dcvs_hw *hw = data;
+
+ lmh_dcvs_notify(hw);
+
+ return IRQ_HANDLED;
+}
+
+static int limits_dcvs_write(uint32_t node_id, uint32_t fn,
+ uint32_t setting, uint32_t val)
+{
+ int ret;
+ struct scm_desc desc_arg;
+ uint32_t *payload = NULL;
+
+ payload = kzalloc(sizeof(uint32_t) * 5, GFP_KERNEL);
+ if (!payload)
+ return -ENOMEM;
+
+ payload[0] = fn; /* algorithm */
+ payload[1] = 0; /* unused sub-algorithm */
+ payload[2] = setting;
+ payload[3] = 1; /* number of values */
+ payload[4] = val;
+
+ desc_arg.args[0] = SCM_BUFFER_PHYS(payload);
+ desc_arg.args[1] = sizeof(uint32_t) * 5;
+ desc_arg.args[2] = LIMITS_NODE_DCVS;
+ desc_arg.args[3] = node_id;
+ desc_arg.args[4] = 0; /* version */
+ desc_arg.arginfo = SCM_ARGS(5, SCM_RO, SCM_VAL, SCM_VAL,
+ SCM_VAL, SCM_VAL);
+
+ dmac_flush_range(payload, (void *)payload + 5 * (sizeof(uint32_t)));
+ ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LIMITS_DCVSH), &desc_arg);
+
+ kfree(payload);
+
+ return ret;
+}
+
+static int lmh_get_temp(void *data, int *val)
+{
+ /*
+ * LMH DCVSh hardware doesn't support temperature read.
+ * return a default value for the thermal core to aggregate
+ * the thresholds
+ */
+ *val = LIMITS_TEMP_DEFAULT;
+
+ return 0;
+}
+
+static int lmh_set_trips(void *data, int low, int high)
+{
+ struct limits_dcvs_hw *hw = (struct limits_dcvs_hw *)data;
+ int ret = 0;
+
+ if (high < LIMITS_LOW_THRESHOLD_OFFSET || low < 0) {
+ pr_err("Value out of range low:%d high:%d\n",
+ low, high);
+ return -EINVAL;
+ }
+
+ /* Sanity check limits before writing to the hardware */
+ if (low >= high)
+ return -EINVAL;
+
+ hw->temp_limits[LIMITS_TRIP_HI] = (uint32_t)high;
+ hw->temp_limits[LIMITS_TRIP_ARM] = (uint32_t)low;
+
+ ret = limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_THERMAL,
+ LIMITS_ARM_THRESHOLD, low);
+ if (ret)
+ return ret;
+ ret = limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_THERMAL,
+ LIMITS_HI_THRESHOLD, high);
+ if (ret)
+ return ret;
+ ret = limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_THERMAL,
+ LIMITS_LOW_THRESHOLD,
+ high - LIMITS_LOW_THRESHOLD_OFFSET);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static struct thermal_zone_of_device_ops limits_sensor_ops = {
+ .get_temp = lmh_get_temp,
+ .set_trips = lmh_set_trips,
+};
+
+static struct limits_dcvs_hw *get_dcvsh_hw_from_cpu(int cpu)
+{
+ struct limits_dcvs_hw *hw;
+
+ list_for_each_entry(hw, &lmh_dcvs_hw_list, list) {
+ if (cpumask_test_cpu(cpu, &hw->core_map))
+ return hw;
+ }
+
+ return NULL;
+}
+
+static int enable_lmh(void)
+{
+ int ret = 0;
+ struct scm_desc desc_arg;
+
+ desc_arg.args[0] = 1;
+ desc_arg.arginfo = SCM_ARGS(1, SCM_VAL);
+ ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LIMITS_PROFILE_CHANGE),
+ &desc_arg);
+ if (ret) {
+ pr_err("Error switching profile:[1]. err:%d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int lmh_set_max_limit(int cpu, u32 freq)
+{
+ struct limits_dcvs_hw *hw = get_dcvsh_hw_from_cpu(cpu);
+
+ if (!hw)
+ return -EINVAL;
+
+ return limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_GENERAL,
+ LIMITS_DOMAIN_MAX, freq);
+}
+
+static int lmh_set_min_limit(int cpu, u32 freq)
+{
+ struct limits_dcvs_hw *hw = get_dcvsh_hw_from_cpu(cpu);
+
+ if (!hw)
+ return -EINVAL;
+
+ if (freq != hw->min_freq)
+ writel_relaxed(0x01, hw->min_freq_reg);
+ else
+ writel_relaxed(0x00, hw->min_freq_reg);
+
+ return 0;
+}
+static struct cpu_cooling_ops cd_ops = {
+ .ceil_limit = lmh_set_max_limit,
+ .floor_limit = lmh_set_min_limit,
+};
+
+static int limits_dcvs_probe(struct platform_device *pdev)
+{
+ int ret;
+ int affinity = -1;
+ struct limits_dcvs_hw *hw;
+ struct thermal_zone_device *tzdev;
+ struct thermal_cooling_device *cdev;
+ struct device_node *dn = pdev->dev.of_node;
+ struct device_node *cpu_node, *lmh_node;
+ uint32_t request_reg, clear_reg, min_reg;
+ unsigned long max_freq, min_freq;
+ int cpu;
+ cpumask_t mask = { CPU_BITS_NONE };
+
+ for_each_possible_cpu(cpu) {
+ cpu_node = of_cpu_device_node_get(cpu);
+ if (!cpu_node)
+ continue;
+ lmh_node = of_parse_phandle(cpu_node, "qcom,lmh-dcvs", 0);
+ if (lmh_node == dn) {
+ affinity = MPIDR_AFFINITY_LEVEL(
+ cpu_logical_map(cpu), 1);
+ /*set the cpumask*/
+ cpumask_set_cpu(cpu, &(mask));
+ }
+ of_node_put(cpu_node);
+ of_node_put(lmh_node);
+ }
+
+ /*
+ * We return error if none of the CPUs have
+ * reference to our LMH node
+ */
+ if (affinity == -1)
+ return -EINVAL;
+
+ ret = limits_dcvs_get_freq_limits(cpumask_first(&mask), &max_freq,
+ &min_freq);
+ if (ret)
+ return ret;
+ hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL);
+ if (!hw)
+ return -ENOMEM;
+
+ cpumask_copy(&hw->core_map, &mask);
+ switch (affinity) {
+ case 0:
+ hw->affinity = LIMITS_CLUSTER_0;
+ break;
+ case 1:
+ hw->affinity = LIMITS_CLUSTER_1;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ /* Enable the thermal algorithm early */
+ ret = limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_THERMAL,
+ LIMITS_ALGO_MODE_ENABLE, 1);
+ if (ret)
+ return ret;
+ /* Enable the LMH outer loop algorithm */
+ ret = limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_CRNT,
+ LIMITS_ALGO_MODE_ENABLE, 1);
+ if (ret)
+ return ret;
+ /* Enable the Reliability algorithm */
+ ret = limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_REL,
+ LIMITS_ALGO_MODE_ENABLE, 1);
+ if (ret)
+ return ret;
+ /* Enable the BCL algorithm */
+ ret = limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_BCL,
+ LIMITS_ALGO_MODE_ENABLE, 1);
+ if (ret)
+ return ret;
+ ret = enable_lmh();
+ if (ret)
+ return ret;
+
+ /*
+ * Setup virtual thermal zones for each LMH-DCVS hardware
+ * The sensor does not do actual thermal temperature readings
+ * but does support setting thresholds for trips.
+ * Let's register with thermal framework, so we have the ability
+ * to set low/high thresholds.
+ */
+ hw->temp_limits[LIMITS_TRIP_HI] = INT_MAX;
+ hw->temp_limits[LIMITS_TRIP_ARM] = 0;
+ hw->hw_freq_limit = hw->max_freq = max_freq;
+ hw->min_freq = min_freq;
+ snprintf(hw->sensor_name, sizeof(hw->sensor_name), "limits_sensor-%02d",
+ affinity);
+ tzdev = thermal_zone_of_sensor_register(&pdev->dev, 0, hw,
+ &limits_sensor_ops);
+ if (IS_ERR_OR_NULL(tzdev))
+ return PTR_ERR(tzdev);
+
+ /* Setup cooling devices to request mitigation states */
+ cdev = cpufreq_platform_cooling_register(&hw->core_map, &cd_ops);
+ if (IS_ERR_OR_NULL(cdev))
+ return PTR_ERR(cdev);
+
+ switch (affinity) {
+ case 0:
+ request_reg = LIMITS_CLUSTER_0_REQ;
+ clear_reg = LIMITS_CLUSTER_0_INT_CLR;
+ min_reg = LIMITS_CLUSTER_0_MIN_FREQ;
+ break;
+ case 1:
+ request_reg = LIMITS_CLUSTER_1_REQ;
+ clear_reg = LIMITS_CLUSTER_1_INT_CLR;
+ min_reg = LIMITS_CLUSTER_1_MIN_FREQ;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ hw->osm_hw_reg = devm_ioremap(&pdev->dev, request_reg, 0x4);
+ if (!hw->osm_hw_reg) {
+ pr_err("register remap failed\n");
+ return -ENOMEM;
+ }
+ hw->int_clr_reg = devm_ioremap(&pdev->dev, clear_reg, 0x4);
+ if (!hw->int_clr_reg) {
+ pr_err("interrupt clear reg remap failed\n");
+ return -ENOMEM;
+ }
+ hw->min_freq_reg = devm_ioremap(&pdev->dev, min_reg, 0x4);
+ if (!hw->min_freq_reg) {
+ pr_err("min frequency enable register remap failed\n");
+ return -ENOMEM;
+ }
+ init_timer_deferrable(&hw->poll_timer);
+ hw->poll_timer.data = (unsigned long)hw;
+ hw->poll_timer.function = limits_dcvs_poll;
+
+ hw->irq_num = of_irq_get(pdev->dev.of_node, 0);
+ if (hw->irq_num < 0) {
+ ret = hw->irq_num;
+ pr_err("Error getting IRQ number. err:%d\n", ret);
+ return ret;
+ }
+ atomic_set(&hw->is_irq_enabled, 1);
+ ret = devm_request_threaded_irq(&pdev->dev, hw->irq_num, NULL,
+ lmh_dcvs_handle_isr, IRQF_TRIGGER_HIGH | IRQF_ONESHOT
+ | IRQF_NO_SUSPEND, hw->sensor_name, hw);
+ if (ret) {
+ pr_err("Error registering for irq. err:%d\n", ret);
+ return ret;
+ }
+
+ INIT_LIST_HEAD(&hw->list);
+ list_add(&hw->list, &lmh_dcvs_hw_list);
+
+ return ret;
+}
+
+static const struct of_device_id limits_dcvs_match[] = {
+ { .compatible = "qcom,msm-hw-limits", },
+ {},
+};
+
+static struct platform_driver limits_dcvs_driver = {
+ .probe = limits_dcvs_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = limits_dcvs_match,
+ },
+};
+builtin_platform_driver(limits_dcvs_driver);
diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
index 7b45b9a..f905103 100644
--- a/drivers/thermal/thermal_core.c
+++ b/drivers/thermal/thermal_core.c
@@ -2443,7 +2443,7 @@ static int __init thermal_init(void)
return result;
}
-static void __exit thermal_exit(void)
+static void thermal_exit(void)
{
unregister_pm_notifier(&thermal_pm_nb);
of_thermal_destroy_zones();
diff --git a/drivers/thermal/tsens-dbg.c b/drivers/thermal/tsens-dbg.c
index d965a5c..7cd8c86 100644
--- a/drivers/thermal/tsens-dbg.c
+++ b/drivers/thermal/tsens-dbg.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
- * it under the term_tm of the GNU General Public License version 2 and
+ * 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,
diff --git a/drivers/thermal/tsens.h b/drivers/thermal/tsens.h
index 45d244e..b9ebb65 100644
--- a/drivers/thermal/tsens.h
+++ b/drivers/thermal/tsens.h
@@ -1,14 +1,14 @@
-/*
- * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 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 free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
+ *
*/
#ifndef __QCOM_TSENS_H__
#define __QCOM_TSENS_H__
diff --git a/drivers/thermal/tsens2xxx.c b/drivers/thermal/tsens2xxx.c
index 0f59dc5..1f0bee9 100644
--- a/drivers/thermal/tsens2xxx.c
+++ b/drivers/thermal/tsens2xxx.c
@@ -1,7 +1,7 @@
/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
- * it under the term_tm of the GNU General Public License version 2 and
+ * 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,
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index a9ded51..bac9975 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1079,6 +1079,12 @@
hardware.
The driver supports console and High speed UART functions.
+config SERIAL_MSM_GENI_CONSOLE
+ tristate "MSM on-chip GENI HW based console support"
+ depends on SERIAL_MSM_GENI=y
+ select SERIAL_CORE_CONSOLE
+ select SERIAL_EARLYCON
+
config SERIAL_MSM_CONSOLE
bool "MSM serial console support"
depends on SERIAL_MSM=y
diff --git a/drivers/tty/serial/msm_geni_serial.c b/drivers/tty/serial/msm_geni_serial.c
index 4d4fdf4..7c4654c 100644
--- a/drivers/tty/serial/msm_geni_serial.c
+++ b/drivers/tty/serial/msm_geni_serial.c
@@ -313,6 +313,7 @@ static void msm_geni_serial_poll_put_char(struct uart_port *uport,
}
#endif
+#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL)
static void msm_geni_serial_wr_char(struct uart_port *uport, int ch)
{
geni_write_reg(ch, uport->membase, SE_GENI_TX_FIFOn);
@@ -384,6 +385,53 @@ static void msm_geni_serial_console_write(struct console *co, const char *s,
spin_unlock(&uport->lock);
}
+static int handle_rx_console(struct uart_port *uport,
+ unsigned int rx_fifo_wc,
+ unsigned int rx_last_byte_valid,
+ unsigned int rx_last)
+{
+ int i, c;
+ unsigned char *rx_char;
+ struct tty_port *tport;
+ struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
+
+ tport = &uport->state->port;
+
+ for (i = 0; i < rx_fifo_wc; i++) {
+ int bytes = 4;
+
+ *(msm_port->rx_fifo) =
+ geni_read_reg(uport->membase, SE_GENI_RX_FIFOn);
+ rx_char = (unsigned char *)msm_port->rx_fifo;
+
+ if (i == (rx_fifo_wc - 1)) {
+ if (rx_last && rx_last_byte_valid)
+ bytes = rx_last_byte_valid;
+ }
+ for (c = 0; c < bytes; c++) {
+ char flag = TTY_NORMAL;
+ int sysrq;
+
+ uport->icount.rx++;
+ sysrq = uart_handle_sysrq_char(uport, rx_char[c]);
+ if (!sysrq)
+ tty_insert_flip_char(tport, rx_char[c], flag);
+ }
+ }
+ tty_flip_buffer_push(tport);
+ return 0;
+}
+#else
+static int handle_rx_console(struct uart_port *uport,
+ unsigned int rx_fifo_wc,
+ unsigned int rx_last_byte_valid,
+ unsigned int rx_last)
+{
+ return -EPERM;
+}
+
+#endif /* (CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL)) */
+
static void msm_geni_serial_start_tx(struct uart_port *uport)
{
unsigned int geni_m_irq_en;
@@ -476,43 +524,6 @@ static void msm_geni_serial_stop_rx(struct uart_port *uport)
WARN_ON(1);
}
-static int handle_rx_console(struct uart_port *uport,
- unsigned int rx_fifo_wc,
- unsigned int rx_last_byte_valid,
- unsigned int rx_last)
-{
- int i, c;
- unsigned char *rx_char;
- struct tty_port *tport;
- struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
-
- tport = &uport->state->port;
-
- for (i = 0; i < rx_fifo_wc; i++) {
- int bytes = 4;
-
- *(msm_port->rx_fifo) =
- geni_read_reg(uport->membase, SE_GENI_RX_FIFOn);
- rx_char = (unsigned char *)msm_port->rx_fifo;
-
- if (i == (rx_fifo_wc - 1)) {
- if (rx_last && rx_last_byte_valid)
- bytes = rx_last_byte_valid;
- }
- for (c = 0; c < bytes; c++) {
- char flag = TTY_NORMAL;
- int sysrq;
-
- uport->icount.rx++;
- sysrq = uart_handle_sysrq_char(uport, rx_char[c]);
- if (!sysrq)
- tty_insert_flip_char(tport, rx_char[c], flag);
- }
- }
- tty_flip_buffer_push(tport);
- return 0;
-}
-
static int handle_rx_hs(struct uart_port *uport,
unsigned int rx_fifo_wc,
unsigned int rx_last_byte_valid,
@@ -943,6 +954,7 @@ static unsigned int msm_geni_serial_tx_empty(struct uart_port *port)
return is_tx_empty;
}
+#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL)
static int __init msm_geni_console_setup(struct console *co, char *options)
{
struct uart_port *uport;
@@ -985,13 +997,100 @@ static int __init msm_geni_console_setup(struct console *co, char *options)
return uart_set_options(uport, co, baud, parity, bits, flow);
}
-static void msm_geni_serial_debug_init(struct uart_port *uport)
+static void
+msm_geni_serial_early_console_write(struct console *con, const char *s,
+ unsigned int n)
{
- struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
+ struct earlycon_device *dev = con->data;
- msm_port->dbg = debugfs_create_dir(dev_name(uport->dev), NULL);
- if (IS_ERR_OR_NULL(msm_port->dbg))
- dev_err(uport->dev, "Failed to create dbg dir\n");
+ __msm_geni_serial_console_write(&dev->port, s, n);
+}
+
+static int __init
+msm_geni_serial_earlycon_setup(struct earlycon_device *dev,
+ const char *opt)
+{
+ struct uart_port *uport = &dev->port;
+ int ret = 0;
+ struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
+ u32 tx_trans_cfg = 0;
+ u32 tx_parity_cfg = 0;
+ u32 rx_trans_cfg = 0;
+ u32 rx_parity_cfg = 0;
+ u32 stop_bit = 0;
+ u32 rx_stale = 0;
+ u32 bits_per_char = 0;
+ u32 s_clk_cfg = 0;
+ u32 baud = 115200;
+ u32 clk_div;
+ unsigned long clk_rate;
+
+ if (!uport->membase) {
+ ret = -ENOMEM;
+ goto exit_geni_serial_earlyconsetup;
+ }
+
+ if (get_se_proto(uport->membase) != UART) {
+ ret = -ENXIO;
+ goto exit_geni_serial_earlyconsetup;
+ }
+
+ msm_port->xfer_mode = FIFO_MODE;
+ set_rfr_wm(msm_port);
+ msm_port->tx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
+ msm_port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
+ msm_port->tx_fifo_width = DEF_FIFO_WIDTH_BITS;
+ geni_se_init(uport->membase, msm_port->xfer_mode, msm_port->rx_wm,
+ msm_port->rx_rfr);
+ /*
+ * Ignore Flow control.
+ * Disable Tx Parity.
+ * Don't check Parity during Rx.
+ * Disable Rx Parity.
+ * n = 8.
+ * Stop bit = 0.
+ * Stale timeout in bit-time (3 chars worth).
+ */
+ tx_trans_cfg |= UART_CTS_MASK;
+ tx_parity_cfg = 0;
+ rx_trans_cfg = 0;
+ rx_parity_cfg = 0;
+ bits_per_char = 0x8;
+ stop_bit = 0;
+ rx_stale = 0x18;
+ clk_div = get_clk_div_rate(baud, &clk_rate);
+ if (clk_div <= 0) {
+ ret = -EINVAL;
+ goto exit_geni_serial_earlyconsetup;
+ }
+
+ s_clk_cfg |= SER_CLK_EN;
+ s_clk_cfg |= (clk_div << CLK_DIV_SHFT);
+
+ geni_serial_write_term_regs(uport, 0, tx_trans_cfg,
+ tx_parity_cfg, rx_trans_cfg, rx_parity_cfg, bits_per_char,
+ stop_bit, rx_stale, s_clk_cfg);
+
+ dev->con->write = msm_geni_serial_early_console_write;
+ dev->con->setup = NULL;
+ /*
+ * Ensure that the early console setup completes before
+ * returning.
+ */
+ mb();
+exit_geni_serial_earlyconsetup:
+ return ret;
+}
+OF_EARLYCON_DECLARE(msm_geni_serial, "qcom,msm-geni-uart",
+ msm_geni_serial_earlycon_setup);
+
+static int console_register(struct uart_driver *drv)
+{
+ return uart_register_driver(drv);
+}
+static void console_unregister(struct uart_driver *drv)
+{
+ uart_unregister_driver(drv);
}
static struct console cons_ops = {
@@ -1004,6 +1103,33 @@ static struct console cons_ops = {
.data = &msm_geni_console_driver,
};
+static struct uart_driver msm_geni_console_driver = {
+ .owner = THIS_MODULE,
+ .driver_name = "msm_geni_console",
+ .dev_name = "ttyMSM",
+ .nr = GENI_UART_NR_PORTS,
+ .cons = &cons_ops,
+};
+#else
+static int console_register(struct uart_driver *drv)
+{
+ return 0;
+}
+
+static void console_unregister(struct uart_driver *drv)
+{
+}
+#endif /* defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL) */
+
+static void msm_geni_serial_debug_init(struct uart_port *uport)
+{
+ struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
+
+ msm_port->dbg = debugfs_create_dir(dev_name(uport->dev), NULL);
+ if (IS_ERR_OR_NULL(msm_port->dbg))
+ dev_err(uport->dev, "Failed to create dbg dir\n");
+}
+
static const struct uart_ops msm_geni_serial_pops = {
.tx_empty = msm_geni_serial_tx_empty,
.stop_tx = msm_geni_serial_stop_tx,
@@ -1022,8 +1148,10 @@ static const struct uart_ops msm_geni_serial_pops = {
};
static const struct of_device_id msm_geni_device_tbl[] = {
+#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(CONFIG_CONSOLE_POLL)
{ .compatible = "qcom,msm-geni-console",
.data = (void *)&msm_geni_console_driver},
+#endif
{ .compatible = "qcom,msm-geni-serial-hs",
.data = (void *)&msm_geni_serial_hs_driver},
{},
@@ -1189,92 +1317,6 @@ static int msm_geni_serial_remove(struct platform_device *pdev)
return 0;
}
-static void
-msm_geni_serial_early_console_write(struct console *con, const char *s,
- unsigned int n)
-{
- struct earlycon_device *dev = con->data;
-
- __msm_geni_serial_console_write(&dev->port, s, n);
-}
-
-static int __init
-msm_geni_serial_earlycon_setup(struct earlycon_device *dev,
- const char *opt)
-{
- struct uart_port *uport = &dev->port;
- int ret = 0;
- struct msm_geni_serial_port *msm_port = GET_DEV_PORT(uport);
- u32 tx_trans_cfg = 0;
- u32 tx_parity_cfg = 0;
- u32 rx_trans_cfg = 0;
- u32 rx_parity_cfg = 0;
- u32 stop_bit = 0;
- u32 rx_stale = 0;
- u32 bits_per_char = 0;
- u32 s_clk_cfg = 0;
- u32 baud = 115200;
- u32 clk_div;
- unsigned long clk_rate;
-
- if (!uport->membase) {
- ret = -ENOMEM;
- goto exit_geni_serial_earlyconsetup;
- }
-
- if (get_se_proto(uport->membase) != UART) {
- ret = -ENXIO;
- goto exit_geni_serial_earlyconsetup;
- }
-
- msm_port->xfer_mode = FIFO_MODE;
- set_rfr_wm(msm_port);
- msm_port->tx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
- msm_port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
- msm_port->tx_fifo_width = DEF_FIFO_WIDTH_BITS;
- geni_se_init(uport->membase, msm_port->xfer_mode, msm_port->rx_wm,
- msm_port->rx_rfr);
- /*
- * Ignore Flow control.
- * Disable Tx Parity.
- * Don't check Parity during Rx.
- * Disable Rx Parity.
- * n = 8.
- * Stop bit = 0.
- * Stale timeout in bit-time (3 chars worth).
- */
- tx_trans_cfg |= UART_CTS_MASK;
- tx_parity_cfg = 0;
- rx_trans_cfg = 0;
- rx_parity_cfg = 0;
- bits_per_char = 0x8;
- stop_bit = 0;
- rx_stale = 0x18;
- clk_div = get_clk_div_rate(baud, &clk_rate);
- if (clk_div <= 0) {
- ret = -EINVAL;
- goto exit_geni_serial_earlyconsetup;
- }
-
- s_clk_cfg |= SER_CLK_EN;
- s_clk_cfg |= (clk_div << CLK_DIV_SHFT);
-
- geni_serial_write_term_regs(uport, 0, tx_trans_cfg,
- tx_parity_cfg, rx_trans_cfg, rx_parity_cfg, bits_per_char,
- stop_bit, rx_stale, s_clk_cfg);
-
- dev->con->write = msm_geni_serial_early_console_write;
- dev->con->setup = NULL;
- /*
- * Ensure that the early console setup completes before
- * returning.
- */
- mb();
-exit_geni_serial_earlyconsetup:
- return ret;
-}
-OF_EARLYCON_DECLARE(msm_geni_serial, "qcom,msm-geni-uart",
- msm_geni_serial_earlycon_setup);
#ifdef CONFIG_PM
static int msm_geni_serial_runtime_suspend(struct device *dev)
@@ -1366,13 +1408,6 @@ static struct platform_driver msm_geni_serial_platform_driver = {
},
};
-static struct uart_driver msm_geni_console_driver = {
- .owner = THIS_MODULE,
- .driver_name = "msm_geni_console",
- .dev_name = "ttyMSM",
- .nr = GENI_UART_NR_PORTS,
- .cons = &cons_ops,
-};
static struct uart_driver msm_geni_serial_hs_driver = {
.owner = THIS_MODULE,
@@ -1393,7 +1428,7 @@ static int __init msm_geni_serial_init(void)
msm_geni_serial_ports[i].uport.line = i;
}
- ret = uart_register_driver(&msm_geni_console_driver);
+ ret = console_register(&msm_geni_console_driver);
if (ret)
return ret;
@@ -1405,7 +1440,7 @@ static int __init msm_geni_serial_init(void)
ret = platform_driver_register(&msm_geni_serial_platform_driver);
if (ret) {
- uart_unregister_driver(&msm_geni_console_driver);
+ console_unregister(&msm_geni_console_driver);
uart_unregister_driver(&msm_geni_serial_hs_driver);
return ret;
}
@@ -1419,7 +1454,7 @@ static void __exit msm_geni_serial_exit(void)
{
platform_driver_unregister(&msm_geni_serial_platform_driver);
uart_unregister_driver(&msm_geni_serial_hs_driver);
- uart_unregister_driver(&msm_geni_console_driver);
+ console_unregister(&msm_geni_console_driver);
}
module_exit(msm_geni_serial_exit);
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 53d730e..cf25708 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -1433,7 +1433,7 @@ void usb_hcd_unmap_urb_setup_for_dma(struct usb_hcd *hcd, struct urb *urb)
{
if (IS_ENABLED(CONFIG_HAS_DMA) &&
(urb->transfer_flags & URB_SETUP_MAP_SINGLE))
- dma_unmap_single(hcd->self.controller,
+ dma_unmap_single(hcd->self.sysdev,
urb->setup_dma,
sizeof(struct usb_ctrlrequest),
DMA_TO_DEVICE);
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index f786947..96684f4 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -2822,7 +2822,7 @@ static int dwc3_msm_extcon_register(struct dwc3_msm *mdwc)
static int dwc3_msm_init_iommu(struct dwc3_msm *mdwc)
{
struct device_node *node = mdwc->dev->of_node;
- int atomic_ctx = 1;
+ int atomic_ctx = 1, s1_bypass;
int ret;
if (!of_property_read_bool(node, "iommus"))
@@ -2846,6 +2846,15 @@ static int dwc3_msm_init_iommu(struct dwc3_msm *mdwc)
goto release_mapping;
}
+ s1_bypass = of_property_read_bool(node, "qcom,smmu-s1-bypass");
+ ret = iommu_domain_set_attr(mdwc->iommu_map->domain,
+ DOMAIN_ATTR_S1_BYPASS, &s1_bypass);
+ if (ret) {
+ dev_err(mdwc->dev, "IOMMU set s1 bypass (%d) failed (%d)\n",
+ s1_bypass, ret);
+ goto release_mapping;
+ }
+
ret = arm_iommu_attach_device(mdwc->dev, mdwc->iommu_map);
if (ret) {
dev_err(mdwc->dev, "IOMMU attach failed (%d)\n", ret);
diff --git a/drivers/usb/gadget/function/f_accessory.c b/drivers/usb/gadget/function/f_accessory.c
index daca68b..46df732 100644
--- a/drivers/usb/gadget/function/f_accessory.c
+++ b/drivers/usb/gadget/function/f_accessory.c
@@ -611,8 +611,7 @@ static ssize_t acc_read(struct file *fp, char __user *buf,
{
struct acc_dev *dev = fp->private_data;
struct usb_request *req;
- ssize_t r = count;
- unsigned xfer;
+ ssize_t r = count, xfer, len;
int ret = 0;
pr_debug("acc_read(%zu)\n", count);
@@ -633,6 +632,8 @@ static ssize_t acc_read(struct file *fp, char __user *buf,
goto done;
}
+ len = ALIGN(count, dev->ep_out->maxpacket);
+
if (dev->rx_done) {
// last req cancelled. try to get it.
req = dev->rx_req[0];
@@ -642,7 +643,7 @@ static ssize_t acc_read(struct file *fp, char __user *buf,
requeue_req:
/* queue a request */
req = dev->rx_req[0];
- req->length = count;
+ req->length = len;
dev->rx_done = 0;
ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
if (ret < 0) {
@@ -941,6 +942,8 @@ int acc_ctrlrequest(struct usb_composite_dev *cdev,
memset(dev->serial, 0, sizeof(dev->serial));
dev->start_requested = 0;
dev->audio_mode = 0;
+ strlcpy(dev->manufacturer, "Android", ACC_STRING_SIZE);
+ strlcpy(dev->model, "Android", ACC_STRING_SIZE);
}
}
@@ -1251,13 +1254,13 @@ static int acc_setup(void)
INIT_DELAYED_WORK(&dev->start_work, acc_start_work);
INIT_WORK(&dev->hid_work, acc_hid_work);
- /* _acc_dev must be set before calling usb_gadget_register_driver */
- _acc_dev = dev;
-
ret = misc_register(&acc_device);
if (ret)
goto err;
+ /* _acc_dev must be set before calling usb_gadget_register_driver */
+ _acc_dev = dev;
+
return 0;
err:
diff --git a/drivers/usb/gadget/function/f_audio_source.c b/drivers/usb/gadget/function/f_audio_source.c
index db7903d..a2a9185 100644
--- a/drivers/usb/gadget/function/f_audio_source.c
+++ b/drivers/usb/gadget/function/f_audio_source.c
@@ -989,6 +989,7 @@ static ssize_t audio_source_pcm_show(struct device *dev,
struct device *create_function_device(char *name);
+#define AUDIO_SOURCE_DEV_NAME_LENGTH 20
static struct usb_function_instance *audio_source_alloc_inst(void)
{
struct audio_source_instance *fi_audio;
@@ -997,6 +998,8 @@ static struct usb_function_instance *audio_source_alloc_inst(void)
struct device *dev;
void *err_ptr;
int err = 0;
+ char device_name[AUDIO_SOURCE_DEV_NAME_LENGTH];
+ static u8 count;
fi_audio = kzalloc(sizeof(*fi_audio), GFP_KERNEL);
if (!fi_audio)
@@ -1014,7 +1017,11 @@ static struct usb_function_instance *audio_source_alloc_inst(void)
config_group_init_type_name(&fi_audio->func_inst.group, "",
&audio_source_func_type);
- dev = create_function_device("f_audio_source");
+
+ snprintf(device_name, AUDIO_SOURCE_DEV_NAME_LENGTH,
+ "f_audio_source%d", count++);
+
+ dev = create_function_device(device_name);
if (IS_ERR(dev)) {
err_ptr = dev;
diff --git a/drivers/usb/gadget/function/f_gsi.c b/drivers/usb/gadget/function/f_gsi.c
index c807b12..12e94d5 100644
--- a/drivers/usb/gadget/function/f_gsi.c
+++ b/drivers/usb/gadget/function/f_gsi.c
@@ -1562,13 +1562,6 @@ static void gsi_ctrl_notify_resp_complete(struct usb_ep *ep,
event->bNotificationType, req->status);
/* FALLTHROUGH */
case 0:
- /*
- * handle multiple pending resp available
- * notifications by queuing same until we're done,
- * rest of the notification require queuing new
- * request.
- */
- gsi_ctrl_send_notification(gsi);
break;
}
}
@@ -1663,6 +1656,14 @@ static void gsi_ctrl_reset_cmd_complete(struct usb_ep *ep,
gsi_ctrl_send_cpkt_tomodem(gsi, req->buf, 0);
}
+static void gsi_ctrl_send_response_complete(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct f_gsi *gsi = req->context;
+
+ gsi_ctrl_send_notification(gsi);
+}
+
static int
gsi_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
{
@@ -1749,6 +1750,8 @@ gsi_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
memcpy(req->buf, cpkt->buf, value);
gsi_ctrl_pkt_free(cpkt);
+ req->complete = gsi_ctrl_send_response_complete;
+ req->context = gsi;
log_event_dbg("copied encap_resp %d bytes",
value);
break;
@@ -3047,6 +3050,9 @@ static void gsi_free_inst(struct usb_function_instance *f)
{
struct gsi_opts *opts = container_of(f, struct gsi_opts, func_inst);
+ if (!opts->gsi)
+ return;
+
if (opts->gsi->c_port.ctrl_device.fops)
misc_deregister(&opts->gsi->c_port.ctrl_device);
diff --git a/drivers/usb/gadget/function/f_midi.c b/drivers/usb/gadget/function/f_midi.c
index fbe6910..217b7ca 100644
--- a/drivers/usb/gadget/function/f_midi.c
+++ b/drivers/usb/gadget/function/f_midi.c
@@ -1238,7 +1238,7 @@ static struct usb_function_instance *f_midi_alloc_inst(void)
opts->func_inst.free_func_inst = f_midi_free_inst;
opts->index = SNDRV_DEFAULT_IDX1;
opts->id = SNDRV_DEFAULT_STR1;
- opts->buflen = 512;
+ opts->buflen = 1024;
opts->qlen = 32;
opts->in_ports = 1;
opts->out_ports = 1;
diff --git a/drivers/usb/gadget/function/rndis.c b/drivers/usb/gadget/function/rndis.c
index ed93f9d..38d58f3 100644
--- a/drivers/usb/gadget/function/rndis.c
+++ b/drivers/usb/gadget/function/rndis.c
@@ -538,14 +538,11 @@ static int gen_ndis_set_resp(struct rndis_params *params, u32 OID,
*/
retval = 0;
if (*params->filter) {
- params->state = RNDIS_DATA_INITIALIZED;
- netif_carrier_on(params->dev);
- if (netif_running(params->dev))
- netif_wake_queue(params->dev);
+ pr_debug("%s(): disable flow control\n", __func__);
+ rndis_flow_control(params, false);
} else {
- params->state = RNDIS_INITIALIZED;
- netif_carrier_off(params->dev);
- netif_stop_queue(params->dev);
+ pr_err("%s(): enable flow control\n", __func__);
+ rndis_flow_control(params, true);
}
break;
@@ -690,12 +687,6 @@ static int rndis_reset_response(struct rndis_params *params,
{
rndis_reset_cmplt_type *resp;
rndis_resp_t *r;
- u8 *xbuf;
- u32 length;
-
- /* drain the response queue */
- while ((xbuf = rndis_get_next_response(params, &length)))
- rndis_free_response(params, xbuf);
r = rndis_add_response(params, sizeof(rndis_reset_cmplt_type));
if (!r)
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 7558021..681b77a 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -407,11 +407,20 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend)
return -ENOMEM;
}
- xhci_queue_stop_endpoint(xhci, command, slot_id, i,
- suspend);
+
+ ret = xhci_queue_stop_endpoint(xhci, command, slot_id,
+ i, suspend);
+ if (ret) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ goto err_cmd_queue;
+ }
}
}
- xhci_queue_stop_endpoint(xhci, cmd, slot_id, 0, suspend);
+ ret = xhci_queue_stop_endpoint(xhci, cmd, slot_id, 0, suspend);
+ if (ret) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ goto err_cmd_queue;
+ }
xhci_ring_cmd_db(xhci);
spin_unlock_irqrestore(&xhci->lock, flags);
@@ -422,6 +431,8 @@ static int xhci_stop_device(struct xhci_hcd *xhci, int slot_id, int suspend)
xhci_warn(xhci, "Timeout while waiting for stop endpoint command\n");
ret = -ETIME;
}
+
+err_cmd_queue:
xhci_free_command(xhci, cmd);
return ret;
}
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 374750f..b59efd2 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -1507,6 +1507,11 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
/* Some devices get this wrong */
if (usb_endpoint_xfer_bulk(&ep->desc) && udev->speed == USB_SPEED_HIGH)
max_packet = 512;
+
+ if (usb_endpoint_xfer_bulk(&ep->desc) && udev->speed == USB_SPEED_FULL
+ && max_packet < 8)
+ max_packet = 8;
+
/* xHCI 1.0 and 1.1 indicates that ctrl ep avg TRB Length should be 8 */
if (usb_endpoint_xfer_control(&ep->desc) && xhci->hci_version >= 0x100)
avg_trb_len = 8;
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 6ee10df..fa1323b 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -200,6 +200,8 @@ static int xhci_plat_probe(struct platform_device *pdev)
struct clk *clk;
int ret;
int irq;
+ u32 temp, imod;
+ unsigned long flags;
if (usb_disabled())
return -ENODEV;
@@ -308,6 +310,9 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (device_property_read_bool(sysdev, "usb3-lpm-capable"))
xhci->quirks |= XHCI_LPM_SUPPORT;
+ if (device_property_read_u32(sysdev, "snps,xhci-imod-value", &imod))
+ imod = 0;
+
hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0);
if (IS_ERR(hcd->usb_phy)) {
ret = PTR_ERR(hcd->usb_phy);
@@ -331,6 +336,18 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (ret)
goto dealloc_usb2_hcd;
+ /* override imod interval if specified */
+ if (imod) {
+ imod &= ER_IRQ_INTERVAL_MASK;
+ spin_lock_irqsave(&xhci->lock, flags);
+ temp = readl_relaxed(&xhci->ir_set->irq_control);
+ temp &= ~ER_IRQ_INTERVAL_MASK;
+ temp |= imod;
+ writel_relaxed(temp, &xhci->ir_set->irq_control);
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ dev_dbg(&pdev->dev, "%s: imod set to %u\n", __func__, imod);
+ }
+
ret = device_create_file(&pdev->dev, &dev_attr_config_imod);
if (ret)
dev_err(&pdev->dev, "%s: unable to create imod sysfs entry\n",
@@ -411,7 +428,7 @@ static int xhci_plat_runtime_suspend(struct device *dev)
dev_dbg(dev, "xhci-plat runtime suspend\n");
- return xhci_suspend(xhci, true);
+ return 0;
}
static int xhci_plat_runtime_resume(struct device *dev)
@@ -425,7 +442,7 @@ static int xhci_plat_runtime_resume(struct device *dev)
dev_dbg(dev, "xhci-plat runtime resume\n");
- ret = xhci_resume(xhci, false);
+ ret = 0;
pm_runtime_mark_last_busy(dev);
return ret;
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index e7e9c07..5d434e0 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -675,7 +675,7 @@ static void xhci_giveback_urb_in_irq(struct xhci_hcd *xhci,
void xhci_unmap_td_bounce_buffer(struct xhci_hcd *xhci, struct xhci_ring *ring,
struct xhci_td *td)
{
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
struct xhci_segment *seg = td->bounce_seg;
struct urb *urb = td->urb;
@@ -3153,7 +3153,7 @@ static u32 xhci_td_remainder(struct xhci_hcd *xhci, int transferred,
static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len,
u32 *trb_buff_len, struct xhci_segment *seg)
{
- struct device *dev = xhci_to_hcd(xhci)->self.controller;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
unsigned int unalign;
unsigned int max_pkt;
u32 new_buff_len;
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index e6e985d..ec9ff3e 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -113,12 +113,21 @@ int xhci_halt(struct xhci_hcd *xhci)
ret = xhci_handshake(&xhci->op_regs->status,
STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC);
- if (!ret) {
+ if (!ret)
xhci->xhc_state |= XHCI_STATE_HALTED;
- xhci->cmd_ring_state = CMD_RING_STATE_STOPPED;
- } else
+ else
xhci_warn(xhci, "Host not halted after %u microseconds.\n",
XHCI_MAX_HALT_USEC);
+
+ xhci->cmd_ring_state = CMD_RING_STATE_STOPPED;
+
+ if (delayed_work_pending(&xhci->cmd_timer)) {
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "Cleanup command queue");
+ cancel_delayed_work(&xhci->cmd_timer);
+ xhci_cleanup_command_queue(xhci);
+ }
+
return ret;
}
diff --git a/drivers/usb/phy/phy-msm-qusb-v2.c b/drivers/usb/phy/phy-msm-qusb-v2.c
index c59e33f..4f0a455 100644
--- a/drivers/usb/phy/phy-msm-qusb-v2.c
+++ b/drivers/usb/phy/phy-msm-qusb-v2.c
@@ -50,6 +50,15 @@
#define QUSB2PHY_PORT_TUNE1 0x23c
#define QUSB2PHY_TEST1 0x24C
+#define QUSB2PHY_PLL_CORE_INPUT_OVERRIDE 0x0a8
+#define CORE_PLL_RATE BIT(0)
+#define CORE_PLL_RATE_MUX BIT(1)
+#define CORE_PLL_EN BIT(2)
+#define CORE_PLL_EN_MUX BIT(3)
+#define CORE_PLL_EN_FROM_RESET BIT(4)
+#define CORE_RESET BIT(5)
+#define CORE_RESET_MUX BIT(6)
+
#define QUSB2PHY_1P8_VOL_MIN 1800000 /* uV */
#define QUSB2PHY_1P8_VOL_MAX 1800000 /* uV */
#define QUSB2PHY_1P8_HPM_LOAD 30000 /* uA */
@@ -330,22 +339,30 @@ static void qusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt,
}
}
+static void qusb_phy_reset(struct qusb_phy *qphy)
+{
+ int ret;
+
+ ret = reset_control_assert(qphy->phy_reset);
+ if (ret)
+ dev_err(qphy->phy.dev, "%s: phy_reset assert failed\n",
+ __func__);
+ usleep_range(100, 150);
+
+ ret = reset_control_deassert(qphy->phy_reset);
+ if (ret)
+ dev_err(qphy->phy.dev, "%s: phy_reset deassert failed\n",
+ __func__);
+}
+
static void qusb_phy_host_init(struct usb_phy *phy)
{
u8 reg;
- int ret;
struct qusb_phy *qphy = container_of(phy, struct qusb_phy, phy);
dev_dbg(phy->dev, "%s\n", __func__);
- /* Perform phy reset */
- ret = reset_control_assert(qphy->phy_reset);
- if (ret)
- dev_err(phy->dev, "%s: phy_reset assert failed\n", __func__);
- usleep_range(100, 150);
- ret = reset_control_deassert(qphy->phy_reset);
- dev_err(phy->dev, "%s: phy_reset deassert failed\n", __func__);
-
+ qusb_phy_reset(qphy);
qusb_phy_write_seq(qphy->base, qphy->qusb_phy_host_init_seq,
qphy->host_init_seq_len, 0);
@@ -377,15 +394,7 @@ static int qusb_phy_init(struct usb_phy *phy)
qusb_phy_enable_clocks(qphy, true);
- /* Perform phy reset */
- ret = reset_control_assert(qphy->phy_reset);
- if (ret)
- dev_err(phy->dev, "%s: phy_reset assert failed\n", __func__);
- usleep_range(100, 150);
- ret = reset_control_deassert(qphy->phy_reset);
- if (ret)
- dev_err(phy->dev, "%s: phy_reset deassert failed\n", __func__);
-
+ qusb_phy_reset(qphy);
if (qphy->emulation) {
if (qphy->emu_init_seq)
qusb_phy_write_seq(qphy->emu_phy_base + 0x8000,
@@ -537,6 +546,11 @@ static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
writel_relaxed(intr_mask,
qphy->base + QUSB2PHY_INTR_CTRL);
+ /* hold core PLL into reset */
+ writel_relaxed(CORE_PLL_EN_FROM_RESET |
+ CORE_RESET | CORE_RESET_MUX,
+ qphy->base + QUSB2PHY_PLL_CORE_INPUT_OVERRIDE);
+
/* enable phy auto-resume */
writel_relaxed(0x91,
qphy->base + QUSB2PHY_TEST1);
@@ -555,14 +569,7 @@ static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
/* Disable all interrupts */
writel_relaxed(0x00,
qphy->base + QUSB2PHY_INTR_CTRL);
-
- /* Put PHY into non-driving mode */
- writel_relaxed(0x23,
- qphy->base + QUSB2PHY_PWR_CTRL1);
-
- /* Makes sure that above write goes through */
- wmb();
-
+ qusb_phy_reset(qphy);
qusb_phy_enable_clocks(qphy, false);
qusb_phy_enable_power(qphy, false, true);
}
@@ -576,6 +583,10 @@ static int qusb_phy_set_suspend(struct usb_phy *phy, int suspend)
writel_relaxed(0x00,
qphy->base + QUSB2PHY_INTR_CTRL);
+ /* bring core PLL out of reset */
+ writel_relaxed(CORE_PLL_EN_FROM_RESET,
+ qphy->base + QUSB2PHY_PLL_CORE_INPUT_OVERRIDE);
+
/* Makes sure that above write goes through */
wmb();
} else { /* Cable connect case */
diff --git a/include/dt-bindings/clock/mdss-10nm-pll-clk.h b/include/dt-bindings/clock/mdss-10nm-pll-clk.h
new file mode 100644
index 0000000..c1350ce
--- /dev/null
+++ b/include/dt-bindings/clock/mdss-10nm-pll-clk.h
@@ -0,0 +1,37 @@
+
+/*
+ * 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.
+ */
+
+#ifndef __MDSS_10NM_PLL_CLK_H
+#define __MDSS_10NM_PLL_CLK_H
+
+/* DSI PLL clocks */
+#define VCO_CLK_0 0
+#define BITCLK_SRC_0_CLK 1
+#define BYTECLK_SRC_0_CLK 2
+#define POST_BIT_DIV_0_CLK 3
+#define POST_VCO_DIV_0_CLK 4
+#define BYTECLK_MUX_0_CLK 5
+#define PCLK_SRC_MUX_0_CLK 6
+#define PCLK_SRC_0_CLK 7
+#define PCLK_MUX_0_CLK 8
+#define VCO_CLK_1 9
+#define BITCLK_SRC_1_CLK 10
+#define BYTECLK_SRC_1_CLK 11
+#define POST_BIT_DIV_1_CLK 12
+#define POST_VCO_DIV_1_CLK 13
+#define BYTECLK_MUX_1_CLK 14
+#define PCLK_SRC_MUX_1_CLK 15
+#define PCLK_SRC_1_CLK 16
+#define PCLK_MUX_1_CLK 17
+#endif
diff --git a/include/dt-bindings/msm/power-on.h b/include/dt-bindings/msm/power-on.h
new file mode 100644
index 0000000..f43841e
--- /dev/null
+++ b/include/dt-bindings/msm/power-on.h
@@ -0,0 +1,24 @@
+/* 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.
+ */
+
+#ifndef __MSM_POWER_ON_H__
+#define __MSM_POWER_ON_H__
+
+#define PON_POWER_OFF_RESERVED 0x00
+#define PON_POWER_OFF_WARM_RESET 0x01
+#define PON_POWER_OFF_SHUTDOWN 0x04
+#define PON_POWER_OFF_DVDD_SHUTDOWN 0x05
+#define PON_POWER_OFF_HARD_RESET 0x07
+#define PON_POWER_OFF_DVDD_HARD_RESET 0x08
+#define PON_POWER_OFF_MAX_TYPE 0x10
+
+#endif
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index b7b92bf..6dd1547 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -85,6 +85,12 @@ struct mmc_ios {
struct mmc_host_ops {
/*
+ * 'enable' is called when the host is claimed and 'disable' is called
+ * when the host is released. 'enable' and 'disable' are deprecated.
+ */
+ int (*enable)(struct mmc_host *host);
+ int (*disable)(struct mmc_host *host);
+ /*
* It is optional for the host to implement pre_req and post_req in
* order to support double buffering of requests (prepare one
* request while another request is active).
@@ -313,6 +319,7 @@ struct mmc_host {
#define MMC_CAP2_HS400_ES (1 << 20) /* Host supports enhanced strobe */
#define MMC_CAP2_NO_SD (1 << 21) /* Do not send SD commands during initialization */
#define MMC_CAP2_NO_MMC (1 << 22) /* Do not send (e)MMC commands during initialization */
+#define MMC_CAP2_PACKED_WR_CONTROL (1 << 23) /* Allow write packing control */
mmc_pm_flag_t pm_caps; /* supported pm features */
diff --git a/include/linux/qpnp/qpnp-misc.h b/include/linux/qpnp/qpnp-misc.h
new file mode 100644
index 0000000..7d95bf2
--- /dev/null
+++ b/include/linux/qpnp/qpnp-misc.h
@@ -0,0 +1,56 @@
+/* Copyright (c) 2013-2014, 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __QPNP_MISC_H
+#define __QPNP_MISC_H
+
+#include <linux/errno.h>
+
+#ifdef CONFIG_QPNP_MISC
+/**
+ * qpnp_misc_irqs_available - check if IRQs are available
+ *
+ * @consumer_dev: device struct
+ *
+ * This function returns true if the MISC interrupts are available
+ * based on a check in the MISC peripheral revision registers.
+ *
+ * Any consumer of this function needs to reference a MISC device phandle
+ * using the "qcom,misc-ref" property in their device tree node.
+ */
+
+int qpnp_misc_irqs_available(struct device *consumer_dev);
+
+/**
+ * qpnp_misc_read_reg - read register from misc device
+ *
+ * @node: device node pointer
+ * @address: address offset in misc peripheral to be read
+ * @val: data read from register
+ *
+ * This function returns zero if reading the MISC register succeeds.
+ *
+ */
+
+int qpnp_misc_read_reg(struct device_node *node, u16 addr, u8 *val);
+#else
+static inline int qpnp_misc_irqs_available(struct device *consumer_dev)
+{
+ return 0;
+}
+static inline int qpnp_misc_read_reg(struct device_node *node, u16 addr,
+ u8 *val)
+{
+ return 0;
+}
+#endif
+#endif
diff --git a/include/linux/qpnp/qpnp-pbs.h b/include/linux/qpnp/qpnp-pbs.h
new file mode 100644
index 0000000..39497ac
--- /dev/null
+++ b/include/linux/qpnp/qpnp-pbs.h
@@ -0,0 +1,25 @@
+/* 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.
+ */
+
+#ifndef _QPNP_PBS_H
+#define _QPNP_PBS_H
+
+#ifdef CONFIG_QPNP_PBS
+int qpnp_pbs_trigger_event(struct device_node *dev_node, u8 bitmap);
+#else
+static inline int qpnp_pbs_trigger_event(struct device_node *dev_node,
+ u8 bitmap) {
+ return -ENODEV;
+}
+#endif
+
+#endif
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 677a047..6d27dae 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -4245,6 +4245,32 @@ int regulatory_set_wiphy_regd_sync_rtnl(struct wiphy *wiphy,
struct ieee80211_regdomain *rd);
/**
+ * regulatory_hint_user - hint to the wireless core a regulatory domain
+ * which the driver has received from an application
+ * @alpha2: the ISO/IEC 3166 alpha2 the driver claims its regulatory domain
+ * should be in. If @rd is set this should be NULL. Note that if you
+ * set this to NULL you should still set rd->alpha2 to some accepted
+ * alpha2.
+ * @user_reg_hint_type: the type of user regulatory hint.
+ *
+ * Wireless drivers can use this function to hint to the wireless core
+ * the current regulatory domain as specified by trusted applications,
+ * it is the driver's responsibilty to estbalish which applications it
+ * trusts.
+ *
+ * The wiphy should be registered to cfg80211 prior to this call.
+ * For cfg80211 drivers this means you must first use wiphy_register(),
+ * for mac80211 drivers you must first use ieee80211_register_hw().
+ *
+ * Drivers should check the return value, its possible you can get
+ * an -ENOMEM or an -EINVAL.
+ *
+ * Return: 0 on success. -ENOMEM, -EINVAL.
+ */
+int regulatory_hint_user(const char *alpha2,
+ enum nl80211_user_reg_hint_type user_reg_hint_type);
+
+/**
* wiphy_apply_custom_regulatory - apply a custom driver regulatory domain
* @wiphy: the wireless device we want to process the regulatory domain on
* @regd: the custom regulatory domain to use for this wiphy
diff --git a/include/soc/qcom/icnss.h b/include/soc/qcom/icnss.h
index 6b567d7..7ef984a 100644
--- a/include/soc/qcom/icnss.h
+++ b/include/soc/qcom/icnss.h
@@ -17,6 +17,21 @@
#define ICNSS_MAX_IRQ_REGISTRATIONS 12
#define ICNSS_MAX_TIMESTAMP_LEN 32
+enum icnss_uevent {
+ ICNSS_UEVENT_FW_READY,
+ ICNSS_UEVENT_FW_CRASHED,
+ ICNSS_UEVENT_FW_DOWN,
+};
+
+struct icnss_uevent_fw_down_data {
+ bool crashed;
+};
+
+struct icnss_uevent_data {
+ enum icnss_uevent uevent;
+ void *data;
+};
+
struct icnss_driver_ops {
char *name;
int (*probe)(struct device *dev);
@@ -28,6 +43,7 @@ struct icnss_driver_ops {
int (*pm_resume)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
+ int (*uevent)(struct device *dev, struct icnss_uevent_data *uevent);
};
diff --git a/include/uapi/linux/msm_ipa.h b/include/uapi/linux/msm_ipa.h
index 1e6ccf4..817feba 100644
--- a/include/uapi/linux/msm_ipa.h
+++ b/include/uapi/linux/msm_ipa.h
@@ -161,6 +161,7 @@ enum ipa_client_type {
IPA_CLIENT_Q6_DECOMP_PROD,
IPA_CLIENT_Q6_DECOMP2_PROD,
IPA_CLIENT_UC_USB_PROD,
+ IPA_CLIENT_ETHERNET_PROD,
/* Below PROD client type is only for test purpose */
IPA_CLIENT_TEST_PROD,
@@ -200,6 +201,8 @@ enum ipa_client_type {
IPA_CLIENT_Q6_DECOMP_CONS,
IPA_CLIENT_Q6_DECOMP2_CONS,
IPA_CLIENT_Q6_LTE_WIFI_AGGR_CONS,
+ IPA_CLIENT_ETHERNET_CONS,
+
/* Below CONS client type is only for test purpose */
IPA_CLIENT_TEST_CONS,
IPA_CLIENT_TEST1_CONS,
@@ -417,6 +420,7 @@ enum ipa_rm_resource_name {
IPA_RM_RESOURCE_WLAN_PROD,
IPA_RM_RESOURCE_ODU_ADAPT_PROD,
IPA_RM_RESOURCE_MHI_PROD,
+ IPA_RM_RESOURCE_ETHERNET_PROD,
IPA_RM_RESOURCE_PROD_MAX,
IPA_RM_RESOURCE_Q6_CONS = IPA_RM_RESOURCE_PROD_MAX,
@@ -427,6 +431,7 @@ enum ipa_rm_resource_name {
IPA_RM_RESOURCE_APPS_CONS,
IPA_RM_RESOURCE_ODU_ADAPT_CONS,
IPA_RM_RESOURCE_MHI_CONS,
+ IPA_RM_RESOURCE_ETHERNET_CONS,
IPA_RM_RESOURCE_MAX
};
diff --git a/kernel/events/core.c b/kernel/events/core.c
index 87b9cd9..41f376d 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -372,6 +372,7 @@ static atomic_t perf_sched_count;
static DEFINE_PER_CPU(atomic_t, perf_cgroup_events);
static DEFINE_PER_CPU(int, perf_sched_cb_usages);
static DEFINE_PER_CPU(struct pmu_event_list, pmu_sb_events);
+static DEFINE_PER_CPU(bool, is_idle);
static atomic_t nr_mmap_events __read_mostly;
static atomic_t nr_comm_events __read_mostly;
@@ -3605,23 +3606,31 @@ u64 perf_event_read_local(struct perf_event *event)
static int perf_event_read(struct perf_event *event, bool group)
{
int event_cpu, ret = 0;
+ bool active_event_skip_read = false;
/*
* If event is enabled and currently active on a CPU, update the
* value in the event structure:
*/
+ event_cpu = READ_ONCE(event->oncpu);
+
+ if (event->state == PERF_EVENT_STATE_ACTIVE) {
+ if ((unsigned int)event_cpu >= nr_cpu_ids)
+ return 0;
+ if (cpu_isolated(event_cpu) ||
+ (event->attr.exclude_idle &&
+ per_cpu(is_idle, event_cpu)))
+ active_event_skip_read = true;
+ }
+
if (event->state == PERF_EVENT_STATE_ACTIVE &&
- !cpu_isolated(event->oncpu)) {
+ !active_event_skip_read) {
struct perf_read_data data = {
.event = event,
.group = group,
.ret = 0,
};
- event_cpu = READ_ONCE(event->oncpu);
- if ((unsigned)event_cpu >= nr_cpu_ids)
- return 0;
-
preempt_disable();
event_cpu = __perf_event_read_cpu(event, event_cpu);
@@ -3635,10 +3644,12 @@ static int perf_event_read(struct perf_event *event, bool group)
* Therefore, either way, we'll have an up-to-date event count
* after this.
*/
- (void)smp_call_function_single(event_cpu, __perf_event_read, &data, 1);
+ (void)smp_call_function_single(event_cpu,
+ __perf_event_read, &data, 1);
preempt_enable();
ret = data.ret;
- } else if (event->state == PERF_EVENT_STATE_INACTIVE) {
+ } else if (event->state == PERF_EVENT_STATE_INACTIVE ||
+ active_event_skip_read) {
struct perf_event_context *ctx = event->ctx;
unsigned long flags;
@@ -3731,7 +3742,8 @@ find_get_context(struct pmu *pmu, struct task_struct *task,
if (!task) {
/* Must be root to operate on a CPU event: */
- if (perf_paranoid_cpu() && !capable(CAP_SYS_ADMIN))
+ if (!is_kernel_event(event) && perf_paranoid_cpu() &&
+ !capable(CAP_SYS_ADMIN))
return ERR_PTR(-EACCES);
/*
@@ -10849,6 +10861,26 @@ static struct notifier_block perf_reboot_notifier = {
.priority = INT_MIN,
};
+static int event_idle_notif(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ switch (action) {
+ case IDLE_START:
+ __this_cpu_write(is_idle, true);
+ break;
+ case IDLE_END:
+ __this_cpu_write(is_idle, false);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block perf_event_idle_nb = {
+ .notifier_call = event_idle_notif,
+};
+
+
void __init perf_event_init(void)
{
int ret;
@@ -10862,6 +10894,7 @@ void __init perf_event_init(void)
perf_pmu_register(&perf_task_clock, NULL, -1);
perf_tp_register();
perf_event_init_cpu(smp_processor_id());
+ idle_notifier_register(&perf_event_idle_nb);
register_reboot_notifier(&perf_reboot_notifier);
ret = init_hw_breakpoint();
@@ -10916,6 +10949,7 @@ static int __init perf_event_sysfs_init(void)
}
device_initcall(perf_event_sysfs_init);
+#ifdef CONFIG_HOTPLUG_CPU
static int perf_cpu_hp_init(void)
{
int ret;
@@ -10930,6 +10964,7 @@ static int perf_cpu_hp_init(void)
return ret;
}
subsys_initcall(perf_cpu_hp_init);
+#endif
#ifdef CONFIG_CGROUP_PERF
static struct cgroup_subsys_state *
diff --git a/kernel/trace/ipc_logging.c b/kernel/trace/ipc_logging.c
index fa7fd14..6d310ab 100644
--- a/kernel/trace/ipc_logging.c
+++ b/kernel/trace/ipc_logging.c
@@ -515,8 +515,8 @@ int ipc_log_string(void *ilctxt, const char *fmt, ...)
tsv_qtimer_write(&ectxt);
avail_size = (MAX_MSG_SIZE - (ectxt.offset + hdr_size));
va_start(arg_list, fmt);
- data_size = vsnprintf((ectxt.buff + ectxt.offset + hdr_size),
- avail_size, fmt, arg_list);
+ data_size = vscnprintf((ectxt.buff + ectxt.offset + hdr_size),
+ avail_size, fmt, arg_list);
va_end(arg_list);
tsv_write_header(&ectxt, TSV_TYPE_BYTE_ARRAY, data_size);
ectxt.offset += data_size;
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 8e82002..f61724f4f 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -2058,8 +2058,12 @@ static void reserve_highatomic_pageblock(struct page *page, struct zone *zone,
* potentially hurts the reliability of high-order allocations when under
* intense memory pressure but failed atomic allocations should be easier
* to recover from than an OOM.
+ *
+ * If @force is true, try to unreserve a pageblock even though highatomic
+ * pageblock is exhausted.
*/
-static void unreserve_highatomic_pageblock(const struct alloc_context *ac)
+static bool unreserve_highatomic_pageblock(const struct alloc_context *ac,
+ bool force)
{
struct zonelist *zonelist = ac->zonelist;
unsigned long flags;
@@ -2067,11 +2071,16 @@ static void unreserve_highatomic_pageblock(const struct alloc_context *ac)
struct zone *zone;
struct page *page;
int order;
+ bool ret;
for_each_zone_zonelist_nodemask(zone, z, zonelist, ac->high_zoneidx,
ac->nodemask) {
- /* Preserve at least one pageblock */
- if (zone->nr_reserved_highatomic <= pageblock_nr_pages)
+ /*
+ * Preserve at least one pageblock unless memory pressure
+ * is really high.
+ */
+ if (!force && zone->nr_reserved_highatomic <=
+ pageblock_nr_pages)
continue;
spin_lock_irqsave(&zone->lock, flags);
@@ -2085,13 +2094,25 @@ static void unreserve_highatomic_pageblock(const struct alloc_context *ac)
continue;
/*
- * It should never happen but changes to locking could
- * inadvertently allow a per-cpu drain to add pages
- * to MIGRATE_HIGHATOMIC while unreserving so be safe
- * and watch for underflows.
+ * In page freeing path, migratetype change is racy so
+ * we can counter several free pages in a pageblock
+ * in this loop althoug we changed the pageblock type
+ * from highatomic to ac->migratetype. So we should
+ * adjust the count once.
*/
- zone->nr_reserved_highatomic -= min(pageblock_nr_pages,
- zone->nr_reserved_highatomic);
+ if (get_pageblock_migratetype(page) ==
+ MIGRATE_HIGHATOMIC) {
+ /*
+ * It should never happen but changes to
+ * locking could inadvertently allow a per-cpu
+ * drain to add pages to MIGRATE_HIGHATOMIC
+ * while unreserving so be safe and watch for
+ * underflows.
+ */
+ zone->nr_reserved_highatomic -= min(
+ pageblock_nr_pages,
+ zone->nr_reserved_highatomic);
+ }
/*
* Convert to ac->migratetype and avoid the normal
@@ -2103,12 +2124,16 @@ static void unreserve_highatomic_pageblock(const struct alloc_context *ac)
* may increase.
*/
set_pageblock_migratetype(page, ac->migratetype);
- move_freepages_block(zone, page, ac->migratetype);
- spin_unlock_irqrestore(&zone->lock, flags);
- return;
+ ret = move_freepages_block(zone, page, ac->migratetype);
+ if (ret) {
+ spin_unlock_irqrestore(&zone->lock, flags);
+ return ret;
+ }
}
spin_unlock_irqrestore(&zone->lock, flags);
}
+
+ return false;
}
/* Remove an element from the buddy allocator from the fallback list */
@@ -2133,7 +2158,8 @@ __rmqueue_fallback(struct zone *zone, unsigned int order, int start_migratetype)
page = list_first_entry(&area->free_list[fallback_mt],
struct page, lru);
- if (can_steal)
+ if (can_steal &&
+ get_pageblock_migratetype(page) != MIGRATE_HIGHATOMIC)
steal_suitable_fallback(zone, page, start_migratetype);
/* Remove the page from the freelists */
@@ -2542,7 +2568,8 @@ int __isolate_free_page(struct page *page, unsigned int order)
struct page *endpage = page + (1 << order) - 1;
for (; page < endpage; page += pageblock_nr_pages) {
int mt = get_pageblock_migratetype(page);
- if (!is_migrate_isolate(mt) && !is_migrate_cma(mt))
+ if (!is_migrate_isolate(mt) && !is_migrate_cma(mt)
+ && mt != MIGRATE_HIGHATOMIC)
set_pageblock_migratetype(page,
MIGRATE_MOVABLE);
}
@@ -3313,7 +3340,7 @@ __alloc_pages_direct_reclaim(gfp_t gfp_mask, unsigned int order,
* Shrink them them and try again
*/
if (!page && !drained) {
- unreserve_highatomic_pageblock(ac);
+ unreserve_highatomic_pageblock(ac, false);
drain_all_pages(NULL);
drained = true;
goto retry;
@@ -3430,8 +3457,10 @@ should_reclaim_retry(gfp_t gfp_mask, unsigned order,
* Make sure we converge to OOM if we cannot make any progress
* several times in the row.
*/
- if (*no_progress_loops > MAX_RECLAIM_RETRIES)
- return false;
+ if (*no_progress_loops > MAX_RECLAIM_RETRIES) {
+ /* Before OOM, exhaust highatomic_reserve */
+ return unreserve_highatomic_pageblock(ac, true);
+ }
/*
* Keep reclaiming pages while there is a chance this will lead
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index eeb23d2..bc0ebd4 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -2340,6 +2340,7 @@ int regulatory_hint_user(const char *alpha2,
return 0;
}
+EXPORT_SYMBOL(regulatory_hint_user);
int regulatory_hint_indoor(bool is_indoor, u32 portid)
{
diff --git a/net/wireless/reg.h b/net/wireless/reg.h
index f6ced31..822ac90 100644
--- a/net/wireless/reg.h
+++ b/net/wireless/reg.h
@@ -28,9 +28,6 @@ bool is_world_regdom(const char *alpha2);
bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region);
enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy);
-int regulatory_hint_user(const char *alpha2,
- enum nl80211_user_reg_hint_type user_reg_hint_type);
-
/**
* regulatory_hint_indoor - hint operation in indoor env. or not
* @is_indoor: if true indicates that user space thinks that the
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index fe135b4..b4867ff 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -932,6 +932,13 @@
config SND_SOC_WCD_MBHC
tristate
default y if (SND_SOC_MSM8909_WCD=y || SND_SOC_SDM660_CDC=y || SND_SOC_WCD9335=y) && SND_SOC_MDMCALIFORNIUM!=y
+ select SND_SOC_WCD_MBHC_LEGACY
+
+config SND_SOC_WCD_MBHC_LEGACY
+ tristate
+
+config SND_SOC_WCD_MBHC_ADC
+ tristate
config SND_SOC_WCD_DSP_MGR
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 20ae32e..8c84460 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -173,7 +173,11 @@
endif
snd-soc-wcd-cpe-objs := wcd_cpe_services.o wcd_cpe_core.o
snd-soc-wsa881x-objs := wsa881x.o wsa881x-tables.o wsa881x-regmap.o wsa881x-temp-sensor.o
-snd-soc-wcd-mbhc-objs := wcd-mbhc-v2.o
+ifeq ($(CONFIG_SND_SOC_WCD_MBHC_LEGACY), y)
+ snd-soc-wcd-mbhc-objs := wcd-mbhc-v2.o wcd-mbhc-legacy.o
+else ifeq ($(CONFIG_SND_SOC_WCD_MBHC_ADC), y)
+ snd-soc-wcd-mbhc-objs := wcd-mbhc-v2.o wcd-mbhc-adc.o
+endif
snd-soc-wsa881x-analog-objs := wsa881x-analog.o wsa881x-tables-analog.o
snd-soc-wsa881x-analog-objs += wsa881x-regmap-analog.o wsa881x-irq.o
snd-soc-wcd-dsp-utils-objs := wcd-dsp-utils.o
diff --git a/sound/soc/codecs/sdm660_cdc/msm-analog-cdc.c b/sound/soc/codecs/sdm660_cdc/msm-analog-cdc.c
index 52e6815..5f8e3fd 100644
--- a/sound/soc/codecs/sdm660_cdc/msm-analog-cdc.c
+++ b/sound/soc/codecs/sdm660_cdc/msm-analog-cdc.c
@@ -32,7 +32,7 @@
#include "sdm660-cdc-registers.h"
#include "msm-cdc-common.h"
#include "../../msm/sdm660-common.h"
-#include "../wcd-mbhc-v2.h"
+#include "../wcd-mbhc-v2-api.h"
#define DRV_NAME "pmic_analog_codec"
#define SDM660_CDC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\
diff --git a/sound/soc/codecs/wcd-mbhc-adc.c b/sound/soc/codecs/wcd-mbhc-adc.c
new file mode 100644
index 0000000..2c7d667
--- /dev/null
+++ b/sound/soc/codecs/wcd-mbhc-adc.c
@@ -0,0 +1,907 @@
+/* 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.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/printk.h>
+#include <linux/ratelimit.h>
+#include <linux/list.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/firmware.h>
+#include <linux/completion.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include "wcd-mbhc-adc.h"
+#include "wcd-mbhc-v2.h"
+
+#define WCD_MBHC_ADC_HS_THRESHOLD_MV 1700
+#define WCD_MBHC_ADC_HPH_THRESHOLD_MV 75
+#define WCD_MBHC_ADC_MICBIAS_MV 1800
+
+static int wcd_mbhc_get_micbias(struct wcd_mbhc *mbhc)
+{
+ int micbias = 0;
+ u8 vout_ctl = 0;
+
+ /* Read MBHC Micbias (Mic Bias2) voltage */
+ WCD_MBHC_REG_READ(WCD_MBHC_MICB2_VOUT, vout_ctl);
+
+ /* Formula for getting micbias from vout
+ * micbias = 1.0V + VOUT_CTL * 50mV
+ */
+ micbias = 1000 + (vout_ctl * 50);
+ pr_debug("%s: vout_ctl: %d, micbias: %d\n",
+ __func__, vout_ctl, micbias);
+
+ return micbias;
+}
+
+static int wcd_get_voltage_from_adc(u8 val, int micbias)
+{
+ /* Formula for calculating voltage from ADC
+ * Voltage = ADC_RESULT*12.5mV*V_MICBIAS/1.8
+ */
+ return ((val * 125 * micbias)/(WCD_MBHC_ADC_MICBIAS_MV * 10));
+}
+
+static int wcd_measure_adc_continuous(struct wcd_mbhc *mbhc)
+{
+ u8 adc_result = 0;
+ int output_mv = 0;
+ int retry = 3;
+ u8 adc_en = 0;
+
+ pr_debug("%s: enter\n", __func__);
+
+ /* Pre-requisites for ADC continuous measurement */
+ /* Read legacy electircal detection and disable */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0x00);
+ /* Set ADC to continuous measurement */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 1);
+ /* Read ADC Enable bit to restore after adc measurement */
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_EN, adc_en);
+ /* Disable ADC_ENABLE bit */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
+ /* Disable MBHC FSM */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ /* Set the MUX selection to IN2P */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL, MUX_CTL_IN2P);
+ /* Enable MBHC FSM */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+ /* Enable ADC_ENABLE bit */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 1);
+
+ while (retry--) {
+ /* wait for 3 msec before reading ADC result */
+ usleep_range(3000, 3100);
+
+ /* Read ADC result */
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_RESULT, adc_result);
+ }
+
+ /* Restore ADC Enable */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, adc_en);
+ /* Get voltage from ADC result */
+ output_mv = wcd_get_voltage_from_adc(adc_result,
+ wcd_mbhc_get_micbias(mbhc));
+ pr_debug("%s: adc_result: 0x%x, output_mv: %d\n",
+ __func__, adc_result, output_mv);
+
+ return output_mv;
+}
+
+static int wcd_measure_adc_once(struct wcd_mbhc *mbhc, int mux_ctl)
+{
+ u8 adc_timeout = 0;
+ u8 adc_complete = 0;
+ u8 adc_result = 0;
+ int retry = 6;
+ int ret = 0;
+ int output_mv = 0;
+ u8 adc_en = 0;
+
+ pr_debug("%s: enter\n", __func__);
+
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 0);
+ /* Read ADC Enable bit to restore after adc measurement */
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_EN, adc_en);
+ /* Trigger ADC one time measurement */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ /* Set the appropriate MUX selection */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL, mux_ctl);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 1);
+
+ while (retry--) {
+ /* wait for 600usec to get adc results */
+ usleep_range(600, 610);
+
+ /* check for ADC Timeout */
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_TIMEOUT, adc_timeout);
+ if (adc_timeout)
+ continue;
+
+ /* Read ADC complete bit */
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_COMPLETE, adc_complete);
+ if (!adc_complete)
+ continue;
+
+ /* Read ADC result */
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_RESULT, adc_result);
+
+ pr_debug("%s: ADC result: 0x%x\n", __func__, adc_result);
+ /* Get voltage from ADC result */
+ output_mv = wcd_get_voltage_from_adc(adc_result,
+ wcd_mbhc_get_micbias(mbhc));
+ break;
+ }
+
+ /* Restore ADC Enable */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, adc_en);
+
+ if (retry <= 0) {
+ pr_err("%s: adc complete: %d, adc timeout: %d\n",
+ __func__, adc_complete, adc_timeout);
+ ret = -EINVAL;
+ } else {
+ pr_debug("%s: adc complete: %d, adc timeout: %d output_mV: %d\n",
+ __func__, adc_complete, adc_timeout, output_mv);
+ ret = output_mv;
+ }
+
+ pr_debug("%s: leave\n", __func__);
+
+ return ret;
+}
+
+static bool wcd_mbhc_adc_detect_anc_plug_type(struct wcd_mbhc *mbhc)
+{
+ bool anc_mic_found = false;
+ u16 fsm_en = 0;
+ u8 det = 0;
+ unsigned long retry = 0;
+ int valid_plug_cnt = 0, invalid_plug_cnt = 0;
+ int ret = 0;
+ u8 elect_ctl = 0;
+ u8 adc_mode = 0;
+ u8 vref = 0;
+ int vref_mv[] = {1650, 1500, 1600, 1700};
+
+ if (mbhc->mbhc_cfg->anc_micbias < MIC_BIAS_1 ||
+ mbhc->mbhc_cfg->anc_micbias > MIC_BIAS_4)
+ return false;
+
+ if (!mbhc->mbhc_cb->mbhc_micbias_control)
+ return false;
+
+ /* Disable Detection done for ADC operation */
+ WCD_MBHC_REG_READ(WCD_MBHC_DETECTION_DONE, det);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 0);
+
+ /* Mask ADC COMPLETE interrupt */
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
+
+ WCD_MBHC_REG_READ(WCD_MBHC_FSM_EN, fsm_en);
+ mbhc->mbhc_cb->mbhc_micbias_control(mbhc->codec,
+ mbhc->mbhc_cfg->anc_micbias,
+ MICB_ENABLE);
+
+ /* Read legacy electircal detection and disable */
+ WCD_MBHC_REG_READ(WCD_MBHC_ELECT_SCHMT_ISRC, elect_ctl);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0x00);
+
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ANC_DET_EN, 1);
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_MODE, adc_mode);
+
+ /*
+ * wait for button debounce time 20ms. If 4-pole plug is inserted
+ * into 5-pole jack, then there will be a button press interrupt
+ * during anc plug detection. In that case though Hs_comp_res is 0,
+ * it should not be declared as ANC plug type
+ */
+ usleep_range(20000, 20100);
+
+ /*
+ * After enabling FSM, to handle slow insertion scenarios,
+ * check IN3 voltage is below the Vref
+ */
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_VREF, vref);
+
+ do {
+ if (wcd_swch_level_remove(mbhc)) {
+ pr_debug("%s: Switch level is low\n", __func__);
+ goto done;
+ }
+ pr_debug("%s: Retry attempt %lu\n", __func__, retry + 1);
+ ret = wcd_measure_adc_once(mbhc, MUX_CTL_IN3P);
+ /* TODO - check the logic */
+ if (ret && (ret < vref_mv[vref]))
+ valid_plug_cnt++;
+ else
+ invalid_plug_cnt++;
+ retry++;
+ } while (retry < ANC_DETECT_RETRY_CNT);
+
+ pr_debug("%s: valid: %d, invalid: %d\n", __func__, valid_plug_cnt,
+ invalid_plug_cnt);
+
+ /* decision logic */
+ if (valid_plug_cnt > invalid_plug_cnt)
+ anc_mic_found = true;
+done:
+ /* Restore ADC mode */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, adc_mode);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ANC_DET_EN, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ /* Set the MUX selection to AUTO */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL, MUX_CTL_AUTO);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, fsm_en);
+ /* Restore detection done */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, det);
+
+ /* Restore electrical detection */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, elect_ctl);
+
+ mbhc->mbhc_cb->mbhc_micbias_control(mbhc->codec,
+ mbhc->mbhc_cfg->anc_micbias,
+ MICB_DISABLE);
+ pr_debug("%s: anc mic %sfound\n", __func__,
+ anc_mic_found ? "" : "not ");
+
+ return anc_mic_found;
+}
+
+/* To determine if cross connection occurred */
+static int wcd_check_cross_conn(struct wcd_mbhc *mbhc)
+{
+ enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_NONE;
+ int hphl_adc_res = 0, hphr_adc_res = 0;
+ u8 fsm_en = 0;
+ int ret = 0;
+ u8 adc_mode = 0;
+ u8 elect_ctl = 0;
+ u8 adc_en = 0;
+
+ pr_debug("%s: enter\n", __func__);
+ /* Check for button press and plug detection */
+ if (wcd_swch_level_remove(mbhc)) {
+ pr_debug("%s: Switch level is low\n", __func__);
+ return -EINVAL;
+ }
+
+ /* If PA is enabled, dont check for cross-connection */
+ if (mbhc->mbhc_cb->hph_pa_on_status)
+ if (mbhc->mbhc_cb->hph_pa_on_status(mbhc->codec))
+ return -EINVAL;
+
+ /* Read legacy electircal detection and disable */
+ WCD_MBHC_REG_READ(WCD_MBHC_ELECT_SCHMT_ISRC, elect_ctl);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0x00);
+
+ /* Read and set ADC to single measurement */
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_MODE, adc_mode);
+ /* Read ADC Enable bit to restore after adc measurement */
+ WCD_MBHC_REG_READ(WCD_MBHC_ADC_EN, adc_en);
+ /* Read FSM status */
+ WCD_MBHC_REG_READ(WCD_MBHC_FSM_EN, fsm_en);
+
+ /* Get adc result for HPH L */
+ hphl_adc_res = wcd_measure_adc_once(mbhc, MUX_CTL_HPH_L);
+ if (hphl_adc_res < 0) {
+ pr_err("%s: hphl_adc_res adc measurement failed\n", __func__);
+ ret = hphl_adc_res;
+ goto done;
+ }
+
+ /* Get adc result for HPH R in mV */
+ hphr_adc_res = wcd_measure_adc_once(mbhc, MUX_CTL_HPH_R);
+ if (hphr_adc_res < 0) {
+ pr_err("%s: hphr_adc_res adc measurement failed\n", __func__);
+ ret = hphr_adc_res;
+ goto done;
+ }
+
+ if (hphl_adc_res > 100 && hphr_adc_res > 100) {
+ plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
+ pr_debug("%s: Cross connection identified\n", __func__);
+ } else {
+ pr_debug("%s: No Cross connection found\n", __func__);
+ }
+
+done:
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ /* Set the MUX selection to Auto */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL, MUX_CTL_AUTO);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+
+ /* Restore ADC Enable */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, adc_en);
+
+ /* Restore ADC mode */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, adc_mode);
+
+ /* Restore FSM state */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, fsm_en);
+
+ /* Restore electrical detection */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, elect_ctl);
+
+ pr_debug("%s: leave, plug type: %d\n", __func__, plug_type);
+
+ return (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP) ? true : false;
+}
+
+static bool wcd_mbhc_adc_check_for_spl_headset(struct wcd_mbhc *mbhc,
+ int *spl_hs_cnt)
+{
+ bool spl_hs = false;
+ int output_mv = 0;
+ int adc_threshold = 0;
+
+ pr_debug("%s: enter\n", __func__);
+ if (!mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic)
+ goto exit;
+
+ /* Bump up MB2 to 2.7V */
+ mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(mbhc->codec,
+ mbhc->mbhc_cfg->mbhc_micbias, true);
+ usleep_range(10000, 10100);
+
+ /*
+ * Use ADC single mode to minimize the chance of missing out
+ * btn press/relesae for HEADSET type during correct work.
+ */
+ output_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P);
+ adc_threshold = ((WCD_MBHC_ADC_HS_THRESHOLD_MV *
+ wcd_mbhc_get_micbias(mbhc))/WCD_MBHC_ADC_MICBIAS_MV);
+
+ if (output_mv > adc_threshold) {
+ spl_hs = false;
+ } else {
+ spl_hs = true;
+ if (spl_hs_cnt)
+ *spl_hs_cnt += 1;
+ }
+
+ /* MB2 back to 1.8v if the type is not special headset */
+ if (!spl_hs) {
+ mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(mbhc->codec,
+ mbhc->mbhc_cfg->mbhc_micbias, false);
+ /* Add 10ms delay for micbias to settle */
+ usleep_range(10000, 10100);
+ } else {
+ pr_debug("%s: Detected special HS (%d)\n", __func__, spl_hs);
+ }
+
+exit:
+ pr_debug("%s: leave\n", __func__);
+ return spl_hs;
+}
+
+static bool wcd_is_special_headset(struct wcd_mbhc *mbhc)
+{
+ int delay = 0;
+ bool ret = false;
+ bool is_spl_hs = false;
+ int spl_hs_count = 0;
+
+ while (!is_spl_hs) {
+ delay += 50;
+ if (mbhc->hs_detect_work_stop) {
+ pr_debug("%s: stop requested: %d\n", __func__,
+ mbhc->hs_detect_work_stop);
+ break;
+ }
+ /* Wait for 50ms for FSM to update result */
+ msleep(50);
+ is_spl_hs = wcd_mbhc_adc_check_for_spl_headset(mbhc,
+ &spl_hs_count);
+ if (is_spl_hs)
+ pr_debug("%s: Spl headset detected in %d msecs\n",
+ __func__, delay);
+ if (delay == SPECIAL_HS_DETECT_TIME_MS) {
+ pr_debug("%s: Spl headset not found in 2 sec\n",
+ __func__);
+ break;
+ }
+ }
+ pr_debug("%s: leave, micb_enable: %d\n", __func__,
+ mbhc->micbias_enable);
+
+ return ret;
+}
+
+static void wcd_mbhc_adc_update_fsm_source(struct wcd_mbhc *mbhc,
+ enum wcd_mbhc_plug_type plug_type)
+{
+ bool micbias2;
+
+ micbias2 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
+ MIC_BIAS_2);
+ switch (plug_type) {
+ case MBHC_PLUG_TYPE_HEADPHONE:
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 3);
+ break;
+ case MBHC_PLUG_TYPE_HEADSET:
+ case MBHC_PLUG_TYPE_ANC_HEADPHONE:
+ if (!mbhc->is_hs_recording && !micbias2)
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 3);
+ break;
+ default:
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0);
+ break;
+
+ };
+}
+
+/* should be called under interrupt context that hold suspend */
+static void wcd_schedule_hs_detect_plug(struct wcd_mbhc *mbhc,
+ struct work_struct *work)
+{
+ pr_debug("%s: scheduling correct_swch_plug\n", __func__);
+ WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);
+ mbhc->hs_detect_work_stop = false;
+ mbhc->mbhc_cb->lock_sleep(mbhc, true);
+ schedule_work(work);
+}
+
+/* called under codec_resource_lock acquisition */
+static void wcd_cancel_hs_detect_plug(struct wcd_mbhc *mbhc,
+ struct work_struct *work)
+{
+ pr_debug("%s: Canceling correct_plug_swch\n", __func__);
+ mbhc->hs_detect_work_stop = true;
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ if (cancel_work_sync(work)) {
+ pr_debug("%s: correct_plug_swch is canceled\n",
+ __func__);
+ mbhc->mbhc_cb->lock_sleep(mbhc, false);
+ }
+ WCD_MBHC_RSC_LOCK(mbhc);
+}
+
+/* called under codec_resource_lock acquisition */
+static void wcd_mbhc_adc_detect_plug_type(struct wcd_mbhc *mbhc)
+{
+ struct snd_soc_codec *codec = mbhc->codec;
+
+ pr_debug("%s: enter\n", __func__);
+ WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);
+
+ if (mbhc->mbhc_cb->hph_pull_down_ctrl)
+ mbhc->mbhc_cb->hph_pull_down_ctrl(codec, false);
+
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 0);
+
+ if (mbhc->mbhc_cb->mbhc_micbias_control) {
+ mbhc->mbhc_cb->mbhc_micbias_control(codec, MIC_BIAS_2,
+ MICB_ENABLE);
+ } else {
+ pr_err("%s: Mic Bias is not enabled\n", __func__);
+ return;
+ }
+
+ /* Re-initialize button press completion object */
+ reinit_completion(&mbhc->btn_press_compl);
+ wcd_schedule_hs_detect_plug(mbhc, &mbhc->correct_plug_swch);
+ pr_debug("%s: leave\n", __func__);
+}
+
+static void wcd_micbias_disable(struct wcd_mbhc *mbhc)
+{
+ if (mbhc->micbias_enable) {
+ mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(
+ mbhc->codec, MIC_BIAS_2, false);
+ if (mbhc->mbhc_cb->set_micbias_value)
+ mbhc->mbhc_cb->set_micbias_value(
+ mbhc->codec);
+ mbhc->micbias_enable = false;
+ }
+}
+
+static int wcd_mbhc_get_plug_from_adc(int adc_result)
+
+{
+ enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_INVALID;
+
+ if (adc_result < WCD_MBHC_ADC_HPH_THRESHOLD_MV)
+ plug_type = MBHC_PLUG_TYPE_HEADPHONE;
+ else if (adc_result > WCD_MBHC_ADC_HS_THRESHOLD_MV)
+ plug_type = MBHC_PLUG_TYPE_HIGH_HPH;
+ else
+ plug_type = MBHC_PLUG_TYPE_HEADSET;
+ pr_debug("%s: plug type is %d found\n", __func__, plug_type);
+
+ return plug_type;
+}
+
+static int wcd_mbhc_get_plug_type(struct wcd_mbhc *mbhc)
+{
+ int result_mv = 0;
+
+ /*
+ * Use ADC single mode to minimize the chance of missing out
+ * btn press/release for HEADSET type during correct work.
+ */
+ result_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P);
+
+ return wcd_mbhc_get_plug_from_adc(result_mv);
+}
+
+static void wcd_correct_swch_plug(struct work_struct *work)
+{
+ struct wcd_mbhc *mbhc;
+ struct snd_soc_codec *codec;
+ enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_INVALID;
+ unsigned long timeout;
+ bool wrk_complete = false;
+ int gnd_mic_swap_cnt = 0;
+ bool is_pa_on = false, spl_hs = false;
+ int ret = 0;
+ int spl_hs_count = 0;
+ int output_mv = 0;
+ int cross_conn;
+ int try = 0;
+
+ pr_debug("%s: enter\n", __func__);
+
+ mbhc = container_of(work, struct wcd_mbhc, correct_plug_swch);
+ codec = mbhc->codec;
+
+ WCD_MBHC_RSC_LOCK(mbhc);
+ /* Mask ADC COMPLETE interrupt */
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+
+ /* Check for cross connection */
+ do {
+ cross_conn = wcd_check_cross_conn(mbhc);
+ try++;
+ } while (try < GND_MIC_SWAP_THRESHOLD);
+
+ if (cross_conn > 0) {
+ plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
+ pr_debug("%s: cross connection found, Plug type %d\n",
+ __func__, plug_type);
+ goto correct_plug_type;
+ }
+ /* Find plug type */
+ output_mv = wcd_measure_adc_continuous(mbhc);
+ plug_type = wcd_mbhc_get_plug_from_adc(output_mv);
+
+ /*
+ * Report plug type if it is either headset or headphone
+ * else start the 3 sec loop
+ */
+ if ((plug_type == MBHC_PLUG_TYPE_HEADSET ||
+ plug_type == MBHC_PLUG_TYPE_HEADPHONE) &&
+ (!wcd_swch_level_remove(mbhc))) {
+ WCD_MBHC_RSC_LOCK(mbhc);
+ wcd_mbhc_find_plug_and_report(mbhc, plug_type);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ }
+
+ /*
+ * Set DETECTION_DONE bit for HEADSET and ANC_HEADPHONE,
+ * so that btn press/release interrupt can be generated.
+ */
+ if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET ||
+ mbhc->current_plug == MBHC_PLUG_TYPE_ANC_HEADPHONE) {
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 1);
+ }
+
+correct_plug_type:
+ timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS);
+ while (!time_after(jiffies, timeout)) {
+ if (mbhc->hs_detect_work_stop) {
+ pr_debug("%s: stop requested: %d\n", __func__,
+ mbhc->hs_detect_work_stop);
+ wcd_micbias_disable(mbhc);
+ goto exit;
+ }
+
+ /* allow sometime and re-check stop requested again */
+ msleep(20);
+ if (mbhc->hs_detect_work_stop) {
+ pr_debug("%s: stop requested: %d\n", __func__,
+ mbhc->hs_detect_work_stop);
+ wcd_micbias_disable(mbhc);
+ goto exit;
+ }
+
+ msleep(180);
+ /*
+ * Use ADC single mode to minimize the chance of missing out
+ * btn press/release for HEADSET type during correct work.
+ */
+ output_mv = wcd_measure_adc_once(mbhc, MUX_CTL_IN2P);
+
+ /*
+ * instead of hogging system by contineous polling, wait for
+ * sometime and re-check stop request again.
+ */
+ plug_type = wcd_mbhc_get_plug_from_adc(output_mv);
+
+ if ((output_mv > WCD_MBHC_ADC_HS_THRESHOLD_MV) &&
+ (spl_hs_count < WCD_MBHC_SPL_HS_CNT)) {
+ spl_hs = wcd_mbhc_adc_check_for_spl_headset(mbhc,
+ &spl_hs_count);
+
+ if (spl_hs_count == WCD_MBHC_SPL_HS_CNT)
+ mbhc->micbias_enable = true;
+ }
+
+ if (mbhc->mbhc_cb->hph_pa_on_status)
+ is_pa_on = mbhc->mbhc_cb->hph_pa_on_status(mbhc->codec);
+
+ if ((output_mv <= WCD_MBHC_ADC_HS_THRESHOLD_MV) &&
+ (!is_pa_on)) {
+ /* Check for cross connection*/
+ ret = wcd_check_cross_conn(mbhc);
+ if (ret < 0)
+ continue;
+ if (ret > 0) {
+ /* Found cross connection, swap mic/gnd */
+ if (gnd_mic_swap_cnt > GND_MIC_SWAP_THRESHOLD) {
+ /*
+ * This is due to GND/MIC switch didn't
+ * work, Report unsupported plug.
+ */
+ pr_debug("%s: switch did not work\n",
+ __func__);
+ plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
+ goto report;
+ }
+ gnd_mic_swap_cnt++;
+ if (mbhc->mbhc_cfg->swap_gnd_mic &&
+ mbhc->mbhc_cfg->swap_gnd_mic(codec)) {
+ pr_debug("%s: US_EU gpio present,flip switch\n"
+ , __func__);
+ continue;
+ }
+ } else {
+ gnd_mic_swap_cnt++;
+ plug_type = wcd_mbhc_get_plug_type(mbhc);
+ if ((gnd_mic_swap_cnt <=
+ GND_MIC_SWAP_THRESHOLD) &&
+ (spl_hs_count != WCD_MBHC_SPL_HS_CNT)) {
+ continue;
+ } else {
+ gnd_mic_swap_cnt = 0;
+ }
+ }
+ }
+
+ if (!spl_hs && (plug_type == MBHC_PLUG_TYPE_HIGH_HPH)) {
+ pr_debug("%s: cable is extension cable\n", __func__);
+ wrk_complete = true;
+ } else {
+ if (plug_type != MBHC_PLUG_TYPE_GND_MIC_SWAP) {
+ if (!spl_hs)
+ plug_type =
+ wcd_mbhc_get_plug_type(mbhc);
+ else
+ plug_type = MBHC_PLUG_TYPE_HEADSET;
+ /*
+ * Report headset only if not already reported
+ * and if there is not button press without
+ * release
+ */
+ if ((mbhc->current_plug !=
+ MBHC_PLUG_TYPE_HEADSET) &&
+ (mbhc->current_plug !=
+ MBHC_PLUG_TYPE_ANC_HEADPHONE)) {
+ if (plug_type == MBHC_PLUG_TYPE_HEADSET)
+ pr_debug("%s: cable is %s headset\n",
+ __func__,
+ ((spl_hs) ?
+ "special ":""));
+ goto report;
+ }
+ }
+ wrk_complete = false;
+ }
+ }
+ if (!wrk_complete) {
+ /*
+ * If plug_tye is headset, we might have already reported either
+ * in detect_plug-type or in above while loop, no need to report
+ * again
+ */
+ if ((plug_type == MBHC_PLUG_TYPE_HEADSET) ||
+ (plug_type == MBHC_PLUG_TYPE_ANC_HEADPHONE)) {
+ pr_debug("%s: plug_type:0x%x already reported\n",
+ __func__, mbhc->current_plug);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
+ goto enable_supply;
+ }
+ }
+ if (plug_type == MBHC_PLUG_TYPE_HIGH_HPH) {
+ if (wcd_is_special_headset(mbhc)) {
+ pr_debug("%s: Special headset found %d\n",
+ __func__, plug_type);
+ plug_type = MBHC_PLUG_TYPE_HEADSET;
+ } else {
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_ISRC_EN, 1);
+ }
+ }
+
+report:
+ if (wcd_swch_level_remove(mbhc)) {
+ pr_debug("%s: Switch level is low\n", __func__);
+ goto exit;
+ }
+
+ pr_debug("%s: Valid plug found, plug type %d wrk_cmpt %d btn_intr %d\n",
+ __func__, plug_type, wrk_complete,
+ mbhc->btn_press_intr);
+
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
+
+ WCD_MBHC_RSC_LOCK(mbhc);
+ wcd_mbhc_find_plug_and_report(mbhc, plug_type);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+enable_supply:
+ /*
+ * Set DETECTION_DONE bit for HEADSET and ANC_HEADPHONE,
+ * so that btn press/release interrupt can be generated.
+ * For other plug type, clear the bit.
+ */
+ if (plug_type == MBHC_PLUG_TYPE_HEADSET ||
+ plug_type == MBHC_PLUG_TYPE_ANC_HEADPHONE)
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 1);
+ else
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 0);
+
+ if (mbhc->mbhc_cb->mbhc_micbias_control)
+ wcd_mbhc_adc_update_fsm_source(mbhc, plug_type);
+exit:
+ if (mbhc->mbhc_cb->mbhc_micbias_control &&
+ !mbhc->micbias_enable)
+ mbhc->mbhc_cb->mbhc_micbias_control(codec, MIC_BIAS_2,
+ MICB_DISABLE);
+ if (mbhc->mbhc_cfg->detect_extn_cable &&
+ ((plug_type == MBHC_PLUG_TYPE_HEADPHONE) ||
+ (plug_type == MBHC_PLUG_TYPE_HEADSET)) &&
+ !mbhc->hs_detect_work_stop) {
+ WCD_MBHC_RSC_LOCK(mbhc);
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM, true);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ }
+
+ /*
+ * Enable ADC COMPLETE interrupt for HEADPHONE.
+ * Btn release may happen after the correct work, ADC COMPLETE
+ * interrupt needs to be captured to correct plug type.
+ */
+ if (plug_type == MBHC_PLUG_TYPE_HEADPHONE) {
+ WCD_MBHC_RSC_LOCK(mbhc);
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
+ true);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ }
+
+ if (mbhc->mbhc_cb->hph_pull_down_ctrl)
+ mbhc->mbhc_cb->hph_pull_down_ctrl(codec, true);
+
+ mbhc->mbhc_cb->lock_sleep(mbhc, false);
+ pr_debug("%s: leave\n", __func__);
+}
+
+static irqreturn_t wcd_mbhc_adc_hs_rem_irq(int irq, void *data)
+{
+ struct wcd_mbhc *mbhc = data;
+
+ pr_debug("%s: enter\n", __func__);
+ WCD_MBHC_RSC_LOCK(mbhc);
+ /*
+ * ADC COMPLETE and ELEC_REM interrupts are both enabled for HEADPHONE,
+ * need to reject the ADC COMPLETE interrupt which follows ELEC_REM one
+ * when HEADPHONE is removed.
+ */
+ if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE)
+ mbhc->extn_cable_hph_rem = true;
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_MODE, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ADC_EN, 0);
+ wcd_mbhc_elec_hs_report_unplug(mbhc);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ pr_debug("%s: leave\n", __func__);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t wcd_mbhc_adc_hs_ins_irq(int irq, void *data)
+{
+ struct wcd_mbhc *mbhc = data;
+
+ pr_debug("%s: enter\n", __func__);
+
+ /*
+ * ADC COMPLETE and ELEC_REM interrupts are both enabled for HEADPHONE,
+ * need to reject the ADC COMPLETE interrupt which follows ELEC_REM one
+ * when HEADPHONE is removed.
+ */
+ if (mbhc->extn_cable_hph_rem == true) {
+ mbhc->extn_cable_hph_rem = false;
+ pr_debug("%s: leave\n", __func__);
+ return IRQ_HANDLED;
+ }
+
+ WCD_MBHC_RSC_LOCK(mbhc);
+ /*
+ * If current plug is headphone then there is no chance to
+ * get ADC complete interrupt, so connected cable should be
+ * headset not headphone.
+ */
+ if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_DETECTION_DONE, 1);
+ wcd_mbhc_find_plug_and_report(mbhc, MBHC_PLUG_TYPE_HEADSET);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ return IRQ_HANDLED;
+ }
+
+ if (!mbhc->mbhc_cfg->detect_extn_cable) {
+ pr_debug("%s: Returning as Extension cable feature not enabled\n",
+ __func__);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ return IRQ_HANDLED;
+ }
+
+ pr_debug("%s: Disable electrical headset insertion interrupt\n",
+ __func__);
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_ISRC_EN, 0);
+ mbhc->is_extn_cable = true;
+ mbhc->btn_press_intr = false;
+ wcd_mbhc_adc_detect_plug_type(mbhc);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ pr_debug("%s: leave\n", __func__);
+ return IRQ_HANDLED;
+}
+
+static struct wcd_mbhc_fn mbhc_fn = {
+ .wcd_mbhc_hs_ins_irq = wcd_mbhc_adc_hs_ins_irq,
+ .wcd_mbhc_hs_rem_irq = wcd_mbhc_adc_hs_rem_irq,
+ .wcd_mbhc_detect_plug_type = wcd_mbhc_adc_detect_plug_type,
+ .wcd_mbhc_detect_anc_plug_type = wcd_mbhc_adc_detect_anc_plug_type,
+ .wcd_cancel_hs_detect_plug = wcd_cancel_hs_detect_plug,
+};
+
+/* Function: wcd_mbhc_adc_init
+ * @mbhc: MBHC function pointer
+ * Description: Initialize MBHC ADC related function pointers to MBHC structure
+ */
+void wcd_mbhc_adc_init(struct wcd_mbhc *mbhc)
+{
+ if (!mbhc) {
+ pr_err("%s: mbhc is NULL\n", __func__);
+ return;
+ }
+ mbhc->mbhc_fn = &mbhc_fn;
+ INIT_WORK(&mbhc->correct_plug_swch, wcd_correct_swch_plug);
+}
+EXPORT_SYMBOL(wcd_mbhc_adc_init);
diff --git a/sound/soc/codecs/wcd-mbhc-adc.h b/sound/soc/codecs/wcd-mbhc-adc.h
new file mode 100644
index 0000000..112d508
--- /dev/null
+++ b/sound/soc/codecs/wcd-mbhc-adc.h
@@ -0,0 +1,35 @@
+/* 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.
+ */
+#ifndef __WCD_MBHC_ADC_H__
+#define __WCD_MBHC_ADC_H__
+
+#include "wcd-mbhc-v2.h"
+
+enum wcd_mbhc_adc_mux_ctl {
+ MUX_CTL_AUTO = 0,
+ MUX_CTL_IN2P,
+ MUX_CTL_IN3P,
+ MUX_CTL_IN4P,
+ MUX_CTL_HPH_L,
+ MUX_CTL_HPH_R,
+ MUX_CTL_NONE,
+};
+
+#ifdef CONFIG_SND_SOC_WCD_MBHC_ADC
+void wcd_mbhc_adc_init(struct wcd_mbhc *mbhc);
+#else
+static inline void wcd_mbhc_adc_init(struct wcd_mbhc *mbhc)
+{
+
+}
+#endif
+#endif /* __WCD_MBHC_ADC_H__ */
diff --git a/sound/soc/codecs/wcd-mbhc-legacy.c b/sound/soc/codecs/wcd-mbhc-legacy.c
new file mode 100644
index 0000000..ffba7f6
--- /dev/null
+++ b/sound/soc/codecs/wcd-mbhc-legacy.c
@@ -0,0 +1,941 @@
+/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/device.h>
+#include <linux/printk.h>
+#include <linux/ratelimit.h>
+#include <linux/list.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/firmware.h>
+#include <linux/completion.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include "wcd-mbhc-legacy.h"
+#include "wcd-mbhc-v2.h"
+
+static int det_extn_cable_en;
+module_param(det_extn_cable_en, int, 0664);
+MODULE_PARM_DESC(det_extn_cable_en, "enable/disable extn cable detect");
+
+static bool wcd_mbhc_detect_anc_plug_type(struct wcd_mbhc *mbhc)
+{
+ bool anc_mic_found = false;
+ u16 val, hs_comp_res, btn_status = 0;
+ unsigned long retry = 0;
+ int valid_plug_cnt = 0, invalid_plug_cnt = 0;
+ int btn_status_cnt = 0;
+ bool is_check_btn_press = false;
+
+
+ if (mbhc->mbhc_cfg->anc_micbias < MIC_BIAS_1 ||
+ mbhc->mbhc_cfg->anc_micbias > MIC_BIAS_4)
+ return false;
+
+ if (!mbhc->mbhc_cb->mbhc_micbias_control)
+ return false;
+
+ WCD_MBHC_REG_READ(WCD_MBHC_FSM_EN, val);
+
+ if (val)
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+
+ mbhc->mbhc_cb->mbhc_micbias_control(mbhc->codec,
+ mbhc->mbhc_cfg->anc_micbias,
+ MICB_ENABLE);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL, 0x2);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ANC_DET_EN, 1);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+ /*
+ * wait for button debounce time 20ms. If 4-pole plug is inserted
+ * into 5-pole jack, then there will be a button press interrupt
+ * during anc plug detection. In that case though Hs_comp_res is 0,
+ * it should not be declared as ANC plug type
+ */
+ usleep_range(20000, 20100);
+
+ /*
+ * After enabling FSM, to handle slow insertion scenarios,
+ * check hs_comp_result for few times to see if the IN3 voltage
+ * is below the Vref
+ */
+ do {
+ if (wcd_swch_level_remove(mbhc)) {
+ pr_debug("%s: Switch level is low\n", __func__);
+ goto exit;
+ }
+ pr_debug("%s: Retry attempt %lu\n", __func__, retry + 1);
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
+
+ if (!hs_comp_res) {
+ valid_plug_cnt++;
+ is_check_btn_press = true;
+ } else
+ invalid_plug_cnt++;
+ /* Wait 1ms before taking another reading */
+ usleep_range(1000, 1100);
+
+ WCD_MBHC_REG_READ(WCD_MBHC_FSM_STATUS, btn_status);
+ if (btn_status)
+ btn_status_cnt++;
+
+ retry++;
+ } while (retry < ANC_DETECT_RETRY_CNT);
+
+ pr_debug("%s: valid: %d, invalid: %d, btn_status_cnt: %d\n",
+ __func__, valid_plug_cnt, invalid_plug_cnt, btn_status_cnt);
+
+ /* decision logic */
+ if ((valid_plug_cnt > invalid_plug_cnt) && is_check_btn_press &&
+ (btn_status_cnt == 0))
+ anc_mic_found = true;
+exit:
+ if (!val)
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ANC_DET_EN, 0);
+
+ mbhc->mbhc_cb->mbhc_micbias_control(mbhc->codec,
+ mbhc->mbhc_cfg->anc_micbias,
+ MICB_DISABLE);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL, 0x0);
+ pr_debug("%s: anc mic %sfound\n", __func__,
+ anc_mic_found ? "" : "not ");
+ return anc_mic_found;
+}
+
+/* To determine if cross connection occurred */
+static int wcd_check_cross_conn(struct wcd_mbhc *mbhc)
+{
+ u16 swap_res = 0;
+ enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_NONE;
+ s16 reg1 = 0;
+ bool hphl_sch_res = 0, hphr_sch_res = 0;
+
+ if (wcd_swch_level_remove(mbhc)) {
+ pr_debug("%s: Switch level is low\n", __func__);
+ return -EINVAL;
+ }
+
+ /* If PA is enabled, dont check for cross-connection */
+ if (mbhc->mbhc_cb->hph_pa_on_status)
+ if (mbhc->mbhc_cb->hph_pa_on_status(mbhc->codec))
+ return false;
+
+ WCD_MBHC_REG_READ(WCD_MBHC_ELECT_SCHMT_ISRC, reg1);
+ /*
+ * Check if there is any cross connection,
+ * Micbias and schmitt trigger (HPHL-HPHR)
+ * needs to be enabled. For some codecs like wcd9335,
+ * pull-up will already be enabled when this function
+ * is called for cross-connection identification. No
+ * need to enable micbias in that case.
+ */
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 2);
+
+ WCD_MBHC_REG_READ(WCD_MBHC_ELECT_RESULT, swap_res);
+ pr_debug("%s: swap_res%x\n", __func__, swap_res);
+
+ /*
+ * Read reg hphl and hphr schmitt result with cross connection
+ * bit. These bits will both be "0" in case of cross connection
+ * otherwise, they stay at 1
+ */
+ WCD_MBHC_REG_READ(WCD_MBHC_HPHL_SCHMT_RESULT, hphl_sch_res);
+ WCD_MBHC_REG_READ(WCD_MBHC_HPHR_SCHMT_RESULT, hphr_sch_res);
+ if (!(hphl_sch_res || hphr_sch_res)) {
+ plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
+ pr_debug("%s: Cross connection identified\n", __func__);
+ } else {
+ pr_debug("%s: No Cross connection found\n", __func__);
+ }
+
+ /* Disable schmitt trigger and restore micbias */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, reg1);
+ pr_debug("%s: leave, plug type: %d\n", __func__, plug_type);
+
+ return (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP) ? true : false;
+}
+
+static bool wcd_is_special_headset(struct wcd_mbhc *mbhc)
+{
+ struct snd_soc_codec *codec = mbhc->codec;
+ int delay = 0, rc;
+ bool ret = false;
+ u16 hs_comp_res;
+ bool is_spl_hs = false;
+
+ /*
+ * Increase micbias to 2.7V to detect headsets with
+ * threshold on microphone
+ */
+ if (mbhc->mbhc_cb->mbhc_micbias_control &&
+ !mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic) {
+ pr_debug("%s: callback fn micb_ctrl_thr_mic not defined\n",
+ __func__);
+ return false;
+ } else if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic) {
+ rc = mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(codec,
+ MIC_BIAS_2, true);
+ if (rc) {
+ pr_err("%s: Micbias control for thr mic failed, rc: %d\n",
+ __func__, rc);
+ return false;
+ }
+ }
+
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
+
+ pr_debug("%s: special headset, start register writes\n", __func__);
+
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
+ while (!is_spl_hs) {
+ if (mbhc->hs_detect_work_stop) {
+ pr_debug("%s: stop requested: %d\n", __func__,
+ mbhc->hs_detect_work_stop);
+ break;
+ }
+ delay = delay + 50;
+ if (mbhc->mbhc_cb->mbhc_common_micb_ctrl) {
+ mbhc->mbhc_cb->mbhc_common_micb_ctrl(codec,
+ MBHC_COMMON_MICB_PRECHARGE,
+ true);
+ mbhc->mbhc_cb->mbhc_common_micb_ctrl(codec,
+ MBHC_COMMON_MICB_SET_VAL,
+ true);
+ }
+ /* Wait for 50msec for MICBIAS to settle down */
+ msleep(50);
+ if (mbhc->mbhc_cb->set_auto_zeroing)
+ mbhc->mbhc_cb->set_auto_zeroing(codec, true);
+ /* Wait for 50msec for FSM to update result values */
+ msleep(50);
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
+ if (!(hs_comp_res)) {
+ pr_debug("%s: Special headset detected in %d msecs\n",
+ __func__, (delay * 2));
+ is_spl_hs = true;
+ }
+ if (delay == SPECIAL_HS_DETECT_TIME_MS) {
+ pr_debug("%s: Spl headset didn't get detect in 4 sec\n",
+ __func__);
+ break;
+ }
+ }
+ if (is_spl_hs) {
+ pr_debug("%s: Headset with threshold found\n", __func__);
+ mbhc->micbias_enable = true;
+ ret = true;
+ }
+ if (mbhc->mbhc_cb->mbhc_common_micb_ctrl)
+ mbhc->mbhc_cb->mbhc_common_micb_ctrl(codec,
+ MBHC_COMMON_MICB_PRECHARGE,
+ false);
+ if (mbhc->mbhc_cb->set_micbias_value && !mbhc->micbias_enable)
+ mbhc->mbhc_cb->set_micbias_value(codec);
+ if (mbhc->mbhc_cb->set_auto_zeroing)
+ mbhc->mbhc_cb->set_auto_zeroing(codec, false);
+
+ if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic &&
+ !mbhc->micbias_enable)
+ mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(codec, MIC_BIAS_2,
+ false);
+
+ pr_debug("%s: leave, micb_enable: %d\n", __func__,
+ mbhc->micbias_enable);
+ return ret;
+}
+
+static void wcd_mbhc_update_fsm_source(struct wcd_mbhc *mbhc,
+ enum wcd_mbhc_plug_type plug_type)
+{
+ bool micbias2;
+
+ micbias2 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
+ MIC_BIAS_2);
+ switch (plug_type) {
+ case MBHC_PLUG_TYPE_HEADPHONE:
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 3);
+ break;
+ case MBHC_PLUG_TYPE_HEADSET:
+ case MBHC_PLUG_TYPE_ANC_HEADPHONE:
+ if (!mbhc->is_hs_recording && !micbias2)
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 3);
+ break;
+ default:
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0);
+ break;
+
+ };
+}
+
+static void wcd_enable_mbhc_supply(struct wcd_mbhc *mbhc,
+ enum wcd_mbhc_plug_type plug_type)
+{
+
+ struct snd_soc_codec *codec = mbhc->codec;
+
+ /*
+ * Do not disable micbias if recording is going on or
+ * headset is inserted on the other side of the extn
+ * cable. If headset has been detected current source
+ * needs to be kept enabled for button detection to work.
+ * If the accessory type is invalid or unsupported, we
+ * dont need to enable either of them.
+ */
+ if (det_extn_cable_en && mbhc->is_extn_cable &&
+ mbhc->mbhc_cb && mbhc->mbhc_cb->extn_use_mb &&
+ mbhc->mbhc_cb->extn_use_mb(codec)) {
+ if (plug_type == MBHC_PLUG_TYPE_HEADPHONE ||
+ plug_type == MBHC_PLUG_TYPE_HEADSET)
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
+ } else {
+ if (plug_type == MBHC_PLUG_TYPE_HEADSET) {
+ if (mbhc->is_hs_recording || mbhc->micbias_enable) {
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
+ } else if ((test_bit(WCD_MBHC_EVENT_PA_HPHL,
+ &mbhc->event_state)) ||
+ (test_bit(WCD_MBHC_EVENT_PA_HPHR,
+ &mbhc->event_state))) {
+ wcd_enable_curr_micbias(mbhc,
+ WCD_MBHC_EN_PULLUP);
+ } else {
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_CS);
+ }
+ } else if (plug_type == MBHC_PLUG_TYPE_HEADPHONE) {
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_CS);
+ } else {
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_NONE);
+ }
+ }
+}
+
+static bool wcd_mbhc_check_for_spl_headset(struct wcd_mbhc *mbhc,
+ int *spl_hs_cnt)
+{
+ u16 hs_comp_res_1_8v = 0, hs_comp_res_2_7v = 0;
+ bool spl_hs = false;
+
+ if (!mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic)
+ goto done;
+
+ if (!spl_hs_cnt) {
+ pr_err("%s: spl_hs_cnt is NULL\n", __func__);
+ goto done;
+ }
+ /* Read back hs_comp_res @ 1.8v Micbias */
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res_1_8v);
+ if (!hs_comp_res_1_8v) {
+ spl_hs = false;
+ goto done;
+ }
+
+ /* Bump up MB2 to 2.7v */
+ mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(mbhc->codec,
+ mbhc->mbhc_cfg->mbhc_micbias, true);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+ usleep_range(10000, 10100);
+
+ /* Read back HS_COMP_RESULT */
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res_2_7v);
+ if (!hs_comp_res_2_7v && hs_comp_res_1_8v)
+ spl_hs = true;
+
+ if (spl_hs)
+ *spl_hs_cnt += 1;
+
+ /* MB2 back to 1.8v */
+ if (*spl_hs_cnt != WCD_MBHC_SPL_HS_CNT) {
+ mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(mbhc->codec,
+ mbhc->mbhc_cfg->mbhc_micbias, false);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+ usleep_range(10000, 10100);
+ }
+
+ if (spl_hs)
+ pr_debug("%s: Detected special HS (%d)\n", __func__, spl_hs);
+
+done:
+ return spl_hs;
+}
+
+/* should be called under interrupt context that hold suspend */
+static void wcd_schedule_hs_detect_plug(struct wcd_mbhc *mbhc,
+ struct work_struct *work)
+{
+ pr_debug("%s: scheduling correct_swch_plug\n", __func__);
+ WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);
+ mbhc->hs_detect_work_stop = false;
+ mbhc->mbhc_cb->lock_sleep(mbhc, true);
+ schedule_work(work);
+}
+
+/* called under codec_resource_lock acquisition */
+static void wcd_cancel_hs_detect_plug(struct wcd_mbhc *mbhc,
+ struct work_struct *work)
+{
+ pr_debug("%s: Canceling correct_plug_swch\n", __func__);
+ mbhc->hs_detect_work_stop = true;
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ if (cancel_work_sync(work)) {
+ pr_debug("%s: correct_plug_swch is canceled\n",
+ __func__);
+ mbhc->mbhc_cb->lock_sleep(mbhc, false);
+ }
+ WCD_MBHC_RSC_LOCK(mbhc);
+}
+
+/* called under codec_resource_lock acquisition */
+static void wcd_mbhc_detect_plug_type(struct wcd_mbhc *mbhc)
+{
+ struct snd_soc_codec *codec = mbhc->codec;
+ bool micbias1 = false;
+
+ pr_debug("%s: enter\n", __func__);
+ WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);
+
+ if (mbhc->mbhc_cb->hph_pull_down_ctrl)
+ mbhc->mbhc_cb->hph_pull_down_ctrl(codec, false);
+
+ if (mbhc->mbhc_cb->micbias_enable_status)
+ micbias1 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
+ MIC_BIAS_1);
+
+ if (mbhc->mbhc_cb->set_cap_mode)
+ mbhc->mbhc_cb->set_cap_mode(codec, micbias1, true);
+
+ if (mbhc->mbhc_cb->mbhc_micbias_control)
+ mbhc->mbhc_cb->mbhc_micbias_control(codec, MIC_BIAS_2,
+ MICB_ENABLE);
+ else
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
+
+ /* Re-initialize button press completion object */
+ reinit_completion(&mbhc->btn_press_compl);
+ wcd_schedule_hs_detect_plug(mbhc, &mbhc->correct_plug_swch);
+ pr_debug("%s: leave\n", __func__);
+}
+
+static void wcd_correct_swch_plug(struct work_struct *work)
+{
+ struct wcd_mbhc *mbhc;
+ struct snd_soc_codec *codec;
+ enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_INVALID;
+ unsigned long timeout;
+ u16 hs_comp_res = 0, hphl_sch = 0, mic_sch = 0, btn_result = 0;
+ bool wrk_complete = false;
+ int pt_gnd_mic_swap_cnt = 0;
+ int no_gnd_mic_swap_cnt = 0;
+ bool is_pa_on = false, spl_hs = false;
+ bool micbias2 = false;
+ bool micbias1 = false;
+ int ret = 0;
+ int rc, spl_hs_count = 0;
+ int cross_conn;
+ int try = 0;
+
+ pr_debug("%s: enter\n", __func__);
+
+ mbhc = container_of(work, struct wcd_mbhc, correct_plug_swch);
+ codec = mbhc->codec;
+
+ /*
+ * Enable micbias/pullup for detection in correct work.
+ * This work will get scheduled from detect_plug_type which
+ * will already request for pullup/micbias. If the pullup/micbias
+ * is handled with ref-counts by individual codec drivers, there is
+ * no need to enabale micbias/pullup here
+ */
+
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
+
+ /* Enable HW FSM */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+ /*
+ * Check for any button press interrupts before starting 3-sec
+ * loop.
+ */
+ rc = wait_for_completion_timeout(&mbhc->btn_press_compl,
+ msecs_to_jiffies(WCD_MBHC_BTN_PRESS_COMPL_TIMEOUT_MS));
+
+ WCD_MBHC_REG_READ(WCD_MBHC_BTN_RESULT, btn_result);
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
+
+ if (!rc) {
+ pr_debug("%s No btn press interrupt\n", __func__);
+ if (!btn_result && !hs_comp_res)
+ plug_type = MBHC_PLUG_TYPE_HEADSET;
+ else if (!btn_result && hs_comp_res)
+ plug_type = MBHC_PLUG_TYPE_HIGH_HPH;
+ else
+ plug_type = MBHC_PLUG_TYPE_INVALID;
+ } else {
+ if (!btn_result && !hs_comp_res)
+ plug_type = MBHC_PLUG_TYPE_HEADPHONE;
+ else
+ plug_type = MBHC_PLUG_TYPE_INVALID;
+ }
+
+ do {
+ cross_conn = wcd_check_cross_conn(mbhc);
+ try++;
+ } while (try < GND_MIC_SWAP_THRESHOLD);
+
+ /*
+ * Check for cross connection 4 times.
+ * Consider the result of the fourth iteration.
+ */
+ if (cross_conn > 0) {
+ pr_debug("%s: cross con found, start polling\n",
+ __func__);
+ plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
+ pr_debug("%s: Plug found, plug type is %d\n",
+ __func__, plug_type);
+ goto correct_plug_type;
+ }
+
+ if ((plug_type == MBHC_PLUG_TYPE_HEADSET ||
+ plug_type == MBHC_PLUG_TYPE_HEADPHONE) &&
+ (!wcd_swch_level_remove(mbhc))) {
+ WCD_MBHC_RSC_LOCK(mbhc);
+ if (mbhc->current_plug == MBHC_PLUG_TYPE_HIGH_HPH)
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE,
+ 0);
+ wcd_mbhc_find_plug_and_report(mbhc, plug_type);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ }
+
+correct_plug_type:
+
+ timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS);
+ while (!time_after(jiffies, timeout)) {
+ if (mbhc->hs_detect_work_stop) {
+ pr_debug("%s: stop requested: %d\n", __func__,
+ mbhc->hs_detect_work_stop);
+ wcd_enable_curr_micbias(mbhc,
+ WCD_MBHC_EN_NONE);
+ if (mbhc->micbias_enable) {
+ mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(
+ mbhc->codec, MIC_BIAS_2, false);
+ if (mbhc->mbhc_cb->set_micbias_value)
+ mbhc->mbhc_cb->set_micbias_value(
+ mbhc->codec);
+ mbhc->micbias_enable = false;
+ }
+ goto exit;
+ }
+ if (mbhc->btn_press_intr) {
+ wcd_cancel_btn_work(mbhc);
+ mbhc->btn_press_intr = false;
+ }
+ /* Toggle FSM */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+
+ /* allow sometime and re-check stop requested again */
+ msleep(20);
+ if (mbhc->hs_detect_work_stop) {
+ pr_debug("%s: stop requested: %d\n", __func__,
+ mbhc->hs_detect_work_stop);
+ wcd_enable_curr_micbias(mbhc,
+ WCD_MBHC_EN_NONE);
+ if (mbhc->micbias_enable) {
+ mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(
+ mbhc->codec, MIC_BIAS_2, false);
+ if (mbhc->mbhc_cb->set_micbias_value)
+ mbhc->mbhc_cb->set_micbias_value(
+ mbhc->codec);
+ mbhc->micbias_enable = false;
+ }
+ goto exit;
+ }
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
+
+ pr_debug("%s: hs_comp_res: %x\n", __func__, hs_comp_res);
+ if (mbhc->mbhc_cb->hph_pa_on_status)
+ is_pa_on = mbhc->mbhc_cb->hph_pa_on_status(codec);
+
+ /*
+ * instead of hogging system by contineous polling, wait for
+ * sometime and re-check stop request again.
+ */
+ msleep(180);
+ if (hs_comp_res && (spl_hs_count < WCD_MBHC_SPL_HS_CNT)) {
+ spl_hs = wcd_mbhc_check_for_spl_headset(mbhc,
+ &spl_hs_count);
+
+ if (spl_hs_count == WCD_MBHC_SPL_HS_CNT) {
+ hs_comp_res = 0;
+ spl_hs = true;
+ mbhc->micbias_enable = true;
+ }
+ }
+
+ if ((!hs_comp_res) && (!is_pa_on)) {
+ /* Check for cross connection*/
+ ret = wcd_check_cross_conn(mbhc);
+ if (ret < 0) {
+ continue;
+ } else if (ret > 0) {
+ pt_gnd_mic_swap_cnt++;
+ no_gnd_mic_swap_cnt = 0;
+ if (pt_gnd_mic_swap_cnt <
+ GND_MIC_SWAP_THRESHOLD) {
+ continue;
+ } else if (pt_gnd_mic_swap_cnt >
+ GND_MIC_SWAP_THRESHOLD) {
+ /*
+ * This is due to GND/MIC switch didn't
+ * work, Report unsupported plug.
+ */
+ pr_debug("%s: switch didn't work\n",
+ __func__);
+ plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
+ goto report;
+ } else {
+ plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
+ }
+ } else {
+ no_gnd_mic_swap_cnt++;
+ pt_gnd_mic_swap_cnt = 0;
+ plug_type = MBHC_PLUG_TYPE_HEADSET;
+ if ((no_gnd_mic_swap_cnt <
+ GND_MIC_SWAP_THRESHOLD) &&
+ (spl_hs_count != WCD_MBHC_SPL_HS_CNT)) {
+ continue;
+ } else {
+ no_gnd_mic_swap_cnt = 0;
+ }
+ }
+ if ((pt_gnd_mic_swap_cnt == GND_MIC_SWAP_THRESHOLD) &&
+ (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP)) {
+ /*
+ * if switch is toggled, check again,
+ * otherwise report unsupported plug
+ */
+ if (mbhc->mbhc_cfg->swap_gnd_mic &&
+ mbhc->mbhc_cfg->swap_gnd_mic(codec)) {
+ pr_debug("%s: US_EU gpio present,flip switch\n"
+ , __func__);
+ continue;
+ }
+ }
+ }
+
+ WCD_MBHC_REG_READ(WCD_MBHC_HPHL_SCHMT_RESULT, hphl_sch);
+ WCD_MBHC_REG_READ(WCD_MBHC_MIC_SCHMT_RESULT, mic_sch);
+ if (hs_comp_res && !(hphl_sch || mic_sch)) {
+ pr_debug("%s: cable is extension cable\n", __func__);
+ plug_type = MBHC_PLUG_TYPE_HIGH_HPH;
+ wrk_complete = true;
+ } else {
+ pr_debug("%s: cable might be headset: %d\n", __func__,
+ plug_type);
+ if (!(plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP)) {
+ plug_type = MBHC_PLUG_TYPE_HEADSET;
+ /*
+ * Report headset only if not already reported
+ * and if there is not button press without
+ * release
+ */
+ if (((mbhc->current_plug !=
+ MBHC_PLUG_TYPE_HEADSET) &&
+ (mbhc->current_plug !=
+ MBHC_PLUG_TYPE_ANC_HEADPHONE)) &&
+ !wcd_swch_level_remove(mbhc) &&
+ !mbhc->btn_press_intr) {
+ pr_debug("%s: cable is %sheadset\n",
+ __func__,
+ ((spl_hs_count ==
+ WCD_MBHC_SPL_HS_CNT) ?
+ "special ":""));
+ goto report;
+ }
+ }
+ wrk_complete = false;
+ }
+ }
+ if (!wrk_complete && mbhc->btn_press_intr) {
+ pr_debug("%s: Can be slow insertion of headphone\n", __func__);
+ wcd_cancel_btn_work(mbhc);
+ plug_type = MBHC_PLUG_TYPE_HEADPHONE;
+ }
+ /*
+ * If plug_tye is headset, we might have already reported either in
+ * detect_plug-type or in above while loop, no need to report again
+ */
+ if (!wrk_complete && ((plug_type == MBHC_PLUG_TYPE_HEADSET) ||
+ (plug_type == MBHC_PLUG_TYPE_ANC_HEADPHONE))) {
+ pr_debug("%s: plug_type:0x%x already reported\n",
+ __func__, mbhc->current_plug);
+ goto enable_supply;
+ }
+
+ if (plug_type == MBHC_PLUG_TYPE_HIGH_HPH &&
+ (!det_extn_cable_en)) {
+ if (wcd_is_special_headset(mbhc)) {
+ pr_debug("%s: Special headset found %d\n",
+ __func__, plug_type);
+ plug_type = MBHC_PLUG_TYPE_HEADSET;
+ goto report;
+ }
+ }
+
+report:
+ if (wcd_swch_level_remove(mbhc)) {
+ pr_debug("%s: Switch level is low\n", __func__);
+ goto exit;
+ }
+ if (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP && mbhc->btn_press_intr) {
+ pr_debug("%s: insertion of headphone with swap\n", __func__);
+ wcd_cancel_btn_work(mbhc);
+ plug_type = MBHC_PLUG_TYPE_HEADPHONE;
+ }
+ pr_debug("%s: Valid plug found, plug type %d wrk_cmpt %d btn_intr %d\n",
+ __func__, plug_type, wrk_complete,
+ mbhc->btn_press_intr);
+ WCD_MBHC_RSC_LOCK(mbhc);
+ wcd_mbhc_find_plug_and_report(mbhc, plug_type);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+enable_supply:
+ if (mbhc->mbhc_cb->mbhc_micbias_control)
+ wcd_mbhc_update_fsm_source(mbhc, plug_type);
+ else
+ wcd_enable_mbhc_supply(mbhc, plug_type);
+exit:
+ if (mbhc->mbhc_cb->mbhc_micbias_control &&
+ !mbhc->micbias_enable)
+ mbhc->mbhc_cb->mbhc_micbias_control(codec, MIC_BIAS_2,
+ MICB_DISABLE);
+ if (mbhc->mbhc_cb->micbias_enable_status) {
+ micbias1 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
+ MIC_BIAS_1);
+ micbias2 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
+ MIC_BIAS_2);
+ }
+
+ if (mbhc->mbhc_cfg->detect_extn_cable &&
+ ((plug_type == MBHC_PLUG_TYPE_HEADPHONE) ||
+ (plug_type == MBHC_PLUG_TYPE_HEADSET)) &&
+ !mbhc->hs_detect_work_stop) {
+ WCD_MBHC_RSC_LOCK(mbhc);
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM, true);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ }
+ if (mbhc->mbhc_cb->set_cap_mode)
+ mbhc->mbhc_cb->set_cap_mode(codec, micbias1, micbias2);
+
+ if (mbhc->mbhc_cb->hph_pull_down_ctrl)
+ mbhc->mbhc_cb->hph_pull_down_ctrl(codec, true);
+
+ mbhc->mbhc_cb->lock_sleep(mbhc, false);
+ pr_debug("%s: leave\n", __func__);
+}
+
+static irqreturn_t wcd_mbhc_hs_rem_irq(int irq, void *data)
+{
+ struct wcd_mbhc *mbhc = data;
+ u8 hs_comp_result = 0, hphl_sch = 0, mic_sch = 0;
+ static u16 hphl_trigerred;
+ static u16 mic_trigerred;
+ unsigned long timeout;
+ bool removed = true;
+ int retry = 0;
+
+ pr_debug("%s: enter\n", __func__);
+
+ WCD_MBHC_RSC_LOCK(mbhc);
+
+ timeout = jiffies +
+ msecs_to_jiffies(WCD_FAKE_REMOVAL_MIN_PERIOD_MS);
+ do {
+ retry++;
+ /*
+ * read the result register every 10ms to look for
+ * any change in HS_COMP_RESULT bit
+ */
+ usleep_range(10000, 10100);
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_result);
+ pr_debug("%s: Check result reg for fake removal: hs_comp_res %x\n",
+ __func__, hs_comp_result);
+ if ((!hs_comp_result) &&
+ retry > FAKE_REM_RETRY_ATTEMPTS) {
+ removed = false;
+ break;
+ }
+ } while (!time_after(jiffies, timeout));
+
+ if (wcd_swch_level_remove(mbhc)) {
+ pr_debug("%s: Switch level is low ", __func__);
+ goto exit;
+ }
+ pr_debug("%s: headset %s actually removed\n", __func__,
+ removed ? "" : "not ");
+
+ WCD_MBHC_REG_READ(WCD_MBHC_HPHL_SCHMT_RESULT, hphl_sch);
+ WCD_MBHC_REG_READ(WCD_MBHC_MIC_SCHMT_RESULT, mic_sch);
+ WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_result);
+
+ if (removed) {
+ if (!(hphl_sch && mic_sch && hs_comp_result)) {
+ /*
+ * extension cable is still plugged in
+ * report it as LINEOUT device
+ */
+ goto report_unplug;
+ } else {
+ if (!mic_sch) {
+ mic_trigerred++;
+ pr_debug("%s: Removal MIC trigerred %d\n",
+ __func__, mic_trigerred);
+ }
+ if (!hphl_sch) {
+ hphl_trigerred++;
+ pr_debug("%s: Removal HPHL trigerred %d\n",
+ __func__, hphl_trigerred);
+ }
+ if (mic_trigerred && hphl_trigerred) {
+ /*
+ * extension cable is still plugged in
+ * report it as LINEOUT device
+ */
+ goto report_unplug;
+ }
+ }
+ }
+exit:
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ pr_debug("%s: leave\n", __func__);
+ return IRQ_HANDLED;
+
+report_unplug:
+ wcd_mbhc_elec_hs_report_unplug(mbhc);
+ hphl_trigerred = 0;
+ mic_trigerred = 0;
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ pr_debug("%s: leave\n", __func__);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t wcd_mbhc_hs_ins_irq(int irq, void *data)
+{
+ struct wcd_mbhc *mbhc = data;
+ bool detection_type = 0, hphl_sch = 0, mic_sch = 0;
+ u16 elect_result = 0;
+ static u16 hphl_trigerred;
+ static u16 mic_trigerred;
+
+ pr_debug("%s: enter\n", __func__);
+ if (!mbhc->mbhc_cfg->detect_extn_cable) {
+ pr_debug("%s: Returning as Extension cable feature not enabled\n",
+ __func__);
+ return IRQ_HANDLED;
+ }
+ WCD_MBHC_RSC_LOCK(mbhc);
+
+ WCD_MBHC_REG_READ(WCD_MBHC_ELECT_DETECTION_TYPE, detection_type);
+ WCD_MBHC_REG_READ(WCD_MBHC_ELECT_RESULT, elect_result);
+
+ pr_debug("%s: detection_type %d, elect_result %x\n", __func__,
+ detection_type, elect_result);
+ if (detection_type) {
+ /* check if both Left and MIC Schmitt triggers are triggered */
+ WCD_MBHC_REG_READ(WCD_MBHC_HPHL_SCHMT_RESULT, hphl_sch);
+ WCD_MBHC_REG_READ(WCD_MBHC_MIC_SCHMT_RESULT, mic_sch);
+ if (hphl_sch && mic_sch) {
+ /* Go for plug type determination */
+ pr_debug("%s: Go for plug type determination\n",
+ __func__);
+ goto determine_plug;
+
+ } else {
+ if (mic_sch) {
+ mic_trigerred++;
+ pr_debug("%s: Insertion MIC trigerred %d\n",
+ __func__, mic_trigerred);
+ WCD_MBHC_REG_UPDATE_BITS(
+ WCD_MBHC_ELECT_SCHMT_ISRC,
+ 0);
+ msleep(20);
+ WCD_MBHC_REG_UPDATE_BITS(
+ WCD_MBHC_ELECT_SCHMT_ISRC,
+ 1);
+ }
+ if (hphl_sch) {
+ hphl_trigerred++;
+ pr_debug("%s: Insertion HPHL trigerred %d\n",
+ __func__, hphl_trigerred);
+ }
+ if (mic_trigerred && hphl_trigerred) {
+ /* Go for plug type determination */
+ pr_debug("%s: Go for plug type determination\n",
+ __func__);
+ goto determine_plug;
+ }
+ }
+ }
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ pr_debug("%s: leave\n", __func__);
+ return IRQ_HANDLED;
+
+determine_plug:
+ /*
+ * Disable HPHL trigger and MIC Schmitt triggers.
+ * Setup for insertion detection.
+ */
+ pr_debug("%s: Disable insertion interrupt\n", __func__);
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
+ false);
+
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
+ hphl_trigerred = 0;
+ mic_trigerred = 0;
+ mbhc->is_extn_cable = true;
+ mbhc->btn_press_intr = false;
+ wcd_mbhc_detect_plug_type(mbhc);
+ WCD_MBHC_RSC_UNLOCK(mbhc);
+ pr_debug("%s: leave\n", __func__);
+ return IRQ_HANDLED;
+}
+
+static struct wcd_mbhc_fn mbhc_fn = {
+ .wcd_mbhc_hs_ins_irq = wcd_mbhc_hs_ins_irq,
+ .wcd_mbhc_hs_rem_irq = wcd_mbhc_hs_rem_irq,
+ .wcd_mbhc_detect_plug_type = wcd_mbhc_detect_plug_type,
+ .wcd_mbhc_detect_anc_plug_type = wcd_mbhc_detect_anc_plug_type,
+ .wcd_cancel_hs_detect_plug = wcd_cancel_hs_detect_plug,
+};
+
+/* Function: wcd_mbhc_legacy_init
+ * @mbhc: MBHC function pointer
+ * Description: Initialize MBHC legacy based function pointers to MBHC structure
+ */
+void wcd_mbhc_legacy_init(struct wcd_mbhc *mbhc)
+{
+ if (!mbhc) {
+ pr_err("%s: mbhc is NULL\n", __func__);
+ return;
+ }
+ mbhc->mbhc_fn = &mbhc_fn;
+ INIT_WORK(&mbhc->correct_plug_swch, wcd_correct_swch_plug);
+}
+EXPORT_SYMBOL(wcd_mbhc_legacy_init);
diff --git a/sound/soc/codecs/wcd-mbhc-legacy.h b/sound/soc/codecs/wcd-mbhc-legacy.h
new file mode 100644
index 0000000..594393d
--- /dev/null
+++ b/sound/soc/codecs/wcd-mbhc-legacy.h
@@ -0,0 +1,26 @@
+/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __WCD_MBHC_LEGACY_H__
+#define __WCD_MBHC_LEGACY_H__
+
+#include "wcdcal-hwdep.h"
+#include "wcd-mbhc-v2.h"
+
+#ifdef CONFIG_SND_SOC_WCD_MBHC_LEGACY
+void wcd_mbhc_legacy_init(struct wcd_mbhc *mbhc);
+#else
+static inline void wcd_mbhc_legacy_init(struct wcd_mbhc *mbhc)
+{
+}
+#endif
+
+#endif /* __WCD_MBHC_LEGACY_H__ */
diff --git a/sound/soc/codecs/wcd-mbhc-v2-api.h b/sound/soc/codecs/wcd-mbhc-v2-api.h
new file mode 100644
index 0000000..fab2b49
--- /dev/null
+++ b/sound/soc/codecs/wcd-mbhc-v2-api.h
@@ -0,0 +1,60 @@
+/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#ifndef __WCD_MBHC_V2_API_H__
+#define __WCD_MBHC_V2_API_H__
+
+#include "wcd-mbhc-v2.h"
+
+#ifdef CONFIG_SND_SOC_WCD_MBHC
+int wcd_mbhc_start(struct wcd_mbhc *mbhc,
+ struct wcd_mbhc_config *mbhc_cfg);
+void wcd_mbhc_stop(struct wcd_mbhc *mbhc);
+int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
+ const struct wcd_mbhc_cb *mbhc_cb,
+ const struct wcd_mbhc_intr *mbhc_cdc_intr_ids,
+ struct wcd_mbhc_register *wcd_mbhc_regs,
+ bool impedance_det_en);
+int wcd_mbhc_get_impedance(struct wcd_mbhc *mbhc, uint32_t *zl,
+ uint32_t *zr);
+void wcd_mbhc_deinit(struct wcd_mbhc *mbhc);
+
+#else
+static inline void wcd_mbhc_stop(struct wcd_mbhc *mbhc)
+{
+}
+int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
+ const struct wcd_mbhc_cb *mbhc_cb,
+ const struct wcd_mbhc_intr *mbhc_cdc_intr_ids,
+ struct wcd_mbhc_register *wcd_mbhc_regs,
+ bool impedance_det_en)
+{
+ return 0;
+}
+static inline int wcd_mbhc_start(struct wcd_mbhc *mbhc,
+ struct wcd_mbhc_config *mbhc_cfg)
+{
+ return 0;
+}
+static inline int wcd_mbhc_get_impedance(struct wcd_mbhc *mbhc,
+ uint32_t *zl,
+ uint32_t *zr)
+{
+ *zl = 0;
+ *zr = 0;
+ return -EINVAL;
+}
+static inline void wcd_mbhc_deinit(struct wcd_mbhc *mbhc)
+{
+}
+#endif
+
+#endif /* __WCD_MBHC_V2_API_H__ */
diff --git a/sound/soc/codecs/wcd-mbhc-v2.c b/sound/soc/codecs/wcd-mbhc-v2.c
index 75e2709..510a8dc 100644
--- a/sound/soc/codecs/wcd-mbhc-v2.c
+++ b/sound/soc/codecs/wcd-mbhc-v2.c
@@ -28,49 +28,17 @@
#include <linux/mfd/msm-cdc-pinctrl.h>
#include <sound/soc.h>
#include <sound/jack.h>
-#include "wcd-mbhc-v2.h"
#include "wcdcal-hwdep.h"
+#include "wcd-mbhc-legacy.h"
+#include "wcd-mbhc-adc.h"
+#include "wcd-mbhc-v2-api.h"
-#define WCD_MBHC_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | \
- SND_JACK_OC_HPHR | SND_JACK_LINEOUT | \
- SND_JACK_MECHANICAL | SND_JACK_MICROPHONE2 | \
- SND_JACK_UNSUPPORTED)
-
-#define WCD_MBHC_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \
- SND_JACK_BTN_2 | SND_JACK_BTN_3 | \
- SND_JACK_BTN_4 | SND_JACK_BTN_5)
-#define OCP_ATTEMPT 20
-#define HS_DETECT_PLUG_TIME_MS (3 * 1000)
-#define SPECIAL_HS_DETECT_TIME_MS (2 * 1000)
-#define MBHC_BUTTON_PRESS_THRESHOLD_MIN 250
-#define GND_MIC_SWAP_THRESHOLD 4
-#define WCD_FAKE_REMOVAL_MIN_PERIOD_MS 100
-#define HS_VREF_MIN_VAL 1400
-#define FW_READ_ATTEMPTS 15
-#define FW_READ_TIMEOUT 4000000
-#define FAKE_REM_RETRY_ATTEMPTS 3
-#define MAX_IMPED 60000
-
-#define WCD_MBHC_BTN_PRESS_COMPL_TIMEOUT_MS 50
-#define ANC_DETECT_RETRY_CNT 7
-#define WCD_MBHC_SPL_HS_CNT 2
-
-static int det_extn_cable_en;
-module_param(det_extn_cable_en, int, 0664);
-MODULE_PARM_DESC(det_extn_cable_en, "enable/disable extn cable detect");
-
-enum wcd_mbhc_cs_mb_en_flag {
- WCD_MBHC_EN_CS = 0,
- WCD_MBHC_EN_MB,
- WCD_MBHC_EN_PULLUP,
- WCD_MBHC_EN_NONE,
-};
-
-static void wcd_mbhc_jack_report(struct wcd_mbhc *mbhc,
- struct snd_soc_jack *jack, int status, int mask)
+void wcd_mbhc_jack_report(struct wcd_mbhc *mbhc,
+ struct snd_soc_jack *jack, int status, int mask)
{
snd_soc_jack_report(jack, status, mask);
}
+EXPORT_SYMBOL(wcd_mbhc_jack_report);
static void __hphocp_off_report(struct wcd_mbhc *mbhc, u32 jack_status,
int irq)
@@ -144,7 +112,7 @@ static void wcd_program_btn_threshold(const struct wcd_mbhc *mbhc, bool micbias)
micbias);
}
-static void wcd_enable_curr_micbias(const struct wcd_mbhc *mbhc,
+void wcd_enable_curr_micbias(const struct wcd_mbhc *mbhc,
const enum wcd_mbhc_cs_mb_en_flag cs_mb_en)
{
@@ -194,6 +162,7 @@ static void wcd_enable_curr_micbias(const struct wcd_mbhc *mbhc,
pr_debug("%s: exit\n", __func__);
}
+EXPORT_SYMBOL(wcd_enable_curr_micbias);
static const char *wcd_mbhc_get_event_string(int event)
{
@@ -414,7 +383,7 @@ static int wcd_event_notify(struct notifier_block *self, unsigned long val,
return 0;
}
-static int wcd_cancel_btn_work(struct wcd_mbhc *mbhc)
+int wcd_cancel_btn_work(struct wcd_mbhc *mbhc)
{
int r;
@@ -427,40 +396,16 @@ static int wcd_cancel_btn_work(struct wcd_mbhc *mbhc)
mbhc->mbhc_cb->lock_sleep(mbhc, false);
return r;
}
+EXPORT_SYMBOL(wcd_cancel_btn_work);
-static bool wcd_swch_level_remove(struct wcd_mbhc *mbhc)
+bool wcd_swch_level_remove(struct wcd_mbhc *mbhc)
{
u16 result2 = 0;
WCD_MBHC_REG_READ(WCD_MBHC_SWCH_LEVEL_REMOVE, result2);
return (result2) ? true : false;
}
-
-/* should be called under interrupt context that hold suspend */
-static void wcd_schedule_hs_detect_plug(struct wcd_mbhc *mbhc,
- struct work_struct *work)
-{
- pr_debug("%s: scheduling correct_swch_plug\n", __func__);
- WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);
- mbhc->hs_detect_work_stop = false;
- mbhc->mbhc_cb->lock_sleep(mbhc, true);
- schedule_work(work);
-}
-
-/* called under codec_resource_lock acquisition */
-static void wcd_cancel_hs_detect_plug(struct wcd_mbhc *mbhc,
- struct work_struct *work)
-{
- pr_debug("%s: Canceling correct_plug_swch\n", __func__);
- mbhc->hs_detect_work_stop = true;
- WCD_MBHC_RSC_UNLOCK(mbhc);
- if (cancel_work_sync(work)) {
- pr_debug("%s: correct_plug_swch is canceled\n",
- __func__);
- mbhc->mbhc_cb->lock_sleep(mbhc, false);
- }
- WCD_MBHC_RSC_LOCK(mbhc);
-}
+EXPORT_SYMBOL(wcd_swch_level_remove);
static void wcd_mbhc_clr_and_turnon_hph_padac(struct wcd_mbhc *mbhc)
{
@@ -539,8 +484,9 @@ int wcd_mbhc_get_impedance(struct wcd_mbhc *mbhc, uint32_t *zl,
else
return -EINVAL;
}
+EXPORT_SYMBOL(wcd_mbhc_get_impedance);
-static void wcd_mbhc_hs_elec_irq(struct wcd_mbhc *mbhc, int irq_type,
+void wcd_mbhc_hs_elec_irq(struct wcd_mbhc *mbhc, int irq_type,
bool enable)
{
int irq;
@@ -567,12 +513,14 @@ static void wcd_mbhc_hs_elec_irq(struct wcd_mbhc *mbhc, int irq_type,
clear_bit(irq_type, &mbhc->intr_status);
}
}
+EXPORT_SYMBOL(wcd_mbhc_hs_elec_irq);
static void wcd_mbhc_report_plug(struct wcd_mbhc *mbhc, int insertion,
enum snd_jack_types jack_type)
{
struct snd_soc_codec *codec = mbhc->codec;
bool is_pa_on = false;
+ u8 fsm_en = 0;
WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);
@@ -664,9 +612,6 @@ static void wcd_mbhc_report_plug(struct wcd_mbhc *mbhc, int insertion,
wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
pr_debug("%s: set up elec removal detection\n",
__func__);
- WCD_MBHC_REG_UPDATE_BITS(
- WCD_MBHC_ELECT_DETECTION_TYPE,
- 0);
usleep_range(200, 210);
wcd_mbhc_hs_elec_irq(mbhc,
WCD_MBHC_ELEC_HS_REM,
@@ -702,8 +647,16 @@ static void wcd_mbhc_report_plug(struct wcd_mbhc *mbhc, int insertion,
mbhc->mbhc_cb->compute_impedance &&
(mbhc->mbhc_cfg->linein_th != 0) &&
(!is_pa_on)) {
+ /* Set MUX_CTL to AUTO for Z-det */
+ WCD_MBHC_REG_READ(WCD_MBHC_FSM_EN, fsm_en);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL,
+ MUX_CTL_AUTO);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
mbhc->mbhc_cb->compute_impedance(mbhc,
&mbhc->zl, &mbhc->zr);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN,
+ fsm_en);
if ((mbhc->zl > mbhc->mbhc_cfg->linein_th &&
mbhc->zl < MAX_IMPED) &&
(mbhc->zr > mbhc->mbhc_cfg->linein_th &&
@@ -737,94 +690,47 @@ static void wcd_mbhc_report_plug(struct wcd_mbhc *mbhc, int insertion,
pr_debug("%s: leave hph_status %x\n", __func__, mbhc->hph_status);
}
-static bool wcd_mbhc_detect_anc_plug_type(struct wcd_mbhc *mbhc)
+void wcd_mbhc_elec_hs_report_unplug(struct wcd_mbhc *mbhc)
{
- bool anc_mic_found = false;
- u16 val, hs_comp_res, btn_status = 0;
- unsigned long retry = 0;
- int valid_plug_cnt = 0, invalid_plug_cnt = 0;
- int btn_status_cnt = 0;
- bool is_check_btn_press = false;
+ /* cancel pending button press */
+ if (wcd_cancel_btn_work(mbhc))
+ pr_debug("%s: button press is canceled\n", __func__);
+ /* cancel correct work function */
+ if (mbhc->mbhc_fn->wcd_cancel_hs_detect_plug)
+ mbhc->mbhc_fn->wcd_cancel_hs_detect_plug(mbhc,
+ &mbhc->correct_plug_swch);
+ else
+ pr_info("%s: hs_detect_plug work not cancelled\n", __func__);
-
- if (mbhc->mbhc_cfg->anc_micbias < MIC_BIAS_1 ||
- mbhc->mbhc_cfg->anc_micbias > MIC_BIAS_4)
- return false;
-
- if (!mbhc->mbhc_cb->mbhc_micbias_control)
- return false;
-
- WCD_MBHC_REG_READ(WCD_MBHC_FSM_EN, val);
-
- if (val)
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
-
- mbhc->mbhc_cb->mbhc_micbias_control(mbhc->codec,
- mbhc->mbhc_cfg->anc_micbias,
- MICB_ENABLE);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL, 0x2);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ANC_DET_EN, 1);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
+ pr_debug("%s: Report extension cable\n", __func__);
+ wcd_mbhc_report_plug(mbhc, 1, SND_JACK_LINEOUT);
/*
- * wait for button debounce time 20ms. If 4-pole plug is inserted
- * into 5-pole jack, then there will be a button press interrupt
- * during anc plug detection. In that case though Hs_comp_res is 0,
- * it should not be declared as ANC plug type
+ * If PA is enabled HPHL schmitt trigger can
+ * be unreliable, make sure to disable it
*/
- usleep_range(20000, 20100);
-
+ if (test_bit(WCD_MBHC_EVENT_PA_HPHL,
+ &mbhc->event_state))
+ wcd_mbhc_set_and_turnoff_hph_padac(mbhc);
/*
- * After enabling FSM, to handle slow insertion scenarios,
- * check hs_comp_result for few times to see if the IN3 voltage
- * is below the Vref
+ * Disable HPHL trigger and MIC Schmitt triggers.
+ * Setup for insertion detection.
*/
- do {
- if (wcd_swch_level_remove(mbhc)) {
- pr_debug("%s: Switch level is low\n", __func__);
- goto exit;
- }
- pr_debug("%s: Retry attempt %lu\n", __func__, retry + 1);
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM,
+ false);
+ wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_NONE);
+ /* Disable HW FSM */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 3);
- if (!hs_comp_res) {
- valid_plug_cnt++;
- is_check_btn_press = true;
- } else
- invalid_plug_cnt++;
- /* Wait 1ms before taking another reading */
- usleep_range(1000, 1100);
-
- WCD_MBHC_REG_READ(WCD_MBHC_FSM_STATUS, btn_status);
- if (btn_status)
- btn_status_cnt++;
-
- retry++;
- } while (retry < ANC_DETECT_RETRY_CNT);
-
- pr_debug("%s: valid: %d, invalid: %d, btn_status_cnt: %d\n",
- __func__, valid_plug_cnt, invalid_plug_cnt, btn_status_cnt);
-
- /* decision logic */
- if ((valid_plug_cnt > invalid_plug_cnt) && is_check_btn_press &&
- (btn_status_cnt == 0))
- anc_mic_found = true;
-exit:
- if (!val)
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
-
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ANC_DET_EN, 0);
-
- mbhc->mbhc_cb->mbhc_micbias_control(mbhc->codec,
- mbhc->mbhc_cfg->anc_micbias,
- MICB_DISABLE);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_MUX_CTL, 0x0);
- pr_debug("%s: anc mic %sfound\n", __func__,
- anc_mic_found ? "" : "not ");
- return anc_mic_found;
+ /* Set the detection type appropriately */
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE, 1);
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
+ true);
}
+EXPORT_SYMBOL(wcd_mbhc_elec_hs_report_unplug);
-static void wcd_mbhc_find_plug_and_report(struct wcd_mbhc *mbhc,
- enum wcd_mbhc_plug_type plug_type)
+void wcd_mbhc_find_plug_and_report(struct wcd_mbhc *mbhc,
+ enum wcd_mbhc_plug_type plug_type)
{
bool anc_mic_found = false;
enum snd_jack_types jack_type;
@@ -852,9 +758,10 @@ static void wcd_mbhc_find_plug_and_report(struct wcd_mbhc *mbhc,
wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADSET);
wcd_mbhc_report_plug(mbhc, 1, SND_JACK_UNSUPPORTED);
} else if (plug_type == MBHC_PLUG_TYPE_HEADSET) {
- if (mbhc->mbhc_cfg->enable_anc_mic_detect)
- anc_mic_found = wcd_mbhc_detect_anc_plug_type(mbhc);
-
+ if (mbhc->mbhc_cfg->enable_anc_mic_detect &&
+ mbhc->mbhc_fn->wcd_mbhc_detect_anc_plug_type)
+ anc_mic_found =
+ mbhc->mbhc_fn->wcd_mbhc_detect_anc_plug_type(mbhc);
jack_type = SND_JACK_HEADSET;
if (anc_mic_found)
jack_type = SND_JACK_ANC_HEADPHONE;
@@ -895,618 +802,17 @@ static void wcd_mbhc_find_plug_and_report(struct wcd_mbhc *mbhc,
exit:
pr_debug("%s: leave\n", __func__);
}
-
-/* To determine if cross connection occurred */
-static int wcd_check_cross_conn(struct wcd_mbhc *mbhc)
-{
- u16 swap_res = 0;
- enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_NONE;
- s16 reg1 = 0;
- bool hphl_sch_res = 0, hphr_sch_res = 0;
-
- if (wcd_swch_level_remove(mbhc)) {
- pr_debug("%s: Switch level is low\n", __func__);
- return -EINVAL;
- }
-
- /* If PA is enabled, dont check for cross-connection */
- if (mbhc->mbhc_cb->hph_pa_on_status)
- if (mbhc->mbhc_cb->hph_pa_on_status(mbhc->codec))
- return false;
-
- WCD_MBHC_REG_READ(WCD_MBHC_ELECT_SCHMT_ISRC, reg1);
- /*
- * Check if there is any cross connection,
- * Micbias and schmitt trigger (HPHL-HPHR)
- * needs to be enabled. For some codecs like wcd9335,
- * pull-up will already be enabled when this function
- * is called for cross-connection identification. No
- * need to enable micbias in that case.
- */
- wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 2);
-
- WCD_MBHC_REG_READ(WCD_MBHC_ELECT_RESULT, swap_res);
- pr_debug("%s: swap_res%x\n", __func__, swap_res);
-
- /*
- * Read reg hphl and hphr schmitt result with cross connection
- * bit. These bits will both be "0" in case of cross connection
- * otherwise, they stay at 1
- */
- WCD_MBHC_REG_READ(WCD_MBHC_HPHL_SCHMT_RESULT, hphl_sch_res);
- WCD_MBHC_REG_READ(WCD_MBHC_HPHR_SCHMT_RESULT, hphr_sch_res);
- if (!(hphl_sch_res || hphr_sch_res)) {
- plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
- pr_debug("%s: Cross connection identified\n", __func__);
- } else {
- pr_debug("%s: No Cross connection found\n", __func__);
- }
-
- /* Disable schmitt trigger and restore micbias */
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, reg1);
- pr_debug("%s: leave, plug type: %d\n", __func__, plug_type);
-
- return (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP) ? true : false;
-}
-
-static bool wcd_is_special_headset(struct wcd_mbhc *mbhc)
-{
- struct snd_soc_codec *codec = mbhc->codec;
- int delay = 0, rc;
- bool ret = false;
- u16 hs_comp_res;
- bool is_spl_hs = false;
-
- /*
- * Increase micbias to 2.7V to detect headsets with
- * threshold on microphone
- */
- if (mbhc->mbhc_cb->mbhc_micbias_control &&
- !mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic) {
- pr_debug("%s: callback fn micb_ctrl_thr_mic not defined\n",
- __func__);
- return false;
- } else if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic) {
- rc = mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(codec,
- MIC_BIAS_2, true);
- if (rc) {
- pr_err("%s: Micbias control for thr mic failed, rc: %d\n",
- __func__, rc);
- return false;
- }
- }
-
- wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
-
- pr_debug("%s: special headset, start register writes\n", __func__);
-
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
- while (!is_spl_hs) {
- if (mbhc->hs_detect_work_stop) {
- pr_debug("%s: stop requested: %d\n", __func__,
- mbhc->hs_detect_work_stop);
- break;
- }
- delay = delay + 50;
- if (mbhc->mbhc_cb->mbhc_common_micb_ctrl) {
- mbhc->mbhc_cb->mbhc_common_micb_ctrl(codec,
- MBHC_COMMON_MICB_PRECHARGE,
- true);
- mbhc->mbhc_cb->mbhc_common_micb_ctrl(codec,
- MBHC_COMMON_MICB_SET_VAL,
- true);
- }
- /* Wait for 50msec for MICBIAS to settle down */
- msleep(50);
- if (mbhc->mbhc_cb->set_auto_zeroing)
- mbhc->mbhc_cb->set_auto_zeroing(codec, true);
- /* Wait for 50msec for FSM to update result values */
- msleep(50);
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
- if (!(hs_comp_res)) {
- pr_debug("%s: Special headset detected in %d msecs\n",
- __func__, (delay * 2));
- is_spl_hs = true;
- }
- if (delay == SPECIAL_HS_DETECT_TIME_MS) {
- pr_debug("%s: Spl headset did not get detect in 4 sec\n",
- __func__);
- break;
- }
- }
- if (is_spl_hs) {
- pr_debug("%s: Headset with threshold found\n", __func__);
- mbhc->micbias_enable = true;
- ret = true;
- }
- if (mbhc->mbhc_cb->mbhc_common_micb_ctrl)
- mbhc->mbhc_cb->mbhc_common_micb_ctrl(codec,
- MBHC_COMMON_MICB_PRECHARGE,
- false);
- if (mbhc->mbhc_cb->set_micbias_value && !mbhc->micbias_enable)
- mbhc->mbhc_cb->set_micbias_value(codec);
- if (mbhc->mbhc_cb->set_auto_zeroing)
- mbhc->mbhc_cb->set_auto_zeroing(codec, false);
-
- if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic &&
- !mbhc->micbias_enable)
- mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(codec, MIC_BIAS_2,
- false);
-
- pr_debug("%s: leave, micb_enable: %d\n", __func__,
- mbhc->micbias_enable);
- return ret;
-}
-
-static void wcd_mbhc_update_fsm_source(struct wcd_mbhc *mbhc,
- enum wcd_mbhc_plug_type plug_type)
-{
- bool micbias2;
-
- micbias2 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
- MIC_BIAS_2);
- switch (plug_type) {
- case MBHC_PLUG_TYPE_HEADPHONE:
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 3);
- break;
- case MBHC_PLUG_TYPE_HEADSET:
- case MBHC_PLUG_TYPE_ANC_HEADPHONE:
- if (!mbhc->is_hs_recording && !micbias2)
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 3);
- break;
- default:
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0);
- break;
-
- };
-}
-
-static void wcd_enable_mbhc_supply(struct wcd_mbhc *mbhc,
- enum wcd_mbhc_plug_type plug_type)
-{
-
- struct snd_soc_codec *codec = mbhc->codec;
-
- /*
- * Do not disable micbias if recording is going on or
- * headset is inserted on the other side of the extn
- * cable. If headset has been detected current source
- * needs to be kept enabled for button detection to work.
- * If the accessory type is invalid or unsupported, we
- * dont need to enable either of them.
- */
- if (det_extn_cable_en && mbhc->is_extn_cable &&
- mbhc->mbhc_cb && mbhc->mbhc_cb->extn_use_mb &&
- mbhc->mbhc_cb->extn_use_mb(codec)) {
- if (plug_type == MBHC_PLUG_TYPE_HEADPHONE ||
- plug_type == MBHC_PLUG_TYPE_HEADSET)
- wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
- } else {
- if (plug_type == MBHC_PLUG_TYPE_HEADSET) {
- if (mbhc->is_hs_recording || mbhc->micbias_enable)
- wcd_enable_curr_micbias(mbhc,
- WCD_MBHC_EN_MB);
- else if ((test_bit(WCD_MBHC_EVENT_PA_HPHL,
- &mbhc->event_state)) ||
- (test_bit(WCD_MBHC_EVENT_PA_HPHR,
- &mbhc->event_state)))
- wcd_enable_curr_micbias(mbhc,
- WCD_MBHC_EN_PULLUP);
- else
- wcd_enable_curr_micbias(mbhc,
- WCD_MBHC_EN_CS);
- } else if (plug_type == MBHC_PLUG_TYPE_HEADPHONE) {
- wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_CS);
- } else {
- wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_NONE);
- }
- }
-}
-
-static bool wcd_mbhc_check_for_spl_headset(struct wcd_mbhc *mbhc,
- int *spl_hs_cnt)
-{
- u16 hs_comp_res_1_8v = 0, hs_comp_res_2_7v = 0;
- bool spl_hs = false;
-
- if (!mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic)
- goto exit;
-
- /* Read back hs_comp_res @ 1.8v Micbias */
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res_1_8v);
- if (!hs_comp_res_1_8v) {
- spl_hs = false;
- goto exit;
- }
-
- /* Bump up MB2 to 2.7v */
- mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(mbhc->codec,
- mbhc->mbhc_cfg->mbhc_micbias, true);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
- usleep_range(10000, 10100);
-
- /* Read back HS_COMP_RESULT */
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res_2_7v);
- if (!hs_comp_res_2_7v && hs_comp_res_1_8v)
- spl_hs = true;
-
- if (spl_hs && spl_hs_cnt)
- *spl_hs_cnt += 1;
-
- /* MB2 back to 1.8v */
- if (*spl_hs_cnt != WCD_MBHC_SPL_HS_CNT) {
- mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(mbhc->codec,
- mbhc->mbhc_cfg->mbhc_micbias, false);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
- usleep_range(10000, 10100);
- }
-
- if (spl_hs)
- pr_debug("%s: Detected special HS (%d)\n", __func__, spl_hs);
-
-exit:
- return spl_hs;
-}
-
-static void wcd_correct_swch_plug(struct work_struct *work)
-{
- struct wcd_mbhc *mbhc;
- struct snd_soc_codec *codec;
- enum wcd_mbhc_plug_type plug_type = MBHC_PLUG_TYPE_INVALID;
- unsigned long timeout;
- u16 hs_comp_res = 0, hphl_sch = 0, mic_sch = 0, btn_result = 0;
- bool wrk_complete = false;
- int pt_gnd_mic_swap_cnt = 0;
- int no_gnd_mic_swap_cnt = 0;
- bool is_pa_on = false, spl_hs = false;
- bool micbias2 = false;
- bool micbias1 = false;
- int ret = 0;
- int rc, spl_hs_count = 0;
- int cross_conn;
- int try = 0;
-
- pr_debug("%s: enter\n", __func__);
-
- mbhc = container_of(work, struct wcd_mbhc, correct_plug_swch);
- codec = mbhc->codec;
-
- /*
- * Enable micbias/pullup for detection in correct work.
- * This work will get scheduled from detect_plug_type which
- * will already request for pullup/micbias. If the pullup/micbias
- * is handled with ref-counts by individual codec drivers, there is
- * no need to enabale micbias/pullup here
- */
-
- wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
-
-
- /* Enable HW FSM */
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
- /*
- * Check for any button press interrupts before starting 3-sec
- * loop.
- */
- rc = wait_for_completion_timeout(&mbhc->btn_press_compl,
- msecs_to_jiffies(WCD_MBHC_BTN_PRESS_COMPL_TIMEOUT_MS));
-
- WCD_MBHC_REG_READ(WCD_MBHC_BTN_RESULT, btn_result);
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
-
- if (!rc) {
- pr_debug("%s No btn press interrupt\n", __func__);
- if (!btn_result && !hs_comp_res)
- plug_type = MBHC_PLUG_TYPE_HEADSET;
- else if (!btn_result && hs_comp_res)
- plug_type = MBHC_PLUG_TYPE_HIGH_HPH;
- else
- plug_type = MBHC_PLUG_TYPE_INVALID;
- } else {
- if (!btn_result && !hs_comp_res)
- plug_type = MBHC_PLUG_TYPE_HEADPHONE;
- else
- plug_type = MBHC_PLUG_TYPE_INVALID;
- }
-
- do {
- cross_conn = wcd_check_cross_conn(mbhc);
- try++;
- } while (try < GND_MIC_SWAP_THRESHOLD);
- /*
- * check for cross coneection 4 times.
- * conisder the result of the fourth iteration.
- */
- if (cross_conn > 0) {
- pr_debug("%s: cross con found, start polling\n",
- __func__);
- plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
- pr_debug("%s: Plug found, plug type is %d\n",
- __func__, plug_type);
- goto correct_plug_type;
- }
-
- if ((plug_type == MBHC_PLUG_TYPE_HEADSET ||
- plug_type == MBHC_PLUG_TYPE_HEADPHONE) &&
- (!wcd_swch_level_remove(mbhc))) {
- WCD_MBHC_RSC_LOCK(mbhc);
- wcd_mbhc_find_plug_and_report(mbhc, plug_type);
- WCD_MBHC_RSC_UNLOCK(mbhc);
- }
-
-correct_plug_type:
-
- timeout = jiffies + msecs_to_jiffies(HS_DETECT_PLUG_TIME_MS);
- while (!time_after(jiffies, timeout)) {
- if (mbhc->hs_detect_work_stop) {
- pr_debug("%s: stop requested: %d\n", __func__,
- mbhc->hs_detect_work_stop);
- wcd_enable_curr_micbias(mbhc,
- WCD_MBHC_EN_NONE);
- if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic &&
- mbhc->micbias_enable) {
- mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(
- mbhc->codec, MIC_BIAS_2, false);
- if (mbhc->mbhc_cb->set_micbias_value)
- mbhc->mbhc_cb->set_micbias_value(
- mbhc->codec);
- mbhc->micbias_enable = false;
- }
- goto exit;
- }
- if (mbhc->btn_press_intr) {
- wcd_cancel_btn_work(mbhc);
- mbhc->btn_press_intr = false;
- }
- /* Toggle FSM */
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 1);
-
- /* allow sometime and re-check stop requested again */
- msleep(20);
- if (mbhc->hs_detect_work_stop) {
- pr_debug("%s: stop requested: %d\n", __func__,
- mbhc->hs_detect_work_stop);
- wcd_enable_curr_micbias(mbhc,
- WCD_MBHC_EN_NONE);
- if (mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic &&
- mbhc->micbias_enable) {
- mbhc->mbhc_cb->mbhc_micb_ctrl_thr_mic(
- mbhc->codec, MIC_BIAS_2, false);
- if (mbhc->mbhc_cb->set_micbias_value)
- mbhc->mbhc_cb->set_micbias_value(
- mbhc->codec);
- mbhc->micbias_enable = false;
- }
- goto exit;
- }
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_res);
-
- pr_debug("%s: hs_comp_res: %x\n", __func__, hs_comp_res);
- if (mbhc->mbhc_cb->hph_pa_on_status)
- is_pa_on = mbhc->mbhc_cb->hph_pa_on_status(codec);
-
- /*
- * instead of hogging system by contineous polling, wait for
- * sometime and re-check stop request again.
- */
- msleep(180);
- if (hs_comp_res && (spl_hs_count < WCD_MBHC_SPL_HS_CNT)) {
- spl_hs = wcd_mbhc_check_for_spl_headset(mbhc,
- &spl_hs_count);
-
- if (spl_hs_count == WCD_MBHC_SPL_HS_CNT) {
- hs_comp_res = 0;
- spl_hs = true;
- mbhc->micbias_enable = true;
- }
- }
-
- if ((!hs_comp_res) && (!is_pa_on)) {
- /* Check for cross connection*/
- ret = wcd_check_cross_conn(mbhc);
- if (ret < 0) {
- continue;
- } else if (ret > 0) {
- pt_gnd_mic_swap_cnt++;
- no_gnd_mic_swap_cnt = 0;
- if (pt_gnd_mic_swap_cnt <
- GND_MIC_SWAP_THRESHOLD) {
- continue;
- } else if (pt_gnd_mic_swap_cnt >
- GND_MIC_SWAP_THRESHOLD) {
- /*
- * This is due to GND/MIC switch didn't
- * work, Report unsupported plug.
- */
- pr_debug("%s: switch did not work\n",
- __func__);
- plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
- goto report;
- } else {
- plug_type = MBHC_PLUG_TYPE_GND_MIC_SWAP;
- }
- } else {
- no_gnd_mic_swap_cnt++;
- pt_gnd_mic_swap_cnt = 0;
- plug_type = MBHC_PLUG_TYPE_HEADSET;
- if ((no_gnd_mic_swap_cnt <
- GND_MIC_SWAP_THRESHOLD) &&
- (spl_hs_count != WCD_MBHC_SPL_HS_CNT)) {
- continue;
- } else {
- no_gnd_mic_swap_cnt = 0;
- }
- }
- if ((pt_gnd_mic_swap_cnt == GND_MIC_SWAP_THRESHOLD) &&
- (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP)) {
- /*
- * if switch is toggled, check again,
- * otherwise report unsupported plug
- */
- if (mbhc->mbhc_cfg->swap_gnd_mic &&
- mbhc->mbhc_cfg->swap_gnd_mic(codec)) {
- pr_debug("%s: US_EU gpio present,flip switch\n"
- , __func__);
- continue;
- }
- }
- }
-
- WCD_MBHC_REG_READ(WCD_MBHC_HPHL_SCHMT_RESULT, hphl_sch);
- WCD_MBHC_REG_READ(WCD_MBHC_MIC_SCHMT_RESULT, mic_sch);
- if (hs_comp_res && !(hphl_sch || mic_sch)) {
- pr_debug("%s: cable is extension cable\n", __func__);
- plug_type = MBHC_PLUG_TYPE_HIGH_HPH;
- wrk_complete = true;
- } else {
- pr_debug("%s: cable might be headset: %d\n", __func__,
- plug_type);
- if (!(plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP)) {
- plug_type = MBHC_PLUG_TYPE_HEADSET;
- /*
- * Report headset only if not already reported
- * and if there is not button press without
- * release
- */
- if (((mbhc->current_plug !=
- MBHC_PLUG_TYPE_HEADSET) &&
- (mbhc->current_plug !=
- MBHC_PLUG_TYPE_ANC_HEADPHONE)) &&
- !wcd_swch_level_remove(mbhc) &&
- !mbhc->btn_press_intr) {
- pr_debug("%s: cable is %sheadset\n",
- __func__,
- ((spl_hs_count ==
- WCD_MBHC_SPL_HS_CNT) ?
- "special ":""));
- goto report;
- }
- }
- wrk_complete = false;
- }
- }
- if (!wrk_complete && mbhc->btn_press_intr) {
- pr_debug("%s: Can be slow insertion of headphone\n", __func__);
- wcd_cancel_btn_work(mbhc);
- plug_type = MBHC_PLUG_TYPE_HEADPHONE;
- }
- /*
- * If plug_tye is headset, we might have already reported either in
- * detect_plug-type or in above while loop, no need to report again
- */
- if (!wrk_complete && ((plug_type == MBHC_PLUG_TYPE_HEADSET) ||
- (plug_type == MBHC_PLUG_TYPE_ANC_HEADPHONE))) {
- pr_debug("%s: plug_type:0x%x already reported\n",
- __func__, mbhc->current_plug);
- goto enable_supply;
- }
-
- if (plug_type == MBHC_PLUG_TYPE_HIGH_HPH &&
- (!det_extn_cable_en)) {
- if (wcd_is_special_headset(mbhc)) {
- pr_debug("%s: Special headset found %d\n",
- __func__, plug_type);
- plug_type = MBHC_PLUG_TYPE_HEADSET;
- goto report;
- }
- }
-
-report:
- if (wcd_swch_level_remove(mbhc)) {
- pr_debug("%s: Switch level is low\n", __func__);
- goto exit;
- }
- if (plug_type == MBHC_PLUG_TYPE_GND_MIC_SWAP && mbhc->btn_press_intr) {
- pr_debug("%s: insertion of headphone with swap\n", __func__);
- wcd_cancel_btn_work(mbhc);
- plug_type = MBHC_PLUG_TYPE_HEADPHONE;
- }
- pr_debug("%s: Valid plug found, plug type %d wrk_cmpt %d btn_intr %d\n",
- __func__, plug_type, wrk_complete,
- mbhc->btn_press_intr);
- WCD_MBHC_RSC_LOCK(mbhc);
- wcd_mbhc_find_plug_and_report(mbhc, plug_type);
- WCD_MBHC_RSC_UNLOCK(mbhc);
-enable_supply:
- if (mbhc->mbhc_cb->mbhc_micbias_control)
- wcd_mbhc_update_fsm_source(mbhc, plug_type);
- else
- wcd_enable_mbhc_supply(mbhc, plug_type);
-exit:
- if (mbhc->mbhc_cb->mbhc_micbias_control &&
- !mbhc->micbias_enable)
- mbhc->mbhc_cb->mbhc_micbias_control(codec, MIC_BIAS_2,
- MICB_DISABLE);
- if (mbhc->mbhc_cb->micbias_enable_status) {
- micbias1 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
- MIC_BIAS_1);
- micbias2 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
- MIC_BIAS_2);
- }
-
- if (mbhc->mbhc_cfg->detect_extn_cable &&
- ((plug_type == MBHC_PLUG_TYPE_HEADPHONE) ||
- (plug_type == MBHC_PLUG_TYPE_HEADSET)) &&
- !mbhc->hs_detect_work_stop) {
- WCD_MBHC_RSC_LOCK(mbhc);
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM, true);
- WCD_MBHC_RSC_UNLOCK(mbhc);
- }
- if (mbhc->mbhc_cb->set_cap_mode)
- mbhc->mbhc_cb->set_cap_mode(codec, micbias1, micbias2);
-
- if (mbhc->mbhc_cb->hph_pull_down_ctrl)
- mbhc->mbhc_cb->hph_pull_down_ctrl(codec, true);
-
- mbhc->mbhc_cb->lock_sleep(mbhc, false);
- pr_debug("%s: leave\n", __func__);
-}
-
-/* called under codec_resource_lock acquisition */
-static void wcd_mbhc_detect_plug_type(struct wcd_mbhc *mbhc)
-{
- struct snd_soc_codec *codec = mbhc->codec;
- bool micbias1 = false;
-
- pr_debug("%s: enter\n", __func__);
- WCD_MBHC_RSC_ASSERT_LOCKED(mbhc);
-
- if (mbhc->mbhc_cb->hph_pull_down_ctrl)
- mbhc->mbhc_cb->hph_pull_down_ctrl(codec, false);
-
- if (mbhc->mbhc_cb->micbias_enable_status)
- micbias1 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
- MIC_BIAS_1);
-
- if (mbhc->mbhc_cb->set_cap_mode)
- mbhc->mbhc_cb->set_cap_mode(codec, micbias1, true);
-
- if (mbhc->mbhc_cb->mbhc_micbias_control)
- mbhc->mbhc_cb->mbhc_micbias_control(codec, MIC_BIAS_2,
- MICB_ENABLE);
- else
- wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_MB);
-
- /* Re-initialize button press completion object */
- reinit_completion(&mbhc->btn_press_compl);
- wcd_schedule_hs_detect_plug(mbhc, &mbhc->correct_plug_swch);
- pr_debug("%s: leave\n", __func__);
-}
+EXPORT_SYMBOL(wcd_mbhc_find_plug_and_report);
static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
{
bool detection_type = 0;
bool micbias1 = false;
struct snd_soc_codec *codec = mbhc->codec;
+ enum snd_jack_types jack_type;
dev_dbg(codec->dev, "%s: enter\n", __func__);
-
WCD_MBHC_RSC_LOCK(mbhc);
-
mbhc->in_swch_irq_handler = true;
/* cancel pending button press */
@@ -1521,7 +827,11 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
pr_debug("%s: mbhc->current_plug: %d detection_type: %d\n", __func__,
mbhc->current_plug, detection_type);
- wcd_cancel_hs_detect_plug(mbhc, &mbhc->correct_plug_swch);
+ if (mbhc->mbhc_fn->wcd_cancel_hs_detect_plug)
+ mbhc->mbhc_fn->wcd_cancel_hs_detect_plug(mbhc,
+ &mbhc->correct_plug_swch);
+ else
+ pr_info("%s: hs_detect_plug work not cancelled\n", __func__);
if (mbhc->mbhc_cb->micbias_enable_status)
micbias1 = mbhc->mbhc_cb->micbias_enable_status(mbhc,
@@ -1554,7 +864,8 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
mbhc->mbhc_cb->enable_mb_source(mbhc, true);
mbhc->btn_press_intr = false;
mbhc->is_btn_press = false;
- wcd_mbhc_detect_plug_type(mbhc);
+ if (mbhc->mbhc_fn)
+ mbhc->mbhc_fn->wcd_mbhc_detect_plug_type(mbhc);
} else if ((mbhc->current_plug != MBHC_PLUG_TYPE_NONE)
&& !detection_type) {
/* Disable external voltage source to micbias if present */
@@ -1572,50 +883,41 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
mbhc->btn_press_intr = false;
mbhc->is_btn_press = false;
- if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM,
- false);
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
- false);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE,
- 1);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
- wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADPHONE);
- } else if (mbhc->current_plug == MBHC_PLUG_TYPE_GND_MIC_SWAP) {
- wcd_mbhc_report_plug(mbhc, 0, SND_JACK_UNSUPPORTED);
- } else if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADSET) {
+ switch (mbhc->current_plug) {
+ case MBHC_PLUG_TYPE_HEADPHONE:
+ jack_type = SND_JACK_HEADPHONE;
+ break;
+ case MBHC_PLUG_TYPE_GND_MIC_SWAP:
+ jack_type = SND_JACK_UNSUPPORTED;
+ break;
+ case MBHC_PLUG_TYPE_HEADSET:
/* make sure to turn off Rbias */
if (mbhc->mbhc_cb->micb_internal)
mbhc->mbhc_cb->micb_internal(codec, 1, false);
-
/* Pulldown micbias */
WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_PULLDOWN_CTRL, 1);
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM,
- false);
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
- false);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE,
- 1);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
- wcd_mbhc_report_plug(mbhc, 0, SND_JACK_HEADSET);
- } else if (mbhc->current_plug == MBHC_PLUG_TYPE_HIGH_HPH) {
+ jack_type = SND_JACK_HEADSET;
+ break;
+ case MBHC_PLUG_TYPE_HIGH_HPH:
mbhc->is_extn_cable = false;
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM,
- false);
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
- false);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE,
- 1);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
- wcd_mbhc_report_plug(mbhc, 0, SND_JACK_LINEOUT);
- } else if (mbhc->current_plug == MBHC_PLUG_TYPE_ANC_HEADPHONE) {
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM, false);
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE,
- 0);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
- wcd_mbhc_report_plug(mbhc, 0, SND_JACK_ANC_HEADPHONE);
+ jack_type = SND_JACK_LINEOUT;
+ break;
+ case MBHC_PLUG_TYPE_ANC_HEADPHONE:
+ jack_type = SND_JACK_ANC_HEADPHONE;
+ break;
+ default:
+ pr_info("%s: Invalid current plug: %d\n",
+ __func__, mbhc->current_plug);
+ jack_type = SND_JACK_UNSUPPORTED;
+ break;
}
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM, false);
+ wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS, false);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE, 1);
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
+ mbhc->extn_cable_hph_rem = false;
+ wcd_mbhc_report_plug(mbhc, 0, jack_type);
+
} else if (!detection_type) {
/* Disable external voltage source to micbias if present */
if (mbhc->mbhc_cb->enable_mb_source)
@@ -1623,6 +925,7 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
/* Disable HW FSM */
WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_BTN_ISRC_CTL, 0);
+ mbhc->extn_cable_hph_rem = false;
}
mbhc->in_swch_irq_handler = false;
@@ -1648,7 +951,7 @@ static irqreturn_t wcd_mbhc_mech_plug_detect_irq(int irq, void *data)
return r;
}
-static int wcd_mbhc_get_button_mask(struct wcd_mbhc *mbhc)
+int wcd_mbhc_get_button_mask(struct wcd_mbhc *mbhc)
{
int mask = 0;
int btn;
@@ -1680,203 +983,7 @@ static int wcd_mbhc_get_button_mask(struct wcd_mbhc *mbhc)
return mask;
}
-
-static irqreturn_t wcd_mbhc_hs_ins_irq(int irq, void *data)
-{
- struct wcd_mbhc *mbhc = data;
- bool detection_type = 0, hphl_sch = 0, mic_sch = 0;
- u16 elect_result = 0;
- static u16 hphl_trigerred;
- static u16 mic_trigerred;
-
- pr_debug("%s: enter\n", __func__);
- if (!mbhc->mbhc_cfg->detect_extn_cable) {
- pr_debug("%s: Returning as Extension cable feature not enabled\n",
- __func__);
- return IRQ_HANDLED;
- }
- WCD_MBHC_RSC_LOCK(mbhc);
-
- WCD_MBHC_REG_READ(WCD_MBHC_ELECT_DETECTION_TYPE, detection_type);
- WCD_MBHC_REG_READ(WCD_MBHC_ELECT_RESULT, elect_result);
-
- pr_debug("%s: detection_type %d, elect_result %x\n", __func__,
- detection_type, elect_result);
- if (detection_type) {
- /* check if both Left and MIC Schmitt triggers are triggered */
- WCD_MBHC_REG_READ(WCD_MBHC_HPHL_SCHMT_RESULT, hphl_sch);
- WCD_MBHC_REG_READ(WCD_MBHC_MIC_SCHMT_RESULT, mic_sch);
- if (hphl_sch && mic_sch) {
- /* Go for plug type determination */
- pr_debug("%s: Go for plug type determination\n",
- __func__);
- goto determine_plug;
-
- } else {
- if (mic_sch) {
- mic_trigerred++;
- pr_debug("%s: Insertion MIC trigerred %d\n",
- __func__, mic_trigerred);
- WCD_MBHC_REG_UPDATE_BITS(
- WCD_MBHC_ELECT_SCHMT_ISRC,
- 0);
- msleep(20);
- WCD_MBHC_REG_UPDATE_BITS(
- WCD_MBHC_ELECT_SCHMT_ISRC,
- 1);
- }
- if (hphl_sch) {
- hphl_trigerred++;
- pr_debug("%s: Insertion HPHL trigerred %d\n",
- __func__, hphl_trigerred);
- }
- if (mic_trigerred && hphl_trigerred) {
- /* Go for plug type determination */
- pr_debug("%s: Go for plug type determination\n",
- __func__);
- goto determine_plug;
- }
- }
- }
- WCD_MBHC_RSC_UNLOCK(mbhc);
- pr_debug("%s: leave\n", __func__);
- return IRQ_HANDLED;
-
-determine_plug:
- /*
- * Disable HPHL trigger and MIC Schmitt triggers.
- * Setup for insertion detection.
- */
- pr_debug("%s: Disable insertion interrupt\n", __func__);
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
- false);
-
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 0);
- hphl_trigerred = 0;
- mic_trigerred = 0;
- mbhc->is_extn_cable = true;
- mbhc->btn_press_intr = false;
- mbhc->is_btn_press = false;
- wcd_mbhc_detect_plug_type(mbhc);
- WCD_MBHC_RSC_UNLOCK(mbhc);
- pr_debug("%s: leave\n", __func__);
- return IRQ_HANDLED;
-}
-
-static irqreturn_t wcd_mbhc_hs_rem_irq(int irq, void *data)
-{
- struct wcd_mbhc *mbhc = data;
- u8 hs_comp_result = 0, hphl_sch = 0, mic_sch = 0;
- static u16 hphl_trigerred;
- static u16 mic_trigerred;
- unsigned long timeout;
- bool removed = true;
- int retry = 0;
-
- pr_debug("%s: enter\n", __func__);
-
- WCD_MBHC_RSC_LOCK(mbhc);
-
- timeout = jiffies +
- msecs_to_jiffies(WCD_FAKE_REMOVAL_MIN_PERIOD_MS);
- do {
- retry++;
- /*
- * read the result register every 10ms to look for
- * any change in HS_COMP_RESULT bit
- */
- usleep_range(10000, 10100);
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_result);
- pr_debug("%s: Check result reg for fake removal: hs_comp_res %x\n",
- __func__, hs_comp_result);
- if ((!hs_comp_result) &&
- retry > FAKE_REM_RETRY_ATTEMPTS) {
- removed = false;
- break;
- }
- } while (!time_after(jiffies, timeout));
-
- if (wcd_swch_level_remove(mbhc)) {
- pr_debug("%s: Switch level is low ", __func__);
- goto exit;
- }
- pr_debug("%s: headset %s actually removed\n", __func__,
- removed ? "" : "not ");
-
- WCD_MBHC_REG_READ(WCD_MBHC_HPHL_SCHMT_RESULT, hphl_sch);
- WCD_MBHC_REG_READ(WCD_MBHC_MIC_SCHMT_RESULT, mic_sch);
- WCD_MBHC_REG_READ(WCD_MBHC_HS_COMP_RESULT, hs_comp_result);
-
- if (removed) {
- if (!(hphl_sch && mic_sch && hs_comp_result)) {
- /*
- * extension cable is still plugged in
- * report it as LINEOUT device
- */
- goto report_unplug;
- } else {
- if (!mic_sch) {
- mic_trigerred++;
- pr_debug("%s: Removal MIC trigerred %d\n",
- __func__, mic_trigerred);
- }
- if (!hphl_sch) {
- hphl_trigerred++;
- pr_debug("%s: Removal HPHL trigerred %d\n",
- __func__, hphl_trigerred);
- }
- if (mic_trigerred && hphl_trigerred) {
- /*
- * extension cable is still plugged in
- * report it as LINEOUT device
- */
- goto report_unplug;
- }
- }
- }
-exit:
- WCD_MBHC_RSC_UNLOCK(mbhc);
- pr_debug("%s: leave\n", __func__);
- return IRQ_HANDLED;
-
-report_unplug:
-
- /* cancel pending button press */
- if (wcd_cancel_btn_work(mbhc))
- pr_debug("%s: button press is canceled\n", __func__);
- /* cancel correct work function */
- wcd_cancel_hs_detect_plug(mbhc, &mbhc->correct_plug_swch);
-
- pr_debug("%s: Report extension cable\n", __func__);
- wcd_mbhc_report_plug(mbhc, 1, SND_JACK_LINEOUT);
- /*
- * If PA is enabled HPHL schmitt trigger can
- * be unreliable, make sure to disable it
- */
- if (test_bit(WCD_MBHC_EVENT_PA_HPHL,
- &mbhc->event_state))
- wcd_mbhc_set_and_turnoff_hph_padac(mbhc);
- /*
- * Disable HPHL trigger and MIC Schmitt triggers.
- * Setup for insertion detection.
- */
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_REM,
- false);
- wcd_enable_curr_micbias(mbhc, WCD_MBHC_EN_NONE);
- /* Disable HW FSM */
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_FSM_EN, 0);
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_SCHMT_ISRC, 3);
-
- /* Set the detection type appropriately */
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_ELECT_DETECTION_TYPE, 1);
- wcd_mbhc_hs_elec_irq(mbhc, WCD_MBHC_ELEC_HS_INS,
- true);
- hphl_trigerred = 0;
- mic_trigerred = 0;
- WCD_MBHC_RSC_UNLOCK(mbhc);
- pr_debug("%s: leave\n", __func__);
- return IRQ_HANDLED;
-}
+EXPORT_SYMBOL(wcd_mbhc_get_button_mask);
static void wcd_btn_lpress_fn(struct work_struct *work)
{
@@ -1998,8 +1105,11 @@ static irqreturn_t wcd_mbhc_release_handler(int irq, void *data)
* If current plug is headphone then there is no chance to
* get btn release interrupt, so connected cable should be
* headset not headphone.
+ * For ADC MBHC, ADC_COMPLETE interrupt will be generated
+ * in this case. So skip the check here.
*/
- if (mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
+ if (!WCD_MBHC_DETECTION &&
+ mbhc->current_plug == MBHC_PLUG_TYPE_HEADPHONE) {
wcd_mbhc_find_plug_and_report(mbhc, MBHC_PLUG_TYPE_HEADSET);
goto exit;
@@ -2181,7 +1291,6 @@ static int wcd_mbhc_initialise(struct wcd_mbhc *mbhc)
wcd_program_btn_threshold(mbhc, false);
- INIT_WORK(&mbhc->correct_plug_swch, wcd_correct_swch_plug);
init_completion(&mbhc->btn_press_compl);
@@ -2271,7 +1380,7 @@ static void wcd_mbhc_fw_read(struct work_struct *work)
(void) wcd_mbhc_initialise(mbhc);
}
-int wcd_mbhc_set_keycode(struct wcd_mbhc *mbhc)
+static int wcd_mbhc_set_keycode(struct wcd_mbhc *mbhc)
{
enum snd_jack_types type;
int i, ret, result = 0;
@@ -2761,6 +1870,7 @@ int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
mbhc->btn_press_intr = false;
mbhc->is_hs_recording = false;
mbhc->is_extn_cable = false;
+ mbhc->extn_cable_hph_rem = false;
mbhc->hph_type = WCD_MBHC_HPH_NONE;
mbhc->wcd_mbhc_regs = wcd_mbhc_regs;
@@ -2784,6 +1894,7 @@ int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
return -EINVAL;
}
+ /* No need to create new sound card jacks if is is already created */
if (mbhc->headset_jack.jack == NULL) {
ret = snd_soc_card_jack_new(codec->component.card,
"Headset Jack", WCD_MBHC_JACK_MASK,
@@ -2833,6 +1944,27 @@ int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
init_waitqueue_head(&mbhc->wait_btn_press);
mutex_init(&mbhc->codec_resource_lock);
+ switch (WCD_MBHC_DETECTION) {
+ case WCD_DETECTION_LEGACY:
+ wcd_mbhc_legacy_init(mbhc);
+ break;
+ case WCD_DETECTION_ADC:
+ wcd_mbhc_adc_init(mbhc);
+ break;
+ default:
+ pr_err("%s: Unknown detection logic type %d\n",
+ __func__, WCD_MBHC_DETECTION);
+ break;
+ }
+
+ if (!mbhc->mbhc_fn ||
+ !mbhc->mbhc_fn->wcd_mbhc_hs_ins_irq ||
+ !mbhc->mbhc_fn->wcd_mbhc_hs_rem_irq ||
+ !mbhc->mbhc_fn->wcd_mbhc_detect_plug_type ||
+ !mbhc->mbhc_fn->wcd_cancel_hs_detect_plug) {
+ pr_err("%s: mbhc function pointer is NULL\n", __func__);
+ goto err_mbhc_sw_irq;
+ }
ret = mbhc->mbhc_cb->request_irq(codec, mbhc->intr_ids->mbhc_sw_intr,
wcd_mbhc_mech_plug_detect_irq,
"mbhc sw intr", mbhc);
@@ -2845,8 +1977,7 @@ int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
ret = mbhc->mbhc_cb->request_irq(codec,
mbhc->intr_ids->mbhc_btn_press_intr,
wcd_mbhc_btn_press_handler,
- "Button Press detect",
- mbhc);
+ "Button Press detect", mbhc);
if (ret) {
pr_err("%s: Failed to request irq %d\n", __func__,
mbhc->intr_ids->mbhc_btn_press_intr);
@@ -2865,7 +1996,7 @@ int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
ret = mbhc->mbhc_cb->request_irq(codec,
mbhc->intr_ids->mbhc_hs_ins_intr,
- wcd_mbhc_hs_ins_irq,
+ mbhc->mbhc_fn->wcd_mbhc_hs_ins_irq,
"Elect Insert", mbhc);
if (ret) {
pr_err("%s: Failed to request irq %d\n", __func__,
@@ -2878,7 +2009,7 @@ int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
ret = mbhc->mbhc_cb->request_irq(codec,
mbhc->intr_ids->mbhc_hs_rem_intr,
- wcd_mbhc_hs_rem_irq,
+ mbhc->mbhc_fn->wcd_mbhc_hs_rem_irq,
"Elect Remove", mbhc);
if (ret) {
pr_err("%s: Failed to request irq %d\n", __func__,
diff --git a/sound/soc/codecs/wcd-mbhc-v2.h b/sound/soc/codecs/wcd-mbhc-v2.h
index e6cd1971..dd3d35c 100644
--- a/sound/soc/codecs/wcd-mbhc-v2.h
+++ b/sound/soc/codecs/wcd-mbhc-v2.h
@@ -27,6 +27,149 @@
#define WCD_MONO_HS_MIN_THR 2
#define WCD_MBHC_STRINGIFY(s) __stringify(s)
+#define WCD_MBHC_REGISTER(rid, rreg, rmask, rshift, rinvert) \
+{ .id = rid, .reg = rreg, .mask = rmask, .offset = rshift, .invert = rinvert }
+
+#define WCD_MBHC_RSC_LOCK(mbhc) \
+{ \
+ pr_debug("%s: Acquiring BCL\n", __func__); \
+ mutex_lock(&mbhc->codec_resource_lock); \
+ pr_debug("%s: Acquiring BCL done\n", __func__); \
+}
+
+#define WCD_MBHC_RSC_UNLOCK(mbhc) \
+{ \
+ pr_debug("%s: Release BCL\n", __func__); \
+ mutex_unlock(&mbhc->codec_resource_lock); \
+}
+
+#define WCD_MBHC_RSC_ASSERT_LOCKED(mbhc) \
+{ \
+ WARN_ONCE(!mutex_is_locked(&mbhc->codec_resource_lock), \
+ "%s: BCL should have acquired\n", __func__); \
+}
+
+/*
+ * Macros to update and read mbhc register bits. Check for
+ * "0" before updating or reading the register, because it
+ * is possible that one codec wants to write to that bit and
+ * other codec does not.
+ */
+#define WCD_MBHC_REG_UPDATE_BITS(function, val) \
+do { \
+ if (mbhc->wcd_mbhc_regs[function].reg) { \
+ snd_soc_update_bits(mbhc->codec, \
+ mbhc->wcd_mbhc_regs[function].reg, \
+ mbhc->wcd_mbhc_regs[function].mask, \
+ val << (mbhc->wcd_mbhc_regs[function].offset)); \
+ } \
+} while (0)
+
+#define WCD_MBHC_REG_READ(function, val) \
+do { \
+ if (mbhc->wcd_mbhc_regs[function].reg) { \
+ val = (((snd_soc_read(mbhc->codec, \
+ mbhc->wcd_mbhc_regs[function].reg)) & \
+ (mbhc->wcd_mbhc_regs[function].mask)) >> \
+ (mbhc->wcd_mbhc_regs[function].offset)); \
+ } else { \
+ val = -EINVAL; \
+ } \
+} while (0)
+
+#define WCD_MBHC_CAL_SIZE(buttons, rload) ( \
+ sizeof(struct wcd_mbhc_general_cfg) + \
+ sizeof(struct wcd_mbhc_plug_detect_cfg) + \
+ ((sizeof(s16) + sizeof(s16)) * buttons) + \
+ sizeof(struct wcd_mbhc_plug_type_cfg) + \
+ sizeof(struct wcd_mbhc_btn_detect_cfg) + \
+ sizeof(struct wcd_mbhc_imped_detect_cfg) + \
+ ((sizeof(u16) + sizeof(u16)) * rload) \
+ )
+
+#define WCD_MBHC_CAL_GENERAL_PTR(cali) ( \
+ (struct wcd_mbhc_general_cfg *) cali)
+#define WCD_MBHC_CAL_PLUG_DET_PTR(cali) ( \
+ (struct wcd_mbhc_plug_detect_cfg *) \
+ &(WCD_MBHC_CAL_GENERAL_PTR(cali)[1]))
+#define WCD_MBHC_CAL_PLUG_TYPE_PTR(cali) ( \
+ (struct wcd_mbhc_plug_type_cfg *) \
+ &(WCD_MBHC_CAL_PLUG_DET_PTR(cali)[1]))
+#define WCD_MBHC_CAL_BTN_DET_PTR(cali) ( \
+ (struct wcd_mbhc_btn_detect_cfg *) \
+ &(WCD_MBHC_CAL_PLUG_TYPE_PTR(cali)[1]))
+#define WCD_MBHC_CAL_IMPED_DET_PTR(cali) ( \
+ (struct wcd_mbhc_imped_detect_cfg *) \
+ (((void *)&WCD_MBHC_CAL_BTN_DET_PTR(cali)[1]) + \
+ (WCD_MBHC_CAL_BTN_DET_PTR(cali)->num_btn * \
+ (sizeof(WCD_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_low[0]) + \
+ sizeof(WCD_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_high[0])))) \
+ )
+
+#define WCD_MBHC_CAL_MIN_SIZE ( \
+ sizeof(struct wcd_mbhc_general_cfg) + \
+ sizeof(struct wcd_mbhc_plug_detect_cfg) + \
+ sizeof(struct wcd_mbhc_plug_type_cfg) + \
+ sizeof(struct wcd_mbhc_btn_detect_cfg) + \
+ sizeof(struct wcd_mbhc_imped_detect_cfg) + \
+ (sizeof(u16)*2) \
+ )
+
+#define WCD_MBHC_CAL_BTN_SZ(cfg_ptr) ( \
+ sizeof(struct wcd_mbhc_btn_detect_cfg) + \
+ (cfg_ptr->num_btn * (sizeof(cfg_ptr->_v_btn_low[0]) + \
+ sizeof(cfg_ptr->_v_btn_high[0]))))
+
+#define WCD_MBHC_CAL_IMPED_MIN_SZ ( \
+ sizeof(struct wcd_mbhc_imped_detect_cfg) + sizeof(u16) * 2)
+
+#define WCD_MBHC_CAL_IMPED_SZ(cfg_ptr) ( \
+ sizeof(struct wcd_mbhc_imped_detect_cfg) + \
+ (cfg_ptr->_n_rload * \
+ (sizeof(cfg_ptr->_rload[0]) + sizeof(cfg_ptr->_alpha[0]))))
+
+#define WCD_MBHC_JACK_MASK (SND_JACK_HEADSET | SND_JACK_OC_HPHL | \
+ SND_JACK_OC_HPHR | SND_JACK_LINEOUT | \
+ SND_JACK_MECHANICAL | SND_JACK_MICROPHONE2 | \
+ SND_JACK_UNSUPPORTED)
+
+#define WCD_MBHC_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 | \
+ SND_JACK_BTN_4 | SND_JACK_BTN_5)
+#define OCP_ATTEMPT 20
+#define HS_DETECT_PLUG_TIME_MS (3 * 1000)
+#define SPECIAL_HS_DETECT_TIME_MS (2 * 1000)
+#define MBHC_BUTTON_PRESS_THRESHOLD_MIN 250
+#define GND_MIC_SWAP_THRESHOLD 4
+#define WCD_FAKE_REMOVAL_MIN_PERIOD_MS 100
+#define HS_VREF_MIN_VAL 1400
+#define FW_READ_ATTEMPTS 15
+#define FW_READ_TIMEOUT 4000000
+#define FAKE_REM_RETRY_ATTEMPTS 3
+#define MAX_IMPED 60000
+
+#define WCD_MBHC_BTN_PRESS_COMPL_TIMEOUT_MS 50
+#define ANC_DETECT_RETRY_CNT 7
+#define WCD_MBHC_SPL_HS_CNT 2
+
+enum wcd_mbhc_detect_logic {
+ WCD_DETECTION_LEGACY,
+ WCD_DETECTION_ADC,
+};
+
+#ifdef CONFIG_SND_SOC_WCD_MBHC_ADC
+#define WCD_MBHC_DETECTION WCD_DETECTION_ADC
+#else
+#define WCD_MBHC_DETECTION WCD_DETECTION_LEGACY
+#endif
+
+enum wcd_mbhc_cs_mb_en_flag {
+ WCD_MBHC_EN_CS = 0,
+ WCD_MBHC_EN_MB,
+ WCD_MBHC_EN_PULLUP,
+ WCD_MBHC_EN_NONE,
+};
+
enum {
WCD_MBHC_ELEC_HS_INS,
WCD_MBHC_ELEC_HS_REM,
@@ -71,6 +214,14 @@ enum wcd_mbhc_register_function {
WCD_MBHC_HPHR_OCP_DET_EN,
WCD_MBHC_HPHL_OCP_STATUS,
WCD_MBHC_HPHR_OCP_STATUS,
+ WCD_MBHC_ADC_EN,
+ WCD_MBHC_ADC_COMPLETE,
+ WCD_MBHC_ADC_TIMEOUT,
+ WCD_MBHC_ADC_RESULT,
+ WCD_MBHC_MICB2_VOUT,
+ WCD_MBHC_ADC_MODE,
+ WCD_MBHC_DETECTION_DONE,
+ WCD_MBHC_ELECT_ISRC_EN,
WCD_MBHC_REG_FUNC_MAX,
};
@@ -141,6 +292,7 @@ enum wcd_mbhc_event_state {
WCD_MBHC_EVENT_PA_HPHL,
WCD_MBHC_EVENT_PA_HPHR,
};
+
struct wcd_mbhc_general_cfg {
u8 t_ldoh;
u8 t_bg_fast_settle;
@@ -295,56 +447,6 @@ struct wcd_mbhc_register {
u8 invert;
};
-#define WCD_MBHC_REGISTER(rid, rreg, rmask, rshift, rinvert) \
-{ .id = rid, .reg = rreg, .mask = rmask, .offset = rshift, .invert = rinvert }
-
-#define WCD_MBHC_RSC_LOCK(mbhc) \
-{ \
- pr_debug("%s: Acquiring BCL\n", __func__); \
- mutex_lock(&mbhc->codec_resource_lock); \
- pr_debug("%s: Acquiring BCL done\n", __func__); \
-}
-
-#define WCD_MBHC_RSC_UNLOCK(mbhc) \
-{ \
- pr_debug("%s: Release BCL\n", __func__); \
- mutex_unlock(&mbhc->codec_resource_lock); \
-}
-
-#define WCD_MBHC_RSC_ASSERT_LOCKED(mbhc) \
-{ \
- WARN_ONCE(!mutex_is_locked(&mbhc->codec_resource_lock), \
- "%s: BCL should have acquired\n", __func__); \
-}
-
-/*
- * Macros to update and read mbhc register bits. Check for
- * "0" before updating or reading the register, because it
- * is possible that one codec wants to write to that bit and
- * other codec does not.
- */
-#define WCD_MBHC_REG_UPDATE_BITS(function, val) \
-do { \
- if (mbhc->wcd_mbhc_regs[function].reg) { \
- snd_soc_update_bits(mbhc->codec, \
- mbhc->wcd_mbhc_regs[function].reg, \
- mbhc->wcd_mbhc_regs[function].mask, \
- val << (mbhc->wcd_mbhc_regs[function].offset)); \
- } \
-} while (0)
-
-#define WCD_MBHC_REG_READ(function, val) \
-do { \
- if (mbhc->wcd_mbhc_regs[function].reg) { \
- val = (((snd_soc_read(mbhc->codec, \
- mbhc->wcd_mbhc_regs[function].reg)) & \
- (mbhc->wcd_mbhc_regs[function].mask)) >> \
- (mbhc->wcd_mbhc_regs[function].offset)); \
- } else { \
- val = -EINVAL; \
- } \
-} while (0)
-
struct wcd_mbhc_cb {
int (*enable_mb_source)(struct wcd_mbhc *, bool);
void (*trim_btn_reg)(struct snd_soc_codec *);
@@ -388,6 +490,15 @@ struct wcd_mbhc_cb {
bool (*hph_register_recovery)(struct wcd_mbhc *);
};
+struct wcd_mbhc_fn {
+ irqreturn_t (*wcd_mbhc_hs_ins_irq)(int irq, void *data);
+ irqreturn_t (*wcd_mbhc_hs_rem_irq)(int irq, void *data);
+ void (*wcd_mbhc_detect_plug_type)(struct wcd_mbhc *mbhc);
+ bool (*wcd_mbhc_detect_anc_plug_type)(struct wcd_mbhc *mbhc);
+ void (*wcd_cancel_hs_detect_plug)(struct wcd_mbhc *mbhc,
+ struct work_struct *work);
+};
+
struct wcd_mbhc {
/* Delayed work to report long button press */
struct delayed_work mbhc_btn_dwork;
@@ -417,6 +528,7 @@ struct wcd_mbhc {
bool is_extn_cable;
bool skip_imped_detection;
bool is_btn_already_regd;
+ bool extn_cable_hph_rem;
struct snd_soc_codec *codec;
/* Work to perform MBHC Firmware Read */
@@ -461,101 +573,20 @@ struct wcd_mbhc {
struct notifier_block psy_nb;
struct power_supply *usb_psy;
struct work_struct usbc_analog_work;
+
+ struct wcd_mbhc_fn *mbhc_fn;
};
-#define WCD_MBHC_CAL_SIZE(buttons, rload) ( \
- sizeof(struct wcd_mbhc_general_cfg) + \
- sizeof(struct wcd_mbhc_plug_detect_cfg) + \
- ((sizeof(s16) + sizeof(s16)) * buttons) + \
- sizeof(struct wcd_mbhc_plug_type_cfg) + \
- sizeof(struct wcd_mbhc_btn_detect_cfg) + \
- sizeof(struct wcd_mbhc_imped_detect_cfg) + \
- ((sizeof(u16) + sizeof(u16)) * rload) \
- )
-
-#define WCD_MBHC_CAL_GENERAL_PTR(cali) ( \
- (struct wcd_mbhc_general_cfg *) cali)
-#define WCD_MBHC_CAL_PLUG_DET_PTR(cali) ( \
- (struct wcd_mbhc_plug_detect_cfg *) \
- &(WCD_MBHC_CAL_GENERAL_PTR(cali)[1]))
-#define WCD_MBHC_CAL_PLUG_TYPE_PTR(cali) ( \
- (struct wcd_mbhc_plug_type_cfg *) \
- &(WCD_MBHC_CAL_PLUG_DET_PTR(cali)[1]))
-#define WCD_MBHC_CAL_BTN_DET_PTR(cali) ( \
- (struct wcd_mbhc_btn_detect_cfg *) \
- &(WCD_MBHC_CAL_PLUG_TYPE_PTR(cali)[1]))
-#define WCD_MBHC_CAL_IMPED_DET_PTR(cali) ( \
- (struct wcd_mbhc_imped_detect_cfg *) \
- (((void *)&WCD_MBHC_CAL_BTN_DET_PTR(cali)[1]) + \
- (WCD_MBHC_CAL_BTN_DET_PTR(cali)->num_btn * \
- (sizeof(WCD_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_low[0]) + \
- sizeof(WCD_MBHC_CAL_BTN_DET_PTR(cali)->_v_btn_high[0])))) \
- )
-
-#define WCD_MBHC_CAL_MIN_SIZE ( \
- sizeof(struct wcd_mbhc_general_cfg) + \
- sizeof(struct wcd_mbhc_plug_detect_cfg) + \
- sizeof(struct wcd_mbhc_plug_type_cfg) + \
- sizeof(struct wcd_mbhc_btn_detect_cfg) + \
- sizeof(struct wcd_mbhc_imped_detect_cfg) + \
- (sizeof(u16)*2) \
- )
-
-#define WCD_MBHC_CAL_BTN_SZ(cfg_ptr) ( \
- sizeof(struct wcd_mbhc_btn_detect_cfg) + \
- (cfg_ptr->num_btn * (sizeof(cfg_ptr->_v_btn_low[0]) + \
- sizeof(cfg_ptr->_v_btn_high[0]))))
-
-#define WCD_MBHC_CAL_IMPED_MIN_SZ ( \
- sizeof(struct wcd_mbhc_imped_detect_cfg) + sizeof(u16) * 2)
-
-#define WCD_MBHC_CAL_IMPED_SZ(cfg_ptr) ( \
- sizeof(struct wcd_mbhc_imped_detect_cfg) + \
- (cfg_ptr->_n_rload * \
- (sizeof(cfg_ptr->_rload[0]) + sizeof(cfg_ptr->_alpha[0]))))
-
-#ifdef CONFIG_SND_SOC_WCD_MBHC
-int wcd_mbhc_set_keycode(struct wcd_mbhc *mbhc);
-int wcd_mbhc_start(struct wcd_mbhc *mbhc,
- struct wcd_mbhc_config *mbhc_cfg);
-void wcd_mbhc_stop(struct wcd_mbhc *mbhc);
-int wcd_mbhc_init(struct wcd_mbhc *mbhc, struct snd_soc_codec *codec,
- const struct wcd_mbhc_cb *mbhc_cb,
- const struct wcd_mbhc_intr *mbhc_cdc_intr_ids,
- struct wcd_mbhc_register *mbhc_reg,
- bool impedance_det_en);
-int wcd_mbhc_get_impedance(struct wcd_mbhc *mbhc, uint32_t *zl,
- uint32_t *zr);
-void wcd_mbhc_deinit(struct wcd_mbhc *mbhc);
-#else
-static inline void wcd_mbhc_stop(struct wcd_mbhc *mbhc)
-{
-}
-static inline int wcd_mbhc_init(struct wcd_mbhc *mbhc,
- struct snd_soc_codec *codec,
- const struct wcd_mbhc_cb *mbhc_cb,
- const struct wcd_mbhc_intr *mbhc_cdc_intr_ids,
- struct wcd_mbhc_register *mbhc_reg,
- bool impedance_det_en)
-{
- return 0;
-}
-static inline int wcd_mbhc_start(struct wcd_mbhc *mbhc,
- struct wcd_mbhc_config *mbhc_cfg)
-{
- return 0;
-}
-static inline int wcd_mbhc_get_impedance(struct wcd_mbhc *mbhc,
- uint32_t *zl,
- uint32_t *zr)
-{
- *zl = 0;
- *zr = 0;
- return -EINVAL;
-}
-static inline void wcd_mbhc_deinit(struct wcd_mbhc *mbhc)
-{
-}
-#endif
+void wcd_mbhc_find_plug_and_report(struct wcd_mbhc *mbhc,
+ enum wcd_mbhc_plug_type plug_type);
+void wcd_mbhc_hs_elec_irq(struct wcd_mbhc *mbhc, int irq_type, bool enable);
+void wcd_mbhc_elec_hs_report_unplug(struct wcd_mbhc *mbhc);
+bool wcd_swch_level_remove(struct wcd_mbhc *mbhc);
+void wcd_enable_curr_micbias(const struct wcd_mbhc *mbhc,
+ const enum wcd_mbhc_cs_mb_en_flag cs_mb_en);
+void wcd_mbhc_jack_report(struct wcd_mbhc *mbhc,
+ struct snd_soc_jack *jack, int status, int mask);
+int wcd_cancel_btn_work(struct wcd_mbhc *mbhc);
+int wcd_mbhc_get_button_mask(struct wcd_mbhc *mbhc);
#endif /* __WCD_MBHC_V2_H__ */
diff --git a/sound/soc/codecs/wcd9335.c b/sound/soc/codecs/wcd9335.c
index 5ea0551..dedf4dc 100644
--- a/sound/soc/codecs/wcd9335.c
+++ b/sound/soc/codecs/wcd9335.c
@@ -46,6 +46,7 @@
#include "wcd9xxx-resmgr-v2.h"
#include "wcd_cpe_core.h"
#include "wcdcal-hwdep.h"
+#include "wcd-mbhc-v2-api.h"
#define TASHA_RX_PORT_START_NUMBER 16
diff --git a/sound/soc/codecs/wcd934x/wcd934x-mbhc.c b/sound/soc/codecs/wcd934x/wcd934x-mbhc.c
index 3d032f0..578c347 100644
--- a/sound/soc/codecs/wcd934x/wcd934x-mbhc.c
+++ b/sound/soc/codecs/wcd934x/wcd934x-mbhc.c
@@ -32,6 +32,7 @@
#include "wcd934x.h"
#include "wcd934x-mbhc.h"
#include "../wcdcal-hwdep.h"
+#include "../wcd-mbhc-v2-api.h"
#define TAVIL_ZDET_SUPPORTED true
/* Z value defined in milliohm */
@@ -113,7 +114,7 @@ static struct wcd_mbhc_register
WCD_MBHC_REGISTER("WCD_MBHC_PULLDOWN_CTRL",
0, 0, 0, 0),
WCD_MBHC_REGISTER("WCD_MBHC_ANC_DET_EN",
- WCD934X_ANA_MBHC_ZDET, 0x01, 0, 0),
+ WCD934X_MBHC_CTL_BCS, 0x02, 1, 0),
WCD_MBHC_REGISTER("WCD_MBHC_FSM_STATUS",
WCD934X_MBHC_STATUS_SPARE_1, 0x01, 0, 0),
WCD_MBHC_REGISTER("WCD_MBHC_MUX_CTL",
@@ -126,6 +127,21 @@ static struct wcd_mbhc_register
WCD934X_INTR_PIN1_STATUS0, 0x04, 2, 0),
WCD_MBHC_REGISTER("WCD_MBHC_HPHR_OCP_STATUS",
WCD934X_INTR_PIN1_STATUS0, 0x08, 3, 0),
+ WCD_MBHC_REGISTER("WCD_MBHC_ADC_EN",
+ WCD934X_MBHC_NEW_CTL_1, 0x08, 3, 0),
+ WCD_MBHC_REGISTER("WCD_MBHC_ADC_COMPLETE", WCD934X_MBHC_NEW_FSM_STATUS,
+ 0x40, 6, 0),
+ WCD_MBHC_REGISTER("WCD_MBHC_ADC_TIMEOUT", WCD934X_MBHC_NEW_FSM_STATUS,
+ 0x80, 7, 0),
+ WCD_MBHC_REGISTER("WCD_MBHC_ADC_RESULT", WCD934X_MBHC_NEW_ADC_RESULT,
+ 0xFF, 0, 0),
+ WCD_MBHC_REGISTER("WCD_MBHC_MICB2_VOUT", WCD934X_ANA_MICB2, 0x3F, 0, 0),
+ WCD_MBHC_REGISTER("WCD_MBHC_ADC_MODE",
+ WCD934X_MBHC_NEW_CTL_1, 0x10, 4, 0),
+ WCD_MBHC_REGISTER("WCD_MBHC_DETECTION_DONE",
+ WCD934X_MBHC_NEW_CTL_1, 0x04, 2, 0),
+ WCD_MBHC_REGISTER("WCD_MBHC_ELECT_ISRC_EN",
+ WCD934X_ANA_MBHC_ZDET, 0x02, 1, 0),
};
static const struct wcd_mbhc_intr intr_ids = {
@@ -993,8 +1009,10 @@ int tavil_mbhc_post_ssr_init(struct wcd934x_mbhc *mbhc,
__func__);
goto done;
}
- snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_1, 0x04, 0x04);
- snd_soc_update_bits(codec, WCD934X_MBHC_CTL_BCS, 0x01, 0x01);
+ if (!WCD_MBHC_DETECTION) {
+ snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_1, 0x04, 0x04);
+ snd_soc_update_bits(codec, WCD934X_MBHC_CTL_BCS, 0x01, 0x01);
+ }
done:
return ret;
@@ -1025,8 +1043,9 @@ int tavil_mbhc_init(struct wcd934x_mbhc **mbhc, struct snd_soc_codec *codec,
wcd934x_mbhc->fw_data = fw_data;
BLOCKING_INIT_NOTIFIER_HEAD(&wcd934x_mbhc->notifier);
- ret = wcd_mbhc_init(&wcd934x_mbhc->wcd_mbhc, codec, &mbhc_cb, &intr_ids,
- wcd_mbhc_registers, TAVIL_ZDET_SUPPORTED);
+ ret = wcd_mbhc_init(&wcd934x_mbhc->wcd_mbhc, codec, &mbhc_cb,
+ &intr_ids, wcd_mbhc_registers,
+ TAVIL_ZDET_SUPPORTED);
if (ret) {
dev_err(codec->dev, "%s: mbhc initialization failed\n",
__func__);
@@ -1050,8 +1069,10 @@ int tavil_mbhc_init(struct wcd934x_mbhc **mbhc, struct snd_soc_codec *codec,
snd_soc_add_codec_controls(codec, hph_type_detect_controls,
ARRAY_SIZE(hph_type_detect_controls));
- snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_1, 0x04, 0x04);
- snd_soc_update_bits(codec, WCD934X_MBHC_CTL_BCS, 0x01, 0x01);
+ if (!WCD_MBHC_DETECTION) {
+ snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_1, 0x04, 0x04);
+ snd_soc_update_bits(codec, WCD934X_MBHC_CTL_BCS, 0x01, 0x01);
+ }
return 0;
err: