diff options
109 files changed, 11092 insertions, 417 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..ae1b07f852c6 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/qcom,idle-state.txt @@ -0,0 +1,81 @@ +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 - + + * Standby + * Retention + * Standalone Power Collapse (Standalone PC or SPC) + * Power Collapse (PC) + +Standby: Standby does a little more in addition to architectural clock gating. +When the WFI instruction is executed the ARM core would gate its internal +clocks. In addition to gating the clocks, QCOM cpus use this instruction as a +trigger to execute the SPM state machine. The SPM state machine waits for the +interrupt to trigger the core back in to active. This triggers the cache +hierarchy to enter standby states, when all cpus are idle. An interrupt brings +the SPM state machine out of its wait, the next step is to ensure that the +cache hierarchy is also out of standby, and then the cpu is allowed to resume +execution. + +Retention: Retention is a low power state where the core is clock gated 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. 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 Standby. + +Standalone PC: A cpu can power down and warmboot if there is a sufficient time +between the time it enters idle and the next known 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. The SPM +sequence for this idle state is programmed to power down the supply to the +core, wait for the interrupt, restore power to the core, and ensure the +system state including cache hierarchy is ready before allowing core to +resume. Applying power and resetting the core causes the core to warmboot +back into Elevation Level (EL) which trampolines the control back to the +kernel. Entering a power down state for the cpu, needs to be done by trapping +into a EL. Failing to do so, would result in a crash enforced by the warm boot +code in the EL for the SoC. On SoCs with write-back L1 cache, the cache has to +be flushed in s/w, before powering down the core. + +Power Collapse: This state is similar to the SPC mode, but distinguishes +itself in 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 clock gated and +voltages reduced, provided all cpus enter this 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 +application processor subsystem shut down. + +The idle-state for QCOM SoCs are distinguished by the compatible property of +the idle-states device node. +The devicetree representation of the idle state should be - + +Required properties: + +- compatible: Must be one of - + "qcom,idle-state-stby", + "qcom,idle-state-ret", + "qcom,idle-state-spc", + "qcom,idle-state-pc", + and "arm,idle-state". + +Other required and optional properties are specified in [1]. + +Example: + + idle-states { + CPU_SPC: spc { + compatible = "qcom,idle-state-spc", "arm,idle-state"; + entry-latency-us = <150>; + exit-latency-us = <200>; + min-residency-us = <2000>; + }; + }; + +[1]. Documentation/devicetree/bindings/arm/idle-states.txt diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,saw2.txt b/Documentation/devicetree/bindings/arm/msm/qcom,saw2.txt index 1505fb8e131a..690c3c002ad2 100644 --- a/Documentation/devicetree/bindings/arm/msm/qcom,saw2.txt +++ b/Documentation/devicetree/bindings/arm/msm/qcom,saw2.txt @@ -2,11 +2,20 @@ SPM AVS Wrapper 2 (SAW2) The SAW2 is a wrapper around the Subsystem Power Manager (SPM) and the Adaptive Voltage Scaling (AVS) hardware. The SPM is a programmable -micro-controller that transitions a piece of hardware (like a processor or +power-controller that transitions a piece of hardware (like a processor or subsystem) into and out of low power modes via a direct connection to the PMIC. It can also be wired up to interact with other processors in the system, notifying them when a low power state is entered or exited. +Multiple revisions of the SAW hardware are supported using these Device Nodes. +SAW2 revisions differ in the register offset and configuration data. Also, the +same revision of the SAW in different SoCs may have different configuration +data due the the differences in hardware capabilities. Hence the SoC name, the +version of the SAW hardware in that SoC and the distinction between cpu (big +or Little) or cache, may be needed to uniquely identify the SAW register +configuration and initialization data. The compatible string is used to +indicate this parameter. + PROPERTIES - compatible: @@ -14,10 +23,13 @@ PROPERTIES Value type: <string> Definition: shall contain "qcom,saw2". A more specific value should be one of: - "qcom,saw2-v1" - "qcom,saw2-v1.1" - "qcom,saw2-v2" - "qcom,saw2-v2.1" + "qcom,saw2-v1" + "qcom,saw2-v1.1" + "qcom,saw2-v2" + "qcom,saw2-v2.1" + "qcom,apq8064-saw2-v1.1-cpu" + "qcom,msm8974-saw2-v2.1-cpu" + "qcom,apq8084-saw2-v2.1-cpu" - reg: Usage: required @@ -26,10 +38,17 @@ PROPERTIES the register region. An optional second element specifies the base address and size of the alias register region. +- regulator: + Usage: optional + Value type: boolean + Definition: Indicates that this SPM device acts as a regulator device + device for the core (CPU or Cache) the SPM is attached + to. Example: - regulator@2099000 { + power-controller@2099000 { compatible = "qcom,saw2"; reg = <0x02099000 0x1000>, <0x02009000 0x1000>; + regulator; }; diff --git a/Documentation/devicetree/bindings/clock/qcom,lcc.txt b/Documentation/devicetree/bindings/clock/qcom,lcc.txt new file mode 100644 index 000000000000..dd755be63a01 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,lcc.txt @@ -0,0 +1,21 @@ +Qualcomm LPASS Clock & Reset Controller Binding +------------------------------------------------ + +Required properties : +- compatible : shall contain only one of the following: + + "qcom,lcc-msm8960" + "qcom,lcc-apq8064" + "qcom,lcc-ipq8064" + +- reg : shall contain base register location and length +- #clock-cells : shall contain 1 +- #reset-cells : shall contain 1 + +Example: + clock-controller@28000000 { + compatible = "qcom,lcc-ipq8064"; + reg = <0x28000000 0x1000>; + #clock-cells = <1>; + #reset-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/mfd/qcom-rpm.txt b/Documentation/devicetree/bindings/mfd/qcom-rpm.txt new file mode 100644 index 000000000000..85e31980017a --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/qcom-rpm.txt @@ -0,0 +1,70 @@ +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 + + += 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>; + }; + diff --git a/Documentation/devicetree/bindings/mmc/mmc-pwrseq-simple.txt b/Documentation/devicetree/bindings/mmc/mmc-pwrseq-simple.txt new file mode 100644 index 000000000000..a462c50f19a8 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/mmc-pwrseq-simple.txt @@ -0,0 +1,25 @@ +* The simple MMC power sequence provider + +The purpose of the simple MMC power sequence provider is to supports a set of +common properties between various SOC designs. It thus enables us to use the +same provider for several SOC designs. + +Required properties: +- compatible : contains "mmc-pwrseq-simple". + +Optional properties: +- reset-gpios : contains a list of GPIO specifiers. The reset GPIOs are asserted + at initialization and prior we start the power up procedure of the card. + They will be de-asserted right after the power has been provided to the + card. +- clocks : Must contain an entry for the entry in clock-names. + See ../clocks/clock-bindings.txt for details. +- clock-names : Must include the following entry: + "ext_clock" (External clock provided to the card). + +Example: + + sdhci0_pwrseq { + compatible = "mmc-pwrseq-simple"; + reset-gpios = <&gpio1 12 0>; + } diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index b52628b18a53..86a9677618d2 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -64,6 +64,19 @@ Optional SDIO properties: - keep-power-in-suspend: Preserves card power during a suspend/resume cycle - enable-sdio-wakeup: Enables wake up of host system on SDIO IRQ assertion +MMC power sequences: +-------------------- + +System on chip designs may specify a specific MMC power sequence. To +successfully detect an (e)MMC/SD/SDIO card, that power sequence must be +maintained while initializing the card. + +Optional property: +- mmc-pwrseq: phandle to the MMC power sequence node. See "mmc-pwrseq-*" + for documentation of MMC power sequence bindings. + + + Example: sdhci@ab000000 { @@ -77,4 +90,5 @@ sdhci@ab000000 { max-frequency = <50000000>; keep-power-in-suspend; enable-sdio-wakeup; + mmc-pwrseq = <&sdhci0_pwrseq> } diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 5ddd4906f7a7..a324ecdfeb21 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -397,6 +397,13 @@ choice Say Y here if you want the debug print routines to direct their output to UART1 serial port on KEYSTONE2 devices. + 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_MESON_UARTAO bool "Kernel low-level debugging via Meson6 UARTAO" depends on ARCH_MESON @@ -496,6 +503,13 @@ 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 + 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 @@ -520,6 +534,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 (except 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 (except 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 (except OMAP730) on the UART3. + config DEBUG_OMAP2UART1 bool "OMAP2/3/4 UART1 (omap2/3 sdp boards and some omap3 boards)" depends on ARCH_OMAP2PLUS @@ -562,6 +600,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 @@ -1031,15 +1093,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 @@ -1183,7 +1236,9 @@ 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/renesas-scif.S" if DEBUG_R7S72100_SCIF2 default "debug/renesas-scif.S" if DEBUG_RCAR_GEN1_SCIF0 @@ -1208,12 +1263,7 @@ 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 + bool # Compatibility options for 8250 config DEBUG_UART_8250 @@ -1229,6 +1279,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 @@ -1269,7 +1320,6 @@ config DEBUG_UART_PHYS DEBUG_S3C2410_UART2) default 0x78000000 if DEBUG_CNS3XXX default 0x7c0003f8 if FOOTBRIDGE - default 0x78000000 if DEBUG_CNS3XXX default 0x80010000 if DEBUG_ASM9260_UART default 0x80070000 if DEBUG_IMX23_UART default 0x80074000 if DEBUG_IMX28_UART @@ -1310,12 +1360,17 @@ config DEBUG_UART_PHYS default 0xffe40000 if DEBUG_RCAR_GEN1_SCIF0 default 0xffe42000 if DEBUG_RCAR_GEN1_SCIF2 default 0xfff36000 if DEBUG_HIGHBANK_UART + default 0xfffb0000 if DEBUG_OMAP1UART1 || DEBUG_OMAP7XXUART1 + default 0xfffb0800 if DEBUG_OMAP1UART2 || DEBUG_OMAP7XXUART2 + default 0xfffb9800 if DEBUG_OMAP1UART3 || DEBUG_OMAP7XXUART3 default 0xfffe8600 if DEBUG_UART_BCM63XX default 0xfffff700 if ARCH_IOP33X - depends on DEBUG_LL_UART_8250 || DEBUG_LL_UART_PL01X || \ + depends on ARCH_EP93XX || \ + DEBUG_LL_UART_8250 || DEBUG_LL_UART_PL01X || \ DEBUG_LL_UART_EFM32 || \ DEBUG_UART_8250 || DEBUG_UART_PL01X || DEBUG_MESON_UARTAO || \ - DEBUG_MSM_UART || DEBUG_QCOM_UARTDM || DEBUG_R7S72100_SCIF2 || \ + DEBUG_MSM_UART || DEBUG_NETX_UART || \ + DEBUG_QCOM_UARTDM || DEBUG_R7S72100_SCIF2 || \ DEBUG_RCAR_GEN1_SCIF0 || DEBUG_RCAR_GEN1_SCIF2 || \ DEBUG_RCAR_GEN2_SCIF0 || DEBUG_RCAR_GEN2_SCIF2 || \ DEBUG_RMOBILE_SCIFA0 || DEBUG_RMOBILE_SCIFA1 || \ @@ -1324,6 +1379,7 @@ config DEBUG_UART_PHYS 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 @@ -1392,18 +1448,23 @@ 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_MSM_UART || DEBUG_NETX_UART || \ + DEBUG_QCOM_UARTDM || DEBUG_S3C24XX_UART || \ DEBUG_UART_BCM63XX || DEBUG_ASM9260_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 || DEBUG_BCM_5301X + default 0 if FOOTBRIDGE || ARCH_IOP32X || DEBUG_BCM_5301X || \ + DEBUG_OMAP7XXUART1 || DEBUG_OMAP7XXUART2 || DEBUG_OMAP7XXUART3 default 2 config DEBUG_UART_8250_WORD diff --git a/arch/arm/boot/dts/qcom-apq8064-cm-qs600.dts b/arch/arm/boot/dts/qcom-apq8064-cm-qs600.dts index 5d75666f7f6c..fc8cdadd2cf6 100644 --- a/arch/arm/boot/dts/qcom-apq8064-cm-qs600.dts +++ b/arch/arm/boot/dts/qcom-apq8064-cm-qs600.dts @@ -1,4 +1,6 @@ #include "qcom-apq8064-v2.0.dtsi" +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/pinctrl/qcom,pmic-gpio.h> / { model = "CompuLab CM-QS600"; @@ -40,6 +42,77 @@ }; }; + /* OTG */ + usb1_phy:phy@12500000 { + status = "ok"; + }; + + usb3_phy:phy@12520000 { + status = "ok"; + }; + + usb4_phy:phy@12530000 { + status = "ok"; + }; + + gadget1:gadget@12500000 { + status = "ok"; + }; + + /* OTG */ + usb1: usb@12500000 { + status = "ok"; + }; + + usb3: usb@12520000 { + status = "ok"; + }; + + usb4: usb@12530000 { + status = "ok"; + }; + + /* on board fixed 3.3v supply */ + v3p3_pcieclk: v3p3-pcieclk { + compatible = "regulator-fixed"; + regulator-name = "PCIE V3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + pci@1b500000 { + status = "ok"; + pcie-clk-supply = <&v3p3_pcieclk>; + avdd-supply = <&pm8921_s3>; + vdd-supply = <&pm8921_lvs6>; + qcom,external-phy-refclk; + reset-gpio = <&tlmm_pinmux 27 GPIO_ACTIVE_LOW>; + }; + + qcom,ssbi@500000 { + pmicintc: pmic@0 { + pm8921_gpio: gpio@150 { + pinctrl-names = "default"; + pinctrl-0 = <&wlan_default_gpios>; + wlan_default_gpios: wlan-gpios { + pios { + pins = "gpio43"; + function = "normal"; + bias-disable; + power-source = <PM8921_GPIO_S4>; + }; + }; + }; + }; + }; + + sdcc4_pwrseq:pwrseq { + compatible = "mmc-pwrseq-simple"; + /* WLAN reset */ + reset-gpios = <&pm8921_gpio 42 GPIO_ACTIVE_LOW>; + }; + amba { /* eMMC */ sdcc1: sdcc@12400000 { @@ -53,6 +126,7 @@ /* WLAN */ sdcc4: sdcc@121c0000 { status = "okay"; + mmc-pwrseq = <&sdcc4_pwrseq>; }; }; }; diff --git a/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts b/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts index b396c8311b27..69ef1816ed7f 100644 --- a/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts +++ b/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts @@ -1,9 +1,16 @@ #include "qcom-apq8064-v2.0.dtsi" +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/pinctrl/qcom,pmic-gpio.h> / { model = "Qualcomm APQ8064/IFC6410"; compatible = "qcom,apq8064-ifc6410", "qcom,apq8064"; + aliases { + serial0 = &serial0; + serial1 = &serial1; + }; + soc { pinctrl@800000 { i2c1_pins: i2c1 { @@ -12,6 +19,21 @@ function = "gsbi1"; }; }; + + uart_pins: uart_pins { + mux { + pins = "gpio14", "gpio15", "gpio16", "gpio17"; + function = "gsbi6"; + }; + }; + + card_detect: card_detect { + mux { + pins = "gpio26"; + function = "gpio"; + bias-disable; + }; + }; }; gsbi@12440000 { @@ -32,6 +54,18 @@ }; }; + gsbi@16500000 { + status = "ok"; + qcom,mode = <GSBI_PROT_I2C_UART>; + + serial@16540000 { + status = "ok"; + + pinctrl-names = "default"; + pinctrl-0 = <&uart_pins>; + }; + }; + gsbi@16600000 { status = "ok"; qcom,mode = <GSBI_PROT_I2C_UART>; @@ -40,6 +74,86 @@ }; }; + /* OTG */ + usb1_phy:phy@12500000 { + status = "ok"; + }; + + usb3_phy:phy@12520000 { + status = "ok"; + }; + + usb4_phy:phy@12530000 { + status = "ok"; + }; + + gadget1:gadget@12500000 { + status = "ok"; + }; + + /* OTG */ + usb1: usb@12500000 { + status = "ok"; + }; + + usb3: usb@12520000 { + status = "ok"; + }; + + usb4: usb@12530000 { + status = "ok"; + }; + + /* on board fixed 3.3v supply */ + v3p3_pcieclk: v3p3-pcieclk { + compatible = "regulator-fixed"; + regulator-name = "PCIE V3P3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + }; + + pci@1b500000 { + status = "ok"; + pcie-clk-supply = <&v3p3_pcieclk>; + avdd-supply = <&pm8921_s3>; + vdd-supply = <&pm8921_lvs6>; + ext-3p3v-supply = <&ext_3p3v>; + qcom,external-phy-refclk; + reset-gpio = <&tlmm_pinmux 27 GPIO_ACTIVE_LOW>; + }; + + qcom,ssbi@500000 { + pmicintc: pmic@0 { + pm8921_gpio: gpio@150 { + pinctrl-names = "default"; + pinctrl-0 = <&wlan_default_gpios &bt_gpios>; + wlan_default_gpios: wlan-gpios { + pios { + pins = "gpio43"; + function = "normal"; + bias-disable; + power-source = <PM8921_GPIO_S4>; + }; + }; + + bt_gpios: bt-gpio { + pios { + pins = "gpio44"; + function = "normal"; + bias-disable; + power-source = <PM8921_GPIO_S4>; + }; + }; + }; + }; + }; + sdcc4_pwrseq:pwrseq { + compatible = "mmc-pwrseq-simple"; + reset-gpios = <&pm8921_gpio 42 GPIO_ACTIVE_LOW>, + <&pm8921_gpio 43 GPIO_ACTIVE_LOW>; + }; + amba { /* eMMC */ sdcc1: sdcc@12400000 { @@ -49,10 +163,15 @@ /* External micro SD card */ sdcc3: sdcc@12180000 { status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&card_detect>; + cd-gpios = <&tlmm_pinmux 26 GPIO_ACTIVE_LOW>; }; /* WLAN */ sdcc4: sdcc@121c0000 { status = "okay"; + mmc-pwrseq = <&sdcc4_pwrseq>; + }; }; }; diff --git a/arch/arm/boot/dts/qcom-apq8064.dtsi b/arch/arm/boot/dts/qcom-apq8064.dtsi index b3154c071652..c78718d9b996 100644 --- a/arch/arm/boot/dts/qcom-apq8064.dtsi +++ b/arch/arm/boot/dts/qcom-apq8064.dtsi @@ -1,11 +1,14 @@ /dts-v1/; #include "skeleton.dtsi" +#include <dt-bindings/gpio/gpio.h> #include <dt-bindings/clock/qcom,gcc-msm8960.h> +#include <dt-bindings/reset/qcom,gcc-msm8960.h> #include <dt-bindings/clock/qcom,mmcc-msm8960.h> +#include <dt-bindings/mfd/qcom-rpm.h> #include <dt-bindings/soc/qcom,gsbi.h> #include <dt-bindings/interrupt-controller/arm-gic.h> - +#include <dt-bindings/gpio/gpio.h> / { model = "Qualcomm APQ8064"; compatible = "qcom,apq8064"; @@ -23,6 +26,10 @@ next-level-cache = <&L2>; qcom,acc = <&acc0>; qcom,saw = <&saw0>; + clocks = <&kraitcc 0>; + clock-names = "cpu"; + clock-latency = <100000>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; cpu@1 { @@ -33,6 +40,10 @@ next-level-cache = <&L2>; qcom,acc = <&acc1>; qcom,saw = <&saw1>; + clocks = <&kraitcc 1>; + clock-names = "cpu"; + clock-latency = <100000>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; cpu@2 { @@ -43,6 +54,10 @@ next-level-cache = <&L2>; qcom,acc = <&acc2>; qcom,saw = <&saw2>; + clocks = <&kraitcc 2>; + clock-names = "cpu"; + clock-latency = <100000>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; cpu@3 { @@ -53,12 +68,33 @@ next-level-cache = <&L2>; qcom,acc = <&acc3>; qcom,saw = <&saw3>; + clocks = <&kraitcc 3>; + clock-names = "cpu"; + clock-latency = <100000>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; L2: l2-cache { compatible = "cache"; cache-level = <2>; }; + + idle-states { + CPU_STBY: standby { + compatible = "qcom,idle-state-stby", "arm,idle-state"; + entry-latency-us = <1>; + exit-latency-us = <1>; + min-residency-us = <2>; + }; + + CPU_SPC: spc { + compatible = "qcom,idle-state-spc", "arm,idle-state"; + entry-latency-us = <400>; + exit-latency-us = <900>; + min-residency-us = <3000>; + }; + }; + }; cpu-pmu { @@ -66,6 +102,222 @@ interrupts = <1 10 0x304>; }; + qcom,pvs { + qcom,pvs-format-a; + qcom,speed0-pvs0-bin-v0 = + < 384000000 950000 >, + < 486000000 975000 >, + < 594000000 1000000 >, + < 702000000 1025000 >, + < 810000000 1075000 >, + < 918000000 1100000 >; + + qcom,speed0-pvs1-bin-v0 = + < 384000000 900000 >, + < 486000000 925000 >, + < 594000000 950000 >, + < 702000000 975000 >, + < 810000000 1025000 >, + < 918000000 1050000 >; + + qcom,speed0-pvs3-bin-v0 = + < 384000000 850000 >, + < 486000000 875000 >, + < 594000000 900000 >, + < 702000000 925000 >, + < 810000000 975000 >, + < 918000000 1000000 >; + + qcom,speed0-pvs4-bin-v0 = + < 384000000 850000 >, + < 486000000 875000 >, + < 594000000 900000 >, + < 702000000 925000 >, + < 810000000 962500 >, + < 918000000 975000 >; + + qcom,speed1-pvs0-bin-v0 = + < 384000000 950000 >, + < 486000000 950000 >, + < 594000000 950000 >, + < 702000000 962500 >, + < 810000000 1000000 >, + < 918000000 1025000 >; + + qcom,speed1-pvs1-bin-v0 = + < 384000000 950000 >, + < 486000000 950000 >, + < 594000000 950000 >, + < 702000000 962500 >, + < 810000000 975000 >, + < 918000000 1000000 >; + + qcom,speed1-pvs2-bin-v0 = + < 384000000 925000 >, + < 486000000 925000 >, + < 594000000 925000 >, + < 702000000 925000 >, + < 810000000 937500 >, + < 918000000 950000 >; + + qcom,speed1-pvs3-bin-v0 = + < 384000000 900000 >, + < 486000000 900000 >, + < 594000000 900000 >, + < 702000000 900000 >, + < 810000000 900000 >, + < 918000000 925000 >; + + qcom,speed1-pvs4-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + + qcom,speed1-pvs5-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + + qcom,speed1-pvs6-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + + qcom,speed2-pvs0-bin-v0 = + < 384000000 950000 >, + < 486000000 950000 >, + < 594000000 950000 >, + < 702000000 950000 >, + < 810000000 962500 >, + < 918000000 975000 >; + + qcom,speed2-pvs1-bin-v0 = + < 384000000 925000 >, + < 486000000 925000 >, + < 594000000 925000 >, + < 702000000 925000 >, + < 810000000 937500 >, + < 918000000 950000 >; + + qcom,speed2-pvs2-bin-v0 = + < 384000000 900000 >, + < 486000000 900000 >, + < 594000000 900000 >, + < 702000000 900000 >, + < 810000000 912500 >, + < 918000000 925000 >; + + qcom,speed2-pvs3-bin-v0 = + < 384000000 900000 >, + < 486000000 900000 >, + < 594000000 900000 >, + < 702000000 900000 >, + < 810000000 900000 >, + < 918000000 912500 >; + + qcom,speed2-pvs4-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + + qcom,speed2-pvs5-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + + qcom,speed2-pvs6-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + + qcom,speed14-pvs0-bin-v0 = + < 384000000 950000 >, + < 486000000 950000 >, + < 594000000 950000 >, + < 702000000 962500 >, + < 810000000 1000000 >, + < 918000000 1025000 >; + + qcom,speed14-pvs1-bin-v0 = + < 384000000 950000 >, + < 486000000 950000 >, + < 594000000 950000 >, + < 702000000 962500 >, + < 810000000 975000 >, + < 918000000 1000000 >; + + qcom,speed14-pvs2-bin-v0 = + < 384000000 925000 >, + < 486000000 925000 >, + < 594000000 925000 >, + < 702000000 925000 >, + < 810000000 937500 >, + < 918000000 950000 >; + + qcom,speed14-pvs3-bin-v0 = + < 384000000 900000 >, + < 486000000 900000 >, + < 594000000 900000 >, + < 702000000 900000 >, + < 810000000 900000 >, + < 918000000 925000 >; + + qcom,speed14-pvs4-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + + qcom,speed14-pvs5-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + + qcom,speed14-pvs6-bin-v0 = + < 384000000 875000 >, + < 486000000 875000 >, + < 594000000 875000 >, + < 702000000 875000 >, + < 810000000 887500 >, + < 918000000 900000 >; + }; + + kraitcc: clock-controller { + compatible = "qcom,krait-cc-v1"; + #clock-cells = <1>; + }; + + clocks { + sleep_clk: sleep_clk { + compatible = "fixed-clock"; + clock-frequency = <32768>; + #clock-cells = <0>; + }; + }; + soc: soc { #address-cells = <1>; #size-cells = <1>; @@ -92,6 +344,20 @@ }; }; + hdmi_pinctrl: hdmi-pinctrl { + mux1 { + pins = "gpio69", "gpio70", "gpio71"; + function = "hdmi"; + bias-pull-up; + drive-strength = <2>; + }; + mux2 { + pins = "gpio72"; + function = "hdmi"; + bias-pull-down; + drive-strength = <16>; + }; + }; ps_hold: ps_hold { mux { pins = "gpio78"; @@ -119,46 +385,63 @@ cpu-offset = <0x80000>; }; + watchdog@208a038 { + compatible = "qcom,kpss-wdt-apq8064"; + reg = <0x0208a038 0x40>; + clocks = <&sleep_clk>; + timeout-sec = <10>; + }; + acc0: clock-controller@2088000 { compatible = "qcom,kpss-acc-v1"; reg = <0x02088000 0x1000>, <0x02008000 0x1000>; + clock-output-names = "acpu0_aux"; }; acc1: clock-controller@2098000 { compatible = "qcom,kpss-acc-v1"; reg = <0x02098000 0x1000>, <0x02008000 0x1000>; + clock-output-names = "acpu1_aux"; }; acc2: clock-controller@20a8000 { compatible = "qcom,kpss-acc-v1"; reg = <0x020a8000 0x1000>, <0x02008000 0x1000>; + clock-output-names = "acpu2_aux"; }; acc3: clock-controller@20b8000 { compatible = "qcom,kpss-acc-v1"; reg = <0x020b8000 0x1000>, <0x02008000 0x1000>; + clock-output-names = "acpu3_aux"; + }; + + l2cc: clock-controller@2011000 { + compatible = "qcom,kpss-gcc"; + reg = <0x2011000 0x1000>; + clock-output-names = "acpu_l2_aux"; }; - saw0: regulator@2089000 { - compatible = "qcom,saw2"; + saw0: power-controller@2089000 { + compatible = "qcom,apq8064-saw2-v1.1-cpu", "qcom,saw2"; reg = <0x02089000 0x1000>, <0x02009000 0x1000>; regulator; }; - saw1: regulator@2099000 { - compatible = "qcom,saw2"; + saw1: power-controller@2099000 { + compatible = "qcom,apq8064-saw2-v1.1-cpu", "qcom,saw2"; reg = <0x02099000 0x1000>, <0x02009000 0x1000>; regulator; }; - saw2: regulator@20a9000 { - compatible = "qcom,saw2"; + saw2: power-controller@20a9000 { + compatible = "qcom,apq8064-saw2-v1.1-cpu", "qcom,saw2"; reg = <0x020a9000 0x1000>, <0x02009000 0x1000>; regulator; }; - saw3: regulator@20b9000 { - compatible = "qcom,saw2"; + saw3: power-controller@20b9000 { + compatible = "qcom,apq8064-saw2-v1.1-cpu", "qcom,saw2"; reg = <0x020b9000 0x1000>, <0x02009000 0x1000>; regulator; }; @@ -205,6 +488,27 @@ }; }; + gsbi6: gsbi@16500000 { + status = "disabled"; + compatible = "qcom,gsbi-v1.0.0"; + reg = <0x16500000 0x03>; + clocks = <&gcc GSBI6_H_CLK>; + clock-names = "iface"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + serial1: serial@16540000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x16540000 0x100>, + <0x16500000 0x03>; + interrupts = <0 156 0x0>; + clocks = <&gcc GSBI6_UART_CLK>, <&gcc GSBI6_H_CLK>; + clock-names = "core", "iface"; + status = "disabled"; + }; + }; + gsbi7: gsbi@16600000 { status = "disabled"; compatible = "qcom,gsbi-v1.0.0"; @@ -215,7 +519,7 @@ #size-cells = <1>; ranges; - serial@16640000 { + serial0: serial@16640000 { compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; reg = <0x16640000 0x1000>, <0x16600000 0x1000>; @@ -226,10 +530,61 @@ }; }; + ext_3p3v: regulator-fixed@1 { + compatible = "regulator-fixed"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-name = "ext_3p3v"; + regulator-type = "voltage"; + startup-delay-us = <0>; + gpio = <&tlmm_pinmux 77 GPIO_ACTIVE_HIGH>; + enable-active-high; + regulator-boot-on; + }; + qcom,ssbi@500000 { compatible = "qcom,ssbi"; reg = <0x00500000 0x1000>; qcom,controller-type = "pmic-arbiter"; + + pmicintc: pmic@0 { + compatible = "qcom,pm8921"; + interrupt-parent = <&tlmm_pinmux>; + interrupts = <74 8>; + #interrupt-cells = <2>; + interrupt-controller; + #address-cells = <1>; + #size-cells = <0>; + pm8921_gpio: gpio@150 { + + compatible = "qcom,pm8921-gpio"; + reg = <0x150>; + 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>; + + }; + }; + }; + qfprom: qfprom@00700000 { + compatible = "qcom,qfprom", "syscon"; + reg = <0x00700000 0x1000>; + stride = <1>; }; gcc: clock-controller@900000 { @@ -237,6 +592,19 @@ reg = <0x00900000 0x4000>; #clock-cells = <1>; #reset-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + /* tsens */ + tsens: tsens { + #thermal-sensor-cells = <1>; + compatible = "qcom,apq8064-tsens"; + qcom,qfprom = <&qfprom 0x404 0x10>, <&qfprom 0x414 0x10>; + qcom,qfprom-names = "calib", "backup_calib"; + interrupts = <0 178 0>; + interrupt-names = "tsens-ul"; + }; }; mmcc: clock-controller@4000000 { @@ -246,6 +614,278 @@ #reset-cells = <1>; }; + apcs: syscon@2011000 { + compatible = "syscon"; + reg = <0x2011000 0x1000>; + }; + + rpm@108000 { + compatible = "qcom,rpm-apq8064"; + 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>; + rpmcc: rpm-clock-controller { + compatible = "qcom,apq8064-rpm-clk"; + #clock-cells = <1>; + #reset-cells = <1>; + }; + + pm8921_s3: pm8921-s3 { + compatible = "qcom,rpm-pm8921-smps"; + reg = <QCOM_RPM_PM8921_SMPS3>; + + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1400000>; + qcom,boot-load = <49360>; + qcom,switch-mode-frequency = <3200000>; + regulator-always-on; + }; + + pm8921_s4: pm8921-s4 { + compatible = "qcom,rpm-pm8921-smps"; + reg = <QCOM_RPM_PM8921_SMPS4>; + + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + qcom,boot-load = <200000>; + qcom,switch-mode-frequency = <3200000>; + regulator-always-on; + }; + + pm8921_l2: pm8921-l2 { + compatible = "qcom,rpm-pm8921-pldo"; + reg = <QCOM_RPM_PM8921_LDO2>; + +// ??? + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; +// qcom,boot-load = <50000>; + }; + + pm8921_l3: pm8921-l3 { + compatible = "qcom,rpm-pm8921-pldo"; + reg = <QCOM_RPM_PM8921_LDO3>; + + regulator-min-microvolt = <3050000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + qcom,boot-load = <50000>; + }; + + pm8921_l4: pm8921-l4 { + compatible = "qcom,rpm-pm8921-pldo"; + reg = <QCOM_RPM_PM8921_LDO4>; + + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + qcom,boot-load = <50000>; + }; + + pm8921_l23: pm8921-l23 { + compatible = "qcom,rpm-pm8921-pldo"; + reg = <QCOM_RPM_PM8921_LDO23>; + + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1800000>; + qcom,boot-load = <50000>; + regulator-always-on; + }; + + + pm8921_lvs6: pm8921-lvs6 { + compatible = "qcom,rpm-pm8921-switch"; + reg = <QCOM_RPM_PM8921_LVS6>; + regulator-always-on; + }; + + pm8921_lvs7: pm8921-lvs7 { + compatible = "qcom,rpm-pm8921-switch"; + reg = <QCOM_RPM_PM8921_LVS7>; + regulator-always-on; + }; + + pm8921_hdmi_mvs: pm8921-hdmi-mvs { + compatible = "qcom,rpm-pm8921-switch"; + reg = <QCOM_RPM_HDMI_SWITCH>; + regulator-always-on; + bias-pull-down; + }; + + }; + + /* fabric clks handoff */ + fabclk-handoff{ + compatible = "qcom,apq8064-rpmcc-handoff"; + clocks = <&rpmcc QCOM_RPM_APPS_FABRIC_CLK>; + + assigned-clocks = <&rpmcc QCOM_RPM_APPS_FABRIC_CLK>; + assigned-clock-rates = <0x7fffffff>; + }; + + /* PCIE */ + + pci@1b500000 { + compatible = "qcom,pcie-ipq8064"; + reg = <0x1b500000 0x1000>, <0x1b502000 0x100>, <0x1b600000 0x80>; + reg-names = "base", "elbi", "parf"; + status = "disabled"; + #address-cells = <3>; + #size-cells = <2>; + device_type = "pci"; + interrupts = <0 35 0x0 + 0 36 0x0 + 0 37 0x0 + 0 38 0x0 + 0 39 0x0 + 0 238 0x0>; + interrupt-names = "irq1", "irq2", "irq3", "irq4", "iqr5", "msi"; + + resets = <&gcc PCIE_ACLK_RESET>, + <&gcc PCIE_HCLK_RESET>, + <&gcc PCIE_POR_RESET>, + <&gcc PCIE_PCI_RESET>, + <&gcc PCIE_PHY_RESET>; + reset-names = "axi", "ahb", "por", "pci", "phy"; + + clocks = <&gcc PCIE_A_CLK>, + <&gcc PCIE_H_CLK>, + <&gcc PCIE_PHY_REF_CLK>; + clock-names = "core", "iface", "phy"; + + ranges = <0x00000000 0 0 0x0ff00000 0 0x00100000 /* configuration space */ + 0x81000000 0 0 0x0fe00000 0 0x00100000 /* downstream I/O */ + 0x82000000 0 0 0x08000000 0 0x07e00000>; /* non-prefetchable memory */ + + }; + + + + usb1_phy:phy@12500000 { + compatible = "qcom,usb-otg-ci"; + reg = <0x12500000 0x400>; + interrupts = <0 100 0>; + status = "disabled"; + dr_mode = "host"; + + clocks = <&gcc USB_HS1_XCVR_CLK>, + <&gcc USB_HS1_H_CLK>; + clock-names = "core", "iface"; + + vddcx-supply = <&pm8921_s3>; + v3p3-supply = <&pm8921_l3>; + v1p8-supply = <&pm8921_l4>; + + resets = <&gcc USB_HS1_RESET>; + reset-names = "link"; + }; + + usb3_phy:phy@12520000 { + compatible = "qcom,usb-otg-ci"; + reg = <0x12520000 0x400>; + interrupts = <0 188 0>; + status = "disabled"; + dr_mode = "host"; + + clocks = <&gcc USB_HS3_XCVR_CLK>, + <&gcc USB_HS3_H_CLK>; + clock-names = "core", "iface"; + + vddcx-supply = <&pm8921_s3>; + v3p3-supply = <&pm8921_l3>; + v1p8-supply = <&pm8921_l23>; + + resets = <&gcc USB_HS3_RESET>; + reset-names = "link"; + }; + + usb4_phy:phy@12530000 { + compatible = "qcom,usb-otg-ci"; + reg = <0x12530000 0x400>; + interrupts = <0 215 0>; + status = "disabled"; + dr_mode = "host"; + + clocks = <&gcc USB_HS4_XCVR_CLK>, + <&gcc USB_HS4_H_CLK>; + clock-names = "core", "iface"; + + vddcx-supply = <&pm8921_s3>; + v3p3-supply = <&pm8921_l3>; + v1p8-supply = <&pm8921_l23>; + + resets = <&gcc USB_HS4_RESET>; + reset-names = "link"; + }; + + gadget1:gadget@12500000 { + compatible = "qcom,ci-hdrc"; + reg = <0x12500000 0x400>; + status = "disabled"; + dr_mode = "peripheral"; + interrupts = <0 100 0>; + usb-phy = <&usb1_phy>; + }; + + usb1: usb@12500000 { + compatible = "qcom,ehci-host"; + reg = <0x12500000 0x400>; + interrupts = <0 100 0>; + status = "disabled"; + usb-phy = <&usb1_phy>; + }; + + usb3: usb@12520000 { + compatible = "qcom,ehci-host"; + reg = <0x12520000 0x400>; + interrupts = <0 188 0>; + status = "disabled"; + usb-phy = <&usb3_phy>; + }; + + usb4: usb@12530000 { + compatible = "qcom,ehci-host"; + reg = <0x12530000 0x400>; + interrupts = <0 215 0>; + status = "disabled"; + usb-phy = <&usb4_phy>; + }; + + sata_phy0:sata-phy@1b400000{ + compatible = "qcom,apq8064-sata-phy"; + reg = <0x1b400000 0x200>; + reg-names = "phy_mem"; + clocks = <&gcc SATA_PHY_CFG_CLK>; + clock-names = "cfg"; + #phy-cells = <0>; + }; + + sata0: sata@29000000 { + compatible = "generic-ahci"; + reg = <0x29000000 0x180>; + interrupts = <0 209 0>; + clocks = <&gcc SFAB_SATA_S_H_CLK>, <&gcc SATA_H_CLK>, + <&gcc SATA_A_CLK>, <&gcc SATA_RXOOB_CLK>, + <&gcc SATA_PMALIVE_CLK>; + + clock-names = "slave_iface", "iface", + "bus", "rxoob", + "core_pmalive"; + assigned-clocks = <&gcc SATA_RXOOB_CLK>, + <&gcc SATA_PMALIVE_CLK>; + assigned-clock-rates = <100000000>, <100000000>; + + phys = <&sata_phy0>; + phy-names = "sata-phy"; + target-supply = <&pm8921_s4>; + }; + /* Temporary fixed regulator */ vsdcc_fixed: vsdcc-regulator { compatible = "regulator-fixed"; @@ -349,5 +989,182 @@ pinctrl-0 = <&sdc4_gpios>; }; }; + + hdmi: qcom,hdmi-tx@4a00000 { + compatible = "qcom,hdmi-tx-8960"; + reg-names = "core_physical"; + reg = <0x04a00000 0x1000>; + interrupts = <GIC_SPI 79 0>; + clock-names = + "core_clk", + "master_iface_clk", + "slave_iface_clk"; + clocks = + <&mmcc HDMI_APP_CLK>, + <&mmcc HDMI_M_AHB_CLK>, + <&mmcc HDMI_S_AHB_CLK>; + qcom,hdmi-tx-ddc-clk = <&tlmm_pinmux 70 GPIO_ACTIVE_HIGH>; + qcom,hdmi-tx-ddc-data = <&tlmm_pinmux 71 GPIO_ACTIVE_HIGH>; + qcom,hdmi-tx-hpd = <&tlmm_pinmux 72 GPIO_ACTIVE_HIGH>; + core-vdda-supply = <&pm8921_hdmi_mvs>; + hdmi-mux-supply = <&ext_3p3v>; + pinctrl-names = "default"; + pinctrl-0 = <&hdmi_pinctrl>; + }; + + gpu: qcom,adreno-3xx@4300000 { + compatible = "qcom,adreno-3xx"; + #stream-id-cells = <16>; + reg = <0x04300000 0x20000>; + reg-names = "kgsl_3d0_reg_memory"; + interrupts = <GIC_SPI 80 0>; + interrupt-names = "kgsl_3d0_irq"; + clock-names = + "core_clk", + "iface_clk", + "mem_clk", + "mem_iface_clk"; + clocks = + <&mmcc GFX3D_CLK>, + <&mmcc GFX3D_AHB_CLK>, + <&mmcc GFX3D_AXI_CLK>, + <&mmcc MMSS_IMEM_AHB_CLK>; + qcom,chipid = <0x03020002>; + qcom,gpu-pwrlevels { + compatible = "qcom,gpu-pwrlevels"; + qcom,gpu-pwrlevel@0 { + qcom,gpu-freq = <450000000>; + }; + qcom,gpu-pwrlevel@1 { + qcom,gpu-freq = <27000000>; + }; + }; + }; + + panel: auo,b101xtn01 { + compatible = "auo,b101xtn01"; + // lcd1_pwr_en_n: + enable-gpio = <&tlmm_pinmux 36 GPIO_ACTIVE_HIGH>; + }; + + mdp: qcom,mdp@5100000 { + compatible = "qcom,mdp"; + #stream-id-cells = <2>; + reg = <0x05100000 0xf0000>; + interrupts = <GIC_SPI 75 0>; + connectors = <&hdmi>; + gpus = <&gpu>; + qcom,lvds-panel = <&panel>; + clock-names = + "core_clk", + "iface_clk", + "lut_clk", + "src_clk", + "hdmi_clk", + "mdp_clk", + "mdp_axi_clk"; + clocks = + <&mmcc MDP_CLK>, + <&mmcc MDP_AHB_CLK>, + <&mmcc MDP_LUT_CLK>, + <&mmcc TV_SRC>, + <&mmcc HDMI_TV_CLK>, + <&mmcc MDP_TV_CLK>, + <&mmcc MDP_AXI_CLK>; +// vdd-supply = <&footswitch_mdp>; + lvds-vccs-3p3v-supply = <&ext_3p3v>; + lvds-pll-vdda-supply = <&pm8921_l2>; + lvds-vdda-supply = <&pm8921_lvs7>; + }; + + mdp_port0: qcom,iommu@7500000 { + compatible = "qcom,iommu-v0"; + clock-names = + "smmu_pclk", + "iommu_clk"; + clocks = + <&mmcc SMMU_AHB_CLK>, + <&mmcc MDP_AXI_CLK>; + reg-names = "physbase"; + reg = <0x07500000 0x100000>; + interrupt-names = + "secure_irq", + "nonsecure_irq"; + interrupts = + <GIC_SPI 63 0>, + <GIC_SPI 64 0>; + ncb = <2>; + mmu-masters = <&mdp 0 2>; + }; + + mdp_port1: qcom,iommu@7600000 { + compatible = "qcom,iommu"; + clock-names = + "smmu_pclk", + "iommu_clk"; + clocks = + <&mmcc SMMU_AHB_CLK>, + <&mmcc MDP_AXI_CLK>; + reg-names = "physbase"; + reg = <0x07600000 0x100000>; + interrupt-names = + "secure_irq", + "nonsecure_irq"; + interrupts = + <GIC_SPI 61 0>, + <GIC_SPI 62 0>; + ncb = <2>; + mmu-masters = <&mdp 0 2>; + }; + + gfx3d: qcom,iommu@7c00000 { + compatible = "qcom,iommu-v0"; + clock-names = + "smmu_pclk", + "iommu_clk"; + clocks = + <&mmcc SMMU_AHB_CLK>, + <&mmcc GFX3D_AXI_CLK>; + reg-names = "physbase"; + reg = <0x07c00000 0x100000>; + interrupt-names = + "secure_irq", + "nonsecure_irq"; + interrupts = + <GIC_SPI 69 0>, + <GIC_SPI 70 0>; + ncb = <3>; + ttbr-split = <1>; + mmu-masters = + /* gfx3d_user: */ + <&gpu 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15>, + /* gfx3d_priv: */ + <&gpu 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31>; + }; + + gfx3d1: qcom,iommu@7d00000 { + compatible = "qcom,iommu-v0"; + clock-names = + "smmu_pclk", + "iommu_clk"; + clocks = + <&mmcc SMMU_AHB_CLK>, + <&mmcc GFX3D_AXI_CLK>; + reg-names = "physbase"; + reg = <0x07d00000 0x100000>; + interrupt-names = + "secure_irq", + "nonsecure_irq"; + interrupts = + <GIC_SPI 210 0>, + <GIC_SPI 211 0>; + ncb = <3>; + ttbr-split = <1>; + mmu-masters = + /* gfx3d_user: */ + <&gpu 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15>, + /* gfx3d_priv: */ + <&gpu 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31>; + }; }; }; diff --git a/arch/arm/boot/dts/qcom-apq8084.dtsi b/arch/arm/boot/dts/qcom-apq8084.dtsi index 1f130bc16858..207be157b9a7 100644 --- a/arch/arm/boot/dts/qcom-apq8084.dtsi +++ b/arch/arm/boot/dts/qcom-apq8084.dtsi @@ -21,6 +21,8 @@ enable-method = "qcom,kpss-acc-v2"; next-level-cache = <&L2>; qcom,acc = <&acc0>; + qcom,saw = <&saw0>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; cpu@1 { @@ -30,6 +32,8 @@ enable-method = "qcom,kpss-acc-v2"; next-level-cache = <&L2>; qcom,acc = <&acc1>; + qcom,saw = <&saw1>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; cpu@2 { @@ -39,6 +43,8 @@ enable-method = "qcom,kpss-acc-v2"; next-level-cache = <&L2>; qcom,acc = <&acc2>; + qcom,saw = <&saw2>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; cpu@3 { @@ -48,6 +54,8 @@ enable-method = "qcom,kpss-acc-v2"; next-level-cache = <&L2>; qcom,acc = <&acc3>; + qcom,saw = <&saw3>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; L2: l2-cache { @@ -55,6 +63,22 @@ cache-level = <2>; qcom,saw = <&saw_l2>; }; + + idle-states { + CPU_STBY: standby { + compatible = "qcom,idle-state-stby", "arm,idle-state"; + entry-latency-us = <1>; + exit-latency-us = <1>; + min-residency-us = <2>; + }; + + CPU_SPC: spc { + compatible = "qcom,idle-state-spc", "arm,idle-state"; + entry-latency-us = <150>; + exit-latency-us = <200>; + min-residency-us = <2000>; + }; + }; }; cpu-pmu { @@ -144,7 +168,27 @@ }; }; - saw_l2: regulator@f9012000 { + saw0: power-controller@f9089000 { + compatible = "qcom,apq8084-saw2-v2.1-cpu"; + reg = <0xf9089000 0x1000>; + }; + + saw1: power-controller@f9099000 { + compatible = "qcom,apq8084-saw2-v2.1-cpu"; + reg = <0xf9099000 0x1000>; + }; + + saw2: power-controller@f90a9000 { + compatible = "qcom,apq8084-saw2-v2.1-cpu"; + reg = <0xf90a9000 0x1000>; + }; + + saw3: power-controller@f90b9000 { + compatible = "qcom,apq8084-saw2-v2.1-cpu"; + reg = <0xf90b9000 0x1000>; + }; + + saw_l2: power-controller@f9012000 { compatible = "qcom,saw2"; reg = <0xf9012000 0x1000>; regulator; diff --git a/arch/arm/boot/dts/qcom-msm8960.dtsi b/arch/arm/boot/dts/qcom-msm8960.dtsi index e1b0d5cd9e3c..ef89daf7a258 100644 --- a/arch/arm/boot/dts/qcom-msm8960.dtsi +++ b/arch/arm/boot/dts/qcom-msm8960.dtsi @@ -24,6 +24,10 @@ next-level-cache = <&L2>; qcom,acc = <&acc0>; qcom,saw = <&saw0>; + clocks = <&kraitcc 0>; + clock-names = "cpu"; + clock-latency = <100000>; + }; cpu@1 { @@ -34,6 +38,10 @@ next-level-cache = <&L2>; qcom,acc = <&acc1>; qcom,saw = <&saw1>; + clocks = <&kraitcc 1>; + clock-names = "cpu"; + clock-latency = <100000>; + }; L2: l2-cache { @@ -48,6 +56,39 @@ qcom,no-pc-write; }; + qcom,pvs { + qcom,pvs-format-a; + /* Hz uV */ + qcom,speed0-pvs0-bin-v0 = + < 384000000 950000 >, + < 486000000 975000 >, + < 594000000 1000000 >, + < 702000000 1025000 >, + < 810000000 1075000 >, + < 918000000 1100000 >; + + qcom,speed0-pvs1-bin-v0 = + < 384000000 900000 >, + < 486000000 925000 >, + < 594000000 950000 >, + < 702000000 975000 >, + < 810000000 1025000 >, + < 918000000 1050000 >; + + qcom,speed0-pvs3-bin-v0 = + < 384000000 850000 >, + < 486000000 875000 >, + < 594000000 900000 >, + < 702000000 925000 >, + < 810000000 975000 >, + < 918000000 1000000 >; + }; + + kraitcc: clock-controller { + compatible = "qcom,krait-cc-v1"; + #clock-cells = <1>; + }; + soc: soc { #address-cells = <1>; #size-cells = <1>; @@ -101,11 +142,19 @@ acc0: clock-controller@2088000 { compatible = "qcom,kpss-acc-v1"; reg = <0x02088000 0x1000>, <0x02008000 0x1000>; + clock-output-names = "acpu0_aux"; }; acc1: clock-controller@2098000 { compatible = "qcom,kpss-acc-v1"; reg = <0x02098000 0x1000>, <0x02008000 0x1000>; + clock-output-names = "acpu1_aux"; + }; + + l2cc: clock-controller@2011000 { + compatible = "qcom,kpss-gcc"; + reg = <0x2011000 0x1000>; + clock-output-names = "acpu_l2_aux"; }; saw0: regulator@2089000 { diff --git a/arch/arm/boot/dts/qcom-msm8974.dtsi b/arch/arm/boot/dts/qcom-msm8974.dtsi index e265ec16a787..819a6f407e70 100644 --- a/arch/arm/boot/dts/qcom-msm8974.dtsi +++ b/arch/arm/boot/dts/qcom-msm8974.dtsi @@ -14,40 +14,60 @@ #size-cells = <0>; interrupts = <1 9 0xf04>; - cpu@0 { + cpu0: cpu@0 { compatible = "qcom,krait"; enable-method = "qcom,kpss-acc-v2"; device_type = "cpu"; reg = <0>; next-level-cache = <&L2>; qcom,acc = <&acc0>; + clocks = <&kraitcc 0>; + clock-names = "cpu"; + clock-latency = <100000>; + qcom,saw = <&saw0>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; - cpu@1 { + cpu1: cpu@1 { compatible = "qcom,krait"; enable-method = "qcom,kpss-acc-v2"; device_type = "cpu"; reg = <1>; next-level-cache = <&L2>; qcom,acc = <&acc1>; + clocks = <&kraitcc 1>; + clock-names = "cpu"; + clock-latency = <100000>; + qcom,saw = <&saw1>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; - cpu@2 { + cpu2: cpu@2 { compatible = "qcom,krait"; enable-method = "qcom,kpss-acc-v2"; device_type = "cpu"; reg = <2>; next-level-cache = <&L2>; qcom,acc = <&acc2>; + clocks = <&kraitcc 2>; + clock-names = "cpu"; + clock-latency = <100000>; + qcom,saw = <&saw2>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; - cpu@3 { + cpu3: cpu@3 { compatible = "qcom,krait"; enable-method = "qcom,kpss-acc-v2"; device_type = "cpu"; reg = <3>; next-level-cache = <&L2>; qcom,acc = <&acc3>; + clocks = <&kraitcc 3>; + clock-names = "cpu"; + clock-latency = <100000>; + qcom,saw = <&saw3>; + cpu-idle-states = <&CPU_STBY &CPU_SPC>; }; L2: l2-cache { @@ -55,6 +75,22 @@ cache-level = <2>; qcom,saw = <&saw_l2>; }; + + idle-states { + CPU_STBY: standby { + compatible = "qcom,idle-state-stby", "arm,idle-state"; + entry-latency-us = <1>; + exit-latency-us = <1>; + min-residency-us = <2>; + }; + + CPU_SPC: spc { + compatible = "qcom,idle-state-spc", "arm,idle-state"; + entry-latency-us = <150>; + exit-latency-us = <200>; + min-residency-us = <2000>; + }; + }; }; cpu-pmu { @@ -71,6 +107,267 @@ clock-frequency = <19200000>; }; + qcom,pvs { + qcom,pvs-format-b; + /* Hz uV ua */ + qcom,speed0-pvs0-bin-v0 = + < 300000000 815000 73 >, + < 345600000 825000 85 >, + < 422400000 835000 104 >, + < 499200000 845000 124 >, + < 576000000 855000 144 >, + < 652800000 865000 165 >, + < 729600000 875000 186 >, + < 806400000 890000 208 >, + < 883200000 900000 229 >, + < 960000000 915000 252 >; + + qcom,speed0-pvs1-bin-v0 = + < 300000000 800000 73 >, + < 345600000 810000 85 >, + < 422400000 820000 104 >, + < 499200000 830000 124 >, + < 576000000 840000 144 >, + < 652800000 850000 165 >, + < 729600000 860000 186 >, + < 806400000 875000 208 >, + < 883200000 885000 229 >, + < 960000000 895000 252 >; + + qcom,speed0-pvs2-bin-v0 = + < 300000000 785000 73 >, + < 345600000 795000 85 >, + < 422400000 805000 104 >, + < 499200000 815000 124 >, + < 576000000 825000 144 >, + < 652800000 835000 165 >, + < 729600000 845000 186 >, + < 806400000 855000 208 >, + < 883200000 865000 229 >, + < 960000000 875000 252 >; + + qcom,speed0-pvs3-bin-v0 = + < 300000000 775000 73 >, + < 345600000 780000 85 >, + < 422400000 790000 104 >, + < 499200000 800000 124 >, + < 576000000 810000 144 >, + < 652800000 820000 165 >, + < 729600000 830000 186 >, + < 806400000 840000 208 >, + < 883200000 850000 229 >, + < 960000000 860000 252 >; + + qcom,speed0-pvs4-bin-v0 = + < 300000000 775000 73 >, + < 345600000 775000 85 >, + < 422400000 780000 104 >, + < 499200000 790000 124 >, + < 576000000 800000 144 >, + < 652800000 810000 165 >, + < 729600000 820000 186 >, + < 806400000 830000 208 >, + < 883200000 840000 229 >, + < 960000000 850000 252 >; + + qcom,speed0-pvs5-bin-v0 = + < 300000000 750000 73 >, + < 345600000 760000 85 >, + < 422400000 770000 104 >, + < 499200000 780000 124 >, + < 576000000 790000 144 >, + < 652800000 800000 165 >, + < 729600000 810000 186 >, + < 806400000 820000 208 >, + < 883200000 830000 229 >, + < 960000000 840000 252 >; + + qcom,speed0-pvs6-bin-v0 = + < 300000000 750000 73 >, + < 345600000 750000 85 >, + < 422400000 760000 104 >, + < 499200000 770000 124 >, + < 576000000 780000 144 >, + < 652800000 790000 165 >, + < 729600000 800000 186 >, + < 806400000 810000 208 >, + < 883200000 820000 229 >, + < 960000000 830000 252 >; + + qcom,speed2-pvs0-bin-v0 = + < 300000000 800000 72 >, + < 345600000 800000 83 >, + < 422400000 805000 102 >, + < 499200000 815000 121 >, + < 576000000 825000 141 >, + < 652800000 835000 161 >, + < 729600000 845000 181 >, + < 806400000 855000 202 >, + < 883200000 865000 223 >, + < 960000000 875000 245 >; + + qcom,speed2-pvs1-bin-v0 = + < 300000000 800000 72 >, + < 345600000 800000 83 >, + < 422400000 800000 102 >, + < 499200000 800000 121 >, + < 576000000 810000 141 >, + < 652800000 820000 161 >, + < 729600000 830000 181 >, + < 806400000 840000 202 >, + < 883200000 850000 223 >, + < 960000000 860000 245 >; + + qcom,speed2-pvs2-bin-v0 = + < 300000000 775000 72 >, + < 345600000 775000 83 >, + < 422400000 775000 102 >, + < 499200000 785000 121 >, + < 576000000 795000 141 >, + < 652800000 805000 161 >, + < 729600000 815000 181 >, + < 806400000 825000 202 >, + < 883200000 835000 223 >, + < 960000000 845000 245 >; + + qcom,speed2-pvs3-bin-v0 = + < 300000000 775000 72 >, + < 345600000 775000 83 >, + < 422400000 775000 102 >, + < 499200000 775000 121 >, + < 576000000 780000 141 >, + < 652800000 790000 161 >, + < 729600000 800000 181 >, + < 806400000 810000 202 >, + < 883200000 820000 223 >, + < 960000000 830000 245 >; + + qcom,speed2-pvs4-bin-v0 = + < 300000000 775000 72 >, + < 345600000 775000 83 >, + < 422400000 775000 102 >, + < 499200000 775000 121 >, + < 576000000 775000 141 >, + < 652800000 780000 161 >, + < 729600000 790000 181 >, + < 806400000 800000 202 >, + < 883200000 810000 223 >, + < 960000000 820000 245 >; + + qcom,speed2-pvs5-bin-v0 = + < 300000000 750000 72 >, + < 345600000 750000 83 >, + < 422400000 750000 102 >, + < 499200000 750000 121 >, + < 576000000 760000 141 >, + < 652800000 770000 161 >, + < 729600000 780000 181 >, + < 806400000 790000 202 >, + < 883200000 800000 223 >, + < 960000000 810000 245 >; + + qcom,speed2-pvs6-bin-v0 = + < 300000000 750000 72 >, + < 345600000 750000 83 >, + < 422400000 750000 102 >, + < 499200000 750000 121 >, + < 576000000 750000 141 >, + < 652800000 760000 161 >, + < 729600000 770000 181 >, + < 806400000 780000 202 >, + < 883200000 790000 223 >, + < 960000000 800000 245 >; + + qcom,speed1-pvs0-bin-v0 = + < 300000000 775000 72 >, + < 345600000 775000 83 >, + < 422400000 775000 101 >, + < 499200000 780000 120 >, + < 576000000 790000 139 >, + < 652800000 800000 159 >, + < 729600000 810000 180 >, + < 806400000 820000 200 >, + < 883200000 830000 221 >, + < 960000000 840000 242 >; + + qcom,speed1-pvs1-bin-v0 = + < 300000000 775000 72 >, + < 345600000 775000 83 >, + < 422400000 775000 101 >, + < 499200000 775000 120 >, + < 576000000 775000 139 >, + < 652800000 785000 159 >, + < 729600000 795000 180 >, + < 806400000 805000 200 >, + < 883200000 815000 221 >, + < 960000000 825000 242 >; + + qcom,speed1-pvs2-bin-v0 = + < 300000000 750000 72 >, + < 345600000 750000 83 >, + < 422400000 750000 101 >, + < 499200000 750000 120 >, + < 576000000 760000 139 >, + < 652800000 770000 159 >, + < 729600000 780000 180 >, + < 806400000 790000 200 >, + < 883200000 800000 221 >, + < 960000000 810000 242 >; + + qcom,speed1-pvs3-bin-v0 = + < 300000000 750000 72 >, + < 345600000 750000 83 >, + < 422400000 750000 101 >, + < 499200000 750000 120 >, + < 576000000 750000 139 >, + < 652800000 755000 159 >, + < 729600000 765000 180 >, + < 806400000 775000 200 >, + < 883200000 785000 221 >, + < 960000000 795000 242 >; + + qcom,speed1-pvs4-bin-v0 = + < 300000000 750000 72 >, + < 345600000 750000 83 >, + < 422400000 750000 101 >, + < 499200000 750000 120 >, + < 576000000 750000 139 >, + < 652800000 750000 159 >, + < 729600000 755000 180 >, + < 806400000 765000 200 >, + < 883200000 775000 221 >, + < 960000000 785000 242 >; + + qcom,speed1-pvs5-bin-v0 = + < 300000000 725000 72 >, + < 345600000 725000 83 >, + < 422400000 725000 101 >, + < 499200000 725000 120 >, + < 576000000 725000 139 >, + < 652800000 735000 159 >, + < 729600000 745000 180 >, + < 806400000 755000 200 >, + < 883200000 765000 221 >, + < 960000000 775000 242 >; + + qcom,speed1-pvs6-bin-v0 = + < 300000000 725000 72 >, + < 345600000 725000 83 >, + < 422400000 725000 101 >, + < 499200000 725000 120 >, + < 576000000 725000 139 >, + < 652800000 725000 159 >, + < 729600000 735000 180 >, + < 806400000 745000 200 >, + < 883200000 755000 221 >, + < 960000000 765000 242 >; + }; + + kraitcc: clock-controller { + compatible = "qcom,krait-cc-v2"; + #clock-cells = <1>; + }; + soc: soc { #address-cells = <1>; #size-cells = <1>; @@ -144,7 +441,57 @@ }; }; - saw_l2: regulator@f9012000 { + clock-controller@f9016000 { + compatible = "qcom,hfpll"; + reg = <0xf9016000 0x30>; + clock-output-names = "hfpll_l2"; + }; + + clock-controller@f908a000 { + compatible = "qcom,hfpll"; + reg = <0xf908a000 0x30>, <0xf900a000 0x30>; + clock-output-names = "hfpll0"; + }; + + clock-controller@f909a000 { + compatible = "qcom,hfpll"; + reg = <0xf909a000 0x30>, <0xf900a000 0x30>; + clock-output-names = "hfpll1"; + }; + + clock-controller@f90aa000 { + compatible = "qcom,hfpll"; + reg = <0xf90aa000 0x30>, <0xf900a000 0x30>; + clock-output-names = "hfpll2"; + }; + + clock-controller@f90ba000 { + compatible = "qcom,hfpll"; + reg = <0xf90ba000 0x30>, <0xf900a000 0x30>; + clock-output-names = "hfpll3"; + }; + + saw0: power-controller@f9089000 { + compatible = "qcom,msm8974-saw2-v2.1-cpu"; + reg = <0xf9089000 0x1000>; + }; + + saw1: power-controller@f9099000 { + compatible = "qcom,msm8974-saw2-v2.1-cpu"; + reg = <0xf9099000 0x1000>; + }; + + saw2: power-controller@f90a9000 { + compatible = "qcom,msm8974-saw2-v2.1-cpu"; + reg = <0xf90a9000 0x1000>; + }; + + saw3: power-controller@f90b9000 { + compatible = "qcom,msm8974-saw2-v2.1-cpu"; + reg = <0xf90b9000 0x1000>; + }; + + saw_l2: power-controller@f9012000 { compatible = "qcom,saw2"; reg = <0xf9012000 0x1000>; regulator; 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/iop32x_defconfig b/arch/arm/configs/iop32x_defconfig index 4f2ec3ac138e..c3058da631da 100644 --- a/arch/arm/configs/iop32x_defconfig +++ b/arch/arm/configs/iop32x_defconfig @@ -106,6 +106,7 @@ CONFIG_MAGIC_SYSRQ=y CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_USER=y CONFIG_DEBUG_LL=y +CONFIG_DEBUG_LL_UART_8250=y CONFIG_KEYS=y CONFIG_KEYS_DEBUG_PROC_KEYS=y CONFIG_CRYPTO_NULL=y diff --git a/arch/arm/configs/iop33x_defconfig b/arch/arm/configs/iop33x_defconfig index aa36128abca2..713faeee8cf4 100644 --- a/arch/arm/configs/iop33x_defconfig +++ b/arch/arm/configs/iop33x_defconfig @@ -87,5 +87,6 @@ CONFIG_DEBUG_KERNEL=y # CONFIG_RCU_CPU_STALL_DETECTOR is not set CONFIG_DEBUG_USER=y CONFIG_DEBUG_LL=y +CONFIG_DEBUG_LL_UART_8250=y # CONFIG_CRYPTO_ANSI_CPRNG is not set # CONFIG_CRC32 is not set diff --git a/arch/arm/configs/ixp4xx_defconfig b/arch/arm/configs/ixp4xx_defconfig index 1af665e847d1..24636cfdf6df 100644 --- a/arch/arm/configs/ixp4xx_defconfig +++ b/arch/arm/configs/ixp4xx_defconfig @@ -202,3 +202,4 @@ CONFIG_MAGIC_SYSRQ=y CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_ERRORS=y CONFIG_DEBUG_LL=y +CONFIG_DEBUG_LL_UART_8250=y diff --git a/arch/arm/configs/lpc32xx_defconfig b/arch/arm/configs/lpc32xx_defconfig index 9f56ca3985ae..c100b7df5441 100644 --- a/arch/arm/configs/lpc32xx_defconfig +++ b/arch/arm/configs/lpc32xx_defconfig @@ -204,6 +204,7 @@ CONFIG_DEBUG_INFO=y # CONFIG_FTRACE is not set # CONFIG_ARM_UNWIND is not set CONFIG_DEBUG_LL=y +CONFIG_DEBUG_LL_UART_8250=y CONFIG_EARLY_PRINTK=y CONFIG_CRYPTO_ANSI_CPRNG=y # CONFIG_CRYPTO_HW is not set diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig index bc393b7e5ece..61962a6850cc 100644 --- a/arch/arm/configs/multi_v7_defconfig +++ b/arch/arm/configs/multi_v7_defconfig @@ -20,9 +20,9 @@ CONFIG_MACH_ARMADA_XP=y CONFIG_MACH_DOVE=y CONFIG_ARCH_BCM=y CONFIG_ARCH_BCM_CYGNUS=y -CONFIG_ARCH_BCM_21664=y -CONFIG_ARCH_BCM_281XX=y CONFIG_ARCH_BCM_5301X=y +CONFIG_ARCH_BCM_281XX=y +CONFIG_ARCH_BCM_21664=y CONFIG_ARCH_BRCMSTB=y CONFIG_ARCH_BERLIN=y CONFIG_MACH_BERLIN_BG2=y @@ -31,8 +31,8 @@ CONFIG_MACH_BERLIN_BG2Q=y CONFIG_ARCH_HIGHBANK=y CONFIG_ARCH_HISI=y CONFIG_ARCH_HI3xxx=y -CONFIG_ARCH_HIX5HD2=y CONFIG_ARCH_HIP04=y +CONFIG_ARCH_HIX5HD2=y CONFIG_ARCH_KEYSTONE=y CONFIG_ARCH_MESON=y CONFIG_ARCH_MXC=y @@ -41,6 +41,7 @@ CONFIG_SOC_IMX53=y CONFIG_SOC_IMX6Q=y CONFIG_SOC_IMX6SL=y CONFIG_SOC_VF610=y +CONFIG_ARCH_MEDIATEK=y CONFIG_ARCH_OMAP3=y CONFIG_ARCH_OMAP4=y CONFIG_SOC_OMAP5=y @@ -48,7 +49,6 @@ CONFIG_SOC_AM33XX=y CONFIG_SOC_AM43XX=y CONFIG_SOC_DRA7XX=y CONFIG_ARCH_QCOM=y -CONFIG_ARCH_MEDIATEK=y CONFIG_ARCH_MSM8X60=y CONFIG_ARCH_MSM8960=y CONFIG_ARCH_MSM8974=y @@ -67,16 +67,12 @@ CONFIG_ARCH_TEGRA_2x_SOC=y CONFIG_ARCH_TEGRA_3x_SOC=y CONFIG_ARCH_TEGRA_114_SOC=y CONFIG_ARCH_TEGRA_124_SOC=y -CONFIG_TEGRA_EMC_SCALING_ENABLE=y CONFIG_ARCH_U8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y -CONFIG_MACH_UX500_DT=y CONFIG_ARCH_VEXPRESS=y -CONFIG_ARCH_VEXPRESS_CA9X4=y CONFIG_ARCH_WM8850=y CONFIG_ARCH_ZYNQ=y -CONFIG_TRUSTED_FOUNDATIONS=y CONFIG_PCI=y CONFIG_PCI_MSI=y CONFIG_PCI_MVEBU=y @@ -92,9 +88,14 @@ 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_DT=y +CONFIG_ARM_QCOM_CPUFREQ=y CONFIG_CPU_IDLE=y -CONFIG_NEON=y CONFIG_ARM_ZYNQ_CPUIDLE=y +CONFIG_ARM_QCOM_CPUIDLE=y CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y @@ -112,13 +113,21 @@ CONFIG_IPV6_MIP6=m CONFIG_IPV6_TUNNEL=m CONFIG_IPV6_MULTIPLE_TABLES=y CONFIG_CAN=y -CONFIG_CAN_RAW=y -CONFIG_CAN_BCM=y -CONFIG_CAN_DEV=y CONFIG_CAN_XILINXCAN=y CONFIG_CAN_MCP251X=y -CONFIG_CFG80211=m -CONFIG_MAC80211=m +CONFIG_BT=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=y +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_ATH3K=y +CONFIG_CFG80211=y +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=y CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y CONFIG_RFKILL_GPIO=y @@ -144,7 +153,6 @@ CONFIG_EEPROM_AT24=y CONFIG_EEPROM_SUNXI_SID=y CONFIG_BLK_DEV_SD=y CONFIG_BLK_DEV_SR=y -CONFIG_SCSI_MULTI_LUN=y CONFIG_ATA=y CONFIG_SATA_AHCI=y CONFIG_SATA_AHCI_PLATFORM=y @@ -154,10 +162,11 @@ CONFIG_AHCI_TEGRA=y CONFIG_SATA_HIGHBANK=y CONFIG_SATA_MV=y CONFIG_NETDEVICES=y -CONFIG_HIX5HD2_GMAC=y CONFIG_SUN4I_EMAC=y +CONFIG_ATL1C=y CONFIG_MACB=y CONFIG_NET_CALXEDA_XGMAC=y +CONFIG_HIX5HD2_GMAC=y CONFIG_IGB=y CONFIG_MV643XX_ETH=y CONFIG_MVNETA=y @@ -175,6 +184,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 @@ -182,8 +195,8 @@ CONFIG_INPUT_JOYDEV=y CONFIG_INPUT_EVDEV=y CONFIG_KEYBOARD_GPIO=y CONFIG_KEYBOARD_TEGRA=y -CONFIG_KEYBOARD_SPEAR=y CONFIG_KEYBOARD_ST_KEYSCAN=y +CONFIG_KEYBOARD_SPEAR=y CONFIG_KEYBOARD_CROS_EC=y CONFIG_MOUSE_PS2_ELANTECH=y CONFIG_INPUT_TOUCHSCREEN=y @@ -220,19 +233,17 @@ CONFIG_SERIAL_FSL_LPUART_CONSOLE=y CONFIG_SERIAL_ST_ASC=y CONFIG_SERIAL_ST_ASC_CONSOLE=y CONFIG_I2C_CHARDEV=y -CONFIG_I2C_MUX=y CONFIG_I2C_MUX_PCA954x=y CONFIG_I2C_MUX_PINCTRL=y CONFIG_I2C_CADENCE=y CONFIG_I2C_DESIGNWARE_PLATFORM=y -CONFIG_I2C_EXYNOS5=y CONFIG_I2C_MV64XXX=y CONFIG_I2C_S3C2410=y CONFIG_I2C_SIRF=y -CONFIG_I2C_TEGRA=y CONFIG_I2C_ST=y -CONFIG_SPI=y +CONFIG_I2C_TEGRA=y CONFIG_I2C_XILINX=y +CONFIG_SPI=y CONFIG_SPI_CADENCE=y CONFIG_SPI_OMAP24XX=y CONFIG_SPI_ORION=y @@ -245,11 +256,15 @@ CONFIG_SPI_TEGRA20_SFLASH=y CONFIG_SPI_TEGRA20_SLINK=y CONFIG_SPI_XILINX=y CONFIG_SPI_SPIDEV=y +CONFIG_SPMI=y CONFIG_PINCTRL_AS3722=y CONFIG_PINCTRL_PALMAS=y +CONFIG_PINCTRL_APQ8064=y CONFIG_PINCTRL_APQ8084=y +CONFIG_PINCTRL_IPQ8064=y +CONFIG_PINCTRL_QCOM_SPMI_PMIC=y +CONFIG_PINCTRL_SSBI_PMIC=y CONFIG_GPIO_SYSFS=y -CONFIG_GPIO_GENERIC_PLATFORM=y CONFIG_GPIO_DWAPB=y CONFIG_GPIO_XILINX=y CONFIG_GPIO_ZYNQ=y @@ -264,6 +279,7 @@ CONFIG_CHARGER_TPS65090=y CONFIG_POWER_RESET_AS3722=y CONFIG_POWER_RESET_GPIO=y CONFIG_POWER_RESET_SUN6I=y +CONFIG_POWER_RESET_SYSCON=y CONFIG_SENSORS_LM90=y CONFIG_SENSORS_LM95245=y CONFIG_THERMAL=y @@ -282,6 +298,8 @@ CONFIG_MFD_CROS_EC=y CONFIG_MFD_CROS_EC_SPI=y CONFIG_MFD_MAX77686=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 @@ -292,11 +310,10 @@ CONFIG_REGULATOR_AB8500=y CONFIG_REGULATOR_AS3722=y CONFIG_REGULATOR_BCM590XX=y CONFIG_REGULATOR_GPIO=y -CONFIG_MFD_SYSCON=y -CONFIG_POWER_RESET_SYSCON=y CONFIG_REGULATOR_MAX8907=y CONFIG_REGULATOR_MAX77686=y CONFIG_REGULATOR_PALMAS=y +CONFIG_REGULATOR_QCOM_RPM=y CONFIG_REGULATOR_S2MPS11=y CONFIG_REGULATOR_S5M8767=y CONFIG_REGULATOR_TPS51632=y @@ -317,15 +334,11 @@ CONFIG_DRM_PANEL_SIMPLE=y CONFIG_FB_ARMCLCD=y CONFIG_FB_WM8505=y CONFIG_FB_SIMPLE=y -CONFIG_BACKLIGHT_LCD_SUPPORT=y -CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_BACKLIGHT_PWM=y CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y CONFIG_SOUND=y CONFIG_SND=y -CONFIG_SND_DYNAMIC_MINORS=y -CONFIG_SND_USB_AUDIO=y CONFIG_SND_SOC=y CONFIG_SND_SOC_TEGRA=y CONFIG_SND_SOC_TEGRA_RT5640=y @@ -338,26 +351,23 @@ CONFIG_USB=y CONFIG_USB_XHCI_HCD=y CONFIG_USB_XHCI_MVEBU=y CONFIG_USB_EHCI_HCD=y -CONFIG_USB_EHCI_EXYNOS=y -CONFIG_USB_EHCI_TEGRA=y CONFIG_USB_EHCI_HCD_STI=y -CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_EHCI_MSM=y +CONFIG_USB_EHCI_TEGRA=y +CONFIG_USB_EHCI_EXYNOS=y CONFIG_USB_ISP1760_HCD=y CONFIG_USB_OHCI_HCD=y CONFIG_USB_OHCI_HCD_STI=y -CONFIG_USB_OHCI_HCD_PLATFORM=y CONFIG_USB_STORAGE=y CONFIG_USB_CHIPIDEA=y CONFIG_USB_CHIPIDEA_HOST=y CONFIG_AB8500_USB=y -CONFIG_OMAP_USB3=y -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 +CONFIG_MMC_BLOCK_MINORS=32 CONFIG_MMC_ARMMMCI=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_PLTFM=y @@ -365,21 +375,21 @@ CONFIG_MMC_SDHCI_OF_ARASAN=y CONFIG_MMC_SDHCI_ESDHC_IMX=y CONFIG_MMC_SDHCI_DOVE=y CONFIG_MMC_SDHCI_TEGRA=y +CONFIG_MMC_SDHCI_S3C=y CONFIG_MMC_SDHCI_PXAV3=y 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_ST=y CONFIG_MMC_OMAP=y CONFIG_MMC_OMAP_HS=y +CONFIG_MMC_SDHCI_MSM=y CONFIG_MMC_MVSDIO=y -CONFIG_MMC_SUNXI=y CONFIG_MMC_DW=y CONFIG_MMC_DW_IDMAC=y -CONFIG_MMC_DW_PLTFM=y CONFIG_MMC_DW_EXYNOS=y CONFIG_MMC_DW_ROCKCHIP=y +CONFIG_MMC_SUNXI=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_LEDS_GPIO=y @@ -427,6 +437,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 @@ -436,27 +447,32 @@ 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_COMMON_CLK_MAX77686=y CONFIG_APQ_MMCC_8084=y CONFIG_MSM_GCC_8660=y CONFIG_MSM_MMCC_8960=y CONFIG_MSM_MMCC_8974=y +CONFIG_QCOM_RPMCC=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 CONFIG_IIO=y CONFIG_XILINX_XADC=y CONFIG_AK8975=y CONFIG_PWM=y CONFIG_PWM_TEGRA=y CONFIG_PWM_VT8500=y -CONFIG_PHY_HIX5HD2_SATA=y +CONFIG_PHY_MIPHY365X=y CONFIG_OMAP_USB2=y CONFIG_TI_PIPE3=y -CONFIG_PHY_MIPHY365X=y -CONFIG_PHY_STIH41X_USB=y +CONFIG_PHY_HIX5HD2_SATA=y CONFIG_PHY_SUN4I_USB=y +CONFIG_PHY_QCOM_APQ8064_SATA=y +CONFIG_PHY_STIH41X_USB=y CONFIG_EXT4_FS=y CONFIG_AUTOFS4_FS=y CONFIG_MSDOS_FS=y @@ -464,7 +480,6 @@ CONFIG_VFAT_FS=y CONFIG_NTFS_FS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_UBIFS_FS=y -CONFIG_TMPFS=y CONFIG_SQUASHFS=y CONFIG_SQUASHFS_LZO=y CONFIG_SQUASHFS_XZ=y @@ -476,8 +491,9 @@ CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y CONFIG_NLS_UTF8=y CONFIG_PRINTK_TIME=y -CONFIG_DEBUG_FS=y CONFIG_MAGIC_SYSRQ=y CONFIG_LOCKUP_DETECTOR=y -CONFIG_CRYPTO_DEV_TEGRA_AES=y -CONFIG_CPUFREQ_DT=y +CONFIG_FUNCTION_TRACER=y +CONFIG_SCHED_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +CONFIG_TRACEPOINT_BENCHMARK=y diff --git a/arch/arm/configs/mv78xx0_defconfig b/arch/arm/configs/mv78xx0_defconfig index 0dae1c1f007a..85d10d2e3d66 100644 --- a/arch/arm/configs/mv78xx0_defconfig +++ b/arch/arm/configs/mv78xx0_defconfig @@ -132,6 +132,7 @@ CONFIG_SYSCTL_SYSCALL_CHECK=y CONFIG_DEBUG_USER=y CONFIG_DEBUG_ERRORS=y CONFIG_DEBUG_LL=y +CONFIG_DEBUG_LL_UART_8250=y CONFIG_CRYPTO_CBC=m CONFIG_CRYPTO_ECB=m CONFIG_CRYPTO_PCBC=m diff --git a/arch/arm/configs/orion5x_defconfig b/arch/arm/configs/orion5x_defconfig index 952430d9e2d9..855143fac6bd 100644 --- a/arch/arm/configs/orion5x_defconfig +++ b/arch/arm/configs/orion5x_defconfig @@ -156,6 +156,7 @@ CONFIG_LATENCYTOP=y # CONFIG_FTRACE is not set CONFIG_DEBUG_USER=y CONFIG_DEBUG_LL=y +CONFIG_DEBUG_LL_UART_8250=y CONFIG_CRYPTO_CBC=m CONFIG_CRYPTO_ECB=m CONFIG_CRYPTO_PCBC=m diff --git a/arch/arm/configs/qcom_defconfig b/arch/arm/configs/qcom_defconfig index 8c7da3319d82..4ca12df69ed5 100644 --- a/arch/arm/configs/qcom_defconfig +++ b/arch/arm/configs/qcom_defconfig @@ -1,8 +1,10 @@ CONFIG_SYSVIPC=y +CONFIG_FHANDLE=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_IKCONFIG=y CONFIG_IKCONFIG_PROC=y +CONFIG_CGROUPS=y CONFIG_BLK_DEV_INITRD=y CONFIG_SYSCTL_SYSCALL=y CONFIG_KALLSYMS_ALL=y @@ -21,18 +23,35 @@ 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 CONFIG_HIGHMEM=y CONFIG_HIGHPTE=y CONFIG_CLEANCACHE=y +CONFIG_CMA=y +CONFIG_SECCOMP=y CONFIG_ARM_APPENDED_DTB=y CONFIG_ARM_ATAG_DTB_COMPAT=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_DT=y +CONFIG_ARM_QCOM_CPUFREQ=y CONFIG_CPU_IDLE=y +CONFIG_ARM_QCOM_CPUIDLE=y CONFIG_VFP=y CONFIG_NEON=y # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_PM_RUNTIME=y CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y @@ -47,25 +66,43 @@ CONFIG_IP_PNP_DHCP=y # CONFIG_INET_XFRM_MODE_BEET is not set # CONFIG_INET_LRO is not set # CONFIG_IPV6 is not set +CONFIG_BT=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=y +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_BT_HCIUART=y +CONFIG_BT_HCIUART_H4=y +CONFIG_BT_HCIUART_ATH3K=y CONFIG_CFG80211=y +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=y CONFIG_RFKILL=y CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS_MOUNT=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=64 CONFIG_MTD=y CONFIG_MTD_BLOCK=y CONFIG_MTD_M25P80=y CONFIG_MTD_SPI_NOR=y CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_RAM=y -CONFIG_SCSI=y +CONFIG_EEPROM_AT24=y CONFIG_BLK_DEV_SD=y CONFIG_CHR_DEV_SG=y 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_KS8851=y CONFIG_MDIO_BITBANG=y CONFIG_MDIO_GPIO=y CONFIG_SLIP=y @@ -74,6 +111,9 @@ CONFIG_SLIP_MODE_SLIP6=y CONFIG_USB_USBNET=y # CONFIG_USB_NET_AX8817X is not set # CONFIG_USB_NET_ZAURUS is not set +CONFIG_ATH_CARDS=m +CONFIG_ATH6KL=m +CONFIG_ATH6KL_SDIO=m CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set # CONFIG_MOUSE_PS2 is not set @@ -86,7 +126,6 @@ CONFIG_SERIO_LIBPS2=y CONFIG_SERIAL_MSM=y CONFIG_SERIAL_MSM_CONSOLE=y CONFIG_HW_RANDOM=y -CONFIG_I2C=y CONFIG_I2C_CHARDEV=y CONFIG_I2C_QUP=y CONFIG_SPI=y @@ -97,16 +136,83 @@ CONFIG_PINCTRL_APQ8084=y CONFIG_PINCTRL_IPQ8064=y CONFIG_PINCTRL_MSM8960=y CONFIG_PINCTRL_MSM8X74=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_THERMAL_TSENS8960=y +CONFIG_WATCHDOG=y +CONFIG_QCOM_WDT=y +CONFIG_MFD_PM8921_CORE=y +CONFIG_MFD_QCOM_RPM=y +CONFIG_MFD_SPMI_PMIC=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_MEDIA_CAMERA_SUPPORT=y +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_VIDEO_CLASS=y +CONFIG_USB_GSPCA=y +CONFIG_USB_M5602=m +CONFIG_USB_STV06XX=m +CONFIG_USB_GL860=m +CONFIG_USB_GSPCA_BENQ=m +CONFIG_USB_GSPCA_CONEX=m +CONFIG_USB_GSPCA_CPIA1=m +CONFIG_USB_GSPCA_DTCS033=m +CONFIG_USB_GSPCA_ETOMS=m +CONFIG_USB_GSPCA_FINEPIX=m +CONFIG_USB_GSPCA_JEILINJ=m +CONFIG_USB_GSPCA_JL2005BCD=m +CONFIG_USB_GSPCA_KINECT=m +CONFIG_USB_GSPCA_KONICA=m +CONFIG_USB_GSPCA_MARS=m +CONFIG_USB_GSPCA_MR97310A=m +CONFIG_USB_GSPCA_NW80X=m +CONFIG_USB_GSPCA_OV519=m +CONFIG_USB_GSPCA_OV534=m +CONFIG_USB_GSPCA_OV534_9=m +CONFIG_USB_GSPCA_PAC207=m +CONFIG_USB_GSPCA_PAC7302=m +CONFIG_USB_GSPCA_PAC7311=m +CONFIG_USB_GSPCA_SE401=m +CONFIG_USB_GSPCA_SN9C2028=m +CONFIG_USB_GSPCA_SN9C20X=m +CONFIG_USB_GSPCA_SONIXB=m +CONFIG_USB_GSPCA_SONIXJ=m +CONFIG_USB_GSPCA_SPCA500=m +CONFIG_USB_GSPCA_SPCA501=m +CONFIG_USB_GSPCA_SPCA505=m +CONFIG_USB_GSPCA_SPCA506=m +CONFIG_USB_GSPCA_SPCA508=m +CONFIG_USB_GSPCA_SPCA561=m +CONFIG_USB_GSPCA_SPCA1528=m +CONFIG_USB_GSPCA_SQ905=m +CONFIG_USB_GSPCA_SQ905C=m +CONFIG_USB_GSPCA_SQ930X=m +CONFIG_USB_GSPCA_STK014=m +CONFIG_USB_GSPCA_STK1135=m +CONFIG_USB_GSPCA_STV0680=m +CONFIG_USB_GSPCA_SUNPLUS=m +CONFIG_USB_GSPCA_T613=m +CONFIG_USB_GSPCA_TOPRO=m +CONFIG_USB_GSPCA_TV8532=m +CONFIG_USB_GSPCA_VC032X=m +CONFIG_USB_GSPCA_VICAM=m +CONFIG_USB_GSPCA_XIRLINK_CIT=m +CONFIG_USB_GSPCA_ZC3XX=m +CONFIG_USB_PWC=m +CONFIG_USB_ZR364XX=m +CONFIG_DRM=y +CONFIG_DRM_PANEL_SIMPLE=y +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_BACKLIGHT_CLASS_DEVICE=y +CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_DYNAMIC_MINORS=y @@ -115,17 +221,34 @@ CONFIG_SND_DYNAMIC_MINORS=y # CONFIG_SND_USB is not set CONFIG_SND_SOC=y CONFIG_HID_BATTERY_STRENGTH=y +CONFIG_HID_APPLE=y +CONFIG_HID_LOGITECH=m +CONFIG_HID_MAGICMOUSE=m +CONFIG_HID_MICROSOFT=m CONFIG_USB=y CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_OTG_FSM=y CONFIG_USB_MON=y CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_MSM=y CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +CONFIG_USB_CHIPIDEA=y +CONFIG_USB_CHIPIDEA_UDC=y +CONFIG_USB_CHIPIDEA_HOST=y CONFIG_USB_SERIAL=y +CONFIG_USB_MSM_OTG=y CONFIG_USB_GADGET=y CONFIG_USB_GADGET_DEBUG_FILES=y CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_ETH=m +CONFIG_USB_GADGETFS=m +CONFIG_USB_FUNCTIONFS=m +CONFIG_USB_FUNCTIONFS_RNDIS=y +CONFIG_USB_MASS_STORAGE=m CONFIG_MMC=y -CONFIG_MMC_BLOCK_MINORS=16 +CONFIG_MMC_BLOCK_MINORS=32 CONFIG_MMC_ARMMMCI=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_PLTFM=y @@ -135,13 +258,18 @@ CONFIG_DMADEVICES=y CONFIG_QCOM_BAM_DMA=y CONFIG_STAGING=y CONFIG_QCOM_GSBI=y +CONFIG_QCOM_PM=y CONFIG_COMMON_CLK_QCOM=y CONFIG_APQ_MMCC_8084=y CONFIG_IPQ_GCC_806X=y CONFIG_MSM_GCC_8660=y CONFIG_MSM_MMCC_8960=y CONFIG_MSM_MMCC_8974=y -CONFIG_MSM_IOMMU=y +CONFIG_QCOM_HFPLL=y +CONFIG_KPSS_XCC=y +CONFIG_KRAITCC=y +CONFIG_QCOM_RPMCC=y +CONFIG_QCOM_IOMMU_V0=y CONFIG_PHY_QCOM_APQ8064_SATA=y CONFIG_PHY_QCOM_IPQ806X_SATA=y CONFIG_EXT2_FS=y @@ -149,13 +277,19 @@ CONFIG_EXT2_FS_XATTR=y CONFIG_EXT3_FS=y # CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set CONFIG_EXT4_FS=y +CONFIG_FANOTIFY=y +CONFIG_AUTOFS4_FS=y CONFIG_FUSE_FS=y CONFIG_VFAT_FS=y -CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y CONFIG_JFFS2_FS=y CONFIG_NFS_FS=y CONFIG_NFS_V3_ACL=y CONFIG_NFS_V4=y +CONFIG_ROOT_NFS=y +CONFIG_NFSD=m +CONFIG_NFSD_V3=y +CONFIG_NFSD_V3_ACL=y CONFIG_CIFS=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ASCII=y @@ -167,5 +301,5 @@ CONFIG_DEBUG_INFO=y CONFIG_MAGIC_SYSRQ=y CONFIG_LOCKUP_DETECTOR=y # CONFIG_DETECT_HUNG_TASK is not set -# CONFIG_SCHED_DEBUG is not set +CONFIG_SCHEDSTATS=y CONFIG_TIMER_STATS=y diff --git a/arch/arm/configs/rpc_defconfig b/arch/arm/configs/rpc_defconfig index 00515ef9782d..89631795a915 100644 --- a/arch/arm/configs/rpc_defconfig +++ b/arch/arm/configs/rpc_defconfig @@ -131,3 +131,4 @@ CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_USER=y CONFIG_DEBUG_ERRORS=y CONFIG_DEBUG_LL=y +CONFIG_DEBUG_LL_UART_8250=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/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..81e1b2af70f7 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 \rv, =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/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/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/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c index c0a842b335c5..8dfe39086ee2 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) ((1 << (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; + unsigned int div; - val = clk_readl(divider->reg) >> divider->shift; - val &= div_mask(divider); - - 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,19 +284,11 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate, if (!rate) rate = 1; - /* if read only, just return current value */ - if (divider->flags & CLK_DIVIDER_READ_ONLY) { - bestdiv = readl(divider->reg) >> divider->shift; - bestdiv &= div_mask(divider); - bestdiv = _get_div(divider, bestdiv); - return bestdiv; - } - - 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; @@ -287,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) { /* @@ -302,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; @@ -310,48 +323,79 @@ 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); + int bestdiv; + + /* if read only, just return current value */ + if (divider->flags & CLK_DIVIDER_READ_ONLY) { + bestdiv = readl(divider->reg) >> divider->shift; + bestdiv &= div_mask(divider->width); + bestdiv = _get_div(divider->table, bestdiv, divider->flags); + return bestdiv; + } + + 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); + + return min_t(unsigned int, value, div_mask(width)); +} +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 6e1ecf94bf58..d3a7d1f941ce 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) + if (table) { + val = table[val]; + } else { + if (flags & CLK_MUX_INDEX_BIT) index = 1 << index; - 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 d48ac71c6c8b..d939e7092b0c 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -343,13 +343,9 @@ unlock: static void clk_debug_unregister(struct clk *clk) { mutex_lock(&clk_debug_lock); - if (!clk->dentry) - goto out; - hlist_del_init(&clk->debug_node); debugfs_remove_recursive(clk->dentry); clk->dentry = NULL; -out: mutex_unlock(&clk_debug_lock); } @@ -694,14 +690,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_hw **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_hw **best_parent_p, + unsigned long flags) { struct clk *clk = hw->clk, *parent, *best_parent = NULL; int i, num_parents; @@ -729,7 +731,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; } @@ -742,8 +744,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_hw **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_hw **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) @@ -1323,6 +1348,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; @@ -1332,6 +1358,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); @@ -1417,14 +1454,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; } @@ -1451,23 +1516,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) { @@ -1485,10 +1553,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 @@ -1498,12 +1563,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); } /** @@ -1531,6 +1597,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; @@ -1564,9 +1631,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..29d6a8992179 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -29,6 +29,15 @@ config IPQ_GCC_806X Say Y if you want to use peripheral devices such as UART, SPI, i2c, USB, SD/eMMC, etc. +config IPQ_LCC_806X + tristate "IPQ806x LPASS Clock Controller" + select IPQ_GCC_806X + depends on COMMON_CLK_QCOM + help + Support for the LPASS clock controller on ipq806x devices. + Say Y if you want to use audio devices such as i2s, pcm, + S/PDIF, etc. + config MSM_GCC_8660 tristate "MSM8660 Global Clock Controller" depends on COMMON_CLK_QCOM @@ -45,6 +54,15 @@ config MSM_GCC_8960 Say Y if you want to use peripheral devices such as UART, SPI, i2c, USB, SD/eMMC, SATA, PCIe, etc. +config MSM_LCC_8960 + tristate "APQ8064/MSM8960 LPASS Clock Controller" + select MSM_GCC_8960 + depends on COMMON_CLK_QCOM + help + Support for the LPASS clock controller on apq8064/msm8960 devices. + Say Y if you want to use audio devices such as i2s, pcm, + SLIMBus, etc. + config MSM_MMCC_8960 tristate "MSM8960 Multimedia Clock Controller" select MSM_GCC_8960 @@ -70,3 +88,39 @@ 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 + +config QCOM_RPMCC + tristate "Qualcomm RPM Clock Controller" + depends on COMMON_CLK_QCOM + help + Support for the RPM clock controller which controls fabric clocks + on SoCs like APQ8064. + Say Y if you want to use fabric clocks like AFAB, DAYTONA etc. diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 783cfb24faa4..c3ad2f6992a2 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -6,13 +6,23 @@ 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 += clk-regmap-divider.o +clk-qcom-y += clk-regmap-mux.o clk-qcom-y += reset.o obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o obj-$(CONFIG_APQ_MMCC_8084) += mmcc-apq8084.o obj-$(CONFIG_IPQ_GCC_806X) += gcc-ipq806x.o +obj-$(CONFIG_IPQ_LCC_806X) += lcc-ipq806x.o obj-$(CONFIG_MSM_GCC_8660) += gcc-msm8660.o obj-$(CONFIG_MSM_GCC_8960) += gcc-msm8960.o +obj-$(CONFIG_MSM_LCC_8960) += lcc-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 +obj-$(CONFIG_QCOM_RPMCC) += clk-rpm.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/clk-regmap-divider.c b/drivers/clk/qcom/clk-regmap-divider.c new file mode 100644 index 000000000000..53484912301e --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-divider.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 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. + */ + +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/regmap.h> +#include <linux/export.h> + +#include "clk-regmap-divider.h" + +static inline struct clk_regmap_div *to_clk_regmap_div(struct clk_hw *hw) +{ + return container_of(to_clk_regmap(hw), struct clk_regmap_div, clkr); +} + +static long div_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct clk_regmap_div *divider = to_clk_regmap_div(hw); + + return divider_round_rate(hw, rate, prate, NULL, divider->width, + CLK_DIVIDER_ROUND_CLOSEST); +} + +static int div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_regmap_div *divider = to_clk_regmap_div(hw); + struct clk_regmap *clkr = ÷r->clkr; + u32 div; + + div = divider_get_val(rate, parent_rate, NULL, divider->width, + CLK_DIVIDER_ROUND_CLOSEST); + + return regmap_update_bits(clkr->regmap, divider->reg, + (BIT(divider->width) - 1) << divider->shift, + div << divider->shift); +} + +static unsigned long div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_regmap_div *divider = to_clk_regmap_div(hw); + struct clk_regmap *clkr = ÷r->clkr; + u32 div; + + regmap_read(clkr->regmap, divider->reg, &div); + div >>= divider->shift; + div &= BIT(divider->width) - 1; + + return divider_recalc_rate(hw, parent_rate, div, NULL, + CLK_DIVIDER_ROUND_CLOSEST); +} + +const struct clk_ops clk_regmap_div_ops = { + .round_rate = div_round_rate, + .set_rate = div_set_rate, + .recalc_rate = div_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_regmap_div_ops); diff --git a/drivers/clk/qcom/clk-regmap-divider.h b/drivers/clk/qcom/clk-regmap-divider.h new file mode 100644 index 000000000000..fc4492e3a827 --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-divider.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 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_CLK_REGMAP_DIVIDER_H__ +#define __QCOM_CLK_REGMAP_DIVIDER_H__ + +#include <linux/clk-provider.h> +#include "clk-regmap.h" + +struct clk_regmap_div { + u32 reg; + u32 shift; + u32 width; + struct clk_regmap clkr; +}; + +extern const struct clk_ops clk_regmap_div_ops; + +#endif diff --git a/drivers/clk/qcom/clk-regmap-mux.c b/drivers/clk/qcom/clk-regmap-mux.c new file mode 100644 index 000000000000..cae3071f384c --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-mux.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 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. + */ + +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/regmap.h> +#include <linux/export.h> + +#include "clk-regmap-mux.h" + +static inline struct clk_regmap_mux *to_clk_regmap_mux(struct clk_hw *hw) +{ + return container_of(to_clk_regmap(hw), struct clk_regmap_mux, clkr); +} + +static u8 mux_get_parent(struct clk_hw *hw) +{ + struct clk_regmap_mux *mux = to_clk_regmap_mux(hw); + struct clk_regmap *clkr = to_clk_regmap(hw); + unsigned int mask = GENMASK(mux->width - 1, 0); + unsigned int val; + + regmap_read(clkr->regmap, mux->reg, &val); + + val >>= mux->shift; + val &= mask; + + return val; +} + +static int mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_regmap_mux *mux = to_clk_regmap_mux(hw); + struct clk_regmap *clkr = to_clk_regmap(hw); + unsigned int mask = GENMASK(mux->width + mux->shift - 1, mux->shift); + unsigned int val; + + val = index; + val <<= mux->shift; + + return regmap_update_bits(clkr->regmap, mux->reg, mask, val); +} + +const struct clk_ops clk_regmap_mux_closest_ops = { + .get_parent = mux_get_parent, + .set_parent = mux_set_parent, + .determine_rate = __clk_mux_determine_rate_closest, +}; +EXPORT_SYMBOL_GPL(clk_regmap_mux_closest_ops); diff --git a/drivers/clk/qcom/clk-regmap-mux.h b/drivers/clk/qcom/clk-regmap-mux.h new file mode 100644 index 000000000000..5cec76154fda --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-mux.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 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_CLK_REGMAP_MUX_H__ +#define __QCOM_CLK_REGMAP_MUX_H__ + +#include <linux/clk-provider.h> +#include "clk-regmap.h" + +struct clk_regmap_mux { + u32 reg; + u32 shift; + u32 width; + struct clk_regmap clkr; +}; + +extern const struct clk_ops clk_regmap_mux_closest_ops; + +#endif diff --git a/drivers/clk/qcom/clk-rpm.c b/drivers/clk/qcom/clk-rpm.c new file mode 100644 index 000000000000..c9107e3723db --- /dev/null +++ b/drivers/clk/qcom/clk-rpm.c @@ -0,0 +1,373 @@ +#include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk-provider.h> +#include <linux/mfd/qcom_rpm.h> +#include <dt-bindings/mfd/qcom-rpm.h> + +struct rpm_cc { + struct clk_onecell_data data; + struct clk *clks[]; +}; +struct rpm_clk { + const int rpm_clk_id; + struct qcom_rpm *rpm; + unsigned last_set_khz; + bool enabled; + bool branch; /* true: RPM only accepts 1 for ON and 0 for OFF */ + unsigned factor; + struct clk_hw hw; +}; + +#define to_rpm_clk(_hw) container_of(_hw, struct rpm_clk, hw) + +static int rpm_clk_prepare(struct clk_hw *hw) +{ + struct rpm_clk *r = to_rpm_clk(hw); + uint32_t value; + int rc = 0; + unsigned long this_khz; + + this_khz = r->last_set_khz; + /* Don't send requests to the RPM if the rate has not been set. */ + if (r->last_set_khz == 0) + goto out; + + value = this_khz; + if (r->branch) + value = !!value; + + rc = qcom_rpm_write(r->rpm, QCOM_RPM_ACTIVE_STATE, + r->rpm_clk_id, &value, 1); + if (rc) + goto out; + +out: + if (!rc) + r->enabled = true; + return rc; +} + +static void rpm_clk_unprepare(struct clk_hw *hw) +{ + struct rpm_clk *r = to_rpm_clk(hw); + + if (r->last_set_khz) { + uint32_t value = 0; + int rc; + + rc = qcom_rpm_write(r->rpm, QCOM_RPM_ACTIVE_STATE, + r->rpm_clk_id, &value, 1); + if (rc) + return; + + } + r->enabled = false; +} + +int rpm_clk_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long prate) +{ + struct rpm_clk *r = to_rpm_clk(hw); + unsigned long this_khz; + int rc = 0; + + this_khz = DIV_ROUND_UP(rate, r->factor); + + if (r->enabled) { + uint32_t value = this_khz; + + rc = qcom_rpm_write(r->rpm, QCOM_RPM_ACTIVE_STATE, + r->rpm_clk_id, &value, 1); + if (rc) + goto out; + } + + if (!rc) + r->last_set_khz = this_khz; + +out: + return rc; +} + +static long rpm_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return rate; +} + +static unsigned long rpm_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rpm_clk *r = to_rpm_clk(hw); + u32 val; + int rc; + + rc = qcom_rpm_read(r->rpm, r->rpm_clk_id, &val, 1); + if (rc < 0) + return 0; + + return val * r->factor; +} + +static unsigned long rpm_branch_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rpm_clk *r = to_rpm_clk(hw); + + return r->last_set_khz * r->factor; +} + +static const struct clk_ops branch_clk_ops_rpm = { + .prepare = rpm_clk_prepare, + .unprepare = rpm_clk_unprepare, + .recalc_rate = rpm_branch_clk_recalc_rate, + .round_rate = rpm_clk_round_rate, +}; + +static const struct clk_ops clk_ops_rpm = { + .prepare = rpm_clk_prepare, + .unprepare = rpm_clk_unprepare, + .set_rate = rpm_clk_set_rate, + .recalc_rate = rpm_clk_recalc_rate, + .round_rate = rpm_clk_round_rate, +}; + +static struct rpm_clk pxo_clk = { + .rpm_clk_id = QCOM_RPM_PXO_CLK, + .branch = true, + .factor = 1000, + .last_set_khz = 27000, + .hw.init = &(struct clk_init_data){ + .name = "pxo", + .ops = &branch_clk_ops_rpm, + }, +}; + +static struct rpm_clk cxo_clk = { + .rpm_clk_id = QCOM_RPM_CXO_CLK, + .branch = true, + .factor = 1000, + .last_set_khz = 19200, + .hw.init = &(struct clk_init_data){ + .name = "cxo", + .ops = &branch_clk_ops_rpm, + }, +}; + +static struct rpm_clk afab_clk = { + .rpm_clk_id = QCOM_RPM_APPS_FABRIC_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "afab_clk", + .ops = &clk_ops_rpm, + }, +}; + +static struct rpm_clk cfpb_clk = { + .rpm_clk_id = QCOM_RPM_CFPB_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "cfpb_clk", + .ops = &clk_ops_rpm, + }, +}; + +static struct rpm_clk daytona_clk = { + .rpm_clk_id = QCOM_RPM_DAYTONA_FABRIC_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "daytona_clk", + .ops = &clk_ops_rpm, + }, +}; + +static struct rpm_clk ebi1_clk = { + .rpm_clk_id = QCOM_RPM_EBI1_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "ebi1_clk", + .ops = &clk_ops_rpm, + }, +}; + +static struct rpm_clk mmfab_clk = { + .rpm_clk_id = QCOM_RPM_MM_FABRIC_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "mmfab_clk", + .ops = &clk_ops_rpm, + }, +}; + +static struct rpm_clk mmfpb_clk = { + .rpm_clk_id = QCOM_RPM_MMFPB_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "mmfpb_clk", + .ops = &clk_ops_rpm, + }, +}; + +static struct rpm_clk sfab_clk = { + .rpm_clk_id = QCOM_RPM_SYS_FABRIC_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "sfab_clk", + .ops = &clk_ops_rpm, + }, +}; + +static struct rpm_clk sfpb_clk = { + .rpm_clk_id = QCOM_RPM_SFPB_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "sfpb_clk", + .ops = &clk_ops_rpm, + }, +}; + +/* +static struct rpm_clk qdss_clk = { + .rpm_clk_id = QCOM_RPM_QDSS_CLK, + .factor = 1000, + .hw.init = &(struct clk_init_data){ + .name = "qdss_clk", + .ops = &clk_ops_rpm, + }, +}; +*/ + +struct rpm_clk *rpm_clks[] = { + [QCOM_RPM_PXO_CLK] = &pxo_clk, + [QCOM_RPM_CXO_CLK] = &cxo_clk, + [QCOM_RPM_APPS_FABRIC_CLK] = &afab_clk, + [QCOM_RPM_CFPB_CLK] = &cfpb_clk, + [QCOM_RPM_DAYTONA_FABRIC_CLK] = &daytona_clk, + [QCOM_RPM_EBI1_CLK] = &ebi1_clk, + [QCOM_RPM_MM_FABRIC_CLK] = &mmfab_clk, + [QCOM_RPM_MMFPB_CLK] = &mmfpb_clk, + [QCOM_RPM_SYS_FABRIC_CLK] = &sfab_clk, + [QCOM_RPM_SFPB_CLK] = &sfpb_clk, +/** [QCOM_RPM_QDSS_CLK] = &qdss_clk, Needs more checking here **/ +}; + +static int rpm_clk_probe(struct platform_device *pdev) +{ + struct clk **clks; + struct clk *clk; + struct rpm_cc *cc; + struct qcom_rpm *rpm; + int num_clks = ARRAY_SIZE(rpm_clks); + struct clk_onecell_data *data; + int i; + + if (!pdev->dev.of_node) + return -ENODEV; + + cc = devm_kzalloc(&pdev->dev, sizeof(*cc) + sizeof(*clks) * num_clks, + GFP_KERNEL); + if (!cc) + return -ENOMEM; + + clks = cc->clks; + data = &cc->data; + data->clks = clks; + data->clk_num = num_clks; + + rpm = dev_get_drvdata(pdev->dev.parent); + if (!rpm) { + dev_err(&pdev->dev, "unable to retrieve handle to rpm\n"); + return -ENODEV; + } + + for (i = 0; i < num_clks; i++) { + if (!rpm_clks[i]) { + clks[i] = ERR_PTR(-ENOENT); + continue; + } + rpm_clks[i]->rpm = rpm; + clk = devm_clk_register(&pdev->dev, &rpm_clks[i]->hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + clks[i] = clk; + } + + return of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, + data); +} + +static int rpm_clk_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + return 0; +} + +static const struct of_device_id rpm_clk_of_match[] = { + { .compatible = "qcom,apq8064-rpm-clk" }, + { }, +}; + +static struct platform_driver rpm_clk_driver = { + .driver = { + .name = "qcom-rpm-clk", + .of_match_table = rpm_clk_of_match, + }, + .probe = rpm_clk_probe, + .remove = rpm_clk_remove, +}; + +/* dummy handoff clk handling... */ +static int rpm_clk_ho_probe(struct platform_device *pdev) +{ + int i, max_clks; + struct clk *clk; + max_clks = of_count_phandle_with_args(pdev->dev.of_node, + "clocks", "#clock-cells"); + + for (i = 0; i < max_clks; i++) { + clk = of_clk_get(pdev->dev.of_node, i); + + if (IS_ERR(clk)) + break; + + clk_prepare_enable(clk); + } + return 0; +} + +static const struct of_device_id rpm_clk_ho_of_match[] = { + { .compatible = "qcom,apq8064-rpmcc-handoff" }, + { }, +}; + +static struct platform_driver rpm_clk_ho_driver = { + .driver = { + .name = "qcom-rpm-clk-handoff", + .of_match_table = rpm_clk_ho_of_match, + }, + .probe = rpm_clk_ho_probe, +}; + +static int __init rpm_clk_init(void) +{ + platform_driver_register(&rpm_clk_driver); + return platform_driver_register(&rpm_clk_ho_driver); +} +subsys_initcall(rpm_clk_init); + +static void __exit rpm_clk_exit(void) +{ + platform_driver_unregister(&rpm_clk_ho_driver); + platform_driver_unregister(&rpm_clk_driver); +} +module_exit(rpm_clk_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org>"); +MODULE_DESCRIPTION("Driver for the RPM clocks"); diff --git a/drivers/clk/qcom/gcc-ipq806x.c b/drivers/clk/qcom/gcc-ipq806x.c index afed5eb0691e..5a03e91f1edc 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 = { @@ -75,6 +76,17 @@ static struct clk_pll pll3 = { }, }; +static struct clk_regmap pll4_vote = { + .enable_reg = 0x34c0, + .enable_mask = BIT(4), + .hw.init = &(struct clk_init_data){ + .name = "pll4_vote", + .parent_names = (const char *[]){ "pll4" }, + .num_parents = 1, + .ops = &clk_pll_vote_ops, + }, +}; + static struct clk_pll pll8 = { .l_reg = 0x3144, .m_reg = 0x3148, @@ -102,6 +114,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, @@ -2163,6 +2254,7 @@ static struct clk_regmap *gcc_ipq806x_clks[] = { [PLL0] = &pll0.clkr, [PLL0_VOTE] = &pll0_vote, [PLL3] = &pll3.clkr, + [PLL4_VOTE] = &pll4_vote, [PLL8] = &pll8.clkr, [PLL8_VOTE] = &pll8_vote, [PLL14] = &pll14.clkr, @@ -2261,6 +2353,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 b0b562b9ce0e..69eb7fba681d 100644 --- a/drivers/clk/qcom/gcc-msm8960.c +++ b/drivers/clk/qcom/gcc-msm8960.c @@ -15,6 +15,7 @@ #include <linux/bitops.h> #include <linux/err.h> #include <linux/platform_device.h> +#include <linux/of_platform.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> @@ -30,6 +31,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 +77,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 +3300,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 +3513,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[] = { @@ -3488,24 +3656,19 @@ MODULE_DEVICE_TABLE(of, gcc_msm8960_match_table); static int gcc_msm8960_probe(struct platform_device *pdev) { - struct clk *clk; - struct device *dev = &pdev->dev; const struct of_device_id *match; match = of_match_device(gcc_msm8960_match_table, &pdev->dev); if (!match) return -EINVAL; - /* Temporary until RPM clocks supported */ - clk = clk_register_fixed_rate(dev, "cxo", NULL, CLK_IS_ROOT, 19200000); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - clk = clk_register_fixed_rate(dev, "pxo", NULL, CLK_IS_ROOT, 27000000); - if (IS_ERR(clk)) - return PTR_ERR(clk); + if (match->data == &gcc_apq8064_desc) { + hfpll1.d = &hfpll1_8064_data; + hfpll_l2.d = &hfpll_l2_8064_data; + } - return qcom_cc_probe(pdev, match->data); + qcom_cc_probe(pdev, match->data); + return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); } static int gcc_msm8960_remove(struct platform_device *pdev) 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/clk/qcom/lcc-ipq806x.c b/drivers/clk/qcom/lcc-ipq806x.c new file mode 100644 index 000000000000..121ffde25dc3 --- /dev/null +++ b/drivers/clk/qcom/lcc-ipq806x.c @@ -0,0 +1,473 @@ +/* + * Copyright (c) 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. + */ + +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk-provider.h> +#include <linux/regmap.h> + +#include <dt-bindings/clock/qcom,lcc-ipq806x.h> + +#include "common.h" +#include "clk-regmap.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-branch.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" + +static struct clk_pll pll4 = { + .l_reg = 0x4, + .m_reg = 0x8, + .n_reg = 0xc, + .config_reg = 0x14, + .mode_reg = 0x0, + .status_reg = 0x18, + .status_bit = 16, + .clkr.hw.init = &(struct clk_init_data){ + .name = "pll4", + .parent_names = (const char *[]){ "pxo" }, + .num_parents = 1, + .ops = &clk_pll_ops, + }, +}; + +static const struct pll_config pll4_config = { + .l = 0xf, + .m = 0x91, + .n = 0xc7, + .vco_val = 0x0, + .vco_mask = BIT(17) | BIT(16), + .pre_div_val = 0x0, + .pre_div_mask = BIT(19), + .post_div_val = 0x0, + .post_div_mask = BIT(21) | BIT(20), + .mn_ena_mask = BIT(22), + .main_output_mask = BIT(23), +}; + +#define P_PXO 0 +#define P_PLL4 1 + +static const u8 lcc_pxo_pll4_map[] = { + [P_PXO] = 0, + [P_PLL4] = 2, +}; + +static const char *lcc_pxo_pll4[] = { + "pxo", + "pll4_vote", +}; + +static struct freq_tbl clk_tbl_aif_mi2s[] = { + { 1024000, P_PLL4, 4, 1, 96 }, + { 1411200, P_PLL4, 4, 2, 139 }, + { 1536000, P_PLL4, 4, 1, 64 }, + { 2048000, P_PLL4, 4, 1, 48 }, + { 2116800, P_PLL4, 4, 2, 93 }, + { 2304000, P_PLL4, 4, 2, 85 }, + { 2822400, P_PLL4, 4, 6, 209 }, + { 3072000, P_PLL4, 4, 1, 32 }, + { 3175200, P_PLL4, 4, 1, 31 }, + { 4096000, P_PLL4, 4, 1, 24 }, + { 4233600, P_PLL4, 4, 9, 209 }, + { 4608000, P_PLL4, 4, 3, 64 }, + { 5644800, P_PLL4, 4, 12, 209 }, + { 6144000, P_PLL4, 4, 1, 16 }, + { 6350400, P_PLL4, 4, 2, 31 }, + { 8192000, P_PLL4, 4, 1, 12 }, + { 8467200, P_PLL4, 4, 18, 209 }, + { 9216000, P_PLL4, 4, 3, 32 }, + { 11289600, P_PLL4, 4, 24, 209 }, + { 12288000, P_PLL4, 4, 1, 8 }, + { 12700800, P_PLL4, 4, 27, 209 }, + { 13824000, P_PLL4, 4, 9, 64 }, + { 16384000, P_PLL4, 4, 1, 6 }, + { 16934400, P_PLL4, 4, 41, 238 }, + { 18432000, P_PLL4, 4, 3, 16 }, + { 22579200, P_PLL4, 2, 24, 209 }, + { 24576000, P_PLL4, 4, 1, 4 }, + { 27648000, P_PLL4, 4, 9, 32 }, + { 33868800, P_PLL4, 4, 41, 119 }, + { 36864000, P_PLL4, 4, 3, 8 }, + { 45158400, P_PLL4, 1, 24, 209 }, + { 49152000, P_PLL4, 4, 1, 2 }, + { 50803200, P_PLL4, 1, 27, 209 }, + { } +}; + +static struct clk_rcg mi2s_osr_src = { + .ns_reg = 0x48, + .md_reg = 0x4c, + .mn = { + .mnctr_en_bit = 8, + .mnctr_reset_bit = 7, + .mnctr_mode_shift = 5, + .n_val_shift = 24, + .m_val_shift = 8, + .width = 8, + }, + .p = { + .pre_div_shift = 3, + .pre_div_width = 2, + }, + .s = { + .src_sel_shift = 0, + .parent_map = lcc_pxo_pll4_map, + }, + .freq_tbl = clk_tbl_aif_mi2s, + .clkr = { + .enable_reg = 0x48, + .enable_mask = BIT(9), + .hw.init = &(struct clk_init_data){ + .name = "mi2s_osr_src", + .parent_names = lcc_pxo_pll4, + .num_parents = 2, + .ops = &clk_rcg_ops, + .flags = CLK_SET_RATE_GATE, + }, + }, +}; + +static const char *lcc_mi2s_parents[] = { + "mi2s_osr_src", +}; + +static struct clk_branch mi2s_osr_clk = { + .halt_reg = 0x50, + .halt_bit = 1, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0x48, + .enable_mask = BIT(17), + .hw.init = &(struct clk_init_data){ + .name = "mi2s_osr_clk", + .parent_names = lcc_mi2s_parents, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct clk_regmap_div mi2s_div_clk = { + .reg = 0x48, + .shift = 10, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "mi2s_div_clk", + .parent_names = lcc_mi2s_parents, + .num_parents = 1, + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_branch mi2s_bit_div_clk = { + .halt_reg = 0x50, + .halt_bit = 0, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0x48, + .enable_mask = BIT(15), + .hw.init = &(struct clk_init_data){ + .name = "mi2s_bit_div_clk", + .parent_names = (const char *[]){ "mi2s_div_clk" }, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + + +static struct clk_regmap_mux mi2s_bit_clk = { + .reg = 0x48, + .shift = 14, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "mi2s_bit_clk", + .parent_names = (const char *[]){ + "mi2s_bit_div_clk", + "mi2s_codec_clk", + }, + .num_parents = 2, + .ops = &clk_regmap_mux_closest_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct freq_tbl clk_tbl_pcm[] = { + { 64000, P_PLL4, 4, 1, 1536 }, + { 128000, P_PLL4, 4, 1, 768 }, + { 256000, P_PLL4, 4, 1, 384 }, + { 512000, P_PLL4, 4, 1, 192 }, + { 1024000, P_PLL4, 4, 1, 96 }, + { 2048000, P_PLL4, 4, 1, 48 }, + { }, +}; + +static struct clk_rcg pcm_src = { + .ns_reg = 0x54, + .md_reg = 0x58, + .mn = { + .mnctr_en_bit = 8, + .mnctr_reset_bit = 7, + .mnctr_mode_shift = 5, + .n_val_shift = 16, + .m_val_shift = 16, + .width = 16, + }, + .p = { + .pre_div_shift = 3, + .pre_div_width = 2, + }, + .s = { + .src_sel_shift = 0, + .parent_map = lcc_pxo_pll4_map, + }, + .freq_tbl = clk_tbl_pcm, + .clkr = { + .enable_reg = 0x54, + .enable_mask = BIT(9), + .hw.init = &(struct clk_init_data){ + .name = "pcm_src", + .parent_names = lcc_pxo_pll4, + .num_parents = 2, + .ops = &clk_rcg_ops, + .flags = CLK_SET_RATE_GATE, + }, + }, +}; + +static struct clk_branch pcm_clk_out = { + .halt_reg = 0x5c, + .halt_bit = 0, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0x54, + .enable_mask = BIT(11), + .hw.init = &(struct clk_init_data){ + .name = "pcm_clk_out", + .parent_names = (const char *[]){ "pcm_src" }, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct clk_regmap_mux pcm_clk = { + .reg = 0x54, + .shift = 10, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "pcm_clk", + .parent_names = (const char *[]){ + "pcm_clk_out", + "pcm_codec_clk", + }, + .num_parents = 2, + .ops = &clk_regmap_mux_closest_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct freq_tbl clk_tbl_aif_osr[] = { + { 22050, P_PLL4, 1, 147, 20480 }, + { 32000, P_PLL4, 1, 1, 96 }, + { 44100, P_PLL4, 1, 147, 10240 }, + { 48000, P_PLL4, 1, 1, 64 }, + { 88200, P_PLL4, 1, 147, 5120 }, + { 96000, P_PLL4, 1, 1, 32 }, + { 176400, P_PLL4, 1, 147, 2560 }, + { 192000, P_PLL4, 1, 1, 16 }, + { }, +}; + +static struct clk_rcg spdif_src = { + .ns_reg = 0xcc, + .md_reg = 0xd0, + .mn = { + .mnctr_en_bit = 8, + .mnctr_reset_bit = 7, + .mnctr_mode_shift = 5, + .n_val_shift = 16, + .m_val_shift = 16, + .width = 8, + }, + .p = { + .pre_div_shift = 3, + .pre_div_width = 2, + }, + .s = { + .src_sel_shift = 0, + .parent_map = lcc_pxo_pll4_map, + }, + .freq_tbl = clk_tbl_aif_osr, + .clkr = { + .enable_reg = 0xcc, + .enable_mask = BIT(9), + .hw.init = &(struct clk_init_data){ + .name = "spdif_src", + .parent_names = lcc_pxo_pll4, + .num_parents = 2, + .ops = &clk_rcg_ops, + .flags = CLK_SET_RATE_GATE, + }, + }, +}; + +static const char *lcc_spdif_parents[] = { + "spdif_src", +}; + +static struct clk_branch spdif_clk = { + .halt_reg = 0xd4, + .halt_bit = 1, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0xcc, + .enable_mask = BIT(12), + .hw.init = &(struct clk_init_data){ + .name = "spdif_clk", + .parent_names = lcc_spdif_parents, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct freq_tbl clk_tbl_ahbix[] = { + { 131072, P_PLL4, 1, 1, 3 }, + { }, +}; + +static struct clk_rcg ahbix_clk = { + .ns_reg = 0x38, + .md_reg = 0x3c, + .mn = { + .mnctr_en_bit = 8, + .mnctr_reset_bit = 7, + .mnctr_mode_shift = 5, + .n_val_shift = 24, + .m_val_shift = 8, + .width = 8, + }, + .p = { + .pre_div_shift = 3, + .pre_div_width = 2, + }, + .s = { + .src_sel_shift = 0, + .parent_map = lcc_pxo_pll4_map, + }, + .freq_tbl = clk_tbl_ahbix, + .clkr = { + .enable_reg = 0x38, + .enable_mask = BIT(10), /* toggle the gfmux to select mn/pxo */ + .hw.init = &(struct clk_init_data){ + .name = "ahbix", + .parent_names = lcc_pxo_pll4, + .num_parents = 2, + .ops = &clk_rcg_ops, + .flags = CLK_SET_RATE_GATE, + }, + }, +}; + +static struct clk_regmap *lcc_ipq806x_clks[] = { + [PLL4] = &pll4.clkr, + [MI2S_OSR_SRC] = &mi2s_osr_src.clkr, + [MI2S_OSR_CLK] = &mi2s_osr_clk.clkr, + [MI2S_DIV_CLK] = &mi2s_div_clk.clkr, + [MI2S_BIT_DIV_CLK] = &mi2s_bit_div_clk.clkr, + [MI2S_BIT_CLK] = &mi2s_bit_clk.clkr, + [PCM_SRC] = &pcm_src.clkr, + [PCM_CLK_OUT] = &pcm_clk_out.clkr, + [PCM_CLK] = &pcm_clk.clkr, + [SPDIF_SRC] = &spdif_src.clkr, + [SPDIF_CLK] = &spdif_clk.clkr, + [AHBIX_CLK] = &ahbix_clk.clkr, +}; + +static const struct regmap_config lcc_ipq806x_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xfc, + .fast_io = true, +}; + +static const struct qcom_cc_desc lcc_ipq806x_desc = { + .config = &lcc_ipq806x_regmap_config, + .clks = lcc_ipq806x_clks, + .num_clks = ARRAY_SIZE(lcc_ipq806x_clks), +}; + +static const struct of_device_id lcc_ipq806x_match_table[] = { + { .compatible = "qcom,lcc-ipq8064" }, + { } +}; +MODULE_DEVICE_TABLE(of, lcc_ipq806x_match_table); + +static int lcc_ipq806x_probe(struct platform_device *pdev) +{ + u32 val; + struct regmap *regmap; + + regmap = qcom_cc_map(pdev, &lcc_ipq806x_desc); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* Configure the rate of PLL4 if the bootloader hasn't already */ + val = regmap_read(regmap, 0x0, &val); + if (!val) + clk_pll_configure_sr(&pll4, regmap, &pll4_config, true); + /* Enable PLL4 source on the LPASS Primary PLL Mux */ + regmap_write(regmap, 0xc4, 0x1); + + return qcom_cc_really_probe(pdev, &lcc_ipq806x_desc, regmap); +} + +static int lcc_ipq806x_remove(struct platform_device *pdev) +{ + qcom_cc_remove(pdev); + return 0; +} + +static struct platform_driver lcc_ipq806x_driver = { + .probe = lcc_ipq806x_probe, + .remove = lcc_ipq806x_remove, + .driver = { + .name = "lcc-ipq806x", + .owner = THIS_MODULE, + .of_match_table = lcc_ipq806x_match_table, + }, +}; +module_platform_driver(lcc_ipq806x_driver); + +MODULE_DESCRIPTION("QCOM LCC IPQ806x Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:lcc-ipq806x"); diff --git a/drivers/clk/qcom/lcc-msm8960.c b/drivers/clk/qcom/lcc-msm8960.c new file mode 100644 index 000000000000..a75a408cfccd --- /dev/null +++ b/drivers/clk/qcom/lcc-msm8960.c @@ -0,0 +1,585 @@ +/* + * Copyright (c) 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. + */ + +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk-provider.h> +#include <linux/regmap.h> + +#include <dt-bindings/clock/qcom,lcc-msm8960.h> + +#include "common.h" +#include "clk-regmap.h" +#include "clk-pll.h" +#include "clk-rcg.h" +#include "clk-branch.h" +#include "clk-regmap-divider.h" +#include "clk-regmap-mux.h" + +static struct clk_pll pll4 = { + .l_reg = 0x4, + .m_reg = 0x8, + .n_reg = 0xc, + .config_reg = 0x14, + .mode_reg = 0x0, + .status_reg = 0x18, + .status_bit = 16, + .clkr.hw.init = &(struct clk_init_data){ + .name = "pll4", + .parent_names = (const char *[]){ "pxo" }, + .num_parents = 1, + .ops = &clk_pll_ops, + }, +}; + +#define P_PXO 0 +#define P_PLL4 1 + +static const u8 lcc_pxo_pll4_map[] = { + [P_PXO] = 0, + [P_PLL4] = 2, +}; + +static const char *lcc_pxo_pll4[] = { + "pxo", + "pll4_vote", +}; + +static struct freq_tbl clk_tbl_aif_osr_492[] = { + { 512000, P_PLL4, 4, 1, 240 }, + { 768000, P_PLL4, 4, 1, 160 }, + { 1024000, P_PLL4, 4, 1, 120 }, + { 1536000, P_PLL4, 4, 1, 80 }, + { 2048000, P_PLL4, 4, 1, 60 }, + { 3072000, P_PLL4, 4, 1, 40 }, + { 4096000, P_PLL4, 4, 1, 30 }, + { 6144000, P_PLL4, 4, 1, 20 }, + { 8192000, P_PLL4, 4, 1, 15 }, + { 12288000, P_PLL4, 4, 1, 10 }, + { 24576000, P_PLL4, 4, 1, 5 }, + { 27000000, P_PXO, 1, 0, 0 }, + { } +}; + +static struct freq_tbl clk_tbl_aif_osr_393[] = { + { 512000, P_PLL4, 4, 1, 192 }, + { 768000, P_PLL4, 4, 1, 128 }, + { 1024000, P_PLL4, 4, 1, 96 }, + { 1536000, P_PLL4, 4, 1, 64 }, + { 2048000, P_PLL4, 4, 1, 48 }, + { 3072000, P_PLL4, 4, 1, 32 }, + { 4096000, P_PLL4, 4, 1, 24 }, + { 6144000, P_PLL4, 4, 1, 16 }, + { 8192000, P_PLL4, 4, 1, 12 }, + { 12288000, P_PLL4, 4, 1, 8 }, + { 24576000, P_PLL4, 4, 1, 4 }, + { 27000000, P_PXO, 1, 0, 0 }, + { } +}; + +static struct clk_rcg mi2s_osr_src = { + .ns_reg = 0x48, + .md_reg = 0x4c, + .mn = { + .mnctr_en_bit = 8, + .mnctr_reset_bit = 7, + .mnctr_mode_shift = 5, + .n_val_shift = 24, + .m_val_shift = 8, + .width = 8, + }, + .p = { + .pre_div_shift = 3, + .pre_div_width = 2, + }, + .s = { + .src_sel_shift = 0, + .parent_map = lcc_pxo_pll4_map, + }, + .freq_tbl = clk_tbl_aif_osr_393, + .clkr = { + .enable_reg = 0x48, + .enable_mask = BIT(9), + .hw.init = &(struct clk_init_data){ + .name = "mi2s_osr_src", + .parent_names = lcc_pxo_pll4, + .num_parents = 2, + .ops = &clk_rcg_ops, + .flags = CLK_SET_RATE_GATE, + }, + }, +}; + +static const char *lcc_mi2s_parents[] = { + "mi2s_osr_src", +}; + +static struct clk_branch mi2s_osr_clk = { + .halt_reg = 0x50, + .halt_bit = 1, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0x48, + .enable_mask = BIT(17), + .hw.init = &(struct clk_init_data){ + .name = "mi2s_osr_clk", + .parent_names = lcc_mi2s_parents, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct clk_regmap_div mi2s_div_clk = { + .reg = 0x48, + .shift = 10, + .width = 4, + .clkr = { + .enable_reg = 0x48, + .enable_mask = BIT(15), + .hw.init = &(struct clk_init_data){ + .name = "mi2s_div_clk", + .parent_names = lcc_mi2s_parents, + .num_parents = 1, + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_branch mi2s_bit_div_clk = { + .halt_reg = 0x50, + .halt_bit = 0, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0x48, + .enable_mask = BIT(15), + .hw.init = &(struct clk_init_data){ + .name = "mi2s_bit_div_clk", + .parent_names = (const char *[]){ "mi2s_div_clk" }, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct clk_regmap_mux mi2s_bit_clk = { + .reg = 0x48, + .shift = 14, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "mi2s_bit_clk", + .parent_names = (const char *[]){ + "mi2s_bit_div_clk", + "mi2s_codec_clk", + }, + .num_parents = 2, + .ops = &clk_regmap_mux_closest_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +#define CLK_AIF_OSR_DIV(prefix, _ns, _md, hr) \ +static struct clk_rcg prefix##_osr_src = { \ + .ns_reg = _ns, \ + .md_reg = _md, \ + .mn = { \ + .mnctr_en_bit = 8, \ + .mnctr_reset_bit = 7, \ + .mnctr_mode_shift = 5, \ + .n_val_shift = 24, \ + .m_val_shift = 8, \ + .width = 8, \ + }, \ + .p = { \ + .pre_div_shift = 3, \ + .pre_div_width = 2, \ + }, \ + .s = { \ + .src_sel_shift = 0, \ + .parent_map = lcc_pxo_pll4_map, \ + }, \ + .freq_tbl = clk_tbl_aif_osr_393, \ + .clkr = { \ + .enable_reg = _ns, \ + .enable_mask = BIT(9), \ + .hw.init = &(struct clk_init_data){ \ + .name = #prefix "_osr_src", \ + .parent_names = lcc_pxo_pll4, \ + .num_parents = 2, \ + .ops = &clk_rcg_ops, \ + .flags = CLK_SET_RATE_GATE, \ + }, \ + }, \ +}; \ + \ +static const char *lcc_##prefix##_parents[] = { \ + #prefix "_osr_src", \ +}; \ + \ +static struct clk_branch prefix##_osr_clk = { \ + .halt_reg = hr, \ + .halt_bit = 1, \ + .halt_check = BRANCH_HALT_ENABLE, \ + .clkr = { \ + .enable_reg = _ns, \ + .enable_mask = BIT(21), \ + .hw.init = &(struct clk_init_data){ \ + .name = #prefix "_osr_clk", \ + .parent_names = lcc_##prefix##_parents, \ + .num_parents = 1, \ + .ops = &clk_branch_ops, \ + .flags = CLK_SET_RATE_PARENT, \ + }, \ + }, \ +}; \ + \ +static struct clk_regmap_div prefix##_div_clk = { \ + .reg = _ns, \ + .shift = 10, \ + .width = 8, \ + .clkr = { \ + .hw.init = &(struct clk_init_data){ \ + .name = #prefix "_div_clk", \ + .parent_names = lcc_##prefix##_parents, \ + .num_parents = 1, \ + .ops = &clk_regmap_div_ops, \ + }, \ + }, \ +}; \ + \ +static struct clk_branch prefix##_bit_div_clk = { \ + .halt_reg = hr, \ + .halt_bit = 0, \ + .halt_check = BRANCH_HALT_ENABLE, \ + .clkr = { \ + .enable_reg = _ns, \ + .enable_mask = BIT(19), \ + .hw.init = &(struct clk_init_data){ \ + .name = #prefix "_bit_div_clk", \ + .parent_names = (const char *[]){ \ + #prefix "_div_clk" \ + }, \ + .num_parents = 1, \ + .ops = &clk_branch_ops, \ + .flags = CLK_SET_RATE_PARENT, \ + }, \ + }, \ +}; \ + \ +static struct clk_regmap_mux prefix##_bit_clk = { \ + .reg = _ns, \ + .shift = 18, \ + .width = 1, \ + .clkr = { \ + .hw.init = &(struct clk_init_data){ \ + .name = #prefix "_bit_clk", \ + .parent_names = (const char *[]){ \ + #prefix "_bit_div_clk", \ + #prefix "_codec_clk", \ + }, \ + .num_parents = 2, \ + .ops = &clk_regmap_mux_closest_ops, \ + .flags = CLK_SET_RATE_PARENT, \ + }, \ + }, \ +} + +CLK_AIF_OSR_DIV(codec_i2s_mic, 0x60, 0x64, 0x68); +CLK_AIF_OSR_DIV(spare_i2s_mic, 0x78, 0x7c, 0x80); +CLK_AIF_OSR_DIV(codec_i2s_spkr, 0x6c, 0x70, 0x74); +CLK_AIF_OSR_DIV(spare_i2s_spkr, 0x84, 0x88, 0x8c); + +static struct freq_tbl clk_tbl_pcm_492[] = { + { 256000, P_PLL4, 4, 1, 480 }, + { 512000, P_PLL4, 4, 1, 240 }, + { 768000, P_PLL4, 4, 1, 160 }, + { 1024000, P_PLL4, 4, 1, 120 }, + { 1536000, P_PLL4, 4, 1, 80 }, + { 2048000, P_PLL4, 4, 1, 60 }, + { 3072000, P_PLL4, 4, 1, 40 }, + { 4096000, P_PLL4, 4, 1, 30 }, + { 6144000, P_PLL4, 4, 1, 20 }, + { 8192000, P_PLL4, 4, 1, 15 }, + { 12288000, P_PLL4, 4, 1, 10 }, + { 24576000, P_PLL4, 4, 1, 5 }, + { 27000000, P_PXO, 1, 0, 0 }, + { } +}; + +static struct freq_tbl clk_tbl_pcm_393[] = { + { 256000, P_PLL4, 4, 1, 384 }, + { 512000, P_PLL4, 4, 1, 192 }, + { 768000, P_PLL4, 4, 1, 128 }, + { 1024000, P_PLL4, 4, 1, 96 }, + { 1536000, P_PLL4, 4, 1, 64 }, + { 2048000, P_PLL4, 4, 1, 48 }, + { 3072000, P_PLL4, 4, 1, 32 }, + { 4096000, P_PLL4, 4, 1, 24 }, + { 6144000, P_PLL4, 4, 1, 16 }, + { 8192000, P_PLL4, 4, 1, 12 }, + { 12288000, P_PLL4, 4, 1, 8 }, + { 24576000, P_PLL4, 4, 1, 4 }, + { 27000000, P_PXO, 1, 0, 0 }, + { } +}; + +static struct clk_rcg pcm_src = { + .ns_reg = 0x54, + .md_reg = 0x58, + .mn = { + .mnctr_en_bit = 8, + .mnctr_reset_bit = 7, + .mnctr_mode_shift = 5, + .n_val_shift = 16, + .m_val_shift = 16, + .width = 16, + }, + .p = { + .pre_div_shift = 3, + .pre_div_width = 2, + }, + .s = { + .src_sel_shift = 0, + .parent_map = lcc_pxo_pll4_map, + }, + .freq_tbl = clk_tbl_pcm_393, + .clkr = { + .enable_reg = 0x54, + .enable_mask = BIT(9), + .hw.init = &(struct clk_init_data){ + .name = "pcm_src", + .parent_names = lcc_pxo_pll4, + .num_parents = 2, + .ops = &clk_rcg_ops, + .flags = CLK_SET_RATE_GATE, + }, + }, +}; + +static struct clk_branch pcm_clk_out = { + .halt_reg = 0x5c, + .halt_bit = 0, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0x54, + .enable_mask = BIT(11), + .hw.init = &(struct clk_init_data){ + .name = "pcm_clk_out", + .parent_names = (const char *[]){ "pcm_src" }, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct clk_regmap_mux pcm_clk = { + .reg = 0x54, + .shift = 10, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "pcm_clk", + .parent_names = (const char *[]){ + "pcm_clk_out", + "pcm_codec_clk", + }, + .num_parents = 2, + .ops = &clk_regmap_mux_closest_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct clk_rcg slimbus_src = { + .ns_reg = 0xcc, + .md_reg = 0xd0, + .mn = { + .mnctr_en_bit = 8, + .mnctr_reset_bit = 7, + .mnctr_mode_shift = 5, + .n_val_shift = 16, + .m_val_shift = 16, + .width = 8, + }, + .p = { + .pre_div_shift = 3, + .pre_div_width = 2, + }, + .s = { + .src_sel_shift = 0, + .parent_map = lcc_pxo_pll4_map, + }, + .freq_tbl = clk_tbl_aif_osr_393, + .clkr = { + .enable_reg = 0xcc, + .enable_mask = BIT(9), + .hw.init = &(struct clk_init_data){ + .name = "slimbus_src", + .parent_names = lcc_pxo_pll4, + .num_parents = 2, + .ops = &clk_rcg_ops, + .flags = CLK_SET_RATE_GATE, + }, + }, +}; + +static const char *lcc_slimbus_parents[] = { + "slimbus_src", +}; + +static struct clk_branch audio_slimbus_clk = { + .halt_reg = 0xd4, + .halt_bit = 0, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0xcc, + .enable_mask = BIT(10), + .hw.init = &(struct clk_init_data){ + .name = "audio_slimbus_clk", + .parent_names = lcc_slimbus_parents, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct clk_branch sps_slimbus_clk = { + .halt_reg = 0xd4, + .halt_bit = 1, + .halt_check = BRANCH_HALT_ENABLE, + .clkr = { + .enable_reg = 0xcc, + .enable_mask = BIT(12), + .hw.init = &(struct clk_init_data){ + .name = "sps_slimbus_clk", + .parent_names = lcc_slimbus_parents, + .num_parents = 1, + .ops = &clk_branch_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }, +}; + +static struct clk_regmap *lcc_msm8960_clks[] = { + [PLL4] = &pll4.clkr, + [MI2S_OSR_SRC] = &mi2s_osr_src.clkr, + [MI2S_OSR_CLK] = &mi2s_osr_clk.clkr, + [MI2S_DIV_CLK] = &mi2s_div_clk.clkr, + [MI2S_BIT_DIV_CLK] = &mi2s_bit_div_clk.clkr, + [MI2S_BIT_CLK] = &mi2s_bit_clk.clkr, + [PCM_SRC] = &pcm_src.clkr, + [PCM_CLK_OUT] = &pcm_clk_out.clkr, + [PCM_CLK] = &pcm_clk.clkr, + [SLIMBUS_SRC] = &slimbus_src.clkr, + [AUDIO_SLIMBUS_CLK] = &audio_slimbus_clk.clkr, + [SPS_SLIMBUS_CLK] = &sps_slimbus_clk.clkr, + [CODEC_I2S_MIC_OSR_SRC] = &codec_i2s_mic_osr_src.clkr, + [CODEC_I2S_MIC_OSR_CLK] = &codec_i2s_mic_osr_clk.clkr, + [CODEC_I2S_MIC_DIV_CLK] = &codec_i2s_mic_div_clk.clkr, + [CODEC_I2S_MIC_BIT_DIV_CLK] = &codec_i2s_mic_bit_div_clk.clkr, + [CODEC_I2S_MIC_BIT_CLK] = &codec_i2s_mic_bit_clk.clkr, + [SPARE_I2S_MIC_OSR_SRC] = &spare_i2s_mic_osr_src.clkr, + [SPARE_I2S_MIC_OSR_CLK] = &spare_i2s_mic_osr_clk.clkr, + [SPARE_I2S_MIC_DIV_CLK] = &spare_i2s_mic_div_clk.clkr, + [SPARE_I2S_MIC_BIT_DIV_CLK] = &spare_i2s_mic_bit_div_clk.clkr, + [SPARE_I2S_MIC_BIT_CLK] = &spare_i2s_mic_bit_clk.clkr, + [CODEC_I2S_SPKR_OSR_SRC] = &codec_i2s_spkr_osr_src.clkr, + [CODEC_I2S_SPKR_OSR_CLK] = &codec_i2s_spkr_osr_clk.clkr, + [CODEC_I2S_SPKR_DIV_CLK] = &codec_i2s_spkr_div_clk.clkr, + [CODEC_I2S_SPKR_BIT_DIV_CLK] = &codec_i2s_spkr_bit_div_clk.clkr, + [CODEC_I2S_SPKR_BIT_CLK] = &codec_i2s_spkr_bit_clk.clkr, + [SPARE_I2S_SPKR_OSR_SRC] = &spare_i2s_spkr_osr_src.clkr, + [SPARE_I2S_SPKR_OSR_CLK] = &spare_i2s_spkr_osr_clk.clkr, + [SPARE_I2S_SPKR_DIV_CLK] = &spare_i2s_spkr_div_clk.clkr, + [SPARE_I2S_SPKR_BIT_DIV_CLK] = &spare_i2s_spkr_bit_div_clk.clkr, + [SPARE_I2S_SPKR_BIT_CLK] = &spare_i2s_spkr_bit_clk.clkr, +}; + +static const struct regmap_config lcc_msm8960_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0xfc, + .fast_io = true, +}; + +static const struct qcom_cc_desc lcc_msm8960_desc = { + .config = &lcc_msm8960_regmap_config, + .clks = lcc_msm8960_clks, + .num_clks = ARRAY_SIZE(lcc_msm8960_clks), +}; + +static const struct of_device_id lcc_msm8960_match_table[] = { + { .compatible = "qcom,lcc-msm8960" }, + { .compatible = "qcom,lcc-apq8064" }, + { } +}; +MODULE_DEVICE_TABLE(of, lcc_msm8960_match_table); + +static int lcc_msm8960_probe(struct platform_device *pdev) +{ + u32 val; + struct regmap *regmap; + + regmap = qcom_cc_map(pdev, &lcc_msm8960_desc); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + /* Use the correct frequency plan depending on speed of PLL4 */ + val = regmap_read(regmap, 0x4, &val); + if (val == 0x12) { + slimbus_src.freq_tbl = clk_tbl_aif_osr_492; + mi2s_osr_src.freq_tbl = clk_tbl_aif_osr_492; + codec_i2s_mic_osr_src.freq_tbl = clk_tbl_aif_osr_492; + spare_i2s_mic_osr_src.freq_tbl = clk_tbl_aif_osr_492; + codec_i2s_spkr_osr_src.freq_tbl = clk_tbl_aif_osr_492; + spare_i2s_spkr_osr_src.freq_tbl = clk_tbl_aif_osr_492; + pcm_src.freq_tbl = clk_tbl_pcm_492; + } + /* Enable PLL4 source on the LPASS Primary PLL Mux */ + regmap_write(regmap, 0xc4, 0x1); + + return qcom_cc_really_probe(pdev, &lcc_msm8960_desc, regmap); +} + +static int lcc_msm8960_remove(struct platform_device *pdev) +{ + qcom_cc_remove(pdev); + return 0; +} + +static struct platform_driver lcc_msm8960_driver = { + .probe = lcc_msm8960_probe, + .remove = lcc_msm8960_remove, + .driver = { + .name = "lcc-msm8960", + .owner = THIS_MODULE, + .of_match_table = lcc_msm8960_match_table, + }, +}; +module_platform_driver(lcc_msm8960_driver); + +MODULE_DESCRIPTION("QCOM LCC MSM8960 Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:lcc-msm8960"); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 0f9a2c3c0e0d..afa04bc46cac 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 b3ca7b0b2c33..d8218c7116f5 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -62,6 +62,7 @@ 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_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o +obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o diff --git a/drivers/cpufreq/qcom-cpufreq.c b/drivers/cpufreq/qcom-cpufreq.c new file mode 100644 index 000000000000..26bce596a146 --- /dev/null +++ b/drivers/cpufreq/qcom-cpufreq.c @@ -0,0 +1,208 @@ +/* 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/cpufreq-dt.h> +#include <linux/pm_opp.h> +#include <linux/init.h> + +static struct cpufreq_dt_platform_data cpufreq_data = { + .independent_clocks = true, +}; + +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-dt", + .data = &cpufreq_data, + .size_data = sizeof(cpufreq_data) + }; + 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..e98993c872a2 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 ARCH_QCOM + 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..aa6a6c9a3590 --- /dev/null +++ b/drivers/cpuidle/cpuidle-qcom.c @@ -0,0 +1,99 @@ +/* + * 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/cpuidle.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <soc/qcom/pm.h> +#include "dt_idle_states.h" + +static struct qcom_cpu_pm_ops *lpm_ops; + +static int qcom_cpu_stby(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int ret; + + ret = lpm_ops->standby(NULL); + if (ret) + return ret; + + return index; +} + +static int qcom_cpu_spc(struct cpuidle_device *dev, + struct cpuidle_driver *drv, int index) +{ + int ret; + + ret = lpm_ops->spc(NULL); + if (ret) + return ret; + + return index; +} + +static struct cpuidle_driver qcom_cpuidle_driver = { + .name = "qcom_cpuidle", +}; + +static const struct of_device_id qcom_idle_state_match[] = { + { .compatible = "qcom,idle-state-stby", .data = qcom_cpu_stby }, + { .compatible = "qcom,idle-state-spc", .data = qcom_cpu_spc }, + { }, +}; + +static int qcom_cpuidle_probe(struct platform_device *pdev) +{ + struct cpuidle_driver *drv = &qcom_cpuidle_driver; + int ret; + + lpm_ops = pdev->dev.platform_data; + + /* Probe for other states, including standby */ + ret = dt_init_idle_driver(drv, qcom_idle_state_match, 0); + if (ret < 0) + return ret; + + /* + * We will not register for cpu's cpuidle device here, + * they will be registered as and when their power controllers + * are ready. + */ + return cpuidle_register_driver(drv); +} + +static struct platform_driver qcom_cpuidle = { + .probe = qcom_cpuidle_probe, + .driver = { + .name = "qcom_cpuidle", + }, +}; + +/* + * Register the driver early so the we have a successul registration + * when the device shows up. + * This way the cpuidle driver could be registered before the cpuidle + * devices are registered. + */ +static int __init qcom_cpuidle_driver_init(void) +{ + return platform_driver_register(&qcom_cpuidle); +} +core_initcall(qcom_cpuidle_driver_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("CPUIDLE driver for QCOM SoC"); +MODULE_ALIAS("platform:qcom-cpuidle"); diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index 325188eef1c1..9718005c6ac8 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 7b976f294a69..2a01574c4560 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 e1b05379ca0e..19c5e0141aaa 100644 --- a/drivers/iommu/msm_iommu.c +++ b/drivers/iommu/msm_iommu.c @@ -422,12 +422,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 2e6b7311fabc..dc1cf68f80e4 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -589,6 +589,20 @@ config MFD_PM8921_CORE Say M here if you want to include support for PM8921 chip as a module. This will build a module called "pm8921-core". +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_SPMI_PMIC tristate "Qualcomm SPMI PMICs" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 53467e211381..ae608cc2d15f 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -153,6 +153,7 @@ obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.o 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_QCOM_RPM) += qcom_rpm.o obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o obj-$(CONFIG_MFD_TPS65090) += tps65090.o diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c index 5a92646a2ccb..d6dbe57ae113 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..eceb34f503da --- /dev/null +++ b/drivers/mfd/qcom_rpm.c @@ -0,0 +1,617 @@ +/* + * 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_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); + +#define QCOM_RPM_STATUS_ID_SEQUENCE 7 +int qcom_rpm_read(struct qcom_rpm *rpm, + int resource, + u32 *val, + size_t count) +{ + const struct qcom_rpm_resource *res; + const struct qcom_rpm_data *data = rpm->data; + uint32_t seq_begin; + uint32_t seq_end; + int rc; + 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); + seq_begin = readl(RPM_STATUS_REG(rpm, QCOM_RPM_STATUS_ID_SEQUENCE)); + + for (i = 0; i < count; i++) + *val++ = readl(RPM_STATUS_REG(rpm, res->status_id)); + + seq_end = readl(RPM_STATUS_REG(rpm, QCOM_RPM_STATUS_ID_SEQUENCE)); + + rc = (seq_begin != seq_end || (seq_begin & 0x01)) ? -EBUSY : 0; + + mutex_unlock(&rpm->lock); + return rc; + +} +EXPORT_SYMBOL(qcom_rpm_read); + +int qcom_rpm_write(struct qcom_rpm *rpm, + int state, + 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(BIT(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 27986f641f7d..39734999fe40 100644 --- a/drivers/mfd/ssbi.c +++ b/drivers/mfd/ssbi.c @@ -330,7 +330,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/Makefile b/drivers/mmc/core/Makefile index 38ed210ce2f3..b39cbd2f830b 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -8,5 +8,5 @@ mmc_core-y := core.o bus.o host.o \ sdio.o sdio_ops.o sdio_bus.o \ sdio_cis.o sdio_io.o sdio_irq.o \ quirks.o slot-gpio.o - +mmc_core-$(CONFIG_OF) += pwrseq.o pwrseq_simple.o mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9584bffa8b22..6163d5331cf7 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -40,6 +40,7 @@ #include "bus.h" #include "host.h" #include "sdio_bus.h" +#include "pwrseq.h" #include "mmc_ops.h" #include "sd_ops.h" @@ -1555,6 +1556,8 @@ void mmc_power_up(struct mmc_host *host, u32 ocr) mmc_host_clk_hold(host); + mmc_pwrseq_pre_power_on(host); + host->ios.vdd = fls(ocr) - 1; host->ios.power_mode = MMC_POWER_UP; /* Set initial state and call mmc_set_ios */ @@ -1585,6 +1588,8 @@ void mmc_power_up(struct mmc_host *host, u32 ocr) */ mmc_delay(10); + mmc_pwrseq_post_power_on(host); + mmc_host_clk_release(host); } @@ -1595,6 +1600,8 @@ void mmc_power_off(struct mmc_host *host) mmc_host_clk_hold(host); + mmc_pwrseq_power_off(host); + host->ios.clock = 0; host->ios.vdd = 0; diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 270d58a4c43d..5ca2ce52f6c5 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -29,6 +29,7 @@ #include "core.h" #include "host.h" +#include "pwrseq.h" #define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev) @@ -562,7 +563,7 @@ int mmc_add_host(struct mmc_host *host) mmc_start_host(host); register_pm_notifier(&host->pm_notify); - return 0; + return mmc_pwrseq_alloc(host); } EXPORT_SYMBOL(mmc_add_host); @@ -604,7 +605,8 @@ void mmc_free_host(struct mmc_host *host) spin_lock(&mmc_host_lock); idr_remove(&mmc_host_idr, host->index); spin_unlock(&mmc_host_lock); - + + mmc_pwrseq_free(host); put_device(&host->class_dev); } diff --git a/drivers/mmc/core/pwrseq.c b/drivers/mmc/core/pwrseq.c new file mode 100644 index 000000000000..2cea00ed4e65 --- /dev/null +++ b/drivers/mmc/core/pwrseq.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 Linaro Ltd + * + * Author: Ulf Hansson <ulf.hansson@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + * + * MMC power sequence management + */ +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_platform.h> + +#include <linux/mmc/host.h> + +#include "pwrseq.h" + +struct mmc_pwrseq_match { + const char *compatible; + int (*alloc)(struct mmc_host *host, struct device *dev); +}; + +static struct mmc_pwrseq_match pwrseq_match[] = { + { + .compatible = "mmc-pwrseq-simple", + .alloc = mmc_pwrseq_simple_alloc, + }, +}; + +static struct mmc_pwrseq_match *mmc_pwrseq_find(struct device_node *np) +{ + struct mmc_pwrseq_match *match = ERR_PTR(-ENODEV); + int i; + + for (i = 0; i < ARRAY_SIZE(pwrseq_match); i++) { + if (of_device_is_compatible(np, pwrseq_match[i].compatible)) { + match = &pwrseq_match[i]; + break; + } + } + + return match; +} + +int mmc_pwrseq_alloc(struct mmc_host *host) +{ + struct platform_device *pdev; + struct device_node *np; + struct mmc_pwrseq_match *match; + int ret = 0; + + np = of_parse_phandle(host->parent->of_node, "mmc-pwrseq", 0); + if (!np) + return 0; + + pdev = of_find_device_by_node(np); + if (!pdev) { + ret = -ENODEV; + goto err; + } + + match = mmc_pwrseq_find(np); + if (IS_ERR(match)) { + ret = PTR_ERR(match); + goto err; + } + + ret = match->alloc(host, &pdev->dev); + if (!ret) + dev_info(host->parent, "allocated mmc-pwrseq\n"); + +err: + of_node_put(np); + return ret; +} + +void mmc_pwrseq_pre_power_on(struct mmc_host *host) +{ + struct mmc_pwrseq *pwrseq = host->pwrseq; + + if (pwrseq && pwrseq->ops && pwrseq->ops->pre_power_on) + pwrseq->ops->pre_power_on(host); +} + +void mmc_pwrseq_post_power_on(struct mmc_host *host) +{ + struct mmc_pwrseq *pwrseq = host->pwrseq; + + if (pwrseq && pwrseq->ops && pwrseq->ops->post_power_on) + pwrseq->ops->post_power_on(host); +} + +void mmc_pwrseq_power_off(struct mmc_host *host) +{ + struct mmc_pwrseq *pwrseq = host->pwrseq; + + if (pwrseq && pwrseq->ops && pwrseq->ops->power_off) + pwrseq->ops->power_off(host); +} + +void mmc_pwrseq_free(struct mmc_host *host) +{ + struct mmc_pwrseq *pwrseq = host->pwrseq; + + if (pwrseq && pwrseq->ops && pwrseq->ops->free) + pwrseq->ops->free(host); +} diff --git a/drivers/mmc/core/pwrseq.h b/drivers/mmc/core/pwrseq.h new file mode 100644 index 000000000000..bd860d88f116 --- /dev/null +++ b/drivers/mmc/core/pwrseq.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Linaro Ltd + * + * Author: Ulf Hansson <ulf.hansson@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + */ +#ifndef _MMC_CORE_PWRSEQ_H +#define _MMC_CORE_PWRSEQ_H + +struct mmc_pwrseq_ops { + void (*pre_power_on)(struct mmc_host *host); + void (*post_power_on)(struct mmc_host *host); + void (*power_off)(struct mmc_host *host); + void (*free)(struct mmc_host *host); +}; + +struct mmc_pwrseq { + struct mmc_pwrseq_ops *ops; +}; + +#ifdef CONFIG_OF + +int mmc_pwrseq_alloc(struct mmc_host *host); +void mmc_pwrseq_pre_power_on(struct mmc_host *host); +void mmc_pwrseq_post_power_on(struct mmc_host *host); +void mmc_pwrseq_power_off(struct mmc_host *host); +void mmc_pwrseq_free(struct mmc_host *host); + +int mmc_pwrseq_simple_alloc(struct mmc_host *host, struct device *dev); + +#else + +static inline int mmc_pwrseq_alloc(struct mmc_host *host) { return 0; } +static inline void mmc_pwrseq_pre_power_on(struct mmc_host *host) {} +static inline void mmc_pwrseq_post_power_on(struct mmc_host *host) {} +static inline void mmc_pwrseq_power_off(struct mmc_host *host) {} +static inline void mmc_pwrseq_free(struct mmc_host *host) {} + +#endif + +#endif diff --git a/drivers/mmc/core/pwrseq_simple.c b/drivers/mmc/core/pwrseq_simple.c new file mode 100644 index 000000000000..5e34c77efa5e --- /dev/null +++ b/drivers/mmc/core/pwrseq_simple.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2014 Linaro Ltd + * + * Author: Ulf Hansson <ulf.hansson@linaro.org> + * + * License terms: GNU General Public License (GPL) version 2 + * + * Simple MMC power sequence management + */ +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/of_gpio.h> +#include <linux/gpio/consumer.h> + +#include <linux/mmc/host.h> + +#include "pwrseq.h" + +struct mmc_pwrseq_simple { + struct mmc_pwrseq pwrseq; + struct clk *ext_clk; + int nr_gpios; + struct gpio_desc *reset_gpios[0]; +}; + +static void mmc_pwrseq_simple_set_gpios_value(struct mmc_pwrseq_simple *pwrseq, + int value) +{ + int i; + + for (i = 0; i < pwrseq->nr_gpios; i++) + if (!IS_ERR(pwrseq->reset_gpios[i])) + gpiod_set_value_cansleep(pwrseq->reset_gpios[i], value); +} + +static void mmc_pwrseq_simple_pre_power_on(struct mmc_host *host) +{ + struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq, + struct mmc_pwrseq_simple, pwrseq); + + if (!IS_ERR(pwrseq->ext_clk)) + clk_prepare_enable(pwrseq->ext_clk); + + mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); +} + +static void mmc_pwrseq_simple_post_power_on(struct mmc_host *host) +{ + struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq, + struct mmc_pwrseq_simple, pwrseq); + + mmc_pwrseq_simple_set_gpios_value(pwrseq, 0); +} + +static void mmc_pwrseq_simple_power_off(struct mmc_host *host) +{ + struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq, + struct mmc_pwrseq_simple, pwrseq); + + mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); + + if (!IS_ERR(pwrseq->ext_clk)) + clk_disable_unprepare(pwrseq->ext_clk); +} + +static void mmc_pwrseq_simple_free(struct mmc_host *host) +{ + struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq, + struct mmc_pwrseq_simple, pwrseq); + int i; + + for (i = 0; i < pwrseq->nr_gpios; i++) + if (!IS_ERR(pwrseq->reset_gpios[i])) + gpiod_put(pwrseq->reset_gpios[i]); + + if (!IS_ERR(pwrseq->ext_clk)) + clk_put(pwrseq->ext_clk); + + kfree(pwrseq); + host->pwrseq = NULL; +} + +static struct mmc_pwrseq_ops mmc_pwrseq_simple_ops = { + .pre_power_on = mmc_pwrseq_simple_pre_power_on, + .post_power_on = mmc_pwrseq_simple_post_power_on, + .power_off = mmc_pwrseq_simple_power_off, + .free = mmc_pwrseq_simple_free, +}; + +int mmc_pwrseq_simple_alloc(struct mmc_host *host, struct device *dev) +{ + struct mmc_pwrseq_simple *pwrseq; + int i, nr_gpios, ret = 0; + + nr_gpios = of_gpio_named_count(dev->of_node, "reset-gpios"); + if (nr_gpios < 0) + nr_gpios = 0; + + pwrseq = kzalloc(sizeof(struct mmc_pwrseq_simple) + nr_gpios * + sizeof(struct gpio_desc *), GFP_KERNEL); + if (!pwrseq) + return -ENOMEM; + + pwrseq->ext_clk = clk_get(dev, "ext_clock"); + if (IS_ERR(pwrseq->ext_clk) && + PTR_ERR(pwrseq->ext_clk) != -ENOENT && + PTR_ERR(pwrseq->ext_clk) != -ENOSYS) { + ret = PTR_ERR(pwrseq->ext_clk); + goto free; + } + + for (i = 0; i < nr_gpios; i++) { + pwrseq->reset_gpios[i] = gpiod_get_index(dev, "reset", i, + GPIOD_OUT_HIGH); + if (IS_ERR(pwrseq->reset_gpios[i]) && + PTR_ERR(pwrseq->reset_gpios[i]) != -ENOENT && + PTR_ERR(pwrseq->reset_gpios[i]) != -ENOSYS) { + ret = PTR_ERR(pwrseq->reset_gpios[i]); + + while (--i) + gpiod_put(pwrseq->reset_gpios[i]); + + goto clk_put; + } + } + + pwrseq->nr_gpios = nr_gpios; + pwrseq->pwrseq.ops = &mmc_pwrseq_simple_ops; + host->pwrseq = &pwrseq->pwrseq; + + return 0; +clk_put: + if (!IS_ERR(pwrseq->ext_clk)) + clk_put(pwrseq->ext_clk); +free: + kfree(pwrseq); + return ret; +} diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 8232e9a02d40..47dcece97e63 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; @@ -809,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 44c26998027f..6a95adb1ecfa 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -12,3 +12,4 @@ 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_PCI_LAYERSCAPE) += pci-layerscape.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..fc851b73a372 --- /dev/null +++ b/drivers/pci/host/pci-qcom.c @@ -0,0 +1,976 @@ +/* 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_controller 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 regulator *pcie_ext3p3v_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; +} + + +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, + }, + { +#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_controller *chip) +{ + return container_of(chip, struct qcom_msi, chip); +} + +static int qcom_msi_setup_irq(struct msi_controller *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_controller *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; + } + + err = regulator_enable(qcom_pcie->pcie_ext3p3v_supply); + if (err < 0) { + dev_err(qcom_pcie->dev, "failed to enable pcie_ext3p3v 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->pcie_ext3p3v_supply = devm_regulator_get(&pdev->dev, + "ext-3p3v"); + if (IS_ERR(qcom_pcie->pcie_ext3p3v_supply)) { + dev_err(&pdev->dev, "Failed to get pcie_ext3p3v supply\n"); + return PTR_ERR(qcom_pcie->pcie_ext3p3v_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]; + +#ifdef CONFIG_PCI_MSI + hw->msi_ctrl = &qcom_pcie->msi.chip; +#endif + 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 3cd243c26b7d..295b2af4369b 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -60,4 +60,15 @@ config PINCTRL_QCOM_SPMI_PMIC 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 bfd79af5f982..27485f41a225 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_PINCTRL_MSM8960) += pinctrl-msm8960.o obj-$(CONFIG_PINCTRL_MSM8X74) += pinctrl-msm8x74.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-gpio.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-mpp.o +obj-$(CONFIG_PINCTRL_SSBI_PMIC) += pinctrl-ssbi-pmic.o 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/regulator/qcom_rpm-regulator.c b/drivers/regulator/qcom_rpm-regulator.c index 8364ff331a81..edd0a1776936 100644 --- a/drivers/regulator/qcom_rpm-regulator.c +++ b/drivers/regulator/qcom_rpm-regulator.c @@ -205,6 +205,7 @@ static int rpm_reg_write(struct qcom_rpm_reg *vreg, vreg->val[req->word] |= value << req->shift; return qcom_rpm_write(vreg->rpm, + QCOM_RPM_ACTIVE_STATE, vreg->resource, vreg->val, vreg->parts->request_len); diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index fe70960bcc32..389ec3ea7190 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -9,6 +9,17 @@ 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 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. + config QCOM_QFPROM tristate "QCOM QFPROM Interface" depends on ARCH_QCOM && OF diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index a5b892d62cf3..54249f557778 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,2 +1,5 @@ obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o +obj-$(CONFIG_QCOM_PM) += spm.o +CFLAGS_scm.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) +obj-$(CONFIG_QCOM_SCM) += scm.o scm-boot.o scm-pas.o obj-$(CONFIG_QCOM_QFPROM) += qfprom.o diff --git a/arch/arm/mach-qcom/scm-boot.c b/drivers/soc/qcom/scm-boot.c index 45cee3e469a5..f653217ae442 100644 --- a/arch/arm/mach-qcom/scm-boot.c +++ b/drivers/soc/qcom/scm-boot.c @@ -18,8 +18,25 @@ #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> + +#define SCM_FLAG_WARMBOOT_CPU0 0x04 +#define SCM_FLAG_WARMBOOT_CPU1 0x02 +#define SCM_FLAG_WARMBOOT_CPU2 0x10 +#define SCM_FLAG_WARMBOOT_CPU3 0x40 + +struct scm_warmboot { + int flag; + void *entry; +}; + +static struct scm_warmboot scm_flags[] = { + { .flag = SCM_FLAG_WARMBOOT_CPU0 }, + { .flag = SCM_FLAG_WARMBOOT_CPU1 }, + { .flag = SCM_FLAG_WARMBOOT_CPU2 }, + { .flag = SCM_FLAG_WARMBOOT_CPU3 }, +}; /* * Set the cold/warm boot address for one of the CPU cores. @@ -37,3 +54,21 @@ int scm_set_boot_addr(phys_addr_t addr, int flags) &cmd, sizeof(cmd), NULL, 0); } EXPORT_SYMBOL(scm_set_boot_addr); + +int scm_set_warm_boot_addr(void *entry, int cpu) +{ + int ret; + + /* + * Reassign only if we are switching from hotplug entry point + * to cpuidle entry point or vice versa. + */ + if (entry == scm_flags[cpu].entry) + return 0; + + ret = scm_set_boot_addr(virt_to_phys(entry), scm_flags[cpu].flag); + if (!ret) + scm_flags[cpu].entry = entry; + + return ret; +} diff --git a/drivers/soc/qcom/scm-pas.c b/drivers/soc/qcom/scm-pas.c new file mode 100644 index 000000000000..9b32cc478434 --- /dev/null +++ b/drivers/soc/qcom/scm-pas.c @@ -0,0 +1,112 @@ +/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "scm-pas: " fmt + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/dma-mapping.h> + +#include <soc/qcom/scm.h> +#include <asm/cacheflush.h> +#include "scm-pas.h" + +#define PAS_INIT_IMAGE_CMD 1 +#define PAS_MEM_SETUP_CMD 2 +#define PAS_AUTH_AND_RESET_CMD 5 +#define PAS_SHUTDOWN_CMD 6 +#define PAS_IS_SUPPORTED_CMD 7 + + +int pas_init_image(enum pas_id id, const u8 *metadata, size_t size) +{ + int ret; + struct pas_init_image_req { + u32 proc; + u32 image_addr; + } request; + u32 scm_ret = 0; + void *mdata_buf; + dma_addr_t mdata_phys; + DEFINE_DMA_ATTRS(attrs); + + + dma_set_attr(DMA_ATTR_WRITE_BARRIER, &attrs); + mdata_buf = dma_alloc_attrs(NULL, size, &mdata_phys, GFP_KERNEL, + &attrs); + if (!mdata_buf) { + pr_err("Allocation for metadata failed.\n"); + return -ENOMEM; + } + + memcpy(mdata_buf, metadata, size); + + request.proc = id; + request.image_addr = mdata_phys; + + ret = scm_call(SCM_SVC_PIL, PAS_INIT_IMAGE_CMD, &request, + sizeof(request), &scm_ret, sizeof(scm_ret)); + + dma_free_attrs(NULL, size, mdata_buf, mdata_phys, &attrs); + + if (ret) + return ret; + return scm_ret; +} +EXPORT_SYMBOL(pas_init_image); + +int pas_auth_and_reset(enum pas_id id) +{ + int ret; + u32 proc = id, scm_ret = 0; + ret = scm_call(SCM_SVC_PIL, PAS_AUTH_AND_RESET_CMD, &proc, + sizeof(proc), &scm_ret, sizeof(scm_ret)); + if (ret) + scm_ret = ret; + + return scm_ret; +} +EXPORT_SYMBOL(pas_auth_and_reset); + +int pas_shutdown(enum pas_id id) +{ + int ret; + u32 proc = id, scm_ret = 0; + + ret = scm_call(SCM_SVC_PIL, PAS_SHUTDOWN_CMD, &proc, sizeof(proc), + &scm_ret, sizeof(scm_ret)); + if (ret) + return ret; + + return scm_ret; +} +EXPORT_SYMBOL(pas_shutdown); + +int pas_supported(enum pas_id id) +{ + int ret; + u32 periph = id, ret_val = 0; + + if (scm_is_call_available(SCM_SVC_PIL, PAS_IS_SUPPORTED_CMD) <= 0) + return 0; + + ret = scm_call(SCM_SVC_PIL, PAS_IS_SUPPORTED_CMD, &periph, + sizeof(periph), &ret_val, sizeof(ret_val)); + if (ret) + return ret; + + return ret_val; +} +EXPORT_SYMBOL(pas_supported); diff --git a/drivers/soc/qcom/scm-pas.h b/drivers/soc/qcom/scm-pas.h new file mode 100644 index 000000000000..86b5b30f5bea --- /dev/null +++ b/drivers/soc/qcom/scm-pas.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __MSM_SCM_PAS_H +#define __MSM_SCM_PAS_H + +enum pas_id { + PAS_MODEM, + PAS_Q6, + PAS_DSPS, + PAS_TZAPPS, + PAS_MODEM_SW, + PAS_MODEM_FW, + PAS_WCNSS, + PAS_SECAPP, + PAS_GSS, + PAS_VIDC, +}; + +extern int pas_init_image(enum pas_id id, const u8 *metadata, size_t size); +extern int pas_auth_and_reset(enum pas_id id); +extern int pas_shutdown(enum pas_id id); +extern int pas_supported(enum pas_id id); +#endif diff --git a/arch/arm/mach-qcom/scm.c b/drivers/soc/qcom/scm.c index c536fd6bf827..cdcaccc5dec2 100644 --- a/arch/arm/mach-qcom/scm.c +++ b/drivers/soc/qcom/scm.c @@ -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 @@ -152,8 +151,13 @@ static inline void *scm_get_response_buffer(const struct scm_response *rsp) return (void *)rsp + rsp->buf_offset; } -static int scm_remap_error(int err) +static int scm_remap_error(const struct scm_command *cmd, int err) { + u32 svc_id = cmd->id >> 10; + u32 cmd_id = cmd->id & GENMASK(10, 0); + + pr_err("scm_call for svc_id %d & cmd_id %d failed with error code %d\n", + svc_id, cmd_id, err); switch (err) { case SCM_ERROR: return -EIO; @@ -198,18 +202,38 @@ 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); + ret = scm_remap_error(cmd, ret); 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 +244,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 +258,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 +275,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 +292,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 +400,20 @@ 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); + diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c new file mode 100644 index 000000000000..e6fbed081b47 --- /dev/null +++ b/drivers/soc/qcom/spm.c @@ -0,0 +1,339 @@ +/* + * 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/init.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/cpuidle.h> +#include <linux/cpu_pm.h> + +#include <asm/proc-fns.h> +#include <asm/suspend.h> + +#include <soc/qcom/pm.h> +#include <soc/qcom/pm.h> +#include <soc/qcom/scm.h> +#include <soc/qcom/scm-boot.h> + + +#define SCM_CMD_TERMINATE_PC 0x2 +#define SCM_FLUSH_FLAG_MASK 0x3 +#define SCM_L2_ON 0x0 +#define SCM_L2_OFF 0x1 +#define MAX_PMIC_DATA 2 +#define MAX_SEQ_DATA 64 +#define SPM_CTL_INDEX 0x7f +#define SPM_CTL_INDEX_SHIFT 4 +#define SPM_CTL_EN BIT(0) + +enum spm_reg { + SPM_REG_CFG, + SPM_REG_SPM_CTL, + SPM_REG_DLY, + SPM_REG_PMIC_DLY, + SPM_REG_PMIC_DATA_0, + SPM_REG_PMIC_DATA_1, + SPM_REG_VCTL, + SPM_REG_SEQ_ENTRY, + SPM_REG_SPM_STS, + SPM_REG_PMIC_STS, + SPM_REG_NR, +}; + +struct spm_reg_data { + const u8 *reg_offset; + u32 spm_cfg; + u32 spm_dly; + u32 pmic_dly; + u32 pmic_data[MAX_PMIC_DATA]; + u8 seq[MAX_SEQ_DATA]; + u8 start_index[PM_SLEEP_MODE_NR]; +}; + +struct spm_driver_data { + void __iomem *reg_base; + const struct spm_reg_data *reg_data; +}; + +static const u8 spm_reg_offset_v2_1[SPM_REG_NR] = { + [SPM_REG_CFG] = 0x08, + [SPM_REG_SPM_CTL] = 0x30, + [SPM_REG_DLY] = 0x34, + [SPM_REG_SEQ_ENTRY] = 0x80, +}; + +/* SPM register data for 8974, 8084 */ +static const struct spm_reg_data spm_reg_8974_8084_cpu = { + .reg_offset = spm_reg_offset_v2_1, + .spm_cfg = 0x1, + .spm_dly = 0x3C102800, + .seq = { 0x03, 0x0B, 0x0F, 0x00, 0x20, 0x80, 0x10, 0xE8, 0x5B, 0x03, + 0x3B, 0xE8, 0x5B, 0x82, 0x10, 0x0B, 0x30, 0x06, 0x26, 0x30, + 0x0F }, + .start_index[PM_SLEEP_MODE_STBY] = 0, + .start_index[PM_SLEEP_MODE_SPC] = 3, +}; + +static const u8 spm_reg_offset_v1_1[SPM_REG_NR] = { + [SPM_REG_CFG] = 0x08, + [SPM_REG_SPM_CTL] = 0x20, + [SPM_REG_PMIC_DLY] = 0x24, + [SPM_REG_PMIC_DATA_0] = 0x28, + [SPM_REG_PMIC_DATA_1] = 0x2C, + [SPM_REG_SEQ_ENTRY] = 0x80, +}; + +/* SPM register data for 8064 */ +static const struct spm_reg_data spm_reg_8064_cpu = { + .reg_offset = spm_reg_offset_v1_1, + .spm_cfg = 0x1F, + .pmic_dly = 0x02020004, + .pmic_data[0] = 0x0084009C, + .pmic_data[1] = 0x00A4001C, + .seq = { 0x03, 0x0F, 0x00, 0x24, 0x54, 0x10, 0x09, 0x03, 0x01, + 0x10, 0x54, 0x30, 0x0C, 0x24, 0x30, 0x0F }, + .start_index[PM_SLEEP_MODE_STBY] = 0, + .start_index[PM_SLEEP_MODE_SPC] = 2, +}; + +static DEFINE_PER_CPU_SHARED_ALIGNED(struct spm_driver_data *, cpu_spm_drv); + +static inline void spm_register_write(struct spm_driver_data *drv, + enum spm_reg reg, u32 val) +{ + if (drv->reg_data->reg_offset[reg]) + writel_relaxed(val, drv->reg_base + + drv->reg_data->reg_offset[reg]); +} + +/* Ensure a guaranteed write, before return */ +static inline void spm_register_write_sync(struct spm_driver_data *drv, + enum spm_reg reg, u32 val) +{ + u32 ret; + + if (!drv->reg_data->reg_offset[reg]) + return; + + do { + writel_relaxed(val, drv->reg_base + + drv->reg_data->reg_offset[reg]); + ret = readl_relaxed(drv->reg_base + + drv->reg_data->reg_offset[reg]); + if (ret == val) + break; + cpu_relax(); + } while (1); +} + +static inline u32 spm_register_read(struct spm_driver_data *drv, + enum spm_reg reg) +{ + return readl_relaxed(drv->reg_base + drv->reg_data->reg_offset[reg]); +} + +static void spm_set_low_power_mode(enum pm_sleep_mode mode) +{ + struct spm_driver_data *drv = per_cpu(cpu_spm_drv, + smp_processor_id()); + u32 start_index; + u32 ctl_val; + + start_index = drv->reg_data->start_index[mode]; + + ctl_val = spm_register_read(drv, SPM_REG_SPM_CTL); + ctl_val &= ~(SPM_CTL_INDEX << SPM_CTL_INDEX_SHIFT); + ctl_val |= start_index << SPM_CTL_INDEX_SHIFT; + ctl_val |= SPM_CTL_EN; + spm_register_write_sync(drv, SPM_REG_SPM_CTL, ctl_val); +} + +static int qcom_pm_collapse(unsigned long int unused) +{ + int ret; + u32 flag; + int cpu = smp_processor_id(); + + ret = scm_set_warm_boot_addr(cpu_resume, cpu); + if (ret) + return ret; + + flag = SCM_L2_ON & SCM_FLUSH_FLAG_MASK; + ret = scm_call_atomic1(SCM_SVC_BOOT, SCM_CMD_TERMINATE_PC, flag); + + /* + * Returns here only if there was a pending interrupt and we did not + * power down as a result. + */ + /* Hack:: Ignore scm call return values in power down path */ + return 0; +} + +static int qcom_cpu_standby(void *unused) +{ + spm_set_low_power_mode(PM_SLEEP_MODE_STBY); + cpu_do_idle(); + + return 0; +} + +static int qcom_cpu_spc(void *unused) +{ + int ret; + + spm_set_low_power_mode(PM_SLEEP_MODE_STBY); + cpu_pm_enter(); + ret = cpu_suspend(0, qcom_pm_collapse); + cpu_pm_exit(); + + return ret; +} + +static struct spm_driver_data *spm_get_drv(struct platform_device *pdev, + int *spm_cpu) +{ + struct spm_driver_data *drv = NULL; + struct device_node *cpu_node, *saw_node; + int cpu; + bool found; + + for_each_possible_cpu(cpu) { + cpu_node = of_cpu_device_node_get(cpu); + if (!cpu_node) + continue; + saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0); + found = (saw_node == pdev->dev.of_node); + of_node_put(saw_node); + of_node_put(cpu_node); + if (found) + break; + } + + if (found) { + drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); + if (drv) + *spm_cpu = cpu; + } + + return drv; +} + +static const struct of_device_id spm_match_table[] = { + { .compatible = "qcom,msm8974-saw2-v2.1-cpu", + .data = &spm_reg_8974_8084_cpu }, + { .compatible = "qcom,apq8084-saw2-v2.1-cpu", + .data = &spm_reg_8974_8084_cpu }, + { .compatible = "qcom,apq8064-saw2-v1.1-cpu", + .data = &spm_reg_8064_cpu }, + { }, +}; + +static int spm_dev_probe(struct platform_device *pdev) +{ + struct spm_driver_data *drv; + struct resource *res; + const struct of_device_id *match_id; + void __iomem *addr; + int cpu; + struct cpuidle_device *dev; + + drv = spm_get_drv(pdev, &cpu); + if (!drv) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drv->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(drv->reg_base)) + return PTR_ERR(drv->reg_base); + + match_id = of_match_node(spm_match_table, pdev->dev.of_node); + if (!match_id) + return -ENODEV; + + drv->reg_data = match_id->data; + + /* Write the SPM sequences first.. */ + addr = drv->reg_base + drv->reg_data->reg_offset[SPM_REG_SEQ_ENTRY]; + __iowrite32_copy(addr, drv->reg_data->seq, + ARRAY_SIZE(drv->reg_data->seq) / 4); + + /* + * ..and then the control registers. + * On some SoC if the control registers are written first and if the + * CPU was held in reset, the reset signal could trigger the SPM state + * machine, before the sequences are completely written. + */ + spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg); + spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly); + spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly); + spm_register_write(drv, SPM_REG_PMIC_DATA_0, + drv->reg_data->pmic_data[0]); + spm_register_write(drv, SPM_REG_PMIC_DATA_1, + drv->reg_data->pmic_data[1]); + + per_cpu(cpu_spm_drv, cpu) = drv; + + /* Register the cpuidle device for the cpu, we are ready for cpuidle */ + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->cpu = cpu; + return cpuidle_register_device(dev); +} + +static struct qcom_cpu_pm_ops lpm_ops = { + .standby = qcom_cpu_standby, + .spc = qcom_cpu_spc, +}; + +static struct platform_device qcom_cpuidle_drv = { + .name = "qcom_cpuidle", + .id = -1, + .dev.platform_data = &lpm_ops, +}; + +static struct platform_driver spm_driver = { + .probe = spm_dev_probe, + .driver = { + .name = "saw", + .of_match_table = spm_match_table, + }, +}; + +static int __init qcom_spm_init(void) +{ + int ret; + + /* + * cpuidle driver need to registered before the cpuidle device + * for any cpu. Register the device for the the cpuidle driver. + */ + ret = platform_device_register(&qcom_cpuidle_drv); + if (ret) + return ret; + + return platform_driver_register(&spm_driver); +} +module_init(qcom_spm_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SAW power controller driver"); +MODULE_ALIAS("platform:saw"); 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 Binary files differnew file mode 100644 index 000000000000..67f2a17eaaa8 --- /dev/null +++ b/fixup.bin 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-ipq806x.h b/include/dt-bindings/clock/qcom,gcc-ipq806x.h index b857cadb0bd4..04fb29ae30e6 100644 --- a/include/dt-bindings/clock/qcom,gcc-ipq806x.h +++ b/include/dt-bindings/clock/qcom,gcc-ipq806x.h @@ -238,7 +238,6 @@ #define PLL0_VOTE 221 #define PLL3 222 #define PLL3_VOTE 223 -#define PLL4 224 #define PLL4_VOTE 225 #define PLL8 226 #define PLL8_VOTE 227 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/clock/qcom,lcc-ipq806x.h b/include/dt-bindings/clock/qcom,lcc-ipq806x.h new file mode 100644 index 000000000000..4e944b85c56d --- /dev/null +++ b/include/dt-bindings/clock/qcom,lcc-ipq806x.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 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 _DT_BINDINGS_CLK_LCC_IPQ806X_H +#define _DT_BINDINGS_CLK_LCC_IPQ806X_H + +#define PLL4 0 +#define MI2S_OSR_SRC 1 +#define MI2S_OSR_CLK 2 +#define MI2S_DIV_CLK 3 +#define MI2S_BIT_DIV_CLK 4 +#define MI2S_BIT_CLK 5 +#define PCM_SRC 6 +#define PCM_CLK_OUT 7 +#define PCM_CLK 8 +#define SPDIF_SRC 9 +#define SPDIF_CLK 10 +#define AHBIX_CLK 11 + +#endif diff --git a/include/dt-bindings/clock/qcom,lcc-msm8960.h b/include/dt-bindings/clock/qcom,lcc-msm8960.h new file mode 100644 index 000000000000..4fb2aa64d9fe --- /dev/null +++ b/include/dt-bindings/clock/qcom,lcc-msm8960.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 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 _DT_BINDINGS_CLK_LCC_MSM8960_H +#define _DT_BINDINGS_CLK_LCC_MSM8960_H + +#define PLL4 0 +#define MI2S_OSR_SRC 1 +#define MI2S_OSR_CLK 2 +#define MI2S_DIV_CLK 3 +#define MI2S_BIT_DIV_CLK 4 +#define MI2S_BIT_CLK 5 +#define PCM_SRC 6 +#define PCM_CLK_OUT 7 +#define PCM_CLK 8 +#define SLIMBUS_SRC 9 +#define AUDIO_SLIMBUS_CLK 10 +#define SPS_SLIMBUS_CLK 11 +#define CODEC_I2S_MIC_OSR_SRC 12 +#define CODEC_I2S_MIC_OSR_CLK 13 +#define CODEC_I2S_MIC_DIV_CLK 14 +#define CODEC_I2S_MIC_BIT_DIV_CLK 15 +#define CODEC_I2S_MIC_BIT_CLK 16 +#define SPARE_I2S_MIC_OSR_SRC 17 +#define SPARE_I2S_MIC_OSR_CLK 18 +#define SPARE_I2S_MIC_DIV_CLK 19 +#define SPARE_I2S_MIC_BIT_DIV_CLK 20 +#define SPARE_I2S_MIC_BIT_CLK 21 +#define CODEC_I2S_SPKR_OSR_SRC 22 +#define CODEC_I2S_SPKR_OSR_CLK 23 +#define CODEC_I2S_SPKR_DIV_CLK 24 +#define CODEC_I2S_SPKR_BIT_DIV_CLK 25 +#define CODEC_I2S_SPKR_BIT_CLK 26 +#define SPARE_I2S_SPKR_OSR_SRC 27 +#define SPARE_I2S_SPKR_OSR_CLK 28 +#define SPARE_I2S_SPKR_DIV_CLK 29 +#define SPARE_I2S_SPKR_BIT_DIV_CLK 30 +#define SPARE_I2S_SPKR_BIT_CLK 31 + +#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/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 d936409520f8..af779266f0bc 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -179,6 +179,7 @@ struct clk_ops { struct clk_hw **best_parent_hw); 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, @@ -352,6 +353,17 @@ struct clk_divider { #define CLK_DIVIDER_READ_ONLY BIT(5) extern const struct clk_ops clk_divider_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, @@ -382,11 +394,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; @@ -396,11 +410,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, @@ -409,7 +429,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); @@ -552,6 +574,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_hw **best_parent_p); +long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate, + unsigned long *best_parent_rate, + struct clk_hw **best_parent_p); /* * FIXME clock api without lock protection diff --git a/include/linux/memblock.h b/include/linux/memblock.h index e8cc45307f8f..96b7073ad452 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -285,6 +285,7 @@ phys_addr_t memblock_end_of_DRAM(void); void memblock_enforce_memory_limit(phys_addr_t memory_limit); int memblock_is_memory(phys_addr_t addr); int memblock_is_region_memory(phys_addr_t base, phys_addr_t size); +int memblock_overlaps_memory(phys_addr_t base, phys_addr_t size); int memblock_is_reserved(phys_addr_t addr); int memblock_is_region_reserved(phys_addr_t base, phys_addr_t size); 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..a7af2edf0f64 --- /dev/null +++ b/include/linux/mfd/qcom_rpm.h @@ -0,0 +1,14 @@ +#ifndef __QCOM_RPM_H__ +#define __QCOM_RPM_H__ + +#include <linux/types.h> + +struct qcom_rpm; + +#define QCOM_RPM_ACTIVE_STATE 0 +#define QCOM_RPM_SLEEP_STATE 1 + +int qcom_rpm_write(struct qcom_rpm *rpm, int state, int resource, u32 *buf, size_t count); +int qcom_rpm_read(struct qcom_rpm *rpm, int resource, u32 *buf, size_t count); + +#endif diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 9f322706f7cb..8946d9978920 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -197,6 +197,7 @@ struct mmc_context_info { }; struct regulator; +struct mmc_pwrseq; struct mmc_supply { struct regulator *vmmc; /* Card power supply */ @@ -208,6 +209,7 @@ struct mmc_host { struct device class_dev; int index; const struct mmc_host_ops *ops; + struct mmc_pwrseq *pwrseq; unsigned int f_min; unsigned int f_max; unsigned int f_init; diff --git a/include/soc/qcom/pm.h b/include/soc/qcom/pm.h new file mode 100644 index 000000000000..d9a56d7c0206 --- /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 pm_sleep_mode { + PM_SLEEP_MODE_STBY, + PM_SLEEP_MODE_RET, + PM_SLEEP_MODE_SPC, + PM_SLEEP_MODE_PC, + PM_SLEEP_MODE_NR, +}; + +struct qcom_cpu_pm_ops { + int (*standby)(void *data); + int (*spc)(void *data); +}; + +#endif /* __QCOM_PM_H */ diff --git a/arch/arm/mach-qcom/scm-boot.h b/include/soc/qcom/scm-boot.h index 6aabb2428176..529f55ae07f7 100644 --- a/arch/arm/mach-qcom/scm-boot.h +++ b/include/soc/qcom/scm-boot.h @@ -16,9 +16,8 @@ #define SCM_FLAG_COLDBOOT_CPU1 0x01 #define SCM_FLAG_COLDBOOT_CPU2 0x08 #define SCM_FLAG_COLDBOOT_CPU3 0x20 -#define SCM_FLAG_WARMBOOT_CPU0 0x04 -#define SCM_FLAG_WARMBOOT_CPU1 0x02 int scm_set_boot_addr(phys_addr_t addr, int flags); +int scm_set_warm_boot_addr(void *entry, int cpu); #endif diff --git a/arch/arm/mach-qcom/scm.h b/include/soc/qcom/scm.h index 00b31ea58f29..e486266c2009 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,26 @@ #define SCM_SVC_BOOT 0x1 #define SCM_SVC_PIL 0x2 +#define SCM_SVC_UTIL 0x3 +#define SCM_SVC_TZ 0x4 +#define SCM_SVC_IO 0x5 +#define SCM_SVC_INFO 0x6 +#define SCM_SVC_SSD 0x7 +#define SCM_SVC_FUSE 0x8 +#define SCM_SVC_PWR 0x9 +#define SCM_SVC_CP 0xC +#define SCM_SVC_DCVS 0xD +#define SCM_SVC_TZSCHEDULER 0xFC 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); #endif diff --git a/mm/memblock.c b/mm/memblock.c index 252b77bdf65e..d02ae8f9599b 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -1437,6 +1437,12 @@ int __init_memblock memblock_is_region_memory(phys_addr_t base, phys_addr_t size memblock.memory.regions[idx].size) >= end; } +int __init_memblock memblock_overlaps_memory(phys_addr_t base, phys_addr_t size) +{ + memblock_cap_size(base, &size); + return memblock_overlaps_region(&memblock.memory, base, size) >= 0; +} + /** * memblock_is_region_reserved - check if a region intersects reserved memory * @base: base of region to check diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 7633c503a116..269fc93f8973 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -5005,6 +5005,8 @@ static void __paginginit free_area_init_core(struct pglist_data *pgdat, static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat) { + unsigned long __maybe_unused offset = 0; + /* Skip empty nodes */ if (!pgdat->node_spanned_pages) return; @@ -5021,6 +5023,7 @@ static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat) * for the buddy allocator to function correctly. */ start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1); + offset = pgdat->node_start_pfn - start; end = pgdat_end_pfn(pgdat); end = ALIGN(end, MAX_ORDER_NR_PAGES); size = (end - start) * sizeof(struct page); @@ -5028,7 +5031,7 @@ static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat) if (!map) map = memblock_virt_alloc_node_nopanic(size, pgdat->node_id); - pgdat->node_mem_map = map + (pgdat->node_start_pfn - start); + pgdat->node_mem_map = map + offset; } #ifndef CONFIG_NEED_MULTIPLE_NODES /* @@ -5036,10 +5039,13 @@ static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat) */ if (pgdat == NODE_DATA(0)) { mem_map = NODE_DATA(0)->node_mem_map; -#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP - if (page_to_pfn(mem_map) != pgdat->node_start_pfn) - mem_map -= (pgdat->node_start_pfn - ARCH_PFN_OFFSET); -#endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */ +#if defined(CONFIG_HAVE_MEMBLOCK_NODE_MAP) || defined(CONFIG_FLATMEM) + if (page_to_pfn(mem_map) != pgdat->node_start_pfn) { + if (IS_ENABLED(CONFIG_HAVE_MEMBLOCK_NODE_MAP)) + offset = pgdat->node_start_pfn - ARCH_PFN_OFFSET; + mem_map -= offset; + } +#endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP || CONFIG_FLATMEM */ } #endif #endif /* CONFIG_FLAT_NODE_MEM_MAP */ |