aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/arm/msm/qcom,idle-state.txt72
-rw-r--r--Documentation/devicetree/bindings/arm/msm/spm.txt47
-rw-r--r--Documentation/devicetree/bindings/dma/qcom_bam_dma.txt4
-rw-r--r--Documentation/devicetree/bindings/mfd/qcom-rpm.txt246
-rw-r--r--Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.txt214
-rw-r--r--Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt159
-rw-r--r--arch/arm/Kconfig.debug136
-rw-r--r--arch/arm/common/Kconfig3
-rw-r--r--arch/arm/common/Makefile1
-rw-r--r--arch/arm/common/krait-l2-accessors.c58
-rw-r--r--arch/arm/configs/multi_v7_defconfig36
-rw-r--r--arch/arm/configs/qcom_defconfig15
-rw-r--r--arch/arm/include/asm/krait-l2-accessors.h20
-rw-r--r--arch/arm/include/asm/vfp.h5
-rw-r--r--arch/arm/include/debug/ks8695.S (renamed from arch/arm/mach-ks8695/include/mach/debug-macro.S)10
-rw-r--r--arch/arm/include/debug/netx.S (renamed from arch/arm/mach-netx/include/mach/debug-macro.S)22
-rw-r--r--arch/arm/include/debug/sa1100.S37
-rw-r--r--arch/arm/mach-omap1/include/mach/debug-macro.S101
-rw-r--r--arch/arm/mach-qcom/Kconfig5
-rw-r--r--arch/arm/mach-qcom/Makefile3
-rw-r--r--arch/arm/mach-qcom/board.c31
-rw-r--r--arch/arm/mach-qcom/platsmp.c2
-rw-r--r--arch/arm/mach-sa1100/include/mach/debug-macro.S62
-rw-r--r--arch/arm/mm/proc-v7.S5
-rw-r--r--arch/arm/vfp/vfphw.S6
-rw-r--r--arch/arm/vfp/vfpmodule.c93
-rw-r--r--drivers/clk/clk-divider.c197
-rw-r--r--drivers/clk/clk-mux.c91
-rw-r--r--drivers/clk/clk.c133
-rw-r--r--drivers/clk/qcom/Kconfig28
-rw-r--r--drivers/clk/qcom/Makefile5
-rw-r--r--drivers/clk/qcom/clk-hfpll.c253
-rw-r--r--drivers/clk/qcom/clk-hfpll.h54
-rw-r--r--drivers/clk/qcom/clk-krait.c166
-rw-r--r--drivers/clk/qcom/clk-krait.h49
-rw-r--r--drivers/clk/qcom/gcc-ipq806x.c83
-rw-r--r--drivers/clk/qcom/gcc-msm8960.c172
-rw-r--r--drivers/clk/qcom/hfpll.c110
-rw-r--r--drivers/clk/qcom/kpss-xcc.c94
-rw-r--r--drivers/clk/qcom/krait-cc.c357
-rw-r--r--drivers/cpufreq/Kconfig.arm9
-rw-r--r--drivers/cpufreq/Makefile1
-rw-r--r--drivers/cpufreq/qcom-cpufreq.c199
-rw-r--r--drivers/cpuidle/Kconfig.arm7
-rw-r--r--drivers/cpuidle/Makefile1
-rw-r--r--drivers/cpuidle/cpuidle-qcom.c87
-rw-r--r--drivers/dma/qcom_bam_dma.c230
-rw-r--r--drivers/iommu/Kconfig11
-rw-r--r--drivers/iommu/Makefile1
-rw-r--r--drivers/iommu/msm_iommu.c4
-rw-r--r--drivers/iommu/msm_iommu_hw-8xxx.h6
-rw-r--r--drivers/iommu/qcom_iommu_v0.c1223
-rw-r--r--drivers/iommu/qcom_iommu_v0.h99
-rw-r--r--drivers/mfd/Kconfig14
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/pm8921-core.c36
-rw-r--r--drivers/mfd/qcom_rpm.c580
-rw-r--r--drivers/mfd/ssbi.c7
-rw-r--r--drivers/mmc/core/core.c12
-rw-r--r--drivers/mmc/host/mmci.c17
-rw-r--r--drivers/pci/host/Makefile1
-rw-r--r--drivers/pci/host/pci-qcom.c968
-rw-r--r--drivers/pinctrl/qcom/Kconfig23
-rw-r--r--drivers/pinctrl/qcom/Makefile3
-rw-r--r--drivers/pinctrl/qcom/pinctrl-spmi-pmic-gpio.c938
-rw-r--r--drivers/pinctrl/qcom/pinctrl-spmi-pmic-mpp.c952
-rw-r--r--drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c875
-rw-r--r--drivers/soc/qcom/Kconfig10
-rw-r--r--drivers/soc/qcom/Makefile3
-rw-r--r--drivers/soc/qcom/msm-pm.c106
-rw-r--r--drivers/soc/qcom/scm-boot.c (renamed from arch/arm/mach-qcom/scm-boot.c)4
-rw-r--r--drivers/soc/qcom/scm.c (renamed from arch/arm/mach-qcom/scm.c)162
-rw-r--r--drivers/soc/qcom/spm-devices.c198
-rw-r--r--drivers/soc/qcom/spm-drv.h69
-rw-r--r--drivers/soc/qcom/spm.c192
-rw-r--r--fixup.S84
-rw-r--r--fixup.binbin0 -> 84 bytes
-rw-r--r--fixup.txt17
-rw-r--r--include/dt-bindings/clock/qcom,gcc-msm8960.h2
-rw-r--r--include/dt-bindings/mfd/qcom-rpm.h154
-rw-r--r--include/dt-bindings/pinctrl/qcom,pmic-gpio.h142
-rw-r--r--include/dt-bindings/pinctrl/qcom,pmic-mpp.h44
-rw-r--r--include/linux/clk-private.h2
-rw-r--r--include/linux/clk-provider.h32
-rw-r--r--include/linux/mfd/pm8921-core.h31
-rw-r--r--include/linux/mfd/qcom_rpm.h10
-rw-r--r--include/soc/qcom/pm.h31
-rw-r--r--include/soc/qcom/scm-boot.h (renamed from arch/arm/mach-qcom/scm-boot.h)2
-rw-r--r--include/soc/qcom/scm.h (renamed from arch/arm/mach-qcom/scm.h)8
-rw-r--r--include/soc/qcom/spm.h38
90 files changed, 10345 insertions, 486 deletions
diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,idle-state.txt b/Documentation/devicetree/bindings/arm/msm/qcom,idle-state.txt
new file mode 100644
index 000000000000..47095b9b184c
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,idle-state.txt
@@ -0,0 +1,72 @@
+QCOM Idle States for cpuidle driver
+
+ARM provides idle-state node to define the cpuidle states, as defined in [1].
+cpuidle-qcom is the cpuidle driver for Qualcomm SoCs and uses these idle
+states. Idle states have different enter/exit latency and residency values.
+The idle states supported by the QCOM SoC are defined as -
+
+ * WFI
+ * Retention
+ * Standalone Power Collapse (Standalone PC or SPC)
+ * Power Collapse (PC)
+
+WFI: WFI does a little more in addition to architectural clock gating. ARM
+processors when execute the wfi instruction will gate their internal clocks.
+QCOM cpus use this instruction as a trigger for the SPM state machine. Usually
+with a cpu entering WFI, the SPM is configured to do clock-gating as well. The
+SPM state machine waits for the interrrupt to trigger the core back in to
+active. When all CPUs in the SoC, clock gate using the ARM wfi instruction, the
+second level cache usually can also clock gate sensing no cpu activity. When a
+cpu is ready to run, it needs the cache to be active before starting execution.
+Allowing the SPM to execute the clock gating statemachine and waiting for
+interrupt on behalf of the processor has a benefit of guaranteeing that the
+system state is conducive for the core to resume execution.
+
+Retention: Retention is a low power state where the core is clockgated and the
+memory and the registers associated with the core are retained. The voltage
+may be reduced to the minimum value needed to keep the processor registers
+active. Retention is triggered when the core executes wfi instruction. The SPM
+should be configured to execute the retention sequence and would wait for
+interrupt, before restoring the cpu to execution state. Retention may have a
+slightly higher latency than WFI.
+
+Standalone PC: A cpu can power down and warmboot if there is a sufficient time
+between now and the next know wake up. SPC mode is used to indicate a core
+entering a power down state without consulting any other cpu or the system
+resources. This helps save power only on that core. Like WFI and Retention, the
+core executes wfi and the SPM programmed to do SPC would use the cpu control
+logic to power down the core's supply and restore it back when woken up by an
+interrupt. Applying power and reseting the core causes the core to warmboot
+back into secure mode which trampolines the control back to the kernel. To
+enter a power down state the kernel needs to call into the secure layer which
+would then execute the ARM wfi instruction. Failing to do so, would result in a
+crash enforced by the warm boot code in the secure layer. On a SoC with
+write-back L1 cache, the cache would need to be flushed.
+
+Power Collapse: This state is similiar to the SPC mode, but distinguishes
+itself in the fact that the cpu acknowledges and permits the SoC to enter
+deeper sleep modes. In a hierarchical power domain SoC, this means L2 and other
+caches can be flushed, system bus, clocks - lowered, and SoC main XO turned off
+and voltages reduced, provided all cpus enter this state. In other words, it is
+a coupled idle state. Since the span of low power modes possible at this state
+is vast, the exit latency and the residency of this low power mode would be
+considered high even though at a cpu level, this essentially is cpu power down.
+The SPM in this state also may handshake with the Resource power manager
+processor in the SoC to indicate a complete subsystem shut down.
+
+The idle-state for QCOM SoCs are distinguished by the compatible property of
+the node. They indicate to the cpuidle driver the entry point to use for
+cpuidle. The devicetree representation of the idle state should be -
+
+Required properties:
+
+- compatible: Must be "arm,idle-state"
+ and one of -
+ "qcom,idle-state-wfi",
+ "qcom,idle-state-ret",
+ "qcom,idle-state-spc",
+ "qcom,idle-state-pc",
+
+Other required and optional properties are specified in [1].
+
+[1]. Documentation/devicetree/bindings/arm/idle-states.txt
diff --git a/Documentation/devicetree/bindings/arm/msm/spm.txt b/Documentation/devicetree/bindings/arm/msm/spm.txt
new file mode 100644
index 000000000000..30623b0d7e51
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/spm.txt
@@ -0,0 +1,47 @@
+* Subsystem Power Manager (SAW2)
+
+S4 generation of MSMs have SPM hardware blocks to control the Application
+Processor Sub-System power. These SPM blocks run individual state machine
+to determine what the core (L2 or Krait/Scorpion) would do when the WFI
+instruction is executed by the core.
+
+The devicetree representation of the SPM block should be:
+
+Required properties
+
+- compatible: Could be one of -
+ "qcom,spm-v2.1"
+- reg: The physical address and the size of the SPM's memory mapped registers
+- qcom,cpu: phandle for the CPU that the SPM block is attached to.
+ This field is required on only for SPMs that control the CPU.
+- qcom,saw2-clk-div: SAW2 configuration register to program the SPM runtime
+ clocks.
+- qcom,saw2-delays: The SPM delay values that SPM sequences would refer to.
+- qcom,saw2-enable: The SPM control register to enable/disable the sleep state
+ machine.
+
+Optional properties
+
+- qcom,saw2-spm-cmd-wfi: The WFI command sequence
+- qcom,saw2-spm-cmd-ret: The Retention command sequence
+- qcom,saw2-spm-cmd-spc: The Standalone PC command sequence
+- qcom,saw2-spm-cmd-pc: The Power Collapse command sequence. This sequence may
+ turn off other SoC components.
+- qcom,saw2-spm-cmd-gdhs: GDHS (Globally Distributed Head Switch) command
+ sequence. This sequence will retain the memory but turn off the logic.
+-
+Example:
+ spm@f9089000 {
+ compatible = "qcom,spm-v2.1";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0xf9089000 0x1000>;
+ qcom,cpu = <&CPU0>;
+ qcom,saw2-clk-div = <0x1>;
+ qcom,saw2-delays = <0x20000400>;
+ qcom,saw2-enable = <0x1>;
+ qcom,saw2-spm-cmd-wfi = [03 0b 0f];
+ qcom,saw2-spm-cmd-spc = [00 20 50 80 60 70 10 92
+ a0 b0 03 68 70 3b 92 a0 b0
+ 82 2b 50 10 30 02 22 30 0f];
+ };
diff --git a/Documentation/devicetree/bindings/dma/qcom_bam_dma.txt b/Documentation/devicetree/bindings/dma/qcom_bam_dma.txt
index d75a9d767022..83c8e57ea2e7 100644
--- a/Documentation/devicetree/bindings/dma/qcom_bam_dma.txt
+++ b/Documentation/devicetree/bindings/dma/qcom_bam_dma.txt
@@ -1,7 +1,9 @@
QCOM BAM DMA controller
Required properties:
-- compatible: must contain "qcom,bam-v1.4.0" for MSM8974
+- compatible: must be one of the following:
+ * "qcom,bam-v1.4.0" for MSM8974, APQ8074 and APQ8084
+ * "qcom,bam-v1.3.0" for APQ8064 and MSM8960
- reg: Address range for DMA registers
- interrupts: Should contain the one interrupt shared by all channels
- #dma-cells: must be <1>, the cell in the dmas property of the client device
diff --git a/Documentation/devicetree/bindings/mfd/qcom-rpm.txt b/Documentation/devicetree/bindings/mfd/qcom-rpm.txt
new file mode 100644
index 000000000000..4264021c498b
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/qcom-rpm.txt
@@ -0,0 +1,246 @@
+Qualcomm Resource Power Manager (RPM)
+
+This driver is used to interface with the Resource Power Manager (RPM) found in
+various Qualcomm platforms. The RPM allows each component in the system to vote
+for state of the system resources, such as clocks, regulators and bus
+frequencies.
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: must be one of:
+ "qcom,rpm-apq8064"
+ "qcom,rpm-msm8660"
+ "qcom,rpm-msm8960"
+
+- reg:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: base address and size of the RPM's message ram
+
+- interrupts:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: three entries specifying the RPM's:
+ 1. acknowledgement interrupt
+ 2. error interrupt
+ 3. wakeup interrupt
+
+- interrupt-names:
+ Usage: required
+ Value type: <string-array>
+ Definition: must be the three strings "ack", "err" and "wakeup", in order
+
+- #address-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: must be 1
+
+- #size-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: must be 0
+
+- qcom,ipc:
+ Usage: required
+ Value type: <prop-encoded-array>
+
+ Definition: three entries specifying the outgoing ipc bit used for
+ signaling the RPM:
+ - phandle to a syscon node representing the apcs registers
+ - u32 representing offset to the register within the syscon
+ - u32 representing the ipc bit within the register
+
+
+= SUBDEVICES
+
+The RPM exposes resources to its subnodes. The below bindings specify the set
+of valid subnodes that can operate on these resources.
+
+== Switch-mode Power Supply regulator
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: must be one of:
+ "qcom,rpm-pm8058-smps"
+ "qcom,rpm-pm8901-ftsmps"
+ "qcom,rpm-pm8921-smps"
+ "qcom,rpm-pm8921-ftsmps"
+
+- reg:
+ Usage: required
+ Value type: <u32>
+ Definition: resource as defined in <dt-bindings/mfd/qcom-rpm.h>
+ must be one of:
+ QCOM_RPM_PM8058_SMPS0 - QCOM_RPM_PM8058_SMPS4,
+ QCOM_RPM_PM8821_SMPS1 - QCOM_RPM_PM8821_SMPS2,
+ QCOM_RPM_PM8901_SMPS0 - QCOM_RPM_PM8901_SMPS4,
+ QCOM_RPM_PM8921_SMPS1 - QCOM_RPM_PM8921_SMPS8
+
+- bias-pull-down:
+ Usage: optional
+ Value type: <empty>
+ Definition: enable pull down of the regulator when inactive
+
+- qcom,switch-mode-frequency:
+ Usage: required
+ Value type: <u32>
+ Definition: Frequency (Hz) of the switch-mode power supply;
+ must be one of:
+ 19200000, 9600000, 6400000, 4800000, 3840000, 3200000,
+ 2740000, 2400000, 2130000, 1920000, 1750000, 1600000,
+ 1480000, 1370000, 1280000, 1200000
+
+- qcom,force-mode:
+ Usage: optional (default if no other qcom,force-mode is specified)
+ Value type: <u32>
+ Defintion: indicates that the regulator should be forced to a
+ particular mode, valid values are:
+ QCOM_RPM_FORCE_MODE_NONE - do not force any mode
+ QCOM_RPM_FORCE_MODE_LPM - force into low power mode
+ QCOM_RPM_FORCE_MODE_HPM - force into high power mode
+ QCOM_RPM_FORCE_MODE_AUTO - allow regulator to automatically
+ select its own mode based on
+ realtime current draw, only for:
+ qcom,rpm-pm8921-smps,
+ qcom,rpm-pm8921-ftsmps
+
+- qcom,power-mode-hysteretic:
+ Usage: optional
+ Value type: <empty>
+ Definition: select that the power supply should operate in hysteretic
+ mode, instead of the default pwm mode
+
+Standard regulator bindings are used inside switch mode power supply subnodes.
+Check Documentation/devicetree/bindings/regulator/regulator.txt for more
+details.
+
+== Low-dropout regulator
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: must be one of:
+ "qcom,rpm-pm8058-pldo"
+ "qcom,rpm-pm8058-nldo"
+ "qcom,rpm-pm8901-pldo"
+ "qcom,rpm-pm8901-nldo"
+ "qcom,rpm-pm8921-pldo"
+ "qcom,rpm-pm8921-nldo"
+ "qcom,rpm-pm8921-nldo1200"
+
+- reg:
+ Usage: required
+ Value type: <u32>
+ Definition: resource as defined in <dt-bindings/mfd/qcom-rpm.h>
+ must be one of:
+ QCOM_RPM_PM8058_LDO0 - QCOM_RPM_PM8058_LDO25,
+ QCOM_RPM_PM8821_LDO1,
+ QCOM_RPM_PM8901_LDO0 - QCOM_RPM_PM8901_LDO6,
+ QCOM_RPM_PM8921_LDO1 - QCOM_RPM_PM8921_LDO29
+
+- bias-pull-down:
+ Usage: optional
+ Value type: <empty>
+ Definition: enable pull down of the regulator when inactive
+
+- qcom,force-mode:
+ Usage: optional
+ Value type: <u32>
+ Defintion: indicates that the regulator should not be forced to any
+ particular mode, valid values are:
+ QCOM_RPM_FORCE_MODE_NONE - do not force any mode
+ QCOM_RPM_FORCE_MODE_LPM - force into low power mode
+ QCOM_RPM_FORCE_MODE_HPM - force into high power mode
+ QCOM_RPM_FORCE_MODE_BYPASS - set regulator to use bypass
+ mode, i.e. to act as a switch
+ and not regulate, only for:
+ qcom,rpm-pm8921-pldo,
+ qcom,rpm-pm8921-nldo,
+ qcom,rpm-pm8921-nldo1200
+
+Standard regulator bindings are used inside switch low-dropout regulator
+subnodes. Check Documentation/devicetree/bindings/regulator/regulator.txt for
+more details.
+
+== Negative Charge Pump
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: must be one of:
+ "qcom,rpm-pm8058-ncp"
+ "qcom,rpm-pm8921-ncp"
+
+- reg:
+ Usage: required
+ Value type: <u32>
+ Definition: resource as defined in <dt-bindings/mfd/qcom-rpm.h>
+ must be one of:
+ QCOM_RPM_PM8058_NCP,
+ QCOM_RPM_PM8921_NCP
+
+- qcom,switch-mode-frequency:
+ Usage: required
+ Value type: <u32>
+ Definition: Frequency (Hz) of the swith mode power supply;
+ must be one of:
+ 19200000, 9600000, 6400000, 4800000, 3840000, 3200000,
+ 2740000, 2400000, 2130000, 1920000, 1750000, 1600000,
+ 1480000, 1370000, 1280000, 1200000
+
+Standard regulator bindings are used inside negative charge pump regulator
+subnodes. Check Documentation/devicetree/bindings/regulator/regulator.txt for
+more details.
+
+== Switch
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: must be one of:
+ "qcom,rpm-pm8058-switch"
+ "qcom,rpm-pm8901-switch"
+ "qcom,rpm-pm8921-switch"
+
+- reg:
+ Usage: required
+ Value type: <u32>
+ Definition: resource as defined in <dt-bindings/mfd/qcom/qcom-rpm.h>
+ must be one of:
+ QCOM_RPM_PM8058_LVS0 - QCOM_RPM_PM8058_LVS1,
+ QCOM_RPM_PM8901_LVS0 - QCOM_RPM_PM8901_LVS3,
+ QCOM_RPM_PM8901_MVS,
+ QCOM_RPM_PM8921_LVS1 - QCOM_RPM_PM8921_LVS7,
+ QCOM_RPM_PM8921_MVS
+
+= EXAMPLE
+
+ #include <dt-bindings/mfd/qcom-rpm.h>
+
+ rpm@108000 {
+ compatible = "qcom,rpm-msm8960";
+ reg = <0x108000 0x1000>;
+ qcom,ipc = <&apcs 0x8 2>;
+
+ interrupts = <0 19 0>, <0 21 0>, <0 22 0>;
+ interrupt-names = "ack", "err", "wakeup";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pm8921_smps1: pm8921-smps1 {
+ compatible = "qcom,rpm-pm8921-smps";
+ reg = <QCOM_RPM_PM8921_SMPS1>;
+
+ regulator-min-microvolt = <1225000>;
+ regulator-max-microvolt = <1225000>;
+ regulator-always-on;
+
+ bias-pull-down;
+
+ qcom,switch-mode-frequency = <3200000>;
+ };
+ };
+
diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.txt b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.txt
new file mode 100644
index 000000000000..e146496babaf
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.txt
@@ -0,0 +1,214 @@
+Qualcomm PMIC GPIO block
+
+This binding describes the GPIO block(s) found in the 8xxx series of
+PMIC's from Qualcomm.
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: must be one of:
+ "qcom,pm8018-gpio"
+ "qcom,pm8038-gpio"
+ "qcom,pm8058-gpio"
+ "qcom,pm8917-gpio"
+ "qcom,pm8921-gpio"
+ "qcom,spmi-pmic-gpio"
+
+- reg:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Register base of the GPIO block and length.
+
+- interrupts:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Must contain an array of encoded interrupt specifiers for
+ each available GPIO
+
+- gpio-controller:
+ Usage: required
+ Value type: <none>
+ Definition: Mark the device node as a GPIO controller
+
+- #gpio-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: Must be 2;
+ the first cell will be used to define gpio number and the
+ second denotes the flags for this gpio
+
+Please refer to ../gpio/gpio.txt and ../interrupt-controller/interrupts.txt for
+a general description of GPIO and interrupt bindings.
+
+Please refer to pinctrl-bindings.txt in this directory for details of the
+common pinctrl bindings used by client devices, including the meaning of the
+phrase "pin configuration node".
+
+The pin configuration nodes act as a container for an arbitrary number of
+subnodes. Each of these subnodes represents some desired configuration for a
+pin or a list of pins. This configuration can include the
+mux function to select on those pin(s), and various pin configuration
+parameters, as listed below.
+
+
+SUBNODES:
+
+The name of each subnode is not important; all subnodes should be enumerated
+and processed purely based on their content.
+
+Each subnode only affects those parameters that are explicitly listed. In
+other words, a subnode that lists a mux function but no pin configuration
+parameters implies no information about any pin configuration parameters.
+Similarly, a pin subnode that describes a pullup parameter implies no
+information about e.g. the mux function.
+
+The following generic properties as defined in pinctrl-bindings.txt are valid
+to specify in a pin configuration subnode:
+
+- pins:
+ Usage: required
+ Value type: <string-array>
+ Definition: List of gpio pins affected by the properties specified in
+ this subnode. Valid pins are:
+ gpio1-gpio6 for pm8018
+ gpio1-gpio12 for pm8038
+ gpio1-gpio40 for pm8058
+ gpio1-gpio38 for pm8917
+ gpio1-gpio44 for pm8921
+ gpio1-gpio36 for pm8941
+ gpio1-gpio22 for pma8084
+
+- function:
+ Usage: required
+ Value type: <string>
+ Definition: Specify the alternative function to be configured for the
+ specified pins. Valid values are:
+ "normal",
+ "paired",
+ "func1",
+ "func2",
+ "dtest1",
+ "dtest2",
+ "dtest3",
+ "dtest4"
+
+- bias-disable:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins should be configured as no pull.
+
+- bias-pull-down:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins should be configured as pull down.
+
+- bias-pull-up:
+ Usage: optional
+ Value type: <empty>
+ Definition: The specified pins should be configured as pull up.
+
+- qcom,pull-up-strength:
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the strength to use for pull up, if selected.
+ Valid values are; as defined in
+ <dt-bindings/pinctrl/qcom,pmic-gpio.h>:
+ 1: 30uA (PMIC_GPIO_PULL_UP_30)
+ 2: 1.5uA (PMIC_GPIO_PULL_UP_1P5)
+ 3: 31.5uA (PMIC_GPIO_PULL_UP_31P5)
+ 4: 1.5uA + 30uA boost (PMIC_GPIO_PULL_UP_1P5_30)
+ If this property is ommited 30uA strength will be used if
+ pull up is selected
+
+- bias-high-impedance:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins will put in high-Z mode and disabled.
+
+- input-enable:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are put in input mode.
+
+- output-high:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in output mode, driven
+ high.
+
+- output-low:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in output mode, driven
+ low.
+
+- power-source:
+ Usage: optional
+ Value type: <u32>
+ Definition: Selects the power source for the specified pins. Valid
+ power sources are defined per chip in
+ <dt-bindings/pinctrl/qcom,pmic-gpio.h>
+
+- qcom,drive-strength:
+ Usage: optional
+ Value type: <u32>
+ Definition: Selects the drive strength for the specified pins. Value
+ drive strengths are:
+ 0: no (PMIC_GPIO_STRENGTH_NO)
+ 1: high (PMIC_GPIO_STRENGTH_HIGH) 0.9mA @ 1.8V - 1.9mA @ 2.6V
+ 2: medium (PMIC_GPIO_STRENGTH_MED) 0.6mA @ 1.8V - 1.25mA @ 2.6V
+ 3: low (PMIC_GPIO_STRENGTH_LOW) 0.15mA @ 1.8V - 0.3mA @ 2.6V
+ as defined in <dt-bindings/pinctrl/qcom,pmic-gpio.h>
+
+- drive-push-pull:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in push-pull mode.
+
+- drive-open-drain:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in open-drain mode.
+
+- drive-open-source:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in open-source mode.
+
+Example:
+
+ pm8921_gpio: gpio@150 {
+ compatible = "qcom,pm8921-gpio";
+ reg = <0x150 0x160>;
+ interrupts = <192 1>, <193 1>, <194 1>,
+ <195 1>, <196 1>, <197 1>,
+ <198 1>, <199 1>, <200 1>,
+ <201 1>, <202 1>, <203 1>,
+ <204 1>, <205 1>, <206 1>,
+ <207 1>, <208 1>, <209 1>,
+ <210 1>, <211 1>, <212 1>,
+ <213 1>, <214 1>, <215 1>,
+ <216 1>, <217 1>, <218 1>,
+ <219 1>, <220 1>, <221 1>,
+ <222 1>, <223 1>, <224 1>,
+ <225 1>, <226 1>, <227 1>,
+ <228 1>, <229 1>, <230 1>,
+ <231 1>, <232 1>, <233 1>,
+ <234 1>, <235 1>;
+
+ gpio-controller;
+ #gpio-cells = <2>;
+
+ pm8921_gpio_keys: gpio-keys {
+ volume-keys {
+ pins = "gpio20", "gpio21";
+ function = "normal";
+
+ input-enable;
+ bias-pull-up;
+ drive-push-pull;
+ qcom,drive-strength = <PMIC_GPIO_STRENGTH_NO>;
+ power-source = <PM8921_GPIO_S4>;
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt
new file mode 100644
index 000000000000..9d4779f09e23
--- /dev/null
+++ b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt
@@ -0,0 +1,159 @@
+Qualcomm PMIC Multi-Purpose Pin (MPP) block
+
+This binding describes the MPP block(s) found in the 8xxx series
+of PMIC's from Qualcomm.
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: Should contain "qcom,spmi-pmic-mpp"
+
+- reg:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Register base of the MPP block and length.
+
+- interrupts:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Must contain an array of encoded interrupt specifiers for
+ each available gpio
+
+- gpio-controller:
+ Usage: required
+ Value type: <none>
+ Definition: Mark the device node as a GPIO controller
+
+- #gpio-cells:
+ Usage: required
+ Value type: <u32>
+ Definition: Must be 2;
+ the first cell will be used to define gpio number and the
+ second denotes the flags for this gpio
+
+Please refer to ../gpio/gpio.txt and ../interrupt-controller/interrupts.txt for
+a general description of GPIO and interrupt bindings.
+
+Please refer to pinctrl-bindings.txt in this directory for details of the
+common pinctrl bindings used by client devices, including the meaning of the
+phrase "pin configuration node".
+
+The pin configuration nodes act as a container for an arbitrary number of
+subnodes. Each of these subnodes represents some desired configuration for a
+pin or a list of pins. This configuration can include the
+mux function to select on those pin(s), and various pin configuration
+parameters, as listed below.
+
+SUBNODES:
+
+The name of each subnode is not important; all subnodes should be enumerated
+and processed purely based on their content.
+
+Each subnode only affects those parameters that are explicitly listed. In
+other words, a subnode that lists a mux function but no pin configuration
+parameters implies no information about any pin configuration parameters.
+Similarly, a pin subnode that describes a pullup parameter implies no
+information about e.g. the mux function.
+
+The following generic properties as defined in pinctrl-bindings.txt are valid
+to specify in a pin configuration subnode:
+
+- pins:
+ Usage: required
+ Value type: <string-array>
+ Definition: List of gpio pins affected by the properties specified in
+ this subnode. Valid pins are:
+ mpp1-mpp4 for pm8841
+ mpp1-mpp8 for pm8941
+ mpp1-mpp4 for pma8084
+
+- function:
+ Usage: required
+ Value type: <string>
+ Definition: Specify the alternative function to be configured for the
+ specified pins. Valid values are:
+ "normal",
+ "paired",
+ "dtest1",
+ "dtest2",
+ "dtest3",
+ "dtest4"
+
+- bias-disable:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins should be configured as no pull.
+
+- bias-pull-up:
+ Usage: optional
+ Value type: <u32>
+ Definition: The specified pins should be configured as pull up.
+ Valid values are 600, 10000 and 30000 in bidirectional mode only,
+ i.e. when operating in qcom,analog-mode and input and outputs are
+ enabled. The hardware ignores the configuration when operating in
+ other modes.
+
+- bias-high-impedance:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins will put in high-Z mode and disabled.
+
+- input-enable:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are put in input mode, i.e. thier input
+ buffer is enabled
+
+- output-high:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in output mode, driven
+ high.
+
+- output-low:
+ Usage: optional
+ Value type: <none>
+ Definition: The specified pins are configured in output mode, driven
+ low.
+
+- power-source:
+ Usage: optional
+ Value type: <u32>
+ Definition: Selects the power source for the specified pins. Valid power
+ sources are defined in <dt-bindings/pinctrl/qcom,pmic-mpp.h>
+
+- qcom,analog-mode:
+ Usage: optional
+ Value type: <none>
+ Definition: Selects Analog mode of operation: combined with input-enable
+ and/or output-high, output-log MPP could operate as Bidirectional
+ Logic, Analog Input, Analog Output.
+
+- qcom,amux-route:
+ Usage: optional
+ Value type: <u32>
+ Definition: Selects the source for analog input. Valid values are
+ defined in <dt-bindings/pinctrl/qcom,pmic-mpp.h>
+ PMIC_MPP_AMUX_ROUTE_CH5, PMIC_MPP_AMUX_ROUTE_CH6...
+
+Example:
+
+ mpps@a000 {
+ compatible = "qcom,pm8841-mpp";
+ reg = <0xa000>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ interrupts = <4 0xa0 0 0>, <4 0xa1 0 0>, <4 0xa2 0 0>, <4 0xa3 0 0>;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&pm8841_default>;
+
+ pm8841_default: default {
+ gpio {
+ pins = "mpp1", "mpp2", "mpp3", "mpp4";
+ function = "normal";
+ input-enable;
+ power-source = <PM8841_MPP_S3>;
+ };
+ };
+ };
diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug
index 03dc4c1a8736..cb12eae5e908 100644
--- a/arch/arm/Kconfig.debug
+++ b/arch/arm/Kconfig.debug
@@ -372,6 +372,13 @@ choice
Say Y here if you want kernel low-lever debugging support
on Amlogic Meson6 based platforms on the UARTAO.
+ config DEBUG_KS8695_UART
+ bool "KS8695 Debug UART"
+ depends on ARCH_KS8695
+ help
+ Say Y here if you want kernel low-level debugging support
+ on KS8695.
+
config DEBUG_MMP_UART2
bool "Kernel low-level debugging message via MMP UART2"
depends on ARCH_MMP
@@ -464,6 +471,14 @@ choice
Say Y here if you want kernel low-level debugging support
on Vybrid based platforms.
+ config DEBUG_NETX_UART
+ bool "Kernel low-level debugging messages via NetX UART"
+ depends on ARCH_NETX
+ select DEBUG_UART_NETX
+ help
+ Say Y here if you want kernel low-level debugging support
+ on Hilscher NetX based platforms.
+
config DEBUG_NOMADIK_UART
bool "Kernel low-level debugging messages via NOMADIK UART"
depends on ARCH_NOMADIK
@@ -488,6 +503,30 @@ choice
Say Y here if you want kernel low-level debugging support
on TI-NSPIRE CX models.
+ config DEBUG_OMAP1UART1
+ bool "Kernel low-level debugging via OMAP1 UART1"
+ depends on ARCH_OMAP1
+ select DEBUG_UART_8250
+ help
+ Say Y here if you want kernel low-level debugging support
+ on OMAP1 based platforms (expect OMAP730) on the UART1.
+
+ config DEBUG_OMAP1UART2
+ bool "Kernel low-level debugging via OMAP1 UART2"
+ depends on ARCH_OMAP1
+ select DEBUG_UART_8250
+ help
+ Say Y here if you want kernel low-level debugging support
+ on OMAP1 based platforms (expect OMAP730) on the UART2.
+
+ config DEBUG_OMAP1UART3
+ bool "Kernel low-level debugging via OMAP1 UART3"
+ depends on ARCH_OMAP1
+ select DEBUG_UART_8250
+ help
+ Say Y here if you want kernel low-level debugging support
+ on OMAP1 based platforms (expect OMAP730) on the UART3.
+
config DEBUG_OMAP2UART1
bool "OMAP2/3/4 UART1 (omap2/3 sdp boards and some omap3 boards)"
depends on ARCH_OMAP2PLUS
@@ -530,6 +569,30 @@ choice
depends on ARCH_OMAP2PLUS
select DEBUG_OMAP2PLUS_UART
+ config DEBUG_OMAP7XXUART1
+ bool "Kernel low-level debugging via OMAP730 UART1"
+ depends on ARCH_OMAP730
+ select DEBUG_UART_8250
+ help
+ Say Y here if you want kernel low-level debugging support
+ on OMAP730 based platforms on the UART1.
+
+ config DEBUG_OMAP7XXUART2
+ bool "Kernel low-level debugging via OMAP730 UART2"
+ depends on ARCH_OMAP730
+ select DEBUG_UART_8250
+ help
+ Say Y here if you want kernel low-level debugging support
+ on OMAP730 based platforms on the UART2.
+
+ config DEBUG_OMAP7XXUART3
+ bool "Kernel low-level debugging via OMAP730 UART3"
+ depends on ARCH_OMAP730
+ select DEBUG_UART_8250
+ help
+ Say Y here if you want kernel low-level debugging support
+ on OMAP730 based platforms on the UART3.
+
config DEBUG_TI81XXUART1
bool "Kernel low-level debugging messages via TI81XX UART1 (ti8148evm)"
depends on ARCH_OMAP2PLUS
@@ -723,6 +786,30 @@ choice
their output to UART 2. The port must have been initialised
by the boot-loader before use.
+ config DEBUG_SA1100_UART1
+ depends on ARCH_SA1100
+ select DEBUG_SA1100_UART
+ bool "Kernel low-level debugging messages via SA1100 Ser1"
+ help
+ Say Y here if you want kernel low-level debugging support
+ on SA1100 based platforms.
+
+ config DEBUG_SA1100_UART2
+ depends on ARCH_SA1100
+ select DEBUG_SA1100_UART
+ bool "Kernel low-level debugging messages via SA1100 Ser2"
+ help
+ Say Y here if you want kernel low-level debugging support
+ on SA1100 based platforms.
+
+ config DEBUG_SA1100_UART3
+ depends on ARCH_SA1100
+ select DEBUG_SA1100_UART
+ bool "Kernel low-level debugging messages via SA1100 Ser3"
+ help
+ Say Y here if you want kernel low-level debugging support
+ on SA1100 based platforms.
+
config DEBUG_SOCFPGA_UART
depends on ARCH_SOCFPGA
bool "Use SOCFPGA UART for low-level debug"
@@ -909,15 +996,6 @@ choice
This option selects UART0 on VIA/Wondermedia System-on-a-chip
devices, including VT8500, WM8505, WM8650 and WM8850.
- config DEBUG_LL_UART_NONE
- bool "No low-level debugging UART"
- depends on !ARCH_MULTIPLATFORM
- help
- Say Y here if your platform doesn't provide a UART option
- above. This relies on your platform choosing the right UART
- definition internally in order for low-level debugging to
- work.
-
config DEBUG_ICEDCC
bool "Kernel low-level debugging via EmbeddedICE DCC channel"
help
@@ -1035,6 +1113,10 @@ config DEBUG_TEGRA_UART
bool
depends on ARCH_TEGRA
+config DEBUG_SA1100_UART
+ bool
+ depends on ARCH_SA1100
+
config DEBUG_STI_UART
bool
depends on ARCH_STI
@@ -1059,10 +1141,13 @@ config DEBUG_LL_INCLUDE
DEBUG_IMX6Q_UART || \
DEBUG_IMX6SL_UART || \
DEBUG_IMX6SX_UART
+ default "debug/ks8695.S" if DEBUG_KS8695_UART
default "debug/msm.S" if DEBUG_MSM_UART || DEBUG_QCOM_UARTDM
+ default "debug/netx.S" if DEBUG_NETX_UART
default "debug/omap2plus.S" if DEBUG_OMAP2PLUS_UART
default "debug/s3c24xx.S" if DEBUG_S3C24XX_UART
default "debug/s5pv210.S" if DEBUG_S5PV210_UART
+ default "debug/sa1100.S" if DEBUG_SA1100_UART
default "debug/sirf.S" if DEBUG_SIRFPRIMA2_UART1 || DEBUG_SIRFMARCO_UART1
default "debug/sti.S" if DEBUG_STI_UART
default "debug/tegra.S" if DEBUG_TEGRA_UART
@@ -1076,14 +1161,9 @@ config DEBUG_LL_INCLUDE
# Compatibility options for PL01x
config DEBUG_UART_PL01X
- def_bool ARCH_EP93XX || \
- ARCH_INTEGRATOR || \
- ARCH_SPEAR3XX || \
- ARCH_SPEAR6XX || \
- ARCH_SPEAR13XX || \
- ARCH_VERSATILE
-
-# Compatibility options for 8250
+ bool
+
+#Compatibility options for 8250
config DEBUG_UART_8250
def_bool ARCH_DOVE || ARCH_EBSA110 || \
(FOOTBRIDGE && !DEBUG_DC21285_PORT) || \
@@ -1097,6 +1177,7 @@ config DEBUG_UART_BCM63XX
config DEBUG_UART_PHYS
hex "Physical base address of debug UART"
+ default 0x00100a00 if DEBUG_NETX_UART
default 0x01c20000 if DEBUG_DAVINCI_DMx_UART0
default 0x01c28000 if DEBUG_SUNXI_UART0
default 0x01c28400 if DEBUG_SUNXI_UART1
@@ -1135,6 +1216,9 @@ config DEBUG_UART_PHYS
default 0x78000000 if DEBUG_CNS3XXX
default 0x7c0003f8 if FOOTBRIDGE
default 0x78000000 if DEBUG_CNS3XXX
+ default 0x80010000 if DEBUG_SA1100_UART1
+ default 0x80030000 if DEBUG_SA1100_UART2
+ default 0x80050000 if DEBUG_SA1100_UART3
default 0x80070000 if DEBUG_IMX23_UART
default 0x80074000 if DEBUG_IMX28_UART
default 0x80230000 if DEBUG_PICOXCELL_UART
@@ -1164,6 +1248,9 @@ config DEBUG_UART_PHYS
default 0xff690000 if DEBUG_RK32_UART2
default 0xffc02000 if DEBUG_SOCFPGA_UART
default 0xffd82340 if ARCH_IOP13XX
+ default 0xfffb0000 if DEBUG_OMAP1UART1 || DEBUG_OMAP7XXUART1
+ default 0xfffb0800 if DEBUG_OMAP1UART2 || DEBUG_OMAP7XXUART2
+ default 0xfffb9800 if DEBUG_OMAP1UART3 || DEBUG_OMAP7XXUART3
default 0xfff36000 if DEBUG_HIGHBANK_UART
default 0xfffe8600 if DEBUG_UART_BCM63XX
default 0xfffff700 if ARCH_IOP33X
@@ -1171,10 +1258,12 @@ config DEBUG_UART_PHYS
DEBUG_LL_UART_EFM32 || \
DEBUG_UART_8250 || DEBUG_UART_PL01X || DEBUG_MESON_UARTAO || \
DEBUG_MSM_UART || DEBUG_QCOM_UARTDM || DEBUG_S3C24XX_UART || \
- DEBUG_UART_BCM63XX
+ DEBUG_UART_BCM63XX || DEBUG_NETX_UART || DEBUG_SA1100_UART || \
+ ARCH_EP93XX
config DEBUG_UART_VIRT
hex "Virtual base address of debug UART"
+ default 0xe0000a00 if DEBUG_NETX_UART
default 0xe0010fe0 if ARCH_RPC
default 0xe1000000 if DEBUG_MSM_UART
default 0xf0000be0 if ARCH_EBSA110
@@ -1199,6 +1288,9 @@ config DEBUG_UART_VIRT
default 0xf7fc9000 if DEBUG_BERLIN_UART
default 0xf8007000 if DEBUG_HIP04_UART
default 0xf8009000 if DEBUG_VEXPRESS_UART0_CA9
+ default 0xf8010000 if DEBUG_SA1100_UART1
+ default 0xf8030000 if DEBUG_SA1100_UART2
+ default 0xf8050000 if DEBUG_SA1100_UART3
default 0xf8090000 if DEBUG_VEXPRESS_UART0_RS1
default 0xfa71e000 if DEBUG_QCOM_UARTDM
default 0xfb002000 if DEBUG_CNS3XXX
@@ -1238,18 +1330,22 @@ config DEBUG_UART_VIRT
default 0xfef00000 if ARCH_IXP4XX && !CPU_BIG_ENDIAN
default 0xfef00003 if ARCH_IXP4XX && CPU_BIG_ENDIAN
default 0xfef36000 if DEBUG_HIGHBANK_UART
+ default 0xfefb0000 if DEBUG_OMAP1UART1 || DEBUG_OMAP7XXUART1
+ default 0xfefb0800 if DEBUG_OMAP1UART2 || DEBUG_OMAP7XXUART2
+ default 0xfefb9800 if DEBUG_OMAP1UART3 || DEBUG_OMAP7XXUART3
default 0xfefff700 if ARCH_IOP33X
default 0xff003000 if DEBUG_U300_UART
default DEBUG_UART_PHYS if !MMU
depends on DEBUG_LL_UART_8250 || DEBUG_LL_UART_PL01X || \
DEBUG_UART_8250 || DEBUG_UART_PL01X || DEBUG_MESON_UARTAO || \
DEBUG_MSM_UART || DEBUG_QCOM_UARTDM || DEBUG_S3C24XX_UART || \
- DEBUG_UART_BCM63XX
+ DEBUG_UART_BCM63XX || DEBUG_NETX_UART || DEBUG_SA1100_UART
config DEBUG_UART_8250_SHIFT
int "Register offset shift for the 8250 debug UART"
depends on DEBUG_LL_UART_8250 || DEBUG_UART_8250
- default 0 if FOOTBRIDGE || ARCH_IOP32X
+ default 0 if FOOTBRIDGE || ARCH_IOP32X || \
+ DEBUG_OMAP7XXUART1 || DEBUG_OMAP7XXUART2 || DEBUG_OMAP7XXUART3
default 2
config DEBUG_UART_8250_WORD
diff --git a/arch/arm/common/Kconfig b/arch/arm/common/Kconfig
index c3a4e9ceba34..9da52dc6260b 100644
--- a/arch/arm/common/Kconfig
+++ b/arch/arm/common/Kconfig
@@ -9,6 +9,9 @@ config DMABOUNCE
bool
select ZONE_DMA
+config KRAIT_L2_ACCESSORS
+ bool
+
config SHARP_LOCOMO
bool
diff --git a/arch/arm/common/Makefile b/arch/arm/common/Makefile
index 70b1eff477b3..6b2cddf6e8d0 100644
--- a/arch/arm/common/Makefile
+++ b/arch/arm/common/Makefile
@@ -7,6 +7,7 @@ obj-y += firmware.o
obj-$(CONFIG_ICST) += icst.o
obj-$(CONFIG_SA1111) += sa1111.o
obj-$(CONFIG_DMABOUNCE) += dmabounce.o
+obj-$(CONFIG_KRAIT_L2_ACCESSORS) += krait-l2-accessors.o
obj-$(CONFIG_SHARP_LOCOMO) += locomo.o
obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o
obj-$(CONFIG_SHARP_SCOOP) += scoop.o
diff --git a/arch/arm/common/krait-l2-accessors.c b/arch/arm/common/krait-l2-accessors.c
new file mode 100644
index 000000000000..5d514bbc88a6
--- /dev/null
+++ b/arch/arm/common/krait-l2-accessors.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011-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
+ * 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/spinlock.h>
+#include <linux/export.h>
+
+#include <asm/barrier.h>
+#include <asm/krait-l2-accessors.h>
+
+static DEFINE_RAW_SPINLOCK(krait_l2_lock);
+
+void krait_set_l2_indirect_reg(u32 addr, u32 val)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&krait_l2_lock, flags);
+ /*
+ * Select the L2 window by poking l2cpselr, then write to the window
+ * via l2cpdr.
+ */
+ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr));
+ isb();
+ asm volatile ("mcr p15, 3, %0, c15, c0, 7 @ l2cpdr" : : "r" (val));
+ isb();
+
+ raw_spin_unlock_irqrestore(&krait_l2_lock, flags);
+}
+EXPORT_SYMBOL(krait_set_l2_indirect_reg);
+
+u32 krait_get_l2_indirect_reg(u32 addr)
+{
+ u32 val;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&krait_l2_lock, flags);
+ /*
+ * Select the L2 window by poking l2cpselr, then read from the window
+ * via l2cpdr.
+ */
+ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr));
+ isb();
+ asm volatile ("mrc p15, 3, %0, c15, c0, 7 @ l2cpdr" : "=r" (val));
+
+ raw_spin_unlock_irqrestore(&krait_l2_lock, flags);
+
+ return val;
+}
+EXPORT_SYMBOL(krait_get_l2_indirect_reg);
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index 491b7d5523bf..1adeeaec71d0 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -88,7 +88,13 @@ CONFIG_KEXEC=y
CONFIG_CPU_FREQ=y
CONFIG_CPU_FREQ_STAT_DETAILS=y
CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPUFREQ_GENERIC=y
+CONFIG_ARM_QCOM_CPUFREQ=y
CONFIG_CPU_IDLE=y
+CONFIG_ARM_QCOM_CPUIDLE=y
CONFIG_NEON=y
CONFIG_ARM_ZYNQ_CPUIDLE=y
CONFIG_NET=y
@@ -113,8 +119,9 @@ CONFIG_CAN_BCM=y
CONFIG_CAN_DEV=y
CONFIG_CAN_XILINXCAN=y
CONFIG_CAN_MCP251X=y
-CONFIG_CFG80211=m
-CONFIG_MAC80211=m
+CONFIG_CFG80211=y
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=y
CONFIG_RFKILL=y
CONFIG_RFKILL_INPUT=y
CONFIG_RFKILL_GPIO=y
@@ -141,12 +148,14 @@ CONFIG_ATA=y
CONFIG_SATA_AHCI=y
CONFIG_SATA_AHCI_PLATFORM=y
CONFIG_AHCI_ST=y
+CONFIG_AHCI_QCOM=y
CONFIG_AHCI_SUNXI=y
CONFIG_AHCI_TEGRA=y
CONFIG_SATA_HIGHBANK=y
CONFIG_SATA_MV=y
CONFIG_NETDEVICES=y
CONFIG_SUN4I_EMAC=y
+CONFIG_ATL1C=y
CONFIG_MACB=y
CONFIG_NET_CALXEDA_XGMAC=y
CONFIG_IGB=y
@@ -165,6 +174,10 @@ CONFIG_USB_PEGASUS=y
CONFIG_USB_USBNET=y
CONFIG_USB_NET_SMSC75XX=y
CONFIG_USB_NET_SMSC95XX=y
+CONFIG_ATH_CARDS=y
+CONFIG_ATH_DEBUG=y
+CONFIG_ATH6KL=m
+CONFIG_ATH6KL_SDIO=m
CONFIG_BRCMFMAC=m
CONFIG_RT2X00=m
CONFIG_RT2800USB=m
@@ -233,7 +246,11 @@ CONFIG_SPI_TEGRA114=y
CONFIG_SPI_TEGRA20_SFLASH=y
CONFIG_SPI_TEGRA20_SLINK=y
CONFIG_SPI_XILINX=y
+CONFIG_SPMI=y
CONFIG_PINCTRL_AS3722=y
+CONFIG_PINCTRL_APQ8064=y
+CONFIG_PINCTRL_SPMI_PMIC=y
+CONFIG_PINCTRL_SSBI_PMIC=y
CONFIG_PINCTRL_PALMAS=y
CONFIG_GPIO_SYSFS=y
CONFIG_GPIO_GENERIC_PLATFORM=y
@@ -266,6 +283,8 @@ CONFIG_MFD_BCM590XX=y
CONFIG_MFD_CROS_EC=y
CONFIG_MFD_CROS_EC_SPI=y
CONFIG_MFD_MAX8907=y
+CONFIG_MFD_PM8921_CORE=y
+CONFIG_MFD_QCOM_RPM=y
CONFIG_MFD_SEC_CORE=y
CONFIG_MFD_STMPE=y
CONFIG_MFD_PALMAS=y
@@ -278,6 +297,7 @@ CONFIG_REGULATOR_BCM590XX=y
CONFIG_REGULATOR_GPIO=y
CONFIG_REGULATOR_MAX8907=y
CONFIG_REGULATOR_PALMAS=y
+CONFIG_REGULATOR_QCOM_RPM=y
CONFIG_REGULATOR_S2MPS11=y
CONFIG_REGULATOR_S5M8767=y
CONFIG_REGULATOR_TPS51632=y
@@ -317,6 +337,7 @@ CONFIG_USB=y
CONFIG_USB_XHCI_HCD=y
CONFIG_USB_XHCI_MVEBU=y
CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_MSM=y
CONFIG_USB_EHCI_TEGRA=y
CONFIG_USB_EHCI_HCD_PLATFORM=y
CONFIG_USB_ISP1760_HCD=y
@@ -331,6 +352,7 @@ CONFIG_SAMSUNG_USB2PHY=y
CONFIG_SAMSUNG_USB3PHY=y
CONFIG_USB_GPIO_VBUS=y
CONFIG_USB_ISP1301=y
+CONFIG_USB_MSM_OTG=y
CONFIG_USB_MXS_PHY=y
CONFIG_MMC=y
CONFIG_MMC_BLOCK_MINORS=16
@@ -346,6 +368,7 @@ CONFIG_MMC_SDHCI_SPEAR=y
CONFIG_MMC_SDHCI_S3C=y
CONFIG_MMC_SDHCI_S3C_DMA=y
CONFIG_MMC_SDHCI_BCM_KONA=y
+CONFIG_MMC_SDHCI_MSM=y
CONFIG_MMC_SDHCI_ST=y
CONFIG_MMC_OMAP=y
CONFIG_MMC_OMAP_HS=y
@@ -399,6 +422,7 @@ CONFIG_IMX_DMA=y
CONFIG_MXS_DMA=y
CONFIG_DMA_OMAP=y
CONFIG_XILINX_VDMA=y
+CONFIG_QCOM_BAM_DMA=y
CONFIG_STAGING=y
CONFIG_SENSORS_ISL29018=y
CONFIG_SENSORS_ISL29028=y
@@ -408,10 +432,17 @@ CONFIG_SERIO_NVEC_PS2=y
CONFIG_NVEC_POWER=y
CONFIG_NVEC_PAZ00=y
CONFIG_QCOM_GSBI=y
+CONFIG_QCOM_PM=y
CONFIG_COMMON_CLK_QCOM=y
+CONFIG_APQ_GCC_8084=y
+CONFIG_APQ_MMCC_8084=y
CONFIG_MSM_GCC_8660=y
CONFIG_MSM_MMCC_8960=y
CONFIG_MSM_MMCC_8974=y
+CONFIG_QCOM_HFPLL=y
+CONFIG_KPSS_XCC=y
+CONFIG_KRAITCC=y
+CONFIG_QCOM_IOMMU_V0=y
CONFIG_TEGRA_IOMMU_GART=y
CONFIG_TEGRA_IOMMU_SMMU=y
CONFIG_MEMORY=y
@@ -425,6 +456,7 @@ CONFIG_OMAP_USB2=y
CONFIG_TI_PIPE3=y
CONFIG_PHY_MIPHY365X=y
CONFIG_PHY_SUN4I_USB=y
+CONFIG_PHY_QCOM_APQ8064_SATA=y
CONFIG_EXT4_FS=y
CONFIG_VFAT_FS=y
CONFIG_TMPFS=y
diff --git a/arch/arm/configs/qcom_defconfig b/arch/arm/configs/qcom_defconfig
index 8c7da3319d82..6fae28336a22 100644
--- a/arch/arm/configs/qcom_defconfig
+++ b/arch/arm/configs/qcom_defconfig
@@ -21,6 +21,11 @@ CONFIG_ARCH_QCOM=y
CONFIG_ARCH_MSM8X60=y
CONFIG_ARCH_MSM8960=y
CONFIG_ARCH_MSM8974=y
+CONFIG_PCI=y
+CONFIG_PCI_MSI=y
+CONFIG_PCI_STUB=y
+CONFIG_PCI_HOST_GENERIC=y
+CONFIG_PCIEPORTBUS=y
CONFIG_SMP=y
CONFIG_PREEMPT=y
CONFIG_AEABI=y
@@ -64,8 +69,11 @@ CONFIG_CHR_DEV_SCH=y
CONFIG_SCSI_CONSTANTS=y
CONFIG_SCSI_LOGGING=y
CONFIG_SCSI_SCAN_ASYNC=y
+CONFIG_ATA=y
+CONFIG_SATA_AHCI_PLATFORM=y
CONFIG_NETDEVICES=y
CONFIG_DUMMY=y
+CONFIG_ATL1C=y
CONFIG_MDIO_BITBANG=y
CONFIG_MDIO_GPIO=y
CONFIG_SLIP=y
@@ -97,14 +105,21 @@ CONFIG_PINCTRL_APQ8084=y
CONFIG_PINCTRL_IPQ8064=y
CONFIG_PINCTRL_MSM8960=y
CONFIG_PINCTRL_MSM8X74=y
+CONFIG_PINCTRL_SPMI_PMIC=y
+CONFIG_PINCTRL_SSBI_PMIC=y
CONFIG_DEBUG_GPIO=y
CONFIG_GPIO_SYSFS=y
CONFIG_POWER_SUPPLY=y
CONFIG_POWER_RESET=y
CONFIG_POWER_RESET_MSM=y
CONFIG_THERMAL=y
+CONFIG_MFD_PM8921_CORE=y
+CONFIG_MFD_SPMI_PMIC=y
+CONFIG_MFD_QCOM_RPM=y
+CONFIG_MFD_SYSCON=y
CONFIG_REGULATOR=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_QCOM_RPM=y
CONFIG_MEDIA_SUPPORT=y
CONFIG_FB=y
CONFIG_SOUND=y
diff --git a/arch/arm/include/asm/krait-l2-accessors.h b/arch/arm/include/asm/krait-l2-accessors.h
new file mode 100644
index 000000000000..48fe5527bc01
--- /dev/null
+++ b/arch/arm/include/asm/krait-l2-accessors.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2011-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
+ * 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 __ASMARM_KRAIT_L2_ACCESSORS_H
+#define __ASMARM_KRAIT_L2_ACCESSORS_H
+
+extern void krait_set_l2_indirect_reg(u32 addr, u32 val);
+extern u32 krait_get_l2_indirect_reg(u32 addr);
+
+#endif
diff --git a/arch/arm/include/asm/vfp.h b/arch/arm/include/asm/vfp.h
index f4ab34fd4f72..ee5f3084243c 100644
--- a/arch/arm/include/asm/vfp.h
+++ b/arch/arm/include/asm/vfp.h
@@ -22,6 +22,7 @@
#define FPSID_NODOUBLE (1<<20)
#define FPSID_ARCH_BIT (16)
#define FPSID_ARCH_MASK (0xF << FPSID_ARCH_BIT)
+#define FPSID_CPUID_ARCH_MASK (0x7F << FPSID_ARCH_BIT)
#define FPSID_PART_BIT (8)
#define FPSID_PART_MASK (0xFF << FPSID_PART_BIT)
#define FPSID_VARIANT_BIT (4)
@@ -75,6 +76,10 @@
/* MVFR0 bits */
#define MVFR0_A_SIMD_BIT (0)
#define MVFR0_A_SIMD_MASK (0xf << MVFR0_A_SIMD_BIT)
+#define MVFR0_SP_BIT (4)
+#define MVFR0_SP_MASK (0xf << MVFR0_SP_BIT)
+#define MVFR0_DP_BIT (8)
+#define MVFR0_DP_MASK (0xf << MVFR0_DP_BIT)
/* Bit patterns for decoding the packaged operation descriptors */
#define VFPOPDESC_LENGTH_BIT (9)
diff --git a/arch/arm/mach-ks8695/include/mach/debug-macro.S b/arch/arm/include/debug/ks8695.S
index a79e48981202..961da1f32ab3 100644
--- a/arch/arm/mach-ks8695/include/mach/debug-macro.S
+++ b/arch/arm/include/debug/ks8695.S
@@ -1,5 +1,5 @@
/*
- * arch/arm/mach-ks8695/include/mach/debug-macro.S
+ * arch/arm/include/debug/ks8695.S
*
* Copyright (C) 2006 Ben Dooks <ben@simtec.co.uk>
* Copyright (C) 2006 Simtec Electronics
@@ -11,8 +11,12 @@
* published by the Free Software Foundation.
*/
-#include <mach/hardware.h>
-#include <mach/regs-uart.h>
+#define KS8695_UART_PA 0x03ffe000
+#define KS8695_UART_VA 0xf00fe000
+#define KS8695_URTH (0x04)
+#define KS8695_URLS (0x14)
+#define URLS_URTE (1 << 6)
+#define URLS_URTHRE (1 << 5)
.macro addruart, rp, rv, tmp
ldr \rp, =KS8695_UART_PA @ physical base address
diff --git a/arch/arm/mach-netx/include/mach/debug-macro.S b/arch/arm/include/debug/netx.S
index 247781e096e2..cf7522aba702 100644
--- a/arch/arm/mach-netx/include/mach/debug-macro.S
+++ b/arch/arm/include/debug/netx.S
@@ -1,5 +1,4 @@
-/* arch/arm/mach-netx/include/mach/debug-macro.S
- *
+/*
* Debugging macro include header
*
* Copyright (C) 1994-1999 Russell King
@@ -11,26 +10,27 @@
*
*/
-#include "hardware.h"
+#define UART_DATA 0
+#define UART_FLAG 0x18
+#define UART_FLAG_BUSY (1 << 3)
.macro addruart, rp, rv, tmp
- mov \rp, #0x00000a00
- orr \rv, \rp, #io_p2v(0x00100000) @ virtual
- orr \rp, \rp, #0x00100000 @ physical
+ ldr \rp, =CONFIG_DEBUG_UART_PHYS
+ ldr \rp, =CONFIG_DEBUG_UART_VIRT
.endm
.macro senduart,rd,rx
- str \rd, [\rx, #0]
+ str \rd, [\rx, #UART_DATA]
.endm
.macro busyuart,rd,rx
-1002: ldr \rd, [\rx, #0x18]
- tst \rd, #(1 << 3)
+1002: ldr \rd, [\rx, #UART_FLAG]
+ tst \rd, #UART_FLAG_BUSY
bne 1002b
.endm
.macro waituart,rd,rx
-1001: ldr \rd, [\rx, #0x18]
- tst \rd, #(1 << 3)
+1001: ldr \rd, [\rx, #UART_FLAG]
+ tst \rd, #UART_FLAG_BUSY
bne 1001b
.endm
diff --git a/arch/arm/include/debug/sa1100.S b/arch/arm/include/debug/sa1100.S
new file mode 100644
index 000000000000..6b5e1ce099d3
--- /dev/null
+++ b/arch/arm/include/debug/sa1100.S
@@ -0,0 +1,37 @@
+/*
+ * Debugging macro include header
+ *
+ * Copyright (C) 1994-1999 Russell King
+ * Moved from linux/arch/arm/kernel/debug.S by Ben Dooks
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+*/
+
+#define UTDR 0x14
+#define UTSR1 0x20
+#define UTSR1_TBY 0x00000001 /* Transmitter BusY (read) */
+#define UTSR1_TNF 0x00000004 /* Transmit FIFO Not Full (read) */
+
+ .macro addruart, rp, rv, tmp
+ ldr \rp, =CONFIG_DEBUG_UART_PHYS
+ ldr \rv, =CONFIG_DEBUG_UART_VIRT
+ .endm
+
+ .macro senduart,rd,rx
+ str \rd, [\rx, #UTDR]
+ .endm
+
+ .macro waituart,rd,rx
+1001: ldr \rd, [\rx, #UTSR1]
+ tst \rd, #UTSR1_TNF
+ beq 1001b
+ .endm
+
+ .macro busyuart,rd,rx
+1001: ldr \rd, [\rx, #UTSR1]
+ tst \rd, #UTSR1_TBY
+ bne 1001b
+ .endm
diff --git a/arch/arm/mach-omap1/include/mach/debug-macro.S b/arch/arm/mach-omap1/include/mach/debug-macro.S
deleted file mode 100644
index 5c1a26c9f490..000000000000
--- a/arch/arm/mach-omap1/include/mach/debug-macro.S
+++ /dev/null
@@ -1,101 +0,0 @@
-/* arch/arm/mach-omap1/include/mach/debug-macro.S
- *
- * Debugging macro include header
- *
- * Copyright (C) 1994-1999 Russell King
- * Moved from linux/arch/arm/kernel/debug.S by Ben Dooks
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
-*/
-
-#include <linux/serial_reg.h>
-
-#include "serial.h"
-
- .pushsection .data
-omap_uart_phys: .word 0x0
-omap_uart_virt: .word 0x0
- .popsection
-
- /*
- * Note that this code won't work if the bootloader passes
- * a wrong machine ID number in r1. To debug, just hardcode
- * the desired UART phys and virt addresses temporarily into
- * the omap_uart_phys and omap_uart_virt above.
- */
- .macro addruart, rp, rv, tmp
-
- /* Use omap_uart_phys/virt if already configured */
-9: adr \rp, 99f @ get effective addr of 99f
- ldr \rv, [\rp] @ get absolute addr of 99f
- sub \rv, \rv, \rp @ offset between the two
- ldr \rp, [\rp, #4] @ abs addr of omap_uart_phys
- sub \tmp, \rp, \rv @ make it effective
- ldr \rp, [\tmp, #0] @ omap_uart_phys
- ldr \rv, [\tmp, #4] @ omap_uart_virt
- cmp \rp, #0 @ is port configured?
- cmpne \rv, #0
- bne 100f @ already configured
-
- /* Check the debug UART configuration set in uncompress.h */
- and \rp, pc, #0xff000000
- ldr \rv, =OMAP_UART_INFO_OFS
- ldr \rp, [\rp, \rv]
-
- /* Select the UART to use based on the UART1 scratchpad value */
-10: cmp \rp, #0 @ no port configured?
- beq 11f @ if none, try to use UART1
- cmp \rp, #OMAP1UART1
- beq 11f @ configure OMAP1UART1
- cmp \rp, #OMAP1UART2
- beq 12f @ configure OMAP1UART2
- cmp \rp, #OMAP1UART3
- beq 13f @ configure OMAP2UART3
-
- /* Configure the UART offset from the phys/virt base */
-11: mov \rp, #0x00fb0000 @ OMAP1UART1
- b 98f
-12: mov \rp, #0x00fb0000 @ OMAP1UART1
- orr \rp, \rp, #0x00000800 @ OMAP1UART2
- b 98f
-13: mov \rp, #0x00fb0000 @ OMAP1UART1
- orr \rp, \rp, #0x00000800 @ OMAP1UART2
- orr \rp, \rp, #0x00009000 @ OMAP1UART3
-
- /* Store both phys and virt address for the uart */
-98: add \rp, \rp, #0xff000000 @ phys base
- str \rp, [\tmp, #0] @ omap_uart_phys
- sub \rp, \rp, #0xff000000 @ phys base
- add \rp, \rp, #0xfe000000 @ virt base
- str \rp, [\tmp, #4] @ omap_uart_virt
- b 9b
-
- .align
-99: .word .
- .word omap_uart_phys
- .ltorg
-
-100:
- .endm
-
- .macro senduart,rd,rx
- strb \rd, [\rx]
- .endm
-
- .macro busyuart,rd,rx
-1001: ldrb \rd, [\rx, #(UART_LSR << OMAP_PORT_SHIFT)]
- and \rd, \rd, #(UART_LSR_TEMT | UART_LSR_THRE)
- teq \rd, #(UART_LSR_TEMT | UART_LSR_THRE)
- beq 1002f
- ldrb \rd, [\rx, #(UART_LSR << OMAP7XX_PORT_SHIFT)]
- and \rd, \rd, #(UART_LSR_TEMT | UART_LSR_THRE)
- teq \rd, #(UART_LSR_TEMT | UART_LSR_THRE)
- bne 1001b
-1002:
- .endm
-
- .macro waituart,rd,rx
- .endm
diff --git a/arch/arm/mach-qcom/Kconfig b/arch/arm/mach-qcom/Kconfig
index ee5697ba05bc..891442eb01b1 100644
--- a/arch/arm/mach-qcom/Kconfig
+++ b/arch/arm/mach-qcom/Kconfig
@@ -5,6 +5,8 @@ menuconfig ARCH_QCOM
select ARM_AMBA
select CLKSRC_OF
select PINCTRL
+ select MIGHT_HAVE_PCI
+ select PCI_DOMAINS if PCI
select QCOM_SCM if SMP
help
Support for Qualcomm's devicetree based systems.
@@ -23,7 +25,4 @@ config ARCH_MSM8974
bool "Enable support for MSM8974"
select HAVE_ARM_ARCH_TIMER
-config QCOM_SCM
- bool
-
endif
diff --git a/arch/arm/mach-qcom/Makefile b/arch/arm/mach-qcom/Makefile
index 8f756ae1ae31..e324375fa919 100644
--- a/arch/arm/mach-qcom/Makefile
+++ b/arch/arm/mach-qcom/Makefile
@@ -1,5 +1,2 @@
obj-y := board.o
obj-$(CONFIG_SMP) += platsmp.o
-obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
-
-CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
diff --git a/arch/arm/mach-qcom/board.c b/arch/arm/mach-qcom/board.c
index 6d8bbf7d39d8..226b5c944def 100644
--- a/arch/arm/mach-qcom/board.c
+++ b/arch/arm/mach-qcom/board.c
@@ -11,6 +11,10 @@
*/
#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
#include <asm/mach/arch.h>
@@ -25,6 +29,31 @@ static const char * const qcom_dt_match[] __initconst = {
NULL
};
+static void __init qcom_late_init(void)
+{
+ struct device_node *node;
+ int reset_gpio, ret;
+
+ for_each_compatible_node(node, NULL, "atheros,ath6kl") {
+ of_node_put(node);
+
+ reset_gpio = of_get_named_gpio(node, "reset-gpio", 0);
+ if (reset_gpio < 0)
+ return;
+
+ ret = gpio_request_one(reset_gpio,
+ GPIOF_DIR_OUT | GPIOF_INIT_HIGH, "reset");
+ if (ret)
+ return;
+
+ udelay(100);
+ gpio_set_value(reset_gpio, 0);
+ udelay(100);
+ gpio_set_value(reset_gpio, 1);
+ }
+}
+
DT_MACHINE_START(QCOM_DT, "Qualcomm (Flattened Device Tree)")
- .dt_compat = qcom_dt_match,
+ .init_late = qcom_late_init,
+ .dt_compat = qcom_dt_match,
MACHINE_END
diff --git a/arch/arm/mach-qcom/platsmp.c b/arch/arm/mach-qcom/platsmp.c
index d6908569ecaf..a692bcb7e7f5 100644
--- a/arch/arm/mach-qcom/platsmp.c
+++ b/arch/arm/mach-qcom/platsmp.c
@@ -20,7 +20,7 @@
#include <asm/smp_plat.h>
-#include "scm-boot.h"
+#include <soc/qcom/scm-boot.h>
#define VDD_SC1_ARRAY_CLAMP_GFS_CTL 0x35a0
#define SCSS_CPU1CORE_RESET 0x2d80
diff --git a/arch/arm/mach-sa1100/include/mach/debug-macro.S b/arch/arm/mach-sa1100/include/mach/debug-macro.S
deleted file mode 100644
index 530772d937ad..000000000000
--- a/arch/arm/mach-sa1100/include/mach/debug-macro.S
+++ /dev/null
@@ -1,62 +0,0 @@
-/* arch/arm/mach-sa1100/include/mach/debug-macro.S
- *
- * Debugging macro include header
- *
- * Copyright (C) 1994-1999 Russell King
- * Moved from linux/arch/arm/kernel/debug.S by Ben Dooks
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
-*/
-#include <mach/hardware.h>
-
- .macro addruart, rp, rv, tmp
- mrc p15, 0, \rp, c1, c0
- tst \rp, #1 @ MMU enabled?
- moveq \rp, #0x80000000 @ physical base address
- movne \rp, #0xf8000000 @ virtual address
-
- @ We probe for the active serial port here, coherently with
- @ the comment in arch/arm/mach-sa1100/include/mach/uncompress.h.
- @ We assume r1 can be clobbered.
-
- @ see if Ser3 is active
- add \rp, \rp, #0x00050000
- ldr \rv, [\rp, #UTCR3]
- tst \rv, #UTCR3_TXE
-
- @ if Ser3 is inactive, then try Ser1
- addeq \rp, \rp, #(0x00010000 - 0x00050000)
- ldreq \rv, [\rp, #UTCR3]
- tsteq \rv, #UTCR3_TXE
-
- @ if Ser1 is inactive, then try Ser2
- addeq \rp, \rp, #(0x00030000 - 0x00010000)
- ldreq \rv, [\rp, #UTCR3]
- tsteq \rv, #UTCR3_TXE
-
- @ clear top bits, and generate both phys and virt addresses
- lsl \rp, \rp, #8
- lsr \rp, \rp, #8
- orr \rv, \rp, #0xf8000000 @ virtual
- orr \rp, \rp, #0x80000000 @ physical
-
- .endm
-
- .macro senduart,rd,rx
- str \rd, [\rx, #UTDR]
- .endm
-
- .macro waituart,rd,rx
-1001: ldr \rd, [\rx, #UTSR1]
- tst \rd, #UTSR1_TNF
- beq 1001b
- .endm
-
- .macro busyuart,rd,rx
-1001: ldr \rd, [\rx, #UTSR1]
- tst \rd, #UTSR1_TBY
- bne 1001b
- .endm
diff --git a/arch/arm/mm/proc-v7.S b/arch/arm/mm/proc-v7.S
index b3a947863ac7..6c33eaf879ae 100644
--- a/arch/arm/mm/proc-v7.S
+++ b/arch/arm/mm/proc-v7.S
@@ -593,9 +593,10 @@ __krait_proc_info:
/*
* Some Krait processors don't indicate support for SDIV and UDIV
* instructions in the ARM instruction set, even though they actually
- * do support them.
+ * do support them. They also don't indicate support for fused multiply
+ * instructions even though they actually do support them.
*/
- __v7_proc __v7_setup, hwcaps = HWCAP_IDIV
+ __v7_proc __v7_setup, hwcaps = HWCAP_IDIV | HWCAP_VFPv4
.size __krait_proc_info, . - __krait_proc_info
/*
diff --git a/arch/arm/vfp/vfphw.S b/arch/arm/vfp/vfphw.S
index cda654cbf2c2..f74a8f7e5f84 100644
--- a/arch/arm/vfp/vfphw.S
+++ b/arch/arm/vfp/vfphw.S
@@ -197,6 +197,12 @@ look_for_VFP_exceptions:
tst r5, #FPSCR_IXE
bne process_exception
+ tst r5, #FPSCR_LENGTH_MASK
+ beq skip
+ orr r1, r1, #FPEXC_DEX
+ b process_exception
+skip:
+
@ Fall into hand on to next handler - appropriate coproc instr
@ not recognised by VFP
diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c
index 2f37e1d6cb45..f901242dee98 100644
--- a/arch/arm/vfp/vfpmodule.c
+++ b/arch/arm/vfp/vfpmodule.c
@@ -722,6 +722,7 @@ static int __init vfp_init(void)
{
unsigned int vfpsid;
unsigned int cpu_arch = cpu_architecture();
+ u32 mvfr0;
if (cpu_arch >= CPU_ARCH_ARMv6)
on_each_cpu(vfp_enable, NULL, 1);
@@ -738,63 +739,73 @@ static int __init vfp_init(void)
vfp_vector = vfp_null_entry;
pr_info("VFP support v0.3: ");
- if (VFP_arch)
+ if (VFP_arch) {
pr_cont("not present\n");
- else if (vfpsid & FPSID_NODOUBLE) {
- pr_cont("no double precision support\n");
- } else {
- hotcpu_notifier(vfp_hotplug, 0);
-
- VFP_arch = (vfpsid & FPSID_ARCH_MASK) >> FPSID_ARCH_BIT; /* Extract the architecture version */
- pr_cont("implementor %02x architecture %d part %02x variant %x rev %x\n",
- (vfpsid & FPSID_IMPLEMENTER_MASK) >> FPSID_IMPLEMENTER_BIT,
- (vfpsid & FPSID_ARCH_MASK) >> FPSID_ARCH_BIT,
- (vfpsid & FPSID_PART_MASK) >> FPSID_PART_BIT,
- (vfpsid & FPSID_VARIANT_MASK) >> FPSID_VARIANT_BIT,
- (vfpsid & FPSID_REV_MASK) >> FPSID_REV_BIT);
-
- vfp_vector = vfp_support_entry;
-
- thread_register_notifier(&vfp_notifier_block);
- vfp_pm_init();
-
+ return 0;
+ /* Extract the arhitecture on CPUID scheme */
+ } else if ((read_cpuid_id() & 0x000f0000) == 0x000f0000) {
+ VFP_arch = vfpsid & FPSID_CPUID_ARCH_MASK;
+ VFP_arch >>= FPSID_ARCH_BIT;
/*
- * We detected VFP, and the support code is
- * in place; report VFP support to userspace.
+ * Check for the presence of the Advanced SIMD
+ * load/store instructions, integer and single
+ * precision floating point operations. Only check
+ * for NEON if the hardware has the MVFR registers.
*/
- elf_hwcap |= HWCAP_VFP;
+#ifdef CONFIG_NEON
+ if ((fmrx(MVFR1) & 0x000fff00) == 0x00011100)
+ elf_hwcap |= HWCAP_NEON;
+#endif
#ifdef CONFIG_VFPv3
- if (VFP_arch >= 2) {
+ mvfr0 = fmrx(MVFR0);
+ if (((mvfr0 & MVFR0_DP_MASK) >> MVFR0_DP_BIT) == 0x2 ||
+ ((mvfr0 & MVFR0_SP_MASK) >> MVFR0_SP_BIT) == 0x2) {
elf_hwcap |= HWCAP_VFPv3;
-
/*
* Check for VFPv3 D16 and VFPv4 D16. CPUs in
* this configuration only have 16 x 64bit
* registers.
*/
- if (((fmrx(MVFR0) & MVFR0_A_SIMD_MASK)) == 1)
- elf_hwcap |= HWCAP_VFPv3D16; /* also v4-D16 */
+ if ((mvfr0 & MVFR0_A_SIMD_MASK) == 1)
+ /* also v4-D16 */
+ elf_hwcap |= HWCAP_VFPv3D16;
else
elf_hwcap |= HWCAP_VFPD32;
}
+
+ if ((fmrx(MVFR1) & 0xf0000000) == 0x10000000)
+ elf_hwcap |= HWCAP_VFPv4;
#endif
- /*
- * Check for the presence of the Advanced SIMD
- * load/store instructions, integer and single
- * precision floating point operations. Only check
- * for NEON if the hardware has the MVFR registers.
- */
- if ((read_cpuid_id() & 0x000f0000) == 0x000f0000) {
-#ifdef CONFIG_NEON
- if ((fmrx(MVFR1) & 0x000fff00) == 0x00011100)
- elf_hwcap |= HWCAP_NEON;
-#endif
-#ifdef CONFIG_VFPv3
- if ((fmrx(MVFR1) & 0xf0000000) == 0x10000000)
- elf_hwcap |= HWCAP_VFPv4;
-#endif
+ /* Extract the architecture version on pre-cpuid scheme */
+ } else {
+ if (vfpsid & FPSID_NODOUBLE) {
+ pr_cont("no double precision support\n");
+ return 0;
}
+
+ VFP_arch = (vfpsid & FPSID_ARCH_MASK) >> FPSID_ARCH_BIT;
}
+
+ hotcpu_notifier(vfp_hotplug, 0);
+
+ vfp_vector = vfp_support_entry;
+
+ thread_register_notifier(&vfp_notifier_block);
+ vfp_pm_init();
+
+ /*
+ * We detected VFP, and the support code is
+ * in place; report VFP support to userspace.
+ */
+ elf_hwcap |= HWCAP_VFP;
+
+ pr_cont("implementor %02x architecture %d part %02x variant %x rev %x\n",
+ (vfpsid & FPSID_IMPLEMENTER_MASK) >> FPSID_IMPLEMENTER_BIT,
+ VFP_arch,
+ (vfpsid & FPSID_PART_MASK) >> FPSID_PART_BIT,
+ (vfpsid & FPSID_VARIANT_MASK) >> FPSID_VARIANT_BIT,
+ (vfpsid & FPSID_REV_MASK) >> FPSID_REV_BIT);
+
return 0;
}
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index 18a9de29df0e..60c7f2841bdf 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -30,7 +30,7 @@
#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw)
-#define div_mask(d) ((1 << ((d)->width)) - 1)
+#define div_mask(width) (BIT(width) - 1)
static unsigned int _get_table_maxdiv(const struct clk_div_table *table)
{
@@ -54,15 +54,16 @@ static unsigned int _get_table_mindiv(const struct clk_div_table *table)
return mindiv;
}
-static unsigned int _get_maxdiv(struct clk_divider *divider)
+static unsigned int _get_maxdiv(const struct clk_div_table *table, u8 width,
+ unsigned long flags)
{
- if (divider->flags & CLK_DIVIDER_ONE_BASED)
- return div_mask(divider);
- if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
- return 1 << div_mask(divider);
- if (divider->table)
- return _get_table_maxdiv(divider->table);
- return div_mask(divider) + 1;
+ if (flags & CLK_DIVIDER_ONE_BASED)
+ return div_mask(width);
+ if (flags & CLK_DIVIDER_POWER_OF_TWO)
+ return 1 << div_mask(width);
+ if (table)
+ return _get_table_maxdiv(table);
+ return div_mask(width) + 1;
}
static unsigned int _get_table_div(const struct clk_div_table *table,
@@ -76,14 +77,15 @@ static unsigned int _get_table_div(const struct clk_div_table *table,
return 0;
}
-static unsigned int _get_div(struct clk_divider *divider, unsigned int val)
+static unsigned int _get_div(const struct clk_div_table *table,
+ unsigned int val, unsigned long flags)
{
- if (divider->flags & CLK_DIVIDER_ONE_BASED)
+ if (flags & CLK_DIVIDER_ONE_BASED)
return val;
- if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
+ if (flags & CLK_DIVIDER_POWER_OF_TWO)
return 1 << val;
- if (divider->table)
- return _get_table_div(divider->table, val);
+ if (table)
+ return _get_table_div(table, val);
return val + 1;
}
@@ -98,29 +100,28 @@ static unsigned int _get_table_val(const struct clk_div_table *table,
return 0;
}
-static unsigned int _get_val(struct clk_divider *divider, unsigned int div)
+static unsigned int _get_val(const struct clk_div_table *table,
+ unsigned int div, unsigned long flags)
{
- if (divider->flags & CLK_DIVIDER_ONE_BASED)
+ if (flags & CLK_DIVIDER_ONE_BASED)
return div;
- if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
+ if (flags & CLK_DIVIDER_POWER_OF_TWO)
return __ffs(div);
- if (divider->table)
- return _get_table_val(divider->table, div);
+ if (table)
+ return _get_table_val(table, div);
return div - 1;
}
-static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
- unsigned long parent_rate)
+unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
+ unsigned int val,
+ const struct clk_div_table *table,
+ unsigned long flags)
{
- struct clk_divider *divider = to_clk_divider(hw);
- unsigned int div, val;
-
- val = clk_readl(divider->reg) >> divider->shift;
- val &= div_mask(divider);
+ unsigned int div;
- div = _get_div(divider, val);
+ div = _get_div(table, val, flags);
if (!div) {
- WARN(!(divider->flags & CLK_DIVIDER_ALLOW_ZERO),
+ WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO),
"%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n",
__clk_get_name(hw->clk));
return parent_rate;
@@ -128,6 +129,20 @@ static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
return DIV_ROUND_UP(parent_rate, div);
}
+EXPORT_SYMBOL_GPL(divider_recalc_rate);
+
+static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ unsigned int val;
+
+ val = clk_readl(divider->reg) >> divider->shift;
+ val &= div_mask(divider->width);
+
+ return divider_recalc_rate(hw, parent_rate, val, divider->table,
+ divider->flags);
+}
/*
* The reverse of DIV_ROUND_UP: The maximum number which
@@ -146,12 +161,13 @@ static bool _is_valid_table_div(const struct clk_div_table *table,
return false;
}
-static bool _is_valid_div(struct clk_divider *divider, unsigned int div)
+static bool _is_valid_div(const struct clk_div_table *table, unsigned int div,
+ unsigned long flags)
{
- if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
+ if (flags & CLK_DIVIDER_POWER_OF_TWO)
return is_power_of_2(div);
- if (divider->table)
- return _is_valid_table_div(divider->table, div);
+ if (table)
+ return _is_valid_table_div(table, div);
return true;
}
@@ -191,71 +207,76 @@ static int _round_down_table(const struct clk_div_table *table, int div)
return down;
}
-static int _div_round_up(struct clk_divider *divider,
- unsigned long parent_rate, unsigned long rate)
+static int _div_round_up(const struct clk_div_table *table,
+ unsigned long parent_rate, unsigned long rate,
+ unsigned long flags)
{
int div = DIV_ROUND_UP(parent_rate, rate);
- if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
+ if (flags & CLK_DIVIDER_POWER_OF_TWO)
div = __roundup_pow_of_two(div);
- if (divider->table)
- div = _round_up_table(divider->table, div);
+ if (table)
+ div = _round_up_table(table, div);
return div;
}
-static int _div_round_closest(struct clk_divider *divider,
- unsigned long parent_rate, unsigned long rate)
+static int _div_round_closest(const struct clk_div_table *table,
+ unsigned long parent_rate, unsigned long rate,
+ unsigned long flags)
{
int up, down, div;
up = down = div = DIV_ROUND_CLOSEST(parent_rate, rate);
- if (divider->flags & CLK_DIVIDER_POWER_OF_TWO) {
+ if (flags & CLK_DIVIDER_POWER_OF_TWO) {
up = __roundup_pow_of_two(div);
down = __rounddown_pow_of_two(div);
- } else if (divider->table) {
- up = _round_up_table(divider->table, div);
- down = _round_down_table(divider->table, div);
+ } else if (table) {
+ up = _round_up_table(table, div);
+ down = _round_down_table(table, div);
}
return (up - div) <= (div - down) ? up : down;
}
-static int _div_round(struct clk_divider *divider, unsigned long parent_rate,
- unsigned long rate)
+static int _div_round(const struct clk_div_table *table,
+ unsigned long parent_rate, unsigned long rate,
+ unsigned long flags)
{
- if (divider->flags & CLK_DIVIDER_ROUND_CLOSEST)
- return _div_round_closest(divider, parent_rate, rate);
+ if (flags & CLK_DIVIDER_ROUND_CLOSEST)
+ return _div_round_closest(table, parent_rate, rate, flags);
- return _div_round_up(divider, parent_rate, rate);
+ return _div_round_up(table, parent_rate, rate, flags);
}
-static bool _is_best_div(struct clk_divider *divider,
- unsigned long rate, unsigned long now, unsigned long best)
+static bool _is_best_div(unsigned long rate, unsigned long now,
+ unsigned long best, unsigned long flags)
{
- if (divider->flags & CLK_DIVIDER_ROUND_CLOSEST)
+ if (flags & CLK_DIVIDER_ROUND_CLOSEST)
return abs(rate - now) < abs(rate - best);
return now <= rate && now > best;
}
-static int _next_div(struct clk_divider *divider, int div)
+static int _next_div(const struct clk_div_table *table, int div,
+ unsigned long flags)
{
div++;
- if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
+ if (flags & CLK_DIVIDER_POWER_OF_TWO)
return __roundup_pow_of_two(div);
- if (divider->table)
- return _round_up_table(divider->table, div);
+ if (table)
+ return _round_up_table(table, div);
return div;
}
static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
- unsigned long *best_parent_rate)
+ unsigned long *best_parent_rate,
+ const struct clk_div_table *table, u8 width,
+ unsigned long flags)
{
- struct clk_divider *divider = to_clk_divider(hw);
int i, bestdiv = 0;
unsigned long parent_rate, best = 0, now, maxdiv;
unsigned long parent_rate_saved = *best_parent_rate;
@@ -263,11 +284,11 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
if (!rate)
rate = 1;
- maxdiv = _get_maxdiv(divider);
+ maxdiv = _get_maxdiv(table, width, flags);
if (!(__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT)) {
parent_rate = *best_parent_rate;
- bestdiv = _div_round(divider, parent_rate, rate);
+ bestdiv = _div_round(table, parent_rate, rate, flags);
bestdiv = bestdiv == 0 ? 1 : bestdiv;
bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv;
return bestdiv;
@@ -279,8 +300,8 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
*/
maxdiv = min(ULONG_MAX / rate, maxdiv);
- for (i = 1; i <= maxdiv; i = _next_div(divider, i)) {
- if (!_is_valid_div(divider, i))
+ for (i = 1; i <= maxdiv; i = _next_div(table, i, flags)) {
+ if (!_is_valid_div(table, i, flags))
continue;
if (rate * i == parent_rate_saved) {
/*
@@ -294,7 +315,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
parent_rate = __clk_round_rate(__clk_get_parent(hw->clk),
MULT_ROUND_UP(rate, i));
now = DIV_ROUND_UP(parent_rate, i);
- if (_is_best_div(divider, rate, now, best)) {
+ if (_is_best_div(rate, now, best, flags)) {
bestdiv = i;
best = now;
*best_parent_rate = parent_rate;
@@ -302,48 +323,72 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
}
if (!bestdiv) {
- bestdiv = _get_maxdiv(divider);
+ bestdiv = _get_maxdiv(table, width, flags);
*best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), 1);
}
return bestdiv;
}
-static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long *prate)
+long divider_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate, const struct clk_div_table *table,
+ u8 width, unsigned long flags)
{
int div;
- div = clk_divider_bestdiv(hw, rate, prate);
+
+ div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
return DIV_ROUND_UP(*prate, div);
}
+EXPORT_SYMBOL_GPL(divider_round_rate);
-static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long parent_rate)
+static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
{
struct clk_divider *divider = to_clk_divider(hw);
+
+ return divider_round_rate(hw, rate, prate, divider->table,
+ divider->width, divider->flags);
+}
+
+int divider_get_val(unsigned long rate, unsigned long parent_rate,
+ const struct clk_div_table *table, u8 width,
+ unsigned long flags)
+{
unsigned int div, value;
- unsigned long flags = 0;
- u32 val;
div = DIV_ROUND_UP(parent_rate, rate);
- if (!_is_valid_div(divider, div))
+ if (!_is_valid_div(table, div, flags))
return -EINVAL;
- value = _get_val(divider, div);
+ value = _get_val(table, div, flags);
+
+ min_t(unsigned int, value, div_mask(width));
+
+ return value;
+}
+EXPORT_SYMBOL_GPL(divider_get_val);
+
+static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_divider *divider = to_clk_divider(hw);
+ unsigned int value;
+ unsigned long flags = 0;
+ u32 val;
- if (value > div_mask(divider))
- value = div_mask(divider);
+ value = divider_get_val(rate, parent_rate, divider->table,
+ divider->width, divider->flags);
if (divider->lock)
spin_lock_irqsave(divider->lock, flags);
if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
- val = div_mask(divider) << (divider->shift + 16);
+ val = div_mask(divider->width) << (divider->shift + 16);
} else {
val = clk_readl(divider->reg);
- val &= ~(div_mask(divider) << divider->shift);
+ val &= ~(div_mask(divider->width) << divider->shift);
}
val |= value << divider->shift;
clk_writel(val, divider->reg);
diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c
index 4f96ff3ba728..54217961ae8a 100644
--- a/drivers/clk/clk-mux.c
+++ b/drivers/clk/clk-mux.c
@@ -29,35 +29,24 @@
#define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw)
-static u8 clk_mux_get_parent(struct clk_hw *hw)
+unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val,
+ unsigned int *table, unsigned long flags)
{
- struct clk_mux *mux = to_clk_mux(hw);
int num_parents = __clk_get_num_parents(hw->clk);
- u32 val;
-
- /*
- * FIXME need a mux-specific flag to determine if val is bitwise or numeric
- * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
- * to 0x7 (index starts at one)
- * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
- * val = 0x4 really means "bit 2, index starts at bit 0"
- */
- val = clk_readl(mux->reg) >> mux->shift;
- val &= mux->mask;
- if (mux->table) {
+ if (table) {
int i;
for (i = 0; i < num_parents; i++)
- if (mux->table[i] == val)
+ if (table[i] == val)
return i;
return -EINVAL;
}
- if (val && (mux->flags & CLK_MUX_INDEX_BIT))
+ if (val && (flags & CLK_MUX_INDEX_BIT))
val = ffs(val) - 1;
- if (val && (mux->flags & CLK_MUX_INDEX_ONE))
+ if (val && (flags & CLK_MUX_INDEX_ONE))
val--;
if (val >= num_parents)
@@ -65,24 +54,53 @@ static u8 clk_mux_get_parent(struct clk_hw *hw)
return val;
}
+EXPORT_SYMBOL_GPL(clk_mux_get_parent);
-static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
+static u8 _clk_mux_get_parent(struct clk_hw *hw)
{
struct clk_mux *mux = to_clk_mux(hw);
u32 val;
- unsigned long flags = 0;
- if (mux->table)
- index = mux->table[index];
+ /*
+ * FIXME need a mux-specific flag to determine if val is bitwise or numeric
+ * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
+ * to 0x7 (index starts at one)
+ * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
+ * val = 0x4 really means "bit 2, index starts at bit 0"
+ */
+ val = clk_readl(mux->reg) >> mux->shift;
+ val &= mux->mask;
+
+ return clk_mux_get_parent(hw, val, mux->table, mux->flags);
+}
+
+unsigned int clk_mux_reindex(u8 index, unsigned int *table,
+ unsigned long flags)
+{
+ unsigned int val = index;
- else {
- if (mux->flags & CLK_MUX_INDEX_BIT)
- index = (1 << ffs(index));
+ if (table) {
+ val = table[val];
+ } else {
+ if (flags & CLK_MUX_INDEX_BIT)
+ val = (1 << ffs(val));
- if (mux->flags & CLK_MUX_INDEX_ONE)
- index++;
+ if (flags & CLK_MUX_INDEX_ONE)
+ val++;
}
+ return val;
+}
+EXPORT_SYMBOL_GPL(clk_mux_reindex);
+
+static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_mux *mux = to_clk_mux(hw);
+ u32 val;
+ unsigned long flags = 0;
+
+ index = clk_mux_reindex(index, mux->table, mux->flags);
+
if (mux->lock)
spin_lock_irqsave(mux->lock, flags);
@@ -102,21 +120,21 @@ static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
}
const struct clk_ops clk_mux_ops = {
- .get_parent = clk_mux_get_parent,
+ .get_parent = _clk_mux_get_parent,
.set_parent = clk_mux_set_parent,
.determine_rate = __clk_mux_determine_rate,
};
EXPORT_SYMBOL_GPL(clk_mux_ops);
const struct clk_ops clk_mux_ro_ops = {
- .get_parent = clk_mux_get_parent,
+ .get_parent = _clk_mux_get_parent,
};
EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
struct clk *clk_register_mux_table(struct device *dev, const char *name,
const char **parent_names, u8 num_parents, unsigned long flags,
void __iomem *reg, u8 shift, u32 mask,
- u8 clk_mux_flags, u32 *table, spinlock_t *lock)
+ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock)
{
struct clk_mux *mux;
struct clk *clk;
@@ -177,3 +195,18 @@ struct clk *clk_register_mux(struct device *dev, const char *name,
NULL, lock);
}
EXPORT_SYMBOL_GPL(clk_register_mux);
+
+void clk_unregister_mux(struct clk *clk)
+{
+ struct clk_hw *hw;
+ struct clk_mux *mux;
+
+ hw = __clk_get_hw(clk);
+ if (!hw)
+ return;
+
+ mux = to_clk_mux(hw);
+ clk_unregister(clk);
+ kfree(mux);
+}
+EXPORT_SYMBOL_GPL(clk_unregister_mux);
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 4896ae9e23da..b06b403f2a54 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -700,14 +700,20 @@ struct clk *__clk_lookup(const char *name)
return NULL;
}
-/*
- * Helper for finding best parent to provide a given frequency. This can be used
- * directly as a determine_rate callback (e.g. for a mux), or from a more
- * complex clock that may combine a mux with other operations.
- */
-long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
- unsigned long *best_parent_rate,
- struct clk **best_parent_p)
+static bool mux_is_better_rate(unsigned long rate, unsigned long now,
+ unsigned long best, unsigned long flags)
+{
+ if (flags & CLK_MUX_ROUND_CLOSEST)
+ return abs(now - rate) < abs(best - rate);
+
+ return now <= rate && now > best;
+}
+
+static long
+clk_mux_determine_rate_flags(struct clk_hw *hw, unsigned long rate,
+ unsigned long *best_parent_rate,
+ struct clk **best_parent_p,
+ unsigned long flags)
{
struct clk *clk = hw->clk, *parent, *best_parent = NULL;
int i, num_parents;
@@ -735,7 +741,7 @@ long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
parent_rate = __clk_round_rate(parent, rate);
else
parent_rate = __clk_get_rate(parent);
- if (parent_rate <= rate && parent_rate > best) {
+ if (mux_is_better_rate(rate, parent_rate, best, flags)) {
best_parent = parent;
best = parent_rate;
}
@@ -748,8 +754,31 @@ out:
return best;
}
+
+/*
+ * Helper for finding best parent to provide a given frequency. This can be used
+ * directly as a determine_rate callback (e.g. for a mux), or from a more
+ * complex clock that may combine a mux with other operations.
+ */
+long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *best_parent_rate,
+ struct clk **best_parent_p)
+{
+ return clk_mux_determine_rate_flags(hw, rate, best_parent_rate,
+ best_parent_p, 0);
+}
EXPORT_SYMBOL_GPL(__clk_mux_determine_rate);
+long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate,
+ unsigned long *best_parent_rate,
+ struct clk **best_parent_p)
+{
+ return clk_mux_determine_rate_flags(hw, rate, best_parent_rate,
+ best_parent_p,
+ CLK_MUX_ROUND_CLOSEST);
+}
+EXPORT_SYMBOL_GPL(__clk_mux_determine_rate_closest);
+
/*** clk api ***/
void __clk_unprepare(struct clk *clk)
@@ -1327,6 +1356,7 @@ static void clk_calc_subtree(struct clk *clk, unsigned long new_rate,
struct clk *new_parent, u8 p_index)
{
struct clk *child;
+ struct clk *parent;
clk->new_rate = new_rate;
clk->new_parent = new_parent;
@@ -1336,6 +1366,17 @@ static void clk_calc_subtree(struct clk *clk, unsigned long new_rate,
if (new_parent && new_parent != clk->parent)
new_parent->new_child = clk;
+ if (clk->ops->get_safe_parent) {
+ parent = clk->ops->get_safe_parent(clk->hw);
+ if (parent) {
+ p_index = clk_fetch_parent_index(clk, parent);
+ clk->safe_parent_index = p_index;
+ clk->safe_parent = parent;
+ }
+ } else {
+ clk->safe_parent = NULL;
+ }
+
hlist_for_each_entry(child, &clk->children, child_node) {
child->new_rate = clk_recalc(child, new_rate);
clk_calc_subtree(child, child->new_rate, NULL, 0);
@@ -1418,14 +1459,42 @@ out:
static struct clk *clk_propagate_rate_change(struct clk *clk, unsigned long event)
{
struct clk *child, *tmp_clk, *fail_clk = NULL;
+ struct clk *old_parent;
int ret = NOTIFY_DONE;
- if (clk->rate == clk->new_rate)
+ if (clk->rate == clk->new_rate && event != POST_RATE_CHANGE)
return NULL;
+ switch (event) {
+ case PRE_RATE_CHANGE:
+ if (clk->safe_parent)
+ clk->ops->set_parent(clk->hw, clk->safe_parent_index);
+ break;
+ case POST_RATE_CHANGE:
+ if (clk->safe_parent) {
+ old_parent = __clk_set_parent_before(clk,
+ clk->new_parent);
+ if (clk->ops->set_rate_and_parent) {
+ clk->ops->set_rate_and_parent(clk->hw,
+ clk->new_rate,
+ clk->new_parent ?
+ clk->new_parent->rate : 0,
+ clk->new_parent_index);
+ } else if (clk->ops->set_parent) {
+ clk->ops->set_parent(clk->hw,
+ clk->new_parent_index);
+ }
+ __clk_set_parent_after(clk, clk->new_parent,
+ old_parent);
+ }
+ break;
+ }
+
if (clk->notifier_count) {
- ret = __clk_notify(clk, event, clk->rate, clk->new_rate);
- if (ret & NOTIFY_STOP_MASK)
+ if (event != POST_RATE_CHANGE)
+ ret = __clk_notify(clk, event, clk->rate,
+ clk->new_rate);
+ if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE)
fail_clk = clk;
}
@@ -1452,23 +1521,26 @@ static struct clk *clk_propagate_rate_change(struct clk *clk, unsigned long even
* walk down a subtree and set the new rates notifying the rate
* change on the way
*/
-static void clk_change_rate(struct clk *clk)
+static void clk_change_rate(struct clk *clk, unsigned long best_parent_rate)
{
struct clk *child;
struct hlist_node *tmp;
unsigned long old_rate;
- unsigned long best_parent_rate = 0;
bool skip_set_rate = false;
struct clk *old_parent;
- old_rate = clk->rate;
+ hlist_for_each_entry(child, &clk->children, child_node) {
+ /* Skip children who will be reparented to another clock */
+ if (child->new_parent && child->new_parent != clk)
+ continue;
+ if (child->new_rate > child->rate)
+ clk_change_rate(child, clk->new_rate);
+ }
- if (clk->new_parent)
- best_parent_rate = clk->new_parent->rate;
- else if (clk->parent)
- best_parent_rate = clk->parent->rate;
+ old_rate = clk->rate;
- if (clk->new_parent && clk->new_parent != clk->parent) {
+ if (clk->new_parent && clk->new_parent != clk->parent &&
+ !clk->safe_parent) {
old_parent = __clk_set_parent_before(clk, clk->new_parent);
if (clk->ops->set_rate_and_parent) {
@@ -1486,10 +1558,7 @@ static void clk_change_rate(struct clk *clk)
if (!skip_set_rate && clk->ops->set_rate)
clk->ops->set_rate(clk->hw, clk->new_rate, best_parent_rate);
- clk->rate = clk_recalc(clk, best_parent_rate);
-
- if (clk->notifier_count && old_rate != clk->rate)
- __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
+ clk->rate = clk->new_rate;
/*
* Use safe iteration, as change_rate can actually swap parents
@@ -1499,12 +1568,13 @@ static void clk_change_rate(struct clk *clk)
/* Skip children who will be reparented to another clock */
if (child->new_parent && child->new_parent != clk)
continue;
- clk_change_rate(child);
+ if (child->new_rate != child->rate)
+ clk_change_rate(child, clk->new_rate);
}
/* handle the new child who might not be in clk->children yet */
- if (clk->new_child)
- clk_change_rate(clk->new_child);
+ if (clk->new_child && clk->new_child->new_rate != clk->new_child->rate)
+ clk_change_rate(clk->new_child, clk->new_rate);
}
/**
@@ -1532,6 +1602,7 @@ int clk_set_rate(struct clk *clk, unsigned long rate)
{
struct clk *top, *fail_clk;
int ret = 0;
+ unsigned long parent_rate;
if (!clk)
return 0;
@@ -1565,9 +1636,15 @@ int clk_set_rate(struct clk *clk, unsigned long rate)
goto out;
}
+ if (top->parent)
+ parent_rate = top->parent->rate;
+ else
+ parent_rate = 0;
+
/* change the rates */
- clk_change_rate(top);
+ clk_change_rate(top, parent_rate);
+ clk_propagate_rate_change(top, POST_RATE_CHANGE);
out:
clk_prepare_unlock();
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index 1107351ed346..63285d116013 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -70,3 +70,31 @@ config MSM_MMCC_8974
Support for the multimedia clock controller on msm8974 devices.
Say Y if you want to support multimedia devices such as display,
graphics, video encode/decode, camera, etc.
+
+config QCOM_HFPLL
+ tristate "High-Frequency PLL (HFPLL) Clock Controller"
+ depends on COMMON_CLK_QCOM
+ help
+ Support for the high-frequency PLLs present on Qualcomm devices.
+ Say Y if you want to support CPU frequency scaling on devices
+ such as MSM8974, APQ8084, etc.
+
+config KPSS_XCC
+ tristate "KPSS Clock Controller"
+ depends on COMMON_CLK_QCOM
+ help
+ Support for the Krait ACC and GCC clock controllers. Say Y
+ if you want to support CPU frequency scaling on devices such
+ as MSM8960, APQ8064, etc.
+
+config KRAITCC
+ tristate "Krait Clock Controller"
+ depends on COMMON_CLK_QCOM && ARM
+ select KRAIT_CLOCKS
+ help
+ Support for the Krait CPU clocks on Qualcomm devices.
+ Say Y if you want to support CPU frequency scaling.
+
+config KRAIT_CLOCKS
+ bool
+ select KRAIT_L2_ACCESSORS
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 783cfb24faa4..866f0fe600d9 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -6,6 +6,8 @@ clk-qcom-y += clk-pll.o
clk-qcom-y += clk-rcg.o
clk-qcom-y += clk-rcg2.o
clk-qcom-y += clk-branch.o
+clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
+clk-qcom-y += clk-hfpll.o
clk-qcom-y += reset.o
obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o
@@ -16,3 +18,6 @@ obj-$(CONFIG_MSM_GCC_8960) += gcc-msm8960.o
obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
+obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
+obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
+obj-$(CONFIG_KRAITCC) += krait-cc.o
diff --git a/drivers/clk/qcom/clk-hfpll.c b/drivers/clk/qcom/clk-hfpll.c
new file mode 100644
index 000000000000..367eb95f1477
--- /dev/null
+++ b/drivers/clk/qcom/clk-hfpll.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/spinlock.h>
+
+#include "clk-regmap.h"
+#include "clk-hfpll.h"
+
+#define PLL_OUTCTRL BIT(0)
+#define PLL_BYPASSNL BIT(1)
+#define PLL_RESET_N BIT(2)
+
+/* Initialize a HFPLL at a given rate and enable it. */
+static void __clk_hfpll_init_once(struct clk_hw *hw)
+{
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ struct hfpll_data const *hd = h->d;
+ struct regmap *regmap = h->clkr.regmap;
+
+ if (likely(h->init_done))
+ return;
+
+ /* Configure PLL parameters for integer mode. */
+ if (hd->config_val)
+ regmap_write(regmap, hd->config_reg, hd->config_val);
+ regmap_write(regmap, hd->m_reg, 0);
+ regmap_write(regmap, hd->n_reg, 1);
+
+ if (hd->user_reg) {
+ u32 regval = hd->user_val;
+ unsigned long rate;
+
+ rate = __clk_get_rate(hw->clk);
+
+ /* Pick the right VCO. */
+ if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
+ regval |= hd->user_vco_mask;
+ regmap_write(regmap, hd->user_reg, regval);
+ }
+
+ if (hd->droop_reg)
+ regmap_write(regmap, hd->droop_reg, hd->droop_val);
+
+ h->init_done = true;
+}
+
+static void __clk_hfpll_enable(struct clk_hw *hw)
+{
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ struct hfpll_data const *hd = h->d;
+ struct regmap *regmap = h->clkr.regmap;
+ u32 val;
+
+ __clk_hfpll_init_once(hw);
+
+ /* Disable PLL bypass mode. */
+ regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL);
+
+ /*
+ * H/W requires a 5us delay between disabling the bypass and
+ * de-asserting the reset. Delay 10us just to be safe.
+ */
+ udelay(10);
+
+ /* De-assert active-low PLL reset. */
+ regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N);
+
+ /* Wait for PLL to lock. */
+ if (hd->status_reg) {
+ do {
+ regmap_read(regmap, hd->status_reg, &val);
+ } while (!(val & BIT(hd->lock_bit)));
+ } else {
+ udelay(60);
+ }
+
+ /* Enable PLL output. */
+ regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL);
+}
+
+/* Enable an already-configured HFPLL. */
+static int clk_hfpll_enable(struct clk_hw *hw)
+{
+ unsigned long flags;
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ struct hfpll_data const *hd = h->d;
+ struct regmap *regmap = h->clkr.regmap;
+ u32 mode;
+
+ spin_lock_irqsave(&h->lock, flags);
+ regmap_read(regmap, hd->mode_reg, &mode);
+ if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)))
+ __clk_hfpll_enable(hw);
+ spin_unlock_irqrestore(&h->lock, flags);
+
+ return 0;
+}
+
+static void __clk_hfpll_disable(struct clk_hfpll *h)
+{
+ struct hfpll_data const *hd = h->d;
+ struct regmap *regmap = h->clkr.regmap;
+
+ /*
+ * Disable the PLL output, disable test mode, enable the bypass mode,
+ * and assert the reset.
+ */
+ regmap_update_bits(regmap, hd->mode_reg,
+ PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0);
+}
+
+static void clk_hfpll_disable(struct clk_hw *hw)
+{
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ unsigned long flags;
+
+ spin_lock_irqsave(&h->lock, flags);
+ __clk_hfpll_disable(h);
+ spin_unlock_irqrestore(&h->lock, flags);
+}
+
+static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ struct hfpll_data const *hd = h->d;
+ unsigned long rrate;
+
+ rate = clamp(rate, hd->min_rate, hd->max_rate);
+
+ rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate;
+ if (rrate > hd->max_rate)
+ rrate -= *parent_rate;
+
+ return rrate;
+}
+
+/*
+ * For optimization reasons, assumes no downstream clocks are actively using
+ * it.
+ */
+static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ struct hfpll_data const *hd = h->d;
+ struct regmap *regmap = h->clkr.regmap;
+ unsigned long flags;
+ u32 l_val, val;
+ bool enabled;
+
+ l_val = rate / parent_rate;
+
+ spin_lock_irqsave(&h->lock, flags);
+
+ enabled = __clk_is_enabled(hw->clk);
+ if (enabled)
+ __clk_hfpll_disable(h);
+
+ /* Pick the right VCO. */
+ if (hd->user_reg && hd->user_vco_mask) {
+ regmap_read(regmap, hd->user_reg, &val);
+ if (rate <= hd->low_vco_max_rate)
+ val &= ~hd->user_vco_mask;
+ else
+ val |= hd->user_vco_mask;
+ regmap_write(regmap, hd->user_reg, val);
+ }
+
+ regmap_write(regmap, hd->l_reg, l_val);
+
+ if (enabled)
+ __clk_hfpll_enable(hw);
+
+ spin_unlock_irqrestore(&h->lock, flags);
+
+ return 0;
+}
+
+static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ struct hfpll_data const *hd = h->d;
+ struct regmap *regmap = h->clkr.regmap;
+ u32 l_val;
+
+ regmap_read(regmap, hd->l_reg, &l_val);
+
+ return l_val * parent_rate;
+}
+
+static void clk_hfpll_init(struct clk_hw *hw)
+{
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ struct hfpll_data const *hd = h->d;
+ struct regmap *regmap = h->clkr.regmap;
+ u32 mode, status;
+
+ regmap_read(regmap, hd->mode_reg, &mode);
+ if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) {
+ __clk_hfpll_init_once(hw);
+ return;
+ }
+
+ if (hd->status_reg) {
+ regmap_read(regmap, hd->status_reg, &status);
+ if (!(status & BIT(hd->lock_bit))) {
+ WARN(1, "HFPLL %s is ON, but not locked!\n",
+ __clk_get_name(hw->clk));
+ clk_hfpll_disable(hw);
+ __clk_hfpll_init_once(hw);
+ }
+ }
+}
+
+static int hfpll_is_enabled(struct clk_hw *hw)
+{
+ struct clk_hfpll *h = to_clk_hfpll(hw);
+ struct hfpll_data const *hd = h->d;
+ struct regmap *regmap = h->clkr.regmap;
+ u32 mode;
+
+ regmap_read(regmap, hd->mode_reg, &mode);
+ mode &= 0x7;
+ return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL);
+}
+
+const struct clk_ops clk_ops_hfpll = {
+ .enable = clk_hfpll_enable,
+ .disable = clk_hfpll_disable,
+ .is_enabled = hfpll_is_enabled,
+ .round_rate = clk_hfpll_round_rate,
+ .set_rate = clk_hfpll_set_rate,
+ .recalc_rate = clk_hfpll_recalc_rate,
+ .init = clk_hfpll_init,
+};
+EXPORT_SYMBOL_GPL(clk_ops_hfpll);
diff --git a/drivers/clk/qcom/clk-hfpll.h b/drivers/clk/qcom/clk-hfpll.h
new file mode 100644
index 000000000000..48c18d664f4e
--- /dev/null
+++ b/drivers/clk/qcom/clk-hfpll.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2013-2014, 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 __QCOM_CLK_HFPLL_H__
+#define __QCOM_CLK_HFPLL_H__
+
+#include <linux/clk-provider.h>
+#include <linux/spinlock.h>
+#include "clk-regmap.h"
+
+struct hfpll_data {
+ u32 mode_reg;
+ u32 l_reg;
+ u32 m_reg;
+ u32 n_reg;
+ u32 user_reg;
+ u32 droop_reg;
+ u32 config_reg;
+ u32 status_reg;
+ u8 lock_bit;
+
+ u32 droop_val;
+ u32 config_val;
+ u32 user_val;
+ u32 user_vco_mask;
+ unsigned long low_vco_max_rate;
+
+ unsigned long min_rate;
+ unsigned long max_rate;
+};
+
+struct clk_hfpll {
+ struct hfpll_data const *d;
+ int init_done;
+
+ struct clk_regmap clkr;
+ spinlock_t lock;
+};
+
+#define to_clk_hfpll(_hw) \
+ container_of(to_clk_regmap(_hw), struct clk_hfpll, clkr)
+
+extern const struct clk_ops clk_ops_hfpll;
+
+#endif
diff --git a/drivers/clk/qcom/clk-krait.c b/drivers/clk/qcom/clk-krait.c
new file mode 100644
index 000000000000..615bfbee1b6a
--- /dev/null
+++ b/drivers/clk/qcom/clk-krait.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/clk-provider.h>
+#include <linux/spinlock.h>
+
+#include <asm/krait-l2-accessors.h>
+
+#include "clk-krait.h"
+
+/* Secondary and primary muxes share the same cp15 register */
+static DEFINE_SPINLOCK(krait_clock_reg_lock);
+
+#define LPL_SHIFT 8
+static void __krait_mux_set_sel(struct krait_mux_clk *mux, int sel)
+{
+ unsigned long flags;
+ u32 regval;
+
+ spin_lock_irqsave(&krait_clock_reg_lock, flags);
+ regval = krait_get_l2_indirect_reg(mux->offset);
+ regval &= ~(mux->mask << mux->shift);
+ regval |= (sel & mux->mask) << mux->shift;
+ if (mux->lpl) {
+ regval &= ~(mux->mask << (mux->shift + LPL_SHIFT));
+ regval |= (sel & mux->mask) << (mux->shift + LPL_SHIFT);
+ }
+ krait_set_l2_indirect_reg(mux->offset, regval);
+ spin_unlock_irqrestore(&krait_clock_reg_lock, flags);
+
+ /* Wait for switch to complete. */
+ mb();
+ udelay(1);
+}
+
+static int krait_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
+ u32 sel;
+
+ sel = clk_mux_reindex(index, mux->parent_map, 0);
+ mux->en_mask = sel;
+ /* Don't touch mux if CPU is off as it won't work */
+ if (__clk_is_enabled(hw->clk))
+ __krait_mux_set_sel(mux, sel);
+ return 0;
+}
+
+static u8 krait_mux_get_parent(struct clk_hw *hw)
+{
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
+ u32 sel;
+
+ sel = krait_get_l2_indirect_reg(mux->offset);
+ sel >>= mux->shift;
+ sel &= mux->mask;
+ mux->en_mask = sel;
+
+ return clk_mux_get_parent(hw, sel, mux->parent_map, 0);
+}
+
+static struct clk *krait_mux_get_safe_parent(struct clk_hw *hw)
+{
+ int i;
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
+ int num_parents = __clk_get_num_parents(hw->clk);
+
+ i = mux->safe_sel;
+ for (i = 0; i < num_parents; i++)
+ if (mux->safe_sel == mux->parent_map[i])
+ break;
+
+ return clk_get_parent_by_index(hw->clk, i);
+}
+
+static int krait_mux_enable(struct clk_hw *hw)
+{
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
+
+ __krait_mux_set_sel(mux, mux->en_mask);
+
+ return 0;
+}
+
+static void krait_mux_disable(struct clk_hw *hw)
+{
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
+
+ __krait_mux_set_sel(mux, mux->safe_sel);
+}
+
+const struct clk_ops krait_mux_clk_ops = {
+ .enable = krait_mux_enable,
+ .disable = krait_mux_disable,
+ .set_parent = krait_mux_set_parent,
+ .get_parent = krait_mux_get_parent,
+ .determine_rate = __clk_mux_determine_rate_closest,
+ .get_safe_parent = krait_mux_get_safe_parent,
+};
+EXPORT_SYMBOL_GPL(krait_mux_clk_ops);
+
+/* The divider can divide by 2, 4, 6 and 8. But we only really need div-2. */
+static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ *parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), rate * 2);
+ return DIV_ROUND_UP(*parent_rate, 2);
+}
+
+static int krait_div2_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct krait_div2_clk *d = to_krait_div2_clk(hw);
+ unsigned long flags;
+ u32 val;
+ u32 mask = BIT(d->width) - 1;
+
+ if (d->lpl)
+ mask |= mask << (d->shift + LPL_SHIFT);
+
+ spin_lock_irqsave(&krait_clock_reg_lock, flags);
+ val = krait_get_l2_indirect_reg(d->offset);
+ val &= ~mask;
+ krait_set_l2_indirect_reg(d->offset, val);
+ spin_unlock_irqrestore(&krait_clock_reg_lock, flags);
+
+ return 0;
+}
+
+static unsigned long
+krait_div2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+ struct krait_div2_clk *d = to_krait_div2_clk(hw);
+ u32 mask = BIT(d->width) - 1;
+ u32 div;
+
+ div = krait_get_l2_indirect_reg(d->offset);
+ div >>= d->shift;
+ div &= mask;
+ div = (div + 1) * 2;
+
+ return DIV_ROUND_UP(parent_rate, div);
+}
+
+const struct clk_ops krait_div2_clk_ops = {
+ .round_rate = krait_div2_round_rate,
+ .set_rate = krait_div2_set_rate,
+ .recalc_rate = krait_div2_recalc_rate,
+};
+EXPORT_SYMBOL_GPL(krait_div2_clk_ops);
diff --git a/drivers/clk/qcom/clk-krait.h b/drivers/clk/qcom/clk-krait.h
new file mode 100644
index 000000000000..5d0063538e5d
--- /dev/null
+++ b/drivers/clk/qcom/clk-krait.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 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
+ * 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_CLK_KRAIT_H
+#define __QCOM_CLK_KRAIT_H
+
+#include <linux/clk-provider.h>
+
+struct krait_mux_clk {
+ unsigned int *parent_map;
+ bool has_safe_parent;
+ u8 safe_sel;
+ u32 offset;
+ u32 mask;
+ u32 shift;
+ u32 en_mask;
+ bool lpl;
+
+ struct clk_hw hw;
+};
+
+#define to_krait_mux_clk(_hw) container_of(_hw, struct krait_mux_clk, hw)
+
+extern const struct clk_ops krait_mux_clk_ops;
+
+struct krait_div2_clk {
+ u32 offset;
+ u8 width;
+ u32 shift;
+ bool lpl;
+
+ struct clk_hw hw;
+};
+
+#define to_krait_div2_clk(_hw) container_of(_hw, struct krait_div2_clk, hw)
+
+extern const struct clk_ops krait_div2_clk_ops;
+
+#endif
diff --git a/drivers/clk/qcom/gcc-ipq806x.c b/drivers/clk/qcom/gcc-ipq806x.c
index 5cd62a709ac7..1bdf2a1e609b 100644
--- a/drivers/clk/qcom/gcc-ipq806x.c
+++ b/drivers/clk/qcom/gcc-ipq806x.c
@@ -30,6 +30,7 @@
#include "clk-pll.h"
#include "clk-rcg.h"
#include "clk-branch.h"
+#include "clk-hfpll.h"
#include "reset.h"
static struct clk_pll pll0 = {
@@ -102,6 +103,85 @@ static struct clk_regmap pll8_vote = {
},
};
+static struct hfpll_data hfpll0_data = {
+ .mode_reg = 0x3200,
+ .l_reg = 0x3208,
+ .m_reg = 0x320c,
+ .n_reg = 0x3210,
+ .config_reg = 0x3204,
+ .status_reg = 0x321c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3214,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct clk_hfpll hfpll0 = {
+ .d = &hfpll0_data,
+ .clkr.hw.init = &(struct clk_init_data){
+ .parent_names = (const char *[]){ "pxo" },
+ .num_parents = 1,
+ .name = "hfpll0",
+ .ops = &clk_ops_hfpll,
+ .flags = CLK_IGNORE_UNUSED,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll0.lock),
+};
+
+static struct hfpll_data hfpll1_data = {
+ .mode_reg = 0x3240,
+ .l_reg = 0x3248,
+ .m_reg = 0x324c,
+ .n_reg = 0x3250,
+ .config_reg = 0x3244,
+ .status_reg = 0x325c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3314,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct clk_hfpll hfpll1 = {
+ .d = &hfpll1_data,
+ .clkr.hw.init = &(struct clk_init_data){
+ .parent_names = (const char *[]){ "pxo" },
+ .num_parents = 1,
+ .name = "hfpll1",
+ .ops = &clk_ops_hfpll,
+ .flags = CLK_IGNORE_UNUSED,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll1.lock),
+};
+
+static struct hfpll_data hfpll_l2_data = {
+ .mode_reg = 0x3300,
+ .l_reg = 0x3308,
+ .m_reg = 0x330c,
+ .n_reg = 0x3310,
+ .config_reg = 0x3304,
+ .status_reg = 0x331c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3314,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct clk_hfpll hfpll_l2 = {
+ .d = &hfpll_l2_data,
+ .clkr.hw.init = &(struct clk_init_data){
+ .parent_names = (const char *[]){ "pxo" },
+ .num_parents = 1,
+ .name = "hfpll_l2",
+ .ops = &clk_ops_hfpll,
+ .flags = CLK_IGNORE_UNUSED,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll_l2.lock),
+};
+
+
static struct clk_pll pll14 = {
.l_reg = 0x31c4,
.m_reg = 0x31c8,
@@ -2261,6 +2341,9 @@ static struct clk_regmap *gcc_ipq806x_clks[] = {
[USB_FS1_XCVR_SRC] = &usb_fs1_xcvr_clk_src.clkr,
[USB_FS1_XCVR_CLK] = &usb_fs1_xcvr_clk.clkr,
[USB_FS1_SYSTEM_CLK] = &usb_fs1_sys_clk.clkr,
+ [PLL9] = &hfpll0.clkr,
+ [PLL10] = &hfpll1.clkr,
+ [PLL12] = &hfpll_l2.clkr,
};
static const struct qcom_reset_map gcc_ipq806x_resets[] = {
diff --git a/drivers/clk/qcom/gcc-msm8960.c b/drivers/clk/qcom/gcc-msm8960.c
index 007534f7a2d7..e60327baf5db 100644
--- a/drivers/clk/qcom/gcc-msm8960.c
+++ b/drivers/clk/qcom/gcc-msm8960.c
@@ -30,6 +30,7 @@
#include "clk-pll.h"
#include "clk-rcg.h"
#include "clk-branch.h"
+#include "clk-hfpll.h"
#include "reset.h"
static struct clk_pll pll3 = {
@@ -75,6 +76,164 @@ static struct clk_regmap pll8_vote = {
},
};
+static struct hfpll_data hfpll0_data = {
+ .mode_reg = 0x3200,
+ .l_reg = 0x3208,
+ .m_reg = 0x320c,
+ .n_reg = 0x3210,
+ .config_reg = 0x3204,
+ .status_reg = 0x321c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3214,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct clk_hfpll hfpll0 = {
+ .d = &hfpll0_data,
+ .clkr.hw.init = &(struct clk_init_data){
+ .parent_names = (const char *[]){ "pxo" },
+ .num_parents = 1,
+ .name = "hfpll0",
+ .ops = &clk_ops_hfpll,
+ .flags = CLK_IGNORE_UNUSED,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll0.lock),
+};
+
+static struct hfpll_data hfpll1_8064_data = {
+ .mode_reg = 0x3240,
+ .l_reg = 0x3248,
+ .m_reg = 0x324c,
+ .n_reg = 0x3250,
+ .config_reg = 0x3244,
+ .status_reg = 0x325c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3254,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct hfpll_data hfpll1_data = {
+ .mode_reg = 0x3300,
+ .l_reg = 0x3308,
+ .m_reg = 0x330c,
+ .n_reg = 0x3310,
+ .config_reg = 0x3304,
+ .status_reg = 0x331c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3314,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct clk_hfpll hfpll1 = {
+ .d = &hfpll1_data,
+ .clkr.hw.init = &(struct clk_init_data){
+ .parent_names = (const char *[]){ "pxo" },
+ .num_parents = 1,
+ .name = "hfpll1",
+ .ops = &clk_ops_hfpll,
+ .flags = CLK_IGNORE_UNUSED,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll1.lock),
+};
+
+static struct hfpll_data hfpll2_data = {
+ .mode_reg = 0x3280,
+ .l_reg = 0x3288,
+ .m_reg = 0x328c,
+ .n_reg = 0x3290,
+ .config_reg = 0x3284,
+ .status_reg = 0x329c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3294,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct clk_hfpll hfpll2 = {
+ .d = &hfpll2_data,
+ .clkr.hw.init = &(struct clk_init_data){
+ .parent_names = (const char *[]){ "pxo" },
+ .num_parents = 1,
+ .name = "hfpll2",
+ .ops = &clk_ops_hfpll,
+ .flags = CLK_IGNORE_UNUSED,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll2.lock),
+};
+
+static struct hfpll_data hfpll3_data = {
+ .mode_reg = 0x32c0,
+ .l_reg = 0x32c8,
+ .m_reg = 0x32cc,
+ .n_reg = 0x32d0,
+ .config_reg = 0x32c4,
+ .status_reg = 0x32dc,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x32d4,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct clk_hfpll hfpll3 = {
+ .d = &hfpll3_data,
+ .clkr.hw.init = &(struct clk_init_data){
+ .parent_names = (const char *[]){ "pxo" },
+ .num_parents = 1,
+ .name = "hfpll3",
+ .ops = &clk_ops_hfpll,
+ .flags = CLK_IGNORE_UNUSED,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll3.lock),
+};
+
+static struct hfpll_data hfpll_l2_8064_data = {
+ .mode_reg = 0x3300,
+ .l_reg = 0x3308,
+ .m_reg = 0x330c,
+ .n_reg = 0x3310,
+ .config_reg = 0x3304,
+ .status_reg = 0x331c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3314,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct hfpll_data hfpll_l2_data = {
+ .mode_reg = 0x3400,
+ .l_reg = 0x3408,
+ .m_reg = 0x340c,
+ .n_reg = 0x3410,
+ .config_reg = 0x3404,
+ .status_reg = 0x341c,
+ .config_val = 0x7845c665,
+ .droop_reg = 0x3414,
+ .droop_val = 0x0108c000,
+ .min_rate = 600000000UL,
+ .max_rate = 1800000000UL,
+};
+
+static struct clk_hfpll hfpll_l2 = {
+ .d = &hfpll_l2_data,
+ .clkr.hw.init = &(struct clk_init_data){
+ .parent_names = (const char *[]){ "pxo" },
+ .num_parents = 1,
+ .name = "hfpll_l2",
+ .ops = &clk_ops_hfpll,
+ .flags = CLK_IGNORE_UNUSED,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll_l2.lock),
+};
+
static struct clk_pll pll14 = {
.l_reg = 0x31c4,
.m_reg = 0x31c8,
@@ -3140,6 +3299,9 @@ static struct clk_regmap *gcc_msm8960_clks[] = {
[PMIC_ARB1_H_CLK] = &pmic_arb1_h_clk.clkr,
[PMIC_SSBI2_CLK] = &pmic_ssbi2_clk.clkr,
[RPM_MSG_RAM_H_CLK] = &rpm_msg_ram_h_clk.clkr,
+ [PLL9] = &hfpll0.clkr,
+ [PLL10] = &hfpll1.clkr,
+ [PLL12] = &hfpll_l2.clkr,
};
static const struct qcom_reset_map gcc_msm8960_resets[] = {
@@ -3350,6 +3512,11 @@ static struct clk_regmap *gcc_apq8064_clks[] = {
[PMIC_ARB1_H_CLK] = &pmic_arb1_h_clk.clkr,
[PMIC_SSBI2_CLK] = &pmic_ssbi2_clk.clkr,
[RPM_MSG_RAM_H_CLK] = &rpm_msg_ram_h_clk.clkr,
+ [PLL9] = &hfpll0.clkr,
+ [PLL10] = &hfpll1.clkr,
+ [PLL12] = &hfpll_l2.clkr,
+ [PLL16] = &hfpll2.clkr,
+ [PLL17] = &hfpll3.clkr,
};
static const struct qcom_reset_map gcc_apq8064_resets[] = {
@@ -3496,6 +3663,11 @@ static int gcc_msm8960_probe(struct platform_device *pdev)
if (!match)
return -EINVAL;
+ if (match->data == &gcc_apq8064_desc) {
+ hfpll1.d = &hfpll1_8064_data;
+ hfpll_l2.d = &hfpll_l2_8064_data;
+ }
+
/* Temporary until RPM clocks supported */
clk = clk_register_fixed_rate(dev, "cxo", NULL, CLK_IS_ROOT, 19200000);
if (IS_ERR(clk))
diff --git a/drivers/clk/qcom/hfpll.c b/drivers/clk/qcom/hfpll.c
new file mode 100644
index 000000000000..701a377d35c1
--- /dev/null
+++ b/drivers/clk/qcom/hfpll.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+#include "clk-regmap.h"
+#include "clk-hfpll.h"
+
+static const struct hfpll_data hdata = {
+ .mode_reg = 0x00,
+ .l_reg = 0x04,
+ .m_reg = 0x08,
+ .n_reg = 0x0c,
+ .user_reg = 0x10,
+ .config_reg = 0x14,
+ .config_val = 0x430405d,
+ .status_reg = 0x1c,
+ .lock_bit = 16,
+
+ .user_val = 0x8,
+ .user_vco_mask = 0x100000,
+ .low_vco_max_rate = 1248000000,
+ .min_rate = 537600000UL,
+ .max_rate = 2900000000UL,
+};
+
+static const struct of_device_id qcom_hfpll_match_table[] = {
+ { .compatible = "qcom,hfpll" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qcom_hfpll_match_table);
+
+static struct regmap_config hfpll_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x30,
+ .fast_io = true,
+};
+
+static int qcom_hfpll_probe(struct platform_device *pdev)
+{
+ struct clk *clk;
+ struct resource *res;
+ struct device *dev = &pdev->dev;
+ void __iomem *base;
+ struct regmap *regmap;
+ struct clk_hfpll *h;
+ struct clk_init_data init = {
+ .parent_names = (const char *[]){ "xo" },
+ .num_parents = 1,
+ .ops = &clk_ops_hfpll,
+ };
+
+ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
+ if (!h)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = devm_regmap_init_mmio(&pdev->dev, base, &hfpll_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ if (of_property_read_string_index(dev->of_node, "clock-output-names",
+ 0, &init.name))
+ return -ENODEV;
+
+ h->d = &hdata;
+ h->clkr.hw.init = &init;
+ spin_lock_init(&h->lock);
+
+ clk = devm_clk_register_regmap(&pdev->dev, &h->clkr);
+
+ return PTR_ERR_OR_ZERO(clk);
+}
+
+static struct platform_driver qcom_hfpll_driver = {
+ .probe = qcom_hfpll_probe,
+ .driver = {
+ .name = "qcom-hfpll",
+ .owner = THIS_MODULE,
+ .of_match_table = qcom_hfpll_match_table,
+ },
+};
+module_platform_driver(qcom_hfpll_driver);
+
+MODULE_DESCRIPTION("QCOM HFPLL Clock Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:qcom-hfpll");
diff --git a/drivers/clk/qcom/kpss-xcc.c b/drivers/clk/qcom/kpss-xcc.c
new file mode 100644
index 000000000000..9c49496afa6a
--- /dev/null
+++ b/drivers/clk/qcom/kpss-xcc.c
@@ -0,0 +1,94 @@
+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+
+static const char *aux_parents[] = {
+ "pll8_vote",
+ "pxo",
+};
+
+static unsigned int aux_parent_map[] = {
+ 3,
+ 0,
+};
+
+static const struct of_device_id kpss_xcc_match_table[] = {
+ { .compatible = "qcom,kpss-acc-v1", .data = (void *)1UL },
+ { .compatible = "qcom,kpss-gcc" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, kpss_xcc_match_table);
+
+static int kpss_xcc_driver_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *id;
+ struct clk *clk;
+ struct resource *res;
+ void __iomem *base;
+ const char *name;
+
+ id = of_match_device(kpss_xcc_match_table, &pdev->dev);
+ if (!id)
+ return -ENODEV;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ if (id->data) {
+ if (of_property_read_string_index(pdev->dev.of_node,
+ "clock-output-names", 0, &name))
+ return -ENODEV;
+ base += 0x14;
+ } else {
+ name = "acpu_l2_aux";
+ base += 0x28;
+ }
+
+ clk = clk_register_mux_table(&pdev->dev, name, aux_parents,
+ ARRAY_SIZE(aux_parents), 0, base, 0, 0x3,
+ 0, aux_parent_map, NULL);
+
+ platform_set_drvdata(pdev, clk);
+
+ return PTR_ERR_OR_ZERO(clk);
+}
+
+static int kpss_xcc_driver_remove(struct platform_device *pdev)
+{
+ clk_unregister_mux(platform_get_drvdata(pdev));
+ return 0;
+}
+
+static struct platform_driver kpss_xcc_driver = {
+ .probe = kpss_xcc_driver_probe,
+ .remove = kpss_xcc_driver_remove,
+ .driver = {
+ .name = "kpss-xcc",
+ .of_match_table = kpss_xcc_match_table,
+ .owner = THIS_MODULE,
+ },
+};
+module_platform_driver(kpss_xcc_driver);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/qcom/krait-cc.c b/drivers/clk/qcom/krait-cc.c
new file mode 100644
index 000000000000..8c5d24e2c6cb
--- /dev/null
+++ b/drivers/clk/qcom/krait-cc.c
@@ -0,0 +1,357 @@
+/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+
+#include <asm/smp_plat.h>
+
+#include "clk-krait.h"
+
+static unsigned int sec_mux_map[] = {
+ 2,
+ 0,
+};
+
+static unsigned int pri_mux_map[] = {
+ 1,
+ 2,
+ 0,
+};
+
+static int
+krait_add_div(struct device *dev, int id, const char *s, unsigned offset)
+{
+ struct krait_div2_clk *div;
+ struct clk_init_data init = {
+ .num_parents = 1,
+ .ops = &krait_div2_clk_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ };
+ const char *p_names[1];
+ struct clk *clk;
+
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return -ENOMEM;
+
+ div->width = 2;
+ div->shift = 6;
+ div->lpl = id >= 0;
+ div->offset = offset;
+ div->hw.init = &init;
+
+ init.name = kasprintf(GFP_KERNEL, "hfpll%s_div", s);
+ if (!init.name)
+ return -ENOMEM;
+
+ init.parent_names = p_names;
+ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s);
+ if (!p_names[0]) {
+ kfree(init.name);
+ return -ENOMEM;
+ }
+
+ clk = devm_clk_register(dev, &div->hw);
+ kfree(p_names[0]);
+ kfree(init.name);
+
+ return PTR_ERR_OR_ZERO(clk);
+}
+
+static int
+krait_add_sec_mux(struct device *dev, int id, const char *s, unsigned offset,
+ bool unique_aux)
+{
+ struct krait_mux_clk *mux;
+ static const char *sec_mux_list[] = {
+ "acpu_aux",
+ "qsb",
+ };
+ struct clk_init_data init = {
+ .parent_names = sec_mux_list,
+ .num_parents = ARRAY_SIZE(sec_mux_list),
+ .ops = &krait_mux_clk_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ };
+ struct clk *clk;
+
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return -ENOMEM;
+
+ mux->offset = offset;
+ mux->lpl = id >= 0;
+ mux->has_safe_parent = true;
+ mux->safe_sel = 2;
+ mux->mask = 0x3;
+ mux->shift = 2;
+ mux->parent_map = sec_mux_map;
+ mux->hw.init = &init;
+
+ init.name = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s);
+ if (!init.name)
+ return -ENOMEM;
+
+ if (unique_aux) {
+ sec_mux_list[0] = kasprintf(GFP_KERNEL, "acpu%s_aux", s);
+ if (!sec_mux_list[0]) {
+ clk = ERR_PTR(-ENOMEM);
+ goto err_aux;
+ }
+ }
+
+ clk = devm_clk_register(dev, &mux->hw);
+
+ if (unique_aux)
+ kfree(sec_mux_list[0]);
+err_aux:
+ kfree(init.name);
+ return PTR_ERR_OR_ZERO(clk);
+}
+
+static struct clk *
+krait_add_pri_mux(struct device *dev, int id, const char *s, unsigned offset)
+{
+ struct krait_mux_clk *mux;
+ const char *p_names[3];
+ struct clk_init_data init = {
+ .parent_names = p_names,
+ .num_parents = ARRAY_SIZE(p_names),
+ .ops = &krait_mux_clk_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ };
+ struct clk *clk;
+
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+
+ mux->has_safe_parent = true;
+ mux->safe_sel = 0;
+ mux->mask = 0x3;
+ mux->shift = 0;
+ mux->offset = offset;
+ mux->lpl = id >= 0;
+ mux->parent_map = pri_mux_map;
+ mux->hw.init = &init;
+
+ init.name = kasprintf(GFP_KERNEL, "krait%s_pri_mux", s);
+ if (!init.name)
+ return ERR_PTR(-ENOMEM);
+
+ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s);
+ if (!p_names[0]) {
+ clk = ERR_PTR(-ENOMEM);
+ goto err_p0;
+ }
+
+ p_names[1] = kasprintf(GFP_KERNEL, "hfpll%s_div", s);
+ if (!p_names[1]) {
+ clk = ERR_PTR(-ENOMEM);
+ goto err_p1;
+ }
+
+ p_names[2] = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s);
+ if (!p_names[2]) {
+ clk = ERR_PTR(-ENOMEM);
+ goto err_p2;
+ }
+
+ clk = devm_clk_register(dev, &mux->hw);
+
+ kfree(p_names[2]);
+err_p2:
+ kfree(p_names[1]);
+err_p1:
+ kfree(p_names[0]);
+err_p0:
+ kfree(init.name);
+ return clk;
+}
+
+/* id < 0 for L2, otherwise id == physical CPU number */
+static struct clk *krait_add_clks(struct device *dev, int id, bool unique_aux)
+{
+ int ret;
+ unsigned offset;
+ void *p = NULL;
+ const char *s;
+ struct clk *clk;
+
+ if (id >= 0) {
+ offset = 0x4501 + (0x1000 * id);
+ s = p = kasprintf(GFP_KERNEL, "%d", id);
+ if (!s)
+ return ERR_PTR(-ENOMEM);
+ } else {
+ offset = 0x500;
+ s = "_l2";
+ }
+
+ ret = krait_add_div(dev, id, s, offset);
+ if (ret) {
+ clk = ERR_PTR(ret);
+ goto err;
+ }
+
+ ret = krait_add_sec_mux(dev, id, s, offset, unique_aux);
+ if (ret) {
+ clk = ERR_PTR(ret);
+ goto err;
+ }
+
+ clk = krait_add_pri_mux(dev, id, s, offset);
+err:
+ kfree(p);
+ return clk;
+}
+
+static struct clk *krait_of_get(struct of_phandle_args *clkspec, void *data)
+{
+ unsigned int idx = clkspec->args[0];
+ struct clk **clks = data;
+
+ if (idx >= 5) {
+ pr_err("%s: invalid clock index %d\n", __func__, idx);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return clks[idx] ? : ERR_PTR(-ENODEV);
+}
+
+static const struct of_device_id krait_cc_match_table[] = {
+ { .compatible = "qcom,krait-cc-v1", (void *)1UL },
+ { .compatible = "qcom,krait-cc-v2" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, krait_cc_match_table);
+
+static int krait_cc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *id;
+ unsigned long cur_rate, aux_rate;
+ int i, cpu;
+ struct clk *clk;
+ struct clk **clks;
+ struct clk *l2_pri_mux_clk;
+
+ id = of_match_device(krait_cc_match_table, &pdev->dev);
+ if (!id)
+ return -ENODEV;
+
+ /* Rate is 1 because 0 causes problems for __clk_mux_determine_rate */
+ clk = clk_register_fixed_rate(dev, "qsb", NULL, CLK_IS_ROOT, 1);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ if (!id->data) {
+ clk = clk_register_fixed_factor(&pdev->dev, "acpu_aux",
+ "gpll0_vote", 0, 1, 2);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ }
+
+ /* Krait configurations have at most 4 CPUs and one L2 */
+ clks = devm_kcalloc(dev, 5, sizeof(*clks), GFP_KERNEL);
+ if (!clks)
+ return -ENOMEM;
+
+ for_each_possible_cpu(i) {
+ cpu = cpu_logical_map(i);
+ clk = krait_add_clks(dev, cpu, id->data);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ clks[cpu] = clk;
+ }
+
+ l2_pri_mux_clk = krait_add_clks(dev, -1, id->data);
+ if (IS_ERR(l2_pri_mux_clk))
+ return PTR_ERR(l2_pri_mux_clk);
+ clks[4] = l2_pri_mux_clk;
+
+ /*
+ * We don't want the CPU or L2 clocks to be turned off at late init
+ * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the
+ * refcount of these clocks. Any cpufreq/hotplug manager can assume
+ * that the clocks have already been prepared and enabled by the time
+ * they take over.
+ */
+ for_each_online_cpu(i) {
+ cpu = cpu_logical_map(i);
+ clk_prepare_enable(l2_pri_mux_clk);
+ WARN(clk_prepare_enable(clks[cpu]),
+ "Unable to turn on CPU%d clock", cpu);
+ }
+
+ /*
+ * Force reinit of HFPLLs and muxes to overwrite any potential
+ * incorrect configuration of HFPLLs and muxes by the bootloader.
+ * While at it, also make sure the cores are running at known rates
+ * and print the current rate.
+ *
+ * The clocks are set to aux clock rate first to make sure the
+ * secondary mux is not sourcing off of QSB. The rate is then set to
+ * two different rates to force a HFPLL reinit under all
+ * circumstances.
+ */
+ cur_rate = clk_get_rate(l2_pri_mux_clk);
+ aux_rate = 384000000;
+ if (cur_rate == 1) {
+ pr_info("L2 @ QSB rate. Forcing new rate.\n");
+ cur_rate = aux_rate;
+ }
+ clk_set_rate(l2_pri_mux_clk, aux_rate);
+ clk_set_rate(l2_pri_mux_clk, 2);
+ clk_set_rate(l2_pri_mux_clk, cur_rate);
+ pr_info("L2 @ %lu KHz\n", clk_get_rate(l2_pri_mux_clk) / 1000);
+ for_each_possible_cpu(i) {
+ cpu = cpu_logical_map(i);
+ clk = clks[cpu];
+ cur_rate = clk_get_rate(clk);
+ if (cur_rate == 1) {
+ pr_info("CPU%d @ QSB rate. Forcing new rate.\n", i);
+ cur_rate = aux_rate;
+ }
+ clk_set_rate(clk, aux_rate);
+ clk_set_rate(clk, 2);
+ clk_set_rate(clk, cur_rate);
+ pr_info("CPU%d @ %lu KHz\n", i, clk_get_rate(clk) / 1000);
+ }
+
+ of_clk_add_provider(dev->of_node, krait_of_get, clks);
+
+ return 0;
+}
+
+static struct platform_driver krait_cc_driver = {
+ .probe = krait_cc_probe,
+ .driver = {
+ .name = "clock-krait",
+ .of_match_table = krait_cc_match_table,
+ .owner = THIS_MODULE,
+ },
+};
+module_platform_driver(krait_cc_driver);
+
+MODULE_DESCRIPTION("Krait CPU Clock Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 83a75dc84761..afc27a434fbf 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -129,6 +129,15 @@ config ARM_OMAP2PLUS_CPUFREQ
depends on ARCH_OMAP2PLUS
default ARCH_OMAP2PLUS
+config ARM_QCOM_CPUFREQ
+ tristate "Qualcomm based"
+ depends on ARCH_QCOM
+ select PM_OPP
+ help
+ This adds the CPUFreq driver for Qualcomm SoC based boards.
+
+ If in doubt, say N.
+
config ARM_S3C_CPUFREQ
bool
help
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 40c53dc1937e..e18591f28dca 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o
obj-$(CONFIG_ARM_INTEGRATOR) += integrator-cpufreq.o
obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += kirkwood-cpufreq.o
obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o
+obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o
obj-$(CONFIG_PXA25x) += pxa2xx-cpufreq.o
obj-$(CONFIG_PXA27x) += pxa2xx-cpufreq.o
obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o
diff --git a/drivers/cpufreq/qcom-cpufreq.c b/drivers/cpufreq/qcom-cpufreq.c
new file mode 100644
index 000000000000..aa8eb97144b6
--- /dev/null
+++ b/drivers/cpufreq/qcom-cpufreq.c
@@ -0,0 +1,199 @@
+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/cpu.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/pm_opp.h>
+#include <linux/init.h>
+
+static void __init get_krait_bin_format_a(int *speed, int *pvs, int *pvs_ver)
+{
+ void __iomem *base;
+ u32 pte_efuse;
+
+ *speed = *pvs = *pvs_ver = 0;
+
+ base = ioremap(0x007000c0, 4);
+ if (!base) {
+ pr_warn("Unable to read efuse data. Defaulting to 0!\n");
+ return;
+ }
+
+ pte_efuse = readl_relaxed(base);
+ iounmap(base);
+
+ *speed = pte_efuse & 0xf;
+ if (*speed == 0xf)
+ *speed = (pte_efuse >> 4) & 0xf;
+
+ if (*speed == 0xf) {
+ *speed = 0;
+ pr_warn("Speed bin: Defaulting to %d\n", *speed);
+ } else {
+ pr_info("Speed bin: %d\n", *speed);
+ }
+
+ *pvs = (pte_efuse >> 10) & 0x7;
+ if (*pvs == 0x7)
+ *pvs = (pte_efuse >> 13) & 0x7;
+
+ if (*pvs == 0x7) {
+ *pvs = 0;
+ pr_warn("PVS bin: Defaulting to %d\n", *pvs);
+ } else {
+ pr_info("PVS bin: %d\n", *pvs);
+ }
+}
+
+static void __init get_krait_bin_format_b(int *speed, int *pvs, int *pvs_ver)
+{
+ u32 pte_efuse, redundant_sel;
+ void __iomem *base;
+
+ *speed = 0;
+ *pvs = 0;
+ *pvs_ver = 0;
+
+ base = ioremap(0xfc4b80b0, 8);
+ if (!base) {
+ pr_warn("Unable to read efuse data. Defaulting to 0!\n");
+ return;
+ }
+
+ pte_efuse = readl_relaxed(base);
+ redundant_sel = (pte_efuse >> 24) & 0x7;
+ *speed = pte_efuse & 0x7;
+ /* 4 bits of PVS are in efuse register bits 31, 8-6. */
+ *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7);
+ *pvs_ver = (pte_efuse >> 4) & 0x3;
+
+ switch (redundant_sel) {
+ case 1:
+ *speed = (pte_efuse >> 27) & 0xf;
+ break;
+ case 2:
+ *pvs = (pte_efuse >> 27) & 0xf;
+ break;
+ }
+
+ /* Check SPEED_BIN_BLOW_STATUS */
+ if (pte_efuse & BIT(3)) {
+ pr_info("Speed bin: %d\n", *speed);
+ } else {
+ pr_warn("Speed bin not set. Defaulting to 0!\n");
+ *speed = 0;
+ }
+
+ /* Check PVS_BLOW_STATUS */
+ pte_efuse = readl_relaxed(base + 0x4) & BIT(21);
+ if (pte_efuse) {
+ pr_info("PVS bin: %d\n", *pvs);
+ } else {
+ pr_warn("PVS bin not set. Defaulting to 0!\n");
+ *pvs = 0;
+ }
+
+ pr_info("PVS version: %d\n", *pvs_ver);
+ iounmap(base);
+}
+
+static int __init qcom_cpufreq_populate_opps(void)
+{
+ int len, num_rows, i, k;
+ char table_name[] = "qcom,speedXX-pvsXX-bin-vXX";
+ struct device_node *np;
+ struct device *dev;
+ int cpu = 0;
+ int speed, pvs, pvs_ver;
+ int cols;
+
+ np = of_find_node_by_name(NULL, "qcom,pvs");
+ if (!np)
+ pr_warn("Can't find PVS node\n");
+
+ if (of_property_read_bool(np, "qcom,pvs-format-a")) {
+ get_krait_bin_format_a(&speed, &pvs, &pvs_ver);
+ cols = 2;
+ } else {
+ get_krait_bin_format_b(&speed, &pvs, &pvs_ver);
+ cols = 3;
+ }
+
+ snprintf(table_name, sizeof(table_name),
+ "qcom,speed%d-pvs%d-bin-v%d", speed, pvs, pvs_ver);
+again:
+ dev = get_cpu_device(cpu);
+ if (!dev)
+ return -ENODEV;
+
+ if (!of_find_property(np, table_name, &len))
+ return -EINVAL;
+
+ len /= sizeof(u32);
+ if (len % cols || len == 0)
+ return -EINVAL;
+
+ num_rows = len / cols;
+
+ for (i = 0, k = 0; i < num_rows; i++) {
+ u32 freq, volt;
+
+ of_property_read_u32_index(np, table_name, k++, &freq);
+ of_property_read_u32_index(np, table_name, k++, &volt);
+ while (k % cols)
+ k++; /* Skip uA entries if present */
+ if (dev_pm_opp_add(dev, freq, volt))
+ pr_warn("failed to add OPP %u\n", freq);
+ }
+
+ if (cpu++ < num_possible_cpus())
+ goto again;
+
+ return 0;
+}
+
+static int __init qcom_cpufreq_driver_init(void)
+{
+ struct platform_device_info devinfo = { .name = "cpufreq-generic", };
+ struct device *cpu_dev;
+ struct device_node *np;
+ struct platform_device *pdev;
+
+ cpu_dev = get_cpu_device(0);
+ if (!cpu_dev)
+ return -ENODEV;
+
+ np = of_node_get(cpu_dev->of_node);
+ if (!np)
+ return -ENOENT;
+
+ if (!of_device_is_compatible(np, "qcom,krait")) {
+ of_node_put(np);
+ return -ENODEV;
+ }
+ of_node_put(np);
+
+ qcom_cpufreq_populate_opps();
+ pdev = platform_device_register_full(&devinfo);
+
+ return PTR_ERR_OR_ZERO(pdev);
+}
+module_init(qcom_cpufreq_driver_init);
+
+MODULE_DESCRIPTION("Qualcomm CPUfreq driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm
index 8c16ab20fb15..13c7c1fe8306 100644
--- a/drivers/cpuidle/Kconfig.arm
+++ b/drivers/cpuidle/Kconfig.arm
@@ -63,3 +63,10 @@ config ARM_MVEBU_V7_CPUIDLE
depends on ARCH_MVEBU
help
Select this to enable cpuidle on Armada 370, 38x and XP processors.
+
+config ARM_QCOM_CPUIDLE
+ bool "CPU Idle drivers for Qualcomm processors"
+ depends on QCOM_PM
+ select DT_IDLE_STATES
+ help
+ Select this to enable cpuidle for QCOM processors
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index 4d177b916f75..6c222d536005 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_ARM_ZYNQ_CPUIDLE) += cpuidle-zynq.o
obj-$(CONFIG_ARM_U8500_CPUIDLE) += cpuidle-ux500.o
obj-$(CONFIG_ARM_AT91_CPUIDLE) += cpuidle-at91.o
obj-$(CONFIG_ARM_EXYNOS_CPUIDLE) += cpuidle-exynos.o
+obj-$(CONFIG_ARM_QCOM_CPUIDLE) += cpuidle-qcom.o
###############################################################################
# MIPS drivers
diff --git a/drivers/cpuidle/cpuidle-qcom.c b/drivers/cpuidle/cpuidle-qcom.c
new file mode 100644
index 000000000000..f3e033b70f35
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-qcom.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014, Linaro Limited.
+ *
+ * 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/cpu_pm.h>
+#include <linux/cpuidle.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <soc/qcom/pm.h>
+#include "dt_idle_states.h"
+
+static void (*qcom_idle_enter)(enum msm_pm_sleep_mode);
+
+static int qcom_lpm_enter_wfi(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int index)
+{
+ qcom_idle_enter(MSM_PM_SLEEP_MODE_WFI);
+
+ return index;
+}
+
+static int qcom_lpm_enter_spc(struct cpuidle_device *dev,
+ struct cpuidle_driver *drv, int index)
+{
+ cpu_pm_enter();
+ qcom_idle_enter(MSM_PM_SLEEP_MODE_SPC);
+ cpu_pm_exit();
+
+ return index;
+}
+
+static struct cpuidle_driver qcom_cpuidle_driver = {
+ .name = "qcom_cpuidle",
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id qcom_idle_state_match[] __initconst = {
+ { .compatible = "qcom,idle-state-wfi", .data = qcom_lpm_enter_wfi },
+ { .compatible = "qcom,idle-state-spc", .data = qcom_lpm_enter_spc },
+ { },
+};
+
+static int qcom_cpuidle_probe(struct platform_device *pdev)
+{
+ struct cpuidle_driver *drv = &qcom_cpuidle_driver;
+ int ret;
+
+ qcom_idle_enter = (void *)(pdev->dev.platform_data);
+
+ /* Probe for other states including platform WFI */
+ ret = dt_init_idle_driver(drv, qcom_idle_state_match, 0);
+ if (ret <= 0) {
+ pr_err("%s: No cpuidle state found.\n", __func__);
+ return ret;
+ }
+
+ ret = cpuidle_register(drv, NULL);
+ if (ret) {
+ pr_err("%s: failed to register cpuidle driver\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver qcom_cpuidle_plat_driver = {
+ .probe = qcom_cpuidle_probe,
+ .driver = {
+ .name = "qcom_cpuidle",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(qcom_cpuidle_plat_driver);
diff --git a/drivers/dma/qcom_bam_dma.c b/drivers/dma/qcom_bam_dma.c
index 7a4bbb0f80a5..777afd2f094d 100644
--- a/drivers/dma/qcom_bam_dma.c
+++ b/drivers/dma/qcom_bam_dma.c
@@ -79,35 +79,97 @@ struct bam_async_desc {
struct bam_desc_hw desc[0];
};
-#define BAM_CTRL 0x0000
-#define BAM_REVISION 0x0004
-#define BAM_SW_REVISION 0x0080
-#define BAM_NUM_PIPES 0x003C
-#define BAM_TIMER 0x0040
-#define BAM_TIMER_CTRL 0x0044
-#define BAM_DESC_CNT_TRSHLD 0x0008
-#define BAM_IRQ_SRCS 0x000C
-#define BAM_IRQ_SRCS_MSK 0x0010
-#define BAM_IRQ_SRCS_UNMASKED 0x0030
-#define BAM_IRQ_STTS 0x0014
-#define BAM_IRQ_CLR 0x0018
-#define BAM_IRQ_EN 0x001C
-#define BAM_CNFG_BITS 0x007C
-#define BAM_IRQ_SRCS_EE(ee) (0x0800 + ((ee) * 0x80))
-#define BAM_IRQ_SRCS_MSK_EE(ee) (0x0804 + ((ee) * 0x80))
-#define BAM_P_CTRL(pipe) (0x1000 + ((pipe) * 0x1000))
-#define BAM_P_RST(pipe) (0x1004 + ((pipe) * 0x1000))
-#define BAM_P_HALT(pipe) (0x1008 + ((pipe) * 0x1000))
-#define BAM_P_IRQ_STTS(pipe) (0x1010 + ((pipe) * 0x1000))
-#define BAM_P_IRQ_CLR(pipe) (0x1014 + ((pipe) * 0x1000))
-#define BAM_P_IRQ_EN(pipe) (0x1018 + ((pipe) * 0x1000))
-#define BAM_P_EVNT_DEST_ADDR(pipe) (0x182C + ((pipe) * 0x1000))
-#define BAM_P_EVNT_REG(pipe) (0x1818 + ((pipe) * 0x1000))
-#define BAM_P_SW_OFSTS(pipe) (0x1800 + ((pipe) * 0x1000))
-#define BAM_P_DATA_FIFO_ADDR(pipe) (0x1824 + ((pipe) * 0x1000))
-#define BAM_P_DESC_FIFO_ADDR(pipe) (0x181C + ((pipe) * 0x1000))
-#define BAM_P_EVNT_TRSHLD(pipe) (0x1828 + ((pipe) * 0x1000))
-#define BAM_P_FIFO_SIZES(pipe) (0x1820 + ((pipe) * 0x1000))
+enum bam_reg {
+ BAM_CTRL,
+ BAM_REVISION,
+ BAM_NUM_PIPES,
+ BAM_DESC_CNT_TRSHLD,
+ BAM_IRQ_SRCS,
+ BAM_IRQ_SRCS_MSK,
+ BAM_IRQ_SRCS_UNMASKED,
+ BAM_IRQ_STTS,
+ BAM_IRQ_CLR,
+ BAM_IRQ_EN,
+ BAM_CNFG_BITS,
+ BAM_IRQ_SRCS_EE,
+ BAM_IRQ_SRCS_MSK_EE,
+ BAM_P_CTRL,
+ BAM_P_RST,
+ BAM_P_HALT,
+ BAM_P_IRQ_STTS,
+ BAM_P_IRQ_CLR,
+ BAM_P_IRQ_EN,
+ BAM_P_EVNT_DEST_ADDR,
+ BAM_P_EVNT_REG,
+ BAM_P_SW_OFSTS,
+ BAM_P_DATA_FIFO_ADDR,
+ BAM_P_DESC_FIFO_ADDR,
+ BAM_P_EVNT_GEN_TRSHLD,
+ BAM_P_FIFO_SIZES,
+};
+
+struct reg_offset_data {
+ u32 base_offset;
+ unsigned int pipe_mult, evnt_mult, ee_mult;
+};
+
+static const struct reg_offset_data bam_v1_3_reg_info[] = {
+ [BAM_CTRL] = { 0x0F80, 0x00, 0x00, 0x00 },
+ [BAM_REVISION] = { 0x0F84, 0x00, 0x00, 0x00 },
+ [BAM_NUM_PIPES] = { 0x0FBC, 0x00, 0x00, 0x00 },
+ [BAM_DESC_CNT_TRSHLD] = { 0x0F88, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_SRCS] = { 0x0F8C, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_SRCS_MSK] = { 0x0F90, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_SRCS_UNMASKED] = { 0x0FB0, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_STTS] = { 0x0F94, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_CLR] = { 0x0F98, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_EN] = { 0x0F9C, 0x00, 0x00, 0x00 },
+ [BAM_CNFG_BITS] = { 0x0FFC, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_SRCS_EE] = { 0x1800, 0x00, 0x00, 0x80 },
+ [BAM_IRQ_SRCS_MSK_EE] = { 0x1804, 0x00, 0x00, 0x80 },
+ [BAM_P_CTRL] = { 0x0000, 0x80, 0x00, 0x00 },
+ [BAM_P_RST] = { 0x0004, 0x80, 0x00, 0x00 },
+ [BAM_P_HALT] = { 0x0008, 0x80, 0x00, 0x00 },
+ [BAM_P_IRQ_STTS] = { 0x0010, 0x80, 0x00, 0x00 },
+ [BAM_P_IRQ_CLR] = { 0x0014, 0x80, 0x00, 0x00 },
+ [BAM_P_IRQ_EN] = { 0x0018, 0x80, 0x00, 0x00 },
+ [BAM_P_EVNT_DEST_ADDR] = { 0x102C, 0x00, 0x40, 0x00 },
+ [BAM_P_EVNT_REG] = { 0x1018, 0x00, 0x40, 0x00 },
+ [BAM_P_SW_OFSTS] = { 0x1000, 0x00, 0x40, 0x00 },
+ [BAM_P_DATA_FIFO_ADDR] = { 0x1024, 0x00, 0x40, 0x00 },
+ [BAM_P_DESC_FIFO_ADDR] = { 0x101C, 0x00, 0x40, 0x00 },
+ [BAM_P_EVNT_GEN_TRSHLD] = { 0x1028, 0x00, 0x40, 0x00 },
+ [BAM_P_FIFO_SIZES] = { 0x1020, 0x00, 0x40, 0x00 },
+};
+
+static const struct reg_offset_data bam_v1_4_reg_info[] = {
+ [BAM_CTRL] = { 0x0000, 0x00, 0x00, 0x00 },
+ [BAM_REVISION] = { 0x0004, 0x00, 0x00, 0x00 },
+ [BAM_NUM_PIPES] = { 0x003C, 0x00, 0x00, 0x00 },
+ [BAM_DESC_CNT_TRSHLD] = { 0x0008, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_SRCS] = { 0x000C, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_SRCS_MSK] = { 0x0010, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_SRCS_UNMASKED] = { 0x0030, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_STTS] = { 0x0014, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_CLR] = { 0x0018, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_EN] = { 0x001C, 0x00, 0x00, 0x00 },
+ [BAM_CNFG_BITS] = { 0x007C, 0x00, 0x00, 0x00 },
+ [BAM_IRQ_SRCS_EE] = { 0x0800, 0x00, 0x00, 0x80 },
+ [BAM_IRQ_SRCS_MSK_EE] = { 0x0804, 0x00, 0x00, 0x80 },
+ [BAM_P_CTRL] = { 0x1000, 0x1000, 0x00, 0x00 },
+ [BAM_P_RST] = { 0x1004, 0x1000, 0x00, 0x00 },
+ [BAM_P_HALT] = { 0x1008, 0x1000, 0x00, 0x00 },
+ [BAM_P_IRQ_STTS] = { 0x1010, 0x1000, 0x00, 0x00 },
+ [BAM_P_IRQ_CLR] = { 0x1014, 0x1000, 0x00, 0x00 },
+ [BAM_P_IRQ_EN] = { 0x1018, 0x1000, 0x00, 0x00 },
+ [BAM_P_EVNT_DEST_ADDR] = { 0x102C, 0x00, 0x1000, 0x00 },
+ [BAM_P_EVNT_REG] = { 0x1018, 0x00, 0x1000, 0x00 },
+ [BAM_P_SW_OFSTS] = { 0x1000, 0x00, 0x1000, 0x00 },
+ [BAM_P_DATA_FIFO_ADDR] = { 0x1824, 0x00, 0x1000, 0x00 },
+ [BAM_P_DESC_FIFO_ADDR] = { 0x181C, 0x00, 0x1000, 0x00 },
+ [BAM_P_EVNT_GEN_TRSHLD] = { 0x1828, 0x00, 0x1000, 0x00 },
+ [BAM_P_FIFO_SIZES] = { 0x1820, 0x00, 0x1000, 0x00 },
+};
/* BAM CTRL */
#define BAM_SW_RST BIT(0)
@@ -297,6 +359,8 @@ struct bam_device {
/* execution environment ID, from DT */
u32 ee;
+ const struct reg_offset_data *layout;
+
struct clk *bamclk;
int irq;
@@ -305,6 +369,23 @@ struct bam_device {
};
/**
+ * bam_addr - returns BAM register address
+ * @bdev: bam device
+ * @pipe: pipe instance (ignored when register doesn't have multiple instances)
+ * @reg: register enum
+ */
+static inline void __iomem *bam_addr(struct bam_device *bdev, u32 pipe,
+ enum bam_reg reg)
+{
+ const struct reg_offset_data r = bdev->layout[reg];
+
+ return bdev->regs + r.base_offset +
+ r.pipe_mult * pipe +
+ r.evnt_mult * pipe +
+ r.ee_mult * bdev->ee;
+}
+
+/**
* bam_reset_channel - Reset individual BAM DMA channel
* @bchan: bam channel
*
@@ -317,8 +398,8 @@ static void bam_reset_channel(struct bam_chan *bchan)
lockdep_assert_held(&bchan->vc.lock);
/* reset channel */
- writel_relaxed(1, bdev->regs + BAM_P_RST(bchan->id));
- writel_relaxed(0, bdev->regs + BAM_P_RST(bchan->id));
+ writel_relaxed(1, bam_addr(bdev, bchan->id, BAM_P_RST));
+ writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_RST));
/* don't allow cpu to reorder BAM register accesses done after this */
wmb();
@@ -347,17 +428,18 @@ static void bam_chan_init_hw(struct bam_chan *bchan,
* because we allocated 1 more descriptor (8 bytes) than we can use
*/
writel_relaxed(ALIGN(bchan->fifo_phys, sizeof(struct bam_desc_hw)),
- bdev->regs + BAM_P_DESC_FIFO_ADDR(bchan->id));
- writel_relaxed(BAM_DESC_FIFO_SIZE, bdev->regs +
- BAM_P_FIFO_SIZES(bchan->id));
+ bam_addr(bdev, bchan->id, BAM_P_DESC_FIFO_ADDR));
+ writel_relaxed(BAM_DESC_FIFO_SIZE,
+ bam_addr(bdev, bchan->id, BAM_P_FIFO_SIZES));
/* enable the per pipe interrupts, enable EOT, ERR, and INT irqs */
- writel_relaxed(P_DEFAULT_IRQS_EN, bdev->regs + BAM_P_IRQ_EN(bchan->id));
+ writel_relaxed(P_DEFAULT_IRQS_EN,
+ bam_addr(bdev, bchan->id, BAM_P_IRQ_EN));
/* unmask the specific pipe and EE combo */
- val = readl_relaxed(bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee));
+ val = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE));
val |= BIT(bchan->id);
- writel_relaxed(val, bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee));
+ writel_relaxed(val, bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE));
/* don't allow cpu to reorder the channel enable done below */
wmb();
@@ -367,7 +449,7 @@ static void bam_chan_init_hw(struct bam_chan *bchan,
if (dir == DMA_DEV_TO_MEM)
val |= P_DIRECTION;
- writel_relaxed(val, bdev->regs + BAM_P_CTRL(bchan->id));
+ writel_relaxed(val, bam_addr(bdev, bchan->id, BAM_P_CTRL));
bchan->initialized = 1;
@@ -432,12 +514,12 @@ static void bam_free_chan(struct dma_chan *chan)
bchan->fifo_virt = NULL;
/* mask irq for pipe/channel */
- val = readl_relaxed(bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee));
+ val = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE));
val &= ~BIT(bchan->id);
- writel_relaxed(val, bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee));
+ writel_relaxed(val, bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE));
/* disable irq */
- writel_relaxed(0, bdev->regs + BAM_P_IRQ_EN(bchan->id));
+ writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_IRQ_EN));
}
/**
@@ -583,14 +665,14 @@ static int bam_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
switch (cmd) {
case DMA_PAUSE:
spin_lock_irqsave(&bchan->vc.lock, flag);
- writel_relaxed(1, bdev->regs + BAM_P_HALT(bchan->id));
+ writel_relaxed(1, bam_addr(bdev, bchan->id, BAM_P_HALT));
bchan->paused = 1;
spin_unlock_irqrestore(&bchan->vc.lock, flag);
break;
case DMA_RESUME:
spin_lock_irqsave(&bchan->vc.lock, flag);
- writel_relaxed(0, bdev->regs + BAM_P_HALT(bchan->id));
+ writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_HALT));
bchan->paused = 0;
spin_unlock_irqrestore(&bchan->vc.lock, flag);
break;
@@ -626,7 +708,7 @@ static u32 process_channel_irqs(struct bam_device *bdev)
unsigned long flags;
struct bam_async_desc *async_desc;
- srcs = readl_relaxed(bdev->regs + BAM_IRQ_SRCS_EE(bdev->ee));
+ srcs = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_EE));
/* return early if no pipe/channel interrupts are present */
if (!(srcs & P_IRQ))
@@ -639,11 +721,9 @@ static u32 process_channel_irqs(struct bam_device *bdev)
continue;
/* clear pipe irq */
- pipe_stts = readl_relaxed(bdev->regs +
- BAM_P_IRQ_STTS(i));
+ pipe_stts = readl_relaxed(bam_addr(bdev, i, BAM_P_IRQ_STTS));
- writel_relaxed(pipe_stts, bdev->regs +
- BAM_P_IRQ_CLR(i));
+ writel_relaxed(pipe_stts, bam_addr(bdev, i, BAM_P_IRQ_CLR));
spin_lock_irqsave(&bchan->vc.lock, flags);
async_desc = bchan->curr_txd;
@@ -694,12 +774,12 @@ static irqreturn_t bam_dma_irq(int irq, void *data)
tasklet_schedule(&bdev->task);
if (srcs & BAM_IRQ)
- clr_mask = readl_relaxed(bdev->regs + BAM_IRQ_STTS);
+ clr_mask = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_STTS));
/* don't allow reorder of the various accesses to the BAM registers */
mb();
- writel_relaxed(clr_mask, bdev->regs + BAM_IRQ_CLR);
+ writel_relaxed(clr_mask, bam_addr(bdev, 0, BAM_IRQ_CLR));
return IRQ_HANDLED;
}
@@ -763,7 +843,7 @@ static void bam_apply_new_config(struct bam_chan *bchan,
else
maxburst = bchan->slave.dst_maxburst;
- writel_relaxed(maxburst, bdev->regs + BAM_DESC_CNT_TRSHLD);
+ writel_relaxed(maxburst, bam_addr(bdev, 0, BAM_DESC_CNT_TRSHLD));
bchan->reconfigure = 0;
}
@@ -830,7 +910,7 @@ static void bam_start_dma(struct bam_chan *bchan)
/* ensure descriptor writes and dma start not reordered */
wmb();
writel_relaxed(bchan->tail * sizeof(struct bam_desc_hw),
- bdev->regs + BAM_P_EVNT_REG(bchan->id));
+ bam_addr(bdev, bchan->id, BAM_P_EVNT_REG));
}
/**
@@ -918,43 +998,44 @@ static int bam_init(struct bam_device *bdev)
u32 val;
/* read revision and configuration information */
- val = readl_relaxed(bdev->regs + BAM_REVISION) >> NUM_EES_SHIFT;
+ val = readl_relaxed(bam_addr(bdev, 0, BAM_REVISION)) >> NUM_EES_SHIFT;
val &= NUM_EES_MASK;
/* check that configured EE is within range */
if (bdev->ee >= val)
return -EINVAL;
- val = readl_relaxed(bdev->regs + BAM_NUM_PIPES);
+ val = readl_relaxed(bam_addr(bdev, 0, BAM_NUM_PIPES));
bdev->num_channels = val & BAM_NUM_PIPES_MASK;
/* s/w reset bam */
/* after reset all pipes are disabled and idle */
- val = readl_relaxed(bdev->regs + BAM_CTRL);
+ val = readl_relaxed(bam_addr(bdev, 0, BAM_CTRL));
val |= BAM_SW_RST;
- writel_relaxed(val, bdev->regs + BAM_CTRL);
+ writel_relaxed(val, bam_addr(bdev, 0, BAM_CTRL));
val &= ~BAM_SW_RST;
- writel_relaxed(val, bdev->regs + BAM_CTRL);
+ writel_relaxed(val, bam_addr(bdev, 0, BAM_CTRL));
/* make sure previous stores are visible before enabling BAM */
wmb();
/* enable bam */
val |= BAM_EN;
- writel_relaxed(val, bdev->regs + BAM_CTRL);
+ writel_relaxed(val, bam_addr(bdev, 0, BAM_CTRL));
/* set descriptor threshhold, start with 4 bytes */
- writel_relaxed(DEFAULT_CNT_THRSHLD, bdev->regs + BAM_DESC_CNT_TRSHLD);
+ writel_relaxed(DEFAULT_CNT_THRSHLD,
+ bam_addr(bdev, 0, BAM_DESC_CNT_TRSHLD));
/* Enable default set of h/w workarounds, ie all except BAM_FULL_PIPE */
- writel_relaxed(BAM_CNFG_BITS_DEFAULT, bdev->regs + BAM_CNFG_BITS);
+ writel_relaxed(BAM_CNFG_BITS_DEFAULT, bam_addr(bdev, 0, BAM_CNFG_BITS));
/* enable irqs for errors */
writel_relaxed(BAM_ERROR_EN | BAM_HRESP_ERR_EN,
- bdev->regs + BAM_IRQ_EN);
+ bam_addr(bdev, 0, BAM_IRQ_EN));
/* unmask global bam interrupt */
- writel_relaxed(BAM_IRQ_MSK, bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee));
+ writel_relaxed(BAM_IRQ_MSK, bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE));
return 0;
}
@@ -969,9 +1050,18 @@ static void bam_channel_init(struct bam_device *bdev, struct bam_chan *bchan,
bchan->vc.desc_free = bam_dma_free_desc;
}
+static const struct of_device_id bam_of_match[] = {
+ { .compatible = "qcom,bam-v1.3.0", .data = &bam_v1_3_reg_info },
+ { .compatible = "qcom,bam-v1.4.0", .data = &bam_v1_4_reg_info },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, bam_of_match);
+
static int bam_dma_probe(struct platform_device *pdev)
{
struct bam_device *bdev;
+ const struct of_device_id *match;
struct resource *iores;
int ret, i;
@@ -981,6 +1071,14 @@ static int bam_dma_probe(struct platform_device *pdev)
bdev->dev = &pdev->dev;
+ match = of_match_node(bam_of_match, pdev->dev.of_node);
+ if (!match) {
+ dev_err(&pdev->dev, "Unsupported BAM module\n");
+ return -ENODEV;
+ }
+
+ bdev->layout = match->data;
+
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
bdev->regs = devm_ioremap_resource(&pdev->dev, iores);
if (IS_ERR(bdev->regs))
@@ -1084,7 +1182,7 @@ static int bam_dma_remove(struct platform_device *pdev)
dma_async_device_unregister(&bdev->common);
/* mask all interrupts for this execution environment */
- writel_relaxed(0, bdev->regs + BAM_IRQ_SRCS_MSK_EE(bdev->ee));
+ writel_relaxed(0, bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE));
devm_free_irq(bdev->dev, bdev->irq, bdev);
@@ -1104,12 +1202,6 @@ static int bam_dma_remove(struct platform_device *pdev)
return 0;
}
-static const struct of_device_id bam_of_match[] = {
- { .compatible = "qcom,bam-v1.4.0", },
- {}
-};
-MODULE_DEVICE_TABLE(of, bam_of_match);
-
static struct platform_driver bam_dma_driver = {
.probe = bam_dma_probe,
.remove = bam_dma_remove,
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index dd5112265cc9..fa495262c819 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -27,6 +27,17 @@ config FSL_PAMU
PAMU can authorize memory access, remap the memory address, and remap I/O
transaction types.
+config QCOM_IOMMU_V0
+ bool "Qualcomm IOMMU v0 Support"
+ depends on ARCH_QCOM
+ select IOMMU_API
+ help
+ Support for the IOMMUs found on certain Qualcomm SOCs.
+ These IOMMUs allow virtualization of the address space used by most
+ cores within the multimedia subsystem.
+
+ If unsure, say N here.
+
# MSM IOMMU support
config MSM_IOMMU
bool "MSM IOMMU Support"
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 16edef74b8ee..35acc41592ed 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -3,6 +3,7 @@ obj-$(CONFIG_IOMMU_API) += iommu-traces.o
obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o
obj-$(CONFIG_OF_IOMMU) += of_iommu.o
obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
+obj-$(CONFIG_QCOM_IOMMU_V0) += qcom_iommu_v0.o
obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o
obj-$(CONFIG_ARM_SMMU) += arm-smmu.o
diff --git a/drivers/iommu/msm_iommu.c b/drivers/iommu/msm_iommu.c
index 6e3dcc289d59..491c3d7cde65 100644
--- a/drivers/iommu/msm_iommu.c
+++ b/drivers/iommu/msm_iommu.c
@@ -423,12 +423,12 @@ static int msm_iommu_map(struct iommu_domain *domain, unsigned long va,
int i = 0;
for (i = 0; i < 16; i++)
*(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION |
- FL_AP_READ | FL_AP_WRITE | FL_TYPE_SECT |
+ FL_AP1 | FL_AP0 | FL_TYPE_SECT |
FL_SHARED | FL_NG | pgprot;
}
if (len == SZ_1M)
- *fl_pte = (pa & 0xFFF00000) | FL_AP_READ | FL_AP_WRITE | FL_NG |
+ *fl_pte = (pa & 0xFFF00000) | FL_AP1 | FL_AP0 | FL_NG |
FL_TYPE_SECT | FL_SHARED | pgprot;
/* Need a 2nd level table */
diff --git a/drivers/iommu/msm_iommu_hw-8xxx.h b/drivers/iommu/msm_iommu_hw-8xxx.h
index fc160101dead..84ba573927d1 100644
--- a/drivers/iommu/msm_iommu_hw-8xxx.h
+++ b/drivers/iommu/msm_iommu_hw-8xxx.h
@@ -61,8 +61,9 @@ do { \
#define FL_TYPE_TABLE (1 << 0)
#define FL_TYPE_SECT (2 << 0)
#define FL_SUPERSECTION (1 << 18)
-#define FL_AP_WRITE (1 << 10)
-#define FL_AP_READ (1 << 11)
+#define FL_AP0 (1 << 10)
+#define FL_AP1 (1 << 11)
+#define FL_AP2 (1 << 15)
#define FL_SHARED (1 << 16)
#define FL_BUFFERABLE (1 << 2)
#define FL_CACHEABLE (1 << 3)
@@ -77,6 +78,7 @@ do { \
#define SL_TYPE_SMALL (2 << 0)
#define SL_AP0 (1 << 4)
#define SL_AP1 (2 << 4)
+#define SL_AP2 (1 << 9)
#define SL_SHARED (1 << 10)
#define SL_BUFFERABLE (1 << 2)
#define SL_CACHEABLE (1 << 3)
diff --git a/drivers/iommu/qcom_iommu_v0.c b/drivers/iommu/qcom_iommu_v0.c
new file mode 100644
index 000000000000..190f41e7f7c6
--- /dev/null
+++ b/drivers/iommu/qcom_iommu_v0.c
@@ -0,0 +1,1223 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ * Copyright (C) 2014 Red Hat
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+
+/* NOTE: originally based on msm_iommu non-DT driver for same hw
+ * but as the structure of the driver changes considerably for DT
+ * it seemed easier to not try to support old platforms with the
+ * same driver.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/iommu.h>
+#include <linux/clk.h>
+
+#include <asm/cacheflush.h>
+#include <asm/sizes.h>
+
+#include "msm_iommu_hw-8xxx.h"
+#include "qcom_iommu_v0.h"
+
+#define MRC(reg, processor, op1, crn, crm, op2) \
+__asm__ __volatile__ ( \
+" mrc " #processor "," #op1 ", %0," #crn "," #crm "," #op2 "\n" \
+: "=r" (reg))
+
+#define RCP15_PRRR(reg) MRC(reg, p15, 0, c10, c2, 0)
+#define RCP15_NMRR(reg) MRC(reg, p15, 0, c10, c2, 1)
+
+/* bitmap of the page sizes currently supported */
+#define QCOM_IOMMU_PGSIZES (SZ_4K | SZ_64K | SZ_1M | SZ_16M)
+
+static int qcom_iommu_tex_class[4];
+
+// TODO any good reason for global lock vs per-iommu lock?
+static DEFINE_MUTEX(qcom_iommu_lock);
+static LIST_HEAD(qcom_iommu_devices);
+
+/* Note that a single iommu_domain can, for devices sitting behind
+ * more than one IOMMU (ie. one per AXI interface) will have more
+ * than one iommu in the iommu_list. But all are programmed to
+ * point at the same pagetables so from client device perspective
+ * they act as a single IOMMU.
+ */
+struct qcom_domain_priv {
+ unsigned long *pgtable;
+ struct iommu_domain *domain;
+ struct list_head iommu_list; /* list of attached 'struct qcom_iommu' */
+};
+
+static int __enable_clocks(struct qcom_iommu *iommu)
+{
+ int ret;
+
+ ret = clk_prepare_enable(iommu->pclk);
+ if (ret)
+ goto fail;
+
+ if (iommu->clk) {
+ ret = clk_prepare_enable(iommu->clk);
+ if (ret)
+ clk_disable_unprepare(iommu->pclk);
+ }
+fail:
+ return ret;
+}
+
+static void __disable_clocks(struct qcom_iommu *iommu)
+{
+ if (iommu->clk)
+ clk_disable_unprepare(iommu->clk);
+ clk_disable_unprepare(iommu->pclk);
+}
+
+static void __flush_range(struct qcom_iommu *iommu,
+ unsigned long *start, unsigned long *end)
+{
+ dmac_flush_range(start, end);
+}
+
+static int __flush_iotlb_va(struct qcom_domain_priv *priv, unsigned int va)
+{
+ struct qcom_iommu *iommu;
+ int ret = 0;
+
+ list_for_each_entry(iommu, &priv->iommu_list, dom_node) {
+ struct qcom_iommu_ctx *iommu_ctx;
+ list_for_each_entry(iommu_ctx, &iommu->ctx_list, node) {
+ int ctx = iommu_ctx->num;
+ uint32_t asid;
+
+ ret = __enable_clocks(iommu);
+ if (ret)
+ goto fail;
+
+ asid = GET_CONTEXTIDR_ASID(iommu->base, ctx);
+
+ SET_TLBIVA(iommu->base, ctx, asid | (va & TLBIVA_VA));
+ mb();
+
+ __disable_clocks(iommu);
+ }
+ }
+
+fail:
+ return ret;
+}
+
+static int __flush_iotlb(struct qcom_domain_priv *priv)
+{
+ struct qcom_iommu *iommu;
+ int ret = 0;
+
+#ifndef CONFIG_IOMMU_PGTABLES_L2
+ unsigned long *fl_table = priv->pgtable;
+ int i;
+
+ list_for_each_entry(iommu, &priv->iommu_list, dom_node) {
+
+ __flush_range(iommu, fl_table, fl_table + SZ_16K);
+
+ for (i = 0; i < NUM_FL_PTE; i++) {
+ if ((fl_table[i] & 0x03) == FL_TYPE_TABLE) {
+ void *sl_table = __va(fl_table[i] &
+ FL_BASE_MASK);
+ __flush_range(iommu, sl_table, sl_table + SZ_4K);
+ }
+ }
+
+ /*
+ * Only need to flush once, all iommu's attached
+ * to the domain use common set of pagetables:
+ */
+ break;
+ }
+#endif
+
+ list_for_each_entry(iommu, &priv->iommu_list, dom_node) {
+ struct qcom_iommu_ctx *iommu_ctx;
+
+ ret = __enable_clocks(iommu);
+ if (ret)
+ goto fail;
+
+ list_for_each_entry(iommu_ctx, &iommu->ctx_list, node) {
+ int ctx = iommu_ctx->num;
+ uint32_t asid;
+
+ asid = GET_CONTEXTIDR_ASID(iommu->base, ctx);
+
+ SET_TLBIASID(iommu->base, ctx, asid);
+ mb();
+ }
+ __disable_clocks(iommu);
+ }
+
+fail:
+ return ret;
+}
+
+static void __reset_context(struct qcom_iommu *iommu, int ctx)
+{
+ void __iomem *base = iommu->base;
+
+ SET_BPRCOSH(base, ctx, 0);
+ SET_BPRCISH(base, ctx, 0);
+ SET_BPRCNSH(base, ctx, 0);
+ SET_BPSHCFG(base, ctx, 0);
+ SET_BPMTCFG(base, ctx, 0);
+ SET_ACTLR(base, ctx, 0);
+ SET_SCTLR(base, ctx, 0);
+ SET_FSRRESTORE(base, ctx, 0);
+ SET_TTBR0(base, ctx, 0);
+ SET_TTBR1(base, ctx, 0);
+ SET_TTBCR(base, ctx, 0);
+ SET_BFBCR(base, ctx, 0);
+ SET_PAR(base, ctx, 0);
+ SET_FAR(base, ctx, 0);
+ SET_TLBFLPTER(base, ctx, 0);
+ SET_TLBSLPTER(base, ctx, 0);
+ SET_TLBLKCR(base, ctx, 0);
+ SET_PRRR(base, ctx, 0);
+ SET_NMRR(base, ctx, 0);
+ SET_RESUME(base, ctx, 1);
+ mb();
+}
+
+static void __reset_iommu(struct qcom_iommu *iommu)
+{
+ void __iomem *base = iommu->base;
+ int ctx;
+
+ SET_RPUE(base, 0);
+ SET_RPUEIE(base, 0);
+ SET_ESRRESTORE(base, 0);
+ SET_TBE(base, 0);
+ SET_CR(base, 0);
+ SET_SPDMBE(base, 0);
+ SET_TESTBUSCR(base, 0);
+ SET_TLBRSW(base, 0);
+ SET_GLOBAL_TLBIALL(base, 0);
+ SET_RPU_ACR(base, 0);
+ SET_TLBLKCRWE(base, 1);
+
+ for (ctx = 0; ctx < iommu->ncb; ctx++) {
+ SET_BPRCOSH(base, ctx, 0);
+ SET_BPRCISH(base, ctx, 0);
+ SET_BPRCNSH(base, ctx, 0);
+ SET_BPSHCFG(base, ctx, 0);
+ SET_BPMTCFG(base, ctx, 0);
+ SET_ACTLR(base, ctx, 0);
+ SET_SCTLR(base, ctx, 0);
+ SET_FSRRESTORE(base, ctx, 0);
+ SET_TTBR0(base, ctx, 0);
+ SET_TTBR1(base, ctx, 0);
+ SET_TTBCR(base, ctx, 0);
+ SET_BFBCR(base, ctx, 0);
+ SET_PAR(base, ctx, 0);
+ SET_FAR(base, ctx, 0);
+ SET_TLBFLPTER(base, ctx, 0);
+ SET_TLBSLPTER(base, ctx, 0);
+ SET_TLBLKCR(base, ctx, 0);
+ SET_CTX_TLBIALL(base, ctx, 0);
+ SET_TLBIVA(base, ctx, 0);
+ SET_PRRR(base, ctx, 0);
+ SET_NMRR(base, ctx, 0);
+ SET_CONTEXTIDR(base, ctx, 0);
+ }
+ mb();
+}
+
+
+static void __program_context(struct qcom_domain_priv *priv,
+ struct qcom_iommu *iommu, int ctx)
+{
+ void __iomem *base = iommu->base;
+ phys_addr_t pgtable = __pa(priv->pgtable);
+ unsigned int prrr, nmrr;
+ bool found;
+ int i, j;
+
+ __reset_context(iommu, ctx);
+
+ /* Set up HTW mode */
+ /* TLB miss configuration: perform HTW on miss */
+ SET_TLBMCFG(base, ctx, 0x3);
+
+ /* V2P configuration: HTW for access */
+ SET_V2PCFG(base, ctx, 0x3);
+
+ SET_TTBCR(base, ctx, iommu->ttbr_split);
+ SET_TTBR0_PA(base, ctx, (pgtable >> TTBR0_PA_SHIFT));
+ if (iommu->ttbr_split)
+ SET_TTBR1_PA(base, ctx, (pgtable >> TTBR1_PA_SHIFT));
+
+ /* Enable context fault interrupt */
+ SET_CFEIE(base, ctx, 1);
+
+ /* Stall access on a context fault and let the handler deal with it */
+ SET_CFCFG(base, ctx, 1);
+
+ /* Redirect all cacheable requests to L2 slave port. */
+ SET_RCISH(base, ctx, 1);
+ SET_RCOSH(base, ctx, 1);
+ SET_RCNSH(base, ctx, 1);
+
+ /* Turn on TEX Remap */
+ SET_TRE(base, ctx, 1);
+
+ /* Set TEX remap attributes */
+ RCP15_PRRR(prrr);
+ RCP15_NMRR(nmrr);
+ SET_PRRR(base, ctx, prrr);
+ SET_NMRR(base, ctx, nmrr);
+
+ /* Turn on BFB prefetch */
+ SET_BFBDFE(base, ctx, 1);
+
+#ifdef CONFIG_IOMMU_PGTABLES_L2
+ /* Configure page tables as inner-cacheable and shareable to reduce
+ * the TLB miss penalty.
+ */
+ SET_TTBR0_SH(base, ctx, 1);
+ SET_TTBR1_SH(base, ctx, 1);
+
+ SET_TTBR0_NOS(base, ctx, 1);
+ SET_TTBR1_NOS(base, ctx, 1);
+
+ SET_TTBR0_IRGNH(base, ctx, 0); /* WB, WA */
+ SET_TTBR0_IRGNL(base, ctx, 1);
+
+ SET_TTBR1_IRGNH(base, ctx, 0); /* WB, WA */
+ SET_TTBR1_IRGNL(base, ctx, 1);
+
+ SET_TTBR0_ORGN(base, ctx, 1); /* WB, WA */
+ SET_TTBR1_ORGN(base, ctx, 1); /* WB, WA */
+#endif
+
+ /* Find if this page table is used elsewhere, and re-use ASID */
+ found = false;
+ for (i = 0; i < iommu->ncb; i++) {
+ if (i == ctx)
+ continue;
+
+ if (GET_TTBR0_PA(base, i) == (pgtable >> TTBR0_PA_SHIFT)) {
+ SET_CONTEXTIDR_ASID(base, ctx, GET_CONTEXTIDR_ASID(base, i));
+ found = true;
+ break;
+ }
+ }
+
+ /* If page table is new, find an unused ASID */
+ if (!found) {
+ for (i = 0; i < iommu->ncb; i++) {
+ found = false;
+ for (j = 0; j < iommu->ncb; j++) {
+ if (j != ctx)
+ continue;
+ if (GET_CONTEXTIDR_ASID(base, j) == i)
+ found = true;
+ }
+
+ if (!found) {
+ SET_CONTEXTIDR_ASID(base, ctx, i);
+ break;
+ }
+ }
+ BUG_ON(found);
+ }
+
+ /* Enable the MMU */
+ SET_M(base, ctx, 1);
+ mb();
+}
+
+static int qcom_iommu_domain_init(struct iommu_domain *domain)
+{
+ struct qcom_domain_priv *priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+
+ if (!priv)
+ goto fail_nomem;
+
+ INIT_LIST_HEAD(&priv->iommu_list);
+ priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL,
+ get_order(SZ_16K));
+
+ if (!priv->pgtable)
+ goto fail_nomem;
+
+ memset(priv->pgtable, 0, SZ_16K);
+ domain->priv = priv;
+ priv->domain = domain;
+
+//XXX I think not needed?
+ dmac_flush_range(priv->pgtable, priv->pgtable + NUM_FL_PTE);
+
+ domain->geometry.aperture_start = 0;
+ domain->geometry.aperture_end = (1ULL << 32) - 1;
+ domain->geometry.force_aperture = true;
+
+ return 0;
+
+fail_nomem:
+ kfree(priv);
+ return -ENOMEM;
+}
+
+static void qcom_iommu_domain_destroy(struct iommu_domain *domain)
+{
+ struct qcom_domain_priv *priv;
+ unsigned long *fl_table;
+ int i;
+
+ mutex_lock(&qcom_iommu_lock);
+ priv = domain->priv;
+ domain->priv = NULL;
+
+ if (priv) {
+ fl_table = priv->pgtable;
+
+ for (i = 0; i < NUM_FL_PTE; i++)
+ if ((fl_table[i] & 0x03) == FL_TYPE_TABLE)
+ free_page((unsigned long) __va(((fl_table[i]) &
+ FL_BASE_MASK)));
+
+ free_pages((unsigned long)priv->pgtable, get_order(SZ_16K));
+ priv->pgtable = NULL;
+ }
+
+ kfree(priv);
+ mutex_unlock(&qcom_iommu_lock);
+}
+
+static int qcom_iommu_attach_dev(struct iommu_domain *domain, struct device *dev)
+{
+ struct qcom_domain_priv *priv = domain->priv;
+ struct qcom_iommu *iommu;
+ struct qcom_iommu_ctx *iommu_ctx = NULL;
+ int ret = 0;
+ bool found = false;
+
+ if (!priv)
+ return -EINVAL;
+
+ mutex_lock(&qcom_iommu_lock);
+ list_for_each_entry(iommu, &qcom_iommu_devices, dev_node) {
+ iommu_ctx = list_first_entry(&iommu->ctx_list,
+ struct qcom_iommu_ctx, node);
+
+ if (iommu_ctx->of_node == dev->of_node) {
+ found = true;
+
+ ret = __enable_clocks(iommu);
+ if (ret)
+ goto fail;
+
+ /* we found a matching device, attach all it's contexts: */
+ list_for_each_entry(iommu_ctx, &iommu->ctx_list, node)
+ __program_context(priv, iommu, iommu_ctx->num);
+
+ __disable_clocks(iommu);
+
+ // TODO check for double attaches, etc..
+
+ list_add_tail(&iommu->dom_node, &priv->iommu_list);
+ iommu->domain = domain;
+ }
+ }
+
+ if (!found) {
+ ret = -ENXIO;
+ goto fail;
+ }
+
+ // TODO might want to get_device(iommu->dev) unless iommu framework
+ // does this somewhere for us?
+
+ ret = __flush_iotlb(priv);
+
+fail:
+ if (ret) {
+ // TODO make sure we completely detach..
+ }
+ mutex_unlock(&qcom_iommu_lock);
+ return ret;
+}
+
+static void qcom_iommu_detach_dev(struct iommu_domain *domain,
+ struct device *dev)
+{
+ struct qcom_domain_priv *priv = domain->priv;
+ struct qcom_iommu *iommu;
+ struct qcom_iommu_ctx *iommu_ctx;
+ int ret;
+
+ if (!priv)
+ return;
+
+ mutex_lock(&qcom_iommu_lock);
+
+ ret = __flush_iotlb(priv);
+ if (ret)
+ goto fail;
+
+ while (!list_empty(&priv->iommu_list)) {
+ iommu = list_first_entry(&priv->iommu_list,
+ struct qcom_iommu, dom_node);
+
+ ret = __enable_clocks(iommu);
+ if (ret)
+ goto fail;
+
+ /* reset all contexts: */
+ list_for_each_entry(iommu_ctx, &iommu->ctx_list, node) {
+ int ctx = iommu_ctx->num;
+ uint32_t asid = GET_CONTEXTIDR_ASID(iommu->base, ctx);
+ SET_TLBIASID(iommu->base, ctx, asid);
+ __reset_context(iommu, ctx);
+ }
+
+ __disable_clocks(iommu);
+
+ list_del(&iommu->dom_node);
+ }
+
+ // TODO might want to put_device(iommu->dev) unless iommu framework
+ // does this somewhere for us?
+
+fail:
+ mutex_unlock(&qcom_iommu_lock);
+}
+
+static int __get_pgprot(int prot, int len)
+{
+ unsigned int pgprot;
+ int tex;
+
+ if (!(prot & (IOMMU_READ | IOMMU_WRITE))) {
+ prot |= IOMMU_READ | IOMMU_WRITE;
+ WARN_ONCE(1, "No attributes in iommu mapping; assuming RW\n");
+ }
+
+ if ((prot & IOMMU_WRITE) && !(prot & IOMMU_READ)) {
+ prot |= IOMMU_READ;
+ WARN_ONCE(1, "Write-only iommu mappings unsupported; falling back to RW\n");
+ }
+
+ if (prot & IOMMU_CACHE)
+ tex = (pgprot_kernel >> 2) & 0x07;
+ else
+ tex = qcom_iommu_tex_class[QCOM_IOMMU_ATTR_NONCACHED];
+
+ if (tex < 0 || tex > NUM_TEX_CLASS - 1)
+ return 0;
+
+ if (len == SZ_16M || len == SZ_1M) {
+ pgprot = FL_SHARED;
+ pgprot |= tex & 0x01 ? FL_BUFFERABLE : 0;
+ pgprot |= tex & 0x02 ? FL_CACHEABLE : 0;
+ pgprot |= tex & 0x04 ? FL_TEX0 : 0;
+ pgprot |= FL_AP0 | FL_AP1;
+ pgprot |= prot & IOMMU_WRITE ? 0 : FL_AP2;
+ } else {
+ pgprot = SL_SHARED;
+ pgprot |= tex & 0x01 ? SL_BUFFERABLE : 0;
+ pgprot |= tex & 0x02 ? SL_CACHEABLE : 0;
+ pgprot |= tex & 0x04 ? SL_TEX0 : 0;
+ pgprot |= SL_AP0 | SL_AP1;
+ pgprot |= prot & IOMMU_WRITE ? 0 : SL_AP2;
+ }
+
+ return pgprot;
+}
+
+static int qcom_iommu_map(struct iommu_domain *domain, unsigned long va,
+ phys_addr_t pa, size_t len, int prot)
+{
+ struct qcom_domain_priv *priv = domain->priv;
+ struct qcom_iommu *iommu;
+ unsigned long *fl_table, *fl_pte, fl_offset;
+ unsigned long *sl_table, *sl_pte, sl_offset;
+ unsigned int pgprot;
+ int ret = 0;
+
+ mutex_lock(&qcom_iommu_lock);
+
+ if (!priv || list_empty(&priv->iommu_list))
+ goto fail;
+
+ /* all IOMMU's in the domain have same pgtables: */
+ iommu = list_first_entry(&priv->iommu_list,
+ struct qcom_iommu, dom_node);
+
+ fl_table = priv->pgtable;
+
+ if (len != SZ_16M && len != SZ_1M &&
+ len != SZ_64K && len != SZ_4K) {
+ dev_err(iommu->dev, "Bad size: %d\n", len);
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (!fl_table) {
+ dev_err(iommu->dev, "Null page table\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ pgprot = __get_pgprot(prot, len);
+
+ if (!pgprot) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ fl_offset = FL_OFFSET(va); /* Upper 12 bits */
+ fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */
+
+ if (len == SZ_16M) {
+ int i = 0;
+
+ for (i = 0; i < 16; i++)
+ if (*(fl_pte+i)) {
+ ret = -EBUSY;
+ goto fail;
+ }
+
+ for (i = 0; i < 16; i++)
+ *(fl_pte+i) = (pa & 0xFF000000) | FL_SUPERSECTION
+ | FL_TYPE_SECT | FL_SHARED | FL_NG | pgprot;
+ __flush_range(iommu, fl_pte, fl_pte + 16);
+ }
+
+ if (len == SZ_1M) {
+ if (*fl_pte) {
+ ret = -EBUSY;
+ goto fail;
+ }
+
+ *fl_pte = (pa & 0xFFF00000) | FL_NG | FL_TYPE_SECT | FL_SHARED
+ | pgprot;
+ __flush_range(iommu, fl_pte, fl_pte + 1);
+ }
+
+ /* Need a 2nd level table */
+ if (len == SZ_4K || len == SZ_64K) {
+
+ if (*fl_pte == 0) {
+ unsigned long *sl;
+ sl = (unsigned long *) __get_free_pages(GFP_ATOMIC,
+ get_order(SZ_4K));
+
+ if (!sl) {
+ dev_err(iommu->dev, "Could not allocate second level table\n");
+ ret = -ENOMEM;
+ goto fail;
+ }
+ memset(sl, 0, SZ_4K);
+ __flush_range(iommu, sl, sl + NUM_SL_PTE);
+
+ *fl_pte = ((((int)__pa(sl)) & FL_BASE_MASK) | \
+ FL_TYPE_TABLE);
+
+ __flush_range(iommu, fl_pte, fl_pte + 1);
+ }
+
+ if (!(*fl_pte & FL_TYPE_TABLE)) {
+ ret = -EBUSY;
+ goto fail;
+ }
+ }
+
+ sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK));
+ sl_offset = SL_OFFSET(va);
+ sl_pte = sl_table + sl_offset;
+
+ if (len == SZ_4K) {
+ if (*sl_pte) {
+ ret = -EBUSY;
+ goto fail;
+ }
+
+ *sl_pte = (pa & SL_BASE_MASK_SMALL) | SL_NG | SL_SHARED
+ | SL_TYPE_SMALL | pgprot;
+ __flush_range(iommu, sl_pte, sl_pte + 1);
+ }
+
+ if (len == SZ_64K) {
+ int i;
+
+ for (i = 0; i < 16; i++)
+ if (*(sl_pte+i)) {
+ ret = -EBUSY;
+ goto fail;
+ }
+
+ for (i = 0; i < 16; i++)
+ *(sl_pte+i) = (pa & SL_BASE_MASK_LARGE) | SL_NG
+ | SL_SHARED | SL_TYPE_LARGE | pgprot;
+
+ __flush_range(iommu, sl_pte, sl_pte + 16);
+ }
+
+ ret = __flush_iotlb_va(priv, va);
+
+fail:
+ mutex_unlock(&qcom_iommu_lock);
+ return ret;
+}
+
+static size_t qcom_iommu_unmap(struct iommu_domain *domain, unsigned long va,
+ size_t len)
+{
+ struct qcom_domain_priv *priv = domain->priv;
+ struct qcom_iommu *iommu;
+ unsigned long *fl_table, *fl_pte, fl_offset;
+ unsigned long *sl_table, *sl_pte, sl_offset;
+ int i, ret = 0;
+
+ mutex_lock(&qcom_iommu_lock);
+
+ if (!priv || list_empty(&priv->iommu_list))
+ goto fail;
+
+ /* all IOMMU's in the domain have same pgtables: */
+ iommu = list_first_entry(&priv->iommu_list,
+ struct qcom_iommu, dom_node);
+
+ fl_table = priv->pgtable;
+
+ if (len != SZ_16M && len != SZ_1M &&
+ len != SZ_64K && len != SZ_4K) {
+ dev_err(iommu->dev, "Bad length: %d\n", len);
+ goto fail;
+ }
+
+ if (!fl_table) {
+ dev_err(iommu->dev, "Null page table\n");
+ goto fail;
+ }
+
+ fl_offset = FL_OFFSET(va); /* Upper 12 bits */
+ fl_pte = fl_table + fl_offset; /* int pointers, 4 bytes */
+
+ if (*fl_pte == 0) {
+ dev_err(iommu->dev, "First level PTE is 0\n");
+ goto fail;
+ }
+
+ /* Unmap supersection */
+ if (len == SZ_16M) {
+ for (i = 0; i < 16; i++)
+ *(fl_pte+i) = 0;
+
+ __flush_range(iommu, fl_pte, fl_pte + 16);
+ }
+
+ if (len == SZ_1M) {
+ *fl_pte = 0;
+
+ __flush_range(iommu, fl_pte, fl_pte + 1);
+ }
+
+ sl_table = (unsigned long *) __va(((*fl_pte) & FL_BASE_MASK));
+ sl_offset = SL_OFFSET(va);
+ sl_pte = sl_table + sl_offset;
+
+ if (len == SZ_64K) {
+ for (i = 0; i < 16; i++)
+ *(sl_pte+i) = 0;
+
+ __flush_range(iommu, sl_pte, sl_pte + 16);
+ }
+
+ if (len == SZ_4K) {
+ *sl_pte = 0;
+
+ __flush_range(iommu, sl_pte, sl_pte + 1);
+ }
+
+ if (len == SZ_4K || len == SZ_64K) {
+ int used = 0;
+
+ for (i = 0; i < NUM_SL_PTE; i++)
+ if (sl_table[i])
+ used = 1;
+ if (!used) {
+ free_page((unsigned long)sl_table);
+ *fl_pte = 0;
+
+ __flush_range(iommu, fl_pte, fl_pte + 1);
+ }
+ }
+
+ ret = __flush_iotlb_va(priv, va);
+
+fail:
+ mutex_unlock(&qcom_iommu_lock);
+
+ /* the IOMMU API requires us to return how many bytes were unmapped */
+ len = ret ? 0 : len;
+ return len;
+}
+
+static phys_addr_t qcom_iommu_iova_to_phys(struct iommu_domain *domain,
+ dma_addr_t va)
+{
+ struct qcom_domain_priv *priv = domain->priv;
+ struct qcom_iommu *iommu;
+ struct qcom_iommu_ctx *iommu_ctx;
+ unsigned int par;
+ void __iomem *base;
+ phys_addr_t ret = 0;
+ int ctx;
+
+ mutex_lock(&qcom_iommu_lock);
+
+ if (!priv || list_empty(&priv->iommu_list))
+ goto fail;
+
+ /* all IOMMU's in the domain have same pgtables: */
+ iommu = list_first_entry(&priv->iommu_list,
+ struct qcom_iommu, dom_node);
+
+ if (list_empty(&iommu->ctx_list))
+ goto fail;
+
+ iommu_ctx = list_first_entry(&iommu->ctx_list,
+ struct qcom_iommu_ctx, node);
+
+ base = iommu->base;
+ ctx = iommu_ctx->num;
+
+ ret = __enable_clocks(iommu);
+ if (ret)
+ goto fail;
+
+ SET_V2PPR(base, ctx, va & V2Pxx_VA);
+
+ mb();
+ par = GET_PAR(base, ctx);
+
+ /* We are dealing with a supersection */
+ if (GET_NOFAULT_SS(base, ctx))
+ ret = (par & 0xFF000000) | (va & 0x00FFFFFF);
+ else /* Upper 20 bits from PAR, lower 12 from VA */
+ ret = (par & 0xFFFFF000) | (va & 0x00000FFF);
+
+ if (GET_FAULT(base, ctx))
+ ret = 0;
+
+ __disable_clocks(iommu);
+
+fail:
+ mutex_unlock(&qcom_iommu_lock);
+ return ret;
+}
+
+static void print_ctx_regs(void __iomem *base, int ctx)
+{
+ unsigned int fsr = GET_FSR(base, ctx);
+ pr_err("FAR = %08x PAR = %08x\n",
+ GET_FAR(base, ctx), GET_PAR(base, ctx));
+ pr_err("FSR = %08x [%s%s%s%s%s%s%s%s%s%s]\n", fsr,
+ (fsr & 0x02) ? "TF " : "",
+ (fsr & 0x04) ? "AFF " : "",
+ (fsr & 0x08) ? "APF " : "",
+ (fsr & 0x10) ? "TLBMF " : "",
+ (fsr & 0x20) ? "HTWDEEF " : "",
+ (fsr & 0x40) ? "HTWSEEF " : "",
+ (fsr & 0x80) ? "MHF " : "",
+ (fsr & 0x10000) ? "SL " : "",
+ (fsr & 0x40000000) ? "SS " : "",
+ (fsr & 0x80000000) ? "MULTI " : "");
+
+ pr_err("FSYNR0 = %08x FSYNR1 = %08x\n",
+ GET_FSYNR0(base, ctx), GET_FSYNR1(base, ctx));
+ pr_err("TTBR0 = %08x TTBR1 = %08x\n",
+ GET_TTBR0(base, ctx), GET_TTBR1(base, ctx));
+ pr_err("SCTLR = %08x ACTLR = %08x\n",
+ GET_SCTLR(base, ctx), GET_ACTLR(base, ctx));
+ pr_err("PRRR = %08x NMRR = %08x\n",
+ GET_PRRR(base, ctx), GET_NMRR(base, ctx));
+}
+
+static irqreturn_t __fault_handler(int irq, void *dev_id)
+{
+ struct platform_device *pdev = dev_id;
+ struct qcom_iommu *iommu = platform_get_drvdata(pdev);
+ struct qcom_iommu_ctx *iommu_ctx = NULL;
+ void __iomem *base = iommu->base;
+ int ret;
+ bool first = true;
+
+ mutex_lock(&qcom_iommu_lock);
+
+ ret = __enable_clocks(iommu);
+ if (ret)
+ goto fail;
+
+ list_for_each_entry(iommu_ctx, &iommu->ctx_list, node) {
+ int i = iommu_ctx->num;
+ unsigned int fsr = GET_FSR(base, i);
+ unsigned long iova;
+ int flags;
+
+ if (!fsr)
+ continue;
+
+ iova = GET_FAR(base, i);
+
+ /* TODO without docs, not sure about IOMMU_FAULT_* flags */
+ flags = 0;
+
+ if (!report_iommu_fault(iommu->domain, iommu->dev,
+ iova, flags)) {
+ ret = IRQ_HANDLED;
+ } else {
+ // XXX ratelimited
+ if (first) {
+ /* only print header for first context */
+ pr_err("Unexpected IOMMU page fault!\n");
+ pr_err("base = %08x\n", (unsigned int) base);
+ first = false;
+ }
+ pr_err("Fault occurred in context %d.\n", i);
+ pr_err("name = %s\n", dev_name(&pdev->dev));
+ pr_err("Interesting registers:\n");
+ print_ctx_regs(base, i);
+ }
+
+ SET_FSR(base, i, fsr);
+ SET_RESUME(base, i, 1);
+ }
+ __disable_clocks(iommu);
+
+fail:
+ mutex_unlock(&qcom_iommu_lock);
+ return IRQ_HANDLED;
+}
+
+static struct iommu_ops qcom_iommu_ops = {
+ .domain_init = qcom_iommu_domain_init,
+ .domain_destroy = qcom_iommu_domain_destroy,
+ .attach_dev = qcom_iommu_attach_dev,
+ .detach_dev = qcom_iommu_detach_dev,
+ .map = qcom_iommu_map,
+ .unmap = qcom_iommu_unmap,
+ .iova_to_phys = qcom_iommu_iova_to_phys,
+ .pgsize_bitmap = QCOM_IOMMU_PGSIZES,
+};
+
+/*
+ * IOMMU Platform Driver:
+ */
+
+static int __register_ctx(struct qcom_iommu *iommu,
+ struct of_phandle_args *masterspec, int ctx)
+{
+ struct qcom_iommu_ctx *iommu_ctx;
+ int i, ret;
+
+ if (masterspec->args_count > MAX_NUM_MIDS)
+ return -EINVAL;
+
+ iommu_ctx = kzalloc(sizeof(*iommu_ctx), GFP_KERNEL);
+ if (!iommu) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ iommu_ctx->of_node = masterspec->np;
+ iommu_ctx->num = ctx;
+
+ for (i = 0; i < masterspec->args_count; i++)
+ iommu_ctx->mids[i] = masterspec->args[i];
+ for (; i < ARRAY_SIZE(iommu_ctx->mids); i++)
+ iommu_ctx->mids[i] = -1;
+
+ list_add_tail(&iommu_ctx->node, &iommu->ctx_list);
+
+ return 0;
+
+fail:
+ // TODO cleanup;
+ return ret;
+}
+
+static int qcom_iommu_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct qcom_iommu *iommu;
+ struct qcom_iommu_ctx *iommu_ctx;
+ struct of_phandle_args masterspec;
+ struct resource *r;
+ int ctx, ret, par, i;
+ u32 val;
+
+ iommu = kzalloc(sizeof(*iommu), GFP_KERNEL);
+ if (!iommu) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ iommu->dev = &pdev->dev;
+ INIT_LIST_HEAD(&iommu->ctx_list);
+
+ ret = of_property_read_u32(node, "ncb", &val);
+ if (ret) {
+ dev_err(iommu->dev, "could not get ncb\n");
+ goto fail;
+ }
+ iommu->ncb = val;
+
+ ret = of_property_read_u32(node, "ttbr-split", &val);
+ if (ret)
+ val = 0;
+ iommu->ttbr_split = val;
+
+ iommu->pclk = devm_clk_get(iommu->dev, "smmu_pclk");
+ if (IS_ERR(iommu->pclk)) {
+ dev_err(iommu->dev, "could not get smmu_pclk\n");
+ ret = PTR_ERR(iommu->pclk);
+ iommu->pclk = NULL;
+ goto fail;
+ }
+
+ iommu->clk = devm_clk_get(iommu->dev, "iommu_clk");
+ if (IS_ERR(iommu->clk)) {
+ dev_err(iommu->dev, "could not get iommu_clk\n");
+ ret = PTR_ERR(iommu->clk);
+ iommu->clk = NULL;
+ goto fail;
+ }
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "physbase");
+ if (!r) {
+ dev_err(iommu->dev, "could not get physbase\n");
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ iommu->base = devm_ioremap_resource(iommu->dev, r);
+ if (IS_ERR(iommu->base)) {
+ ret = PTR_ERR(iommu->base);
+ goto fail;
+ }
+
+ iommu->irq = platform_get_irq_byname(pdev, "nonsecure_irq");
+ if (WARN_ON(iommu->irq < 0)) {
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ /* now find our contexts: */
+ i = 0;
+ while (!of_parse_phandle_with_args(node, "mmu-masters",
+ "#stream-id-cells", i, &masterspec)) {
+ ret = __register_ctx(iommu, &masterspec, i);
+ if (ret) {
+ dev_err(iommu->dev, "failed to add context %s\n",
+ masterspec.np->name);
+ goto fail;
+ }
+
+ i++;
+ }
+ dev_notice(iommu->dev, "registered %d master devices\n", i);
+
+ __enable_clocks(iommu);
+ __reset_iommu(iommu);
+
+ SET_M(iommu->base, 0, 1);
+ SET_PAR(iommu->base, 0, 0);
+ SET_V2PCFG(iommu->base, 0, 1);
+ SET_V2PPR(iommu->base, 0, 0);
+ mb();
+ par = GET_PAR(iommu->base, 0);
+ SET_V2PCFG(iommu->base, 0, 0);
+ SET_M(iommu->base, 0, 0);
+ mb();
+
+ ctx = 0;
+ list_for_each_entry(iommu_ctx, &iommu->ctx_list, node) {
+ for (i = 0; iommu_ctx->mids[i] != -1; i++) {
+ int mid = iommu_ctx->mids[i];
+
+ SET_M2VCBR_N(iommu->base, mid, 0);
+ SET_CBACR_N(iommu->base, ctx, 0);
+
+ /* Set VMID = 0 */
+ SET_VMID(iommu->base, mid, 0);
+
+ /* Set the context number for that MID to this context */
+ SET_CBNDX(iommu->base, mid, ctx);
+
+ /* Set MID associated with this context bank to 0*/
+ SET_CBVMID(iommu->base, ctx, 0);
+
+ /* Set the ASID for TLB tagging for this context */
+ SET_CONTEXTIDR_ASID(iommu->base, ctx, ctx);
+
+ /* Set security bit override to be Non-secure */
+ SET_NSCFG(iommu->base, mid, 3);
+ }
+
+ ctx++;
+ }
+
+ __disable_clocks(iommu);
+
+ if (!par) {
+ dev_err(iommu->dev, "Invalid PAR value detected\n");
+ ret = -ENODEV;
+ goto fail;
+ }
+
+ ret = devm_request_threaded_irq(iommu->dev, iommu->irq, NULL,
+ __fault_handler, IRQF_ONESHOT | IRQF_SHARED,
+ "iommu", pdev);
+ if (ret) {
+ dev_err(iommu->dev, "Request IRQ %d failed with ret=%d\n",
+ iommu->irq, ret);
+ goto fail;
+ }
+
+ dev_info(iommu->dev, "device mapped at %p, irq %d with %d ctx banks\n",
+ iommu->base, iommu->irq, iommu->ncb);
+
+ platform_set_drvdata(pdev, iommu);
+
+ mutex_lock(&qcom_iommu_lock);
+ list_add(&iommu->dev_node, &qcom_iommu_devices);
+ mutex_unlock(&qcom_iommu_lock);
+
+ return 0;
+fail:
+ // TODO cleanup..
+ return ret;
+}
+
+static int qcom_iommu_remove(struct platform_device *pdev)
+{
+ struct qcom_iommu *priv = platform_get_drvdata(pdev);
+
+ if (WARN_ON(!priv))
+ return 0;
+
+ if (priv->clk)
+ clk_disable_unprepare(priv->clk);
+
+ if (priv->pclk)
+ clk_disable_unprepare(priv->pclk);
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(priv);
+
+ return 0;
+}
+
+static const struct of_device_id qcom_iommu_dt_match[] = {
+ { .compatible = "qcom,iommu-v0" },
+ {}
+};
+
+static struct platform_driver qcom_iommu_driver = {
+ .driver = {
+ .name = "qcom-iommu-v0",
+ .of_match_table = qcom_iommu_dt_match,
+ },
+ .probe = qcom_iommu_probe,
+ .remove = qcom_iommu_remove,
+};
+
+static int __init get_tex_class(int icp, int ocp, int mt, int nos)
+{
+ int i = 0;
+ unsigned int prrr = 0;
+ unsigned int nmrr = 0;
+ int c_icp, c_ocp, c_mt, c_nos;
+
+ RCP15_PRRR(prrr);
+ RCP15_NMRR(nmrr);
+
+ for (i = 0; i < NUM_TEX_CLASS; i++) {
+ c_nos = PRRR_NOS(prrr, i);
+ c_mt = PRRR_MT(prrr, i);
+ c_icp = NMRR_ICP(nmrr, i);
+ c_ocp = NMRR_OCP(nmrr, i);
+
+ if (icp == c_icp && ocp == c_ocp && c_mt == mt && c_nos == nos)
+ return i;
+ }
+
+ return -ENODEV;
+}
+
+static int __init qcom_iommu_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&qcom_iommu_driver);
+ if (ret) {
+ pr_err("Failed to register IOMMU driver\n");
+ goto error;
+ }
+
+ qcom_iommu_tex_class[QCOM_IOMMU_ATTR_NONCACHED] =
+ get_tex_class(CP_NONCACHED, CP_NONCACHED, MT_NORMAL, 1);
+
+ qcom_iommu_tex_class[QCOM_IOMMU_ATTR_CACHED_WB_WA] =
+ get_tex_class(CP_WB_WA, CP_WB_WA, MT_NORMAL, 1);
+
+ qcom_iommu_tex_class[QCOM_IOMMU_ATTR_CACHED_WB_NWA] =
+ get_tex_class(CP_WB_NWA, CP_WB_NWA, MT_NORMAL, 1);
+
+ qcom_iommu_tex_class[QCOM_IOMMU_ATTR_CACHED_WT] =
+ get_tex_class(CP_WT, CP_WT, MT_NORMAL, 1);
+
+ bus_set_iommu(&platform_bus_type, &qcom_iommu_ops);
+
+ return 0;
+
+error:
+ return ret;
+}
+
+static void __exit qcom_iommu_driver_exit(void)
+{
+ platform_driver_unregister(&qcom_iommu_driver);
+}
+
+subsys_initcall(qcom_iommu_init);
+module_exit(qcom_iommu_driver_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Stepan Moskovchenko <stepanm@codeaurora.org>");
+MODULE_AUTHOR("Rob Clark <robdclark@gmail.com>");
diff --git a/drivers/iommu/qcom_iommu_v0.h b/drivers/iommu/qcom_iommu_v0.h
new file mode 100644
index 000000000000..a3d1e37182c8
--- /dev/null
+++ b/drivers/iommu/qcom_iommu_v0.h
@@ -0,0 +1,99 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ * Copyright (C) 2014 Red Hat
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/* NOTE: originally based on msm_iommu non-DT driver for same hw
+ * but as the structure of the driver changes considerably for DT
+ * it seemed easier to not try to support old platforms with the
+ * same driver.
+ */
+
+#ifndef QCOM_IOMMU_V0_H
+#define QCOM_IOMMU_V0_H
+
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+
+/* Sharability attributes of QCOM IOMMU mappings */
+#define QCOM_IOMMU_ATTR_NON_SH 0x0
+#define QCOM_IOMMU_ATTR_SH 0x4
+
+/* Cacheability attributes of QCOM IOMMU mappings */
+#define QCOM_IOMMU_ATTR_NONCACHED 0x0
+#define QCOM_IOMMU_ATTR_CACHED_WB_WA 0x1
+#define QCOM_IOMMU_ATTR_CACHED_WB_NWA 0x2
+#define QCOM_IOMMU_ATTR_CACHED_WT 0x3
+
+/* Mask for the cache policy attribute */
+#define QCOM_IOMMU_CP_MASK 0x03
+
+/* Maximum number of Machine IDs that we are allowing to be mapped to the same
+ * context bank. The number of MIDs mapped to the same CB does not affect
+ * performance, but there is a practical limit on how many distinct MIDs may
+ * be present. These mappings are typically determined at design time and are
+ * not expected to change at run time.
+ */
+#define MAX_NUM_MIDS 32
+
+/**
+ * struct qcom_iommu - a single IOMMU hardware instance
+ * @dev: IOMMU device
+ * @base: IOMMU config port base address (VA)
+ * @irq: Interrupt number
+ * @ncb: Number of context banks present on this IOMMU HW instance
+ * @ttbr_split: ttbr split
+ * @clk: The bus clock for this IOMMU hardware instance
+ * @pclk: The clock for the IOMMU bus interconnect
+ * @ctx_list: list of 'struct qcom_iommu_ctx'
+ * @dev_node: list head in qcom_iommu_devices list
+ * @dom_node: list head in domain
+ * @domain: attached domain. Note that the relationship between domain and
+ * and iommu's is N:1, ie. an IOMMU can only be attached to one domain,
+ * but a domain can be attached to many IOMMUs
+ */
+struct qcom_iommu {
+ struct device *dev;
+ void __iomem *base;
+ int irq;
+ int ncb;
+ int ttbr_split;
+ struct clk *clk;
+ struct clk *pclk;
+ struct list_head ctx_list;
+ struct list_head dev_node;
+ struct list_head dom_node;
+ struct iommu_domain *domain;
+};
+
+/**
+ * struct qcom_iommu_ctx - an IOMMU context bank instance
+ * @of_node: node ptr of client device
+ * @num: Index of this context bank within the hardware
+ * @mids: List of Machine IDs that are to be mapped into this context
+ * bank, terminated by -1. The MID is a set of signals on the
+ * AXI bus that identifies the function associated with a specific
+ * memory request. (See ARM spec).
+ * @node: list head in ctx_list
+ */
+struct qcom_iommu_ctx {
+ struct device_node *of_node;
+ int num;
+ int mids[MAX_NUM_MIDS];
+ struct list_head node;
+};
+
+#endif
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 1456ea70bbc7..9efbffd6e432 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -582,6 +582,20 @@ config MFD_SPMI_PMIC
Say M here if you want to include support for the SPMI PMIC
series as a module. The module will be called "qcom-spmi-pmic".
+config MFD_QCOM_RPM
+ tristate "Qualcomm Resource Power Manager (RPM)"
+ depends on ARCH_QCOM && OF
+ help
+ If you say yes to this option, support will be included for the
+ Resource Power Manager system found in the Qualcomm 8660, 8960 and
+ 8064 based devices.
+
+ This is required to access many regulators, clocks and bus
+ frequencies controlled by the RPM on these devices.
+
+ Say M here if you want to include support for the Qualcomm RPM as a
+ module. This will build a module called "qcom_rpm".
+
config MFD_RDC321X
tristate "RDC R-321x southbridge"
select MFD_CORE
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 8bd54b1253af..5ca4c7a50e3c 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -154,6 +154,7 @@ obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o
obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o
obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o
obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o
+obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o
obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o
obj-$(CONFIG_MFD_TPS65090) += tps65090.o
obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o
diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c
index 39904f77c049..96b60db0dac9 100644
--- a/drivers/mfd/pm8921-core.c
+++ b/drivers/mfd/pm8921-core.c
@@ -26,6 +26,7 @@
#include <linux/regmap.h>
#include <linux/of_platform.h>
#include <linux/mfd/core.h>
+#include <linux/mfd/pm8921-core.h>
#define SSBI_REG_ADDR_IRQ_BASE 0x1BB
@@ -65,6 +66,41 @@ struct pm_irq_chip {
u8 config[0];
};
+int pm8xxx_read_irq_status(int irq)
+{
+ struct irq_data *d = irq_get_irq_data(irq);
+ struct pm_irq_chip *chip = irq_data_get_irq_chip_data(d);
+ unsigned int pmirq = irqd_to_hwirq(d);
+ unsigned int bits;
+ int irq_bit;
+ u8 block;
+ int rc;
+
+ if (!chip) {
+ pr_err("Failed to resolve pm_irq_chip\n");
+ return -EINVAL;
+ }
+
+ block = pmirq / 8;
+ irq_bit = pmirq % 8;
+
+ spin_lock(&chip->pm_irq_lock);
+ rc = regmap_write(chip->regmap, SSBI_REG_ADDR_IRQ_BLK_SEL, block);
+ if (rc) {
+ pr_err("Failed Selecting Block %d rc=%d\n", block, rc);
+ goto bail;
+ }
+
+ rc = regmap_read(chip->regmap, SSBI_REG_ADDR_IRQ_RT_STATUS, &bits);
+ if (rc)
+ pr_err("Failed Reading Status rc=%d\n", rc);
+bail:
+ spin_unlock(&chip->pm_irq_lock);
+
+ return rc ? rc : !!(bits & BIT(irq_bit));
+}
+EXPORT_SYMBOL(pm8xxx_read_irq_status);
+
static int pm8xxx_read_block_irq(struct pm_irq_chip *chip, unsigned int bp,
unsigned int *ip)
{
diff --git a/drivers/mfd/qcom_rpm.c b/drivers/mfd/qcom_rpm.c
new file mode 100644
index 000000000000..0dd7a6fe518f
--- /dev/null
+++ b/drivers/mfd/qcom_rpm.c
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2014, Sony Mobile Communications AB.
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ * Author: Bjorn Andersson <bjorn.andersson@sonymobile.com>
+ *
+ * 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/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/qcom_rpm.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/mfd/qcom-rpm.h>
+
+struct qcom_rpm_resource {
+ unsigned target_id;
+ unsigned status_id;
+ unsigned select_id;
+ unsigned size;
+};
+
+struct qcom_rpm_data {
+ u32 version;
+ const struct qcom_rpm_resource *resource_table;
+ unsigned n_resources;
+};
+
+struct qcom_rpm {
+ struct device *dev;
+ struct regmap *ipc_regmap;
+ unsigned ipc_offset;
+ unsigned ipc_bit;
+
+ struct completion ack;
+ struct mutex lock;
+
+ void __iomem *status_regs;
+ void __iomem *ctrl_regs;
+ void __iomem *req_regs;
+
+ u32 ack_status;
+
+ const struct qcom_rpm_data *data;
+};
+
+#define RPM_STATUS_REG(rpm, i) ((rpm)->status_regs + (i) * 4)
+#define RPM_CTRL_REG(rpm, i) ((rpm)->ctrl_regs + (i) * 4)
+#define RPM_REQ_REG(rpm, i) ((rpm)->req_regs + (i) * 4)
+
+#define RPM_REQUEST_TIMEOUT (5 * HZ)
+
+#define RPM_REQUEST_CONTEXT 3
+#define RPM_REQ_SELECT 11
+#define RPM_ACK_CONTEXT 15
+#define RPM_ACK_SELECTOR 23
+#define RPM_SELECT_SIZE 7
+
+#define RPM_ACTIVE_STATE BIT(0)
+#define RPM_NOTIFICATION BIT(30)
+#define RPM_REJECTED BIT(31)
+
+#define RPM_SIGNAL BIT(2)
+
+static const struct qcom_rpm_resource apq8064_rpm_resource_table[] = {
+ [QCOM_RPM_CXO_CLK] = { 25, 9, 5, 1 },
+ [QCOM_RPM_PXO_CLK] = { 26, 10, 6, 1 },
+ [QCOM_RPM_APPS_FABRIC_CLK] = { 27, 11, 8, 1 },
+ [QCOM_RPM_SYS_FABRIC_CLK] = { 28, 12, 9, 1 },
+ [QCOM_RPM_MM_FABRIC_CLK] = { 29, 13, 10, 1 },
+ [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 30, 14, 11, 1 },
+ [QCOM_RPM_SFPB_CLK] = { 31, 15, 12, 1 },
+ [QCOM_RPM_CFPB_CLK] = { 32, 16, 13, 1 },
+ [QCOM_RPM_MMFPB_CLK] = { 33, 17, 14, 1 },
+ [QCOM_RPM_EBI1_CLK] = { 34, 18, 16, 1 },
+ [QCOM_RPM_APPS_FABRIC_HALT] = { 35, 19, 18, 1 },
+ [QCOM_RPM_APPS_FABRIC_MODE] = { 37, 20, 19, 1 },
+ [QCOM_RPM_APPS_FABRIC_IOCTL] = { 40, 21, 20, 1 },
+ [QCOM_RPM_APPS_FABRIC_ARB] = { 41, 22, 21, 12 },
+ [QCOM_RPM_SYS_FABRIC_HALT] = { 53, 23, 22, 1 },
+ [QCOM_RPM_SYS_FABRIC_MODE] = { 55, 24, 23, 1 },
+ [QCOM_RPM_SYS_FABRIC_IOCTL] = { 58, 25, 24, 1 },
+ [QCOM_RPM_SYS_FABRIC_ARB] = { 59, 26, 25, 30 },
+ [QCOM_RPM_MM_FABRIC_HALT] = { 89, 27, 26, 1 },
+ [QCOM_RPM_MM_FABRIC_MODE] = { 91, 28, 27, 1 },
+ [QCOM_RPM_MM_FABRIC_IOCTL] = { 94, 29, 28, 1 },
+ [QCOM_RPM_MM_FABRIC_ARB] = { 95, 30, 29, 21 },
+ [QCOM_RPM_PM8921_SMPS1] = { 116, 31, 30, 2 },
+ [QCOM_RPM_PM8921_SMPS2] = { 118, 33, 31, 2 },
+ [QCOM_RPM_PM8921_SMPS3] = { 120, 35, 32, 2 },
+ [QCOM_RPM_PM8921_SMPS4] = { 122, 37, 33, 2 },
+ [QCOM_RPM_PM8921_SMPS5] = { 124, 39, 34, 2 },
+ [QCOM_RPM_PM8921_SMPS6] = { 126, 41, 35, 2 },
+ [QCOM_RPM_PM8921_SMPS7] = { 128, 43, 36, 2 },
+ [QCOM_RPM_PM8921_SMPS8] = { 130, 45, 37, 2 },
+ [QCOM_RPM_PM8921_LDO1] = { 132, 47, 38, 2 },
+ [QCOM_RPM_PM8921_LDO2] = { 134, 49, 39, 2 },
+ [QCOM_RPM_PM8921_LDO3] = { 136, 51, 40, 2 },
+ [QCOM_RPM_PM8921_LDO4] = { 138, 53, 41, 2 },
+ [QCOM_RPM_PM8921_LDO5] = { 140, 55, 42, 2 },
+ [QCOM_RPM_PM8921_LDO6] = { 142, 57, 43, 2 },
+ [QCOM_RPM_PM8921_LDO7] = { 144, 59, 44, 2 },
+ [QCOM_RPM_PM8921_LDO8] = { 146, 61, 45, 2 },
+ [QCOM_RPM_PM8921_LDO9] = { 148, 63, 46, 2 },
+ [QCOM_RPM_PM8921_LDO10] = { 150, 65, 47, 2 },
+ [QCOM_RPM_PM8921_LDO11] = { 152, 67, 48, 2 },
+ [QCOM_RPM_PM8921_LDO12] = { 154, 69, 49, 2 },
+ [QCOM_RPM_PM8921_LDO13] = { 156, 71, 50, 2 },
+ [QCOM_RPM_PM8921_LDO14] = { 158, 73, 51, 2 },
+ [QCOM_RPM_PM8921_LDO15] = { 160, 75, 52, 2 },
+ [QCOM_RPM_PM8921_LDO16] = { 162, 77, 53, 2 },
+ [QCOM_RPM_PM8921_LDO17] = { 164, 79, 54, 2 },
+ [QCOM_RPM_PM8921_LDO18] = { 166, 81, 55, 2 },
+ [QCOM_RPM_PM8921_LDO19] = { 168, 83, 56, 2 },
+ [QCOM_RPM_PM8921_LDO20] = { 170, 85, 57, 2 },
+ [QCOM_RPM_PM8921_LDO21] = { 172, 87, 58, 2 },
+ [QCOM_RPM_PM8921_LDO22] = { 174, 89, 59, 2 },
+ [QCOM_RPM_PM8921_LDO23] = { 176, 91, 60, 2 },
+ [QCOM_RPM_PM8921_LDO24] = { 178, 93, 61, 2 },
+ [QCOM_RPM_PM8921_LDO25] = { 180, 95, 62, 2 },
+ [QCOM_RPM_PM8921_LDO26] = { 182, 97, 63, 2 },
+ [QCOM_RPM_PM8921_LDO27] = { 184, 99, 64, 2 },
+ [QCOM_RPM_PM8921_LDO28] = { 186, 101, 65, 2 },
+ [QCOM_RPM_PM8921_LDO29] = { 188, 103, 66, 2 },
+ [QCOM_RPM_PM8921_CLK1] = { 190, 105, 67, 2 },
+ [QCOM_RPM_PM8921_CLK2] = { 192, 107, 68, 2 },
+ [QCOM_RPM_PM8921_LVS1] = { 194, 109, 69, 1 },
+ [QCOM_RPM_PM8921_LVS2] = { 195, 110, 70, 1 },
+ [QCOM_RPM_PM8921_LVS3] = { 196, 111, 71, 1 },
+ [QCOM_RPM_PM8921_LVS4] = { 197, 112, 72, 1 },
+ [QCOM_RPM_PM8921_LVS5] = { 198, 113, 73, 1 },
+ [QCOM_RPM_PM8921_LVS6] = { 199, 114, 74, 1 },
+ [QCOM_RPM_PM8921_LVS7] = { 200, 115, 75, 1 },
+ [QCOM_RPM_PM8821_SMPS1] = { 201, 116, 76, 2 },
+ [QCOM_RPM_PM8821_SMPS2] = { 203, 118, 77, 2 },
+ [QCOM_RPM_PM8821_LDO1] = { 205, 120, 78, 2 },
+ [QCOM_RPM_PM8921_NCP] = { 207, 122, 80, 2 },
+ [QCOM_RPM_CXO_BUFFERS] = { 209, 124, 81, 1 },
+ [QCOM_RPM_USB_OTG_SWITCH] = { 210, 125, 82, 1 },
+ [QCOM_RPM_HDMI_SWITCH] = { 211, 126, 83, 1 },
+ [QCOM_RPM_DDR_DMM] = { 212, 127, 84, 2 },
+ [QCOM_RPM_VDDMIN_GPIO] = { 215, 131, 89, 1 },
+};
+
+static const struct qcom_rpm_data apq8064_template = {
+ .version = 3,
+ .resource_table = apq8064_rpm_resource_table,
+ .n_resources = ARRAY_SIZE(apq8064_rpm_resource_table),
+};
+
+static const struct qcom_rpm_resource msm8660_rpm_resource_table[] = {
+ [QCOM_RPM_CXO_CLK] = { 32, 12, 5, 1 },
+ [QCOM_RPM_PXO_CLK] = { 33, 13, 6, 1 },
+ [QCOM_RPM_PLL_4] = { 34, 14, 7, 1 },
+ [QCOM_RPM_APPS_FABRIC_CLK] = { 35, 15, 8, 1 },
+ [QCOM_RPM_SYS_FABRIC_CLK] = { 36, 16, 9, 1 },
+ [QCOM_RPM_MM_FABRIC_CLK] = { 37, 17, 10, 1 },
+ [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 38, 18, 11, 1 },
+ [QCOM_RPM_SFPB_CLK] = { 39, 19, 12, 1 },
+ [QCOM_RPM_CFPB_CLK] = { 40, 20, 13, 1 },
+ [QCOM_RPM_MMFPB_CLK] = { 41, 21, 14, 1 },
+ [QCOM_RPM_SMI_CLK] = { 42, 22, 15, 1 },
+ [QCOM_RPM_EBI1_CLK] = { 43, 23, 16, 1 },
+ [QCOM_RPM_APPS_L2_CACHE_CTL] = { 44, 24, 17, 1 },
+ [QCOM_RPM_APPS_FABRIC_HALT] = { 45, 25, 18, 2 },
+ [QCOM_RPM_APPS_FABRIC_MODE] = { 47, 26, 19, 3 },
+ [QCOM_RPM_APPS_FABRIC_ARB] = { 51, 28, 21, 6 },
+ [QCOM_RPM_SYS_FABRIC_HALT] = { 63, 29, 22, 2 },
+ [QCOM_RPM_SYS_FABRIC_MODE] = { 65, 30, 23, 3 },
+ [QCOM_RPM_SYS_FABRIC_ARB] = { 69, 32, 25, 22 },
+ [QCOM_RPM_MM_FABRIC_HALT] = { 105, 33, 26, 2 },
+ [QCOM_RPM_MM_FABRIC_MODE] = { 107, 34, 27, 3 },
+ [QCOM_RPM_MM_FABRIC_ARB] = { 111, 36, 29, 23 },
+ [QCOM_RPM_PM8901_SMPS0] = { 134, 37, 30, 2 },
+ [QCOM_RPM_PM8901_SMPS1] = { 136, 39, 31, 2 },
+ [QCOM_RPM_PM8901_SMPS2] = { 138, 41, 32, 2 },
+ [QCOM_RPM_PM8901_SMPS3] = { 140, 43, 33, 2 },
+ [QCOM_RPM_PM8901_SMPS4] = { 142, 45, 34, 2 },
+ [QCOM_RPM_PM8901_LDO0] = { 144, 47, 35, 2 },
+ [QCOM_RPM_PM8901_LDO1] = { 146, 49, 36, 2 },
+ [QCOM_RPM_PM8901_LDO2] = { 148, 51, 37, 2 },
+ [QCOM_RPM_PM8901_LDO3] = { 150, 53, 38, 2 },
+ [QCOM_RPM_PM8901_LDO4] = { 152, 55, 39, 2 },
+ [QCOM_RPM_PM8901_LDO5] = { 154, 57, 40, 2 },
+ [QCOM_RPM_PM8901_LDO6] = { 156, 59, 41, 2 },
+ [QCOM_RPM_PM8901_LVS0] = { 158, 61, 42, 1 },
+ [QCOM_RPM_PM8901_LVS1] = { 159, 62, 43, 1 },
+ [QCOM_RPM_PM8901_LVS2] = { 160, 63, 44, 1 },
+ [QCOM_RPM_PM8901_LVS3] = { 161, 64, 45, 1 },
+ [QCOM_RPM_PM8901_MVS] = { 162, 65, 46, 1 },
+ [QCOM_RPM_PM8058_SMPS0] = { 163, 66, 47, 2 },
+ [QCOM_RPM_PM8058_SMPS1] = { 165, 68, 48, 2 },
+ [QCOM_RPM_PM8058_SMPS2] = { 167, 70, 49, 2 },
+ [QCOM_RPM_PM8058_SMPS3] = { 169, 72, 50, 2 },
+ [QCOM_RPM_PM8058_SMPS4] = { 171, 74, 51, 2 },
+ [QCOM_RPM_PM8058_LDO0] = { 173, 76, 52, 2 },
+ [QCOM_RPM_PM8058_LDO1] = { 175, 78, 53, 2 },
+ [QCOM_RPM_PM8058_LDO2] = { 177, 80, 54, 2 },
+ [QCOM_RPM_PM8058_LDO3] = { 179, 82, 55, 2 },
+ [QCOM_RPM_PM8058_LDO4] = { 181, 84, 56, 2 },
+ [QCOM_RPM_PM8058_LDO5] = { 183, 86, 57, 2 },
+ [QCOM_RPM_PM8058_LDO6] = { 185, 88, 58, 2 },
+ [QCOM_RPM_PM8058_LDO7] = { 187, 90, 59, 2 },
+ [QCOM_RPM_PM8058_LDO8] = { 189, 92, 60, 2 },
+ [QCOM_RPM_PM8058_LDO9] = { 191, 94, 61, 2 },
+ [QCOM_RPM_PM8058_LDO10] = { 193, 96, 62, 2 },
+ [QCOM_RPM_PM8058_LDO11] = { 195, 98, 63, 2 },
+ [QCOM_RPM_PM8058_LDO12] = { 197, 100, 64, 2 },
+ [QCOM_RPM_PM8058_LDO13] = { 199, 102, 65, 2 },
+ [QCOM_RPM_PM8058_LDO14] = { 201, 104, 66, 2 },
+ [QCOM_RPM_PM8058_LDO15] = { 203, 106, 67, 2 },
+ [QCOM_RPM_PM8058_LDO16] = { 205, 108, 68, 2 },
+ [QCOM_RPM_PM8058_LDO17] = { 207, 110, 69, 2 },
+ [QCOM_RPM_PM8058_LDO18] = { 209, 112, 70, 2 },
+ [QCOM_RPM_PM8058_LDO19] = { 211, 114, 71, 2 },
+ [QCOM_RPM_PM8058_LDO20] = { 213, 116, 72, 2 },
+ [QCOM_RPM_PM8058_LDO21] = { 215, 118, 73, 2 },
+ [QCOM_RPM_PM8058_LDO22] = { 217, 120, 74, 2 },
+ [QCOM_RPM_PM8058_LDO23] = { 219, 122, 75, 2 },
+ [QCOM_RPM_PM8058_LDO24] = { 221, 124, 76, 2 },
+ [QCOM_RPM_PM8058_LDO25] = { 223, 126, 77, 2 },
+ [QCOM_RPM_PM8058_LVS0] = { 225, 128, 78, 1 },
+ [QCOM_RPM_PM8058_LVS1] = { 226, 129, 79, 1 },
+ [QCOM_RPM_PM8058_NCP] = { 227, 130, 80, 2 },
+ [QCOM_RPM_CXO_BUFFERS] = { 229, 132, 81, 1 },
+};
+
+static const struct qcom_rpm_data msm8660_template = {
+ .version = 2,
+ .resource_table = msm8660_rpm_resource_table,
+ .n_resources = ARRAY_SIZE(msm8660_rpm_resource_table),
+};
+
+static const struct qcom_rpm_resource msm8960_rpm_resource_table[] = {
+ [QCOM_RPM_CXO_CLK] = { 25, 9, 5, 1 },
+ [QCOM_RPM_PXO_CLK] = { 26, 10, 6, 1 },
+ [QCOM_RPM_APPS_FABRIC_CLK] = { 27, 11, 8, 1 },
+ [QCOM_RPM_SYS_FABRIC_CLK] = { 28, 12, 9, 1 },
+ [QCOM_RPM_MM_FABRIC_CLK] = { 29, 13, 10, 1 },
+ [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 30, 14, 11, 1 },
+ [QCOM_RPM_SFPB_CLK] = { 31, 15, 12, 1 },
+ [QCOM_RPM_CFPB_CLK] = { 32, 16, 13, 1 },
+ [QCOM_RPM_MMFPB_CLK] = { 33, 17, 14, 1 },
+ [QCOM_RPM_EBI1_CLK] = { 34, 18, 16, 1 },
+ [QCOM_RPM_APPS_FABRIC_HALT] = { 35, 19, 18, 1 },
+ [QCOM_RPM_APPS_FABRIC_MODE] = { 37, 20, 19, 1 },
+ [QCOM_RPM_APPS_FABRIC_IOCTL] = { 40, 21, 20, 1 },
+ [QCOM_RPM_APPS_FABRIC_ARB] = { 41, 22, 21, 12 },
+ [QCOM_RPM_SYS_FABRIC_HALT] = { 53, 23, 22, 1 },
+ [QCOM_RPM_SYS_FABRIC_MODE] = { 55, 24, 23, 1 },
+ [QCOM_RPM_SYS_FABRIC_IOCTL] = { 58, 25, 24, 1 },
+ [QCOM_RPM_SYS_FABRIC_ARB] = { 59, 26, 25, 29 },
+ [QCOM_RPM_MM_FABRIC_HALT] = { 88, 27, 26, 1 },
+ [QCOM_RPM_MM_FABRIC_MODE] = { 90, 28, 27, 1 },
+ [QCOM_RPM_MM_FABRIC_IOCTL] = { 93, 29, 28, 1 },
+ [QCOM_RPM_MM_FABRIC_ARB] = { 94, 30, 29, 23 },
+ [QCOM_RPM_PM8921_SMPS1] = { 117, 31, 30, 2 },
+ [QCOM_RPM_PM8921_SMPS2] = { 119, 33, 31, 2 },
+ [QCOM_RPM_PM8921_SMPS3] = { 121, 35, 32, 2 },
+ [QCOM_RPM_PM8921_SMPS4] = { 123, 37, 33, 2 },
+ [QCOM_RPM_PM8921_SMPS5] = { 125, 39, 34, 2 },
+ [QCOM_RPM_PM8921_SMPS6] = { 127, 41, 35, 2 },
+ [QCOM_RPM_PM8921_SMPS7] = { 129, 43, 36, 2 },
+ [QCOM_RPM_PM8921_SMPS8] = { 131, 45, 37, 2 },
+ [QCOM_RPM_PM8921_LDO1] = { 133, 47, 38, 2 },
+ [QCOM_RPM_PM8921_LDO2] = { 135, 49, 39, 2 },
+ [QCOM_RPM_PM8921_LDO3] = { 137, 51, 40, 2 },
+ [QCOM_RPM_PM8921_LDO4] = { 139, 53, 41, 2 },
+ [QCOM_RPM_PM8921_LDO5] = { 141, 55, 42, 2 },
+ [QCOM_RPM_PM8921_LDO6] = { 143, 57, 43, 2 },
+ [QCOM_RPM_PM8921_LDO7] = { 145, 59, 44, 2 },
+ [QCOM_RPM_PM8921_LDO8] = { 147, 61, 45, 2 },
+ [QCOM_RPM_PM8921_LDO9] = { 149, 63, 46, 2 },
+ [QCOM_RPM_PM8921_LDO10] = { 151, 65, 47, 2 },
+ [QCOM_RPM_PM8921_LDO11] = { 153, 67, 48, 2 },
+ [QCOM_RPM_PM8921_LDO12] = { 155, 69, 49, 2 },
+ [QCOM_RPM_PM8921_LDO13] = { 157, 71, 50, 2 },
+ [QCOM_RPM_PM8921_LDO14] = { 159, 73, 51, 2 },
+ [QCOM_RPM_PM8921_LDO15] = { 161, 75, 52, 2 },
+ [QCOM_RPM_PM8921_LDO16] = { 163, 77, 53, 2 },
+ [QCOM_RPM_PM8921_LDO17] = { 165, 79, 54, 2 },
+ [QCOM_RPM_PM8921_LDO18] = { 167, 81, 55, 2 },
+ [QCOM_RPM_PM8921_LDO19] = { 169, 83, 56, 2 },
+ [QCOM_RPM_PM8921_LDO20] = { 171, 85, 57, 2 },
+ [QCOM_RPM_PM8921_LDO21] = { 173, 87, 58, 2 },
+ [QCOM_RPM_PM8921_LDO22] = { 175, 89, 59, 2 },
+ [QCOM_RPM_PM8921_LDO23] = { 177, 91, 60, 2 },
+ [QCOM_RPM_PM8921_LDO24] = { 179, 93, 61, 2 },
+ [QCOM_RPM_PM8921_LDO25] = { 181, 95, 62, 2 },
+ [QCOM_RPM_PM8921_LDO26] = { 183, 97, 63, 2 },
+ [QCOM_RPM_PM8921_LDO27] = { 185, 99, 64, 2 },
+ [QCOM_RPM_PM8921_LDO28] = { 187, 101, 65, 2 },
+ [QCOM_RPM_PM8921_LDO29] = { 189, 103, 66, 2 },
+ [QCOM_RPM_PM8921_CLK1] = { 191, 105, 67, 2 },
+ [QCOM_RPM_PM8921_CLK2] = { 193, 107, 68, 2 },
+ [QCOM_RPM_PM8921_LVS1] = { 195, 109, 69, 1 },
+ [QCOM_RPM_PM8921_LVS2] = { 196, 110, 70, 1 },
+ [QCOM_RPM_PM8921_LVS3] = { 197, 111, 71, 1 },
+ [QCOM_RPM_PM8921_LVS4] = { 198, 112, 72, 1 },
+ [QCOM_RPM_PM8921_LVS5] = { 199, 113, 73, 1 },
+ [QCOM_RPM_PM8921_LVS6] = { 200, 114, 74, 1 },
+ [QCOM_RPM_PM8921_LVS7] = { 201, 115, 75, 1 },
+ [QCOM_RPM_PM8921_NCP] = { 202, 116, 80, 2 },
+ [QCOM_RPM_CXO_BUFFERS] = { 204, 118, 81, 1 },
+ [QCOM_RPM_USB_OTG_SWITCH] = { 205, 119, 82, 1 },
+ [QCOM_RPM_HDMI_SWITCH] = { 206, 120, 83, 1 },
+ [QCOM_RPM_DDR_DMM] = { 207, 121, 84, 2 },
+};
+
+static const struct qcom_rpm_data msm8960_template = {
+ .version = 3,
+ .resource_table = msm8960_rpm_resource_table,
+ .n_resources = ARRAY_SIZE(msm8960_rpm_resource_table),
+};
+
+static const struct of_device_id qcom_rpm_of_match[] = {
+ { .compatible = "qcom,rpm-apq8064", .data = &apq8064_template },
+ { .compatible = "qcom,rpm-msm8660", .data = &msm8660_template },
+ { .compatible = "qcom,rpm-msm8960", .data = &msm8960_template },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qcom_rpm_of_match);
+
+int qcom_rpm_write(struct qcom_rpm *rpm, int resource, u32 *buf, size_t count)
+{
+ const struct qcom_rpm_resource *res;
+ const struct qcom_rpm_data *data = rpm->data;
+ u32 sel_mask[RPM_SELECT_SIZE] = { 0 };
+ int left;
+ int ret = 0;
+ int i;
+
+ if (WARN_ON(resource < 0 || resource >= data->n_resources))
+ return -EINVAL;
+
+ res = &data->resource_table[resource];
+ if (WARN_ON(res->size != count))
+ return -EINVAL;
+
+ mutex_lock(&rpm->lock);
+
+ for (i = 0; i < res->size; i++)
+ writel_relaxed(buf[i], RPM_REQ_REG(rpm, res->target_id + i));
+
+ bitmap_set((unsigned long *)sel_mask, res->select_id, 1);
+ for (i = 0; i < ARRAY_SIZE(sel_mask); i++) {
+ writel_relaxed(sel_mask[i],
+ RPM_CTRL_REG(rpm, RPM_REQ_SELECT + i));
+ }
+
+ writel_relaxed(RPM_ACTIVE_STATE,
+ RPM_CTRL_REG(rpm, RPM_REQUEST_CONTEXT));
+
+ reinit_completion(&rpm->ack);
+ regmap_write(rpm->ipc_regmap, rpm->ipc_offset, BIT(rpm->ipc_bit));
+
+ left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT);
+ if (!left)
+ ret = -ETIMEDOUT;
+ else if (rpm->ack_status & RPM_REJECTED)
+ ret = -EIO;
+
+ mutex_unlock(&rpm->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(qcom_rpm_write);
+
+static irqreturn_t qcom_rpm_ack_interrupt(int irq, void *dev)
+{
+ struct qcom_rpm *rpm = dev;
+ u32 ack;
+ int i;
+
+ ack = readl_relaxed(RPM_CTRL_REG(rpm, RPM_ACK_CONTEXT));
+ for (i = 0; i < RPM_SELECT_SIZE; i++)
+ writel_relaxed(0, RPM_CTRL_REG(rpm, RPM_ACK_SELECTOR + i));
+ writel(0, RPM_CTRL_REG(rpm, RPM_ACK_CONTEXT));
+
+ if (ack & RPM_NOTIFICATION) {
+ dev_warn(rpm->dev, "ignoring notification!\n");
+ } else {
+ rpm->ack_status = ack;
+ complete(&rpm->ack);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qcom_rpm_err_interrupt(int irq, void *dev)
+{
+ struct qcom_rpm *rpm = dev;
+
+ regmap_write(rpm->ipc_regmap, rpm->ipc_offset, BIT(rpm->ipc_bit));
+ dev_err(rpm->dev, "RPM triggered fatal error\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qcom_rpm_wakeup_interrupt(int irq, void *dev)
+{
+ return IRQ_HANDLED;
+}
+
+static int qcom_rpm_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct device_node *syscon_np;
+ struct resource *res;
+ struct qcom_rpm *rpm;
+ u32 fw_version[3];
+ int irq_wakeup;
+ int irq_ack;
+ int irq_err;
+ int ret;
+
+ rpm = devm_kzalloc(&pdev->dev, sizeof(*rpm), GFP_KERNEL);
+ if (!rpm)
+ return -ENOMEM;
+
+ rpm->dev = &pdev->dev;
+ mutex_init(&rpm->lock);
+ init_completion(&rpm->ack);
+
+ irq_ack = platform_get_irq_byname(pdev, "ack");
+ if (irq_ack < 0) {
+ dev_err(&pdev->dev, "required ack interrupt missing\n");
+ return irq_ack;
+ }
+
+ irq_err = platform_get_irq_byname(pdev, "err");
+ if (irq_err < 0) {
+ dev_err(&pdev->dev, "required err interrupt missing\n");
+ return irq_err;
+ }
+
+ irq_wakeup = platform_get_irq_byname(pdev, "wakeup");
+ if (irq_wakeup < 0) {
+ dev_err(&pdev->dev, "required wakeup interrupt missing\n");
+ return irq_wakeup;
+ }
+
+ match = of_match_device(qcom_rpm_of_match, &pdev->dev);
+ rpm->data = match->data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ rpm->status_regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(rpm->status_regs))
+ return PTR_ERR(rpm->status_regs);
+ rpm->ctrl_regs = rpm->status_regs + 0x400;
+ rpm->req_regs = rpm->status_regs + 0x600;
+
+ syscon_np = of_parse_phandle(pdev->dev.of_node, "qcom,ipc", 0);
+ if (!syscon_np) {
+ dev_err(&pdev->dev, "no qcom,ipc node\n");
+ return -ENODEV;
+ }
+
+ rpm->ipc_regmap = syscon_node_to_regmap(syscon_np);
+ if (IS_ERR(rpm->ipc_regmap))
+ return PTR_ERR(rpm->ipc_regmap);
+
+ ret = of_property_read_u32_index(pdev->dev.of_node, "qcom,ipc", 1,
+ &rpm->ipc_offset);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "no offset in qcom,ipc\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_index(pdev->dev.of_node, "qcom,ipc", 2,
+ &rpm->ipc_bit);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "no bit in qcom,ipc\n");
+ return -EINVAL;
+ }
+
+ dev_set_drvdata(&pdev->dev, rpm);
+
+ fw_version[0] = readl(RPM_STATUS_REG(rpm, 0));
+ fw_version[1] = readl(RPM_STATUS_REG(rpm, 1));
+ fw_version[2] = readl(RPM_STATUS_REG(rpm, 2));
+ if (fw_version[0] != rpm->data->version) {
+ dev_err(&pdev->dev,
+ "RPM version %u.%u.%u incompatible with driver version %u",
+ fw_version[0],
+ fw_version[1],
+ fw_version[2],
+ rpm->data->version);
+ return -EFAULT;
+ }
+
+ dev_info(&pdev->dev, "RPM firmware %u.%u.%u\n", fw_version[0],
+ fw_version[1],
+ fw_version[2]);
+
+ ret = devm_request_irq(&pdev->dev,
+ irq_ack,
+ qcom_rpm_ack_interrupt,
+ IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND,
+ "qcom_rpm_ack",
+ rpm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request ack interrupt\n");
+ return ret;
+ }
+
+ ret = irq_set_irq_wake(irq_ack, 1);
+ if (ret)
+ dev_warn(&pdev->dev, "failed to mark ack irq as wakeup\n");
+
+ ret = devm_request_irq(&pdev->dev,
+ irq_err,
+ qcom_rpm_err_interrupt,
+ IRQF_TRIGGER_RISING,
+ "qcom_rpm_err",
+ rpm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request err interrupt\n");
+ return ret;
+ }
+
+ ret = devm_request_irq(&pdev->dev,
+ irq_wakeup,
+ qcom_rpm_wakeup_interrupt,
+ IRQF_TRIGGER_RISING,
+ "qcom_rpm_wakeup",
+ rpm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request wakeup interrupt\n");
+ return ret;
+ }
+
+ ret = irq_set_irq_wake(irq_wakeup, 1);
+ if (ret)
+ dev_warn(&pdev->dev, "failed to mark wakeup irq as wakeup\n");
+
+ return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+}
+
+static int qcom_rpm_remove(struct platform_device *pdev)
+{
+ of_platform_depopulate(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver qcom_rpm_driver = {
+ .probe = qcom_rpm_probe,
+ .remove = qcom_rpm_remove,
+ .driver = {
+ .name = "qcom_rpm",
+ .of_match_table = qcom_rpm_of_match,
+ },
+};
+
+static int __init qcom_rpm_init(void)
+{
+ return platform_driver_register(&qcom_rpm_driver);
+}
+arch_initcall(qcom_rpm_init);
+
+static void __exit qcom_rpm_exit(void)
+{
+ platform_driver_unregister(&qcom_rpm_driver);
+}
+module_exit(qcom_rpm_exit)
+
+MODULE_DESCRIPTION("Qualcomm Resource Power Manager driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
diff --git a/drivers/mfd/ssbi.c b/drivers/mfd/ssbi.c
index b78942ed4c67..4fbe02ed74da 100644
--- a/drivers/mfd/ssbi.c
+++ b/drivers/mfd/ssbi.c
@@ -331,7 +331,12 @@ static struct platform_driver ssbi_driver = {
.of_match_table = ssbi_match_table,
},
};
-module_platform_driver(ssbi_driver);
+
+static int ssbi_init(void)
+{
+ return platform_driver_register(&ssbi_driver);
+}
+subsys_initcall(ssbi_init);
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index f26a5f1d926d..bfe6bef6ed51 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -538,8 +538,18 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host,
if (host->card && mmc_card_mmc(host->card) &&
((mmc_resp_type(host->areq->mrq->cmd) == MMC_RSP_R1) ||
(mmc_resp_type(host->areq->mrq->cmd) == MMC_RSP_R1B)) &&
- (host->areq->mrq->cmd->resp[0] & R1_EXCEPTION_EVENT))
+ (host->areq->mrq->cmd->resp[0] & R1_EXCEPTION_EVENT)) {
+
+ /* Cancel the prepared request */
+ if (areq)
+ mmc_post_req(host, areq->mrq, -EINVAL);
+
mmc_start_bkops(host->card, true);
+
+ /* prepare the request again */
+ if (areq)
+ mmc_pre_req(host, areq->mrq, !host->areq);
+ }
}
if (!err && areq)
diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c
index 43af791e2e45..f31d70201ad7 100644
--- a/drivers/mmc/host/mmci.c
+++ b/drivers/mmc/host/mmci.c
@@ -78,6 +78,7 @@ static unsigned int fmax = 515633;
* @qcom_fifo: enables qcom specific fifo pio read logic.
* @qcom_dml: enables qcom specific dma glue for dma transfers.
* @reversed_irq_handling: handle data irq before cmd irq.
+ * @any_blksize: true if block any sizes are supported
*/
struct variant_data {
unsigned int clkreg;
@@ -104,6 +105,7 @@ struct variant_data {
bool qcom_fifo;
bool qcom_dml;
bool reversed_irq_handling;
+ bool any_blksize;
};
static struct variant_data variant_arm = {
@@ -200,6 +202,7 @@ static struct variant_data variant_ux500v2 = {
.pwrreg_clkgate = true,
.busy_detect = true,
.pwrreg_nopower = true,
+ .any_blksize = true,
};
static struct variant_data variant_qcom = {
@@ -218,6 +221,7 @@ static struct variant_data variant_qcom = {
.explicit_mclk_control = true,
.qcom_fifo = true,
.qcom_dml = true,
+ .any_blksize = true,
};
static int mmci_card_busy(struct mmc_host *mmc)
@@ -245,10 +249,11 @@ static int mmci_card_busy(struct mmc_host *mmc)
static int mmci_validate_data(struct mmci_host *host,
struct mmc_data *data)
{
+ struct variant_data *variant = host->variant;
+
if (!data)
return 0;
-
- if (!is_power_of_2(data->blksz)) {
+ if (!is_power_of_2(data->blksz) && !variant->any_blksize) {
dev_err(mmc_dev(host->mmc),
"unsupported block size (%d bytes)\n", data->blksz);
return -EINVAL;
@@ -736,8 +741,15 @@ static void mmci_post_request(struct mmc_host *mmc, struct mmc_request *mrq,
chan = host->dma_tx_channel;
dmaengine_terminate_all(chan);
+ if (host->dma_desc_current == next->dma_desc)
+ host->dma_desc_current = NULL;
+
+ if (host->dma_current == next->dma_chan)
+ host->dma_current = NULL;
+
next->dma_desc = NULL;
next->dma_chan = NULL;
+ data->host_cookie = 0;
}
}
@@ -802,7 +814,6 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data)
writel(host->size, base + MMCIDATALENGTH);
blksz_bits = ffs(data->blksz) - 1;
- BUG_ON(1 << blksz_bits != data->blksz);
if (variant->blksz_datactrl16)
datactrl = MCI_DPSM_ENABLE | (data->blksz << 16);
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
index 26b3461d68d7..7225882bf4b4 100644
--- a/drivers/pci/host/Makefile
+++ b/drivers/pci/host/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spear13xx.o
obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o
obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o
obj-$(CONFIG_PCI_XGENE) += pci-xgene.o
+obj-$(CONFIG_ARCH_QCOM) += pci-qcom.o
diff --git a/drivers/pci/host/pci-qcom.c b/drivers/pci/host/pci-qcom.c
new file mode 100644
index 000000000000..67d3a497a13a
--- /dev/null
+++ b/drivers/pci/host/pci-qcom.c
@@ -0,0 +1,968 @@
+/* Copyright (c) 2012-2014, 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 MSM PCIe controller driver.
+ */
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of_gpio.h>
+#include <linux/msi.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_address.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/delay.h>
+
+#define INT_PCI_MSI_NR (8 * 32)
+#define MSM_PCIE_MSI_PHY 0xa0000000
+
+#define PCIE20_MSI_CTRL_ADDR (0x820)
+#define PCIE20_MSI_CTRL_UPPER_ADDR (0x824)
+#define PCIE20_MSI_CTRL_INTR_EN (0x828)
+#define PCIE20_MSI_CTRL_INTR_MASK (0x82C)
+#define PCIE20_MSI_CTRL_INTR_STATUS (0x830)
+
+#define PCIE20_MSI_CTRL_MAX 8
+/* Root Complex Port vendor/device IDs */
+#define PCIE_VENDOR_ID_RCP 0x17cb
+#define PCIE_DEVICE_ID_RCP 0x0101
+
+#define __set(v, a, b) (((v) << (b)) & GENMASK(a, b))
+
+#define PCIE20_PARF_PCS_DEEMPH 0x34
+#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN1(x) __set(x, 21, 16)
+#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(x) __set(x, 13, 8)
+#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(x) __set(x, 5, 0)
+
+#define PCIE20_PARF_PCS_SWING 0x38
+#define PCIE20_PARF_PCS_SWING_TX_SWING_FULL(x) __set(x, 14, 8)
+#define PCIE20_PARF_PCS_SWING_TX_SWING_LOW(x) __set(x, 6, 0)
+
+#define PCIE20_PARF_PHY_CTRL 0x40
+#define PCIE20_PARF_PHY_CTRL_PHY_TX0_TERM_OFFST(x) __set(x, 20, 16)
+#define PCIE20_PARF_PHY_CTRL_PHY_LOS_LEVEL(x) __set(x, 12, 8)
+#define PCIE20_PARF_PHY_CTRL_PHY_RTUNE_REQ (1 << 4)
+#define PCIE20_PARF_PHY_CTRL_PHY_TEST_BURNIN (1 << 2)
+#define PCIE20_PARF_PHY_CTRL_PHY_TEST_BYPASS (1 << 1)
+#define PCIE20_PARF_PHY_CTRL_PHY_TEST_PWR_DOWN (1 << 0)
+
+#define PCIE20_PARF_PHY_REFCLK 0x4C
+#define PCIE20_PARF_CONFIG_BITS 0x50
+
+#define PCIE20_ELBI_SYS_CTRL 0x04
+#define PCIE20_ELBI_SYS_CTRL_LTSSM_EN 0x01
+
+#define PCIE20_CAP 0x70
+#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10)
+
+#define PCIE20_COMMAND_STATUS 0x04
+#define PCIE20_BUSNUMBERS 0x18
+#define PCIE20_MEMORY_BASE_LIMIT 0x20
+
+#define PCIE20_AXI_MSTR_RESP_COMP_CTRL0 0x818
+#define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c
+#define PCIE20_PLR_IATU_VIEWPORT 0x900
+#define PCIE20_PLR_IATU_CTRL1 0x904
+#define PCIE20_PLR_IATU_CTRL2 0x908
+#define PCIE20_PLR_IATU_LBAR 0x90C
+#define PCIE20_PLR_IATU_UBAR 0x910
+#define PCIE20_PLR_IATU_LAR 0x914
+#define PCIE20_PLR_IATU_LTAR 0x918
+#define PCIE20_PLR_IATU_UTAR 0x91c
+
+#define MSM_PCIE_DEV_CFG_ADDR 0x01000000
+
+#define RD 0
+#define WR 1
+
+#define MAX_RC_NUM 3
+#define PCIE_BUS_PRIV_DATA(pdev) \
+ (((struct pci_sys_data *)pdev->bus->sysdata)->private_data)
+
+/* PCIe TLP types that we are interested in */
+#define PCI_CFG0_RDWR 0x4
+#define PCI_CFG1_RDWR 0x5
+
+#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \
+({ \
+ unsigned long timeout = jiffies + usecs_to_jiffies(timeout_us); \
+ might_sleep_if(timeout_us); \
+ for (;;) { \
+ (val) = readl(addr); \
+ if (cond) \
+ break; \
+ if (timeout_us && time_after(jiffies, timeout)) { \
+ (val) = readl(addr); \
+ break; \
+ } \
+ if (sleep_us) \
+ usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \
+ } \
+ (cond) ? 0 : -ETIMEDOUT; \
+})
+
+struct qcom_msi {
+ struct msi_chip chip;
+ DECLARE_BITMAP(used, INT_PCI_MSI_NR);
+ struct irq_domain *domain;
+ unsigned long pages;
+ struct mutex lock;
+ int irq;
+};
+
+struct qcom_pcie {
+ void __iomem *elbi_base;
+ void __iomem *parf_base;
+ void __iomem *dwc_base;
+ void __iomem *cfg_base;
+ struct device *dev;
+ int reset_gpio;
+ bool ext_phy_ref_clk;
+ struct clk *iface_clk;
+ struct clk *bus_clk;
+ struct clk *phy_clk;
+ int irq_int[4];
+ struct reset_control *axi_reset;
+ struct reset_control *ahb_reset;
+ struct reset_control *por_reset;
+ struct reset_control *pci_reset;
+ struct reset_control *phy_reset;
+
+ struct resource conf;
+ struct resource io;
+ struct resource mem;
+
+ struct regulator *vdd_supply;
+ struct regulator *avdd_supply;
+ struct regulator *pcie_clk_supply;
+
+ struct qcom_msi msi;
+};
+
+static int nr_controllers;
+static DEFINE_SPINLOCK(qcom_hw_pci_lock);
+
+static inline struct qcom_pcie *sys_to_pcie(struct pci_sys_data *sys)
+{
+ return sys->private_data;
+}
+
+static void qcom_pcie_add_bus(struct pci_bus *bus)
+{
+ if (IS_ENABLED(CONFIG_PCI_MSI)) {
+ struct qcom_pcie *pcie = sys_to_pcie(bus->sysdata);
+
+ bus->msi = &pcie->msi.chip;
+ }
+}
+
+inline int is_msm_pcie_rc(struct pci_bus *bus)
+{
+ return (bus->number == 0);
+}
+
+static int qcom_pcie_is_link_up(struct qcom_pcie *dev)
+{
+ return readl_relaxed(dev->dwc_base + PCIE20_CAP_LINKCTRLSTATUS) &
+ BIT(29);
+}
+
+inline int msm_pcie_get_cfgtype(struct pci_bus *bus)
+{
+ /*
+ * http://www.tldp.org/LDP/tlk/dd/pci.html
+ * Pass it onto the secondary bus interface unchanged if the
+ * bus number specified is greater than the secondary bus
+ * number and less than or equal to the subordinate bus
+ * number.
+ *
+ * Read/Write to the RC and Device/Switch connected to the RC
+ * are CFG0 type transactions. Rest have to be forwarded
+ * down stream as CFG1 transactions.
+ *
+ */
+ if (bus->number == 0)
+ return PCI_CFG0_RDWR;
+
+ return PCI_CFG0_RDWR;
+}
+
+void msm_pcie_config_cfgtype(struct pci_bus *bus, u32 devfn)
+{
+ uint32_t bdf, cfgtype;
+ struct qcom_pcie *dev = sys_to_pcie(bus->sysdata);
+
+ cfgtype = msm_pcie_get_cfgtype(bus);
+
+ if (cfgtype == PCI_CFG0_RDWR) {
+ bdf = MSM_PCIE_DEV_CFG_ADDR;
+ } else {
+ /*
+ * iATU Lower Target Address Register
+ * Bits Description
+ * *-1:0 Forms bits [*:0] of the
+ * start address of the new
+ * address of the translated
+ * region. The start address
+ * must be aligned to a
+ * CX_ATU_MIN_REGION_SIZE kB
+ * boundary, so these bits are
+ * always 0. A write to this
+ * location is ignored by the
+ * PCIe core.
+ * 31:*1 Forms bits [31:*] of the of
+ * the new address of the
+ * translated region.
+ *
+ * * is log2(CX_ATU_MIN_REGION_SIZE)
+ */
+ bdf = (((bus->number & 0xff) << 24) & 0xff000000) |
+ (((devfn & 0xff) << 16) & 0x00ff0000);
+ }
+
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT);
+ wmb();
+
+ /* Program Bdf Address */
+ writel_relaxed(bdf, dev->dwc_base + PCIE20_PLR_IATU_LTAR);
+ wmb();
+
+ /* Write Config Request Type */
+ writel_relaxed(cfgtype, dev->dwc_base + PCIE20_PLR_IATU_CTRL1);
+ wmb();
+}
+
+static inline int msm_pcie_oper_conf(struct pci_bus *bus, u32 devfn, int oper,
+ int where, int size, u32 *val)
+{
+ uint32_t word_offset, byte_offset, mask;
+ uint32_t rd_val, wr_val;
+ struct qcom_pcie *dev = sys_to_pcie(bus->sysdata);
+ void __iomem *config_base;
+ int rc;
+
+ rc = is_msm_pcie_rc(bus);
+
+ /*
+ * For downstream bus, make sure link is up
+ */
+ if (rc && (devfn != 0)) {
+ *val = ~0;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ } else if ((!rc) && (!qcom_pcie_is_link_up(dev))) {
+ *val = ~0;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ msm_pcie_config_cfgtype(bus, devfn);
+
+ word_offset = where & ~0x3;
+ byte_offset = where & 0x3;
+ mask = (~0 >> (8 * (4 - size))) << (8 * byte_offset);
+
+ config_base = (rc) ? dev->dwc_base : dev->cfg_base;
+ rd_val = readl_relaxed(config_base + word_offset);
+
+ if (oper == RD) {
+ *val = ((rd_val & mask) >> (8 * byte_offset));
+ } else {
+ wr_val = (rd_val & ~mask) |
+ ((*val << (8 * byte_offset)) & mask);
+ writel_relaxed(wr_val, config_base + word_offset);
+ wmb(); /* ensure config data is written to hardware register */
+ }
+
+ return 0;
+}
+
+static int msm_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where,
+ int size, u32 *val)
+{
+ return msm_pcie_oper_conf(bus, devfn, RD, where, size, val);
+}
+
+static int msm_pcie_wr_conf(struct pci_bus *bus, u32 devfn,
+ int where, int size, u32 val)
+{
+ /*
+ *Attempt to reset secondary bus is causing PCIE core to reset.
+ *Disable secondary bus reset functionality.
+ */
+ if ((bus->number == 0) && (where == PCI_BRIDGE_CONTROL) &&
+ (val & PCI_BRIDGE_CTL_BUS_RESET)) {
+ pr_info("PCIE secondary bus reset not supported\n");
+ val &= ~PCI_BRIDGE_CTL_BUS_RESET;
+ }
+
+ return msm_pcie_oper_conf(bus, devfn, WR, where, size, &val);
+}
+
+static int qcom_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+{
+ struct qcom_pcie *pcie_dev = PCIE_BUS_PRIV_DATA(dev);
+
+ return pcie_dev->irq_int[pin-1];
+}
+
+static int qcom_pcie_setup(int nr, struct pci_sys_data *sys)
+{
+ struct qcom_pcie *qcom_pcie = sys->private_data;
+
+ /*
+ * specify linux PCI framework to allocate device memory (BARs)
+ * from msm_pcie_dev.dev_mem_res resource.
+ */
+ sys->mem_offset = 0;
+ sys->io_offset = 0;
+
+ pci_add_resource(&sys->resources, &qcom_pcie->mem);
+ pci_add_resource(&sys->resources, &qcom_pcie->io);
+
+ return 1;
+}
+
+static struct pci_ops qcom_pcie_ops = {
+ .read = msm_pcie_rd_conf,
+ .write = msm_pcie_wr_conf,
+};
+
+static struct hw_pci qcom_hw_pci[MAX_RC_NUM] = {
+ {
+#ifdef CONFIG_PCI_DOMAINS
+ .domain = 0,
+#endif
+ .ops = &qcom_pcie_ops,
+ .nr_controllers = 1,
+ .swizzle = pci_common_swizzle,
+ .setup = qcom_pcie_setup,
+ .map_irq = qcom_pcie_map_irq,
+ .add_bus = qcom_pcie_add_bus,
+ },
+ {
+#ifdef CONFIG_PCI_DOMAINS
+ .domain = 1,
+#endif
+ .ops = &qcom_pcie_ops,
+ .nr_controllers = 1,
+ .swizzle = pci_common_swizzle,
+ .setup = qcom_pcie_setup,
+ .map_irq = qcom_pcie_map_irq,
+ },
+ {
+#ifdef CONFIG_PCI_DOMAINS
+ .domain = 2,
+#endif
+ .ops = &qcom_pcie_ops,
+ .nr_controllers = 1,
+ .swizzle = pci_common_swizzle,
+ .setup = qcom_pcie_setup,
+ .map_irq = qcom_pcie_map_irq,
+ },
+};
+
+static inline void qcom_elbi_writel_relaxed(struct qcom_pcie *pcie,
+ u32 val, u32 reg)
+{
+ writel_relaxed(val, pcie->elbi_base + reg);
+}
+
+static inline u32 qcom_elbi_readl_relaxed(struct qcom_pcie *pcie, u32 reg)
+{
+ return readl_relaxed(pcie->elbi_base + reg);
+}
+
+static inline void qcom_parf_writel_relaxed(struct qcom_pcie *pcie,
+ u32 val, u32 reg)
+{
+ writel_relaxed(val, pcie->parf_base + reg);
+}
+
+static inline u32 qcom_parf_readl_relaxed(struct qcom_pcie *pcie, u32 reg)
+{
+ return readl_relaxed(pcie->parf_base + reg);
+}
+
+static void msm_pcie_write_mask(void __iomem *addr,
+ uint32_t clear_mask, uint32_t set_mask)
+{
+ uint32_t val;
+
+ val = (readl_relaxed(addr) & ~clear_mask) | set_mask;
+ writel_relaxed(val, addr);
+ wmb(); /* ensure data is written to hardware register */
+}
+
+static void qcom_pcie_config_controller(struct qcom_pcie *dev)
+{
+ /*
+ * program and enable address translation region 0 (device config
+ * address space); region type config;
+ * axi config address range to device config address range
+ */
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT);
+ /* ensure that hardware locks the region before programming it */
+ wmb();
+
+ writel_relaxed(4, dev->dwc_base + PCIE20_PLR_IATU_CTRL1);
+ writel_relaxed(BIT(31), dev->dwc_base + PCIE20_PLR_IATU_CTRL2);
+ writel_relaxed(dev->conf.start, dev->dwc_base + PCIE20_PLR_IATU_LBAR);
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UBAR);
+ writel_relaxed(dev->conf.end, dev->dwc_base + PCIE20_PLR_IATU_LAR);
+ writel_relaxed(MSM_PCIE_DEV_CFG_ADDR,
+ dev->dwc_base + PCIE20_PLR_IATU_LTAR);
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UTAR);
+ /* ensure that hardware registers the configuration */
+ wmb();
+
+ /*
+ * program and enable address translation region 2 (device resource
+ * address space); region type memory;
+ * axi device bar address range to device bar address range
+ */
+ writel_relaxed(2, dev->dwc_base + PCIE20_PLR_IATU_VIEWPORT);
+ /* ensure that hardware locks the region before programming it */
+ wmb();
+
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_CTRL1);
+ writel_relaxed(BIT(31), dev->dwc_base + PCIE20_PLR_IATU_CTRL2);
+ writel_relaxed(dev->mem.start, dev->dwc_base + PCIE20_PLR_IATU_LBAR);
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UBAR);
+ writel_relaxed(dev->mem.end, dev->dwc_base + PCIE20_PLR_IATU_LAR);
+ writel_relaxed(dev->mem.start,
+ dev->dwc_base + PCIE20_PLR_IATU_LTAR);
+ writel_relaxed(0, dev->dwc_base + PCIE20_PLR_IATU_UTAR);
+ /* ensure that hardware registers the configuration */
+ wmb();
+
+ /* 1K PCIE buffer setting */
+ writel_relaxed(0x3, dev->dwc_base + PCIE20_AXI_MSTR_RESP_COMP_CTRL0);
+ writel_relaxed(0x1, dev->dwc_base + PCIE20_AXI_MSTR_RESP_COMP_CTRL1);
+ /* ensure that hardware registers the configuration */
+ wmb();
+}
+
+static int qcom_msi_alloc(struct qcom_msi *chip)
+{
+ int msi;
+
+ mutex_lock(&chip->lock);
+
+ msi = find_first_zero_bit(chip->used, INT_PCI_MSI_NR);
+ if (msi < INT_PCI_MSI_NR)
+ set_bit(msi, chip->used);
+ else
+ msi = -ENOSPC;
+
+ mutex_unlock(&chip->lock);
+
+ return msi;
+}
+
+static void qcom_msi_free(struct qcom_msi *chip, unsigned long irq)
+{
+ struct device *dev = chip->chip.dev;
+
+ mutex_lock(&chip->lock);
+
+ if (!test_bit(irq, chip->used))
+ dev_err(dev, "trying to free unused MSI#%lu\n", irq);
+ else
+ clear_bit(irq, chip->used);
+
+ mutex_unlock(&chip->lock);
+}
+
+
+static irqreturn_t handle_msi_irq(int irq, void *data)
+{
+ int i, j, index;
+ unsigned long val;
+ struct qcom_pcie *dev = data;
+ void __iomem *ctrl_status;
+ struct qcom_msi *msi = &dev->msi;
+
+ /* check for set bits, clear it by setting that bit
+ and trigger corresponding irq */
+ for (i = 0; i < PCIE20_MSI_CTRL_MAX; i++) {
+ ctrl_status = dev->dwc_base +
+ PCIE20_MSI_CTRL_INTR_STATUS + (i * 12);
+
+ val = readl_relaxed(ctrl_status);
+ while (val) {
+ j = find_first_bit(&val, 32);
+ index = j + (32 * i);
+ writel_relaxed(BIT(j), ctrl_status);
+ /* ensure that interrupt is cleared (acked) */
+ wmb();
+
+ irq = irq_find_mapping(msi->domain, index);
+ if (irq) {
+ if (test_bit(index, msi->used))
+ generic_handle_irq(irq);
+ else
+ dev_info(dev->dev, "unhandled MSI\n");
+ }
+ val = readl_relaxed(ctrl_status);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static inline struct qcom_msi *to_qcom_msi(struct msi_chip *chip)
+{
+ return container_of(chip, struct qcom_msi, chip);
+}
+
+static int qcom_msi_setup_irq(struct msi_chip *chip, struct pci_dev *pdev,
+ struct msi_desc *desc)
+{
+ struct qcom_msi *msi = to_qcom_msi(chip);
+ struct msi_msg msg;
+ unsigned int irq;
+ int hwirq;
+
+ hwirq = qcom_msi_alloc(msi);
+ if (hwirq < 0)
+ return hwirq;
+
+ irq = irq_create_mapping(msi->domain, hwirq);
+ if (!irq)
+ return -EINVAL;
+
+ irq_set_msi_desc(irq, desc);
+
+ msg.address_lo = MSM_PCIE_MSI_PHY;
+ /* 32 bit address only */
+ msg.address_hi = 0;
+ msg.data = hwirq;
+
+ write_msi_msg(irq, &msg);
+
+ return 0;
+}
+
+static void qcom_msi_teardown_irq(struct msi_chip *chip, unsigned int irq)
+{
+ struct qcom_msi *msi = to_qcom_msi(chip);
+ struct irq_data *d = irq_get_irq_data(irq);
+
+ qcom_msi_free(msi, d->hwirq);
+}
+
+static struct irq_chip qcom_msi_irq_chip = {
+ .name = "PCI-MSI",
+ .irq_enable = unmask_msi_irq,
+ .irq_disable = mask_msi_irq,
+ .irq_mask = mask_msi_irq,
+ .irq_unmask = unmask_msi_irq,
+};
+
+
+static int qcom_pcie_msi_map(struct irq_domain *domain, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &qcom_msi_irq_chip, handle_simple_irq);
+ irq_set_chip_data(irq, domain->host_data);
+ set_irq_flags(irq, IRQF_VALID);
+
+ return 0;
+}
+
+
+static const struct irq_domain_ops msi_domain_ops = {
+ .map = qcom_pcie_msi_map,
+};
+uint32_t msm_pcie_msi_init(struct qcom_pcie *pcie, struct platform_device *pdev)
+{
+ int i, rc;
+ struct qcom_msi *msi = &pcie->msi;
+ int err;
+
+ mutex_init(&msi->lock);
+
+ msi->chip.dev = pcie->dev;
+ msi->chip.setup_irq = qcom_msi_setup_irq;
+ msi->chip.teardown_irq = qcom_msi_teardown_irq;
+ msi->domain = irq_domain_add_linear(pdev->dev.of_node, INT_PCI_MSI_NR,
+ &msi_domain_ops, &msi->chip);
+ if (!msi->domain) {
+ dev_err(&pdev->dev, "failed to create IRQ domain\n");
+ return -ENOMEM;
+ }
+
+
+ err = platform_get_irq_byname(pdev, "msi");
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to get IRQ: %d\n", err);
+ return err;
+ }
+
+ msi->irq = err;
+
+ /* program MSI controller and enable all interrupts */
+ writel_relaxed(MSM_PCIE_MSI_PHY, pcie->dwc_base + PCIE20_MSI_CTRL_ADDR);
+ writel_relaxed(0, pcie->dwc_base + PCIE20_MSI_CTRL_UPPER_ADDR);
+
+ for (i = 0; i < PCIE20_MSI_CTRL_MAX; i++)
+ writel_relaxed(~0, pcie->dwc_base +
+ PCIE20_MSI_CTRL_INTR_EN + (i * 12));
+
+ /* ensure that hardware is configured before proceeding */
+ wmb();
+
+ /* register handler for physical MSI interrupt line */
+ rc = request_irq(msi->irq, handle_msi_irq, IRQF_TRIGGER_RISING,
+ "msm_pcie_msi", pcie);
+ if (rc) {
+ pr_err("Unable to allocate msi interrupt\n");
+ return rc;
+ }
+
+ return rc;
+}
+
+static int qcom_pcie_vreg_on(struct qcom_pcie *qcom_pcie)
+{
+ int err;
+ /* enable regulators */
+ err = regulator_enable(qcom_pcie->vdd_supply);
+ if (err < 0) {
+ dev_err(qcom_pcie->dev, "failed to enable VDD regulator\n");
+ return err;
+ }
+
+ err = regulator_enable(qcom_pcie->pcie_clk_supply);
+ if (err < 0) {
+ dev_err(qcom_pcie->dev, "failed to enable pcie-clk regulator\n");
+ return err;
+ }
+
+ err = regulator_enable(qcom_pcie->avdd_supply);
+ if (err < 0) {
+ dev_err(qcom_pcie->dev, "failed to enable AVDD regulator\n");
+ return err;
+ }
+
+ return err;
+
+}
+
+static int qcom_pcie_parse_dt(struct qcom_pcie *qcom_pcie,
+ struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *elbi_base, *parf_base, *dwc_base;
+ struct of_pci_range range;
+ struct of_pci_range_parser parser;
+ int ret, i;
+
+ qcom_pcie->ext_phy_ref_clk = of_property_read_bool(np,
+ "qcom,external-phy-refclk");
+
+ elbi_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi");
+ qcom_pcie->elbi_base = devm_ioremap_resource(&pdev->dev, elbi_base);
+ if (IS_ERR(qcom_pcie->elbi_base)) {
+ dev_err(&pdev->dev, "Failed to ioremap elbi space\n");
+ return PTR_ERR(qcom_pcie->elbi_base);
+ }
+
+ parf_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf");
+ qcom_pcie->parf_base = devm_ioremap_resource(&pdev->dev, parf_base);
+ if (IS_ERR(qcom_pcie->parf_base)) {
+ dev_err(&pdev->dev, "Failed to ioremap parf space\n");
+ return PTR_ERR(qcom_pcie->parf_base);
+ }
+
+ dwc_base = platform_get_resource_byname(pdev, IORESOURCE_MEM, "base");
+ qcom_pcie->dwc_base = devm_ioremap_resource(&pdev->dev, dwc_base);
+ if (IS_ERR(qcom_pcie->dwc_base)) {
+ dev_err(&pdev->dev, "Failed to ioremap dwc_base space\n");
+ return PTR_ERR(qcom_pcie->dwc_base);
+ }
+
+ if (of_pci_range_parser_init(&parser, np)) {
+ dev_err(&pdev->dev, "missing ranges property\n");
+ return -EINVAL;
+ }
+
+ /* Get the I/O and memory ranges from DT */
+ for_each_of_pci_range(&parser, &range) {
+ switch (range.pci_space & 0x3) {
+ case 0: /* cfg */
+ of_pci_range_to_resource(&range, np, &qcom_pcie->conf);
+ qcom_pcie->conf.flags = IORESOURCE_MEM;
+ break;
+ case 1: /* io */
+ of_pci_range_to_resource(&range, np, &qcom_pcie->io);
+ break;
+ default: /* mem */
+ of_pci_range_to_resource(&range, np, &qcom_pcie->mem);
+ break;
+ }
+ }
+
+ qcom_pcie->vdd_supply = devm_regulator_get(&pdev->dev, "vdd");
+ if (IS_ERR(qcom_pcie->vdd_supply)) {
+ dev_err(&pdev->dev, "Failed to get vdd supply\n");
+ return PTR_ERR(qcom_pcie->vdd_supply);
+ }
+
+ qcom_pcie->pcie_clk_supply = devm_regulator_get(&pdev->dev, "pcie-clk");
+ if (IS_ERR(qcom_pcie->pcie_clk_supply)) {
+ dev_err(&pdev->dev, "Failed to get pcie clk supply\n");
+ return PTR_ERR(qcom_pcie->pcie_clk_supply);
+ }
+ qcom_pcie->avdd_supply = devm_regulator_get(&pdev->dev, "avdd");
+ if (IS_ERR(qcom_pcie->avdd_supply)) {
+ dev_err(&pdev->dev, "Failed to get avdd supply\n");
+ return PTR_ERR(qcom_pcie->avdd_supply);
+ }
+
+
+ qcom_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0);
+ if (!gpio_is_valid(qcom_pcie->reset_gpio)) {
+ dev_err(&pdev->dev, "pcie reset gpio is not valid\n");
+ return -EINVAL;
+ }
+
+ ret = devm_gpio_request_one(&pdev->dev, qcom_pcie->reset_gpio,
+ GPIOF_DIR_OUT, "pcie_reset");
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request pcie reset gpio\n");
+ return ret;
+ }
+
+ qcom_pcie->iface_clk = devm_clk_get(&pdev->dev, "iface");
+ if (IS_ERR(qcom_pcie->iface_clk)) {
+ dev_err(&pdev->dev, "Failed to get pcie iface clock\n");
+ return PTR_ERR(qcom_pcie->iface_clk);
+ }
+
+ qcom_pcie->phy_clk = devm_clk_get(&pdev->dev, "phy");
+ if (IS_ERR(qcom_pcie->phy_clk)) {
+ dev_err(&pdev->dev, "Failed to get pcie phy clock\n");
+ return PTR_ERR(qcom_pcie->phy_clk);
+ }
+
+ qcom_pcie->bus_clk = devm_clk_get(&pdev->dev, "core");
+ if (IS_ERR(qcom_pcie->bus_clk)) {
+ dev_err(&pdev->dev, "Failed to get pcie core clock\n");
+ return PTR_ERR(qcom_pcie->bus_clk);
+ }
+
+ qcom_pcie->axi_reset = devm_reset_control_get(&pdev->dev, "axi");
+ if (IS_ERR(qcom_pcie->axi_reset)) {
+ dev_err(&pdev->dev, "Failed to get axi reset\n");
+ return PTR_ERR(qcom_pcie->axi_reset);
+ }
+
+ qcom_pcie->ahb_reset = devm_reset_control_get(&pdev->dev, "ahb");
+ if (IS_ERR(qcom_pcie->ahb_reset)) {
+ dev_err(&pdev->dev, "Failed to get ahb reset\n");
+ return PTR_ERR(qcom_pcie->ahb_reset);
+ }
+
+ qcom_pcie->por_reset = devm_reset_control_get(&pdev->dev, "por");
+ if (IS_ERR(qcom_pcie->por_reset)) {
+ dev_err(&pdev->dev, "Failed to get por reset\n");
+ return PTR_ERR(qcom_pcie->por_reset);
+ }
+
+ qcom_pcie->pci_reset = devm_reset_control_get(&pdev->dev, "pci");
+ if (IS_ERR(qcom_pcie->pci_reset)) {
+ dev_err(&pdev->dev, "Failed to get pci reset\n");
+ return PTR_ERR(qcom_pcie->pci_reset);
+ }
+
+ qcom_pcie->phy_reset = devm_reset_control_get(&pdev->dev, "phy");
+ if (IS_ERR(qcom_pcie->phy_reset)) {
+ dev_err(&pdev->dev, "Failed to get phy reset\n");
+ return PTR_ERR(qcom_pcie->phy_reset);
+ }
+
+ for (i = 0; i < 4; i++) {
+ qcom_pcie->irq_int[i] = platform_get_irq(pdev, i+1);
+ if (qcom_pcie->irq_int[i] < 0) {
+ dev_err(&pdev->dev, "failed to get irq resource\n");
+ return qcom_pcie->irq_int[i];
+ }
+ }
+
+ return 0;
+}
+
+static int qcom_pcie_probe(struct platform_device *pdev)
+{
+ unsigned long flags;
+ struct qcom_pcie *qcom_pcie;
+ struct hw_pci *hw;
+ int ret;
+ u32 val;
+
+ qcom_pcie = devm_kzalloc(&pdev->dev, sizeof(*qcom_pcie), GFP_KERNEL);
+ if (!qcom_pcie) {
+ dev_err(&pdev->dev, "no memory for qcom_pcie\n");
+ return -ENOMEM;
+ }
+ qcom_pcie->dev = &pdev->dev;
+
+ ret = qcom_pcie_parse_dt(qcom_pcie, pdev);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+
+ qcom_pcie->cfg_base = devm_ioremap_resource(&pdev->dev,
+ &qcom_pcie->conf);
+ if (IS_ERR(qcom_pcie->cfg_base)) {
+ dev_err(&pdev->dev, "Failed to ioremap PCIe cfg space\n");
+ return PTR_ERR(qcom_pcie->cfg_base);
+ }
+
+ gpio_set_value(qcom_pcie->reset_gpio, 0);
+ usleep_range(10000, 15000);
+
+ /* enable power */
+ qcom_pcie_vreg_on(qcom_pcie);
+ /* assert PCIe PARF reset while powering the core */
+ reset_control_assert(qcom_pcie->ahb_reset);
+
+ /* enable clocks */
+ ret = clk_prepare_enable(qcom_pcie->iface_clk);
+ if (ret)
+ return ret;
+ ret = clk_prepare_enable(qcom_pcie->phy_clk);
+ if (ret)
+ return ret;
+ ret = clk_prepare_enable(qcom_pcie->bus_clk);
+ if (ret)
+ return ret;
+
+ /*
+ * de-assert PCIe PARF reset;
+ * wait 1us before accessing PARF registers
+ */
+ reset_control_deassert(qcom_pcie->ahb_reset);
+ udelay(1);
+
+ /* enable PCIe clocks and resets */
+ msm_pcie_write_mask(qcom_pcie->parf_base + PCIE20_PARF_PHY_CTRL,
+ BIT(0), 0);
+
+ /* Set Tx Termination Offset */
+ val = qcom_parf_readl_relaxed(qcom_pcie, PCIE20_PARF_PHY_CTRL);
+ val |= PCIE20_PARF_PHY_CTRL_PHY_TX0_TERM_OFFST(7);
+ qcom_parf_writel_relaxed(qcom_pcie, val, PCIE20_PARF_PHY_CTRL);
+
+ /* PARF programming */
+ qcom_parf_writel_relaxed(qcom_pcie,
+ PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN1(0x18) |
+ PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(0x18) |
+ PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(0x22),
+ PCIE20_PARF_PCS_DEEMPH);
+ qcom_parf_writel_relaxed(qcom_pcie,
+ PCIE20_PARF_PCS_SWING_TX_SWING_FULL(0x78) |
+ PCIE20_PARF_PCS_SWING_TX_SWING_LOW(0x78),
+ PCIE20_PARF_PCS_SWING);
+ qcom_parf_writel_relaxed(qcom_pcie, (4<<24), PCIE20_PARF_CONFIG_BITS);
+ /* ensure that hardware registers the PARF configuration */
+ wmb();
+
+ /* enable reference clock */
+ msm_pcie_write_mask(qcom_pcie->parf_base + PCIE20_PARF_PHY_REFCLK,
+ qcom_pcie->ext_phy_ref_clk ? 0 : BIT(12),
+ BIT(16));
+
+ /* ensure that access is enabled before proceeding */
+ wmb();
+
+ /* de-assert PICe PHY, Core, POR and AXI clk domain resets */
+ reset_control_deassert(qcom_pcie->phy_reset);
+ reset_control_deassert(qcom_pcie->pci_reset);
+ reset_control_deassert(qcom_pcie->por_reset);
+ reset_control_deassert(qcom_pcie->axi_reset);
+
+ /* wait 150ms for clock acquisition */
+ usleep_range(10000, 15000);
+
+ /* de-assert PCIe reset link to bring EP out of reset */
+ gpio_set_value(qcom_pcie->reset_gpio, 1);
+ usleep_range(10000, 15000);
+
+ /* enable link training */
+ val = qcom_elbi_readl_relaxed(qcom_pcie, PCIE20_ELBI_SYS_CTRL);
+ val |= PCIE20_ELBI_SYS_CTRL_LTSSM_EN;
+ qcom_elbi_writel_relaxed(qcom_pcie, val, PCIE20_ELBI_SYS_CTRL);
+ wmb();
+
+ /* poll for link to come up for upto 100ms */
+ ret = readl_poll_timeout(
+ (qcom_pcie->dwc_base + PCIE20_CAP_LINKCTRLSTATUS),
+ val, (val & BIT(29)), 10000, 100000);
+
+ dev_info(&pdev->dev, "link initialized %d\n", ret);
+
+ qcom_pcie_config_controller(qcom_pcie);
+
+ platform_set_drvdata(pdev, qcom_pcie);
+
+ spin_lock_irqsave(&qcom_hw_pci_lock, flags);
+ qcom_hw_pci[nr_controllers].private_data = (void **)&qcom_pcie;
+ hw = &qcom_hw_pci[nr_controllers];
+ nr_controllers++;
+ spin_unlock_irqrestore(&qcom_hw_pci_lock, flags);
+
+ pci_common_init(hw);
+
+ msm_pcie_msi_init(qcom_pcie, pdev);
+ return 0;
+}
+
+static int __exit qcom_pcie_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct of_device_id qcom_pcie_match[] = {
+ { .compatible = "qcom,pcie-ipq8064", },
+ {}
+};
+
+static struct platform_driver qcom_pcie_driver = {
+ .probe = qcom_pcie_probe,
+ .remove = qcom_pcie_remove,
+ .driver = {
+ .name = "qcom_pcie",
+ .owner = THIS_MODULE,
+ .of_match_table = qcom_pcie_match,
+ },
+};
+
+static int qcom_pcie_init(void)
+{
+ return platform_driver_register(&qcom_pcie_driver);
+}
+subsys_initcall_sync(qcom_pcie_init);
+
+/* RC do not represent the right class; set it to PCI_CLASS_BRIDGE_PCI */
+static void msm_pcie_fixup_early(struct pci_dev *dev)
+{
+ if (dev->hdr_type == 1)
+ dev->class = (dev->class & 0xff) | (PCI_CLASS_BRIDGE_PCI << 8);
+}
+DECLARE_PCI_FIXUP_EARLY(PCIE_VENDOR_ID_RCP, PCIE_DEVICE_ID_RCP, msm_pcie_fixup_early);
diff --git a/drivers/pinctrl/qcom/Kconfig b/drivers/pinctrl/qcom/Kconfig
index 81275af9638b..ed0e64e75969 100644
--- a/drivers/pinctrl/qcom/Kconfig
+++ b/drivers/pinctrl/qcom/Kconfig
@@ -47,4 +47,27 @@ config PINCTRL_MSM8X74
This is the pinctrl, pinmux, pinconf and gpiolib driver for the
Qualcomm TLMM block found in the Qualcomm 8974 platform.
+config PINCTRL_SPMI_PMIC
+ tristate "Qualcomm SPMI PMIC pin controller driver"
+ depends on GPIOLIB && OF
+ select PINMUX
+ select PINCONF
+ select GENERIC_PINCONF
+ help
+ This is the pinctrl, pinmux, pinconf and gpiolib driver for the
+ Qualcomm GPIO and MPP blocks found in the Qualcomm PMIC's chips,
+ which are using SPMI for communication with SoC. Example PMIC's
+ devices are pm8841, pm8941 and pma8084.
+
+config PINCTRL_SSBI_PMIC
+ tristate "Qualcomm SSBI PMIC pin controller driver"
+ depends on GPIOLIB && OF && MFD_PM8921_CORE
+ select PINCONF
+ select PINMUX
+ select GENERIC_PINCONF
+ help
+ This is the pinctrl, pinmux, pinconf and gpiolib driver for the
+ Qualcomm GPIO blocks found in the pm8018, pm8038, pm8058, pm8917 and
+ pm8921 pmics from Qualcomm.
+
endif
diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile
index ba8519fcd8d3..1c50d3daa186 100644
--- a/drivers/pinctrl/qcom/Makefile
+++ b/drivers/pinctrl/qcom/Makefile
@@ -5,3 +5,6 @@ obj-$(CONFIG_PINCTRL_APQ8084) += pinctrl-apq8084.o
obj-$(CONFIG_PINCTRL_IPQ8064) += pinctrl-ipq8064.o
obj-$(CONFIG_PINCTRL_MSM8960) += pinctrl-msm8960.o
obj-$(CONFIG_PINCTRL_MSM8X74) += pinctrl-msm8x74.o
+obj-$(CONFIG_PINCTRL_SPMI_PMIC) += pinctrl-spmi-pmic-gpio.o
+obj-$(CONFIG_PINCTRL_SPMI_PMIC) += pinctrl-spmi-pmic-mpp.o
+obj-$(CONFIG_PINCTRL_SSBI_PMIC) += pinctrl-ssbi-pmic.o
diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-pmic-gpio.c b/drivers/pinctrl/qcom/pinctrl-spmi-pmic-gpio.c
new file mode 100644
index 000000000000..0d69758c25a3
--- /dev/null
+++ b/drivers/pinctrl/qcom/pinctrl-spmi-pmic-gpio.c
@@ -0,0 +1,938 @@
+/*
+ * Copyright (c) 2012-2014, 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/gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <dt-bindings/pinctrl/qcom,pmic-gpio.h>
+
+#include "../core.h"
+#include "../pinctrl-utils.h"
+
+#define PMIC_GPIO_ADDRESS_RANGE 0x100
+
+/* type and subtype registers base address offsets */
+#define PMIC_GPIO_REG_TYPE 0x4
+#define PMIC_GPIO_REG_SUBTYPE 0x5
+
+/* GPIO peripheral type and subtype out_values */
+#define PMIC_GPIO_TYPE 0x10
+#define PMIC_GPIO_SUBTYPE_GPIO_4CH 0x1
+#define PMIC_GPIO_SUBTYPE_GPIOC_4CH 0x5
+#define PMIC_GPIO_SUBTYPE_GPIO_8CH 0x9
+#define PMIC_GPIO_SUBTYPE_GPIOC_8CH 0xd
+
+#define PMIC_MPP_REG_RT_STS 0x10
+#define PMIC_MPP_REG_RT_STS_VAL_MASK 0x1
+
+/* control register base address offsets */
+#define PMIC_GPIO_REG_MODE_CTL 0x40
+#define PMIC_GPIO_REG_DIG_VIN_CTL 0x41
+#define PMIC_GPIO_REG_DIG_PULL_CTL 0x42
+#define PMIC_GPIO_REG_DIG_OUT_CTL 0x45
+#define PMIC_GPIO_REG_EN_CTL 0x46
+
+/* PMIC_GPIO_REG_MODE_CTL */
+#define PMIC_GPIO_REG_MODE_VALUE_SHIFT 0x1
+#define PMIC_GPIO_REG_MODE_FUNCTION_SHIFT 1
+#define PMIC_GPIO_REG_MODE_FUNCTION_MASK 0x7
+#define PMIC_GPIO_REG_MODE_DIR_SHIFT 4
+#define PMIC_GPIO_REG_MODE_DIR_MASK 0x7
+
+/* PMIC_GPIO_REG_DIG_VIN_CTL */
+#define PMIC_GPIO_REG_VIN_SHIFT 0
+#define PMIC_GPIO_REG_VIN_MASK 0x7
+
+/* PMIC_GPIO_REG_DIG_PULL_CTL */
+#define PMIC_GPIO_REG_PULL_SHIFT 0
+#define PMIC_GPIO_REG_PULL_MASK 0x7
+
+#define PMIC_GPIO_PULL_DOWN 4
+#define PMIC_GPIO_PULL_DISABLE 5
+
+/* PMIC_GPIO_REG_DIG_OUT_CTL */
+#define PMIC_GPIO_REG_OUT_STRENGTH_SHIFT 0
+#define PMIC_GPIO_REG_OUT_STRENGTH_MASK 0x3
+#define PMIC_GPIO_REG_OUT_TYPE_SHIFT 4
+#define PMIC_GPIO_REG_OUT_TYPE_MASK 0x3
+
+/*
+ * Output type - indicates pin should be configured as push-pull,
+ * open drain or open source.
+ */
+#define PMIC_GPIO_OUT_BUF_CMOS 0
+#define PMIC_GPIO_OUT_BUF_OPEN_DRAIN_NMOS 1
+#define PMIC_GPIO_OUT_BUF_OPEN_DRAIN_PMOS 2
+
+/* PMIC_GPIO_REG_EN_CTL */
+#define PMIC_GPIO_REG_MASTER_EN_SHIFT 7
+
+#define PMIC_GPIO_PHYSICAL_OFFSET 1
+
+/* Qualcomm specific pin configurations */
+#define PMIC_GPIO_CONF_PULL_UP (PIN_CONFIG_END + 1)
+#define PMIC_GPIO_CONF_STRENGTH (PIN_CONFIG_END + 2)
+
+/**
+ * struct pmic_gpio_pad - keep current GPIO settings
+ * @base: Address base in SPMI device.
+ * @irq: IRQ number which this GPIO generate.
+ * @is_enabled: Set to false when GPIO should be put in high Z state.
+ * @out_value: Cached pin output value
+ * @have_buffer: Set to true if GPIO output could be configured in push-pull,
+ * open-drain or open-source mode.
+ * @output_enabled: Set to true if GPIO output logic is enabled.
+ * @input_enabled: Set to true if GPIO input buffer logic is enabled.
+ * @num_sources: Number of power-sources supported by this GPIO.
+ * @power_source: Current power-source used.
+ * @buffer_type: Push-pull, open-drain or open-source.
+ * @pullup: Constant current which flow trough GPIO output buffer.
+ * @strength: No, Low, Medium, High
+ * @function: See pmic_gpio_functions[]
+ */
+struct pmic_gpio_pad {
+ u16 base;
+ int irq;
+ bool is_enabled;
+ bool out_value;
+ bool have_buffer;
+ bool output_enabled;
+ bool input_enabled;
+ unsigned int num_sources;
+ unsigned int power_source;
+ unsigned int buffer_type;
+ unsigned int pullup;
+ unsigned int strength;
+ unsigned int function;
+};
+
+struct pmic_gpio_state {
+ struct device *dev;
+ struct regmap *map;
+ struct pinctrl_dev *ctrl;
+ struct gpio_chip chip;
+};
+
+struct pmic_gpio_bindings {
+ const char *property;
+ unsigned param;
+};
+
+static struct pmic_gpio_bindings pmic_gpio_bindings[] = {
+ {"qcom,pull-up-strength", PMIC_GPIO_CONF_PULL_UP},
+ {"qcom,drive-strength", PMIC_GPIO_CONF_STRENGTH},
+};
+
+static const char *const pmic_gpio_groups[] = {
+ "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7", "gpio8",
+ "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14", "gpio15",
+ "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21", "gpio22",
+ "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28", "gpio29",
+ "gpio30", "gpio31", "gpio32", "gpio33", "gpio34", "gpio35", "gpio36",
+};
+
+static const char *const pmic_gpio_functions[] = {
+ PMIC_GPIO_FUNC_NORMAL, PMIC_GPIO_FUNC_PAIRED,
+ PMIC_GPIO_FUNC_FUNC1, PMIC_GPIO_FUNC_FUNC2,
+ PMIC_GPIO_FUNC_DTEST1, PMIC_GPIO_FUNC_DTEST2,
+ PMIC_GPIO_FUNC_DTEST3, PMIC_GPIO_FUNC_DTEST4,
+};
+
+static int pmic_gpio_read(struct pmic_gpio_state *state,
+ struct pmic_gpio_pad *pad, unsigned int addr)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(state->map, pad->base + addr, &val);
+ if (ret < 0)
+ dev_err(state->dev, "read 0x%x failed\n", addr);
+ else
+ ret = val;
+
+ return ret;
+}
+
+static int pmic_gpio_write(struct pmic_gpio_state *state,
+ struct pmic_gpio_pad *pad, unsigned int addr,
+ unsigned int val)
+{
+ int ret;
+
+ ret = regmap_write(state->map, pad->base + addr, val);
+ if (ret < 0)
+ dev_err(state->dev, "write 0x%x failed\n", addr);
+
+ return ret;
+}
+
+static int pmic_gpio_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ /* Every PIN is a group */
+ return pctldev->desc->npins;
+}
+
+static const char *pmic_gpio_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned pin)
+{
+ return pctldev->desc->pins[pin].name;
+}
+
+static int pmic_gpio_get_group_pins(struct pinctrl_dev *pctldev, unsigned pin,
+ const unsigned **pins, unsigned *num_pins)
+{
+ *pins = &pctldev->desc->pins[pin].number;
+ *num_pins = 1;
+ return 0;
+}
+
+static int pmic_gpio_parse_dt_config(struct device_node *np,
+ struct pinctrl_dev *pctldev,
+ unsigned long **configs,
+ unsigned int *nconfs)
+{
+ struct pmic_gpio_bindings *par;
+ unsigned long cfg;
+ int ret, i;
+ u32 val;
+
+ for (i = 0; i < ARRAY_SIZE(pmic_gpio_bindings); i++) {
+
+ par = &pmic_gpio_bindings[i];
+ ret = of_property_read_u32(np, par->property, &val);
+
+ /* property not found */
+ if (ret == -EINVAL)
+ continue;
+
+ /* use zero as default value */
+ if (ret)
+ val = 0;
+
+ dev_dbg(pctldev->dev, "found %s with value %u\n",
+ par->property, val);
+
+ cfg = pinconf_to_config_packed(par->param, val);
+
+ ret = pinctrl_utils_add_config(pctldev, configs, nconfs, cfg);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pmic_gpio_dt_subnode_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np,
+ struct pinctrl_map **map,
+ unsigned *reserv, unsigned *nmaps,
+ enum pinctrl_map_type type)
+{
+ unsigned long *configs = NULL;
+ unsigned nconfs = 0;
+ struct property *prop;
+ const char *group;
+ int ret;
+
+ ret = pmic_gpio_parse_dt_config(np, pctldev, &configs, &nconfs);
+ if (ret < 0)
+ return ret;
+
+ if (!nconfs)
+ return 0;
+
+ ret = of_property_count_strings(np, "pins");
+ if (ret < 0)
+ goto exit;
+
+ ret = pinctrl_utils_reserve_map(pctldev, map, reserv, nmaps, ret);
+ if (ret < 0)
+ goto exit;
+
+ of_property_for_each_string(np, "pins", prop, group) {
+ ret = pinctrl_utils_add_map_configs(pctldev, map,
+ reserv, nmaps, group,
+ configs, nconfs, type);
+ if (ret < 0)
+ break;
+ }
+exit:
+ kfree(configs);
+ return ret;
+}
+
+static int pmic_gpio_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np_config,
+ struct pinctrl_map **map, unsigned *nmaps)
+{
+ enum pinctrl_map_type type;
+ struct device_node *np;
+ unsigned reserv;
+ int ret;
+
+ ret = 0;
+ *map = NULL;
+ *nmaps = 0;
+ reserv = 0;
+ type = PIN_MAP_TYPE_CONFIGS_GROUP;
+
+ for_each_child_of_node(np_config, np) {
+
+ ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map,
+ &reserv, nmaps, type);
+ if (ret)
+ break;
+
+ ret = pmic_gpio_dt_subnode_to_map(pctldev, np, map, &reserv,
+ nmaps, type);
+ if (ret)
+ break;
+ }
+
+ if (ret < 0)
+ pinctrl_utils_dt_free_map(pctldev, *map, *nmaps);
+
+ return ret;
+}
+
+static const struct pinctrl_ops pmic_gpio_pinctrl_ops = {
+ .get_groups_count = pmic_gpio_get_groups_count,
+ .get_group_name = pmic_gpio_get_group_name,
+ .get_group_pins = pmic_gpio_get_group_pins,
+ .dt_node_to_map = pmic_gpio_dt_node_to_map,
+ .dt_free_map = pinctrl_utils_dt_free_map,
+};
+
+static int pmic_gpio_get_functions_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(pmic_gpio_functions);
+}
+
+static const char *pmic_gpio_get_function_name(struct pinctrl_dev *pctldev,
+ unsigned function)
+{
+ return pmic_gpio_functions[function];
+}
+
+static int pmic_gpio_get_function_groups(struct pinctrl_dev *pctldev,
+ unsigned function,
+ const char *const **groups,
+ unsigned *const num_qgroups)
+{
+ *groups = pmic_gpio_groups;
+ *num_qgroups = pctldev->desc->npins;
+ return 0;
+}
+
+static int pmic_gpio_pinmux_enable(struct pinctrl_dev *pctldev,
+ unsigned function, unsigned pin)
+{
+ struct pmic_gpio_state *state = pinctrl_dev_get_drvdata(pctldev);
+ struct pmic_gpio_pad *pad;
+ unsigned int val;
+ int ret;
+
+ pad = pctldev->desc->pins[pin].drv_data;
+
+ pad->function = function;
+
+ val = 0;
+ if (pad->output_enabled) {
+ if (pad->input_enabled)
+ val = 2;
+ else
+ val = 1;
+ }
+
+ val |= pad->function << PMIC_GPIO_REG_MODE_FUNCTION_SHIFT;
+ val |= pad->out_value & PMIC_GPIO_REG_MODE_VALUE_SHIFT;
+
+ ret = pmic_gpio_write(state, pad, PMIC_GPIO_REG_MODE_CTL, val);
+ if (ret < 0)
+ return ret;
+
+ val = pad->is_enabled << PMIC_GPIO_REG_MASTER_EN_SHIFT;
+
+ return pmic_gpio_write(state, pad, PMIC_GPIO_REG_EN_CTL, val);
+}
+
+static const struct pinmux_ops pmic_gpio_pinmux_ops = {
+ .get_functions_count = pmic_gpio_get_functions_count,
+ .get_function_name = pmic_gpio_get_function_name,
+ .get_function_groups = pmic_gpio_get_function_groups,
+ .set_mux = pmic_gpio_pinmux_enable,
+};
+
+static int pmic_gpio_config_get(struct pinctrl_dev *pctldev,
+ unsigned int pin, unsigned long *config)
+{
+ unsigned param = pinconf_to_config_param(*config);
+ struct pmic_gpio_pad *pad;
+ unsigned arg;
+
+ pad = pctldev->desc->pins[pin].drv_data;
+
+ switch (param) {
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ arg = pad->buffer_type == PMIC_GPIO_OUT_BUF_CMOS;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ arg = pad->buffer_type == PMIC_GPIO_OUT_BUF_OPEN_DRAIN_NMOS;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ arg = pad->buffer_type == PMIC_GPIO_OUT_BUF_OPEN_DRAIN_PMOS;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ arg = pad->pullup == PMIC_GPIO_PULL_DOWN;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ arg = pad->pullup = PMIC_GPIO_PULL_DISABLE;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ arg = pad->pullup == PMIC_GPIO_PULL_UP_30;
+ break;
+ break;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ arg = !pad->is_enabled;
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ arg = pad->power_source;
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ arg = pad->input_enabled;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ arg = pad->out_value;
+ break;
+ case PMIC_GPIO_CONF_PULL_UP:
+ arg = pad->pullup;
+ break;
+ case PMIC_GPIO_CONF_STRENGTH:
+ arg = pad->strength;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *config = pinconf_to_config_packed(param, arg);
+ return 0;
+}
+
+static int pmic_gpio_config_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned nconfs)
+{
+ struct pmic_gpio_state *state = pinctrl_dev_get_drvdata(pctldev);
+ struct pmic_gpio_pad *pad;
+ unsigned param, arg;
+ unsigned int val;
+ int i, ret;
+
+ pad = pctldev->desc->pins[pin].drv_data;
+
+ for (i = 0; i < nconfs; i++) {
+ param = pinconf_to_config_param(configs[i]);
+ arg = pinconf_to_config_argument(configs[i]);
+
+ switch (param) {
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ pad->buffer_type = PMIC_GPIO_OUT_BUF_CMOS;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ if (!pad->have_buffer)
+ return -EINVAL;
+ pad->buffer_type = PMIC_GPIO_OUT_BUF_OPEN_DRAIN_NMOS;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_SOURCE:
+ if (!pad->have_buffer)
+ return -EINVAL;
+ pad->buffer_type = PMIC_GPIO_OUT_BUF_OPEN_DRAIN_PMOS;
+ break;
+ case PIN_CONFIG_BIAS_DISABLE:
+ pad->pullup = PMIC_GPIO_PULL_DISABLE;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ pad->pullup = PMIC_GPIO_PULL_UP_30;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ if (arg)
+ pad->pullup = PMIC_GPIO_PULL_DOWN;
+ else
+ pad->pullup = PMIC_GPIO_PULL_DISABLE;
+ break;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ pad->is_enabled = false;
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ if (arg > pad->num_sources)
+ return -EINVAL;
+ pad->power_source = arg;
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ pad->input_enabled = arg ? true : false;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ pad->output_enabled = true;
+ pad->out_value = arg;
+ break;
+ case PMIC_GPIO_CONF_PULL_UP:
+ if (arg > PMIC_GPIO_PULL_UP_1P5_30)
+ return -EINVAL;
+ pad->pullup = arg;
+ break;
+ case PMIC_GPIO_CONF_STRENGTH:
+ if (arg > PMIC_GPIO_STRENGTH_LOW)
+ return -EINVAL;
+ pad->strength = arg;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ val = pad->power_source << PMIC_GPIO_REG_VIN_SHIFT;
+
+ ret = pmic_gpio_write(state, pad, PMIC_GPIO_REG_DIG_VIN_CTL, val);
+ if (ret < 0)
+ return ret;
+
+ val = pad->pullup << PMIC_GPIO_REG_PULL_SHIFT;
+
+ ret = pmic_gpio_write(state, pad, PMIC_GPIO_REG_DIG_PULL_CTL, val);
+ if (ret < 0)
+ return ret;
+
+ val = pad->buffer_type << PMIC_GPIO_REG_OUT_TYPE_SHIFT;
+ val = pad->strength << PMIC_GPIO_REG_OUT_STRENGTH_SHIFT;
+
+ ret = pmic_gpio_write(state, pad, PMIC_GPIO_REG_DIG_OUT_CTL, val);
+ if (ret < 0)
+ return ret;
+
+ val = 0;
+ if (pad->output_enabled) {
+ if (pad->input_enabled)
+ val = 2;
+ else
+ val = 1;
+ }
+
+ val = val << PMIC_GPIO_REG_MODE_DIR_SHIFT;
+ val |= pad->function << PMIC_GPIO_REG_MODE_FUNCTION_SHIFT;
+ val |= pad->out_value & PMIC_GPIO_REG_MODE_VALUE_SHIFT;
+
+ return pmic_gpio_write(state, pad, PMIC_GPIO_REG_MODE_CTL, val);
+}
+
+static void pmic_gpio_config_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned pin)
+{
+ struct pmic_gpio_state *state = pinctrl_dev_get_drvdata(pctldev);
+ struct pmic_gpio_pad *pad;
+ int ret, val;
+
+ static const char *const biases[] = {
+ "pull-up 30uA", "pull-up 1.5uA", "pull-up 31.5uA",
+ "pull-up 1.5uA + 30uA boost", "pull-down 10uA", "no pull"
+ };
+ static const char *const buffer_types[] = {
+ "push-pull", "open-drain", "open-source"
+ };
+ static const char *const strengths[] = {
+ "no", "high", "medium", "low"
+ };
+
+ pad = pctldev->desc->pins[pin].drv_data;
+
+ seq_printf(s, " gpio%-2d:", pin + PMIC_GPIO_PHYSICAL_OFFSET);
+
+ val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_EN_CTL);
+
+ if (val < 0 || !(val >> PMIC_GPIO_REG_MASTER_EN_SHIFT)) {
+ seq_puts(s, " ---");
+ } else {
+
+ if (!pad->input_enabled) {
+ ret = pmic_gpio_read(state, pad, PMIC_MPP_REG_RT_STS);
+ if (!ret) {
+ ret &= PMIC_MPP_REG_RT_STS_VAL_MASK;
+ pad->out_value = ret;
+ }
+ }
+
+ seq_printf(s, " %-4s", pad->output_enabled ? "out" : "in");
+ seq_printf(s, " %-7s", pmic_gpio_functions[pad->function]);
+ seq_printf(s, " vin-%d", pad->power_source);
+ seq_printf(s, " %-27s", biases[pad->pullup]);
+ seq_printf(s, " %-10s", buffer_types[pad->buffer_type]);
+ seq_printf(s, " %-4s", pad->out_value ? "high" : "low");
+ seq_printf(s, " %-7s", strengths[pad->strength]);
+ }
+}
+
+static const struct pinconf_ops pmic_gpio_pinconf_ops = {
+ .pin_config_group_get = pmic_gpio_config_get,
+ .pin_config_group_set = pmic_gpio_config_set,
+ .pin_config_group_dbg_show = pmic_gpio_config_dbg_show,
+};
+
+static int pmic_gpio_direction_input(struct gpio_chip *chip, unsigned pin)
+{
+ struct pmic_gpio_state *state;
+ unsigned long config;
+
+ state = container_of(chip, struct pmic_gpio_state, chip);
+ config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1);
+
+ return pmic_gpio_config_set(state->ctrl, pin, &config, 1);
+}
+
+static int pmic_gpio_direction_output(struct gpio_chip *chip,
+ unsigned pin, int val)
+{
+ struct pmic_gpio_state *state;
+ unsigned long config;
+
+ state = container_of(chip, struct pmic_gpio_state, chip);
+ config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val);
+
+ return pmic_gpio_config_set(state->ctrl, pin, &config, 1);
+}
+
+static int pmic_gpio_get(struct gpio_chip *chip, unsigned pin)
+{
+ struct pmic_gpio_state *state;
+ struct pmic_gpio_pad *pad;
+ int ret;
+
+ state = container_of(chip, struct pmic_gpio_state, chip);
+ pad = state->ctrl->desc->pins[pin].drv_data;
+
+ if (!pad->is_enabled)
+ return -EINVAL;
+
+ if (pad->input_enabled) {
+ ret = pmic_gpio_read(state, pad, PMIC_MPP_REG_RT_STS);
+ if (ret < 0)
+ return ret;
+
+ pad->out_value = ret & PMIC_MPP_REG_RT_STS_VAL_MASK;
+ }
+
+ return pad->out_value;
+}
+
+static void pmic_gpio_set(struct gpio_chip *chip, unsigned pin, int value)
+{
+ struct pmic_gpio_state *state;
+ unsigned long config;
+
+ state = container_of(chip, struct pmic_gpio_state, chip);
+ config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value);
+
+ pmic_gpio_config_set(state->ctrl, pin, &config, 1);
+}
+
+static int pmic_gpio_request(struct gpio_chip *chip, unsigned base)
+{
+ return pinctrl_request_gpio(chip->base + base);
+}
+
+static void pmic_gpio_free(struct gpio_chip *chip, unsigned base)
+{
+ pinctrl_free_gpio(chip->base + base);
+}
+
+static int pmic_gpio_of_xlate(struct gpio_chip *chip,
+ const struct of_phandle_args *gpio_desc,
+ u32 *flags)
+{
+ if (chip->of_gpio_n_cells < 2)
+ return -EINVAL;
+
+ if (flags)
+ *flags = gpio_desc->args[1];
+
+ return gpio_desc->args[0] - PMIC_GPIO_PHYSICAL_OFFSET;
+}
+
+static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned pin)
+{
+ struct pmic_gpio_state *state;
+ struct pmic_gpio_pad *pad;
+
+ state = container_of(chip, struct pmic_gpio_state, chip);
+ pad = state->ctrl->desc->pins[pin].drv_data;
+
+ return pad->irq;
+}
+
+static void pmic_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct pmic_gpio_state *state;
+ unsigned i;
+
+ state = container_of(chip, struct pmic_gpio_state, chip);
+
+ for (i = 0; i < chip->ngpio; i++) {
+ pmic_gpio_config_dbg_show(state->ctrl, s, i);
+ seq_puts(s, "\n");
+ }
+}
+
+static const struct gpio_chip pmic_gpio_gpio_template = {
+ .direction_input = pmic_gpio_direction_input,
+ .direction_output = pmic_gpio_direction_output,
+ .get = pmic_gpio_get,
+ .set = pmic_gpio_set,
+ .request = pmic_gpio_request,
+ .free = pmic_gpio_free,
+ .of_xlate = pmic_gpio_of_xlate,
+ .to_irq = pmic_gpio_to_irq,
+ .dbg_show = pmic_gpio_dbg_show,
+};
+
+static int pmic_gpio_populate(struct pmic_gpio_state *state,
+ struct pmic_gpio_pad *pad)
+{
+ int type, subtype, val, dir;
+
+ type = pmic_gpio_read(state, pad, PMIC_GPIO_REG_TYPE);
+ if (type < 0)
+ return type;
+
+ if (type != PMIC_GPIO_TYPE) {
+ dev_err(state->dev, "incorrect block type 0x%x at 0x%x\n",
+ type, pad->base);
+ return -ENODEV;
+ }
+
+ subtype = pmic_gpio_read(state, pad, PMIC_GPIO_REG_SUBTYPE);
+ if (subtype < 0)
+ return subtype;
+
+ switch (subtype) {
+ case PMIC_GPIO_SUBTYPE_GPIO_4CH:
+ pad->have_buffer = true;
+ case PMIC_GPIO_SUBTYPE_GPIOC_4CH:
+ pad->num_sources = 4;
+ break;
+ case PMIC_GPIO_SUBTYPE_GPIO_8CH:
+ pad->have_buffer = true;
+ case PMIC_GPIO_SUBTYPE_GPIOC_8CH:
+ pad->num_sources = 8;
+ break;
+ default:
+ dev_err(state->dev, "unknown GPIO type 0x%x\n", subtype);
+ return -ENODEV;
+ }
+
+ val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_MODE_CTL);
+ if (val < 0)
+ return val;
+
+ pad->out_value = val & PMIC_GPIO_REG_MODE_VALUE_SHIFT;
+
+ dir = val >> PMIC_GPIO_REG_MODE_DIR_SHIFT;
+ dir &= PMIC_GPIO_REG_MODE_DIR_MASK;
+ switch (dir) {
+ case 0:
+ pad->input_enabled = true;
+ pad->output_enabled = false;
+ break;
+ case 1:
+ pad->input_enabled = false;
+ pad->output_enabled = true;
+ break;
+ case 2:
+ pad->input_enabled = true;
+ pad->output_enabled = true;
+ break;
+ default:
+ dev_err(state->dev, "unknown GPIO direction\n");
+ return -ENODEV;
+ }
+
+ pad->function = val >> PMIC_GPIO_REG_MODE_FUNCTION_SHIFT;
+ pad->function &= PMIC_GPIO_REG_MODE_FUNCTION_MASK;
+
+ val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_DIG_VIN_CTL);
+ if (val < 0)
+ return val;
+
+ pad->power_source = val >> PMIC_GPIO_REG_VIN_SHIFT;
+ pad->power_source &= PMIC_GPIO_REG_VIN_MASK;
+
+ val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_DIG_PULL_CTL);
+ if (val < 0)
+ return val;
+
+ pad->pullup = val >> PMIC_GPIO_REG_PULL_SHIFT;
+ pad->pullup &= PMIC_GPIO_REG_PULL_MASK;
+
+ val = pmic_gpio_read(state, pad, PMIC_GPIO_REG_DIG_OUT_CTL);
+ if (val < 0)
+ return val;
+
+ pad->strength = val >> PMIC_GPIO_REG_OUT_STRENGTH_SHIFT;
+ pad->strength &= PMIC_GPIO_REG_OUT_STRENGTH_MASK;
+
+ pad->buffer_type = val >> PMIC_GPIO_REG_OUT_TYPE_SHIFT;
+ pad->buffer_type &= PMIC_GPIO_REG_OUT_TYPE_MASK;
+
+ /* Pin could be disabled with PIN_CONFIG_BIAS_HIGH_IMPEDANCE */
+ pad->is_enabled = true;
+ return 0;
+}
+
+static int pmic_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pinctrl_pin_desc *pindesc;
+ struct pinctrl_desc *pctrldesc;
+ struct pmic_gpio_pad *pad, *pads;
+ struct pmic_gpio_state *state;
+ int ret, npins, i;
+ u32 res[2];
+
+ ret = of_property_read_u32_array(dev->of_node, "reg", res, 2);
+ if (ret < 0) {
+ dev_err(dev, "missing base address and/or range");
+ return ret;
+ }
+
+ npins = res[1] / PMIC_GPIO_ADDRESS_RANGE;
+
+ if (!npins)
+ return -EINVAL;
+
+ BUG_ON(npins > ARRAY_SIZE(pmic_gpio_groups));
+
+ state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, state);
+
+ state->dev = &pdev->dev;
+ state->map = dev_get_regmap(dev->parent, NULL);
+
+ pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL);
+ if (!pindesc)
+ return -ENOMEM;
+
+ pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL);
+ if (!pads)
+ return -ENOMEM;
+
+ pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL);
+ if (!pctrldesc)
+ return -ENOMEM;
+
+ pctrldesc->pctlops = &pmic_gpio_pinctrl_ops;
+ pctrldesc->pmxops = &pmic_gpio_pinmux_ops;
+ pctrldesc->confops = &pmic_gpio_pinconf_ops;
+ pctrldesc->owner = THIS_MODULE;
+ pctrldesc->name = dev_name(dev);
+ pctrldesc->pins = pindesc;
+ pctrldesc->npins = npins;
+
+ for (i = 0; i < npins; i++, pindesc++) {
+ pad = &pads[i];
+ pindesc->drv_data = pad;
+ pindesc->number = i;
+ pindesc->name = pmic_gpio_groups[i];
+
+ pad->irq = platform_get_irq(pdev, i);
+ if (pad->irq < 0)
+ return pad->irq;
+
+ pad->base = res[0] + i * PMIC_GPIO_ADDRESS_RANGE;
+
+ ret = pmic_gpio_populate(state, pad);
+ if (ret < 0)
+ return ret;
+ }
+
+ state->chip = pmic_gpio_gpio_template;
+ state->chip.dev = dev;
+ state->chip.base = -1;
+ state->chip.ngpio = npins;
+ state->chip.label = dev_name(dev);
+ state->chip.of_gpio_n_cells = 2;
+ state->chip.can_sleep = false;
+
+ state->ctrl = pinctrl_register(pctrldesc, dev, state);
+ if (!state->ctrl)
+ return -ENODEV;
+
+ ret = gpiochip_add(&state->chip);
+ if (ret) {
+ dev_err(state->dev, "can't add gpio chip\n");
+ goto err_chip;
+ }
+
+ ret = gpiochip_add_pin_range(&state->chip, dev_name(dev), 0, 0, npins);
+ if (ret) {
+ dev_err(dev, "failed to add pin range\n");
+ goto err_range;
+ }
+
+ return 0;
+
+err_range:
+ gpiochip_remove(&state->chip);
+err_chip:
+ pinctrl_unregister(state->ctrl);
+ return ret;
+}
+
+static int pmic_gpio_remove(struct platform_device *pdev)
+{
+ struct pmic_gpio_state *state = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&state->chip);
+ pinctrl_unregister(state->ctrl);
+ return 0;
+}
+
+static const struct of_device_id pmic_gpio_of_match[] = {
+ { .compatible = "qcom,spmi-pmic-gpio" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, pmic_gpio_of_match);
+
+static struct platform_driver pmic_gpio_driver = {
+ .driver = {
+ .name = "spmi-pmic-gpio",
+ .owner = THIS_MODULE,
+ .of_match_table = pmic_gpio_of_match,
+ },
+ .probe = pmic_gpio_probe,
+ .remove = pmic_gpio_remove,
+};
+
+module_platform_driver(pmic_gpio_driver);
+
+MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>");
+MODULE_DESCRIPTION("Qualcomm SPMI PMIC GPIO pin control driver");
+MODULE_ALIAS("platform:spmi-pmic-gpio");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-pmic-mpp.c b/drivers/pinctrl/qcom/pinctrl-spmi-pmic-mpp.c
new file mode 100644
index 000000000000..2ab3b1f3e545
--- /dev/null
+++ b/drivers/pinctrl/qcom/pinctrl-spmi-pmic-mpp.c
@@ -0,0 +1,952 @@
+/*
+ * Copyright (c) 2012-2014, 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/gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <dt-bindings/pinctrl/qcom,pmic-mpp.h>
+
+#include "../core.h"
+#include "../pinctrl-utils.h"
+
+#define PMIC_MPP_ADDRESS_RANGE 0x100
+
+/*
+ * Pull Up Values - it indicates whether a pull-up should be
+ * applied for bidirectional mode only. The hardware ignores the
+ * configuration when operating in other modes.
+ */
+#define PMIC_MPP_PULL_UP_0P6KOHM 0
+#define PMIC_MPP_PULL_UP_10KOHM 1
+#define PMIC_MPP_PULL_UP_30KOHM 2
+#define PMIC_MPP_PULL_UP_OPEN 3
+
+/* type registers base address bases */
+#define PMIC_MPP_REG_TYPE 0x4
+#define PMIC_MPP_REG_SUBTYPE 0x5
+
+/* mpp peripheral type and subtype values */
+#define PMIC_MPP_TYPE 0x11
+#define PMIC_MPP_SUBTYPE_4CH_NO_ANA_OUT 0x3
+#define PMIC_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT 0x4
+#define PMIC_MPP_SUBTYPE_4CH_NO_SINK 0x5
+#define PMIC_MPP_SUBTYPE_ULT_4CH_NO_SINK 0x6
+#define PMIC_MPP_SUBTYPE_4CH_FULL_FUNC 0x7
+#define PMIC_MPP_SUBTYPE_8CH_FULL_FUNC 0xf
+
+#define PMIC_MPP_REG_RT_STS 0x10
+#define PMIC_MPP_REG_RT_STS_VAL_MASK 0x1
+
+/* control register base address bases */
+#define PMIC_MPP_REG_MODE_CTL 0x40
+#define PMIC_MPP_REG_DIG_VIN_CTL 0x41
+#define PMIC_MPP_REG_DIG_PULL_CTL 0x42
+#define PMIC_MPP_REG_DIG_IN_CTL 0x43
+#define PMIC_MPP_REG_EN_CTL 0x46
+#define PMIC_MPP_REG_AIN_CTL 0x4a
+
+/* PMIC_MPP_REG_MODE_CTL */
+#define PMIC_MPP_REG_MODE_VALUE_MASK 0x1
+#define PMIC_MPP_REG_MODE_FUNCTION_SHIFT 1
+#define PMIC_MPP_REG_MODE_FUNCTION_MASK 0x7
+#define PMIC_MPP_REG_MODE_DIR_SHIFT 4
+#define PMIC_MPP_REG_MODE_DIR_MASK 0x7
+
+/* PMIC_MPP_REG_DIG_VIN_CTL */
+#define PMIC_MPP_REG_VIN_SHIFT 0
+#define PMIC_MPP_REG_VIN_MASK 0x7
+
+/* PMIC_MPP_REG_DIG_PULL_CTL */
+#define PMIC_MPP_REG_PULL_SHIFT 0
+#define PMIC_MPP_REG_PULL_MASK 0x7
+
+/* PMIC_MPP_REG_EN_CTL */
+#define PMIC_MPP_REG_MASTER_EN_SHIFT 7
+
+/* PMIC_MPP_REG_AIN_CTL */
+#define PMIC_MPP_REG_AIN_ROUTE_SHIFT 0
+#define PMIC_MPP_REG_AIN_ROUTE_MASK 0x7
+
+#define PMIC_MPP_PHYSICAL_OFFSET 1
+
+/* Qualcomm specific pin configurations */
+#define PMIC_MPP_CONF_AMUX_ROUTE (PIN_CONFIG_END + 1)
+#define PMIC_MPP_CONF_ANALOG_MODE (PIN_CONFIG_END + 2)
+
+/**
+ * struct pmic_mpp_pad - keep current MPP settings
+ * @base: Address base in SPMI device.
+ * @irq: IRQ number which this MPP generate.
+ * @is_enabled: Set to false when MPP should be put in high Z state.
+ * @out_value: Cached pin output value.
+ * @output_enabled: Set to true if MPP output logic is enabled.
+ * @input_enabled: Set to true if MPP input buffer logic is enabled.
+ * @analog_mode: Set to true when MPP should operate in Analog Input, Analog
+ * Output or Bidirectional Analog mode.
+ * @num_sources: Number of power-sources supported by this MPP.
+ * @power_source: Current power-source used.
+ * @amux_input: Set the source for analog input.
+ * @pullup: Pullup resistor value. Valid in Bidirectional mode only.
+ * @function: See pmic_mpp_functions[].
+ */
+struct pmic_mpp_pad {
+ u16 base;
+ int irq;
+ bool is_enabled;
+ bool out_value;
+ bool output_enabled;
+ bool input_enabled;
+ bool analog_mode;
+ unsigned int num_sources;
+ unsigned int power_source;
+ unsigned int amux_input;
+ unsigned int pullup;
+ unsigned int function;
+};
+
+struct pmic_mpp_state {
+ struct device *dev;
+ struct regmap *map;
+ struct pinctrl_dev *ctrl;
+ struct gpio_chip chip;
+};
+
+struct pmic_mpp_bindings {
+ const char *property;
+ unsigned param;
+};
+
+static struct pmic_mpp_bindings pmic_mpp_bindings[] = {
+ {"qcom,amux-route", PMIC_MPP_CONF_AMUX_ROUTE},
+ {"qcom,analog-mode", PMIC_MPP_CONF_ANALOG_MODE},
+};
+
+static const char *const pmic_mpp_groups[] = {
+ "mpp1", "mpp2", "mpp3", "mpp4", "mpp5", "mpp6", "mpp7", "mpp8",
+};
+
+static const char *const pmic_mpp_functions[] = {
+ PMIC_MPP_FUNC_NORMAL, PMIC_MPP_FUNC_PAIRED,
+ "reserved1", "reserved2",
+ PMIC_MPP_FUNC_DTEST1, PMIC_MPP_FUNC_DTEST2,
+ PMIC_MPP_FUNC_DTEST3, PMIC_MPP_FUNC_DTEST4,
+};
+
+static int pmic_mpp_read(struct pmic_mpp_state *state,
+ struct pmic_mpp_pad *pad, unsigned int addr)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(state->map, pad->base + addr, &val);
+ if (ret < 0)
+ dev_err(state->dev, "read 0x%x failed\n", addr);
+ else
+ ret = val;
+
+ return ret;
+}
+
+static int pmic_mpp_write(struct pmic_mpp_state *state,
+ struct pmic_mpp_pad *pad, unsigned int addr,
+ unsigned int val)
+{
+ int ret;
+
+ ret = regmap_write(state->map, pad->base + addr, val);
+ if (ret < 0)
+ dev_err(state->dev, "write 0x%x failed\n", addr);
+
+ return ret;
+}
+
+static int pmic_mpp_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ /* Every PIN is a group */
+ return pctldev->desc->npins;
+}
+
+static const char *pmic_mpp_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned pin)
+{
+ return pctldev->desc->pins[pin].name;
+}
+
+static int pmic_mpp_get_group_pins(struct pinctrl_dev *pctldev,
+ unsigned pin,
+ const unsigned **pins, unsigned *num_pins)
+{
+ *pins = &pctldev->desc->pins[pin].number;
+ *num_pins = 1;
+ return 0;
+}
+
+static int pmic_mpp_parse_dt_config(struct device_node *np,
+ struct pinctrl_dev *pctldev,
+ unsigned long **configs,
+ unsigned int *nconfs)
+{
+ struct pmic_mpp_bindings *par;
+ unsigned long cfg;
+ int ret, i;
+ u32 val;
+
+ for (i = 0; i < ARRAY_SIZE(pmic_mpp_bindings); i++) {
+
+ par = &pmic_mpp_bindings[i];
+ ret = of_property_read_u32(np, par->property, &val);
+
+ /* property not found */
+ if (ret == -EINVAL)
+ continue;
+
+ /* use zero as default value, when no value is specified */
+ if (ret)
+ val = 0;
+
+ dev_dbg(pctldev->dev, "found %s with value %u\n",
+ par->property, val);
+
+ cfg = pinconf_to_config_packed(par->param, val);
+
+ ret = pinctrl_utils_add_config(pctldev, configs, nconfs, cfg);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pmic_mpp_dt_subnode_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np,
+ struct pinctrl_map **map,
+ unsigned *reserv, unsigned *nmaps,
+ enum pinctrl_map_type type)
+{
+ unsigned long *configs = NULL;
+ unsigned nconfs = 0;
+ struct property *prop;
+ const char *group;
+ int ret;
+
+ ret = pmic_mpp_parse_dt_config(np, pctldev, &configs, &nconfs);
+ if (ret < 0)
+ return ret;
+
+ if (!nconfs)
+ return 0;
+
+ ret = of_property_count_strings(np, "pins");
+ if (ret < 0)
+ goto exit;
+
+ ret = pinctrl_utils_reserve_map(pctldev, map, reserv, nmaps, ret);
+ if (ret < 0)
+ goto exit;
+
+ of_property_for_each_string(np, "pins", prop, group) {
+ ret = pinctrl_utils_add_map_configs(pctldev, map,
+ reserv, nmaps, group,
+ configs, nconfs, type);
+ if (ret < 0)
+ break;
+ }
+exit:
+ kfree(configs);
+ return ret;
+}
+
+static int pmic_mpp_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np_config,
+ struct pinctrl_map **map, unsigned *nmaps)
+{
+ struct device_node *np;
+ enum pinctrl_map_type type;
+ unsigned reserv;
+ int ret;
+
+ ret = 0;
+ *map = NULL;
+ *nmaps = 0;
+ reserv = 0;
+ type = PIN_MAP_TYPE_CONFIGS_GROUP;
+
+ for_each_child_of_node(np_config, np) {
+
+ ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map,
+ &reserv, nmaps, type);
+ if (ret)
+ break;
+
+ ret = pmic_mpp_dt_subnode_to_map(pctldev, np, map, &reserv,
+ nmaps, type);
+ if (ret)
+ break;
+ }
+
+ if (ret < 0)
+ pinctrl_utils_dt_free_map(pctldev, *map, *nmaps);
+
+ return ret;
+}
+
+static const struct pinctrl_ops pmic_mpp_pinctrl_ops = {
+ .get_groups_count = pmic_mpp_get_groups_count,
+ .get_group_name = pmic_mpp_get_group_name,
+ .get_group_pins = pmic_mpp_get_group_pins,
+ .dt_node_to_map = pmic_mpp_dt_node_to_map,
+ .dt_free_map = pinctrl_utils_dt_free_map,
+};
+
+static int pmic_mpp_get_functions_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(pmic_mpp_functions);
+}
+
+static const char *pmic_mpp_get_function_name(struct pinctrl_dev *pctldev,
+ unsigned function)
+{
+ return pmic_mpp_functions[function];
+}
+
+static int pmic_mpp_get_function_groups(struct pinctrl_dev *pctldev,
+ unsigned function,
+ const char *const **groups,
+ unsigned *const num_qgroups)
+{
+ *groups = pmic_mpp_groups;
+ *num_qgroups = pctldev->desc->npins;
+ return 0;
+}
+
+static int pmic_mpp_pinmux_enable(struct pinctrl_dev *pctldev,
+ unsigned function, unsigned pin)
+{
+ struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev);
+ struct pmic_mpp_pad *pad;
+ unsigned int val;
+ int ret;
+
+ pad = pctldev->desc->pins[pin].drv_data;
+
+ pad->function = function;
+
+ if (!pad->analog_mode) {
+ val = 0; /* just digital input */
+ if (pad->output_enabled) {
+ if (pad->input_enabled)
+ val = 2; /* digital input and output */
+ else
+ val = 1; /* just digital output */
+ }
+ } else {
+ val = 4; /* just analog input */
+ if (pad->output_enabled) {
+ if (pad->input_enabled)
+ val = 3; /* analog input and output */
+ else
+ val = 5; /* just analog output */
+ }
+ }
+
+ val |= pad->function << PMIC_MPP_REG_MODE_FUNCTION_SHIFT;
+ val |= pad->out_value & PMIC_MPP_REG_MODE_VALUE_MASK;
+
+ ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_MODE_CTL, val);
+ if (ret < 0)
+ return ret;
+
+ val = pad->is_enabled << PMIC_MPP_REG_MASTER_EN_SHIFT;
+
+ return pmic_mpp_write(state, pad, PMIC_MPP_REG_EN_CTL, val);
+}
+
+static const struct pinmux_ops pmic_mpp_pinmux_ops = {
+ .get_functions_count = pmic_mpp_get_functions_count,
+ .get_function_name = pmic_mpp_get_function_name,
+ .get_function_groups = pmic_mpp_get_function_groups,
+ .set_mux = pmic_mpp_pinmux_enable,
+};
+
+static int pmic_mpp_config_get(struct pinctrl_dev *pctldev,
+ unsigned int pin, unsigned long *config)
+{
+ unsigned param = pinconf_to_config_param(*config);
+ struct pmic_mpp_pad *pad;
+ unsigned arg = 0;
+
+ pad = pctldev->desc->pins[pin].drv_data;
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ arg = pad->pullup == PMIC_MPP_PULL_UP_OPEN;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ switch (pad->pullup) {
+ case PMIC_MPP_PULL_UP_OPEN:
+ arg = 0;
+ break;
+ case PMIC_MPP_PULL_UP_0P6KOHM:
+ arg = 600;
+ break;
+ case PMIC_MPP_PULL_UP_10KOHM:
+ arg = 10000;
+ break;
+ case PMIC_MPP_PULL_UP_30KOHM:
+ arg = 30000;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ arg = !pad->is_enabled;
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ arg = pad->power_source;
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ arg = pad->input_enabled;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ arg = pad->out_value;
+ break;
+ case PMIC_MPP_CONF_AMUX_ROUTE:
+ arg = pad->amux_input;
+ break;
+ case PMIC_MPP_CONF_ANALOG_MODE:
+ arg = pad->analog_mode;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Convert register value to pinconf value */
+ *config = pinconf_to_config_packed(param, arg);
+ return 0;
+}
+
+static int pmic_mpp_config_set(struct pinctrl_dev *pctldev, unsigned int pin,
+ unsigned long *configs, unsigned nconfs)
+{
+ struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev);
+ struct pmic_mpp_pad *pad;
+ unsigned param, arg;
+ unsigned int val;
+ int i, ret;
+
+ pad = pctldev->desc->pins[pin].drv_data;
+
+ for (i = 0; i < nconfs; i++) {
+ param = pinconf_to_config_param(configs[i]);
+ arg = pinconf_to_config_argument(configs[i]);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ pad->pullup = PMIC_MPP_PULL_UP_OPEN;
+ break;
+ case PIN_CONFIG_BIAS_PULL_UP:
+ switch (arg) {
+ case 600:
+ pad->pullup = PMIC_MPP_PULL_UP_0P6KOHM;
+ break;
+ case 10000:
+ pad->pullup = PMIC_MPP_PULL_UP_10KOHM;
+ break;
+ case 30000:
+ pad->pullup = PMIC_MPP_PULL_UP_30KOHM;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ pad->is_enabled = false;
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ if (arg >= pad->num_sources)
+ return -EINVAL;
+ pad->power_source = arg;
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ pad->input_enabled = arg ? true : false;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ pad->output_enabled = true;
+ pad->out_value = arg;
+ break;
+ case PMIC_MPP_CONF_AMUX_ROUTE:
+ if (arg >= PMIC_MPP_AMUX_ROUTE_ABUS4)
+ return -EINVAL;
+ pad->amux_input = arg;
+ break;
+ case PMIC_MPP_CONF_ANALOG_MODE:
+ pad->analog_mode = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ val = pad->power_source << PMIC_MPP_REG_VIN_SHIFT;
+
+ ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_DIG_VIN_CTL, val);
+ if (ret < 0)
+ return ret;
+
+ val = pad->pullup << PMIC_MPP_REG_PULL_SHIFT;
+
+ ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_DIG_PULL_CTL, val);
+ if (ret < 0)
+ return ret;
+
+ val = pad->amux_input & PMIC_MPP_REG_AIN_ROUTE_MASK;
+
+ ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_AIN_CTL, val);
+ if (ret < 0)
+ return ret;
+
+ if (!pad->analog_mode) {
+ val = 0; /* just digital input */
+ if (pad->output_enabled) {
+ if (pad->input_enabled)
+ val = 2; /* digital input and output */
+ else
+ val = 1; /* just digital output */
+ }
+ } else {
+ val = 4; /* just analog input */
+ if (pad->output_enabled) {
+ if (pad->input_enabled)
+ val = 3; /* analog input and output */
+ else
+ val = 5; /* just analog output */
+ }
+ }
+
+ val = val << PMIC_MPP_REG_MODE_DIR_SHIFT;
+ val |= pad->function << PMIC_MPP_REG_MODE_FUNCTION_SHIFT;
+ val |= pad->out_value & PMIC_MPP_REG_MODE_VALUE_MASK;
+
+ return pmic_mpp_write(state, pad, PMIC_MPP_REG_MODE_CTL, val);
+}
+
+static void pmic_mpp_config_dbg_show(struct pinctrl_dev *pctldev,
+ struct seq_file *s, unsigned pin)
+{
+ struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev);
+ struct pmic_mpp_pad *pad;
+ int ret, val;
+
+ static const char *const biases[] = {
+ "0.6kOhm", "10kOhm", "30kOhm", "Disabled"
+ };
+
+
+ pad = pctldev->desc->pins[pin].drv_data;
+
+ seq_printf(s, " mpp%-2d:", pin + PMIC_MPP_PHYSICAL_OFFSET);
+
+ val = pmic_mpp_read(state, pad, PMIC_MPP_REG_EN_CTL);
+
+ if (val < 0 || !(val >> PMIC_MPP_REG_MASTER_EN_SHIFT)) {
+ seq_puts(s, " ---");
+ } else {
+
+ if (pad->input_enabled) {
+ ret = pmic_mpp_read(state, pad, PMIC_MPP_REG_RT_STS);
+ if (!ret) {
+ ret &= PMIC_MPP_REG_RT_STS_VAL_MASK;
+ pad->out_value = ret;
+ }
+ }
+
+ seq_printf(s, " %-4s", pad->output_enabled ? "out" : "in");
+ seq_printf(s, " %-4s", pad->analog_mode ? "ana" : "dig");
+ seq_printf(s, " %-7s", pmic_mpp_functions[pad->function]);
+ seq_printf(s, " vin-%d", pad->power_source);
+ seq_printf(s, " %-8s", biases[pad->pullup]);
+ seq_printf(s, " %-4s", pad->out_value ? "high" : "low");
+ }
+}
+
+static const struct pinconf_ops pmic_mpp_pinconf_ops = {
+ .pin_config_group_get = pmic_mpp_config_get,
+ .pin_config_group_set = pmic_mpp_config_set,
+ .pin_config_group_dbg_show = pmic_mpp_config_dbg_show,
+};
+
+static int pmic_mpp_direction_input(struct gpio_chip *chip, unsigned pin)
+{
+ struct pmic_mpp_state *state;
+ unsigned long config;
+
+ state = container_of(chip, struct pmic_mpp_state, chip);
+ config = pinconf_to_config_packed(PIN_CONFIG_INPUT_ENABLE, 1);
+
+ return pmic_mpp_config_set(state->ctrl, pin, &config, 1);
+}
+
+static int pmic_mpp_direction_output(struct gpio_chip *chip,
+ unsigned pin, int val)
+{
+ struct pmic_mpp_state *state;
+ unsigned long config;
+
+ state = container_of(chip, struct pmic_mpp_state, chip);
+ config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, val);
+
+ return pmic_mpp_config_set(state->ctrl, pin, &config, 1);
+}
+
+static int pmic_mpp_get(struct gpio_chip *chip, unsigned pin)
+{
+ struct pmic_mpp_state *state;
+ struct pmic_mpp_pad *pad;
+ int ret;
+
+ state = container_of(chip, struct pmic_mpp_state, chip);
+ pad = state->ctrl->desc->pins[pin].drv_data;
+
+ if (pad->input_enabled) {
+ ret = pmic_mpp_read(state, pad, PMIC_MPP_REG_RT_STS);
+ if (ret < 0)
+ return ret;
+
+ pad->out_value = ret & PMIC_MPP_REG_RT_STS_VAL_MASK;
+ }
+
+ return pad->out_value;
+}
+
+static void pmic_mpp_set(struct gpio_chip *chip, unsigned pin, int value)
+{
+ struct pmic_mpp_state *state;
+ unsigned long config;
+
+ state = container_of(chip, struct pmic_mpp_state, chip);
+ config = pinconf_to_config_packed(PIN_CONFIG_OUTPUT, value);
+
+ pmic_mpp_config_set(state->ctrl, pin, &config, 1);
+}
+
+static int pmic_mpp_request(struct gpio_chip *chip, unsigned base)
+{
+ return pinctrl_request_gpio(chip->base + base);
+}
+
+static void pmic_mpp_free(struct gpio_chip *chip, unsigned base)
+{
+ pinctrl_free_gpio(chip->base + base);
+}
+
+static int pmic_mpp_of_xlate(struct gpio_chip *chip,
+ const struct of_phandle_args *gpio_desc,
+ u32 *flags)
+{
+ if (chip->of_gpio_n_cells < 2)
+ return -EINVAL;
+
+ if (flags)
+ *flags = gpio_desc->args[1];
+
+ return gpio_desc->args[0] - PMIC_MPP_PHYSICAL_OFFSET;
+}
+
+static int pmic_mpp_to_irq(struct gpio_chip *chip, unsigned pin)
+{
+ struct pmic_mpp_state *state;
+ struct pmic_mpp_pad *pad;
+
+ state = container_of(chip, struct pmic_mpp_state, chip);
+ pad = state->ctrl->desc->pins[pin].drv_data;
+
+ return pad->irq;
+}
+
+static void pmic_mpp_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct pmic_mpp_state *state;
+ unsigned i;
+
+ state = container_of(chip, struct pmic_mpp_state, chip);
+
+ for (i = 0; i < chip->ngpio; i++) {
+ pmic_mpp_config_dbg_show(state->ctrl, s, i);
+ seq_puts(s, "\n");
+ }
+}
+
+static const struct gpio_chip pmic_mpp_gpio_template = {
+ .direction_input = pmic_mpp_direction_input,
+ .direction_output = pmic_mpp_direction_output,
+ .get = pmic_mpp_get,
+ .set = pmic_mpp_set,
+ .request = pmic_mpp_request,
+ .free = pmic_mpp_free,
+ .of_xlate = pmic_mpp_of_xlate,
+ .to_irq = pmic_mpp_to_irq,
+ .dbg_show = pmic_mpp_dbg_show,
+};
+
+static int pmic_mpp_populate(struct pmic_mpp_state *state,
+ struct pmic_mpp_pad *pad)
+{
+ int type, subtype, val, dir;
+
+ type = pmic_mpp_read(state, pad, PMIC_MPP_REG_TYPE);
+ if (type < 0)
+ return type;
+
+ if (type != PMIC_MPP_TYPE) {
+ dev_err(state->dev, "incorrect block type 0x%x at 0x%x\n",
+ type, pad->base);
+ return -ENODEV;
+ }
+
+ subtype = pmic_mpp_read(state, pad, PMIC_MPP_REG_SUBTYPE);
+ if (subtype < 0)
+ return subtype;
+
+ switch (subtype) {
+ case PMIC_MPP_SUBTYPE_4CH_NO_ANA_OUT:
+ case PMIC_MPP_SUBTYPE_ULT_4CH_NO_ANA_OUT:
+ case PMIC_MPP_SUBTYPE_4CH_NO_SINK:
+ case PMIC_MPP_SUBTYPE_ULT_4CH_NO_SINK:
+ case PMIC_MPP_SUBTYPE_4CH_FULL_FUNC:
+ pad->num_sources = 4;
+ break;
+ case PMIC_MPP_SUBTYPE_8CH_FULL_FUNC:
+ pad->num_sources = 8;
+ break;
+ default:
+ dev_err(state->dev, "unknown MPP type 0x%x at 0x%x\n",
+ subtype, pad->base);
+ return -ENODEV;
+ }
+
+ val = pmic_mpp_read(state, pad, PMIC_MPP_REG_MODE_CTL);
+ if (val < 0)
+ return val;
+
+ pad->out_value = val & PMIC_MPP_REG_MODE_VALUE_MASK;
+
+ dir = val >> PMIC_MPP_REG_MODE_DIR_SHIFT;
+ dir &= PMIC_MPP_REG_MODE_DIR_MASK;
+
+ switch (dir) {
+ case 0:
+ pad->input_enabled = true;
+ pad->output_enabled = false;
+ pad->analog_mode = false;
+ break;
+ case 1:
+ pad->input_enabled = false;
+ pad->output_enabled = true;
+ pad->analog_mode = false;
+ break;
+ case 2:
+ pad->input_enabled = true;
+ pad->output_enabled = true;
+ pad->analog_mode = false;
+ break;
+ case 3:
+ pad->input_enabled = true;
+ pad->output_enabled = true;
+ pad->analog_mode = true;
+ break;
+ case 4:
+ pad->input_enabled = true;
+ pad->output_enabled = false;
+ pad->analog_mode = true;
+ break;
+ case 5:
+ pad->input_enabled = false;
+ pad->output_enabled = true;
+ pad->analog_mode = true;
+ break;
+ default:
+ dev_err(state->dev, "unknown MPP direction\n");
+ return -ENODEV;
+ }
+
+ pad->function = val >> PMIC_MPP_REG_MODE_FUNCTION_SHIFT;
+ pad->function &= PMIC_MPP_REG_MODE_FUNCTION_MASK;
+
+ val = pmic_mpp_read(state, pad, PMIC_MPP_REG_DIG_VIN_CTL);
+ if (val < 0)
+ return val;
+
+ pad->power_source = val >> PMIC_MPP_REG_VIN_SHIFT;
+ pad->power_source &= PMIC_MPP_REG_VIN_MASK;
+
+ val = pmic_mpp_read(state, pad, PMIC_MPP_REG_DIG_PULL_CTL);
+ if (val < 0)
+ return val;
+
+ pad->pullup = val >> PMIC_MPP_REG_PULL_SHIFT;
+ pad->pullup &= PMIC_MPP_REG_PULL_MASK;
+
+ val = pmic_mpp_read(state, pad, PMIC_MPP_REG_AIN_CTL);
+ if (val < 0)
+ return val;
+
+ pad->amux_input = val >> PMIC_MPP_REG_AIN_ROUTE_SHIFT;
+ pad->amux_input &= PMIC_MPP_REG_AIN_ROUTE_MASK;
+
+ /* Pin could be disabled with PIN_CONFIG_BIAS_HIGH_IMPEDANCE */
+ pad->is_enabled = true;
+ return 0;
+}
+
+static int pmic_mpp_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pinctrl_pin_desc *pindesc;
+ struct pinctrl_desc *pctrldesc;
+ struct pmic_mpp_pad *pad, *pads;
+ struct pmic_mpp_state *state;
+ int ret, npins, i;
+ u32 res[2];
+
+ ret = of_property_read_u32_array(dev->of_node, "reg", res, 2);
+ if (ret < 0) {
+ dev_err(dev, "missing base address and/or range");
+ return ret;
+ }
+
+ npins = res[1] / PMIC_MPP_ADDRESS_RANGE;
+ if (!npins)
+ return -EINVAL;
+
+ BUG_ON(npins > ARRAY_SIZE(pmic_mpp_groups));
+
+ state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, state);
+
+ state->dev = &pdev->dev;
+ state->map = dev_get_regmap(dev->parent, NULL);
+
+ pindesc = devm_kcalloc(dev, npins, sizeof(*pindesc), GFP_KERNEL);
+ if (!pindesc)
+ return -ENOMEM;
+
+ pads = devm_kcalloc(dev, npins, sizeof(*pads), GFP_KERNEL);
+ if (!pads)
+ return -ENOMEM;
+
+ pctrldesc = devm_kzalloc(dev, sizeof(*pctrldesc), GFP_KERNEL);
+ if (!pctrldesc)
+ return -ENOMEM;
+
+ pctrldesc->pctlops = &pmic_mpp_pinctrl_ops;
+ pctrldesc->pmxops = &pmic_mpp_pinmux_ops;
+ pctrldesc->confops = &pmic_mpp_pinconf_ops;
+ pctrldesc->owner = THIS_MODULE;
+ pctrldesc->name = dev_name(dev);
+ pctrldesc->pins = pindesc;
+ pctrldesc->npins = npins;
+
+ for (i = 0; i < npins; i++, pindesc++) {
+ pad = &pads[i];
+ pindesc->drv_data = pad;
+ pindesc->number = i;
+ pindesc->name = pmic_mpp_groups[i];
+
+ pad->irq = platform_get_irq(pdev, i);
+ if (pad->irq < 0)
+ return pad->irq;
+
+ pad->base = res[0] + i * PMIC_MPP_ADDRESS_RANGE;
+
+ ret = pmic_mpp_populate(state, pad);
+ if (ret < 0)
+ return ret;
+ }
+
+ state->chip = pmic_mpp_gpio_template;
+ state->chip.dev = dev;
+ state->chip.base = -1;
+ state->chip.ngpio = npins;
+ state->chip.label = dev_name(dev);
+ state->chip.of_gpio_n_cells = 2;
+ state->chip.can_sleep = false;
+
+ state->ctrl = pinctrl_register(pctrldesc, dev, state);
+ if (!state->ctrl)
+ return -ENODEV;
+
+ ret = gpiochip_add(&state->chip);
+ if (ret) {
+ dev_err(state->dev, "can't add gpio chip\n");
+ goto err_chip;
+ }
+
+ ret = gpiochip_add_pin_range(&state->chip, dev_name(dev), 0, 0, npins);
+ if (ret) {
+ dev_err(dev, "failed to add pin range\n");
+ goto err_range;
+ }
+
+ return 0;
+
+err_range:
+ gpiochip_remove(&state->chip);
+err_chip:
+ pinctrl_unregister(state->ctrl);
+ return ret;
+}
+
+static int pmic_mpp_remove(struct platform_device *pdev)
+{
+ struct pmic_mpp_state *state = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&state->chip);
+ pinctrl_unregister(state->ctrl);
+ return 0;
+}
+
+static const struct of_device_id pmic_mpp_of_match[] = {
+ { .compatible = "qcom,spmi-pmic-mpp" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, pmic_mpp_of_match);
+
+static struct platform_driver pmic_mpp_driver = {
+ .driver = {
+ .name = "spmi-pmic-mpp",
+ .owner = THIS_MODULE,
+ .of_match_table = pmic_mpp_of_match,
+ },
+ .probe = pmic_mpp_probe,
+ .remove = pmic_mpp_remove,
+};
+
+module_platform_driver(pmic_mpp_driver);
+
+MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>");
+MODULE_DESCRIPTION("Qualcomm SPMI PMIC MPP pin control driver");
+MODULE_ALIAS("platform:spmi-pmic-mpp");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
new file mode 100644
index 000000000000..e02145e11baf
--- /dev/null
+++ b/drivers/pinctrl/qcom/pinctrl-ssbi-pmic.c
@@ -0,0 +1,875 @@
+/*
+ * Copyright (c) 2014, Sony Mobile Communications AB.
+ * Copyright (c) 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
+ * 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/platform_device.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/gpio.h>
+#include <linux/mfd/pm8921-core.h>
+
+#include <dt-bindings/pinctrl/qcom,pmic-gpio.h>
+
+#include "../core.h"
+#include "../pinconf.h"
+#include "../pinctrl-utils.h"
+
+/* direction */
+#define PM8XXX_GPIO_DIR_OUT BIT(0)
+#define PM8XXX_GPIO_DIR_IN BIT(1)
+
+/* output buffer */
+#define PM8XXX_GPIO_PUSH_PULL 0
+#define PM8XXX_GPIO_OPEN_DRAIN 1
+
+/* bias */
+#define PM8XXX_GPIO_BIAS_PU_30 0
+#define PM8XXX_GPIO_BIAS_PU_1P5 1
+#define PM8XXX_GPIO_BIAS_PU_31P5 2
+#define PM8XXX_GPIO_BIAS_PU_1P5_30 3
+#define PM8XXX_GPIO_BIAS_PD 4
+#define PM8XXX_GPIO_BIAS_NP 5
+
+/* GPIO registers */
+#define SSBI_REG_ADDR_GPIO_BASE 0x150
+#define SSBI_REG_ADDR_GPIO(n) (SSBI_REG_ADDR_GPIO_BASE + n)
+
+#define PM8XXX_GPIO_MODE_ENABLE BIT(0)
+#define PM8XXX_GPIO_WRITE BIT(7)
+
+#define PM8XXX_MAX_GPIOS 44
+
+/* Qualcomm specific pin configurations */
+#define PM8XXX_PINCONF_PULL_UP (PIN_CONFIG_END + 1)
+#define PM8XXX_PINCONF_STRENGTH (PIN_CONFIG_END + 2)
+
+struct pm8xxx_pinbindings {
+ const char *property;
+ unsigned param;
+ u32 default_value;
+};
+static struct pm8xxx_pinbindings pm8xxx_pinbindings[] = {
+ /* PMIC_GPIO_PULL_UP_30... */
+ {"qcom,pull-up-strength", PM8XXX_PINCONF_PULL_UP, 0},
+ /* PMIC_GPIO_STRENGTH_NO... */
+ {"qcom,drive-strength", PM8XXX_PINCONF_STRENGTH, 0},
+};
+
+struct pm8xxx_gpio_pin {
+ int irq;
+
+ u8 power_source;
+ u8 direction;
+ u8 output_buffer;
+ u8 output_value;
+ u8 bias;
+ u8 output_strength;
+ u8 disable;
+ u8 function;
+ u8 non_inverted;
+};
+
+struct pm8xxx_gpio_data {
+ int ngpio;
+};
+
+struct pm8xxx_gpio {
+ struct device *dev;
+ struct regmap *regmap;
+ struct pinctrl_dev *pctrl;
+ struct gpio_chip chip;
+
+ const struct pm8xxx_gpio_data *data;
+
+ struct pm8xxx_gpio_pin pins[PM8XXX_MAX_GPIOS];
+};
+
+static inline struct pm8xxx_gpio *to_pm8xxx_gpio(struct gpio_chip *chip)
+{
+ return container_of(chip, struct pm8xxx_gpio, chip);
+};
+
+static const char * const pm8xxx_gpio_groups[PM8XXX_MAX_GPIOS] = {
+ "gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7", "gpio8",
+ "gpio9", "gpio10", "gpio11", "gpio12", "gpio13", "gpio14", "gpio15",
+ "gpio16", "gpio17", "gpio18", "gpio19", "gpio20", "gpio21", "gpio22",
+ "gpio23", "gpio24", "gpio25", "gpio26", "gpio27", "gpio28", "gpio29",
+ "gpio30", "gpio31", "gpio32", "gpio33", "gpio34", "gpio35", "gpio36",
+ "gpio37", "gpio38", "gpio39", "gpio40", "gpio41", "gpio42", "gpio43",
+ "gpio44",
+};
+
+static const char * const pm8xxx_gpio_functions[] = {
+ PMIC_GPIO_FUNC_NORMAL, PMIC_GPIO_FUNC_PAIRED,
+ PMIC_GPIO_FUNC_FUNC1, PMIC_GPIO_FUNC_FUNC2,
+ PMIC_GPIO_FUNC_DTEST1, PMIC_GPIO_FUNC_DTEST2,
+ PMIC_GPIO_FUNC_DTEST3, PMIC_GPIO_FUNC_DTEST4,
+};
+
+static int pm8xxx_gpio_read(struct pm8xxx_gpio *pctrl, int pin, int bank)
+{
+ int reg = SSBI_REG_ADDR_GPIO(pin);
+ unsigned int val = bank << 4;
+ int ret;
+
+ ret = regmap_write(pctrl->regmap, reg, val);
+ if (ret) {
+ dev_err(pctrl->dev,
+ "failed to select bank %d of pin %d\n", bank, pin);
+ return ret;
+ }
+
+ ret = regmap_read(pctrl->regmap, reg, &val);
+ if (ret) {
+ dev_err(pctrl->dev,
+ "failed to read register %d of pin %d\n", bank, pin);
+ return ret;
+ }
+
+ return val;
+}
+
+static int pm8xxx_gpio_write(struct pm8xxx_gpio *pctrl,
+ int pin, int bank, u8 val)
+{
+ int ret;
+
+ val |= PM8XXX_GPIO_WRITE;
+ val |= bank << 4;
+
+ ret = regmap_write(pctrl->regmap, SSBI_REG_ADDR_GPIO(pin), val);
+ if (ret)
+ dev_err(pctrl->dev, "failed to write register\n");
+
+ return ret;
+}
+
+static int pm8xxx_gpio_get_groups_count(struct pinctrl_dev *pctldev)
+{
+ struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ return pctrl->data->ngpio;
+}
+
+static const char *pm8xxx_gpio_get_group_name(struct pinctrl_dev *pctldev,
+ unsigned group)
+{
+ return pm8xxx_gpio_groups[group];
+}
+
+static int pm8xxx_parse_dt_config(struct device *dev, struct device_node *np,
+ unsigned long **configs, unsigned int *nconfigs)
+{
+ struct pm8xxx_pinbindings *par;
+ unsigned long cfg[ARRAY_SIZE(pm8xxx_pinbindings)];
+ unsigned int ncfg = 0;
+ int ret, idx;
+ u32 val;
+
+ if (!np)
+ return -EINVAL;
+
+ for (idx = 0; idx < ARRAY_SIZE(pm8xxx_pinbindings); idx++) {
+ par = &pm8xxx_pinbindings[idx];
+ ret = of_property_read_u32(np, par->property, &val);
+
+ /* property not found */
+ if (ret == -EINVAL)
+ continue;
+
+ /* use default value, when no value is specified */
+ if (ret)
+ val = par->default_value;
+
+ dev_dbg(dev, "found %s with value %u\n", par->property, val);
+ cfg[ncfg] = pinconf_to_config_packed(par->param, val);
+ ncfg++;
+ }
+
+ ret = 0;
+
+ /* no configs found at qchip->npads */
+ if (ncfg == 0) {
+ *configs = NULL;
+ *nconfigs = 0;
+ goto out;
+ }
+
+ /*
+ * Now limit the number of configs to the real number of
+ * found properties.
+ */
+ *configs = kcalloc(ncfg, sizeof(unsigned long), GFP_KERNEL);
+ if (!*configs) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(*configs, cfg, ncfg * sizeof(unsigned long));
+ *nconfigs = ncfg;
+
+out:
+ return ret;
+}
+
+static int pm8xxx_dt_subnode_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np,
+ struct pinctrl_map **map,
+ unsigned *reserv, unsigned *nmaps,
+ enum pinctrl_map_type type)
+{
+ unsigned long *configs = NULL;
+ unsigned num_configs = 0;
+ struct property *prop;
+ const char *group;
+ int ret;
+
+ ret = pm8xxx_parse_dt_config(pctldev->dev, np, &configs, &num_configs);
+ if (ret < 0)
+ return ret;
+
+ if (!num_configs)
+ return 0;
+
+ ret = of_property_count_strings(np, "pins");
+ if (ret < 0)
+ goto exit;
+
+ ret = pinctrl_utils_reserve_map(pctldev, map, reserv,
+ nmaps, ret);
+ if (ret < 0)
+ goto exit;
+
+ of_property_for_each_string(np, "pins", prop, group) {
+ ret = pinctrl_utils_add_map_configs(pctldev, map,
+ reserv, nmaps, group, configs,
+ num_configs, type);
+ if (ret < 0)
+ break;
+ }
+exit:
+ kfree(configs);
+ return ret;
+}
+
+static int pm8xxx_dt_node_to_map(struct pinctrl_dev *pctldev,
+ struct device_node *np_config,
+ struct pinctrl_map **map,
+ unsigned *nmaps)
+{
+ struct device_node *np;
+ enum pinctrl_map_type type;
+ unsigned reserv;
+ int ret;
+
+ ret = 0;
+ *map = NULL;
+ *nmaps = 0;
+ reserv = 0;
+ type = PIN_MAP_TYPE_CONFIGS_GROUP;
+
+ for_each_child_of_node(np_config, np) {
+
+ ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map,
+ &reserv, nmaps, type);
+ if (ret)
+ break;
+
+ ret = pm8xxx_dt_subnode_to_map(pctldev, np, map, &reserv,
+ nmaps, type);
+ if (ret)
+ break;
+ }
+
+ if (ret < 0)
+ pinctrl_utils_dt_free_map(pctldev, *map, *nmaps);
+
+ return ret;
+}
+
+static const struct pinctrl_ops pm8xxx_gpio_pinctrl_ops = {
+ .get_groups_count = pm8xxx_gpio_get_groups_count,
+ .get_group_name = pm8xxx_gpio_get_group_name,
+ .dt_node_to_map = pm8xxx_dt_node_to_map,
+ .dt_free_map = pinctrl_utils_dt_free_map,
+};
+
+static int pm8xxx_get_functions_count(struct pinctrl_dev *pctldev)
+{
+ return ARRAY_SIZE(pm8xxx_gpio_functions);
+}
+
+static const char *pm8xxx_get_function_name(struct pinctrl_dev *pctldev,
+ unsigned function)
+{
+ return pm8xxx_gpio_functions[function];
+}
+
+static int pm8xxx_get_function_groups(struct pinctrl_dev *pctldev,
+ unsigned function,
+ const char * const **groups,
+ unsigned * const num_groups)
+{
+ struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
+
+ *groups = pm8xxx_gpio_groups;
+ *num_groups = pctrl->data->ngpio;
+ return 0;
+}
+
+static int pm8xxx_pinmux_enable(struct pinctrl_dev *pctldev,
+ unsigned function,
+ unsigned group)
+{
+ struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[group];
+ u8 val;
+
+ pin->function = function;
+ val = pin->function << 1;
+
+ pm8xxx_gpio_write(pctrl, group, 4, val);
+
+ return 0;
+}
+
+static const struct pinmux_ops pm8xxx_pinmux_ops = {
+ .get_functions_count = pm8xxx_get_functions_count,
+ .get_function_name = pm8xxx_get_function_name,
+ .get_function_groups = pm8xxx_get_function_groups,
+ .set_mux = pm8xxx_pinmux_enable,
+};
+
+static int pm8xxx_gpio_config_get(struct pinctrl_dev *pctldev,
+ unsigned int offset,
+ unsigned long *config)
+{
+ struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
+ unsigned param = pinconf_to_config_param(*config);
+ unsigned arg;
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ arg = pin->bias == PM8XXX_GPIO_BIAS_NP;
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ arg = pin->bias == PM8XXX_GPIO_BIAS_PD;
+ break;
+ case PM8XXX_PINCONF_PULL_UP:
+ if (pin->bias >= PM8XXX_GPIO_BIAS_PU_30 &&
+ pin->bias <= PM8XXX_GPIO_BIAS_PU_1P5_30)
+ arg = PMIC_GPIO_PULL_UP_30 + pin->bias;
+ else
+ arg = 0;
+ break;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ arg = pin->disable;
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ arg = pin->direction == PM8XXX_GPIO_DIR_IN;
+ break;
+ case PIN_CONFIG_OUTPUT:
+ arg = pin->output_value;
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ arg = pin->power_source;
+ break;
+ case PM8XXX_PINCONF_STRENGTH:
+ arg = pin->output_strength;
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ arg = pin->output_buffer == PM8XXX_GPIO_PUSH_PULL;
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ arg = pin->output_buffer == PM8XXX_GPIO_OPEN_DRAIN;
+ break;
+ default:
+ dev_err(pctrl->dev,
+ "unsupported config parameter: %x\n",
+ param);
+ return -EINVAL;
+ }
+
+ *config = pinconf_to_config_packed(param, arg);
+
+ return 0;
+}
+
+static int pm8xxx_gpio_config_set(struct pinctrl_dev *pctldev,
+ unsigned int offset,
+ unsigned long *configs,
+ unsigned num_configs)
+{
+ struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
+ unsigned param;
+ unsigned arg;
+ unsigned i;
+ u8 banks = 0;
+ u8 val;
+
+ for (i = 0; i < num_configs; i++) {
+ param = pinconf_to_config_param(configs[i]);
+ arg = pinconf_to_config_argument(configs[i]);
+
+ switch (param) {
+ case PIN_CONFIG_BIAS_DISABLE:
+ pin->bias = PM8XXX_GPIO_BIAS_NP;
+ banks |= BIT(2);
+ pin->disable = 0;
+ banks |= BIT(3);
+ break;
+ case PIN_CONFIG_BIAS_PULL_DOWN:
+ pin->bias = PM8XXX_GPIO_BIAS_PD;
+ banks |= BIT(2);
+ pin->disable = 0;
+ banks |= BIT(3);
+ break;
+ case PM8XXX_PINCONF_PULL_UP:
+ if (arg < PMIC_GPIO_PULL_UP_30 ||
+ arg > PMIC_GPIO_PULL_UP_1P5_30) {
+ dev_err(pctrl->dev, "invalid pull-up level\n");
+ return -EINVAL;
+ }
+ pin->bias = arg - PM8XXX_GPIO_BIAS_PU_30;
+ banks |= BIT(2);
+ pin->disable = 0;
+ banks |= BIT(3);
+ break;
+ case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+ pin->disable = 1;
+ banks |= BIT(3);
+ break;
+ case PIN_CONFIG_INPUT_ENABLE:
+ pin->direction = PM8XXX_GPIO_DIR_IN;
+ banks |= BIT(1);
+ break;
+ case PIN_CONFIG_OUTPUT:
+ pin->direction = PM8XXX_GPIO_DIR_OUT;
+ pin->output_value = !!arg;
+ banks |= BIT(1);
+ break;
+ case PIN_CONFIG_POWER_SOURCE:
+ pin->power_source = arg;
+ banks |= BIT(0);
+ break;
+ case PM8XXX_PINCONF_STRENGTH:
+ if (arg > PMIC_GPIO_STRENGTH_LOW) {
+ dev_err(pctrl->dev, "invalid drive strength\n");
+ return -EINVAL;
+ }
+ pin->output_strength = arg;
+ banks |= BIT(3);
+ break;
+ case PIN_CONFIG_DRIVE_PUSH_PULL:
+ pin->output_buffer = PM8XXX_GPIO_PUSH_PULL;
+ banks |= BIT(1);
+ break;
+ case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+ pin->output_buffer = PM8XXX_GPIO_OPEN_DRAIN;
+ banks |= BIT(1);
+ break;
+ default:
+ dev_err(pctrl->dev,
+ "unsupported config parameter: %x\n",
+ param);
+ return -EINVAL;
+ }
+ }
+
+ if (banks & BIT(0))
+ pm8xxx_gpio_write(pctrl, offset, 0, pin->power_source << 1 |
+ PM8XXX_GPIO_MODE_ENABLE);
+
+ if (banks & BIT(1)) {
+ val = pin->direction << 2;
+ val |= pin->output_buffer << 1;
+ val |= pin->output_value;
+ pm8xxx_gpio_write(pctrl, offset, 1, val);
+ }
+
+ if (banks & BIT(2)) {
+ val = pin->bias << 1;
+ pm8xxx_gpio_write(pctrl, offset, 2, val);
+ }
+
+ if (banks & BIT(3)) {
+ val = pin->output_strength << 2;
+ val |= pin->disable;
+ pm8xxx_gpio_write(pctrl, offset, 3, val);
+ }
+
+ if (banks & BIT(4)) {
+ val = pin->function << 1;
+ pm8xxx_gpio_write(pctrl, offset, 4, val);
+ }
+
+ if (banks & BIT(5)) {
+ val = 0;
+ if (pin->non_inverted)
+ val |= BIT(3);
+ pm8xxx_gpio_write(pctrl, offset, 5, val);
+ }
+
+ return 0;
+}
+
+static const struct pinconf_ops pm8xxx_gpio_pinconf_ops = {
+ .pin_config_group_get = pm8xxx_gpio_config_get,
+ .pin_config_group_set = pm8xxx_gpio_config_set,
+};
+
+static struct pinctrl_desc pm8xxx_gpio_desc = {
+ .pctlops = &pm8xxx_gpio_pinctrl_ops,
+ .pmxops = &pm8xxx_pinmux_ops,
+ .confops = &pm8xxx_gpio_pinconf_ops,
+ .owner = THIS_MODULE,
+};
+
+static int pm8xxx_gpio_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
+ u8 val;
+
+ pin->direction = PM8XXX_GPIO_DIR_IN;
+ val = pin->direction << 2;
+
+ pm8xxx_gpio_write(pctrl, offset, 1, val);
+
+ return 0;
+}
+
+static int pm8xxx_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset,
+ int value)
+{
+ struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
+ u8 val;
+
+ pin->direction = PM8XXX_GPIO_DIR_OUT;
+ pin->output_value = !!value;
+
+ val = pin->direction << 2;
+ val |= pin->output_buffer << 1;
+ val |= pin->output_value;
+
+ pm8xxx_gpio_write(pctrl, offset, 1, val);
+
+ return 0;
+}
+
+static int pm8xxx_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
+
+ if (pin->direction == PM8XXX_GPIO_DIR_OUT)
+ return pin->output_value;
+
+ return pm8xxx_read_irq_status(pin->irq);
+}
+
+static void pm8xxx_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
+{
+ struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(gc);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
+ u8 val;
+
+ pin->output_value = !!value;
+
+ val = pin->direction << 2;
+ val |= pin->output_buffer << 1;
+ val |= pin->output_value;
+
+ pm8xxx_gpio_write(pctrl, offset, 1, val);
+}
+
+static int pm8xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset - 1];
+
+ return pin->irq;
+}
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/seq_file.h>
+
+static void pm8xxx_gpio_dbg_show_one(struct seq_file *s,
+ struct pinctrl_dev *pctldev,
+ struct gpio_chip *chip,
+ unsigned offset,
+ unsigned gpio)
+{
+ struct pm8xxx_gpio *pctrl = to_pm8xxx_gpio(chip);
+ struct pm8xxx_gpio_pin *pin = &pctrl->pins[offset];
+
+ static const char * const directions[] = {
+ "off", "out", "in", "both"
+ };
+ static const char * const biases[] = {
+ "pull-up 30uA", "pull-up 1.5uA", "pull-up 31.5uA",
+ "pull-up 1.5uA + 30uA boost", "pull-down 10uA", "no pull"
+ };
+ static const char * const buffer_types[] = {
+ "push-pull", "open-drain"
+ };
+ static const char * const strengths[] = {
+ "no", "high", "medium", "low"
+ };
+
+ seq_printf(s, " gpio%-2d:", offset + 1);
+ if (pin->disable) {
+ seq_puts(s, " ---");
+ } else {
+ seq_printf(s, " %-4s", directions[pin->direction]);
+ seq_printf(s, " %-7s", pm8xxx_gpio_functions[pin->function]);
+ seq_printf(s, " VIN%d", pin->power_source);
+ seq_printf(s, " %-27s", biases[pin->bias]);
+ seq_printf(s, " %-10s", buffer_types[pin->output_buffer]);
+ seq_printf(s, " %-4s", pin->output_value ? "high" : "low");
+ seq_printf(s, " %-7s", strengths[pin->output_strength]);
+ if (!pin->non_inverted)
+ seq_puts(s, " inverted");
+ }
+}
+
+static void pm8xxx_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ unsigned gpio = chip->base;
+ unsigned i;
+
+ for (i = 0; i < chip->ngpio; i++, gpio++) {
+ pm8xxx_gpio_dbg_show_one(s, NULL, chip, i, gpio);
+ seq_puts(s, "\n");
+ }
+}
+
+#else
+#define msm_gpio_dbg_show NULL
+#endif
+
+static struct gpio_chip pm8xxx_gpio_template = {
+ .direction_input = pm8xxx_gpio_direction_input,
+ .direction_output = pm8xxx_gpio_direction_output,
+ .get = pm8xxx_gpio_get,
+ .set = pm8xxx_gpio_set,
+ .to_irq = pm8xxx_gpio_to_irq,
+ .dbg_show = pm8xxx_gpio_dbg_show,
+ .owner = THIS_MODULE,
+};
+
+static int pm8xxx_gpio_populate(struct pm8xxx_gpio *pctrl)
+{
+ struct pm8xxx_gpio_pin *pin;
+ int val;
+ int i;
+
+ for (i = 0; i < pctrl->data->ngpio; i++) {
+ pin = &pctrl->pins[i];
+
+ val = pm8xxx_gpio_read(pctrl, i, 0);
+ if (val < 0)
+ return val;
+
+ pin->power_source = (val >> 1) & 0x7;
+
+ val = pm8xxx_gpio_read(pctrl, i, 1);
+ if (val < 0)
+ return val;
+
+ pin->direction = (val >> 2) & 0x3;
+ pin->output_buffer = !!(val & BIT(1));
+ pin->output_value = val & BIT(0);
+
+ val = pm8xxx_gpio_read(pctrl, i, 2);
+ if (val < 0)
+ return val;
+
+ pin->bias = (val >> 1) & 0x7;
+
+ val = pm8xxx_gpio_read(pctrl, i, 3);
+ if (val < 0)
+ return val;
+
+ pin->output_strength = (val >> 2) & 0x3;
+ pin->disable = val & BIT(0);
+
+ val = pm8xxx_gpio_read(pctrl, i, 4);
+ if (val < 0)
+ return val;
+
+ pin->function = (val >> 1) & 0x7;
+
+ val = pm8xxx_gpio_read(pctrl, i, 5);
+ if (val < 0)
+ return val;
+
+ pin->non_inverted = !!(val & BIT(3));
+ }
+
+ return 0;
+}
+
+static const struct pm8xxx_gpio_data pm8018_gpio_data = {
+ .ngpio = 6,
+};
+
+static const struct pm8xxx_gpio_data pm8038_gpio_data = {
+ .ngpio = 12,
+};
+
+static const struct pm8xxx_gpio_data pm8058_gpio_data = {
+ .ngpio = 40,
+};
+static const struct pm8xxx_gpio_data pm8917_gpio_data = {
+ .ngpio = 38,
+};
+
+static const struct pm8xxx_gpio_data pm8921_gpio_data = {
+ .ngpio = 44,
+};
+
+static const struct of_device_id pm8xxx_gpio_of_match[] = {
+ { .compatible = "qcom,pm8018-gpio", .data = &pm8018_gpio_data },
+ { .compatible = "qcom,pm8038-gpio", .data = &pm8038_gpio_data },
+ { .compatible = "qcom,pm8058-gpio", .data = &pm8058_gpio_data },
+ { .compatible = "qcom,pm8917-gpio", .data = &pm8917_gpio_data },
+ { .compatible = "qcom,pm8921-gpio", .data = &pm8921_gpio_data },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pm8xxx_gpio_of_match);
+
+static int pm8xxx_gpio_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct pm8xxx_gpio *pctrl;
+ int ret;
+ int i;
+
+ match = of_match_node(pm8xxx_gpio_of_match, pdev->dev.of_node);
+ if (!match)
+ return -ENXIO;
+
+ pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL);
+ if (!pctrl)
+ return -ENOMEM;
+
+ pctrl->dev = &pdev->dev;
+ pctrl->data = match->data;
+
+ BUG_ON(pctrl->data->ngpio > PM8XXX_MAX_GPIOS);
+
+ pctrl->chip = pm8xxx_gpio_template;
+ pctrl->chip.base = -1;
+ pctrl->chip.dev = &pdev->dev;
+ pctrl->chip.of_node = pdev->dev.of_node;
+ pctrl->chip.label = dev_name(pctrl->dev);
+ pctrl->chip.ngpio = pctrl->data->ngpio;
+
+ pctrl->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!pctrl->regmap) {
+ dev_err(&pdev->dev, "parent regmap unavailable\n");
+ return -ENXIO;
+ }
+
+ for (i = 0; i < pctrl->data->ngpio; i++) {
+ ret = platform_get_irq(pdev, i);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "missing interrupts for pin %d\n", i);
+ return ret;
+ }
+
+ pctrl->pins[i].irq = ret;
+ }
+
+ ret = pm8xxx_gpio_populate(pctrl);
+ if (ret)
+ return ret;
+
+ pm8xxx_gpio_desc.name = dev_name(&pdev->dev);
+ pctrl->pctrl = pinctrl_register(&pm8xxx_gpio_desc, &pdev->dev, pctrl);
+ if (!pctrl->pctrl) {
+ dev_err(&pdev->dev, "couldn't register pm8xxx gpio driver\n");
+ return -ENODEV;
+ }
+
+ ret = gpiochip_add(&pctrl->chip);
+ if (ret) {
+ dev_err(&pdev->dev, "failed register gpiochip\n");
+ goto unregister_pinctrl;
+ }
+
+ ret = gpiochip_add_pin_range(&pctrl->chip,
+ dev_name(pctrl->dev),
+ 1, 0, pctrl->data->ngpio);
+ if (ret) {
+ dev_err(pctrl->dev, "failed to add pin range\n");
+ goto unregister_gpiochip;
+ }
+
+ platform_set_drvdata(pdev, pctrl);
+
+ dev_dbg(&pdev->dev, "Qualcomm pm8xxx gpio driver probed\n");
+
+ return 0;
+
+unregister_pinctrl:
+ pinctrl_unregister(pctrl->pctrl);
+
+unregister_gpiochip:
+ gpiochip_remove(&pctrl->chip);
+
+ return ret;
+}
+
+static int pm8xxx_gpio_remove(struct platform_device *pdev)
+{
+ struct pm8xxx_gpio *pctrl = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&pctrl->chip);
+
+ pinctrl_unregister(pctrl->pctrl);
+
+ return 0;
+}
+
+static struct platform_driver pm8xxx_gpio_driver = {
+ .driver = {
+ .name = "ssbi-pmic-gpio",
+ .owner = THIS_MODULE,
+ .of_match_table = pm8xxx_gpio_of_match,
+ },
+ .probe = pm8xxx_gpio_probe,
+ .remove = pm8xxx_gpio_remove,
+};
+
+static int pm8xxx_gpio_init(void)
+{
+ return platform_driver_register(&pm8xxx_gpio_driver);
+}
+subsys_initcall(pm8xxx_gpio_init);
+
+MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
+MODULE_DESCRIPTION("Qualcomm SSBI PMIC GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 7bd2c94f54a4..cd249c4ff92d 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -9,3 +9,13 @@ config QCOM_GSBI
functions for connecting the underlying serial UART, SPI, and I2C
devices to the output pins.
+config QCOM_SCM
+ bool
+
+config QCOM_PM
+ bool "Qualcomm Power Management"
+ depends on PM && ARCH_QCOM
+ help
+ QCOM Platform specific power driver to manage cores and L2 low power
+ modes. It interface with various system drivers to put the cores in
+ low power modes.
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 438901257ac1..acdd6fa7d8ef 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1 +1,4 @@
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
+obj-$(CONFIG_QCOM_PM) += spm.o spm-devices.o msm-pm.o
+CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1)
+obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o
diff --git a/drivers/soc/qcom/msm-pm.c b/drivers/soc/qcom/msm-pm.c
new file mode 100644
index 000000000000..8926c71b1e64
--- /dev/null
+++ b/drivers/soc/qcom/msm-pm.c
@@ -0,0 +1,106 @@
+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+
+#include <asm/proc-fns.h>
+#include <asm/suspend.h>
+
+#include <soc/qcom/pm.h>
+#include <soc/qcom/scm.h>
+#include <soc/qcom/scm-boot.h>
+#include <soc/qcom/spm.h>
+
+#define SCM_CMD_TERMINATE_PC (0x2)
+#define SCM_FLUSH_FLAG_MASK (0x3)
+
+static int set_up_boot_address(void *entry, int cpu)
+{
+ static int flags[NR_CPUS] = {
+ SCM_FLAG_WARMBOOT_CPU0,
+ SCM_FLAG_WARMBOOT_CPU1,
+ SCM_FLAG_WARMBOOT_CPU2,
+ SCM_FLAG_WARMBOOT_CPU3,
+ };
+ static DEFINE_PER_CPU(void *, last_known_entry);
+
+ if (entry == per_cpu(last_known_entry, cpu))
+ return 0;
+
+ per_cpu(last_known_entry, cpu) = entry;
+ return scm_set_boot_addr(virt_to_phys(entry), flags[cpu]);
+}
+
+static int msm_pm_collapse(unsigned long int unused)
+{
+ int ret;
+ enum msm_pm_l2_scm_flag flag;
+
+ ret = set_up_boot_address(cpu_resume, raw_smp_processor_id());
+ if (ret) {
+ pr_err("Failed to set warm boot address for cpu %d\n",
+ raw_smp_processor_id());
+ return ret;
+ }
+
+ flag = MSM_SCM_L2_ON & SCM_FLUSH_FLAG_MASK;
+ scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag);
+
+ return 0;
+}
+
+/**
+ * msm_cpu_pm_enter_sleep(): Enter a low power mode on current cpu
+ *
+ * @mode - sleep mode to enter
+ *
+ * The code should be called with interrupts disabled and on the core on
+ * which the low power mode is to be executed.
+ *
+ */
+static int msm_cpu_pm_enter_sleep(enum msm_pm_sleep_mode mode)
+{
+ int ret;
+
+ switch (mode) {
+ case MSM_PM_SLEEP_MODE_SPC:
+ msm_spm_set_low_power_mode(MSM_SPM_MODE_POWER_COLLAPSE);
+ ret = cpu_suspend(0, msm_pm_collapse);
+ break;
+ default:
+ case MSM_PM_SLEEP_MODE_WFI:
+ msm_spm_set_low_power_mode(MSM_SPM_MODE_CLOCK_GATING);
+ ret = cpu_do_idle();
+ break;
+ }
+
+ local_irq_enable();
+
+ return ret;
+}
+
+static struct platform_device qcom_cpuidle_device = {
+ .name = "qcom_cpuidle",
+ .id = -1,
+ .dev.platform_data = msm_cpu_pm_enter_sleep,
+};
+
+static int __init msm_pm_device_init(void)
+{
+ platform_device_register(&qcom_cpuidle_device);
+
+ return 0;
+}
+device_initcall(msm_pm_device_init);
diff --git a/arch/arm/mach-qcom/scm-boot.c b/drivers/soc/qcom/scm-boot.c
index 45cee3e469a5..60ff7b482141 100644
--- a/arch/arm/mach-qcom/scm-boot.c
+++ b/drivers/soc/qcom/scm-boot.c
@@ -18,8 +18,8 @@
#include <linux/module.h>
#include <linux/slab.h>
-#include "scm.h"
-#include "scm-boot.h"
+#include <soc/qcom/scm.h>
+#include <soc/qcom/scm-boot.h>
/*
* Set the cold/warm boot address for one of the CPU cores.
diff --git a/arch/arm/mach-qcom/scm.c b/drivers/soc/qcom/scm.c
index c536fd6bf827..2e98d80e2387 100644
--- a/arch/arm/mach-qcom/scm.c
+++ b/drivers/soc/qcom/scm.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2010-2014, 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
@@ -22,12 +22,11 @@
#include <linux/errno.h>
#include <linux/err.h>
-#include <asm/cacheflush.h>
+#include <soc/qcom/scm.h>
-#include "scm.h"
+#include <asm/outercache.h>
+#include <asm/cacheflush.h>
-/* Cache line size for msm8x60 */
-#define CACHELINESIZE 32
#define SCM_ENOMEM -5
#define SCM_EOPNOTSUPP -4
@@ -154,6 +153,7 @@ static inline void *scm_get_response_buffer(const struct scm_response *rsp)
static int scm_remap_error(int err)
{
+ pr_err("scm_call failed with error code %d\n", err);
switch (err) {
case SCM_ERROR:
return -EIO;
@@ -198,11 +198,12 @@ static int __scm_call(const struct scm_command *cmd)
u32 cmd_addr = virt_to_phys(cmd);
/*
- * Flush the entire cache here so callers don't have to remember
- * to flush the cache when passing physical addresses to the secure
- * side in the buffer.
+ * Flush the command buffer so that the secure world sees
+ * the correct data.
*/
- flush_cache_all();
+ __cpuc_flush_dcache_area((void *)cmd, cmd->len);
+ outer_flush_range(cmd_addr, cmd_addr + cmd->len);
+
ret = smc(cmd_addr);
if (ret < 0)
ret = scm_remap_error(ret);
@@ -210,6 +211,25 @@ static int __scm_call(const struct scm_command *cmd)
return ret;
}
+static void scm_inv_range(unsigned long start, unsigned long end)
+{
+ u32 cacheline_size, ctr;
+
+ asm volatile("mrc p15, 0, %0, c0, c0, 1" : "=r" (ctr));
+ cacheline_size = 4 << ((ctr >> 16) & 0xf);
+
+ start = round_down(start, cacheline_size);
+ end = round_up(end, cacheline_size);
+ outer_inv_range(start, end);
+ while (start < end) {
+ asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start)
+ : "memory");
+ start += cacheline_size;
+ }
+ dsb();
+ isb();
+}
+
/**
* scm_call() - Send an SCM command
* @svc_id: service identifier
@@ -220,6 +240,13 @@ static int __scm_call(const struct scm_command *cmd)
* @resp_len: length of the response buffer
*
* Sends a command to the SCM and waits for the command to finish processing.
+ *
+ * A note on cache maintenance:
+ * Note that any buffers that are expected to be accessed by the secure world
+ * must be flushed before invoking scm_call and invalidated in the cache
+ * immediately after scm_call returns. Cache maintenance on the command and
+ * response buffers is taken care of by scm_call; however, callers are
+ * responsible for any other cached buffers passed over to the secure world.
*/
int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len,
void *resp_buf, size_t resp_len)
@@ -227,6 +254,7 @@ int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len,
int ret;
struct scm_command *cmd;
struct scm_response *rsp;
+ unsigned long start, end;
cmd = alloc_scm_command(cmd_len, resp_len);
if (!cmd)
@@ -243,17 +271,15 @@ int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len,
goto out;
rsp = scm_command_to_response(cmd);
+ start = (unsigned long)rsp;
+
do {
- u32 start = (u32)rsp;
- u32 end = (u32)scm_get_response_buffer(rsp) + resp_len;
- start &= ~(CACHELINESIZE - 1);
- while (start < end) {
- asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start)
- : "memory");
- start += CACHELINESIZE;
- }
+ scm_inv_range(start, start + sizeof(*rsp));
} while (!rsp->is_complete);
+ end = (unsigned long)scm_get_response_buffer(rsp) + resp_len;
+ scm_inv_range(start, end);
+
if (resp_buf)
memcpy(resp_buf, scm_get_response_buffer(rsp), resp_len);
out:
@@ -262,6 +288,79 @@ out:
}
EXPORT_SYMBOL(scm_call);
+#define SCM_CLASS_REGISTER (0x2 << 8)
+#define SCM_MASK_IRQS BIT(5)
+#define SCM_ATOMIC(svc, cmd, n) (((((svc) << 10)|((cmd) & 0x3ff)) << 12) | \
+ SCM_CLASS_REGISTER | \
+ SCM_MASK_IRQS | \
+ (n & 0xf))
+
+/**
+ * scm_call_atomic1() - Send an atomic SCM command with one argument
+ * @svc_id: service identifier
+ * @cmd_id: command identifier
+ * @arg1: first argument
+ *
+ * This shall only be used with commands that are guaranteed to be
+ * uninterruptable, atomic and SMP safe.
+ */
+s32 scm_call_atomic1(u32 svc, u32 cmd, u32 arg1)
+{
+ int context_id;
+ register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 1);
+ register u32 r1 asm("r1") = (u32)&context_id;
+ register u32 r2 asm("r2") = arg1;
+
+ asm volatile(
+ __asmeq("%0", "r0")
+ __asmeq("%1", "r0")
+ __asmeq("%2", "r1")
+ __asmeq("%3", "r2")
+#ifdef REQUIRES_SEC
+ ".arch_extension sec\n"
+#endif
+ "smc #0 @ switch to secure world\n"
+ : "=r" (r0)
+ : "r" (r0), "r" (r1), "r" (r2)
+ : "r3");
+ return r0;
+}
+EXPORT_SYMBOL(scm_call_atomic1);
+
+/**
+ * scm_call_atomic2() - Send an atomic SCM command with two arguments
+ * @svc_id: service identifier
+ * @cmd_id: command identifier
+ * @arg1: first argument
+ * @arg2: second argument
+ *
+ * This shall only be used with commands that are guaranteed to be
+ * uninterruptable, atomic and SMP safe.
+ */
+s32 scm_call_atomic2(u32 svc, u32 cmd, u32 arg1, u32 arg2)
+{
+ int context_id;
+ register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 2);
+ register u32 r1 asm("r1") = (u32)&context_id;
+ register u32 r2 asm("r2") = arg1;
+ register u32 r3 asm("r3") = arg2;
+
+ asm volatile(
+ __asmeq("%0", "r0")
+ __asmeq("%1", "r0")
+ __asmeq("%2", "r1")
+ __asmeq("%3", "r2")
+ __asmeq("%4", "r3")
+#ifdef REQUIRES_SEC
+ ".arch_extension sec\n"
+#endif
+ "smc #0 @ switch to secure world\n"
+ : "=r" (r0)
+ : "r" (r0), "r" (r1), "r" (r2), "r" (r3));
+ return r0;
+}
+EXPORT_SYMBOL(scm_call_atomic2);
+
u32 scm_get_version(void)
{
int context_id;
@@ -297,3 +396,32 @@ u32 scm_get_version(void)
return version;
}
EXPORT_SYMBOL(scm_get_version);
+
+#define IS_CALL_AVAIL_CMD 1
+int scm_is_call_available(u32 svc_id, u32 cmd_id)
+{
+ int ret;
+ u32 svc_cmd = (svc_id << 10) | cmd_id;
+ u32 ret_val = 0;
+
+ ret = scm_call(SCM_SVC_INFO, IS_CALL_AVAIL_CMD, &svc_cmd,
+ sizeof(svc_cmd), &ret_val, sizeof(ret_val));
+ if (ret)
+ return ret;
+
+ return ret_val;
+}
+EXPORT_SYMBOL(scm_is_call_available);
+
+#define GET_FEAT_VERSION_CMD 3
+int scm_get_feat_version(u32 feat)
+{
+ if (scm_is_call_available(SCM_SVC_INFO, GET_FEAT_VERSION_CMD)) {
+ u32 version;
+ if (!scm_call(SCM_SVC_INFO, GET_FEAT_VERSION_CMD, &feat,
+ sizeof(feat), &version, sizeof(version)))
+ return version;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(scm_get_feat_version);
diff --git a/drivers/soc/qcom/spm-devices.c b/drivers/soc/qcom/spm-devices.c
new file mode 100644
index 000000000000..776c0af862e1
--- /dev/null
+++ b/drivers/soc/qcom/spm-devices.c
@@ -0,0 +1,198 @@
+/* Copyright (c) 2011-2014, 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/kernel.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+
+#include <soc/qcom/spm.h>
+
+#include "spm-drv.h"
+
+/**
+ * All related information for an SPM device
+ * Helps manage the collective.
+ */
+struct msm_spm_device {
+ bool initialized;
+ struct msm_spm_driver_data drv;
+};
+
+static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_spm_device, msm_cpu_spm_device);
+
+/**
+ * msm_spm_set_low_power_mode() - Configure SPM start address for low power mode
+ * @mode: SPM LPM mode to enter
+ */
+int msm_spm_set_low_power_mode(u32 mode)
+{
+ struct msm_spm_device *dev = &__get_cpu_var(msm_cpu_spm_device);
+ int ret = -EINVAL;
+
+ if (!dev->initialized)
+ return -ENXIO;
+
+ if (mode == MSM_SPM_MODE_DISABLED)
+ ret = msm_spm_drv_set_spm_enable(&dev->drv, false);
+ else if (!msm_spm_drv_set_spm_enable(&dev->drv, true))
+ ret = msm_spm_drv_set_low_power_mode(&dev->drv, mode);
+
+ return ret;
+}
+EXPORT_SYMBOL(msm_spm_set_low_power_mode);
+
+static int get_cpu_id(struct device_node *node)
+{
+ struct device_node *cpu_node;
+ u32 cpu;
+ int ret = -EINVAL;
+ char *key = "qcom,cpu";
+
+ cpu_node = of_parse_phandle(node, key, 0);
+ if (cpu_node) {
+ for_each_possible_cpu(cpu) {
+ if (of_get_cpu_node(cpu, NULL) == cpu_node)
+ return cpu;
+ }
+ }
+ return ret;
+}
+
+static struct msm_spm_device *msm_spm_get_device(struct platform_device *pdev)
+{
+ struct msm_spm_device *dev = NULL;
+ int cpu = get_cpu_id(pdev->dev.of_node);
+
+ if ((cpu >= 0) && cpu < num_possible_cpus())
+ dev = &per_cpu(msm_cpu_spm_device, cpu);
+
+ return dev;
+}
+
+static int msm_spm_dev_probe(struct platform_device *pdev)
+{
+ int ret;
+ int i;
+ struct device_node *node = pdev->dev.of_node;
+ char *key;
+ u32 val;
+ struct msm_spm_mode modes[MSM_SPM_MODE_NR];
+ struct msm_spm_device *spm_dev;
+ struct resource *res;
+ u32 mode_count = 0;
+
+ struct spm_of {
+ char *key;
+ u32 id;
+ };
+
+ /* SPM Configuration registers */
+ struct spm_of spm_of_data[] = {
+ {"qcom,saw2-clk-div", MSM_SPM_REG_SAW2_CFG},
+ {"qcom,saw2-enable", MSM_SPM_REG_SAW2_SPM_CTL},
+ {"qcom,saw2-delays", MSM_SPM_REG_SAW2_SPM_DLY},
+ };
+
+ /* SPM sleep sequences */
+ struct spm_of mode_of_data[] = {
+ {"qcom,saw2-spm-cmd-wfi", MSM_SPM_MODE_CLOCK_GATING},
+ {"qcom,saw2-spm-cmd-spc", MSM_SPM_MODE_POWER_COLLAPSE},
+ {"qcom,saw2-spm-cmd-ret", MSM_SPM_MODE_RETENTION},
+ };
+
+ /* Get the right SPM device */
+ spm_dev = msm_spm_get_device(pdev);
+ if (IS_ERR_OR_NULL(spm_dev))
+ return -EINVAL;
+
+ /* Get the SAW start address */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -EINVAL;
+ goto fail;
+ }
+ spm_dev->drv.reg_base_addr = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!spm_dev->drv.reg_base_addr) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ /* Read the SPM configuration register values */
+ for (i = 0; i < ARRAY_SIZE(spm_of_data); i++) {
+ ret = of_property_read_u32(node, spm_of_data[i].key, &val);
+ if (ret)
+ continue;
+ spm_dev->drv.reg_shadow[spm_of_data[i].id] = val;
+ }
+
+ /* Read the byte arrays for the SPM sleep sequences */
+ for (i = 0; i < ARRAY_SIZE(mode_of_data); i++) {
+ modes[mode_count].start_addr = 0;
+ key = mode_of_data[i].key;
+ modes[mode_count].cmd =
+ (u8 *)of_get_property(node, key, &val);
+ if (!modes[mode_count].cmd)
+ continue;
+ modes[mode_count].mode = mode_of_data[i].id;
+ mode_count++;
+ }
+
+ spm_dev->drv.modes = devm_kcalloc(&pdev->dev, mode_count,
+ sizeof(modes[0]), GFP_KERNEL);
+ if (!spm_dev->drv.modes)
+ return -ENOMEM;
+ spm_dev->drv.num_modes = mode_count;
+ memcpy(spm_dev->drv.modes, &modes[0], sizeof(modes[0]) * mode_count);
+
+ /* Initialize the hardware */
+ ret = msm_spm_drv_init(&spm_dev->drv);
+ if (ret) {
+ kfree(spm_dev->drv.modes);
+ return ret;
+ }
+
+ spm_dev->initialized = true;
+ return ret;
+
+fail:
+ dev_err(&pdev->dev, "SPM device probe failed: %d\n", ret);
+ return ret;
+}
+
+static struct of_device_id msm_spm_match_table[] = {
+ {.compatible = "qcom,spm-v2.1"},
+ {},
+};
+
+static struct platform_driver msm_spm_device_driver = {
+ .probe = msm_spm_dev_probe,
+ .driver = {
+ .name = "spm-v2",
+ .owner = THIS_MODULE,
+ .of_match_table = msm_spm_match_table,
+ },
+};
+
+static int __init msm_spm_device_init(void)
+{
+ return platform_driver_register(&msm_spm_device_driver);
+}
+device_initcall(msm_spm_device_init);
diff --git a/drivers/soc/qcom/spm-drv.h b/drivers/soc/qcom/spm-drv.h
new file mode 100644
index 000000000000..e91df4407162
--- /dev/null
+++ b/drivers/soc/qcom/spm-drv.h
@@ -0,0 +1,69 @@
+/* Copyright (c) 2011-2014, 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 __QCOM_SPM_DRIVER_H
+#define __QCOM_SPM_DRIVER_H
+
+enum {
+ MSM_SPM_REG_SAW2_CFG,
+ MSM_SPM_REG_SAW2_AVS_CTL,
+ MSM_SPM_REG_SAW2_AVS_HYSTERESIS,
+ MSM_SPM_REG_SAW2_SPM_CTL,
+ MSM_SPM_REG_SAW2_PMIC_DLY,
+ MSM_SPM_REG_SAW2_AVS_LIMIT,
+ MSM_SPM_REG_SAW2_AVS_DLY,
+ MSM_SPM_REG_SAW2_SPM_DLY,
+ MSM_SPM_REG_SAW2_PMIC_DATA_0,
+ MSM_SPM_REG_SAW2_PMIC_DATA_1,
+ MSM_SPM_REG_SAW2_PMIC_DATA_2,
+ MSM_SPM_REG_SAW2_PMIC_DATA_3,
+ MSM_SPM_REG_SAW2_PMIC_DATA_4,
+ MSM_SPM_REG_SAW2_PMIC_DATA_5,
+ MSM_SPM_REG_SAW2_PMIC_DATA_6,
+ MSM_SPM_REG_SAW2_PMIC_DATA_7,
+ MSM_SPM_REG_SAW2_RST,
+
+ MSM_SPM_REG_NR_INITIALIZE = MSM_SPM_REG_SAW2_RST,
+
+ MSM_SPM_REG_SAW2_ID,
+ MSM_SPM_REG_SAW2_SECURE,
+ MSM_SPM_REG_SAW2_STS0,
+ MSM_SPM_REG_SAW2_STS1,
+ MSM_SPM_REG_SAW2_STS2,
+ MSM_SPM_REG_SAW2_VCTL,
+ MSM_SPM_REG_SAW2_SEQ_ENTRY,
+ MSM_SPM_REG_SAW2_SPM_STS,
+ MSM_SPM_REG_SAW2_AVS_STS,
+ MSM_SPM_REG_SAW2_PMIC_STS,
+ MSM_SPM_REG_SAW2_VERSION,
+
+ MSM_SPM_REG_NR,
+};
+
+struct msm_spm_mode {
+ u32 mode;
+ u8 *cmd;
+ u32 start_addr;
+};
+
+struct msm_spm_driver_data {
+ void __iomem *reg_base_addr;
+ u32 reg_shadow[MSM_SPM_REG_NR];
+ u32 *reg_offsets;
+ struct msm_spm_mode *modes;
+ u32 num_modes;
+};
+
+int msm_spm_drv_init(struct msm_spm_driver_data *dev);
+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *dev, u32 addr);
+int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *dev, bool enable);
+
+#endif /* __QCOM_SPM_DRIVER_H */
diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
new file mode 100644
index 000000000000..81e578c668fe
--- /dev/null
+++ b/drivers/soc/qcom/spm.c
@@ -0,0 +1,192 @@
+/* Copyright (c) 2011-2014, 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/kernel.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include "spm-drv.h"
+
+#define NUM_SEQ_ENTRY 32
+#define SPM_CTL_ENABLE BIT(0)
+
+static u32 msm_spm_reg_offsets_saw2_v2_1[MSM_SPM_REG_NR] = {
+ [MSM_SPM_REG_SAW2_SECURE] = 0x00,
+ [MSM_SPM_REG_SAW2_ID] = 0x04,
+ [MSM_SPM_REG_SAW2_CFG] = 0x08,
+ [MSM_SPM_REG_SAW2_SPM_STS] = 0x0C,
+ [MSM_SPM_REG_SAW2_AVS_STS] = 0x10,
+ [MSM_SPM_REG_SAW2_PMIC_STS] = 0x14,
+ [MSM_SPM_REG_SAW2_RST] = 0x18,
+ [MSM_SPM_REG_SAW2_VCTL] = 0x1C,
+ [MSM_SPM_REG_SAW2_AVS_CTL] = 0x20,
+ [MSM_SPM_REG_SAW2_AVS_LIMIT] = 0x24,
+ [MSM_SPM_REG_SAW2_AVS_DLY] = 0x28,
+ [MSM_SPM_REG_SAW2_AVS_HYSTERESIS] = 0x2C,
+ [MSM_SPM_REG_SAW2_SPM_CTL] = 0x30,
+ [MSM_SPM_REG_SAW2_SPM_DLY] = 0x34,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_0] = 0x40,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_1] = 0x44,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_2] = 0x48,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_3] = 0x4C,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_4] = 0x50,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_5] = 0x54,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_6] = 0x58,
+ [MSM_SPM_REG_SAW2_PMIC_DATA_7] = 0x5C,
+ [MSM_SPM_REG_SAW2_SEQ_ENTRY] = 0x80,
+ [MSM_SPM_REG_SAW2_VERSION] = 0xFD0,
+};
+
+static void flush_shadow(struct msm_spm_driver_data *drv, u32 reg_index)
+{
+ writel_relaxed(drv->reg_shadow[reg_index],
+ drv->reg_base_addr + drv->reg_offsets[reg_index]);
+}
+
+static void load_shadow(struct msm_spm_driver_data *drv, u32 reg_index)
+{
+ drv->reg_shadow[reg_index] = readl_relaxed(drv->reg_base_addr +
+ drv->reg_offsets[reg_index]);
+}
+
+static inline void set_start_addr(struct msm_spm_driver_data *drv, u32 addr)
+{
+ /* Update bits 10:4 in the SPM CTL register */
+ addr &= 0x7F;
+ addr <<= 4;
+ drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= 0xFFFFF80F;
+ drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= addr;
+}
+
+int msm_spm_drv_set_low_power_mode(struct msm_spm_driver_data *drv, u32 mode)
+{
+ int i;
+ u32 start_addr = 0;
+
+ for (i = 0; i < drv->num_modes; i++) {
+ if (drv->modes[i].mode == mode) {
+ start_addr = drv->modes[i].start_addr;
+ break;
+ }
+ }
+
+ if (i == drv->num_modes)
+ return -EINVAL;
+
+ set_start_addr(drv, start_addr);
+ flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL);
+ /* Barrier to ensure we have written the start address */
+ wmb();
+
+ /* Update our shadow with the status changes, if any */
+ load_shadow(drv, MSM_SPM_REG_SAW2_SPM_STS);
+
+ return 0;
+}
+
+int msm_spm_drv_set_spm_enable(struct msm_spm_driver_data *drv, bool enable)
+{
+ u32 value = enable ? 0x01 : 0x00;
+
+ /* Update SPM_CTL to enable/disable the SPM */
+ if ((drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] & SPM_CTL_ENABLE)
+ != value) {
+ /* Clear the existing value and update */
+ drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] &= ~0x1;
+ drv->reg_shadow[MSM_SPM_REG_SAW2_SPM_CTL] |= value;
+ flush_shadow(drv, MSM_SPM_REG_SAW2_SPM_CTL);
+ /* Ensure we have enabled/disabled before returning */
+ wmb();
+ }
+
+ return 0;
+}
+
+static void flush_seq_data(struct msm_spm_driver_data *drv, u32 *reg_seq_entry)
+{
+ int i;
+
+ /* Write the 32 byte array into the SPM registers */
+ for (i = 0; i < NUM_SEQ_ENTRY; i++) {
+ writel_relaxed(reg_seq_entry[i],
+ drv->reg_base_addr
+ + drv->reg_offsets[MSM_SPM_REG_SAW2_SEQ_ENTRY]
+ + 4 * i);
+ }
+ /* Ensure that the changes are written */
+ wmb();
+}
+
+static void write_seq_data(struct msm_spm_driver_data *drv,
+ u32 *reg_seq_entry, u8 *cmd, u32 *offset)
+{
+ u32 cmd_w;
+ u32 offset_w = *offset / 4;
+ u8 last_cmd;
+
+ while (1) {
+ int i;
+
+ cmd_w = 0;
+ last_cmd = 0;
+ cmd_w = reg_seq_entry[offset_w];
+
+ for (i = (*offset % 4); i < 4; i++) {
+ last_cmd = *(cmd++);
+ cmd_w |= last_cmd << (i * 8);
+ (*offset)++;
+ if (last_cmd == 0x0f)
+ break;
+ }
+
+ reg_seq_entry[offset_w++] = cmd_w;
+ if (last_cmd == 0x0f)
+ break;
+ }
+
+}
+
+int msm_spm_drv_init(struct msm_spm_driver_data *drv)
+{
+ int i;
+ int offset = 0;
+ u32 sequences[NUM_SEQ_ENTRY/4] = {0};
+
+ drv->reg_offsets = msm_spm_reg_offsets_saw2_v2_1;
+
+ /**
+ * Compose the uint32 array based on the individual bytes of the SPM
+ * sequence for each low power mode that we read from the DT.
+ * The sequences are appended if there is space available in the
+ * u32 after the end of the previous sequence.
+ */
+ for (i = 0; i < drv->num_modes; i++) {
+ drv->modes[i].start_addr = offset;
+ write_seq_data(drv, &sequences[0], drv->modes[i].cmd, &offset);
+ }
+
+ /* Flush the integer array */
+ flush_seq_data(drv, &sequences[0]);
+
+ /**
+ * Initialize the hardware with the control registers that
+ * we have read.
+ */
+ for (i = 0; i < MSM_SPM_REG_SAW2_PMIC_DATA_0; i++)
+ flush_shadow(drv, i);
+
+ return 0;
+}
diff --git a/fixup.S b/fixup.S
new file mode 100644
index 000000000000..42d27fc0c949
--- /dev/null
+++ b/fixup.S
@@ -0,0 +1,84 @@
+/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/* Fixup the atags for dragonboard (and possibly other targets). */
+/*
+ * The bootloader on some targets passes an ATAG in that sets the
+ * first memory region to be 2MB after the actual start of memory.
+ * With newer upstream kernels, the PHYS_OFFSET must be a multiple of
+ * a large boundary (currently 128MB). Without devicetree, we work
+ * around this with an early init hook, and a fixup that adds a
+ * reservation. These hooks don't run in time to fix it in Device
+ * tree.
+ *
+ * The following code can be prepended to the zImage. It adjusts the
+ * memory atag back down to the actual start of memory. The
+ * assumption is that the device tree will describe the necessary
+ * memory reservation. The zImage is relocatable, so it is easy to
+ * prepend this code.
+ */
+
+ /* Figure out what the broken mem tag would be */
+ mov r8, pc
+ and r8, r8, #0xf8000000
+ add r8, r8, #0x00200000
+
+ /* R2 is where the atags are passed. r5 on are scratch. */
+ mov r5, r2
+ ldr r7, .tag_mem
+
+.next:
+ /* Load the tag, and check. */
+ ldr r6, [r5, #4]
+ cmp r6, #0
+ beq .done
+
+ /* Is the a 'mem' tag. */
+ cmp r6, r7
+ bne .not_mem
+
+ /* Is this memory base what we want? */
+ ldr r6, [r5, #12]
+ cmp r6, r8
+
+ subeq r6, r6, #0x200000
+ streq r6, [r5, #12]
+ ldreq r6, [r5, #8]
+ addeq r6, r6, #0x200000
+ streq r6, [r5, #8]
+
+.not_mem:
+ /* Move r5 to the next tag. */
+ ldr r6, [r5, #0]
+ add r5, r5, r6, asl #2
+ b .next
+
+.tag_mem:
+ .word 0x54410002
+
+.done:
diff --git a/fixup.bin b/fixup.bin
new file mode 100644
index 000000000000..67f2a17eaaa8
--- /dev/null
+++ b/fixup.bin
Binary files differ
diff --git a/fixup.txt b/fixup.txt
new file mode 100644
index 000000000000..07bcb3c52a0d
--- /dev/null
+++ b/fixup.txt
@@ -0,0 +1,17 @@
+
+Fixup loader to boot mainline on Qualcomm platforms that needs adjustment of
+ATAG MEM.
+
+Found at [1], built by issuing:
+
+ arm-eabi-as -o fixup.o fixup.S
+ arm-eabi-objcopy -O binary fixup.o fixup.bin
+
+
+Use by concatenating together with zImage:
+
+ cat fixup.bin arch/arm/boot/zImage > zImage
+
+
+[1] https://www.codeaurora.org/cgit/quic/kernel/skales/tree/atag-fix/fixup.S
+
diff --git a/include/dt-bindings/clock/qcom,gcc-msm8960.h b/include/dt-bindings/clock/qcom,gcc-msm8960.h
index 7d20eedfee98..e02742fc81cc 100644
--- a/include/dt-bindings/clock/qcom,gcc-msm8960.h
+++ b/include/dt-bindings/clock/qcom,gcc-msm8960.h
@@ -319,5 +319,7 @@
#define CE3_SRC 303
#define CE3_CORE_CLK 304
#define CE3_H_CLK 305
+#define PLL16 306
+#define PLL17 307
#endif
diff --git a/include/dt-bindings/mfd/qcom-rpm.h b/include/dt-bindings/mfd/qcom-rpm.h
new file mode 100644
index 000000000000..388a6f3d6165
--- /dev/null
+++ b/include/dt-bindings/mfd/qcom-rpm.h
@@ -0,0 +1,154 @@
+/*
+ * This header provides constants for the Qualcomm RPM bindings.
+ */
+
+#ifndef _DT_BINDINGS_MFD_QCOM_RPM_H
+#define _DT_BINDINGS_MFD_QCOM_RPM_H
+
+/*
+ * Constants use to identify individual resources in the RPM.
+ */
+#define QCOM_RPM_APPS_FABRIC_ARB 1
+#define QCOM_RPM_APPS_FABRIC_CLK 2
+#define QCOM_RPM_APPS_FABRIC_HALT 3
+#define QCOM_RPM_APPS_FABRIC_IOCTL 4
+#define QCOM_RPM_APPS_FABRIC_MODE 5
+#define QCOM_RPM_APPS_L2_CACHE_CTL 6
+#define QCOM_RPM_CFPB_CLK 7
+#define QCOM_RPM_CXO_BUFFERS 8
+#define QCOM_RPM_CXO_CLK 9
+#define QCOM_RPM_DAYTONA_FABRIC_CLK 10
+#define QCOM_RPM_DDR_DMM 11
+#define QCOM_RPM_EBI1_CLK 12
+#define QCOM_RPM_HDMI_SWITCH 13
+#define QCOM_RPM_MMFPB_CLK 14
+#define QCOM_RPM_MM_FABRIC_ARB 15
+#define QCOM_RPM_MM_FABRIC_CLK 16
+#define QCOM_RPM_MM_FABRIC_HALT 17
+#define QCOM_RPM_MM_FABRIC_IOCTL 18
+#define QCOM_RPM_MM_FABRIC_MODE 19
+#define QCOM_RPM_PLL_4 20
+#define QCOM_RPM_PM8058_LDO0 21
+#define QCOM_RPM_PM8058_LDO1 22
+#define QCOM_RPM_PM8058_LDO2 23
+#define QCOM_RPM_PM8058_LDO3 24
+#define QCOM_RPM_PM8058_LDO4 25
+#define QCOM_RPM_PM8058_LDO5 26
+#define QCOM_RPM_PM8058_LDO6 27
+#define QCOM_RPM_PM8058_LDO7 28
+#define QCOM_RPM_PM8058_LDO8 29
+#define QCOM_RPM_PM8058_LDO9 30
+#define QCOM_RPM_PM8058_LDO10 31
+#define QCOM_RPM_PM8058_LDO11 32
+#define QCOM_RPM_PM8058_LDO12 33
+#define QCOM_RPM_PM8058_LDO13 34
+#define QCOM_RPM_PM8058_LDO14 35
+#define QCOM_RPM_PM8058_LDO15 36
+#define QCOM_RPM_PM8058_LDO16 37
+#define QCOM_RPM_PM8058_LDO17 38
+#define QCOM_RPM_PM8058_LDO18 39
+#define QCOM_RPM_PM8058_LDO19 40
+#define QCOM_RPM_PM8058_LDO20 41
+#define QCOM_RPM_PM8058_LDO21 42
+#define QCOM_RPM_PM8058_LDO22 43
+#define QCOM_RPM_PM8058_LDO23 44
+#define QCOM_RPM_PM8058_LDO24 45
+#define QCOM_RPM_PM8058_LDO25 46
+#define QCOM_RPM_PM8058_LVS0 47
+#define QCOM_RPM_PM8058_LVS1 48
+#define QCOM_RPM_PM8058_NCP 49
+#define QCOM_RPM_PM8058_SMPS0 50
+#define QCOM_RPM_PM8058_SMPS1 51
+#define QCOM_RPM_PM8058_SMPS2 52
+#define QCOM_RPM_PM8058_SMPS3 53
+#define QCOM_RPM_PM8058_SMPS4 54
+#define QCOM_RPM_PM8821_LDO1 55
+#define QCOM_RPM_PM8821_SMPS1 56
+#define QCOM_RPM_PM8821_SMPS2 57
+#define QCOM_RPM_PM8901_LDO0 58
+#define QCOM_RPM_PM8901_LDO1 59
+#define QCOM_RPM_PM8901_LDO2 60
+#define QCOM_RPM_PM8901_LDO3 61
+#define QCOM_RPM_PM8901_LDO4 62
+#define QCOM_RPM_PM8901_LDO5 63
+#define QCOM_RPM_PM8901_LDO6 64
+#define QCOM_RPM_PM8901_LVS0 65
+#define QCOM_RPM_PM8901_LVS1 66
+#define QCOM_RPM_PM8901_LVS2 67
+#define QCOM_RPM_PM8901_LVS3 68
+#define QCOM_RPM_PM8901_MVS 69
+#define QCOM_RPM_PM8901_SMPS0 70
+#define QCOM_RPM_PM8901_SMPS1 71
+#define QCOM_RPM_PM8901_SMPS2 72
+#define QCOM_RPM_PM8901_SMPS3 73
+#define QCOM_RPM_PM8901_SMPS4 74
+#define QCOM_RPM_PM8921_CLK1 75
+#define QCOM_RPM_PM8921_CLK2 76
+#define QCOM_RPM_PM8921_LDO1 77
+#define QCOM_RPM_PM8921_LDO2 78
+#define QCOM_RPM_PM8921_LDO3 79
+#define QCOM_RPM_PM8921_LDO4 80
+#define QCOM_RPM_PM8921_LDO5 81
+#define QCOM_RPM_PM8921_LDO6 82
+#define QCOM_RPM_PM8921_LDO7 83
+#define QCOM_RPM_PM8921_LDO8 84
+#define QCOM_RPM_PM8921_LDO9 85
+#define QCOM_RPM_PM8921_LDO10 86
+#define QCOM_RPM_PM8921_LDO11 87
+#define QCOM_RPM_PM8921_LDO12 88
+#define QCOM_RPM_PM8921_LDO13 89
+#define QCOM_RPM_PM8921_LDO14 90
+#define QCOM_RPM_PM8921_LDO15 91
+#define QCOM_RPM_PM8921_LDO16 92
+#define QCOM_RPM_PM8921_LDO17 93
+#define QCOM_RPM_PM8921_LDO18 94
+#define QCOM_RPM_PM8921_LDO19 95
+#define QCOM_RPM_PM8921_LDO20 96
+#define QCOM_RPM_PM8921_LDO21 97
+#define QCOM_RPM_PM8921_LDO22 98
+#define QCOM_RPM_PM8921_LDO23 99
+#define QCOM_RPM_PM8921_LDO24 100
+#define QCOM_RPM_PM8921_LDO25 101
+#define QCOM_RPM_PM8921_LDO26 102
+#define QCOM_RPM_PM8921_LDO27 103
+#define QCOM_RPM_PM8921_LDO28 104
+#define QCOM_RPM_PM8921_LDO29 105
+#define QCOM_RPM_PM8921_LVS1 106
+#define QCOM_RPM_PM8921_LVS2 107
+#define QCOM_RPM_PM8921_LVS3 108
+#define QCOM_RPM_PM8921_LVS4 109
+#define QCOM_RPM_PM8921_LVS5 110
+#define QCOM_RPM_PM8921_LVS6 111
+#define QCOM_RPM_PM8921_LVS7 112
+#define QCOM_RPM_PM8921_MVS 113
+#define QCOM_RPM_PM8921_NCP 114
+#define QCOM_RPM_PM8921_SMPS1 115
+#define QCOM_RPM_PM8921_SMPS2 116
+#define QCOM_RPM_PM8921_SMPS3 117
+#define QCOM_RPM_PM8921_SMPS4 118
+#define QCOM_RPM_PM8921_SMPS5 119
+#define QCOM_RPM_PM8921_SMPS6 120
+#define QCOM_RPM_PM8921_SMPS7 121
+#define QCOM_RPM_PM8921_SMPS8 122
+#define QCOM_RPM_PXO_CLK 123
+#define QCOM_RPM_QDSS_CLK 124
+#define QCOM_RPM_SFPB_CLK 125
+#define QCOM_RPM_SMI_CLK 126
+#define QCOM_RPM_SYS_FABRIC_ARB 127
+#define QCOM_RPM_SYS_FABRIC_CLK 128
+#define QCOM_RPM_SYS_FABRIC_HALT 129
+#define QCOM_RPM_SYS_FABRIC_IOCTL 130
+#define QCOM_RPM_SYS_FABRIC_MODE 131
+#define QCOM_RPM_USB_OTG_SWITCH 132
+#define QCOM_RPM_VDDMIN_GPIO 133
+
+/*
+ * Constants used to select force mode for regulators.
+ */
+#define QCOM_RPM_FORCE_MODE_NONE 0
+#define QCOM_RPM_FORCE_MODE_LPM 1
+#define QCOM_RPM_FORCE_MODE_HPM 2
+#define QCOM_RPM_FORCE_MODE_AUTO 3
+#define QCOM_RPM_FORCE_MODE_BYPASS 4
+
+#endif
diff --git a/include/dt-bindings/pinctrl/qcom,pmic-gpio.h b/include/dt-bindings/pinctrl/qcom,pmic-gpio.h
new file mode 100644
index 000000000000..fa74d7cc960c
--- /dev/null
+++ b/include/dt-bindings/pinctrl/qcom,pmic-gpio.h
@@ -0,0 +1,142 @@
+/*
+ * This header provides constants for the Qualcomm PMIC GPIO binding.
+ */
+
+#ifndef _DT_BINDINGS_PINCTRL_QCOM_PMIC_GPIO_H
+#define _DT_BINDINGS_PINCTRL_QCOM_PMIC_GPIO_H
+
+#define PMIC_GPIO_PULL_UP_30 0
+#define PMIC_GPIO_PULL_UP_1P5 1
+#define PMIC_GPIO_PULL_UP_31P5 2
+#define PMIC_GPIO_PULL_UP_1P5_30 3
+
+#define PMIC_GPIO_STRENGTH_NO 0
+#define PMIC_GPIO_STRENGTH_HIGH 1
+#define PMIC_GPIO_STRENGTH_MED 2
+#define PMIC_GPIO_STRENGTH_LOW 3
+
+/*
+ * Note: PM8018 GPIO3 and GPIO4 are supporting
+ * only S3 and L2 options (1.8V)
+ */
+#define PM8018_GPIO_L6 0
+#define PM8018_GPIO_L5 1
+#define PM8018_GPIO_S3 2
+#define PM8018_GPIO_L14 3
+#define PM8018_GPIO_L2 4
+#define PM8018_GPIO_L4 5
+#define PM8018_GPIO_VDD 6
+
+/*
+ * Note: PM8038 GPIO7 and GPIO8 are supporting
+ * only L11 and L4 options (1.8V)
+ */
+#define PM8038_GPIO_VPH 0
+#define PM8038_GPIO_BB 1
+#define PM8038_GPIO_L11 2
+#define PM8038_GPIO_L15 3
+#define PM8038_GPIO_L4 4
+#define PM8038_GPIO_L3 5
+#define PM8038_GPIO_L17 6
+
+#define PM8058_GPIO_VPH 0
+#define PM8058_GPIO_BB 1
+#define PM8058_GPIO_S3 2
+#define PM8058_GPIO_L3 3
+#define PM8058_GPIO_L7 4
+#define PM8058_GPIO_L6 5
+#define PM8058_GPIO_L5 6
+#define PM8058_GPIO_L2 7
+
+#define PM8917_GPIO_VPH 0
+#define PM8917_GPIO_S4 2
+#define PM8917_GPIO_L15 3
+#define PM8917_GPIO_L4 4
+#define PM8917_GPIO_L3 5
+#define PM8917_GPIO_L17 6
+
+#define PM8921_GPIO_VPH 0
+#define PM8921_GPIO_BB 1
+#define PM8921_GPIO_S4 2
+#define PM8921_GPIO_L15 3
+#define PM8921_GPIO_L4 4
+#define PM8921_GPIO_L3 5
+#define PM8921_GPIO_L17 6
+
+/*
+ * Note: PM8941 gpios from 15 to 18 are supporting
+ * only S3 and L6 options (1.8V)
+ */
+#define PM8941_GPIO_VPH 0
+#define PM8941_GPIO_L1 1
+#define PM8941_GPIO_S3 2
+#define PM8941_GPIO_L6 3
+
+/*
+ * Note: PMA8084 gpios from 15 to 18 are supporting
+ * only S4 and L6 options (1.8V)
+ */
+#define PMA8084_GPIO_VPH 0
+#define PMA8084_GPIO_L1 1
+#define PMA8084_GPIO_S4 2
+#define PMA8084_GPIO_L6 3
+
+/* To be used with "function" */
+#define PMIC_GPIO_FUNC_NORMAL "normal"
+#define PMIC_GPIO_FUNC_PAIRED "paired"
+#define PMIC_GPIO_FUNC_FUNC1 "func1"
+#define PMIC_GPIO_FUNC_FUNC2 "func2"
+#define PMIC_GPIO_FUNC_DTEST1 "dtest1"
+#define PMIC_GPIO_FUNC_DTEST2 "dtest2"
+#define PMIC_GPIO_FUNC_DTEST3 "dtest3"
+#define PMIC_GPIO_FUNC_DTEST4 "dtest4"
+
+#define PM8038_GPIO1_2_LPG_DRV PMIC_GPIO_FUNC_FUNC1
+#define PM8038_GPIO3_5V_BOOST_EN PMIC_GPIO_FUNC_FUNC1
+#define PM8038_GPIO4_SSBI_ALT_CLK PMIC_GPIO_FUNC_FUNC1
+#define PM8038_GPIO5_6_EXT_REG_EN PMIC_GPIO_FUNC_FUNC1
+#define PM8038_GPIO10_11_EXT_REG_EN PMIC_GPIO_FUNC_FUNC1
+#define PM8038_GPIO6_7_CLK PMIC_GPIO_FUNC_FUNC1
+#define PM8038_GPIO9_BAT_ALRM_OUT PMIC_GPIO_FUNC_FUNC1
+#define PM8038_GPIO6_12_KYPD_DRV PMIC_GPIO_FUNC_FUNC2
+
+#define PM8058_GPIO7_8_MP3_CLK PMIC_GPIO_FUNC_FUNC1
+#define PM8058_GPIO7_8_BCLK_19P2MHZ PMIC_GPIO_FUNC_FUNC2
+#define PM8058_GPIO9_26_KYPD_DRV PMIC_GPIO_FUNC_FUNC1
+#define PM8058_GPIO21_23_UART_TX PMIC_GPIO_FUNC_FUNC2
+#define PM8058_GPIO24_26_LPG_DRV PMIC_GPIO_FUNC_FUNC2
+#define PM8058_GPIO33_BCLK_19P2MHZ PMIC_GPIO_FUNC_FUNC1
+#define PM8058_GPIO34_35_MP3_CLK PMIC_GPIO_FUNC_FUNC1
+#define PM8058_GPIO36_BCLK_19P2MHZ PMIC_GPIO_FUNC_FUNC1
+#define PM8058_GPIO37_UPL_OUT PMIC_GPIO_FUNC_FUNC1
+#define PM8058_GPIO37_UART_M_RX PMIC_GPIO_FUNC_FUNC2
+#define PM8058_GPIO38_XO_SLEEP_CLK PMIC_GPIO_FUNC_FUNC1
+#define PM8058_GPIO38_39_CLK_32KHZ PMIC_GPIO_FUNC_FUNC2
+#define PM8058_GPIO39_MP3_CLK PMIC_GPIO_FUNC_FUNC1
+#define PM8058_GPIO40_EXT_BB_EN PMIC_GPIO_FUNC_FUNC1
+
+#define PM8917_GPIO9_18_KEYP_DRV PMIC_GPIO_FUNC_FUNC1
+#define PM8917_GPIO20_BAT_ALRM_OUT PMIC_GPIO_FUNC_FUNC1
+#define PM8917_GPIO21_23_UART_TX PMIC_GPIO_FUNC_FUNC2
+#define PM8917_GPIO25_26_EXT_REG_EN PMIC_GPIO_FUNC_FUNC1
+#define PM8917_GPIO37_38_XO_SLEEP_CLK PMIC_GPIO_FUNC_FUNC1
+#define PM8917_GPIO37_38_MP3_CLK PMIC_GPIO_FUNC_FUNC2
+
+#define PM8941_GPIO9_14_KYPD_DRV PMIC_GPIO_FUNC_FUNC1
+#define PM8941_GPIO15_18_DIV_CLK PMIC_GPIO_FUNC_FUNC1
+#define PM8941_GPIO15_18_SLEEP_CLK PMIC_GPIO_FUNC_FUNC2
+#define PM8941_GPIO23_26_KYPD_DRV PMIC_GPIO_FUNC_FUNC1
+#define PM8941_GPIO23_26_LPG_DRV_HI PMIC_GPIO_FUNC_FUNC2
+#define PM8941_GPIO31_BAT_ALRM_OUT PMIC_GPIO_FUNC_FUNC1
+#define PM8941_GPIO33_36_LPG_DRV_3D PMIC_GPIO_FUNC_FUNC1
+#define PM8941_GPIO33_36_LPG_DRV_HI PMIC_GPIO_FUNC_FUNC2
+
+#define PMA8084_GPIO4_5_LPG_DRV PMIC_GPIO_FUNC_FUNC1
+#define PMA8084_GPIO7_10_LPG_DRV PMIC_GPIO_FUNC_FUNC1
+#define PMA8084_GPIO5_14_KEYP_DRV PMIC_GPIO_FUNC_FUNC2
+#define PMA8084_GPIO19_21_KEYP_DRV PMIC_GPIO_FUNC_FUNC2
+#define PMA8084_GPIO15_18_DIV_CLK PMIC_GPIO_FUNC_FUNC1
+#define PMA8084_GPIO15_18_SLEEP_CLK PMIC_GPIO_FUNC_FUNC2
+#define PMA8084_GPIO22_BAT_ALRM_OUT PMIC_GPIO_FUNC_FUNC1
+
+#endif
diff --git a/include/dt-bindings/pinctrl/qcom,pmic-mpp.h b/include/dt-bindings/pinctrl/qcom,pmic-mpp.h
new file mode 100644
index 000000000000..d2c7dabe3223
--- /dev/null
+++ b/include/dt-bindings/pinctrl/qcom,pmic-mpp.h
@@ -0,0 +1,44 @@
+/*
+ * This header provides constants for the Qualcomm PMIC's
+ * Multi-Purpose Pin binding.
+ */
+
+#ifndef _DT_BINDINGS_PINCTRL_QCOM_PMIC_MPP_H
+#define _DT_BINDINGS_PINCTRL_QCOM_PMIC_MPP_H
+
+/* power-source */
+#define PM8841_MPP_VPH 0
+#define PM8841_MPP_S3 2
+
+#define PM8941_MPP_VPH 0
+#define PM8941_MPP_L1 1
+#define PM8941_MPP_S3 2
+#define PM8941_MPP_L6 3
+
+#define PMA8084_MPP_VPH 0
+#define PMA8084_MPP_L1 1
+#define PMA8084_MPP_S4 2
+#define PMA8084_MPP_L6 3
+
+/*
+ * Analog Input - Set the source for analog input.
+ * To be used with "qcom,amux-route" property
+ */
+#define PMIC_MPP_AMUX_ROUTE_CH5 0
+#define PMIC_MPP_AMUX_ROUTE_CH6 1
+#define PMIC_MPP_AMUX_ROUTE_CH7 2
+#define PMIC_MPP_AMUX_ROUTE_CH8 3
+#define PMIC_MPP_AMUX_ROUTE_ABUS1 4
+#define PMIC_MPP_AMUX_ROUTE_ABUS2 5
+#define PMIC_MPP_AMUX_ROUTE_ABUS3 6
+#define PMIC_MPP_AMUX_ROUTE_ABUS4 7
+
+/* To be used with "function" */
+#define PMIC_MPP_FUNC_NORMAL "normal"
+#define PMIC_MPP_FUNC_PAIRED "paired"
+#define PMIC_MPP_FUNC_DTEST1 "dtest1"
+#define PMIC_MPP_FUNC_DTEST2 "dtest2"
+#define PMIC_MPP_FUNC_DTEST3 "dtest3"
+#define PMIC_MPP_FUNC_DTEST4 "dtest4"
+
+#endif
diff --git a/include/linux/clk-private.h b/include/linux/clk-private.h
index 0ca5f6046920..681c6e0aa42c 100644
--- a/include/linux/clk-private.h
+++ b/include/linux/clk-private.h
@@ -38,8 +38,10 @@ struct clk {
struct clk **parents;
u8 num_parents;
u8 new_parent_index;
+ u8 safe_parent_index;
unsigned long rate;
unsigned long new_rate;
+ struct clk *safe_parent;
struct clk *new_parent;
struct clk *new_child;
unsigned long flags;
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index be21af149f11..519da1893184 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -179,6 +179,7 @@ struct clk_ops {
struct clk **best_parent_clk);
int (*set_parent)(struct clk_hw *hw, u8 index);
u8 (*get_parent)(struct clk_hw *hw);
+ struct clk *(*get_safe_parent)(struct clk_hw *hw);
int (*set_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate);
int (*set_rate_and_parent)(struct clk_hw *hw,
@@ -353,6 +354,17 @@ struct clk_divider {
extern const struct clk_ops clk_divider_ops;
extern const struct clk_ops clk_divider_ro_ops;
+
+unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
+ unsigned int val, const struct clk_div_table *table,
+ unsigned long flags);
+long divider_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate, const struct clk_div_table *table,
+ u8 width, unsigned long flags);
+int divider_get_val(unsigned long rate, unsigned long parent_rate,
+ const struct clk_div_table *table, u8 width,
+ unsigned long flags);
+
struct clk *clk_register_divider(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
@@ -383,11 +395,13 @@ struct clk *clk_register_divider_table(struct device *dev, const char *name,
* register, and mask of mux bits are in higher 16-bit of this register.
* While setting the mux bits, higher 16-bit should also be updated to
* indicate changing mux bits.
+ * CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired
+ * frequency.
*/
struct clk_mux {
struct clk_hw hw;
void __iomem *reg;
- u32 *table;
+ unsigned int *table;
u32 mask;
u8 shift;
u8 flags;
@@ -397,11 +411,17 @@ struct clk_mux {
#define CLK_MUX_INDEX_ONE BIT(0)
#define CLK_MUX_INDEX_BIT BIT(1)
#define CLK_MUX_HIWORD_MASK BIT(2)
-#define CLK_MUX_READ_ONLY BIT(3) /* mux setting cannot be changed */
+#define CLK_MUX_READ_ONLY BIT(3) /* mux can't be changed */
+#define CLK_MUX_ROUND_CLOSEST BIT(4)
extern const struct clk_ops clk_mux_ops;
extern const struct clk_ops clk_mux_ro_ops;
+unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val,
+ unsigned int *table, unsigned long flags);
+unsigned int clk_mux_reindex(u8 index, unsigned int *table,
+ unsigned long flags);
+
struct clk *clk_register_mux(struct device *dev, const char *name,
const char **parent_names, u8 num_parents, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
@@ -410,7 +430,9 @@ struct clk *clk_register_mux(struct device *dev, const char *name,
struct clk *clk_register_mux_table(struct device *dev, const char *name,
const char **parent_names, u8 num_parents, unsigned long flags,
void __iomem *reg, u8 shift, u32 mask,
- u8 clk_mux_flags, u32 *table, spinlock_t *lock);
+ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock);
+
+void clk_unregister_mux(struct clk *clk);
void of_fixed_factor_clk_setup(struct device_node *node);
@@ -555,7 +577,9 @@ struct clk *__clk_lookup(const char *name);
long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *best_parent_rate,
struct clk **best_parent_p);
-
+long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate,
+ unsigned long *best_parent_rate,
+ struct clk **best_parent_p);
/*
* FIXME clock api without lock protection
*/
diff --git a/include/linux/mfd/pm8921-core.h b/include/linux/mfd/pm8921-core.h
new file mode 100644
index 000000000000..b31d785761f4
--- /dev/null
+++ b/include/linux/mfd/pm8921-core.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2014, Sony Mobile Communications AB
+ *
+ * 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 __MFD_PM8921_CORE_H
+#define __MFD_PM8921_CORE_H
+
+#include <linux/err.h>
+
+#if IS_ENABLED(CONFIG_MFD_PM8921_CORE)
+
+int pm8xxx_read_irq_status(int irq);
+
+#else
+static inline int pm8xxx_read_irq_status(int irq)
+{
+ return -ENOSYS;
+}
+
+#endif
+
+#endif
diff --git a/include/linux/mfd/qcom_rpm.h b/include/linux/mfd/qcom_rpm.h
new file mode 100644
index 000000000000..a60798d1b12e
--- /dev/null
+++ b/include/linux/mfd/qcom_rpm.h
@@ -0,0 +1,10 @@
+#ifndef __QCOM_RPM_H__
+#define __QCOM_RPM_H__
+
+#include <linux/types.h>
+
+struct qcom_rpm;
+
+int qcom_rpm_write(struct qcom_rpm *rpm, int resource, u32 *buf, size_t count);
+
+#endif
diff --git a/include/soc/qcom/pm.h b/include/soc/qcom/pm.h
new file mode 100644
index 000000000000..c2f006bb7b48
--- /dev/null
+++ b/include/soc/qcom/pm.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __QCOM_PM_H
+#define __QCOM_PM_H
+
+enum msm_pm_sleep_mode {
+ MSM_PM_SLEEP_MODE_WFI,
+ MSM_PM_SLEEP_MODE_RET,
+ MSM_PM_SLEEP_MODE_SPC,
+ MSM_PM_SLEEP_MODE_PC,
+ MSM_PM_SLEEP_MODE_NR,
+};
+
+enum msm_pm_l2_scm_flag {
+ MSM_SCM_L2_ON = 0,
+ MSM_SCM_L2_OFF = 1
+};
+
+#endif /* __QCOM_PM_H */
diff --git a/arch/arm/mach-qcom/scm-boot.h b/include/soc/qcom/scm-boot.h
index 6aabb2428176..02b445c426ce 100644
--- a/arch/arm/mach-qcom/scm-boot.h
+++ b/include/soc/qcom/scm-boot.h
@@ -18,6 +18,8 @@
#define SCM_FLAG_COLDBOOT_CPU3 0x20
#define SCM_FLAG_WARMBOOT_CPU0 0x04
#define SCM_FLAG_WARMBOOT_CPU1 0x02
+#define SCM_FLAG_WARMBOOT_CPU2 0x10
+#define SCM_FLAG_WARMBOOT_CPU3 0x40
int scm_set_boot_addr(phys_addr_t addr, int flags);
diff --git a/arch/arm/mach-qcom/scm.h b/include/soc/qcom/scm.h
index 00b31ea58f29..a001ef865623 100644
--- a/arch/arm/mach-qcom/scm.h
+++ b/include/soc/qcom/scm.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2010-2014, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -14,12 +14,18 @@
#define SCM_SVC_BOOT 0x1
#define SCM_SVC_PIL 0x2
+#define SCM_SVC_INFO 0x6
extern int scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf, size_t cmd_len,
void *resp_buf, size_t resp_len);
+extern s32 scm_call_atomic1(u32 svc, u32 cmd, u32 arg1);
+extern s32 scm_call_atomic2(u32 svc, u32 cmd, u32 arg1, u32 arg2);
+
#define SCM_VERSION(major, minor) (((major) << 16) | ((minor) & 0xFF))
extern u32 scm_get_version(void);
+extern int scm_is_call_available(u32 svc_id, u32 cmd_id);
+extern int scm_get_feat_version(u32 feat);
#endif
diff --git a/include/soc/qcom/spm.h b/include/soc/qcom/spm.h
new file mode 100644
index 000000000000..29686efb3ce2
--- /dev/null
+++ b/include/soc/qcom/spm.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2010-2014, 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 __QCOM_SPM_H
+#define __QCOM_SPM_H
+
+enum {
+ MSM_SPM_MODE_DISABLED,
+ MSM_SPM_MODE_CLOCK_GATING,
+ MSM_SPM_MODE_RETENTION,
+ MSM_SPM_MODE_GDHS,
+ MSM_SPM_MODE_POWER_COLLAPSE,
+ MSM_SPM_MODE_NR
+};
+
+struct msm_spm_device;
+
+#if defined(CONFIG_QCOM_PM)
+
+int msm_spm_set_low_power_mode(u32 mode);
+
+#else
+
+static inline int msm_spm_set_low_power_mode(u32 mode)
+{ return -ENOSYS; }
+
+#endif /* CONFIG_QCOM_PM */
+
+#endif /* __QCOM_SPM_H */