diff options
192 files changed, 22104 insertions, 941 deletions
diff --git a/Documentation/ABI/testing/sysfs-driver-sunxi-sid b/Documentation/ABI/testing/sysfs-driver-sunxi-sid deleted file mode 100644 index ffb9536f6ecc..000000000000 --- a/Documentation/ABI/testing/sysfs-driver-sunxi-sid +++ /dev/null @@ -1,22 +0,0 @@ -What: /sys/devices/*/<our-device>/eeprom -Date: August 2013 -Contact: Oliver Schinagl <oliver@schinagl.nl> -Description: read-only access to the SID (Security-ID) on current - A-series SoC's from Allwinner. Currently supports A10, A10s, A13 - and A20 CPU's. The earlier A1x series of SoCs exports 16 bytes, - whereas the newer A20 SoC exposes 512 bytes split into sections. - Besides the 16 bytes of SID, there's also an SJTAG area, - HDMI-HDCP key and some custom keys. Below a quick overview, for - details see the user manual: - 0x000 128 bit root-key (sun[457]i) - 0x010 128 bit boot-key (sun7i) - 0x020 64 bit security-jtag-key (sun7i) - 0x028 16 bit key configuration (sun7i) - 0x02b 16 bit custom-vendor-key (sun7i) - 0x02c 320 bit low general key (sun7i) - 0x040 32 bit read-control access (sun7i) - 0x064 224 bit low general key (sun7i) - 0x080 2304 bit HDCP-key (sun7i) - 0x1a0 768 bit high general key (sun7i) -Users: any user space application which wants to read the SID on - Allwinner's A-series of CPU's. diff --git a/Documentation/devicetree/bindings/arm/cpus.txt b/Documentation/devicetree/bindings/arm/cpus.txt index d6b794cef0b8..d190415b2357 100644 --- a/Documentation/devicetree/bindings/arm/cpus.txt +++ b/Documentation/devicetree/bindings/arm/cpus.txt @@ -185,6 +185,8 @@ nodes to be present and contain the properties described below. be one of: "psci" "spin-table" + "qcom,arm-cortex-acc" + # On ARM 32-bit systems this property is optional and can be one of: "allwinner,sun6i-a31" diff --git a/Documentation/devicetree/bindings/arm/msm/acc.txt b/Documentation/devicetree/bindings/arm/msm/acc.txt new file mode 100644 index 000000000000..ae2d7253b363 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/acc.txt @@ -0,0 +1,19 @@ +Application Processor Sub-system (APSS) Application Clock Controller (ACC) + +The ACC provides clock, power domain, and reset control to a CPU. There is one ACC +register region per CPU within the APSS remapped region as well as an alias register +region that remaps accesses to the ACC associated with the CPU accessing the region. + +Required properties: +- compatible: Must be "qcom,arm-cortex-acc" +- reg: The first element specifies the base address and size of + the register region. An optional second element specifies + the base address and size of the alias register region. + +Example: + + clock-controller@b088000 { + compatible = "qcom,arm-cortex-acc"; + reg = <0x0b088000 0x1000>, + <0x0b008000 0x1000>; + } diff --git a/Documentation/devicetree/bindings/arm/msm/ids.txt b/Documentation/devicetree/bindings/arm/msm/ids.txt new file mode 100644 index 000000000000..9ee8428f4670 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/ids.txt @@ -0,0 +1,65 @@ +* MSM-ID + +The qcom,msm-id entry specifies the MSM chipset and hardware revision. It can +optionally be an array of these to indicate multiple hardware that use the same +device tree. It is expected that the bootloader will use this information at +boot-up to decide which device tree to use when given multiple device trees, +some of which may not be compatible with the actual hardware. It is the +bootloader's responsibility to pass the correct device tree to the kernel. + +PROPERTIES + +- qcom,msm-id: + Usage: required + Value type: <prop-encoded-array> (<chipset_id, rev_id> [, <c2, r2> ..]) + Definition: + The "chipset_id" consists of three fields as below: + + bits 0-15 = The unique MSM chipset id. + bits 16-31 = Reserved. Should be 0 + + chipset_id is an exact match value + + The "rev_id" is a chipset specific 32-bit id that represents + the version of the chipset. + + The rev_id is a best match id. The bootloader will look for + the closest possible patch. + +* BOARD-ID + +The qcom,board-id entry specifies the board type and revision information. It +can optionally be an array of these to indicate multiple boards that use the +same device tree. It is expected that the bootloader will use this information +at boot-up to decide which device tree to use when given multiple device trees, +some of which may not be compatible with the actual hardware. It is the +bootloader's responsibility to pass the correct device tree to the kernel. + +PROPERTIES + +- qcom,board-id: + Usage: required + Value type: <prop-encoded-array> (<board_id, subtype_id> [, <b2, s2> ..]) + Definition: + The "board_id" consists of three fields as below: + + bits 31-24 = Unusued. + bits 23-16 = Platform Version Major + bits 15-8 = Platfrom Version Minor + bits 7-0 = Platform Type + + Platform Type field is an exact match value. The Platform + Major/Minor field is a best match. The bootloader will look + for the closest possible match. + + The "subtype_id" is unique to a Platform Type/Chipset ID. For + a given Platform Type, there will typically only be a single + board and the subtype_id will be 0. However in some cases board + variants may need to be distinquished by different subtype_id + values. + + subtype_id is an exact match value. + +EXAMPLE: + qcom,board-id = <15 2>; + qcom,msm-id = <0x1007e 0>; diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt index 1333db9acfee..382a574a5c55 100644 --- a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt +++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt @@ -21,10 +21,17 @@ PROPERTIES the register region. An optional second element specifies the base address and size of the alias register region. +- clock-output-names: + Usage: optional + Value type: <string> + Definition: Name of the output clock. Typically acpuX_aux where X is a + CPU number starting at 0. + Example: clock-controller@2088000 { compatible = "qcom,kpss-acc-v2"; reg = <0x02088000 0x1000>, <0x02008000 0x1000>; + clock-output-names = "acpu0_aux"; }; diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt new file mode 100644 index 000000000000..d1e12f16a28c --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt @@ -0,0 +1,28 @@ +Krait Processor Sub-system (KPSS) Global Clock Controller (GCC) + +PROPERTIES + +- compatible: + Usage: required + Value type: <string> + Definition: should be one of: + "qcom,kpss-gcc" + +- reg: + Usage: required + Value type: <prop-encoded-array> + Definition: base address and size of the register region + +- clock-output-names: + Usage: required + Value type: <string> + Definition: Name of the output clock. Typically acpu_l2_aux indicating + an L2 cache auxiliary clock. + +Example: + + l2cc: clock-controller@2011000 { + compatible = "qcom,kpss-gcc"; + reg = <0x2011000 0x1000>; + clock-output-names = "acpu_l2_aux"; + }; diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt b/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt new file mode 100644 index 000000000000..e7cb10426a3b --- /dev/null +++ b/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt @@ -0,0 +1,38 @@ +Qualcomm Process Voltage Scaling Tables + +The node name is required to be "qcom,pvs". There shall only be one +such node present in the root of the tree. + +PROPERTIES + +- qcom,pvs-format-a or qcom,pvs-format-b: + Usage: required + Value type: <empty> + Definition: Indicates the format of qcom,speedX-pvsY-bin-vZ properties. + If qcom,pvs-format-a is used the table is two columns + (frequency and voltage in that order). If qcom,pvs-format-b is used the table is three columns (frequency, voltage, + and current in that order). + +- qcom,speedX-pvsY-bin-vZ: + Usage: required + Value type: <prop-encoded-array> + Definition: The PVS table corresponding to the speed bin X, pvs bin Y, + and version Z. +Example: + + qcom,pvs { + qcom,pvs-format-a; + qcom,speed0-pvs0-bin-v0 = + < 384000000 950000 >, + < 486000000 975000 >, + < 594000000 1000000 >, + < 702000000 1025000 >, + < 810000000 1075000 >, + < 918000000 1100000 >, + < 1026000000 1125000 >, + < 1134000000 1175000 >, + < 1242000000 1200000 >, + < 1350000000 1225000 >, + < 1458000000 1237500 >, + < 1512000000 1250000 >; + }; diff --git a/Documentation/devicetree/bindings/clock/qcom,a53cc b/Documentation/devicetree/bindings/clock/qcom,a53cc new file mode 100644 index 000000000000..209cae8afc1f --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,a53cc @@ -0,0 +1,22 @@ +A53 Clock Controller + +Required properties : +- compatible : shall contain: + "qcom,a53cc" +- reg : shall contain base register location and length + of the A53 PLL +- #clock-cells : shall contain 1 +- qcom,apcs : phandle of apcs syscon node + +Example: + apcs: syscon@b011000 { + compatible = "syscon"; + reg = <0x0b011000 0x1000>; + }; + + a53cc: clock-controller@0b016000 { + compatible = "qcom,clock-a53-msm8916"; + reg = <0x0b016000 0x40>; + #clock-cells = <1>; + qcom,apcs = <&apcs>; + }; diff --git a/Documentation/devicetree/bindings/clock/qcom,hfpll.txt b/Documentation/devicetree/bindings/clock/qcom,hfpll.txt new file mode 100644 index 000000000000..fee92bb30344 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,hfpll.txt @@ -0,0 +1,40 @@ +High-Frequency PLL (HFPLL) + +PROPERTIES + +- compatible: + Usage: required + Value type: <string> + Definition: must be "qcom,hfpll" + +- reg: + Usage: required + Value type: <prop-encoded-array> + Definition: address and size of HPLL registers. An optional second + element specifies the address and size of the alias + register region. + +- clock-output-names: + Usage: required + Value type: <string> + Definition: Name of the PLL. Typically hfpllX where X is a CPU number + starting at 0. Otherwise hfpll_Y where Y is more specific + such as "l2". + +Example: + +1) An HFPLL for the L2 cache. + + clock-controller@f9016000 { + compatible = "qcom,hfpll"; + reg = <0xf9016000 0x30>; + clock-output-names = "hfpll_l2"; + }; + +2) An HFPLL for CPU0. This HFPLL has the alias register region. + + clock-controller@f908a000 { + compatible = "qcom,hfpll"; + reg = <0xf908a000 0x30>, <0xf900a000 0x30>; + clock-output-names = "hfpll0"; + }; diff --git a/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt b/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt new file mode 100644 index 000000000000..874138f88ec6 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt @@ -0,0 +1,22 @@ +Krait Clock Controller + +PROPERTIES + +- compatible: + Usage: required + Value type: <string> + Definition: must be one of: + "qcom,krait-cc-v1" + "qcom,krait-cc-v2" + +- #clock-cells: + Usage: required + Value type: <u32> + Definition: must be 1 + +Example: + + kraitcc: clock-controller { + compatible = "qcom,krait-cc-v1"; + #clock-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt b/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt new file mode 100644 index 000000000000..ceef5e66fa24 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt @@ -0,0 +1,15 @@ +Qualcomm RPM Clock Controller Binding +------------------------------------------------ + +Required properties : +- compatible : shall contain only one of the following: + + "qcom,rpmcc-msm8916" + +- #clock-cells : shall contain 1 + +Example: + rpmcc: rpmcc { + compatible = "qcom,rpmcc-msm8916"; + #clock-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/drm/msm/dsi.txt b/Documentation/devicetree/bindings/drm/msm/dsi.txt index cd8fe6cf536c..6ccd8608fe91 100644 --- a/Documentation/devicetree/bindings/drm/msm/dsi.txt +++ b/Documentation/devicetree/bindings/drm/msm/dsi.txt @@ -30,11 +30,11 @@ Optional properties: - panel@0: Node of panel connected to this DSI controller. See files in Documentation/devicetree/bindings/panel/ for each supported panel. -- qcom,dual-panel-mode: Boolean value indicating if the DSI controller is +- qcom,dual-dsi-mode: Boolean value indicating if the DSI controller is driving a panel which needs 2 DSI links. -- qcom,master-panel: Boolean value indicating if the DSI controller is driving +- qcom,master-dsi: Boolean value indicating if the DSI controller is driving the master link of the 2-DSI panel. -- qcom,sync-dual-panel: Boolean value indicating if the DSI controller is +- qcom,sync-dual-dsi: Boolean value indicating if the DSI controller is driving a 2-DSI panel whose 2 links need receive command simultaneously. - interrupt-parent: phandle to the MDP block if the interrupt signal is routed through MDP block @@ -90,9 +90,9 @@ Example: qcom,dsi-phy = <&mdss_dsi_phy0>; - qcom,dual-panel-mode; - qcom,master-panel; - qcom,sync-dual-panel; + qcom,dual-dsi-mode; + qcom,master-dsi; + qcom,sync-dual-dsi; panel: panel@0 { compatible = "sharp,lq101r1sx01"; diff --git a/Documentation/devicetree/bindings/mfd/qcom-rpm-smd.txt b/Documentation/devicetree/bindings/mfd/qcom-rpm-smd.txt new file mode 100644 index 000000000000..9434c73f0d25 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/qcom-rpm-smd.txt @@ -0,0 +1,137 @@ +Qualcomm Resource Power Manager (RPM) over SMD + +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-msm8974" + "qcom,rpm-msm8916" + +- qcom,smd-channels: + Usage: required + Value type: <stringlist> + Definition: Shared Memory channel used for communication with the RPM + += SUBDEVICES + +The RPM exposes resources to its subnodes. The below bindings specify the set +of valid subnodes that can operate on these resources. + +== Regulators + +Regulator nodes are identified by their compatible: + +- compatible: + Usage: required + Value type: <string> + Definition: must be one of: + "qcom,rpm-pm8841-regulators" + "qcom,rpm-pm8941-regulators" + "qcom,rpm-pm8916-regulators" + +- vdd_s1-supply: +- vdd_s2-supply: +- vdd_s3-supply: +- vdd_s4-supply: +- vdd_s5-supply: +- vdd_s6-supply: +- vdd_s7-supply: +- vdd_s8-supply: + Usage: optional (pm8841 only) + Value type: <phandle> + Definition: reference to regulator supplying the input pin, as + described in the data sheet + +- vdd_s1-supply: +- vdd_s2-supply: +- vdd_s3-supply: +- vdd_l1_l3-supply: +- vdd_l2_lvs1_2_3-supply: +- vdd_l4_l11-supply: +- vdd_l5_l7-supply: +- vdd_l6_l12_l14_l15-supply: +- vdd_l8_l16_l18_l19-supply: +- vdd_l9_l10_l17_l22-supply: +- vdd_l13_l20_l23_l24-supply: +- vdd_l21-supply: +- vin_5vs-supply: + Usage: optional (pm8941 only) + Value type: <phandle> + Definition: reference to regulator supplying the input pin, as + described in the data sheet + +- vdd_s1-supply: +- vdd_s2-supply: +- vdd_s3-supply: +- vdd_s4-supply: +- l1_l2_l3-supply: +- l4_l5_l6-supply: +- l7-supply: +- l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18-supply: + + Usage: optional (pm8916 only) + Value type: <phandle> + Definition: reference to regulator supplying the input pin, as + described in the data sheet + +The regulator node houses sub-nodes for each regulator within the device. Each +sub-node is identified using the node's name, with valid values listed for each +of the pmics below. + +pm8841: + s1, s2, s3, s4, s5, s6, s7, s8 + +pm8941: + s1, s2, s3, s4, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, + l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, lvs1, lvs2, + lvs3, 5vs1, 5vs2 + +pm8916: + s1, s2, s3, s4, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, + l14, l15, l16, l17, l18 + +The content of each sub-node is defined by the standard binding for regulators - +see regulator.txt. + += EXAMPLE + + smd { + compatible = "qcom,smd"; + + rpm { + interrupts = <0 168 1>; + qcom,ipc = <&apcs 8 0>; + qcom,smd-edge = <15>; + + rpm_requests { + compatible = "qcom,rpm-msm8974"; + qcom,smd-channels = "rpm_requests"; + + pm8941-regulators { + compatible = "qcom,rpm-pm8941-regulators"; + vdd_l13_l20_l23_l24-supply = <&pm8941_boost>; + + pm8941_s3: s3 { + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + pm8941_boost: s4 { + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + }; + + pm8941_l20: l20 { + regulator-min-microvolt = <2950000>; + regulator-max-microvolt = <2950000>; + }; + }; + }; + }; + }; + diff --git a/Documentation/devicetree/bindings/misc/allwinner,sunxi-sid.txt b/Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt index fabdf64a5737..d543ed3f5363 100644 --- a/Documentation/devicetree/bindings/misc/allwinner,sunxi-sid.txt +++ b/Documentation/devicetree/bindings/nvmem/allwinner,sunxi-sid.txt @@ -4,6 +4,10 @@ Required properties: - compatible: "allwinner,sun4i-a10-sid" or "allwinner,sun7i-a20-sid" - reg: Should contain registers location and length += Data cells = +Are child nodes of qfprom, bindings of which as described in +bindings/nvmem/nvmem.txt + Example for sun4i: sid@01c23800 { compatible = "allwinner,sun4i-a10-sid"; diff --git a/Documentation/devicetree/bindings/nvmem/nvmem.txt b/Documentation/devicetree/bindings/nvmem/nvmem.txt new file mode 100644 index 000000000000..d1a37e7b1068 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/nvmem.txt @@ -0,0 +1,85 @@ += NVMEM(Non Volatile Memory) Data Device Tree Bindings = + +This binding is intended to represent the location of hardware +configuration data stored in NVMEMs like eeprom, efuses and so on. + +On a significant proportion of boards, the manufacturer has stored +some data on NVMEM, for the OS to be able to retrieve these information +and act upon it. Obviously, the OS has to know about where to retrieve +these data from, and where they are stored on the storage device. + +This document is here to document this. + += Data providers = +Contains bindings specific to provider drivers and data cells as children +of this node. + +Optional properties: + read-only: Mark the provider as read only. + += Data cells = +These are the child nodes of the provider which contain data cell +information like offset and size in nvmem provider. + +Required properties: +reg: specifies the offset in byte within that storage device, start bit + in the byte and the length in bits of the data we care about. + There could be more than one offset-length pairs in this property. + +Optional properties: + +bit-offset: specifies the offset in bit within the address range specified + by reg property. Can take values from 0-7. +nbits: specifies number of bits this cell occupies starting from bit-offset. + +For example: + + /* Provider */ + qfprom: qfprom@00700000 { + ... + + /* Data cells */ + tsens_calibration: calib@404 { + reg = <0x404 0x10>; + }; + + tsens_calibration_bckp: calib_bckp@504 { + reg = <0x504 0x11>; + bit-offset = 6; + nbits = 128; + }; + + pvs_version: pvs-version@6 { + reg = <0x6 0x2> + bit-offset = 7; + nbits = 2; + }; + + speed_bin: speed-bin@c{ + reg = <0xc 0x1>; + bit-offset = 2; + nbits = 3; + + }; + ... + }; + += Data consumers = +Are device nodes which consume nvmem data cells/providers. + +Required-properties: +nvmem-cell: list of phandle to the nvmem data cells. +nvmem-cell-names: names for the each nvmem-cell specified. Required if + nvmem-cell is used. + +Optional-properties: +nvmem : list of phandles to nvmem providers. +nvmem-names: names for the each nvmem provider. required if nvmem is used. + +For example: + + tsens { + ... + nvmem-cell = <&tsens_calibration>; + nvmem-cell-names = "calibration"; + }; diff --git a/Documentation/devicetree/bindings/nvmem/qfprom.txt b/Documentation/devicetree/bindings/nvmem/qfprom.txt new file mode 100644 index 000000000000..8a8d55fb2b67 --- /dev/null +++ b/Documentation/devicetree/bindings/nvmem/qfprom.txt @@ -0,0 +1,35 @@ += Qualcomm QFPROM device tree bindings = + +This binding is intended to represent QFPROM which is found in most QCOM SOCs. + +Required properties: +- compatible: should be "qcom,qfprom" +- reg: Should contain registers location and length + += Data cells = +Are child nodes of qfprom, bindings of which as described in +bindings/nvmem/nvmem.txt + +Example: + + qfprom: qfprom@00700000 { + compatible = "qcom,qfprom"; + reg = <0x00700000 0x8000>; + ... + /* Data cells */ + tsens_calibration: calib@404 { + reg = <0x4404 0x10>; + }; + }; + + += Data consumers = +Are device nodes which consume nvmem data cells. + +For example: + + tsens { + ... + nvmem-cell = <&tsens_calibration>; + nvmem-cell-names = "calibration"; + }; diff --git a/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt index ed19991aad35..d7803a2a94e9 100644 --- a/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt +++ b/Documentation/devicetree/bindings/pinctrl/qcom,pmic-mpp.txt @@ -7,8 +7,13 @@ of PMIC's from Qualcomm. Usage: required Value type: <string> Definition: Should contain one of: + "qcom,pm8018-mpp", + "qcom,pm8038-mpp", + "qcom,pm8821-mpp", "qcom,pm8841-mpp", "qcom,pm8916-mpp", + "qcom,pm8917-mpp", + "qcom,pm8921-mpp", "qcom,pm8941-mpp", "qcom,pma8084-mpp", @@ -77,12 +82,9 @@ to specify in a pin configuration subnode: Value type: <string> Definition: Specify the alternative function to be configured for the specified pins. Valid values are: - "normal", - "paired", - "dtest1", - "dtest2", - "dtest3", - "dtest4" + "digital", + "analog", + "sink" - bias-disable: Usage: optional @@ -127,12 +129,18 @@ to specify in a pin configuration subnode: Definition: Selects the power source for the specified pins. Valid power sources are defined in <dt-bindings/pinctrl/qcom,pmic-mpp.h> -- qcom,analog-mode: +- qcom,analog-level: Usage: optional - Value type: <none> - Definition: Selects Analog mode of operation: combined with input-enable - and/or output-high, output-low MPP could operate as - Bidirectional Logic, Analog Input, Analog Output. + Value type: <u32> + Definition: Selects the source for analog output. Valued values are + defined in <dt-binding/pinctrl/qcom,pmic-mpp.h> + PMIC_MPP_AOUT_LVL_* + +- qcom,dtest: + Usage: optional + Value type: <u32> + Definition: Selects which dtest rail to be routed in the various functions. + Valid values are 1-4 - qcom,amux-route: Usage: optional @@ -140,6 +148,10 @@ to specify in a pin configuration subnode: Definition: Selects the source for analog input. Valid values are defined in <dt-bindings/pinctrl/qcom,pmic-mpp.h> PMIC_MPP_AMUX_ROUTE_CH5, PMIC_MPP_AMUX_ROUTE_CH6... +- qcom,paired: + Usage: optional + Value type: <none> + Definition: Indicates that the pin should be operating in paired mode. Example: @@ -156,7 +168,7 @@ Example: pm8841_default: default { gpio { pins = "mpp1", "mpp2", "mpp3", "mpp4"; - function = "normal"; + function = "digital"; input-enable; power-source = <PM8841_MPP_S3>; }; diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,smd.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,smd.txt new file mode 100644 index 000000000000..f65c76db9859 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smd.txt @@ -0,0 +1,79 @@ +Qualcomm Shared Memory Driver (SMD) binding + +This binding describes the Qualcomm Shared Memory Driver, a fifo based +communication channel for sending data between the various subsystems in +Qualcomm platforms. + +- compatible: + Usage: required + Value type: <stringlist> + Definition: must be "qcom,smd" + += EDGES + +Each subnode of the SMD node represents a remote subsystem or a remote +processor of some sort - or in SMD language an "edge". The name of the edges +are not important. +The edge is described by the following properties: + +- interrupts: + Usage: required + Value type: <prop-encoded-array> + Definition: should specify the IRQ used by the remote processor to + signal this processor about communication related updates + +- qcom,ipc: + Usage: required + Value type: <prop-encoded-array> + Definition: three entries specifying the outgoing ipc bit used for + signaling the remote processor: + - 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 + +- qcom,smd-edge: + Usage: required + Value type: <u32> + Definition: the identifier of the remote processor in the smd channel + allocation table + += SMD DEVICES + +In turn, subnodes of the "edges" represent devices tied to SMD channels on that +"edge". The names of the devices are not important. The properties of these +nodes are defined by the individual bindings for the SMD devices - but must +contain the following property: + +- qcom,smd-channels: + Usage: required + Value type: <stringlist> + Definition: a list of channels tied to this device, used for matching + the device to channels + += EXAMPLE + +The following example represents a smd node, with one edge representing the +"rpm" subsystem. For the "rpm" subsystem we have a device tied to the +"rpm_request" channel. + + apcs: syscon@f9011000 { + compatible = "syscon"; + reg = <0xf9011000 0x1000>; + }; + + smd { + compatible = "qcom,smd"; + + rpm { + interrupts = <0 168 1>; + qcom,ipc = <&apcs 8 0>; + qcom,smd-edge = <15>; + + rpm_requests { + compatible = "qcom,rpm-msm8974"; + qcom,smd-channels = "rpm_requests"; + + ... + }; + }; + }; diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt new file mode 100644 index 000000000000..19cad6cc427c --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt @@ -0,0 +1,51 @@ +Qualcomm Shared Memory Manager binding + +This binding describes the Qualcomm Shared Memory Manager, used to share data +between various subsystems and OSes in Qualcomm platforms. + +- compatible: + Usage: required + Value type: <stringlist> + Definition: must be: + "qcom,smem" + +- memory-region: + Usage: required + Value type: <prop-encoded-array> + Definition: handle to memory reservation for main SMEM memory region. + +- reg: + Usage: optional + Value type: <prop-encoded-array> + Definition: base address and size pair for any additional memory areas + of the shared memory. + +- hwlocks: + Usage: required + Value type: <prop-encoded-array> + Definition: reference to a hwspinlock used to protect allocations from + the shared memory + += EXAMPLE +The following example shows the SMEM setup for MSM8974, with a main SMEM region +at 0xfa00000 and an auxiliary region at 0xfc428000: + + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + smem_region: smem@fa00000 { + reg = <0xfa00000 0x200000>; + no-map; + }; + }; + + smem@fa00000 { + compatible = "qcom,smem"; + + memory-region = <&smem_region>; + reg = <0xfc428000 0x4000>; + + hwlocks = <&tcsr_mutex 3>; + }; diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.txt b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt new file mode 100644 index 000000000000..cfb79fc80316 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt @@ -0,0 +1,37 @@ +* QCOM SoC Temperature Sensor (TSENS) + +Required properties: +- compatible : + - "qcom,msm8916-tsens" : For 8916 Family of SoCs + - "qcom,msm8974-tsens" : For 8974 Family of SoCs + - "qcom,msm8960-tsens" : For 8960 Family of SoCs + +- reg: Address range of the thermal registers +- qcom,tsens-slopes : Must contain slope value for each of the sensors controlled + by this device +- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description. +- Refer to Documentation/devicetree/bindings/eeprom/eeprom.txt to know how to specify +an eeprom cell + +Optional properties: +- qcom,sensor-id: List of sensor instances used in a given SoC. A TSENS IP can + have a fixed number of sensors (like 11) but a given SoC can + use only 5 of these and they might not always the first 5. They + could be sensors 0, 1, 4, 8 and 9. This property is used to + describe the subset of the sensors used. If this property is + missing they are assumed to be the first 'n' sensors numbered + sequentially in which case the number of sensors defaults to + the number of slope values. + +Example: +tsens: thermal-sensor@900000 { + compatible = "qcom,msm8916-tsens"; + qcom,tsens-slopes = <1176 1176 1154 1176 1111 + 1132 1132 1199 1132 1199 + 1132>; + calib_data = <&tsens_caldata>; + calib_sel = <&tsens_calsel>; + qcom,tsens-slopes = <3200 3200 3200 3200 3200>; + qcom,sensor-id = <0 1 2 4 5>; + #thermal-sensor-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt index 553e2fae3a76..b423e0d282a0 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt @@ -30,6 +30,11 @@ Optional properties: argument that indicate usb controller index - disable-over-current: (FSL only) disable over current detect - external-vbus-divider: (FSL only) enables off-chip resistor divider for Vbus +- extcon: phandles to external connector devices. First phandle should point to + external connector, which provide "USB" cable events, the second should point + to external connector device, which provide "USB-HOST" cable events. If one + of the external connector devices is not required, empty <0> phandle should + be specified. Example: @@ -41,4 +46,5 @@ Example: phys = <&usb_phy0>; phy-names = "usb-phy"; vbus-supply = <®_usb0_vbus>; + extcon = <0>, <&usb_id>; }; diff --git a/Documentation/devicetree/bindings/usb/msm-hsusb.txt b/Documentation/devicetree/bindings/usb/msm-hsusb.txt index bd8d9e753029..8654a3ec23e4 100644 --- a/Documentation/devicetree/bindings/usb/msm-hsusb.txt +++ b/Documentation/devicetree/bindings/usb/msm-hsusb.txt @@ -52,6 +52,10 @@ Required properties: Optional properties: - dr_mode: One of "host", "peripheral" or "otg". Defaults to "otg" +- switch-gpio: A phandle + gpio-specifier pair. Some boards are using Dual + SPDT USB Switch, witch is cotrolled by GPIO to de/multiplex + D+/D- USB lines between connectors. + - qcom,phy-init-sequence: PHY configuration sequence values. This is related to Device Mode Eye Diagram test. Start address at which these values will be written is ULPI_EXT_VENDOR_SPECIFIC. Value of -1 is reserved as diff --git a/Documentation/nvmem/nvmem.txt b/Documentation/nvmem/nvmem.txt new file mode 100644 index 000000000000..28b357976143 --- /dev/null +++ b/Documentation/nvmem/nvmem.txt @@ -0,0 +1,151 @@ + NVMEM SUBSYSTEM + Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + +This document explains the Simple NVMEM Framework along with the APIs provided, +and how-to-use. + +1. Introduction +=============== +*NVMEM* is the abbreviation for Non Volatile Memory layer. It is used to +retrieve configuration or SOC or Device specific data from a non volatile memories +like eeprom, efuses and so on. + +Up until now, NVMEM drivers like eeprom were stored in drivers/misc, where they +all had to duplicate pretty much the same code to register a sysfs file, allow +in-kernel users to access the content of the devices they were driving, etc. + +This was also a problem as far as other in-kernel users were involved, since +the solutions used were pretty much different from on driver to another, there +was a rather big abstraction leak. + +Introduction of this framework aims at solving this. It also introduces DT +representation for consumer devices to go get the data they require (MAC +Addresses, SoC/Revision ID, part numbers, and so on) from the NVMEMs. +This framework is based on regmap, so that most of the abstraction +available in regmap can be reused, across multiple types of buses. + +NVMEM Providers ++++++++++++++++ + +NVMEM provider refers to an entity that implements methods to initialize, read +and write the non-volatile memory. + +2. Registering/Unregistering the NVMEM provider +=============================================== + +A NVMEM provider can register with NVMEM core by suppling relevant +nvmem configuration to nvmem_register(), on success core would return a valid +nvmem_device pointer. + +nvmem_unregister(nvmem) is used to unregister the already registered provider. + +For example for simple qfprom case: + +static struct nvmem_config econfig = { + .name = "qfprom", + .owner = THIS_MODULE, +}; + +static int qfprom_probe(struct platform_device *pdev) +{ + ... + econfig.dev = &pdev->dev; + nvmem = nvmem_register(&econfig); + ... +} + +It is mandatory that the NVMEM provider has a regmap associated with its +struct device. + +NVMEM Consumers ++++++++++++++++ + +NVMEM consumers are the entities which make use of the NVMEM provider to +read/write into NVMEM. + +3. NVMEM cell based consumer APIs. +================================= + +NVMEM cells are the data entries/fields in the NVMEM. +The NVMEM framework provides 3 APIs to read/write NVMEM cells. + +struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *name); +struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *name); + +void nvmem_cell_put(struct nvmem_cell *cell); +void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell); + +void *nvmem_cell_read(struct nvmem_cell *cell, ssize_t *len); +int nvmem_cell_write(struct nvmem_cell *cell, void *buf, ssize_t len); + +*nvmem_cell_get() apis will get a reference to nvmem cell for a given id, +and nvmem_cell_read/write() can then directly read or write to the cell. +Once the usage of the cell is finished the consumer should call *nvmem_cell_put() +to free all the allocation memory for the cell. + +4. Direct NVMEM device based consumer APIs. +========================================== + +In some instances it is necessary to directly read/write the NVMEM. +To facilitate such consumers NVMEM framework provides below apis. + +struct nvmem_device *nvmem_device_get(struct device *dev, const char *name); +struct nvmem_device *devm_nvmem_device_get(struct device *dev, + const char *name); +void nvmem_device_put(struct nvmem_device *nvmem); +int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset, + size_t bytes, void *buf); +int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset, + size_t bytes, void *buf); +int nvmem_device_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf); +int nvmem_device_cell_write(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf); + +Before the consumers can read/write NVMEM directly, it should get hold +of nvmem_controller from one of the *nvmem_device_get() api. + +Difference between these apis and cell based apis is that these apis +always take nvmem_device as parameter. + +5. Releasing a reference to the NVMEM +===================================== + +When the consumers no longer needs the NVMEM, it has to release the reference +to the NVMEM it has obtained using the APIs mentioned in the above section. +NVMEM framework provides 2 APIs to release a reference to the NVMEM. + +void nvmem_cell_put(struct nvmem_cell *cell); +void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell); +void nvmem_device_put(struct nvmem_device *nvmem); +void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem); + +Both these APIs are used to release a reference to the NVMEM and +devm_nvmem_cell_put and devm_nvmem_device_put destroys the devres associated +with this NVMEM. + +Userspace ++++++++++ + +6. Userspace binary interface. +============================== + +Userspace can read/write the raw NVMEM file located at /sys/class/nvmem/*/nvmem + +ex: + +hexdump /sys/class/nvmem/qfprom0/nvmem + +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +* +00000a0 db10 2240 0000 e000 0c00 0c00 0000 0c00 +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +... +* +0001000 + +7. DeviceTree Binding +===================== + +The documentation for NVMEM dt binding can be found @ +Documentation/devicetree/bindings/nvmem/nvmem.txt diff --git a/MAINTAINERS b/MAINTAINERS index 8133cefb6b6e..b69c11fa761b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7263,6 +7263,15 @@ S: Supported F: drivers/block/nvme* F: include/linux/nvme.h +NVMEM FRAMEWORK +M: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> +M: Maxime Ripard <maxime.ripard@free-electrons.com> +S: Maintained +F: drivers/nvmem/ +F: Documentation/devicetree/bindings/nvmem/ +F: include/linux/nvmem-provider.h +F: include/linux/nvmem-consumer.h + NXP-NCI NFC DRIVER M: Clément Perrochaud <clement.perrochaud@effinnov.com> R: Charles Gorand <charles.gorand@effinnov.com> diff --git a/arch/arm/boot/dts/qcom-apq8064-cm-qs600.dts b/arch/arm/boot/dts/qcom-apq8064-cm-qs600.dts index 71512b3ca444..4c280dbdd885 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"; @@ -67,11 +69,22 @@ bias-pull-down; }; + pm8921_l5: l5 { + regulator-min-microvolt = <2750000>; + regulator-max-microvolt = <3000000>; + bias-pull-down; + }; + pm8921_l23: l23 { regulator-min-microvolt = <1700000>; regulator-max-microvolt = <1900000>; bias-pull-down; }; + + pm8921_lvs6: lvs6 { + bias-pull-down; + }; + }; }; @@ -140,19 +153,66 @@ status = "okay"; }; + /* on board fixed 3.3v supply */ + v3p3_fixed: v3p3 { + 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_fixed>; + 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 { status = "okay"; + vmmc-supply = <&pm8921_l5>; + vqmmc-supply = <&pm8921_s4>; }; /* External micro SD card */ sdcc3: sdcc@12180000 { status = "okay"; + vmmc-supply = <&v3p3_fixed>; }; /* WLAN */ sdcc4: sdcc@121c0000 { status = "okay"; + vmmc-supply = <&v3p3_fixed>; + vqmmc-supply = <&v3p3_fixed>; + 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 a7c939ba8873..cff88d4232bc 100644 --- a/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts +++ b/arch/arm/boot/dts/qcom-apq8064-ifc6410.dts @@ -1,5 +1,6 @@ #include "qcom-apq8064-v2.0.dtsi" #include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/pinctrl/qcom,pmic-gpio.h> / { model = "Qualcomm APQ8064/IFC6410"; @@ -7,6 +8,7 @@ aliases { serial0 = &gsbi7_serial; + serial1 = &gsbi6_serial; }; soc { @@ -61,6 +63,12 @@ qcom,switch-mode-frequency = <3200000>; }; + pm8921_l2: l2 { + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + bias-pull-down; + }; + pm8921_l3: l3 { regulator-min-microvolt = <3050000>; regulator-max-microvolt = <3300000>; @@ -73,6 +81,12 @@ bias-pull-down; }; + pm8921_l5: l5 { + regulator-min-microvolt = <2750000>; + regulator-max-microvolt = <3000000>; + bias-pull-down; + }; + pm8921_l6: l6 { regulator-min-microvolt = <2950000>; regulator-max-microvolt = <2950000>; @@ -84,9 +98,53 @@ regulator-max-microvolt = <1900000>; bias-pull-down; }; + + pm8921_lvs1: lvs1 { + bias-pull-down; + }; + + pm8921_lvs6: lvs6 { + bias-pull-down; + }; + + pm8921_lvs7: lvs7 { + bias-pull-down; + }; }; }; + 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; + }; + + hdmi: qcom,hdmi-tx@4a00000 { + status = "okay"; + core-vdda-supply = <&pm8921_hdmi_switch>; + hdmi-mux-supply = <&ext_3p3v>; + }; + + mdp: qcom,mdp@5100000 { + status = "okay"; + qcom,lvds-panel = <&panel>; + lvds-vccs-3p3v-supply = <&ext_3p3v>; + lvds-pll-vdda-supply = <&pm8921_l2>; + lvds-vdda-supply = <&pm8921_lvs7>; + }; + + panel: auo,b101xtn01 { + status = "okay"; + compatible = "auo,b101xtn01"; + enable-gpio = <&tlmm_pinmux 36 GPIO_ACTIVE_HIGH>; + }; + gsbi3: gsbi@16200000 { status = "okay"; qcom,mode = <GSBI_PROT_I2C>; @@ -115,6 +173,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>; @@ -170,16 +240,91 @@ usb4: usb@12530000 { status = "okay"; }; + + /* 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>; + }; + + leds { + compatible = "gpio-leds"; + pinctrl-names = "default"; + pinctrl-0 = <¬ify_led>; + + led@1 { + label = "apq8064:green:user1"; + gpios = <&pm8921_gpio 18 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + default-state = "on"; + }; + }; + + 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>; + }; + }; + + notify_led: nled { + pios { + pins = "gpio18"; + function = "normal"; + bias-disable; + power-source = <PM8921_GPIO_S4>; + }; + }; + }; + }; + }; + sdcc4_pwrseq:pwrseq { + compatible = "mmc-pwrseq-simple"; + reset-gpios = <&pm8921_gpio 43 GPIO_ACTIVE_LOW>, + <&pm8921_gpio 44 GPIO_ACTIVE_LOW>; + }; amba { /* eMMC */ sdcc1: sdcc@12400000 { status = "okay"; + vmmc-supply = <&pm8921_l5>; + vqmmc-supply = <&pm8921_s4>; }; /* External micro SD card */ sdcc3: sdcc@12180000 { status = "okay"; + vmmc-supply = <&pm8921_l6>; pinctrl-names = "default"; pinctrl-0 = <&card_detect>; cd-gpios = <&tlmm_pinmux 26 GPIO_ACTIVE_LOW>; @@ -187,6 +332,9 @@ /* WLAN */ sdcc4: sdcc@121c0000 { status = "okay"; + vmmc-supply = <&ext_3p3v>; + vqmmc-supply = <&pm8921_lvs1>; + mmc-pwrseq = <&sdcc4_pwrseq>; }; }; }; diff --git a/arch/arm/boot/dts/qcom-apq8064.dtsi b/arch/arm/boot/dts/qcom-apq8064.dtsi index df2061ec630d..9cc7da535569 100644 --- a/arch/arm/boot/dts/qcom-apq8064.dtsi +++ b/arch/arm/boot/dts/qcom-apq8064.dtsi @@ -1,12 +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/soc/qcom,gsbi.h> +#include <dt-bindings/mfd/qcom-rpm.h> #include <dt-bindings/interrupt-controller/arm-gic.h> - +#include <dt-bindings/gpio/gpio.h> / { model = "Qualcomm APQ8064"; compatible = "qcom,apq8064"; @@ -25,6 +27,9 @@ qcom,acc = <&acc0>; qcom,saw = <&saw0>; cpu-idle-states = <&CPU_SPC>; + clocks = <&kraitcc 0>; + clock-names = "cpu"; + clock-latency = <100000>; }; cpu@1 { @@ -36,6 +41,9 @@ qcom,acc = <&acc1>; qcom,saw = <&saw1>; cpu-idle-states = <&CPU_SPC>; + clocks = <&kraitcc 1>; + clock-names = "cpu"; + clock-latency = <100000>; }; cpu@2 { @@ -47,6 +55,9 @@ qcom,acc = <&acc2>; qcom,saw = <&saw2>; cpu-idle-states = <&CPU_SPC>; + clocks = <&kraitcc 2>; + clock-names = "cpu"; + clock-latency = <100000>; }; cpu@3 { @@ -58,6 +69,9 @@ qcom,acc = <&acc3>; qcom,saw = <&saw3>; cpu-idle-states = <&CPU_SPC>; + clocks = <&kraitcc 3>; + clock-names = "cpu"; + clock-latency = <100000>; }; L2: l2-cache { @@ -76,11 +90,309 @@ }; }; + thermal-zones { + cpu-thermal0 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 7>; + + trips { + cpu_alert0: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit0: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal1 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 8>; + + trips { + cpu_alert1: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit1: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal2 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 9>; + + trips { + cpu_alert2: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit2: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal3 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 10>; + + trips { + cpu_alert3: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit3: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + }; + cpu-pmu { compatible = "qcom,krait-pmu"; 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>; @@ -107,6 +419,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"; @@ -127,6 +453,13 @@ function = "gsbi3"; }; }; + + uart_pins: uart_pins { + mux { + pins = "gpio14", "gpio15", "gpio16", "gpio17"; + function = "gsbi6"; + }; + }; }; intc: interrupt-controller@2000000 { @@ -148,24 +481,35 @@ 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"; }; saw0: power-controller@2089000 { @@ -249,7 +593,6 @@ #address-cells = <1>; #size-cells = <1>; ranges; - i2c3: i2c@16280000 { compatible = "qcom,i2c-qup-v1.1.1"; reg = <0x16280000 0x1000>; @@ -260,6 +603,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; + + gsbi6_serial: 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"; @@ -287,6 +651,66 @@ 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>; + + }; + + pm8921_mpps: mpps@50 { + compatible = "qcom,pm8921-mpp"; + reg = <0x50>; + gpio-controller; + #gpio-cells = <2>; + interrupts = + <128 1>, <129 1>, <130 1>, <131 1>, + <132 1>, <133 1>, <134 1>, <135 1>, + <136 1>, <137 1>, <138 1>, <139 1>; + }; + + }; + }; + + qfprom: qfprom@00700000 { + compatible = "qcom,qfprom"; + reg = <0x00700000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + tsens_calib: calib { + reg = <0x404 0x10>; + }; + tsens_backup: backup_calib { + reg = <0x414 0x10>; + }; }; gcc: clock-controller@900000 { @@ -294,6 +718,18 @@ reg = <0x00900000 0x4000>; #clock-cells = <1>; #reset-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + tsens: thermal-sensor@900000 { + compatible = "qcom,msm8960-tsens"; + nvmem-cell = <&tsens_calib>, <&tsens_backup>; + nvmem-cell-names = "calib", "calib_backup"; + qcom,tsens-slopes = <1176 1176 1154 1176 1111 + 1132 1132 1199 1132 1199 1132>; + #thermal-sensor-cells = <1>; + }; }; lcc: clock-controller@28000000 { @@ -319,12 +755,19 @@ compatible = "qcom,rpm-apq8064"; reg = <0x108000 0x1000>; qcom,ipc = <&l2cc 0x8 2>; + #address-cells = <1>; + #size-cells = <0>; interrupts = <GIC_SPI 19 IRQ_TYPE_EDGE_RISING>, <GIC_SPI 21 IRQ_TYPE_EDGE_RISING>, <GIC_SPI 22 IRQ_TYPE_EDGE_RISING>; interrupt-names = "ack", "err", "wakeup"; + rpmcc: rpm-clock-controller { + compatible = "qcom,apq8064-rpm-clk"; + #clock-cells = <1>; + #reset-cells = <1>; + }; regulators { compatible = "qcom,rpm-pm8921-regulators"; @@ -334,7 +777,46 @@ }; }; - usb1_phy: phy@12500000 { + + /* 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 = <GIC_SPI 100 IRQ_TYPE_NONE>; @@ -448,14 +930,6 @@ }; /* Temporary fixed regulator */ - vsdcc_fixed: vsdcc-regulator { - compatible = "regulator-fixed"; - regulator-name = "SDCC Power"; - regulator-min-microvolt = <2700000>; - regulator-max-microvolt = <2700000>; - regulator-always-on; - }; - sdcc1bam:dma@12402000{ compatible = "qcom,bam-v1.3.0"; reg = <0x12402000 0x8000>; @@ -505,7 +979,6 @@ non-removable; cap-sd-highspeed; cap-mmc-highspeed; - vmmc-supply = <&vsdcc_fixed>; dmas = <&sdcc1bam 2>, <&sdcc1bam 1>; dma-names = "tx", "rx"; }; @@ -524,7 +997,6 @@ cap-mmc-highspeed; max-frequency = <192000000>; no-1-8-v; - vmmc-supply = <&vsdcc_fixed>; dmas = <&sdcc3bam 2>, <&sdcc3bam 1>; dma-names = "tx", "rx"; }; @@ -542,8 +1014,6 @@ cap-sd-highspeed; cap-mmc-highspeed; max-frequency = <48000000>; - vmmc-supply = <&vsdcc_fixed>; - vqmmc-supply = <&vsdcc_fixed>; dmas = <&sdcc4bam 2>, <&sdcc4bam 1>; dma-names = "tx", "rx"; pinctrl-names = "default"; @@ -555,5 +1025,169 @@ compatible = "qcom,tcsr-apq8064", "syscon"; reg = <0x1a400000 0x100>; }; + + 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>; + 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>; + }; + }; + }; + + mdp: qcom,mdp@5100000 { + compatible = "qcom,mdp"; + #stream-id-cells = <2>; + reg = <0x05100000 0xf0000>; + interrupts = <GIC_SPI 75 0>; + connectors = <&hdmi>; + gpus = <&gpu>; + 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>; + }; + + 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 7084010ee61b..9d3978c2159b 100644 --- a/arch/arm/boot/dts/qcom-apq8084.dtsi +++ b/arch/arm/boot/dts/qcom-apq8084.dtsi @@ -75,6 +75,88 @@ }; }; + thermal-zones { + cpu-thermal0 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 5>; + + trips { + cpu_alert0: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit0: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal1 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 6>; + + trips { + cpu_alert1: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit1: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal2 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 7>; + + trips { + cpu_alert2: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit2: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal3 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 8>; + + trips { + cpu_alert3: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit3: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + }; + cpu-pmu { compatible = "qcom,krait-pmu"; interrupts = <1 7 0xf04>; @@ -103,6 +185,29 @@ <0xf9002000 0x1000>; }; + qfprom: qfprom@fc4bc000 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qfprom"; + reg = <0xfc4bc000 0x1000>; + tsens_calib: calib@d0 { + reg = <0xd0 0x18>; + }; + tsens_backup: backup@440 { + reg = <0x440 0x10>; + }; + }; + + tsens: thermal-sensor@fc4a8000 { + compatible = "qcom,msm8974-tsens"; + reg = <0xfc4a8000 0x2000>; + nvmem-cell = <&tsens_calib>, <&tsens_backup>; + nvmem-cell-names = "calib", "calib_backup"; + qcom,tsens-slopes = <3200 3200 3200 3200 3200 3200 + 3200 3200 3200 3200 3200>; + #thermal-sensor-cells = <1>; + }; + timer@f9020000 { #address-cells = <1>; #size-cells = <1>; @@ -221,6 +326,7 @@ compatible = "qcom,gcc-apq8084"; #clock-cells = <1>; #reset-cells = <1>; + #power-domain-cells = <1>; reg = <0xfc400000 0x4000>; }; diff --git a/arch/arm/boot/dts/qcom-msm8960.dtsi b/arch/arm/boot/dts/qcom-msm8960.dtsi index a02b984cc68d..4cb1e875647a 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>; @@ -108,11 +149,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 37b47b5538b8..ae6becf1a47b 100644 --- a/arch/arm/boot/dts/qcom-msm8974.dtsi +++ b/arch/arm/boot/dts/qcom-msm8974.dtsi @@ -14,7 +14,7 @@ #size-cells = <0>; interrupts = <1 9 0xf04>; - cpu@0 { + cpu0: cpu@0 { compatible = "qcom,krait"; enable-method = "qcom,kpss-acc-v2"; device_type = "cpu"; @@ -23,9 +23,12 @@ qcom,acc = <&acc0>; qcom,saw = <&saw0>; cpu-idle-states = <&CPU_SPC>; + clocks = <&kraitcc 0>; + clock-names = "cpu"; + clock-latency = <100000>; }; - cpu@1 { + cpu1: cpu@1 { compatible = "qcom,krait"; enable-method = "qcom,kpss-acc-v2"; device_type = "cpu"; @@ -34,9 +37,12 @@ qcom,acc = <&acc1>; qcom,saw = <&saw1>; cpu-idle-states = <&CPU_SPC>; + clocks = <&kraitcc 1>; + clock-names = "cpu"; + clock-latency = <100000>; }; - cpu@2 { + cpu2: cpu@2 { compatible = "qcom,krait"; enable-method = "qcom,kpss-acc-v2"; device_type = "cpu"; @@ -45,9 +51,12 @@ qcom,acc = <&acc2>; qcom,saw = <&saw2>; cpu-idle-states = <&CPU_SPC>; + clocks = <&kraitcc 2>; + clock-names = "cpu"; + clock-latency = <100000>; }; - cpu@3 { + cpu3: cpu@3 { compatible = "qcom,krait"; enable-method = "qcom,kpss-acc-v2"; device_type = "cpu"; @@ -56,6 +65,9 @@ qcom,acc = <&acc3>; qcom,saw = <&saw3>; cpu-idle-states = <&CPU_SPC>; + clocks = <&kraitcc 3>; + clock-names = "cpu"; + clock-latency = <100000>; }; L2: l2-cache { @@ -75,6 +87,88 @@ }; }; + thermal-zones { + cpu-thermal0 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 5>; + + trips { + cpu_alert0: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit0: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal1 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 6>; + + trips { + cpu_alert1: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit1: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal2 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 7>; + + trips { + cpu_alert2: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit2: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal3 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 8>; + + trips { + cpu_alert3: trip@0 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit3: trip@1 { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + }; + cpu-pmu { compatible = "qcom,krait-pmu"; interrupts = <1 7 0xf04>; @@ -89,6 +183,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>; @@ -103,6 +458,29 @@ <0xf9002000 0x1000>; }; + qfprom: qfprom@fc4bc000 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "qcom,qfprom"; + reg = <0xfc4bc000 0x1000>; + tsens_calib: calib@d0 { + reg = <0xd0 0x18>; + }; + tsens_backup: backup@440 { + reg = <0x440 0x10>; + }; + }; + + tsens: thermal-sensor@fc4a8000 { + compatible = "qcom,msm8974-tsens"; + reg = <0xfc4a8000 0x2000>; + nvmem-cell = <&tsens_calib>, <&tsens_backup>; + nvmem-cell-names = "calib", "calib_backup"; + qcom,tsens-slopes = <3200 3200 3200 3200 3200 3200 + 3200 3200 3200 3200 3200>; + #thermal-sensor-cells = <1>; + }; + timer@f9020000 { #address-cells = <1>; #size-cells = <1>; @@ -182,7 +560,37 @@ reg = <0xf90b9000 0x1000>, <0xf9009000 0x1000>; }; - saw_l2: power-controller@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"; + }; + + saw_l2: regulator@f9012000 { compatible = "qcom,saw2"; reg = <0xf9012000 0x1000>; regulator; @@ -217,6 +625,7 @@ compatible = "qcom,gcc-msm8974"; #clock-cells = <1>; #reset-cells = <1>; + #power-domain-cells = <1>; reg = <0xfc400000 0x4000>; }; @@ -224,6 +633,7 @@ compatible = "qcom,mmcc-msm8974"; #clock-cells = <1>; #reset-cells = <1>; + #power-domain-cells = <1>; reg = <0xfd8c0000 0x6000>; }; 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 6ee5959a813b..6764ed0ba87d 100644 --- a/arch/arm/common/Makefile +++ b/arch/arm/common/Makefile @@ -7,6 +7,7 @@ obj-y += firmware.o obj-$(CONFIG_ICST) += icst.o obj-$(CONFIG_SA1111) += sa1111.o obj-$(CONFIG_DMABOUNCE) += dmabounce.o +obj-$(CONFIG_KRAIT_L2_ACCESSORS) += krait-l2-accessors.o obj-$(CONFIG_SHARP_LOCOMO) += locomo.o obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o obj-$(CONFIG_SHARP_SCOOP) += scoop.o diff --git a/arch/arm/common/krait-l2-accessors.c b/arch/arm/common/krait-l2-accessors.c new file mode 100644 index 000000000000..5d514bbc88a6 --- /dev/null +++ b/arch/arm/common/krait-l2-accessors.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/spinlock.h> +#include <linux/export.h> + +#include <asm/barrier.h> +#include <asm/krait-l2-accessors.h> + +static DEFINE_RAW_SPINLOCK(krait_l2_lock); + +void krait_set_l2_indirect_reg(u32 addr, u32 val) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&krait_l2_lock, flags); + /* + * Select the L2 window by poking l2cpselr, then write to the window + * via l2cpdr. + */ + asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr)); + isb(); + asm volatile ("mcr p15, 3, %0, c15, c0, 7 @ l2cpdr" : : "r" (val)); + isb(); + + raw_spin_unlock_irqrestore(&krait_l2_lock, flags); +} +EXPORT_SYMBOL(krait_set_l2_indirect_reg); + +u32 krait_get_l2_indirect_reg(u32 addr) +{ + u32 val; + unsigned long flags; + + raw_spin_lock_irqsave(&krait_l2_lock, flags); + /* + * Select the L2 window by poking l2cpselr, then read from the window + * via l2cpdr. + */ + asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr)); + isb(); + asm volatile ("mrc p15, 3, %0, c15, c0, 7 @ l2cpdr" : "=r" (val)); + + raw_spin_unlock_irqrestore(&krait_l2_lock, flags); + + return val; +} +EXPORT_SYMBOL(krait_get_l2_indirect_reg); diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig index 6d83a1bf0c74..3150d529757b 100644 --- a/arch/arm/configs/multi_v7_defconfig +++ b/arch/arm/configs/multi_v7_defconfig @@ -120,8 +120,15 @@ 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_ARM_CPUIDLE=y +CONFIG_ARM_ZYNQ_CPUIDLE=y +CONFIG_ARM_QCOM_CPUIDLE=y CONFIG_NEON=y CONFIG_KERNEL_MODE_NEON=y CONFIG_ARM_ZYNQ_CPUIDLE=y @@ -149,10 +156,20 @@ CONFIG_CAN_DEV=y CONFIG_CAN_AT91=m CONFIG_CAN_XILINXCAN=y CONFIG_CAN_MCP251X=y -CONFIG_BT=m +CONFIG_BT=y CONFIG_BT_MRVL=m CONFIG_BT_MRVL_SDIO=m +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=m +CONFIG_CFG80211_WEXT=y CONFIG_MAC80211=m CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y @@ -198,6 +215,7 @@ CONFIG_SATA_RCAR=y CONFIG_NETDEVICES=y CONFIG_HIX5HD2_GMAC=y CONFIG_SUN4I_EMAC=y +CONFIG_ATL1C=y CONFIG_MACB=y CONFIG_NET_CALXEDA_XGMAC=y CONFIG_IGB=y @@ -220,6 +238,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 @@ -324,9 +346,16 @@ 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_MSM8960=y +CONFIG_PINCTRL_MSM8X74=y +CONFIG_PINCTRL_QCOM_SPMI_PMIC=y +CONFIG_PINCTRL_QCOM_SSBI_PMIC=y CONFIG_GPIO_SYSFS=y CONFIG_GPIO_GENERIC_PLATFORM=y CONFIG_GPIO_DAVINCI=y @@ -365,6 +394,7 @@ CONFIG_DAVINCI_WATCHDOG=m CONFIG_EXYNOS_THERMAL=m CONFIG_ST_THERMAL_SYSCFG=y CONFIG_ST_THERMAL_MEMMAP=y +CONFIG_QCOM_TSENS=y CONFIG_WATCHDOG=y CONFIG_XILINX_WATCHDOG=y CONFIG_ARM_SP805_WATCHDOG=y @@ -383,6 +413,8 @@ CONFIG_MFD_MAX14577=y CONFIG_MFD_MAX77686=y CONFIG_MFD_MAX77693=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 @@ -404,6 +436,7 @@ CONFIG_REGULATOR_MAX8973=y CONFIG_REGULATOR_MAX77686=y CONFIG_REGULATOR_MAX77693=m CONFIG_REGULATOR_PALMAS=y +CONFIG_REGULATOR_QCOM_RPM=y CONFIG_REGULATOR_S2MPS11=y CONFIG_REGULATOR_S5M8767=y CONFIG_REGULATOR_TPS51632=y @@ -474,6 +507,7 @@ CONFIG_USB=y CONFIG_USB_XHCI_HCD=y CONFIG_USB_XHCI_MVEBU=y CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_MSM=y CONFIG_USB_EHCI_EXYNOS=y CONFIG_USB_EHCI_TEGRA=y CONFIG_USB_EHCI_HCD_STI=y @@ -496,12 +530,13 @@ 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_USB_RCAR_PHY=m CONFIG_USB_GADGET=y CONFIG_USB_RENESAS_USBHS_UDC=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 @@ -518,6 +553,7 @@ CONFIG_MMC_SDHCI_ST=y CONFIG_MMC_OMAP=y CONFIG_MMC_OMAP_HS=y CONFIG_MMC_ATMELMCI=y +CONFIG_MMC_SDHCI_MSM=y CONFIG_MMC_MVSDIO=y CONFIG_MMC_SDHI=y CONFIG_MMC_DW=y @@ -587,6 +623,7 @@ CONFIG_IMX_SDMA=y CONFIG_IMX_DMA=y CONFIG_MXS_DMA=y CONFIG_DMA_OMAP=y +CONFIG_QCOM_BAM_DMA=y CONFIG_XILINX_VDMA=y CONFIG_STAGING=y CONFIG_SENSORS_ISL29018=y @@ -608,6 +645,12 @@ 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_MSM_RPMCC_8064=y +CONFIG_QCOM_IOMMU_V0=y CONFIG_TEGRA_IOMMU_GART=y CONFIG_TEGRA_IOMMU_SMMU=y CONFIG_PM_DEVFREQ=y @@ -628,6 +671,7 @@ CONFIG_PWM_VT8500=y CONFIG_PHY_HIX5HD2_SATA=y CONFIG_OMAP_USB2=y CONFIG_TI_PIPE3=y +CONFIG_PHY_QCOM_APQ8064_SATA=y CONFIG_PHY_MIPHY28LP=y CONFIG_PHY_MIPHY365X=y CONFIG_PHY_RCAR_GEN2=m @@ -636,6 +680,8 @@ CONFIG_PHY_STIH407_USB=y CONFIG_PHY_SUN4I_USB=y CONFIG_PHY_SUN9I_USB=y CONFIG_PHY_SAMSUNG_USB2=m +CONFIG_NVMEM=y +CONFIG_QCOM_QFPROM=y CONFIG_EXT4_FS=y CONFIG_AUTOFS4_FS=y CONFIG_MSDOS_FS=y @@ -659,7 +705,6 @@ CONFIG_DEBUG_FS=y CONFIG_MAGIC_SYSRQ=y CONFIG_LOCKUP_DETECTOR=y CONFIG_CRYPTO_DEV_TEGRA_AES=y -CONFIG_CPUFREQ_DT=y CONFIG_KEYSTONE_IRQ=y CONFIG_ARM_CRYPTO=y CONFIG_CRYPTO_SHA1_ARM=m @@ -675,3 +720,7 @@ CONFIG_CRYPTO_GHASH_ARM_CE=m CONFIG_CRYPTO_DEV_ATMEL_AES=m CONFIG_CRYPTO_DEV_ATMEL_TDES=m CONFIG_CRYPTO_DEV_ATMEL_SHA=m +CONFIG_FUNCTION_TRACER=y +CONFIG_SCHED_TRACER=y +CONFIG_BLK_DEV_IO_TRACE=y +CONFIG_TRACEPOINT_BENCHMARK=y diff --git a/arch/arm/configs/qcom_defconfig b/arch/arm/configs/qcom_defconfig index e6a6f282e3de..ad32d1ac229c 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,19 +23,36 @@ 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_CPUIDLE=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 @@ -48,25 +67,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 @@ -75,6 +112,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 @@ -87,7 +127,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 @@ -98,16 +137,83 @@ CONFIG_PINCTRL_APQ8084=y CONFIG_PINCTRL_IPQ8064=y CONFIG_PINCTRL_MSM8960=y CONFIG_PINCTRL_MSM8X74=y +CONFIG_PINCTRL_SSBI_PMIC=y CONFIG_GPIOLIB=y CONFIG_DEBUG_GPIO=y CONFIG_GPIO_SYSFS=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 @@ -116,15 +222,32 @@ 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=32 CONFIG_MMC_ARMMMCI=y @@ -135,6 +258,8 @@ CONFIG_RTC_CLASS=y 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_LCC_806X=y @@ -145,6 +270,11 @@ CONFIG_MSM_MMCC_8974=y CONFIG_MSM_IOMMU=y CONFIG_QCOM_GSBI=y CONFIG_QCOM_PM=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 @@ -152,13 +282,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 @@ -170,5 +306,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/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-qcom/Kconfig b/arch/arm/mach-qcom/Kconfig index 2256cd1e25d1..afa30dd9521d 100644 --- a/arch/arm/mach-qcom/Kconfig +++ b/arch/arm/mach-qcom/Kconfig @@ -4,6 +4,8 @@ menuconfig ARCH_QCOM select ARM_GIC select ARM_AMBA select PINCTRL + select MIGHT_HAVE_PCI + select PCI_DOMAINS if PCI select QCOM_SCM if SMP help Support for Qualcomm's devicetree based systems. diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 0f6edb14b7e4..c4f5ee18f22f 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -199,6 +199,7 @@ config ARCH_MEDIATEK config ARCH_QCOM bool "Qualcomm Platforms" select PINCTRL + select QCOM_SCM help This enables support for the ARMv8 based Qualcomm chipsets. diff --git a/arch/arm64/boot/dts/qcom/apq8016-sbc-pmic-pins.dtsi b/arch/arm64/boot/dts/qcom/apq8016-sbc-pmic-pins.dtsi index 535532b9287f..92aadc7c0379 100644 --- a/arch/arm64/boot/dts/qcom/apq8016-sbc-pmic-pins.dtsi +++ b/arch/arm64/boot/dts/qcom/apq8016-sbc-pmic-pins.dtsi @@ -2,28 +2,37 @@ &pm8916_gpios { - pinctrl-names = "default"; - pinctrl-0 = <&pm8916_gpios_default>; - - pm8916_gpios_default: default { - usb_hub_reset_pm { - pins = "gpio1"; + usb_hub_reset_pm_default: usb_hub_reset_pm_default { + pinconf { + pins = "gpio3"; function = PMIC_GPIO_FUNC_NORMAL; output-low; }; - usb_sw_sel_pm { - pins = "gpio2"; + }; + + usb_sw_sel_pm_default: usb_sw_sel_pm { + pinconf { + pins = "gpio4"; function = PMIC_GPIO_FUNC_NORMAL; - input-disable; + power-source = <PM8916_GPIO_VPH>; + output-low; // USB device mode }; - usr_led_3_ctrl { - pins = "gpio3"; + }; + + pm8916_gpios_leds_default: pm8916_gpios_leds_default { + pinconf { + pins = "gpio1", "gpio2"; function = PMIC_GPIO_FUNC_NORMAL; output-low; }; - usr_led_4_ctrl { - pins = "gpio4"; - function = PMIC_GPIO_FUNC_NORMAL; + }; +}; + +&pm8916_mpps { + pm8916_mpps_leds_default: pm8916_mpps_leds_default { + pinconf { + pins = "mpp2", "mpp3"; + function = "digital"; output-low; }; }; diff --git a/arch/arm64/boot/dts/qcom/apq8016-sbc-soc-pins.dtsi b/arch/arm64/boot/dts/qcom/apq8016-sbc-soc-pins.dtsi index 5f7023f90df7..bf8ed2f1e1e8 100644 --- a/arch/arm64/boot/dts/qcom/apq8016-sbc-soc-pins.dtsi +++ b/arch/arm64/boot/dts/qcom/apq8016-sbc-soc-pins.dtsi @@ -3,9 +3,6 @@ &msmgpio { - pinctrl-names = "default"; - pinctrl-0 = <&soc_gpios_default>; - soc_gpios_default: default { usr_led_1_ctrl_default: usr_led_1_ctrl_default { pins = "gpio21"; @@ -17,5 +14,69 @@ function = "gpio"; output-low; }; + + usb_id_default: usb_id_default { + pins = "gpio121"; + function = "gpio"; + drive-strength = <8>; + input-enable; + bias-pull-up; + }; + }; + + msm_gpios_leds_default: msm_gpios_leds_default { + pinconf { + pins = "gpio21", "gpio10"; + function = "gpio"; + output-low; + }; + }; + + adv7533_int_active: adv533_int_active { + pinmux { + function = "gpio"; + pins = "gpio31"; + }; + pinconf { + pins = "gpio31"; + drive-strength = <16>; + bias-disable; + }; + }; + + adv7533_int_suspend: adv7533_int_suspend { + pinmux { + function = "gpio"; + pins = "gpio31"; + }; + pinconf { + pins = "gpio31"; + drive-strength = <2>; + bias-disable; + }; + }; + + adv7533_switch_active: adv7533_switch_active { + pinmux { + function = "gpio"; + pins = "gpio32"; + }; + pinconf { + pins = "gpio32"; + drive-strength = <16>; + bias-disable; + }; + }; + + adv7533_switch_suspend: adv7533_switch_suspend { + pinmux { + function = "gpio"; + pins = "gpio32"; + }; + pinconf { + pins = "gpio32"; + drive-strength = <2>; + bias-disable; + }; }; }; diff --git a/arch/arm64/boot/dts/qcom/apq8016-sbc.dts b/arch/arm64/boot/dts/qcom/apq8016-sbc.dts index 825f489a2af7..b0cac5337fcc 100644 --- a/arch/arm64/boot/dts/qcom/apq8016-sbc.dts +++ b/arch/arm64/boot/dts/qcom/apq8016-sbc.dts @@ -12,10 +12,11 @@ */ /dts-v1/; - +#include <dt-bindings/arm/qcom-ids.h> #include "apq8016-sbc.dtsi" / { model = "Qualcomm Technologies, Inc. APQ 8016 SBC"; compatible = "qcom,apq8016-sbc", "qcom,apq8016", "qcom,sbc"; + qcom,board-id = <QCOM_BRD_ID(SBC, 1, 0) QCOM_BRD_SUBTYPE_DEFAULT>; }; diff --git a/arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi b/arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi index 98abece6b233..b706473aaf48 100644 --- a/arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi +++ b/arch/arm64/boot/dts/qcom/apq8016-sbc.dtsi @@ -11,11 +11,13 @@ * GNU General Public License for more details. */ +#include <dt-bindings/gpio/gpio.h> +#include <dt-bindings/sound/apq8016-lpass.h> #include "msm8916.dtsi" #include "pm8916.dtsi" #include "apq8016-sbc-soc-pins.dtsi" #include "apq8016-sbc-pmic-pins.dtsi" - +#include "msm8916-mdss.dtsi" / { aliases { serial0 = &blsp1_uart2; @@ -33,4 +35,189 @@ pinctrl-1 = <&blsp1_uart2_sleep>; }; }; + + usb2513 { + compatible = "smsc,usb3503"; + reset-gpios = <&pm8916_gpios 3 GPIO_ACTIVE_LOW>; + initial-mode = <1>; + }; + + usb_id: usb-id { + interrupt-parent = <&msmgpio>; + compatible = "linux,extcon-usb-gpio"; + id-gpio = <&msmgpio 121 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&usb_id_default>; + }; + + leds { + pinctrl-names = "default"; + pinctrl-0 = <&msm_gpios_leds_default>, + <&pm8916_gpios_leds_default>, + <&pm8916_mpps_leds_default>; + + compatible = "gpio-leds"; + + led@1 { + label = "apq8016-sbc:green:user1"; + gpios = <&msmgpio 21 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + default-state = "off"; + }; + + led@2 { + label = "apq8016-sbc:green:user2"; + gpios = <&msmgpio 120 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "mmc0"; + default-state = "off"; + }; + + led@3 { + label = "apq8016-sbc:green:user3"; + gpios = <&pm8916_gpios 1 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "mmc1"; + default-state = "off"; + }; + + led@4 { + label = "apq8016-sbc:green:user4"; + gpios = <&pm8916_gpios 2 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "none"; + default-state = "off"; + }; + + led@5 { + label = "apq8016-sbc:yellow:wlan"; + gpios = <&pm8916_mpps 2 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "wlan"; + default-state = "off"; + }; + + led@6 { + label = "apq8016-sbc:blue:bt"; + gpios = <&pm8916_mpps 3 GPIO_ACTIVE_HIGH>; + linux,default-trigger = "bt"; + default-state = "off"; + }; + }; + +}; +&blsp_dma { + status = "okay"; +}; + +&blsp_spi3 { + status = "okay"; +}; + +&sdhc_1 { + vmmc-supply = <&pm8916_l8>; + vqmmc-supply = <&pm8916_l5>; + + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&sdc1_clk_on &sdc1_cmd_on &sdc1_data_on>; + pinctrl-1 = <&sdc1_clk_off &sdc1_cmd_off &sdc1_data_off>; + status = "okay"; +}; + +&sdhc_2 { + vmmc-supply = <&pm8916_l11>; + vqmmc-supply = <&pm8916_l12>; + + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&sdc2_clk_on &sdc2_cmd_on &sdc2_data_on &sdc2_cd_on>; + pinctrl-1 = <&sdc2_clk_off &sdc2_cmd_off &sdc2_data_off &sdc2_cd_off>; + #address-cells = <0>; + interrupt-parent = <&sdhc_2>; + interrupts = <0 1 2>; + #interrupt-cells = <1>; + interrupt-map-mask = <0xffffffff>; + interrupt-map = <0 &intc 0 125 0 + 1 &intc 0 221 0 + 2 &msmgpio 38 0>; + interrupt-names = "hc_irq", "pwr_irq", "status_irq"; + cd-gpios = <&msmgpio 38 0x1>; + + status = "okay"; +}; + +&usb_dev { + extcon = <&usb_id>, <&usb_id>; + status = "okay"; +}; + +&usb_host { + status = "okay"; +}; + +&usb_otg { + extcon = <&usb_id>, <&usb_id>; + dr_mode = "otg"; + status = "okay"; + switch-gpio = <&pm8916_gpios 4 GPIO_ACTIVE_HIGH>; // D+/D- lines: 1 - Routed to HUB, 0 - Device + pinctrl-names = "default"; + pinctrl-0 = <&usb_sw_sel_pm_default>; +}; + +&blsp_i2c4 { + status = "ok"; +}; + +&mdss_dsi0 { + adv_bridge: bridge@0 { + status = "ok"; + compatible = "adi,adv7533"; + reg = <0>; + interrupt-parent = <&msmgpio>; + //interrupts = <31 2>; + i2c-bus = <&blsp_i2c4>; + adi,input-depth = <8>; + adi,input-colorspace = "rgb"; + adi,dsi-lanes = <4>; + pd-gpios = <&msmgpio 32 0>; + pinctrl-names = "default","sleep"; + pinctrl-0 = <&adv7533_int_active &adv7533_switch_active>; + pinctrl-1 = <&adv7533_int_suspend &adv7533_switch_suspend>; + #sound-dai-cells = <0>; + }; +}; + +&lpass { + status = "okay"; + + +}; + /* + Internal Codec + playback - Primary MI2S + capture - Ter MI2S + + External Primary: + playback - secondary MI2S + capture - Quat MI2S + + External Secondary: + playback - Quat MI2S + capture - Quat MI2S + + */ +&sound { + status = "okay"; + pinctrl-0 = <&cdc_pdm_lines_act &ext_sec_tlmm_lines_act &ext_mclk_tlmm_lines_act>; + pinctrl-1 = <&cdc_pdm_lines_sus &ext_sec_tlmm_lines_sus &ext_mclk_tlmm_lines_sus>; + pinctrl-names = "default", "sleep"; + qcom,model = "DB410c"; + + + /* External Primary or External Secondary -ADV7533 HDMI */ + external-dai-link@0 { + link-name = "ADV7533"; + + cpu { /* QUAT */ + sound-dai = <&lpass MI2S_QUATERNARY>; + }; + codec { + sound-dai = <&adv_bridge 0>; + }; + }; }; diff --git a/arch/arm64/boot/dts/qcom/msm8916-mdss.dtsi b/arch/arm64/boot/dts/qcom/msm8916-mdss.dtsi new file mode 100644 index 000000000000..25b609e69240 --- /dev/null +++ b/arch/arm64/boot/dts/qcom/msm8916-mdss.dtsi @@ -0,0 +1,87 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +&soc { + mdss_mdp: qcom,mdss_mdp@1a00000 { + compatible = "qcom,mdss_mdp"; + reg = <0x1a00000 0x90000>, + <0x1ac8000 0x3000>; + reg-names = "mdp_phys", "vbif_phys"; + interrupts = <0 72 0>; + + interrupt-controller; + #interrupt-cells = <1>; + + power-domains = <&gcc MDSS_GDSC>; + + connectors = <&mdss_dsi0>; + + clocks = <&gcc GCC_MDSS_AHB_CLK>, + <&gcc GCC_MDSS_AXI_CLK>, + <&gcc MDP_CLK_SRC>, + <&gcc GCC_MDSS_MDP_CLK>, + <&gcc GCC_MDSS_MDP_CLK>, + <&gcc GCC_MDSS_VSYNC_CLK>; + clock-names = "iface_clk", "bus_clk", "core_clk_src", + "core_clk", "lut_clk", "vsync_clk"; + }; + + mdss_dsi0: qcom,mdss_dsi@1a98000 { + compatible = "qcom,mdss-dsi-ctrl"; + label = "MDSS DSI CTRL->0"; + qcom,dsi-host-index = <0>; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x1a98000 0x25c>; + reg-names = "dsi_ctrl"; + + interrupt-parent = <&mdss_mdp>; + interrupts = <4 0>; + + vdda-supply = <&pm8916_l2>; + vdd-supply = <&pm8916_l17>; + vddio-supply = <&pm8916_l6>; + + clocks = <&gcc GCC_MDSS_MDP_CLK>, + <&gcc GCC_MDSS_AHB_CLK>, + <&gcc GCC_MDSS_AXI_CLK>, + <&gcc GCC_MDSS_AHB_CLK>, + <&gcc GCC_MDSS_BYTE0_CLK>, + <&gcc GCC_MDSS_PCLK0_CLK>, + <&gcc GCC_MDSS_ESC0_CLK>, + <&gcc BYTE0_CLK_SRC>, + <&gcc PCLK0_CLK_SRC>; + clock-names = "mdp_core_clk", "iface_clk", "bus_clk", + "core_mmss_clk", "byte_clk", "pixel_clk", + "core_clk", "byte_clk_src", "pixel_clk_src"; + qcom,dsi-phy = <&mdss_dsi_phy0>; + }; + + mdss_dsi_phy0: qcom,mdss_dsi_phy@1a98300 { + compatible = "qcom,dsi-phy-28nm-lp"; + qcom,dsi-phy-index = <0>; + + power-domains = <&gcc MDSS_GDSC>; + + reg-names = "dsi_pll", "dsi_phy", "dsi_phy_regulator"; + reg = <0x1a98300 0xd4>, + <0x1a98500 0x280>, + <0x1a98780 0x30>; + + clocks = <&gcc GCC_MDSS_AHB_CLK>; + clock-names = "iface_clk"; + + vddio-supply = <&pm8916_l6>; + }; +}; + + diff --git a/arch/arm64/boot/dts/qcom/msm8916-mtp.dts b/arch/arm64/boot/dts/qcom/msm8916-mtp.dts index fced77f0fd3a..6c68b4ed4de2 100644 --- a/arch/arm64/boot/dts/qcom/msm8916-mtp.dts +++ b/arch/arm64/boot/dts/qcom/msm8916-mtp.dts @@ -13,10 +13,13 @@ /dts-v1/; +#include <dt-bindings/arm/qcom-ids.h> #include "msm8916-mtp.dtsi" / { model = "Qualcomm Technologies, Inc. MSM 8916 MTP"; compatible = "qcom,msm8916-mtp", "qcom,msm8916-mtp-smb1360", "qcom,msm8916", "qcom,mtp"; + qcom,board-id = <QCOM_BRD_ID(MTP, 1, 0) QCOM_BRD_SUBTYPE_DEFAULT>, + <QCOM_BRD_ID(MTP, 1, 0) QCOM_BRD_SUBTYPE_MTP8916_SMB1360>; }; diff --git a/arch/arm64/boot/dts/qcom/msm8916-mtp.dtsi b/arch/arm64/boot/dts/qcom/msm8916-mtp.dtsi index a1aa0b201e92..622676fd10a3 100644 --- a/arch/arm64/boot/dts/qcom/msm8916-mtp.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8916-mtp.dtsi @@ -23,6 +23,11 @@ stdout-path = "serial0"; }; + aliases { + sdhc1 = &sdhc_1; /* SDC1 eMMC slot */ + sdhc2 = &sdhc_2; /* SDC2 SD card slot */ + }; + soc { serial@78b0000 { status = "okay"; @@ -32,3 +37,42 @@ }; }; }; + +&blsp_dma { + status = "okay"; +}; + +&blsp_spi3 { + status = "okay"; +}; + +&sdhc_1 { + vmmc-supply = <&pm8916_l8>; + vqmmc-supply = <&pm8916_l5>; + + pinctrl-names = "active", "sleep"; + pinctrl-0 = <&sdc1_clk_on &sdc1_cmd_on &sdc1_data_on>; + pinctrl-1 = <&sdc1_clk_off &sdc1_cmd_off &sdc1_data_off>; + status = "okay"; +}; + +&sdhc_2 { + //vmmc-supply = <&pm8916_l11>; + vqmmc-supply = <&pm8916_l12>; + + pinctrl-names = "active", "sleep"; + pinctrl-0 = <&sdc2_clk_on &sdc2_cmd_on &sdc2_data_on &sdc2_cd_on>; + pinctrl-1 = <&sdc2_clk_off &sdc2_cmd_off &sdc2_data_off &sdc2_cd_off>; + #address-cells = <0>; + interrupt-parent = <&sdhc_2>; + interrupts = <0 1 2>; + #interrupt-cells = <1>; + interrupt-map-mask = <0xffffffff>; + interrupt-map = <0 &intc 0 125 0 + 1 &intc 0 221 0 + 2 &msmgpio 38 0>; + interrupt-names = "hc_irq", "pwr_irq", "status_irq"; + cd-gpios = <&msmgpio 38 0x1>; + + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/qcom/msm8916.dtsi b/arch/arm64/boot/dts/qcom/msm8916.dtsi index 0f49ebd0aa8b..1c6a292b6406 100644 --- a/arch/arm64/boot/dts/qcom/msm8916.dtsi +++ b/arch/arm64/boot/dts/qcom/msm8916.dtsi @@ -14,10 +14,18 @@ #include <dt-bindings/interrupt-controller/arm-gic.h> #include <dt-bindings/clock/qcom,gcc-msm8916.h> #include <dt-bindings/reset/qcom,gcc-msm8916.h> +#include <dt-bindings/clock/qcom,rpmcc-msm8916.h> +#include <dt-bindings/arm/qcom-ids.h> / { model = "Qualcomm Technologies, Inc. MSM8916"; compatible = "qcom,msm8916"; + qcom,msm-id = <QCOM_ID_MSM8916 0>, + <QCOM_ID_MSM8216 0>, + <QCOM_ID_MSM8116 0>, + <QCOM_ID_MSM8616 0>, + <QCOM_ID_APQ8016 0>; + interrupt-parent = <&intc>; @@ -34,6 +42,21 @@ reg = <0 0 0 0>; }; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + stuff: stuff@0x86000000 { /* fill in the gap */ + reg = <0x0 0x86000000 0x0 0x0300000>; + no-map; + }; + smem_mem: smem_region@86300000 { + reg = <0x0 0x86300000 0x0 0x0100000>; + no-map; + }; + }; + cpus { #address-cells = <1>; #size-cells = <0>; @@ -42,24 +65,100 @@ device_type = "cpu"; compatible = "arm,cortex-a53", "arm,armv8"; reg = <0x0>; + enable-method = "qcom,arm-cortex-acc"; + qcom,acc = <&acc0>; + next-level-cache = <&L2_0>; + clocks = <&a53cc 1>; + clock-latency = <200000>; + cpu-supply = <&pm8916_s2>; + L2_0: l2-cache { + compatible = "arm,arch-cache"; + cache-level = <2>; + power-domain = <&l2ccc_0>; + }; }; CPU1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a53", "arm,armv8"; reg = <0x1>; + enable-method = "qcom,arm-cortex-acc"; + qcom,acc = <&acc1>; + next-level-cache = <&L2_0>; + clocks = <&a53cc 1>; + clock-latency = <200000>; + cpu-supply = <&pm8916_s2>; }; CPU2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a53", "arm,armv8"; reg = <0x2>; + enable-method = "qcom,arm-cortex-acc"; + qcom,acc = <&acc2>; + next-level-cache = <&L2_0>; + clocks = <&a53cc 1>; + clock-latency = <200000>; + cpu-supply = <&pm8916_s2>; }; CPU3: cpu@3 { device_type = "cpu"; compatible = "arm,cortex-a53", "arm,armv8"; reg = <0x3>; + enable-method = "qcom,arm-cortex-acc"; + qcom,acc = <&acc3>; + next-level-cache = <&L2_0>; + clocks = <&a53cc 1>; + clock-latency = <200000>; + cpu-supply = <&pm8916_s2>; + }; + }; + + cpu-pmu { + compatible = "arm,armv8-pmuv3"; + interrupts = <GIC_PPI 7 GIC_CPU_MASK_SIMPLE(4)>; + }; + + thermal-zones { + cpu-thermal0 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 4>; + + trips { + cpu_alert0: trip@0 { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit0: trip@1 { + temperature = <125000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + cpu-thermal1 { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens 3>; + + trips { + cpu_alert1: trip@0 { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + cpu_crit1: trip@1 { + temperature = <125000>; + hysteresis = <2000>; + type = "critical"; + }; + }; }; }; @@ -114,15 +213,776 @@ bias-pull-down; }; }; + + spi1_default: spi1_default { + pinmux { + function = "blsp_spi1"; + pins = "gpio0", "gpio1", "gpio3"; + }; + pinmux_cs { + function = "gpio"; + pins = "gpio2"; + }; + pinconf { + pins = "gpio0", "gpio1", "gpio3"; + drive-strength = <12>; + bias-disable; + }; + pinconf_cs { + pins = "gpio2"; + drive-strength = <2>; + bias-disable; + output-high; + }; + }; + + spi1_sleep: spi1_sleep { + pinmux { + function = "gpio"; + pins = "gpio0", "gpio1", "gpio2", "gpio3"; + }; + pinconf { + pins = "gpio0", "gpio1", "gpio2", "gpio3"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + spi2_default: spi2_default { + pinmux { + function = "blsp_spi2"; + pins = "gpio4", "gpio5", "gpio7"; + }; + pinmux_cs { + function = "gpio"; + pins = "gpio6"; + }; + pinconf { + pins = "gpio4", "gpio5", "gpio6", "gpio7"; + drive-strength = <12>; + bias-disable; + }; + pinconf_cs { + pins = "gpio6"; + drive-strength = <2>; + bias-disable; + output-high; + }; + }; + + spi2_sleep: spi2_sleep { + pinmux { + function = "gpio"; + pins = "gpio4", "gpio5", "gpio6", "gpio7"; + }; + pinconf { + pins = "gpio4", "gpio5", "gpio6", "gpio7"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + spi3_default: spi3_default { + pinmux { + function = "blsp_spi3"; + pins = "gpio8", "gpio9", "gpio11"; + }; + pinmux_cs { + function = "gpio"; + pins = "gpio10"; + }; + pinconf { + pins = "gpio8", "gpio9", "gpio10", "gpio11"; + drive-strength = <12>; + bias-disable; + }; + pinconf_cs { + pins = "gpio10"; + drive-strength = <2>; + bias-disable; + output-high; + }; + }; + + spi3_sleep: spi3_sleep { + pinmux { + function = "gpio"; + pins = "gpio8", "gpio9", "gpio10", "gpio11"; + }; + pinconf { + pins = "gpio8", "gpio9", "gpio10", "gpio11"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + spi4_default: spi4_default { + pinmux { + function = "blsp_spi4"; + pins = "gpio12", "gpio13", "gpio15"; + }; + pinmux_cs { + function = "gpio"; + pins = "gpio14"; + }; + pinconf { + pins = "gpio12", "gpio13", "gpio14", "gpio15"; + drive-strength = <12>; + bias-disable; + }; + pinconf_cs { + pins = "gpio14"; + drive-strength = <2>; + bias-disable; + output-high; + }; + }; + + spi4_sleep: spi4_sleep { + pinmux { + function = "gpio"; + pins = "gpio12", "gpio13", "gpio14", "gpio15"; + }; + pinconf { + pins = "gpio12", "gpio13", "gpio14", "gpio15"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + spi5_default: spi5_default { + pinmux { + function = "blsp_spi5"; + pins = "gpio16", "gpio17", "gpio19"; + }; + pinmux_cs { + function = "gpio"; + pins = "gpio18"; + }; + pinconf { + pins = "gpio16", "gpio17", "gpio18", "gpio19"; + drive-strength = <12>; + bias-disable; + }; + pinconf_cs { + pins = "gpio18"; + drive-strength = <2>; + bias-disable; + output-high; + }; + }; + + spi5_sleep: spi5_sleep { + pinmux { + function = "gpio"; + pins = "gpio16", "gpio17", "gpio18", "gpio19"; + }; + pinconf { + pins = "gpio16", "gpio17", "gpio18", "gpio19"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + spi6_default: spi6_default { + pinmux { + function = "blsp_spi6"; + pins = "gpio20", "gpio21", "gpio23"; + }; + pinmux_cs { + function = "gpio"; + pins = "gpio22"; + }; + pinconf { + pins = "gpio20", "gpio21", "gpio22", "gpio23"; + drive-strength = <12>; + bias-disable; + }; + pinconf_cs { + pins = "gpio22"; + drive-strength = <2>; + bias-disable; + output-high; + }; + }; + + spi6_sleep: spi6_sleep { + pinmux { + function = "gpio"; + pins = "gpio20", "gpio21", "gpio22", "gpio23"; + }; + pinconf { + pins = "gpio20", "gpio21", "gpio22", "gpio23"; + drive-strength = <2>; + bias-pull-down; + }; + }; + + i2c4_default: i2c4_default { + pinmux { + function = "blsp_i2c4"; + pins = "gpio14", "gpio15"; + }; + pinconf { + pins = "gpio14", "gpio15"; + drive-strength = <2>; + bias-disable = <0>; + }; + }; + + i2c4_sleep: i2c4_sleep { + pinmux { + function = "blsp_i2c4"; + pins = "gpio14", "gpio15"; + }; + pinconf { + pins = "gpio14", "gpio15"; + drive-strength = <2>; + bias-disable = <0>; + }; + }; + + sdhc2_cd_pin { + sdc2_cd_on: cd_on { + pinmux { + function = "gpio"; + pins = "gpio38"; + }; + pinconf { + pins = "gpio38"; + drive-strength = <2>; + bias-pull-up; + }; + }; + sdc2_cd_off: cd_off { + pinmux { + function = "gpio"; + pins = "gpio38"; + }; + pinconf { + pins = "gpio38"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + pmx_sdc1_clk { + sdc1_clk_on: clk_on { + pinmux { + pins = "sdc1_clk"; + }; + pinconf { + pins = "sdc1_clk"; + bias-disable; + drive-strength = <16>; + }; + }; + sdc1_clk_off: clk_off { + pinmux { + pins = "sdc1_clk"; + }; + pinconf { + pins = "sdc1_clk"; + bias-disable; + drive-strength = <2>; + }; + }; + }; + + pmx_sdc1_cmd { + sdc1_cmd_on: cmd_on { + pinmux { + pins = "sdc1_cmd"; + }; + pinconf { + pins = "sdc1_cmd"; + bias-pull-up; + drive-strength = <10>; + }; + }; + sdc1_cmd_off: cmd_off { + pinmux { + pins = "sdc1_cmd"; + }; + pinconf { + pins = "sdc1_cmd"; + bias-pull-up; + drive-strength = <2>; + }; + }; + }; + + pmx_sdc1_data { + sdc1_data_on: data_on { + pinmux { + pins = "sdc1_data"; + }; + pinconf { + pins = "sdc1_data"; + bias-pull-up; + drive-strength = <10>; + }; + }; + sdc1_data_off: data_off { + pinmux { + pins = "sdc1_data"; + }; + pinconf { + pins = "sdc1_data"; + bias-pull-up; + drive-strength = <2>; + }; + }; + }; + + pmx_sdc2_clk { + sdc2_clk_on: clk_on { + pinmux { + pins = "sdc2_clk"; + }; + pinconf { + pins = "sdc2_clk"; + bias-disable; + drive-strength = <16>; + }; + }; + sdc2_clk_off: clk_off { + pinmux { + pins = "sdc2_clk"; + }; + pinconf { + pins = "sdc2_clk"; + bias-disable; + drive-strength = <2>; + }; + }; + }; + + pmx_sdc2_cmd { + sdc2_cmd_on: cmd_on { + pinmux { + pins = "sdc2_cmd"; + }; + pinconf { + pins = "sdc2_cmd"; + bias-pull-up; + drive-strength = <10>; + }; + }; + sdc2_cmd_off: cmd_off { + pinmux { + pins = "sdc2_cmd"; + }; + pinconf { + pins = "sdc2_cmd"; + bias-pull-up; + drive-strength = <2>; + }; + }; + }; + + pmx_sdc2_data { + sdc2_data_on: data_on { + pinmux { + pins = "sdc2_data"; + }; + pinconf { + pins = "sdc2_data"; + bias-pull-up; + drive-strength = <10>; + }; + }; + sdc2_data_off: data_off { + pinmux { + pins = "sdc2_data"; + }; + pinconf { + pins = "sdc2_data"; + bias-pull-up; + drive-strength = <2>; + }; + }; + }; + + ext-codec-lines { + ext_codec_lines_act: lines_on { + pinmux { + function = "gpio"; + pins = "gpio67"; + }; + pinconf { + pins = "gpio67"; + drive-strength = <8>; + bias-disable; + output-high; + }; + }; + ext_codec_lines_sus: lines_off { + pinmux { + function = "gpio"; + pins = "gpio67"; + }; + pinconf { + pins = "gpio67"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + cdc-pdm-lines { + cdc_pdm_lines_act: pdm_lines_on { + pinmux { + function = "cdc_pdm0"; + pins = "gpio63", "gpio64", "gpio65", "gpio66", + "gpio67", "gpio68"; + }; + pinconf { + pins = "gpio63", "gpio64", "gpio65", "gpio66", + "gpio67", "gpio68"; + drive-strength = <8>; + bias-pull-none; + }; + }; + cdc_pdm_lines_sus: pdm_lines_off { + pinmux { + function = "cdc_pdm0"; + pins = "gpio63", "gpio64", "gpio65", "gpio66", + "gpio67", "gpio68"; + }; + pinconf { + pins = "gpio63", "gpio64", "gpio65", "gpio66", + "gpio67", "gpio68"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + ext-pri-tlmm-lines { + ext_pri_tlmm_lines_act: ext_pa_on { + pinmux { + function = "pri_mi2s"; + pins = "gpio113", "gpio114", "gpio115", + "gpio116"; + }; + pinconf { + pins = "gpio113", "gpio114", "gpio115", + "gpio116"; + drive-strength = <8>; + bias-pull-none; + }; + }; + + ext_pri_tlmm_lines_sus: ext_pa_off { + pinmux { + function = "pri_mi2s"; + pins = "gpio113", "gpio114", "gpio115", + "gpio116"; + }; + pinconf { + pins = "gpio113", "gpio114", "gpio115", + "gpio116"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + ext-pri-ws-line { + ext_pri_ws_act: ext_pa_on { + pinmux { + function = "pri_mi2s_ws"; + pins = "gpio110"; + }; + pinconf { + pins = "gpio110"; + drive-strength = <8>; + bias-pull-none; + }; + }; + + ext_pri_ws_sus: ext_pa_off { + pinmux { + function = "pri_mi2s_ws"; + pins = "gpio110"; + }; + pinconf { + pins = "gpio110"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + ext-mclk-tlmm-lines { + ext_mclk_tlmm_lines_act: mclk_lines_on { + pinmux { + function = "pri_mi2s"; + pins = "gpio116"; + }; + pinconf { + pins = "gpio116"; + drive-strength = <8>; + bias-pull-none; + }; + }; + ext_mclk_tlmm_lines_sus: mclk_lines_off { + pinmux { + function = "pri_mi2s"; + pins = "gpio116"; + }; + pinconf { + pins = "gpio116"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + /* secondary Mi2S */ + ext-sec-tlmm-lines { + ext_sec_tlmm_lines_act: tlmm_lines_on { + pinmux { + function = "sec_mi2s"; + pins = "gpio112", "gpio117", "gpio118", + "gpio119"; + }; + pinconf { + pins = "gpio112", "gpio117", "gpio118", + "gpio119"; + drive-strength = <8>; + bias-pull-none; + }; + }; + ext_sec_tlmm_lines_sus: tlmm_lines_off { + pinmux { + function = "sec_mi2s"; + pins = "gpio112", "gpio117", "gpio118", + "gpio119"; + }; + pinconf { + pins = "gpio112", "gpio117", "gpio118", + "gpio119"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + cdc-dmic-lines { + cdc_dmic_lines_act: dmic_lines_on { + pinmux_dmic0_clk { + function = "dmic0_clk"; + pins = "gpio0"; + }; + pinmux_dmic0_data { + function = "dmic0_data"; + pins = "gpio1"; + }; + pinconf { + pins = "gpio0", "gpio1"; + drive-strength = <8>; + }; + }; + cdc_dmic_lines_sus: dmic_lines_off { + pinconf { + pins = "gpio0", "gpio1"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + + cross-conn-det { + cross_conn_det_act: lines_on { + pinmux { + function = "gpio"; + pins = "gpio120"; + }; + pinconf { + pins = "gpio120"; + drive-strength = <8>; + output-low; + bias-pull-down; + }; + }; + cross_conn_det_sus: lines_off { + pinmux { + function = "gpio"; + pins = "gpio120"; + }; + pinconf { + pins = "gpio120"; + drive-strength = <2>; + bias-disable; + }; + }; + }; + }; + + tcsr_mutex_regs: syscon@1905000 { + compatible = "syscon"; + reg = <0x1905000 0x20000>; + }; + + tcsr_mutex: hwlock { + compatible = "qcom,tcsr-mutex"; + syscon = <&tcsr_mutex_regs 0 0x1000>; + #hwlock-cells = <1>; + }; + + smem { + compatible = "qcom,smem"; + reg = <0x60000 0x8000>; + reg-names = "aux-mem1"; + + memory-region = <&smem_mem>; + + hwlocks = <&tcsr_mutex 3>; + }; + + + apcs: syscon@b011000 { + compatible = "syscon"; + reg = <0x0b011000 0x1000>; + }; + + smd { + compatible = "qcom,smd"; + + rpm { + interrupts = <0 168 1>; + qcom,ipc = <&apcs 8 0>; + qcom,smd-edge = <15>; + + rpm_requests { + compatible = "qcom,rpm-msm8916"; + qcom,smd-channels = "rpm_requests"; + + rpmcc: qcom,rpmcc { + compatible = "qcom,rpmcc-msm8916"; + #clock-cells = <1>; + }; + + pm8916-regulators { + compatible = "qcom,rpm-pm8916-regulators"; + + vdd_l1_l2_l3-supply = <&pm8916_s3>; + vdd_l5-supply = <&pm8916_s3>; + vdd_l4_l5_l6-supply = <&pm8916_s4>; + vdd_l7-supply = <&pm8916_s4>; + + pm8916_s1: s1 { + regulator-min-microvolt = <375000>; + regulator-max-microvolt = <1562000>; + }; + pm8916_s2: s2 { + regulator-min-microvolt = <375000>; + regulator-max-microvolt = <1562000>; + }; + pm8916_s3: s3 { + regulator-min-microvolt = <375000>; + regulator-max-microvolt = <1562000>; + }; + pm8916_s4: s4 { + regulator-min-microvolt = <1550000>; + regulator-max-microvolt = <2325000>; + }; + + pm8916_l1: l1 { + regulator-min-microvolt = <375000>; + regulator-max-microvolt = <1525000>; + }; + pm8916_l2: l2 { + regulator-min-microvolt = <375000>; + regulator-max-microvolt = <1525000>; + }; + pm8916_l3: l3 { + regulator-min-microvolt = <375000>; + regulator-max-microvolt = <1525000>; + }; + pm8916_l4: l4 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l5: l5 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l6: l6 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l7: l7 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l8: l8 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l9: l9 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l10: l10 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l11: l11 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l12: l12 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l13: l13 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l14: l14 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l15: l15 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l16: l16 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l17: l17 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + pm8916_l18: l18 { + regulator-min-microvolt = <1750000>; + regulator-max-microvolt = <3337000>; + }; + }; + }; + }; }; gcc: qcom,gcc@1800000 { compatible = "qcom,gcc-msm8916"; #clock-cells = <1>; #reset-cells = <1>; + #power-domain-cells = <1>; reg = <0x1800000 0x80000>; }; + a53cc: qcom,a53cc@0b016000 { + compatible = "qcom,clock-a53-msm8916"; + reg = <0x0b016000 0x40>; + #clock-cells = <1>; + qcom,apcs = <&apcs>; + }; + blsp1_uart2: serial@78b0000 { compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm"; reg = <0x78b0000 0x200>; @@ -132,6 +992,134 @@ status = "disabled"; }; + blsp_dma: dma@7884000 { + compatible = "qcom,bam-v1.7.0"; + reg = <0x07884000 0x23000>; + interrupts = <GIC_SPI 238 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&gcc GCC_BLSP1_AHB_CLK>; + clock-names = "bam_clk"; + #dma-cells = <1>; + qcom,ee = <0>; + status = "disabled"; + }; + + blsp_spi1: spi@78b5000 { + compatible = "qcom,spi-qup-v2.2.1"; + reg = <0x078b5000 0x600>; + interrupts = <GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&gcc GCC_BLSP1_QUP1_SPI_APPS_CLK>, + <&gcc GCC_BLSP1_AHB_CLK>; + clock-names = "core", "iface"; + dmas = <&blsp_dma 5>, <&blsp_dma 4>; + dma-names = "rx", "tx"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi1_default>; + pinctrl-1 = <&spi1_sleep>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + blsp_spi2: spi@78b6000 { + compatible = "qcom,spi-qup-v2.2.1"; + reg = <0x078b6000 0x600>; + interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&gcc GCC_BLSP1_QUP2_SPI_APPS_CLK>, + <&gcc GCC_BLSP1_AHB_CLK>; + clock-names = "core", "iface"; + dmas = <&blsp_dma 7>, <&blsp_dma 6>; + dma-names = "rx", "tx"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi2_default>; + pinctrl-1 = <&spi2_sleep>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + blsp_spi3: spi@78b7000 { + compatible = "qcom,spi-qup-v2.2.1"; + reg = <0x078b7000 0x600>; + interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&gcc GCC_BLSP1_QUP3_SPI_APPS_CLK>, + <&gcc GCC_BLSP1_AHB_CLK>; + clock-names = "core", "iface"; + dmas = <&blsp_dma 9>, <&blsp_dma 8>; + dma-names = "rx", "tx"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi3_default>; + pinctrl-1 = <&spi3_sleep>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + blsp_spi4: spi@78b8000 { + compatible = "qcom,spi-qup-v2.2.1"; + reg = <0x078b8000 0x600>; + interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&gcc GCC_BLSP1_QUP4_SPI_APPS_CLK>, + <&gcc GCC_BLSP1_AHB_CLK>; + clock-names = "core", "iface"; + dmas = <&blsp_dma 11>, <&blsp_dma 10>; + dma-names = "rx", "tx"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi4_default>; + pinctrl-1 = <&spi4_sleep>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + blsp_spi5: spi@78b9000 { + compatible = "qcom,spi-qup-v2.2.1"; + reg = <0x078b9000 0x600>; + interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&gcc GCC_BLSP1_QUP5_SPI_APPS_CLK>, + <&gcc GCC_BLSP1_AHB_CLK>; + clock-names = "core", "iface"; + dmas = <&blsp_dma 13>, <&blsp_dma 12>; + dma-names = "rx", "tx"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi5_default>; + pinctrl-1 = <&spi5_sleep>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + blsp_spi6: spi@78ba000 { + compatible = "qcom,spi-qup-v2.2.1"; + reg = <0x078ba000 0x600>; + interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&gcc GCC_BLSP1_QUP6_SPI_APPS_CLK>, + <&gcc GCC_BLSP1_AHB_CLK>; + clock-names = "core", "iface"; + dmas = <&blsp_dma 15>, <&blsp_dma 14>; + dma-names = "rx", "tx"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi6_default>; + pinctrl-1 = <&spi6_sleep>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + + blsp_i2c4: i2c@78b8000 { + compatible = "qcom,i2c-qup-v2.2.1"; + reg = <0x78b8000 0x1000>; + interrupts = <GIC_SPI 98 0>; + clocks = <&gcc GCC_BLSP1_AHB_CLK>, + <&gcc GCC_BLSP1_QUP4_I2C_APPS_CLK>; + clock-names = "iface", "core"; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&i2c4_default>; + pinctrl-1 = <&i2c4_sleep>; + #address-cells = <1>; + #size-cells = <0>; + status = "disabled"; + }; + intc: interrupt-controller@b000000 { compatible = "qcom,msm-qgic2"; interrupt-controller; @@ -139,6 +1127,11 @@ reg = <0x0b000000 0x1000>, <0x0b002000 0x1000>; }; + l2ccc_0: clock-controller@b011000 { + compatible = "qcom,8916-l2ccc"; + reg = <0x0b011000 0x1000>; + }; + timer@b020000 { #address-cells = <1>; #size-cells = <1>; @@ -198,6 +1191,33 @@ }; }; + sdhc_1: sdhci@07824000 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0x07824900 0x11c>, <0x07824000 0x800>; + reg-names = "hc_mem", "core_mem"; + + interrupts = <0 123 0>, <0 138 0>; + interrupt-names = "hc_irq", "pwr_irq"; + clocks = <&gcc GCC_SDCC1_APPS_CLK>, <&gcc GCC_SDCC1_AHB_CLK>; + clock-names = "core", "iface"; + bus-width = <8>; + non-removable; + status = "disabled"; + }; + + sdhc_2: sdhci@07864000 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0x07864900 0x11c>, <0x07864000 0x800>; + reg-names = "hc_mem", "core_mem"; + + interrupts = <0 125 0>, <0 221 0>; + interrupt-names = "hc_irq", "pwr_irq"; + clocks = <&gcc GCC_SDCC2_APPS_CLK>, <&gcc GCC_SDCC2_AHB_CLK>; + clock-names = "core", "iface"; + bus-width = <4>; + status = "disabled"; + }; + spmi_bus: spmi@200f000 { compatible = "qcom,spmi-pmic-arb"; reg = <0x200f000 0x001000>, @@ -215,5 +1235,161 @@ interrupt-controller; #interrupt-cells = <4>; }; + + usb_dev: usb@78d9000 { + compatible = "qcom,ci-hdrc"; + reg = <0x78d9000 0x400>; + dr_mode = "peripheral"; + interrupts = <GIC_SPI 134 IRQ_TYPE_NONE>; + usb-phy = <&usb_otg>; + status = "disabled"; + }; + + usb_host: ehci@78d9000 { + compatible = "qcom,ehci-host"; + reg = <0x78d9000 0x400>; + interrupts = <GIC_SPI 134 IRQ_TYPE_NONE>; + usb-phy = <&usb_otg>; + status = "disabled"; + }; + + usb_otg: phy@78d9000 { + compatible = "qcom,usb-otg-snps"; + reg = <0x78d9000 0x400>; + interrupts = <GIC_SPI 134 IRQ_TYPE_EDGE_BOTH>, + <GIC_SPI 140 IRQ_TYPE_EDGE_RISING>; + +// vddcx-supply = <&pm8916_s1_corner>; + v1p8-supply = <&pm8916_l7>; + v3p3-supply = <&pm8916_l13>; + qcom,vdd-levels = <1 5 7>; + + qcom,phy-init-sequence = <0x44 0x6B 0x24 0x13>; + dr_mode = "peripheral"; + qcom,otg-control = <2>; // PMIC + qcom,manual-pullup; + + clocks = <&gcc GCC_USB_HS_AHB_CLK>, + <&gcc GCC_USB_HS_SYSTEM_CLK>, + <&gcc GCC_USB2A_PHY_SLEEP_CLK>; + clock-names = "iface", "core", "sleep"; + + resets = <&gcc GCC_USB2A_PHY_BCR>, <&gcc GCC_USB_HS_BCR>; + reset-names = "phy", "link"; + status = "disabled"; + }; + + acc0: clock-controller@b088000 { + compatible = "qcom,arm-cortex-acc"; + reg = <0x0b088000 0x1000>, + <0x0b008000 0x1000>; + }; + + acc1: clock-controller@b098000 { + compatible = "qcom,arm-cortex-acc"; + reg = <0x0b098000 0x1000>, + <0x0b008000 0x1000>; + }; + + acc2: clock-controller@b0a8000 { + compatible = "qcom,arm-cortex-acc"; + reg = <0x0b0a8000 0x1000>, + <0x0b008000 0x1000>; + }; + + acc3: clock-controller@b0b8000 { + compatible = "qcom,arm-cortex-acc"; + reg = <0x0b0b8000 0x1000>, + <0x0b008000 0x1000>; + }; + + /* Audio */ + + lpass: lpass-cpu@07700000 { + status = "disabled"; + compatible = "qcom,lpass-cpu-apq8016"; + clocks = <&gcc GCC_ULTAUDIO_AHBFABRIC_IXFABRIC_CLK>, + <&gcc GCC_ULTAUDIO_PCNOC_MPORT_CLK>, + <&gcc GCC_ULTAUDIO_PCNOC_SWAY_CLK>, + <&gcc GCC_ULTAUDIO_LPAIF_PRI_I2S_CLK>, + <&gcc GCC_ULTAUDIO_LPAIF_SEC_I2S_CLK>, + <&gcc GCC_ULTAUDIO_LPAIF_AUX_I2S_CLK>, + <&gcc GCC_ULTAUDIO_LPAIF_AUX_I2S_CLK>; + + clock-names = "ahbix-clk", + "pcnoc-mport-clk", + "pcnoc-sway-clk", + "mi2s-bit-clk0", + "mi2s-bit-clk1", + "mi2s-bit-clk2", + "mi2s-bit-clk3"; + #sound-dai-cells = <1>; + + interrupts = <0 160 0>; + interrupt-names = "lpass-irq-lpaif"; + reg = <0x07708000 0x10000>, <0x07702000 0x4>, <0x07702004 0x4>; + reg-names = "lpass-lpaif", "mic-iomux", "spkr-iomux"; + }; + + sound: sound { + status = "disabled"; + compatible = "qcom,apq8016-sbc-sndcard"; + reg = <0x07702000 0x4>, <0x07702004 0x4>; + reg-names = "mic-iomux", "spkr-iomux"; + }; + + tcsr: syscon@1937000 { + compatible = "qcom,tcsr-msm8916", "syscon"; + reg = <0x1937000 0x30000>; + }; + + uqfprom: eeprom@58000 { + compatible = "qcom,qfprom-msm8916"; + reg = <0x58000 0x7000>; + }; + + cpr@b018000 { + compatible = "qcom,cpr"; + reg = <0xb018000 0x1000>; + interrupts = <0 15 1>, <0 16 1>, <0 17 1>; + vdd-mx-supply = <&pm8916_l3>; + acc-syscon = <&tcsr>; + eeprom = <&uqfprom>; + + qcom,cpr-ref-clk = <19200>; + qcom,cpr-timer-delay-us = <5000>; + qcom,cpr-timer-cons-up = <0>; + qcom,cpr-timer-cons-down = <2>; + qcom,cpr-up-threshold = <0>; + qcom,cpr-down-threshold = <2>; + qcom,cpr-idle-clocks = <15>; + qcom,cpr-gcnt-us = <1>; + qcom,vdd-apc-step-up-limit = <1>; + qcom,vdd-apc-step-down-limit = <1>; + qcom,cpr-cpus = <&CPU0 &CPU1 &CPU2 &CPU3>; + }; + + qfprom: qfprom@5c000 { + compatible = "qcom,qfprom"; + reg = <0x5c000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + tsens_caldata: caldata@d0 { + reg = <0xd0 0x8>; + }; + tsens_calsel: calsel@ec { + reg = <0xec 0x4>; + }; + }; + + tsens: thermal-sensor@4a8000 { + compatible = "qcom,msm8916-tsens"; + reg = <0x4a8000 0x2000>; + nvmem-cell = <&tsens_caldata>, <&tsens_calsel>; + nvmem-cell-names = "calib", "calib_sel"; + qcom,tsens-slopes = <3200 3200 3200 3200 3200>; + qcom,sensor-id = <0 1 2 4 5>; + #thermal-sensor-cells = <1>; + }; }; }; diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index f38c94f1d898..9049a0f35564 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -23,7 +23,6 @@ CONFIG_CGROUP_HUGETLB=y # CONFIG_NET_NS is not set CONFIG_SCHED_AUTOGROUP=y CONFIG_BLK_DEV_INITRD=y -CONFIG_KALLSYMS_ALL=y # CONFIG_COMPAT_BRK is not set CONFIG_PROFILING=y CONFIG_JUMP_LABEL=y @@ -35,10 +34,10 @@ CONFIG_ARCH_EXYNOS7=y CONFIG_ARCH_FSL_LS2085A=y CONFIG_ARCH_HISI=y CONFIG_ARCH_MEDIATEK=y +CONFIG_ARCH_QCOM=y CONFIG_ARCH_SEATTLE=y CONFIG_ARCH_TEGRA=y CONFIG_ARCH_TEGRA_132_SOC=y -CONFIG_ARCH_QCOM=y CONFIG_ARCH_SPRD=y CONFIG_ARCH_THUNDER=y CONFIG_ARCH_VEXPRESS=y @@ -57,6 +56,14 @@ CONFIG_CMDLINE="console=ttyAMA0" CONFIG_COMPAT=y CONFIG_CPU_IDLE=y CONFIG_ARM_CPUIDLE=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPUFREQ_DT=y +# CONFIG_ARM_TEGRA_CPUFREQ is not set CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y @@ -75,6 +82,7 @@ CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS_MOUNT=y CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_MBYTES=64 CONFIG_BLK_DEV_LOOP=y CONFIG_VIRTIO_BLK=y # CONFIG_SCSI_PROC_FS is not set @@ -93,6 +101,9 @@ CONFIG_NET_XGENE=y CONFIG_SKY2=y CONFIG_SMC91X=y CONFIG_SMSC911X=y +CONFIG_USB_USBNET=y +CONFIG_USB_NET_DM9601=y +CONFIG_USB_NET_PLUSB=y # CONFIG_WLAN is not set CONFIG_INPUT_EVDEV=y CONFIG_KEYBOARD_GPIO=y @@ -111,38 +122,87 @@ CONFIG_SERIAL_XILINX_PS_UART=y CONFIG_SERIAL_XILINX_PS_UART_CONSOLE=y CONFIG_VIRTIO_CONSOLE=y # CONFIG_HW_RANDOM is not set +CONFIG_I2C_QUP=y CONFIG_SPI=y CONFIG_SPI_PL022=y +CONFIG_SPMI=y +CONFIG_DEBUG_PINCTRL=y CONFIG_PINCTRL_MSM8916=y +CONFIG_PINCTRL_QCOM_SPMI_PMIC=y CONFIG_GPIO_PL061=y CONFIG_GPIO_XGENE=y +CONFIG_POWER_RESET_MSM=y CONFIG_POWER_RESET_XGENE=y CONFIG_POWER_RESET_SYSCON=y +CONFIG_POWER_AVS=y +CONFIG_QCOM_CPR=y # CONFIG_HWMON is not set -CONFIG_REGULATOR=y +CONFIG_THERMAL=y +CONFIG_CPU_THERMAL=y +CONFIG_MFD_QCOM_RPM=y +CONFIG_MFD_QCOM_SMD_RPM=y +CONFIG_MFD_SPMI_PMIC=y CONFIG_REGULATOR_FIXED_VOLTAGE=y -CONFIG_FB=y +CONFIG_REGULATOR_QCOM_RPM=y +CONFIG_REGULATOR_QCOM_SMD_RPM=y +CONFIG_REGULATOR_QCOM_SPMI=y +CONFIG_DRM=y +CONFIG_DRM_I2C_ADV7511=y +# CONFIG_DRM_I2C_ADV7511_SLAVE_ENCODER is not set CONFIG_FB_ARMCLCD=y -CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_LOGO=y # CONFIG_LOGO_LINUX_MONO is not set # CONFIG_LOGO_LINUX_VGA16 is not set +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_QCOM=y +CONFIG_SND_SOC_APQ8016_SBC=y CONFIG_USB=y +CONFIG_USB_ANNOUNCE_NEW_DEVICES=y +CONFIG_USB_DYNAMIC_MINORS=y +CONFIG_USB_MON=y CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_MSM=y CONFIG_USB_EHCI_HCD_PLATFORM=y CONFIG_USB_OHCI_HCD=y CONFIG_USB_OHCI_HCD_PLATFORM=y +CONFIG_USB_ACM=y +CONFIG_USB_PRINTER=y +CONFIG_USB_WDM=y CONFIG_USB_STORAGE=y +CONFIG_USB_UAS=y +CONFIG_USBIP_CORE=y +CONFIG_USBIP_HOST=y +CONFIG_USB_CHIPIDEA=y +CONFIG_USB_CHIPIDEA_UDC=y CONFIG_USB_ISP1760=y -CONFIG_USB_ULPI=y +CONFIG_USB_SERIAL=y +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_TEST=m +CONFIG_USB_HSIC_USB3503=y +CONFIG_USB_MSM_OTG=y +CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_DEBUG=y +CONFIG_USB_GADGET_VERBOSE=y +CONFIG_USB_GADGET_DEBUG_FILES=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_CONFIGFS=m +CONFIG_USB_ZERO=m +CONFIG_USB_ETH=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m CONFIG_MMC=y +CONFIG_MMC_BLOCK_MINORS=32 CONFIG_MMC_ARMMMCI=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SDHCI_MSM=y CONFIG_MMC_SPI=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_LEDS_SYSCON=y +CONFIG_LEDS_GPIO=y CONFIG_LEDS_TRIGGERS=y CONFIG_LEDS_TRIGGER_HEARTBEAT=y CONFIG_LEDS_TRIGGER_CPU=y @@ -154,7 +214,14 @@ CONFIG_VIRTIO_BALLOON=y CONFIG_VIRTIO_MMIO=y CONFIG_COMMON_CLK_QCOM=y CONFIG_MSM_GCC_8916=y +CONFIG_REMOTE_SPINLOCK_MSM=y +CONFIG_MSM_RPMCC_8064=y +CONFIG_QCOM_A53=y # CONFIG_IOMMU_SUPPORT is not set +CONFIG_QCOM_GSBI=y +CONFIG_QCOM_SMD=y +CONFIG_QCOM_SMEM=y +CONFIG_EXTCON_USB_GPIO=y CONFIG_PHY_XGENE=y CONFIG_EXT2_FS=y CONFIG_EXT3_FS=y @@ -168,7 +235,6 @@ CONFIG_AUTOFS4_FS=y CONFIG_FUSE_FS=y CONFIG_CUSE=y CONFIG_VFAT_FS=y -CONFIG_TMPFS=y CONFIG_HUGETLBFS=y CONFIG_EFIVAR_FS=y # CONFIG_MISC_FILESYSTEMS is not set @@ -185,8 +251,10 @@ CONFIG_DEBUG_FS=y CONFIG_MAGIC_SYSRQ=y CONFIG_DEBUG_KERNEL=y CONFIG_LOCKUP_DETECTOR=y +# CONFIG_DETECT_HUNG_TASK is not set # CONFIG_SCHED_DEBUG is not set # CONFIG_DEBUG_PREEMPT is not set +CONFIG_PROVE_LOCKING=y # CONFIG_FTRACE is not set CONFIG_MEMTEST=y CONFIG_SECURITY=y diff --git a/arch/arm64/include/asm/cpu_ops.h b/arch/arm64/include/asm/cpu_ops.h index 8f03446cf89f..41bf609206ed 100644 --- a/arch/arm64/include/asm/cpu_ops.h +++ b/arch/arm64/include/asm/cpu_ops.h @@ -71,4 +71,9 @@ static inline void __init cpu_read_bootcpu_ops(void) cpu_read_ops(0); } +#define CPU_METHOD_OF_DECLARE(name, __ops) \ + static const struct cpu_operations *__cpu_method_table_##name \ + __used __section(__cpu_method_of_table) \ + = __ops; + #endif /* ifndef __ASM_CPU_OPS_H */ diff --git a/arch/arm64/include/asm/smp_plat.h b/arch/arm64/include/asm/smp_plat.h index 7abf7570c00f..a2a0cdd59a83 100644 --- a/arch/arm64/include/asm/smp_plat.h +++ b/arch/arm64/include/asm/smp_plat.h @@ -36,6 +36,7 @@ static inline u32 mpidr_hash_size(void) return 1 << mpidr_hash.bits; } +extern void secondary_holding_pen(void); /* * Logical CPU mapping. */ @@ -55,6 +56,7 @@ static inline int get_logical_index(u64 mpidr) return cpu; return -EINVAL; } +extern volatile unsigned long secondary_holding_pen_release; void __init do_post_cpus_up_work(void); diff --git a/arch/arm64/kernel/cpu_ops.c b/arch/arm64/kernel/cpu_ops.c index 5ea337dd2f15..2d2304b985a7 100644 --- a/arch/arm64/kernel/cpu_ops.c +++ b/arch/arm64/kernel/cpu_ops.c @@ -24,29 +24,20 @@ #include <asm/cpu_ops.h> #include <asm/smp_plat.h> -extern const struct cpu_operations smp_spin_table_ops; -extern const struct cpu_operations cpu_psci_ops; - const struct cpu_operations *cpu_ops[NR_CPUS]; -static const struct cpu_operations *supported_cpu_ops[] __initconst = { -#ifdef CONFIG_SMP - &smp_spin_table_ops, -#endif - &cpu_psci_ops, - NULL, -}; +extern struct cpu_operations __cpu_method_of_table[]; +static const struct cpu_operations *__cpu_method_of_table_sentinel + __used __section(__cpu_method_of_table_end); static const struct cpu_operations * __init cpu_get_ops(const char *name) { - const struct cpu_operations **ops = supported_cpu_ops; - - while (*ops) { - if (!strcmp(name, (*ops)->name)) - return *ops; + const struct cpu_operations **start = (void *)__cpu_method_of_table; - ops++; - } + for (; *start; start++) { + if (!strcmp((*start)->name, name)) + return *start; + }; return NULL; } diff --git a/arch/arm64/kernel/psci.c b/arch/arm64/kernel/psci.c index 869f202748e8..fe6484bf2c3e 100644 --- a/arch/arm64/kernel/psci.c +++ b/arch/arm64/kernel/psci.c @@ -597,3 +597,4 @@ const struct cpu_operations cpu_psci_ops = { #endif }; +CPU_METHOD_OF_DECLARE(psci, &cpu_psci_ops); diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index 695801a54ca5..f0c8e80e2367 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -62,6 +62,7 @@ * where to place its SVC stack */ struct secondary_data secondary_data; +volatile unsigned long secondary_holding_pen_release = INVALID_HWID; enum ipi_msg_type { IPI_RESCHEDULE, diff --git a/arch/arm64/kernel/smp_spin_table.c b/arch/arm64/kernel/smp_spin_table.c index aef3605a8c47..3d5325d99e8e 100644 --- a/arch/arm64/kernel/smp_spin_table.c +++ b/arch/arm64/kernel/smp_spin_table.c @@ -28,9 +28,6 @@ #include <asm/io.h> #include <asm/smp_plat.h> -extern void secondary_holding_pen(void); -volatile unsigned long secondary_holding_pen_release = INVALID_HWID; - static phys_addr_t cpu_release_addr[NR_CPUS]; /* @@ -125,9 +122,10 @@ static int smp_spin_table_cpu_boot(unsigned int cpu) return 0; } -const struct cpu_operations smp_spin_table_ops = { +static const struct cpu_operations smp_spin_table_ops = { .name = "spin-table", .cpu_init = smp_spin_table_cpu_init, .cpu_prepare = smp_spin_table_cpu_prepare, .cpu_boot = smp_spin_table_cpu_boot, }; +CPU_METHOD_OF_DECLARE(spin_table, &smp_spin_table_ops); diff --git a/drivers/Kconfig b/drivers/Kconfig index 6e973b8e3a3b..4e2e6aaf0b88 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -184,4 +184,6 @@ source "drivers/android/Kconfig" source "drivers/nvdimm/Kconfig" +source "drivers/nvmem/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index b64b49f6e01b..4c270f5414f0 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -165,3 +165,4 @@ obj-$(CONFIG_RAS) += ras/ obj-$(CONFIG_THUNDERBOLT) += thunderbolt/ obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/ obj-$(CONFIG_ANDROID) += android/ +obj-$(CONFIG_NVMEM) += nvmem/ diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c index 6066a01b20ea..b07ad7c6cfe8 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); +} - else { - if (mux->flags & CLK_MUX_INDEX_BIT) - index = 1 << index; +unsigned int clk_mux_reindex(u8 index, unsigned int *table, + unsigned long flags) +{ + unsigned int val = index; - if (mux->flags & CLK_MUX_INDEX_ONE) - index++; + if (table) { + val = table[val]; + } else { + if (flags & CLK_MUX_INDEX_BIT) + val = 1 << 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,14 +120,14 @@ 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); @@ -117,7 +135,7 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name, const char * const *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; diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index ddb4b541016f..54858a113679 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -50,9 +50,12 @@ struct clk_core { struct clk_core **parents; u8 num_parents; u8 new_parent_index; + u8 safe_parent_index; unsigned long rate; unsigned long req_rate; + unsigned long old_rate; unsigned long new_rate; + struct clk_core *safe_parent; struct clk_core *new_parent; struct clk_core *new_child; unsigned long flags; @@ -1224,7 +1227,8 @@ out: static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate, struct clk_core *new_parent, u8 p_index) { - struct clk_core *child; + struct clk_core *child, *parent; + struct clk_hw *parent_hw; core->new_rate = new_rate; core->new_parent = new_parent; @@ -1234,6 +1238,18 @@ static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate, if (new_parent && new_parent != core->parent) new_parent->new_child = core; + if (core->ops->get_safe_parent) { + parent_hw = core->ops->get_safe_parent(core->hw); + if (parent_hw) { + parent = parent_hw->core; + p_index = clk_fetch_parent_index(core, parent); + core->safe_parent_index = p_index; + core->safe_parent = parent; + } + } else { + core->safe_parent = NULL; + } + hlist_for_each_entry(child, &core->children, child_node) { child->new_rate = clk_recalc(child, new_rate); clk_calc_subtree(child, child->new_rate, NULL, 0); @@ -1338,14 +1354,43 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core, unsigned long event) { struct clk_core *child, *tmp_clk, *fail_clk = NULL; + struct clk_core *old_parent; int ret = NOTIFY_DONE; - if (core->rate == core->new_rate) + if (core->rate == core->new_rate && event != POST_RATE_CHANGE) return NULL; + switch (event) { + case PRE_RATE_CHANGE: + if (core->safe_parent) + core->ops->set_parent(core->hw, core->safe_parent_index); + core->old_rate = core->rate; + break; + case POST_RATE_CHANGE: + if (core->safe_parent) { + old_parent = __clk_set_parent_before(core, + core->new_parent); + if (core->ops->set_rate_and_parent) { + core->ops->set_rate_and_parent(core->hw, + core->new_rate, + core->new_parent ? + core->new_parent->rate : 0, + core->new_parent_index); + } else if (core->ops->set_parent) { + core->ops->set_parent(core->hw, + core->new_parent_index); + } + __clk_set_parent_after(core, core->new_parent, + old_parent); + } + break; + } + if (core->notifier_count) { - ret = __clk_notify(core, event, core->rate, core->new_rate); - if (ret & NOTIFY_STOP_MASK) + if (event != POST_RATE_CHANGE || core->old_rate != core->rate) + ret = __clk_notify(core, event, core->old_rate, + core->new_rate); + if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE) fail_clk = core; } @@ -1372,23 +1417,19 @@ static struct clk_core *clk_propagate_rate_change(struct clk_core *core, * walk down a subtree and set the new rates notifying the rate * change on the way */ -static void clk_change_rate(struct clk_core *core) +static void +clk_change_rate(struct clk_core *core, unsigned long best_parent_rate) { struct clk_core *child; struct hlist_node *tmp; unsigned long old_rate; - unsigned long best_parent_rate = 0; bool skip_set_rate = false; struct clk_core *old_parent; old_rate = core->rate; - if (core->new_parent) - best_parent_rate = core->new_parent->rate; - else if (core->parent) - best_parent_rate = core->parent->rate; - - if (core->new_parent && core->new_parent != core->parent) { + if (core->new_parent && core->new_parent != core->parent && + !core->safe_parent) { old_parent = __clk_set_parent_before(core, core->new_parent); trace_clk_set_parent(core, core->new_parent); @@ -1413,6 +1454,7 @@ static void clk_change_rate(struct clk_core *core) trace_clk_set_rate_complete(core, core->new_rate); core->rate = clk_recalc(core, best_parent_rate); + core->rate = core->new_rate; if (core->notifier_count && old_rate != core->rate) __clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate); @@ -1428,12 +1470,13 @@ static void clk_change_rate(struct clk_core *core) /* Skip children who will be reparented to another clock */ if (child->new_parent && child->new_parent != core) continue; - clk_change_rate(child); + if (child->new_rate != child->rate) + clk_change_rate(child, core->new_rate); } - /* handle the new child who might not be in core->children yet */ - if (core->new_child) - clk_change_rate(core->new_child); + /* handle the new child who might not be in clk->children yet */ + if (core->new_child && core->new_child->new_rate != core->new_child->rate) + clk_change_rate(core->new_child, core->new_rate); } static int clk_core_set_rate_nolock(struct clk_core *core, @@ -1442,6 +1485,7 @@ static int clk_core_set_rate_nolock(struct clk_core *core, struct clk_core *top, *fail_clk; unsigned long rate = req_rate; int ret = 0; + unsigned long parent_rate; if (!core) return 0; @@ -1467,11 +1511,18 @@ static int clk_core_set_rate_nolock(struct clk_core *core, return -EBUSY; } + 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); core->req_rate = req_rate; + clk_propagate_rate_change(top, POST_RATE_CHANGE); + return ret; } diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 59d16668bdf5..4617dcddb654 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -7,6 +7,7 @@ config COMMON_CLK_QCOM config APQ_GCC_8084 tristate "APQ8084 Global Clock Controller" + select QCOM_GDSC depends on COMMON_CLK_QCOM help Support for the global clock controller on apq8084 devices. @@ -16,6 +17,7 @@ config APQ_GCC_8084 config APQ_MMCC_8084 tristate "APQ8084 Multimedia Clock Controller" select APQ_GCC_8084 + select QCOM_GDSC depends on COMMON_CLK_QCOM help Support for the multimedia clock controller on apq8084 devices. @@ -39,6 +41,11 @@ config IPQ_LCC_806X Say Y if you want to use audio devices such as i2s, pcm, S/PDIF, etc. +config QCOM_GDSC + bool + select PM_GENERIC_DOMAINS if PM + depends on COMMON_CLK_QCOM + config MSM_GCC_8660 tristate "MSM8660 Global Clock Controller" depends on COMMON_CLK_QCOM @@ -49,6 +56,7 @@ config MSM_GCC_8660 config MSM_GCC_8916 tristate "MSM8916 Global Clock Controller" + select QCOM_GDSC depends on COMMON_CLK_QCOM help Support for the global clock controller on msm8916 devices. @@ -83,6 +91,7 @@ config MSM_MMCC_8960 config MSM_GCC_8974 tristate "MSM8974 Global Clock Controller" + select QCOM_GDSC depends on COMMON_CLK_QCOM help Support for the global clock controller on msm8974 devices. @@ -92,8 +101,53 @@ config MSM_GCC_8974 config MSM_MMCC_8974 tristate "MSM8974 Multimedia Clock Controller" select MSM_GCC_8974 + select QCOM_GDSC depends on COMMON_CLK_QCOM help 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 MSM_RPMCC_8064 + tristate "MSM8064 RPM Clock Controller" + depends on COMMON_CLK_QCOM + help + Support for the RPM clock controller which controls fabric clocks + on SoCs like MSM8064/APQ8064. + Say Y if you want to use fabric clocks like AFAB, DAYTONA etc. + +config QCOM_A53 + tristate "A53 Clock Controller" + depends on COMMON_CLK_QCOM + help + Support for the A53 clock controller on Qualcomm devices. + Say Y if you want to support CPU frequency scaling on devices + such as MSM8916. diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 50b337a24a87..a6bc895f646e 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -8,7 +8,11 @@ clk-qcom-y += clk-rcg2.o clk-qcom-y += clk-branch.o clk-qcom-y += clk-regmap-divider.o clk-qcom-y += clk-regmap-mux.o +clk-qcom-y += clk-regmap-mux-div.o +clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o +clk-qcom-y += clk-hfpll.o clk-qcom-y += reset.o +clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o obj-$(CONFIG_APQ_MMCC_8084) += mmcc-apq8084.o @@ -16,8 +20,14 @@ 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_8916) += gcc-msm8916.o +obj-$(CONFIG_MSM_GCC_8916) += rpmcc-msm8916.o clk-rpm.o obj-$(CONFIG_MSM_GCC_8960) += gcc-msm8960.o obj-$(CONFIG_MSM_LCC_8960) += lcc-msm8960.o +obj-$(CONFIG_MSM_RPMCC_8064) += rpmcc-apq8064.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_A53) += clk-a53.o diff --git a/drivers/clk/qcom/clk-a53.c b/drivers/clk/qcom/clk-a53.c new file mode 100644 index 000000000000..7b3dc7e6f22a --- /dev/null +++ b/drivers/clk/qcom/clk-a53.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2015, Linaro Limited + * 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/cpu.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include "clk-pll.h" +#include "clk-regmap.h" +#include "clk-regmap-mux-div.h" + +#define F_APCS_PLL(f, l, m, n) { (f), (l), (m), (n), 0 } + +static struct pll_freq_tbl apcs_pll_freq[] = { + F_APCS_PLL( 998400000, 52, 0x0, 0x1), + F_APCS_PLL(1094400000, 57, 0x0, 0x1), + F_APCS_PLL(1152000000, 62, 0x0, 0x1), + F_APCS_PLL(1209600000, 65, 0x0, 0x1), + F_APCS_PLL(1401600000, 73, 0x0, 0x1), +}; + +static struct clk_pll a53sspll = { + .l_reg = 0x04, + .m_reg = 0x08, + .n_reg = 0x0c, + .config_reg = 0x14, + .mode_reg = 0x00, + .status_reg = 0x1c, + .status_bit = 16, + .freq_tbl = apcs_pll_freq, + .clkr.hw.init = &(struct clk_init_data){ + .name = "a53sspll", + .parent_names = (const char *[]){ "xo" }, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_pll_sr2_ops, + }, +}; + +static const struct regmap_config a53sspll_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x40, + .fast_io = true, +}; + +static struct clk *a53ss_add_pll(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *base; + struct regmap *regmap; + struct clk_pll *pll; + + pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return base; + + pll = &a53sspll; + + regmap = devm_regmap_init_mmio(dev, base, &a53sspll_regmap_config); + if (IS_ERR(regmap)) + return ERR_CAST(regmap); + + return devm_clk_register_regmap(dev, &pll->clkr); +} + +enum { + P_GPLL0, + P_A53SSPLL, +}; + +static const struct parent_map gpll0_a53sspll_map[] = { + { P_GPLL0, 4 }, + { P_A53SSPLL, 5 }, +}; + +static const char *gpll0_a53sspll[] = { + "gpll0_vote", + "a53sspll", +}; + +static struct clk_regmap_mux_div a53ssmux = { + .reg_offset = 0x50, + .hid_width = 5, + .hid_shift = 0, + .src_width = 3, + .src_shift = 8, + .safe_src = 4, + .safe_freq = 400000000, + .parent_map = gpll0_a53sspll_map, + .clkr.hw.init = &(struct clk_init_data){ + .name = "a53ssmux", + .parent_names = gpll0_a53sspll, + .num_parents = 2, + .ops = &clk_regmap_mux_div_ops, + .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk *a53ss_add_mux(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct regmap *regmap; + struct clk_regmap_mux_div *mux; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux = &a53ssmux; + + regmap = syscon_regmap_lookup_by_phandle(np, "qcom,apcs"); + if (IS_ERR(regmap)) + return ERR_CAST(regmap); + + mux->clkr.regmap = regmap; + return devm_clk_register(dev, &mux->clkr.hw); +} + +static const struct of_device_id qcom_a53_match_table[] = { + { .compatible = "qcom,clock-a53-msm8916" }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_a53_match_table); + +static int qcom_a53_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct clk *clk_pll, *clk_mux; + struct clk_onecell_data *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->clks = devm_kcalloc(dev, 2, sizeof(struct clk *), GFP_KERNEL); + if (!data->clks) + return -ENOMEM; + + clk_pll = a53ss_add_pll(pdev); + if (IS_ERR(clk_pll)) + return PTR_ERR(clk_pll); + + clk_mux = a53ss_add_mux(pdev); + if (IS_ERR(clk_mux)) + return PTR_ERR(clk_mux); + + data->clks[0] = clk_pll; + data->clks[1] = clk_mux; + data->clk_num = 2; + + clk_prepare_enable(clk_pll); + + return of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, data); +} + +static struct platform_driver qcom_a53_driver = { + .probe = qcom_a53_probe, + .driver = { + .name = "qcom-a53", + .of_match_table = qcom_a53_match_table, + }, +}; + +module_platform_driver(qcom_a53_driver); +arch_initcall(qcom_a53_driver_init); + +MODULE_DESCRIPTION("Qualcomm A53 Clock Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qcom-a53"); 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..c0756989e314 --- /dev/null +++ b/drivers/clk/qcom/clk-krait.c @@ -0,0 +1,170 @@ +/* + * 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_hw *krait_mux_get_safe_parent(struct clk_hw *hw, + unsigned long *safe_freq) +{ + 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; + + if (safe_freq) + *safe_freq = 0; + + return __clk_get_hw(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) | mask << d->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-pll.c b/drivers/clk/qcom/clk-pll.c index 245d5063a385..017eede87237 100644 --- a/drivers/clk/qcom/clk-pll.c +++ b/drivers/clk/qcom/clk-pll.c @@ -292,3 +292,78 @@ void clk_pll_configure_sr_hpm_lp(struct clk_pll *pll, struct regmap *regmap, clk_pll_set_fsm_mode(pll, regmap, 0); } EXPORT_SYMBOL_GPL(clk_pll_configure_sr_hpm_lp); + +static int clk_pll_sr2_enable(struct clk_hw *hw) +{ + struct clk_pll *pll = to_clk_pll(hw); + int ret; + u32 mode; + + ret = regmap_read(pll->clkr.regmap, pll->mode_reg, &mode); + if (ret) + return ret; + + /* Disable PLL bypass mode. */ + ret = regmap_update_bits(pll->clkr.regmap, pll->mode_reg, PLL_BYPASSNL, + PLL_BYPASSNL); + if (ret) + return ret; + + /* + * 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. */ + ret = regmap_update_bits(pll->clkr.regmap, pll->mode_reg, PLL_RESET_N, + PLL_RESET_N); + if (ret) + return ret; + + ret = wait_for_pll(pll); + if (ret) + return ret; + + /* Enable PLL output. */ + return regmap_update_bits(pll->clkr.regmap, pll->mode_reg, PLL_OUTCTRL, + PLL_OUTCTRL); +} + +static int +clk_pll_sr2_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long prate) +{ + struct clk_pll *pll = to_clk_pll(hw); + const struct pll_freq_tbl *f; + bool enabled; + u32 mode; + u32 enable_mask = PLL_OUTCTRL | PLL_BYPASSNL | PLL_RESET_N; + + f = find_freq(pll->freq_tbl, rate); + if (!f) + return -EINVAL; + + regmap_read(pll->clkr.regmap, pll->mode_reg, &mode); + enabled = (mode & enable_mask) == enable_mask; + + if (enabled) + clk_pll_disable(hw); + + regmap_update_bits(pll->clkr.regmap, pll->l_reg, 0x3ff, f->l); + regmap_update_bits(pll->clkr.regmap, pll->m_reg, 0x7ffff, f->m); + regmap_update_bits(pll->clkr.regmap, pll->n_reg, 0x7ffff, f->n); + + if (enabled) + clk_pll_sr2_enable(hw); + + return 0; +} + +const struct clk_ops clk_pll_sr2_ops = { + .enable = clk_pll_sr2_enable, + .disable = clk_pll_disable, + .set_rate = clk_pll_sr2_set_rate, + .recalc_rate = clk_pll_recalc_rate, + .determine_rate = clk_pll_determine_rate, +}; +EXPORT_SYMBOL_GPL(clk_pll_sr2_ops); diff --git a/drivers/clk/qcom/clk-pll.h b/drivers/clk/qcom/clk-pll.h index c9c0cda306d0..ffd0c63bddbc 100644 --- a/drivers/clk/qcom/clk-pll.h +++ b/drivers/clk/qcom/clk-pll.h @@ -62,6 +62,7 @@ struct clk_pll { extern const struct clk_ops clk_pll_ops; extern const struct clk_ops clk_pll_vote_ops; +extern const struct clk_ops clk_pll_sr2_ops; #define to_clk_pll(_hw) container_of(to_clk_regmap(_hw), struct clk_pll, clkr) diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h index 56028bb31d87..9b3ef5d67895 100644 --- a/drivers/clk/qcom/clk-rcg.h +++ b/drivers/clk/qcom/clk-rcg.h @@ -153,6 +153,7 @@ extern const struct clk_ops clk_dyn_rcg_ops; * @hid_width: number of bits in half integer divider * @parent_map: map from software's parent index to hardware's src_sel field * @freq_tbl: frequency table + * @current_freq: cached frequency, used for shared branches * @clkr: regmap clock handle * @lock: register lock * @@ -163,14 +164,17 @@ struct clk_rcg2 { u8 hid_width; const struct parent_map *parent_map; const struct freq_tbl *freq_tbl; + unsigned long current_freq; struct clk_regmap clkr; }; #define to_clk_rcg2(_hw) container_of(to_clk_regmap(_hw), struct clk_rcg2, clkr) extern const struct clk_ops clk_rcg2_ops; +extern const struct clk_ops clk_rcg2_shared_ops; extern const struct clk_ops clk_edp_pixel_ops; extern const struct clk_ops clk_byte_ops; +extern const struct clk_ops clk_byte2_ops; extern const struct clk_ops clk_pixel_ops; #endif diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index b95d17fbb8d7..c5db8e08b3e0 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -301,6 +301,74 @@ const struct clk_ops clk_rcg2_ops = { }; EXPORT_SYMBOL_GPL(clk_rcg2_ops); +static int clk_rcg2_shared_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + const char *name = __clk_get_name(hw->clk); + int ret, count; + + /* cache the frequency */ + rcg->current_freq = rate; + + /* do not set any rate while the clock is off */ + if (!__clk_is_enabled(hw->clk)) + return 0; + + /* force enable RCG */ + ret = regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, + CMD_ROOT_EN, CMD_ROOT_EN); + if (ret) + return ret; + + /* wait for RCG to turn ON */ + for (count = 500; count > 0; count--) { + ret = clk_rcg2_is_enabled(hw); + if (ret) + break; + udelay(1); + } + if (!count) + pr_err("%s: RCG did not turn on\n", name); + + /* set clock rate */ + ret = __clk_rcg2_set_rate(hw, rate); + if (ret) + return ret; + + /* clear force enable RCG */ + return regmap_update_bits(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, + CMD_ROOT_EN, 0); +} + +static int clk_rcg2_shared_enable(struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + + if (!rcg->current_freq) + return 0; + + return clk_rcg2_shared_set_rate(hw, rcg->current_freq, 0); +} + +static void clk_rcg2_shared_disable(struct clk_hw *hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + + /* switch to XO, which is the lowest entry in the freq table */ + clk_rcg2_shared_set_rate(hw, rcg->freq_tbl[0].freq, 0); +} + +const struct clk_ops clk_rcg2_shared_ops = { + .enable = clk_rcg2_shared_enable, + .disable = clk_rcg2_shared_disable, + .get_parent = clk_rcg2_get_parent, + .recalc_rate = clk_rcg2_recalc_rate, + .determine_rate = clk_rcg2_determine_rate, + .set_rate = clk_rcg2_shared_set_rate, +}; +EXPORT_SYMBOL_GPL(clk_rcg2_shared_ops); + struct frac_entry { int num; int den; @@ -486,6 +554,75 @@ const struct clk_ops clk_byte_ops = { }; EXPORT_SYMBOL_GPL(clk_byte_ops); +static long clk_byte2_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long min_rate, unsigned long max_rate, + unsigned long *p_rate, struct clk_hw **p_hw) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + unsigned long parent_rate, div; + u32 mask = BIT(rcg->hid_width) - 1; + struct clk *p; + + if (rate == 0) + return -EINVAL; + + p = (*p_hw)->clk; + *p_rate = parent_rate = __clk_round_rate(p, rate); + + div = DIV_ROUND_UP((2 * parent_rate), rate) - 1; + div = min_t(u32, div, mask); + + return calc_rate(parent_rate, 0, 0, 0, div); +} + +static int clk_byte2_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + struct freq_tbl f = { 0 }; + unsigned long div; + int i, num_parents = __clk_get_num_parents(hw->clk); + u32 mask = BIT(rcg->hid_width) - 1; + u32 cfg; + + div = DIV_ROUND_UP((2 * parent_rate), rate) - 1; + div = min_t(u32, div, mask); + + f.pre_div = div; + + regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &cfg); + cfg &= CFG_SRC_SEL_MASK; + cfg >>= CFG_SRC_SEL_SHIFT; + + for (i = 0; i < num_parents; i++) { + if (cfg == rcg->parent_map[i].cfg) { + f.src = rcg->parent_map[i].src; + return clk_rcg2_configure(rcg, &f); + } + } + + return -EINVAL; +} + +static int clk_byte2_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate, u8 index) +{ + /* Parent index is set statically in frequency table */ + return clk_byte2_set_rate(hw, rate, parent_rate); +} + +/* Read the hardware to determine parent during set_rate */ +const struct clk_ops clk_byte2_ops = { + .is_enabled = clk_rcg2_is_enabled, + .get_parent = clk_rcg2_get_parent, + .set_parent = clk_rcg2_set_parent, + .recalc_rate = clk_rcg2_recalc_rate, + .set_rate = clk_byte2_set_rate, + .set_rate_and_parent = clk_byte2_set_rate_and_parent, + .determine_rate = clk_byte2_determine_rate, +}; +EXPORT_SYMBOL_GPL(clk_byte2_ops); + static const struct frac_entry frac_table_pixel[] = { { 3, 8 }, { 2, 9 }, @@ -499,15 +636,10 @@ static long clk_pixel_determine_rate(struct clk_hw *hw, unsigned long rate, unsigned long max_rate, unsigned long *p_rate, struct clk_hw **p) { - struct clk_rcg2 *rcg = to_clk_rcg2(hw); unsigned long request, src_rate; int delta = 100000; - const struct freq_tbl *f = rcg->freq_tbl; const struct frac_entry *frac = frac_table_pixel; - int index = qcom_find_src_index(hw, rcg->parent_map, f->src); - struct clk *parent = clk_get_parent_by_index(hw->clk, index); - - *p = __clk_get_hw(parent); + struct clk *parent = (*p)->clk; for (; frac->num; frac++) { request = (rate * frac->den) / frac->num; @@ -528,14 +660,28 @@ static int clk_pixel_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct clk_rcg2 *rcg = to_clk_rcg2(hw); - struct freq_tbl f = *rcg->freq_tbl; + struct freq_tbl f = { 0 }; const struct frac_entry *frac = frac_table_pixel; unsigned long request, src_rate; int delta = 100000; u32 mask = BIT(rcg->hid_width) - 1; - u32 hid_div; - int index = qcom_find_src_index(hw, rcg->parent_map, f.src); - struct clk *parent = clk_get_parent_by_index(hw->clk, index); + u32 hid_div, cfg; + struct clk *parent = NULL; + int i, num_parents = __clk_get_num_parents(hw->clk); + + regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, &cfg); + cfg &= CFG_SRC_SEL_MASK; + cfg >>= CFG_SRC_SEL_SHIFT; + + for (i = 0; i < num_parents; i++) + if (cfg == rcg->parent_map[i].cfg) { + parent = clk_get_parent_by_index(hw->clk, i); + f.src = rcg->parent_map[i].src; + break; + } + + if (!parent) + return -EINVAL; for (; frac->num; frac++) { request = (rate * frac->den) / frac->num; diff --git a/drivers/clk/qcom/clk-regmap-mux-div.c b/drivers/clk/qcom/clk-regmap-mux-div.c new file mode 100644 index 000000000000..fe4664d8bcf3 --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-mux-div.c @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2015, Linaro Limited + * 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/bitops.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/regmap.h> + +#include "clk-regmap-mux-div.h" + +#define CMD_RCGR 0x0 +#define CMD_RCGR_UPDATE BIT(0) +#define CMD_RCGR_DIRTY_CFG BIT(4) +#define CMD_RCGR_ROOT_OFF BIT(31) +#define CFG_RCGR 0x4 + +static int __mux_div_update_config(struct clk_regmap_mux_div *md) +{ + int ret; + u32 val, count; + const char *name = __clk_get_name(md->clkr.hw.clk); + + ret = regmap_update_bits(md->clkr.regmap, CMD_RCGR + md->reg_offset, + CMD_RCGR_UPDATE, CMD_RCGR_UPDATE); + if (ret) + return ret; + + /* Wait for update to take effect */ + for (count = 500; count > 0; count--) { + ret = regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, + &val); + if (ret) + return ret; + if (!(val & CMD_RCGR_UPDATE)) + return 0; + udelay(1); + } + + pr_err("%s: rcg did not update its configuration.", name); + return -EBUSY; +} + +static int __mux_div_set_src_div(struct clk_regmap_mux_div *md, u32 src_sel, + u32 src_div) +{ + int ret; + u32 val, mask; + + val = (src_div << md->hid_shift) | (src_sel << md->src_shift); + mask = ((BIT(md->hid_width) - 1) << md->hid_shift) | + ((BIT(md->src_width) - 1) << md->src_shift); + + ret = regmap_update_bits(md->clkr.regmap, CFG_RCGR + md->reg_offset, + mask, val); + if (ret) + return ret; + + ret = __mux_div_update_config(md); + return ret; +} + +static void __mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src_sel, + u32 *src_div) +{ + u32 val, div, src; + const char *name = __clk_get_name(md->clkr.hw.clk); + + regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, &val); + + if (val & CMD_RCGR_DIRTY_CFG) { + pr_err("%s: rcg configuration is pending.\n", name); + return; + } + + regmap_read(md->clkr.regmap, CFG_RCGR + md->reg_offset, &val); + src = (val >> md->src_shift); + src &= BIT(md->src_width) - 1; + *src_sel = src; + + div = (val >> md->hid_shift); + div &= BIT(md->hid_width) - 1; + *src_div = div; +} + +static int mux_div_enable(struct clk_hw *hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + return __mux_div_set_src_div(md, md->src_sel, md->div); +} + +static inline bool is_better_rate(unsigned long req, unsigned long best, + unsigned long new) +{ + return (req <= new && new < best) || (best < req && best < new); +} + +static long mux_div_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long min_rate, + unsigned long max_rate, + unsigned long *best_parent_rate, + struct clk_hw **best_parent_hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + unsigned int i, div, max_div; + unsigned long actual_rate, rrate = 0; + + for (i = 0; i < __clk_get_num_parents(hw->clk); i++) { + struct clk *parent = clk_get_parent_by_index(hw->clk, i); + unsigned long parent_rate = __clk_get_rate(parent); + + max_div = BIT(md->hid_width) - 1; + for (div = 1; div < max_div; div++) { + parent_rate = mult_frac(rate, div, 2); + parent_rate = __clk_round_rate(parent, parent_rate); + actual_rate = mult_frac(parent_rate, 2, div); + + if (is_better_rate(rate, rrate, actual_rate)) { + rrate = actual_rate; + *best_parent_rate = parent_rate; + *best_parent_hw = __clk_get_hw(parent); + } + + if (actual_rate < rate || rrate <= rate) + break; + } + } + + if (!rrate) + return -EINVAL; + + return rrate; +} + +static int __mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + unsigned long prate, u32 src_sel) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + int ret, i; + u32 div, max_div, best_src = 0, best_div = 0; + unsigned long actual_rate = 0, rrate = 0; + + for (i = 0; i < __clk_get_num_parents(hw->clk); i++) { + struct clk *parent = clk_get_parent_by_index(hw->clk, i); + unsigned long parent_rate = __clk_get_rate(parent); + + max_div = BIT(md->hid_width) - 1; + for (div = 1; div < max_div; div++) { + parent_rate = mult_frac(rate, div, 2); + parent_rate = __clk_round_rate(parent, parent_rate); + actual_rate = mult_frac(parent_rate, 2, div); + + if (is_better_rate(rate, rrate, actual_rate)) { + rrate = actual_rate; + best_src = md->parent_map[i].cfg; + best_div = div - 1; + } + + if (actual_rate < rate || rrate <= rate) + break; + } + } + + ret = __mux_div_set_src_div(md, best_src, best_div); + if (!ret) { + md->div = best_div; + md->src_sel = best_src; + } + + return ret; +} + +static u8 mux_div_get_parent(struct clk_hw *hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + int num_parents = __clk_get_num_parents(hw->clk); + const char *name = __clk_get_name(hw->clk); + u32 i, div, src; + + __mux_div_get_src_div(md, &src, &div); + + for (i = 0; i < num_parents; i++) + if (src == md->parent_map[i].cfg) + return i; + + pr_err("%s: Can't find parent %d\n", name, src); + return 0; +} + +static int mux_div_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + return __mux_div_set_src_div(md, md->parent_map[index].cfg, md->div); +} + +static int mux_div_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long prate) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + u8 pindex = mux_div_get_parent(hw); + struct clk *parent = clk_get_parent_by_index(hw->clk, pindex); + unsigned long current_prate = __clk_get_rate(parent); + + if (rate > current_prate) + return -EINVAL; + + return __mux_div_set_rate_and_parent(hw, rate, prate, md->src_sel); +} + +static int mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + unsigned long prate, u8 index) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + + return __mux_div_set_rate_and_parent(hw, rate, prate, + md->parent_map[index].cfg); +} + +static unsigned long mux_div_recalc_rate(struct clk_hw *hw, unsigned long prate) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + u32 div, src; + int i, num_parents = __clk_get_num_parents(hw->clk); + const char *name = __clk_get_name(hw->clk); + + __mux_div_get_src_div(md, &src, &div); + for (i = 0; i < num_parents; i++) + if (src == md->parent_map[i].cfg) { + struct clk *p = clk_get_parent_by_index(hw->clk, i); + unsigned long parent_rate = __clk_get_rate(p); + + return mult_frac(parent_rate, 2, div + 1); + } + + pr_err("%s: Can't find parent %d\n", name, src); + return 0; +} + +static struct clk_hw *mux_div_get_safe_parent(struct clk_hw *hw, + unsigned long *safe_freq) +{ + int i; + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + int num_parents = __clk_get_num_parents(hw->clk); + + if (md->safe_freq) + *safe_freq = md->safe_freq; + + for (i = 0; i < num_parents; i++) + if (md->safe_src == md->parent_map[i].cfg) + break; + + return __clk_get_hw(clk_get_parent_by_index(hw->clk, i)); +} + +static void mux_div_disable(struct clk_hw *hw) +{ + struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw); + struct clk_hw *parent; + u32 div; + + if (!md->safe_freq || !md->safe_src) + return; + + parent = mux_div_get_safe_parent(hw, &md->safe_freq); + div = divider_get_val(md->safe_freq, clk_get_rate(parent->clk), NULL, + md->hid_width, CLK_DIVIDER_ROUND_CLOSEST); + div = 2 * div + 1; + + __mux_div_set_src_div(md, md->safe_src, div); +} + +const struct clk_ops clk_regmap_mux_div_ops = { + .enable = mux_div_enable, + .disable = mux_div_disable, + .get_parent = mux_div_get_parent, + .set_parent = mux_div_set_parent, + .set_rate = mux_div_set_rate, + .set_rate_and_parent = mux_div_set_rate_and_parent, + .determine_rate = mux_div_determine_rate, + .recalc_rate = mux_div_recalc_rate, + .get_safe_parent = mux_div_get_safe_parent, +}; +EXPORT_SYMBOL_GPL(clk_regmap_mux_div_ops); diff --git a/drivers/clk/qcom/clk-regmap-mux-div.h b/drivers/clk/qcom/clk-regmap-mux-div.h new file mode 100644 index 000000000000..b887a900ad89 --- /dev/null +++ b/drivers/clk/qcom/clk-regmap-mux-div.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015, Linaro Limited + * 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_DIV_H__ +#define __QCOM_CLK_REGMAP_MUX_DIV_H__ + +#include <linux/clk-provider.h> +#include "clk-regmap.h" +#include "clk-rcg.h" + +/** + * struct mux_div_clk - combined mux/divider clock + * @reg_offset: offset of the mux/divider register + * @hid_width: number of bits in half integer divider + * @hid_shift: lowest bit of hid value field + * @src_width: number of bits in source select + * @src_shift: lowest bit of source select field + * @div: the divider configuration value + * @src_sel: the mux index which will be used if the clock is enabled + * @safe_src: value for safe source + * @safe_freq: When switching rates from A to B, the mux div clock will + * instead switch from A -> safe_freq -> B. This allows the + * mux_div clock to change rates while enabled, even if this + * behavior is not supported by the parent clocks. + * If changing the rate of parent A also causes the rate of + * parent B to change, then safe_freq must be defined. + * safe_freq is expected to have a source clock which is always + * on and runs at only one rate. + * @parent_map: pointer to parent_map struct + * @clkr: handle between common and hardware-specific interfaces + */ + +struct clk_regmap_mux_div { + u32 reg_offset; + u32 hid_width; + u32 hid_shift; + u32 src_width; + u32 src_shift; + u32 div; + u32 src_sel; + u32 safe_src; + unsigned long safe_freq; + const struct parent_map *parent_map; + struct clk_regmap clkr; +}; + +#define to_clk_regmap_mux_div(_hw) \ + container_of(to_clk_regmap(_hw), struct clk_regmap_mux_div, clkr) + +extern const struct clk_ops clk_regmap_mux_div_ops; + +#endif diff --git a/drivers/clk/qcom/clk-rpm.c b/drivers/clk/qcom/clk-rpm.c new file mode 100644 index 000000000000..b0235a4315c0 --- /dev/null +++ b/drivers/clk/qcom/clk-rpm.c @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2015, Linaro Limited + * 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/clk.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include "clk-rpm.h" + +static int clk_rpm_set_rate_active(struct clk_rpm *r, unsigned long value) +{ + struct rpm_clk_req req = { + .key = QCOM_RPM_SMD_KEY_RATE, + .nbytes = sizeof(u32), + .value = value, + }; + + return qcom_rpm_smd_write(r->rpm, QCOM_SMD_RPM_ACTIVE_STATE, + r->rpm_res_type, r->rpm_clk_id, &req, + sizeof(req)); +} + + +static int clk_rpm_prepare(struct clk_hw *hw) +{ + struct clk_rpm *r = to_clk_rpm(hw); + struct clk_rpm *peer = r->peer; + unsigned long this_khz, peer_khz = 0; + u32 value; + int rc = 0; + + /* Convert to KHz */ + this_khz = DIV_ROUND_UP(r->rate, 1000); + + /* Don't send requests to the RPM if the rate has not been set. */ + if (this_khz == 0) + goto out; + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) + peer_khz = DIV_ROUND_UP(peer->rate, 1000); + + value = max(this_khz, peer_khz); + if (r->branch) + value = !!value; + + rc = clk_rpm_set_rate_active(r, value); + if (rc) + goto out; + +out: + if (!rc) + r->enabled = true; + + return rc; +} + +static void clk_rpm_unprepare(struct clk_hw *hw) +{ + struct clk_rpm *r = to_clk_rpm(hw); + + if (r->rate) { + struct clk_rpm *peer = r->peer; + unsigned long peer_khz = 0; + u32 value; + int rc; + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) + peer_khz = DIV_ROUND_UP(peer->rate, 1000); + + value = r->branch ? !!peer_khz : peer_khz; + rc = clk_rpm_set_rate_active(r, value); + if (rc) + return; + } + r->enabled = false; +} + +static int clk_rpm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_rpm *r = to_clk_rpm(hw); + unsigned long this_khz; + int rc = 0; + + if (r->enabled) { + u32 value; + struct clk_rpm *peer = r->peer; + unsigned long peer_khz = 0; + + this_khz = DIV_ROUND_UP(rate, 1000); + + /* Take peer clock's rate into account only if it's enabled. */ + if (peer->enabled) + peer_khz = DIV_ROUND_UP(peer->rate, 1000); + + value = max(this_khz, peer_khz); + rc = clk_rpm_set_rate_active(r, value); + if (rc) + goto out; + } + + r->rate = rate; +out: + return rc; +} + +static long clk_rpm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return rate; +} + +static unsigned long clk_rpm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_rpm *r = to_clk_rpm(hw); + + return r->rate; +} + +int clk_rpm_enable_scaling(struct qcom_smd_rpm *rpm) +{ + int rc; + struct rpm_clk_req req = { + .key = QCOM_RPM_SMD_KEY_ENABLE, + .nbytes = sizeof(u32), + .value = 1, + }; + + rc = qcom_rpm_smd_write(rpm, QCOM_SMD_RPM_ACTIVE_STATE, + QCOM_SMD_RPM_MISC_CLK, + QCOM_RPM_SCALING_ENABLE_ID, &req, sizeof(req)); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("RPM clock scaling (active set) not enabled!\n"); + return rc; + } + + pr_debug("%s: Enabled RPM scaling\n", __func__); + return 0; +} + +const struct clk_ops clk_rpm_ops = { + .prepare = clk_rpm_prepare, + .unprepare = clk_rpm_unprepare, + .set_rate = clk_rpm_set_rate, + .round_rate = clk_rpm_round_rate, + .recalc_rate = clk_rpm_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_rpm_ops); + +const struct clk_ops clk_rpm_branch_ops = { + .prepare = clk_rpm_prepare, + .unprepare = clk_rpm_unprepare, + .round_rate = clk_rpm_round_rate, + .recalc_rate = clk_rpm_recalc_rate, +}; +EXPORT_SYMBOL_GPL(clk_rpm_branch_ops); diff --git a/drivers/clk/qcom/clk-rpm.h b/drivers/clk/qcom/clk-rpm.h new file mode 100644 index 000000000000..51bedf787775 --- /dev/null +++ b/drivers/clk/qcom/clk-rpm.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2015, Linaro Limited + * 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_RPM_H__ +#define __QCOM_CLK_RPM_H__ + +#include <linux/clk-provider.h> +#include <linux/mfd/qcom-smd-rpm.h> + +#define QCOM_RPM_KEY_SOFTWARE_ENABLE 0x6e657773 +#define QCOM_RPM_KEY_PIN_CTRL_CLK_BUFFER_ENABLE_KEY 0x62636370 +#define QCOM_RPM_SMD_KEY_RATE 0x007a484b +#define QCOM_RPM_SMD_KEY_ENABLE 0x62616e45 +#define QCOM_RPM_SMD_KEY_STATE 0x54415453 +#define QCOM_RPM_SCALING_ENABLE_ID 0x2 + +struct clk_rpm { + const int rpm_res_type; + const int rpm_key; + const int rpm_clk_id; + const int rpm_status_id; + const bool active_only; + bool enabled; + bool branch; + struct clk_rpm *peer; + struct clk_hw hw; + unsigned long rate; + struct qcom_smd_rpm *rpm; +}; + +struct rpm_clk_req { + u32 key; + u32 nbytes; + u32 value; +}; + +extern const struct clk_ops clk_rpm_ops; +extern const struct clk_ops clk_rpm_branch_ops; +int clk_rpm_enable_scaling(struct qcom_smd_rpm *rpm); + +#define to_clk_rpm(_hw) container_of(_hw, struct clk_rpm, hw) + +#define __DEFINE_CLK_RPM(_name, active, type, r_id, stat_id, dep, key) \ + static struct clk_rpm active; \ + static struct clk_rpm _name = { \ + .rpm_res_type = (type), \ + .rpm_clk_id = (r_id), \ + .rpm_status_id = (stat_id), \ + .rpm_key = (key), \ + .peer = &active, \ + .rate = INT_MAX, \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_rpm_ops, \ + .name = #_name, \ + .flags = CLK_IS_ROOT, \ + }, \ + }; \ + static struct clk_rpm active = { \ + .rpm_res_type = (type), \ + .rpm_clk_id = (r_id), \ + .rpm_status_id = (stat_id), \ + .rpm_key = (key), \ + .peer = &_name, \ + .active_only = true, \ + .rate = INT_MAX, \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_rpm_ops, \ + .name = #active, \ + .flags = CLK_IS_ROOT, \ + }, \ + }; + +#define __DEFINE_CLK_RPM_BRANCH(_name, active, type, r_id, stat_id, r, \ + key) \ + static struct clk_rpm active; \ + static struct clk_rpm _name = { \ + .rpm_res_type = (type), \ + .rpm_clk_id = (r_id), \ + .rpm_status_id = (stat_id), \ + .rpm_key = (key), \ + .peer = &active, \ + .branch = true, \ + .rate = (r), \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_rpm_branch_ops, \ + .name = #_name, \ + .flags = CLK_IS_ROOT, \ + }, \ + }; \ + static struct clk_rpm active = { \ + .rpm_res_type = (type), \ + .rpm_clk_id = (r_id), \ + .rpm_status_id = (stat_id), \ + .rpm_key = (key), \ + .peer = &_name, \ + .active_only = true, \ + .branch = true, \ + .rate = (r), \ + .hw.init = &(struct clk_init_data){ \ + .ops = &clk_rpm_branch_ops, \ + .name = #active, \ + .flags = CLK_IS_ROOT, \ + }, \ + }; + +#define DEFINE_CLK_RPM_SMD(_name, active, type, r_id, dep) \ + __DEFINE_CLK_RPM(_name, active, type, r_id, 0, dep, \ + QCOM_RPM_SMD_KEY_RATE) + +#define DEFINE_CLK_RPM_SMD_BRANCH(_name, active, type, r_id, r) \ + __DEFINE_CLK_RPM_BRANCH(_name, active, type, r_id, 0, r, \ + QCOM_RPM_SMD_KEY_ENABLE) + +#define DEFINE_CLK_RPM_SMD_QDSS(_name, active, type, r_id) \ + __DEFINE_CLK_RPM(_name, active, type, r_id, \ + 0, 0, QCOM_RPM_SMD_KEY_STATE) + +#define DEFINE_CLK_RPM_SMD_XO_BUFFER(_name, active, r_id) \ + __DEFINE_CLK_RPM_BRANCH(_name, active, QCOM_SMD_RPM_CLK_BUF_A, \ + r_id, 0, 1000, QCOM_RPM_KEY_SOFTWARE_ENABLE) + +#define DEFINE_CLK_RPM_SMD_XO_BUFFER_PINCTRL(_name, active, r_id) \ + __DEFINE_CLK_RPM_BRANCH(_name, active, QCOM_SMD_RPM_CLK_BUF_A, \ + r_id, 0, 1000, QCOM_RPM_KEY_PIN_CTRL_CLK_BUFFER_ENABLE_KEY) + +#endif diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c index f7101e330b1d..277667913db9 100644 --- a/drivers/clk/qcom/common.c +++ b/drivers/clk/qcom/common.c @@ -21,6 +21,7 @@ #include "clk-rcg.h" #include "clk-regmap.h" #include "reset.h" +#include "gdsc.h" struct qcom_cc { struct qcom_reset_controller reset; @@ -120,8 +121,20 @@ int qcom_cc_really_probe(struct platform_device *pdev, ret = reset_controller_register(&reset->rcdev); if (ret) - of_clk_del_provider(dev->of_node); + goto err_reset; + if (desc->gdscs && desc->num_gdscs) { + ret = gdsc_register(dev, desc->gdscs, desc->num_gdscs, regmap); + if (ret) + goto err_pd; + } + + return 0; +err_pd: + dev_err(dev, "Failed to register power domains\n"); + reset_controller_unregister(&reset->rcdev); +err_reset: + of_clk_del_provider(dev->of_node); return ret; } EXPORT_SYMBOL_GPL(qcom_cc_really_probe); @@ -140,6 +153,7 @@ EXPORT_SYMBOL_GPL(qcom_cc_probe); void qcom_cc_remove(struct platform_device *pdev) { + gdsc_unregister(&pdev->dev); of_clk_del_provider(pdev->dev.of_node); reset_controller_unregister(platform_get_drvdata(pdev)); } diff --git a/drivers/clk/qcom/common.h b/drivers/clk/qcom/common.h index 7a0e73713063..2892b71fbd71 100644 --- a/drivers/clk/qcom/common.h +++ b/drivers/clk/qcom/common.h @@ -28,6 +28,8 @@ struct qcom_cc_desc { size_t num_clks; const struct qcom_reset_map *resets; size_t num_resets; + struct gdsc **gdscs; + size_t num_gdscs; }; extern const struct freq_tbl *qcom_find_freq(const struct freq_tbl *f, diff --git a/drivers/clk/qcom/gcc-apq8084.c b/drivers/clk/qcom/gcc-apq8084.c index 54a756b90a37..f010ffc9dc93 100644 --- a/drivers/clk/qcom/gcc-apq8084.c +++ b/drivers/clk/qcom/gcc-apq8084.c @@ -31,6 +31,7 @@ #include "clk-rcg.h" #include "clk-branch.h" #include "reset.h" +#include "gdsc.h" enum { P_XO, @@ -3253,6 +3254,34 @@ static struct clk_branch gcc_usb_hsic_system_clk = { }, }; +static struct gdsc usb_hs_hsic_gdsc = { + .gdscr = 0x404, + .pd = { + .name = "usb_hs_hsic", + }, +}; + +static struct gdsc pcie0_gdsc = { + .gdscr = 0x1ac4, + .pd = { + .name = "pcie0", + }, +}; + +static struct gdsc pcie1_gdsc = { + .gdscr = 0x1b44, + .pd = { + .name = "pcie1", + }, +}; + +static struct gdsc usb30_gdsc = { + .gdscr = 0x1e84, + .pd = { + .name = "usb30", + }, +}; + static struct clk_regmap *gcc_apq8084_clocks[] = { [GPLL0] = &gpll0.clkr, [GPLL0_VOTE] = &gpll0_vote, @@ -3446,6 +3475,13 @@ static struct clk_regmap *gcc_apq8084_clocks[] = { [GCC_USB_HSIC_SYSTEM_CLK] = &gcc_usb_hsic_system_clk.clkr, }; +static struct gdsc *gcc_apq8084_gdscs[] = { + [USB_HS_HSIC_GDSC] = &usb_hs_hsic_gdsc, + [PCIE0_GDSC] = &pcie0_gdsc, + [PCIE1_GDSC] = &pcie1_gdsc, + [USB30_GDSC] = &usb30_gdsc, +}; + static const struct qcom_reset_map gcc_apq8084_resets[] = { [GCC_SYSTEM_NOC_BCR] = { 0x0100 }, [GCC_CONFIG_NOC_BCR] = { 0x0140 }, @@ -3554,6 +3590,8 @@ static const struct qcom_cc_desc gcc_apq8084_desc = { .num_clks = ARRAY_SIZE(gcc_apq8084_clocks), .resets = gcc_apq8084_resets, .num_resets = ARRAY_SIZE(gcc_apq8084_resets), + .gdscs = gcc_apq8084_gdscs, + .num_gdscs = ARRAY_SIZE(gcc_apq8084_gdscs), }; static const struct of_device_id gcc_apq8084_match_table[] = { diff --git a/drivers/clk/qcom/gcc-ipq806x.c b/drivers/clk/qcom/gcc-ipq806x.c index 563969942a1d..e0caa0dfcdd8 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 = { @@ -113,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, @@ -2837,6 +2917,9 @@ static struct clk_regmap *gcc_ipq806x_clks[] = { [UBI32_CORE2_CLK_SRC] = &ubi32_core2_src_clk.clkr, [NSSTCM_CLK_SRC] = &nss_tcm_src.clkr, [NSSTCM_CLK] = &nss_tcm_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-msm8916.c b/drivers/clk/qcom/gcc-msm8916.c index c66f7bc2ae87..747fbe6409b6 100644 --- a/drivers/clk/qcom/gcc-msm8916.c +++ b/drivers/clk/qcom/gcc-msm8916.c @@ -31,6 +31,7 @@ #include "clk-rcg.h" #include "clk-branch.h" #include "reset.h" +#include "gdsc.h" enum { P_XO, @@ -44,6 +45,9 @@ enum { P_SLEEP_CLK, P_DSI0_PHYPLL_BYTE, P_DSI0_PHYPLL_DSI, + P_EXT_PRI_I2S, + P_EXT_SEC_I2S, + P_EXT_MCLK, }; static const struct parent_map gcc_xo_gpll0_map[] = { @@ -190,6 +194,76 @@ static const char *gcc_xo_gpll0a_gpll1_gpll2[] = { "gpll2_vote", }; +static const struct parent_map gcc_xo_gpll0_gpll1_sleep_map[] = { + { P_XO, 0 }, + { P_GPLL0, 1 }, + { P_GPLL1, 2 }, + { P_SLEEP_CLK, 6 } +}; + +static const char *gcc_xo_gpll0_gpll1_sleep[] = { + "xo", + "gpll0_vote", + "gpll1_vote", + "sleep_clk", +}; + +static const struct parent_map gcc_xo_gpll1_epi2s_emclk_sleep_map[] = { + { P_XO, 0 }, + { P_GPLL1, 1 }, + { P_EXT_PRI_I2S, 2 }, + { P_EXT_MCLK, 3 }, + { P_SLEEP_CLK, 6 } +}; + +static const char *gcc_xo_gpll1_epi2s_emclk_sleep[] = { + "xo", + "gpll1_vote", + "ext_pri_i2s", + "ext_mclk", + "sleep_clk", +}; + +static const struct parent_map gcc_xo_gpll1_esi2s_emclk_sleep_map[] = { + { P_XO, 0 }, + { P_GPLL1, 1 }, + { P_EXT_SEC_I2S, 2 }, + { P_EXT_MCLK, 3 }, + { P_SLEEP_CLK, 6 } +}; + +static const char *gcc_xo_gpll1_esi2s_emclk_sleep[] = { + "xo", + "gpll1_vote", + "ext_sec_i2s", + "ext_mclk", + "sleep_clk", +}; + +static const struct parent_map gcc_xo_sleep_map[] = { + { P_XO, 0 }, + { P_SLEEP_CLK, 6 } +}; + +static const char *gcc_xo_sleep[] = { + "xo", + "sleep_clk", +}; + +static const struct parent_map gcc_xo_gpll1_emclk_sleep_map[] = { + { P_XO, 0 }, + { P_GPLL1, 1 }, + { P_EXT_MCLK, 2 }, + { P_SLEEP_CLK, 6 } +}; + +static const char *gcc_xo_gpll1_emclk_sleep[] = { + "xo", + "gpll1_vote", + "ext_mclk", + "sleep_clk", +}; + #define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n) } static struct clk_pll gpll0 = { @@ -906,21 +980,15 @@ static struct clk_rcg2 gp3_clk_src = { }, }; -static struct freq_tbl ftbl_gcc_mdss_byte0_clk[] = { - { .src = P_DSI0_PHYPLL_BYTE }, - { } -}; - static struct clk_rcg2 byte0_clk_src = { .cmd_rcgr = 0x4d044, .hid_width = 5, .parent_map = gcc_xo_gpll0a_dsibyte_map, - .freq_tbl = ftbl_gcc_mdss_byte0_clk, .clkr.hw.init = &(struct clk_init_data){ .name = "byte0_clk_src", .parent_names = gcc_xo_gpll0a_dsibyte, .num_parents = 3, - .ops = &clk_byte_ops, + .ops = &clk_byte2_ops, .flags = CLK_SET_RATE_PARENT, }, }; @@ -968,17 +1036,11 @@ static struct clk_rcg2 mdp_clk_src = { }, }; -static struct freq_tbl ftbl_gcc_mdss_pclk[] = { - { .src = P_DSI0_PHYPLL_DSI }, - { } -}; - static struct clk_rcg2 pclk0_clk_src = { .cmd_rcgr = 0x4d000, .mnd_width = 8, .hid_width = 5, .parent_map = gcc_xo_gpll0a_dsiphy_map, - .freq_tbl = ftbl_gcc_mdss_pclk, .clkr.hw.init = &(struct clk_init_data){ .name = "pclk0_clk_src", .parent_names = gcc_xo_gpll0a_dsiphy, @@ -1094,6 +1156,29 @@ static struct clk_rcg2 apss_tcu_clk_src = { }, }; +static const struct freq_tbl ftbl_gcc_bimc_gpu_clk[] = { + F(19200000, P_XO, 1, 0, 0), + F(100000000, P_GPLL0, 8, 0, 0), + F(200000000, P_GPLL0, 4, 0, 0), + F(266500000, P_BIMC, 4, 0, 0), + F(533000000, P_BIMC, 2, 0, 0), + { } +}; + +static struct clk_rcg2 bimc_gpu_clk_src = { + .cmd_rcgr = 0x31028, + .hid_width = 5, + .parent_map = gcc_xo_gpll0_bimc_map, + .freq_tbl = ftbl_gcc_bimc_gpu_clk, + .clkr.hw.init = &(struct clk_init_data){ + .name = "bimc_gpu_clk_src", + .parent_names = gcc_xo_gpll0_bimc, + .num_parents = 3, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_rcg2_shared_ops, + }, +}; + static const struct freq_tbl ftbl_gcc_usb_hs_system_clk[] = { F(80000000, P_GPLL0, 10, 0, 0), { } @@ -1112,6 +1197,305 @@ static struct clk_rcg2 usb_hs_system_clk_src = { }, }; +static const struct freq_tbl ftbl_gcc_ultaudio_ahb_clk[] = { + F(3200000, P_XO, 6, 0, 0), + F(6400000, P_XO, 3, 0, 0), + F(9600000, P_XO, 2, 0, 0), + F(19200000, P_XO, 1, 0, 0), + F(40000000, P_GPLL0, 10, 1, 2), + F(66670000, P_GPLL0, 12, 0, 0), + F(80000000, P_GPLL0, 10, 0, 0), + F(100000000, P_GPLL0, 8, 0, 0), + { } +}; + +static struct clk_rcg2 ultaudio_ahbfabric_clk_src = { + .cmd_rcgr = 0x1c010, + .hid_width = 5, + .mnd_width = 8, + .parent_map = gcc_xo_gpll0_gpll1_sleep_map, + .freq_tbl = ftbl_gcc_ultaudio_ahb_clk, + .clkr.hw.init = &(struct clk_init_data){ + .name = "ultaudio_ahbfabric_clk_src", + .parent_names = gcc_xo_gpll0_gpll1_sleep, + .num_parents = 4, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch gcc_ultaudio_ahbfabric_ixfabric_clk = { + .halt_reg = 0x1c028, + .clkr = { + .enable_reg = 0x1c028, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_ahbfabric_ixfabric_clk", + .parent_names = (const char *[]){ + "ultaudio_ahbfabric_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_ultaudio_ahbfabric_ixfabric_lpm_clk = { + .halt_reg = 0x1c024, + .clkr = { + .enable_reg = 0x1c024, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_ahbfabric_ixfabric_lpm_clk", + .parent_names = (const char *[]){ + "ultaudio_ahbfabric_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_gcc_ultaudio_lpaif_i2s_clk[] = { + F(256000, P_XO, 5, 1, 15), + F(512000, P_XO, 5, 2, 15), + F(705600, P_GPLL1, 16, 1, 80), + F(768000, P_XO, 5, 1, 5), + F(800000, P_XO, 5, 5, 24), + F(1024000, P_GPLL1, 14, 1, 63), + F(1152000, P_XO, 1, 3, 50), + F(1411200, P_GPLL1, 16, 1, 40), + F(1536000, P_XO, 1, 2, 25), + F(1600000, P_XO, 12, 0, 0), + F(2048000, P_GPLL1, 9, 1, 49), + F(2400000, P_XO, 8, 0, 0), + F(2822400, P_GPLL1, 16, 1, 20), + F(3072000, P_GPLL1, 14, 1, 21), + F(4096000, P_GPLL1, 9, 2, 49), + F(4800000, P_XO, 4, 0, 0), + F(5644800, P_GPLL1, 16, 1, 10), + F(6144000, P_GPLL1, 7, 1, 21), + F(8192000, P_GPLL1, 9, 4, 49), + F(9600000, P_XO, 2, 0, 0), + F(11289600, P_GPLL1, 16, 1, 5), + F(12288000, P_GPLL1, 7, 2, 21), + { } +}; + +static struct clk_rcg2 ultaudio_lpaif_pri_i2s_clk_src = { + .cmd_rcgr = 0x1c054, + .hid_width = 5, + .mnd_width = 8, + .parent_map = gcc_xo_gpll1_epi2s_emclk_sleep_map, + .freq_tbl = ftbl_gcc_ultaudio_lpaif_i2s_clk, + .clkr.hw.init = &(struct clk_init_data){ + .name = "ultaudio_lpaif_pri_i2s_clk_src", + .parent_names = gcc_xo_gpll1_epi2s_emclk_sleep, + .num_parents = 5, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch gcc_ultaudio_lpaif_pri_i2s_clk = { + .halt_reg = 0x1c068, + .clkr = { + .enable_reg = 0x1c068, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_lpaif_pri_i2s_clk", + .parent_names = (const char *[]){ + "ultaudio_lpaif_pri_i2s_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_rcg2 ultaudio_lpaif_sec_i2s_clk_src = { + .cmd_rcgr = 0x1c06c, + .hid_width = 5, + .mnd_width = 8, + .parent_map = gcc_xo_gpll1_esi2s_emclk_sleep_map, + .freq_tbl = ftbl_gcc_ultaudio_lpaif_i2s_clk, + .clkr.hw.init = &(struct clk_init_data){ + .name = "ultaudio_lpaif_sec_i2s_clk_src", + .parent_names = gcc_xo_gpll1_esi2s_emclk_sleep, + .num_parents = 5, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch gcc_ultaudio_lpaif_sec_i2s_clk = { + .halt_reg = 0x1c080, + .clkr = { + .enable_reg = 0x1c080, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_lpaif_sec_i2s_clk", + .parent_names = (const char *[]){ + "ultaudio_lpaif_sec_i2s_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_rcg2 ultaudio_lpaif_aux_i2s_clk_src = { + .cmd_rcgr = 0x1c084, + .hid_width = 5, + .mnd_width = 8, + .parent_map = gcc_xo_gpll1_emclk_sleep_map, + .freq_tbl = ftbl_gcc_ultaudio_lpaif_i2s_clk, + .clkr.hw.init = &(struct clk_init_data){ + .name = "ultaudio_lpaif_aux_i2s_clk_src", + .parent_names = gcc_xo_gpll1_emclk_sleep, + .num_parents = 5, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch gcc_ultaudio_lpaif_aux_i2s_clk = { + .halt_reg = 0x1c098, + .clkr = { + .enable_reg = 0x1c098, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_lpaif_aux_i2s_clk", + .parent_names = (const char *[]){ + "ultaudio_lpaif_aux_i2s_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_gcc_ultaudio_xo_clk[] = { + F(19200000, P_XO, 1, 0, 0), + { } +}; + +static struct clk_rcg2 ultaudio_xo_clk_src = { + .cmd_rcgr = 0x1c034, + .hid_width = 5, + .parent_map = gcc_xo_sleep_map, + .freq_tbl = ftbl_gcc_ultaudio_xo_clk, + .clkr.hw.init = &(struct clk_init_data){ + .name = "ultaudio_xo_clk_src", + .parent_names = gcc_xo_sleep, + .num_parents = 2, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch gcc_ultaudio_avsync_xo_clk = { + .halt_reg = 0x1c04c, + .clkr = { + .enable_reg = 0x1c04c, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_avsync_xo_clk", + .parent_names = (const char *[]){ + "ultaudio_xo_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_ultaudio_stc_xo_clk = { + .halt_reg = 0x1c050, + .clkr = { + .enable_reg = 0x1c050, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_stc_xo_clk", + .parent_names = (const char *[]){ + "ultaudio_xo_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static const struct freq_tbl ftbl_codec_clk[] = { + F(19200000, P_XO, 1, 0, 0), + F(11289600, P_EXT_MCLK, 1, 0, 0), + { } +}; + +static struct clk_rcg2 codec_digcodec_clk_src = { + .cmd_rcgr = 0x1c09c, + .hid_width = 5, + .parent_map = gcc_xo_gpll1_emclk_sleep_map, + .freq_tbl = ftbl_codec_clk, + .clkr.hw.init = &(struct clk_init_data){ + .name = "codec_digcodec_clk_src", + .parent_names = gcc_xo_gpll1_emclk_sleep, + .num_parents = 4, + .ops = &clk_rcg2_ops, + }, +}; + +static struct clk_branch gcc_codec_digcodec_clk = { + .halt_reg = 0x1c0b0, + .clkr = { + .enable_reg = 0x1c0b0, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_codec_digcodec_clk", + .parent_names = (const char *[]){ + "codec_digcodec_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_ultaudio_pcnoc_mport_clk = { + .halt_reg = 0x1c000, + .clkr = { + .enable_reg = 0x1c000, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_pcnoc_mport_clk", + .parent_names = (const char *[]){ + "pcnoc_bfdcd_clk_src", + }, + .num_parents = 1, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_ultaudio_pcnoc_sway_clk = { + .halt_reg = 0x1c004, + .clkr = { + .enable_reg = 0x1c004, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_ultaudio_pcnoc_sway_clk", + .parent_names = (const char *[]){ + "pcnoc_bfdcd_clk_src", + }, + .num_parents = 1, + .ops = &clk_branch2_ops, + }, + }, +}; + static const struct freq_tbl ftbl_gcc_venus0_vcodec0_clk[] = { F(100000000, P_GPLL0, 8, 0, 0), F(160000000, P_GPLL0, 5, 0, 0), @@ -2358,6 +2742,51 @@ static struct clk_branch gcc_sdcc2_apps_clk = { }, }; +static struct clk_rcg2 bimc_ddr_clk_src = { + .cmd_rcgr = 0x32004, + .hid_width = 5, + .parent_map = gcc_xo_gpll0_bimc_map, + .clkr.hw.init = &(struct clk_init_data){ + .name = "bimc_ddr_clk_src", + .parent_names = gcc_xo_gpll0_bimc, + .num_parents = 3, + .ops = &clk_rcg2_ops, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_branch gcc_apss_tcu_clk = { + .halt_reg = 0x12018, + .clkr = { + .enable_reg = 0x4500c, + .enable_mask = BIT(1), + .hw.init = &(struct clk_init_data){ + .name = "gcc_apss_tcu_clk", + .parent_names = (const char *[]){ + "bimc_ddr_clk_src", + }, + .num_parents = 1, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_gfx_tcu_clk = { + .halt_reg = 0x12020, + .clkr = { + .enable_reg = 0x4500c, + .enable_mask = BIT(2), + .hw.init = &(struct clk_init_data){ + .name = "gcc_gfx_tcu_clk", + .parent_names = (const char *[]){ + "bimc_ddr_clk_src", + }, + .num_parents = 1, + .ops = &clk_branch2_ops, + }, + }, +}; + static struct clk_branch gcc_gtcu_ahb_clk = { .halt_reg = 0x12044, .clkr = { @@ -2375,6 +2804,40 @@ static struct clk_branch gcc_gtcu_ahb_clk = { }, }; +static struct clk_branch gcc_bimc_gfx_clk = { + .halt_reg = 0x31024, + .clkr = { + .enable_reg = 0x31024, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_bimc_gfx_clk", + .parent_names = (const char *[]){ + "bimc_gpu_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + +static struct clk_branch gcc_bimc_gpu_clk = { + .halt_reg = 0x31040, + .clkr = { + .enable_reg = 0x31040, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_bimc_gpu_clk", + .parent_names = (const char *[]){ + "bimc_gpu_clk_src", + }, + .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, + }, +}; + static struct clk_branch gcc_jpeg_tbu_clk = { .halt_reg = 0x12034, .clkr = { @@ -2562,6 +3025,42 @@ static struct clk_branch gcc_venus0_vcodec0_clk = { }, }; +static struct gdsc venus_gdsc = { + .gdscr = 0x4c018, + .pd = { + .name = "venus", + }, +}; + +static struct gdsc mdss_gdsc = { + .gdscr = 0x4d078, + .pd = { + .name = "mdss", + }, +}; + +static struct gdsc jpeg_gdsc = { + .gdscr = 0x5701c, + .pd = { + .name = "jpeg", + }, +}; + +static struct gdsc vfe_gdsc = { + .gdscr = 0x58034, + .pd = { + .name = "vfe", + }, +}; + +static struct gdsc oxili_gdsc = { + .gdscr = 0x5901c, + .pd = { + .name = "oxili", + }, + .root_con_id = "gfx3d_clk_src", +}; + static struct clk_regmap *gcc_msm8916_clocks[] = { [GPLL0] = &gpll0.clkr, [GPLL0_VOTE] = &gpll0_vote, @@ -2701,6 +3200,36 @@ static struct clk_regmap *gcc_msm8916_clocks[] = { [GCC_VENUS0_AHB_CLK] = &gcc_venus0_ahb_clk.clkr, [GCC_VENUS0_AXI_CLK] = &gcc_venus0_axi_clk.clkr, [GCC_VENUS0_VCODEC0_CLK] = &gcc_venus0_vcodec0_clk.clkr, + [BIMC_DDR_CLK_SRC] = &bimc_ddr_clk_src.clkr, + [GCC_APSS_TCU_CLK] = &gcc_apss_tcu_clk.clkr, + [GCC_GFX_TCU_CLK] = &gcc_gfx_tcu_clk.clkr, + [BIMC_GPU_CLK_SRC] = &bimc_gpu_clk_src.clkr, + [GCC_BIMC_GFX_CLK] = &gcc_bimc_gfx_clk.clkr, + [GCC_BIMC_GPU_CLK] = &gcc_bimc_gpu_clk.clkr, + [ULTAUDIO_AHBFABRIC_CLK_SRC] = &ultaudio_ahbfabric_clk_src.clkr, + [ULTAUDIO_LPAIF_PRI_I2S_CLK_SRC] = &ultaudio_lpaif_pri_i2s_clk_src.clkr, + [ULTAUDIO_LPAIF_SEC_I2S_CLK_SRC] = &ultaudio_lpaif_sec_i2s_clk_src.clkr, + [ULTAUDIO_LPAIF_AUX_I2S_CLK_SRC] = &ultaudio_lpaif_aux_i2s_clk_src.clkr, + [ULTAUDIO_XO_CLK_SRC] = &ultaudio_xo_clk_src.clkr, + [CODEC_DIGCODEC_CLK_SRC] = &codec_digcodec_clk_src.clkr, + [GCC_ULTAUDIO_PCNOC_MPORT_CLK] = &gcc_ultaudio_pcnoc_mport_clk.clkr, + [GCC_ULTAUDIO_PCNOC_SWAY_CLK] = &gcc_ultaudio_pcnoc_sway_clk.clkr, + [GCC_ULTAUDIO_AVSYNC_XO_CLK] = &gcc_ultaudio_avsync_xo_clk.clkr, + [GCC_ULTAUDIO_STC_XO_CLK] = &gcc_ultaudio_stc_xo_clk.clkr, + [GCC_ULTAUDIO_AHBFABRIC_IXFABRIC_CLK] = &gcc_ultaudio_ahbfabric_ixfabric_clk.clkr, + [GCC_ULTAUDIO_AHBFABRIC_IXFABRIC_LPM_CLK] = &gcc_ultaudio_ahbfabric_ixfabric_lpm_clk.clkr, + [GCC_ULTAUDIO_LPAIF_PRI_I2S_CLK] = &gcc_ultaudio_lpaif_pri_i2s_clk.clkr, + [GCC_ULTAUDIO_LPAIF_SEC_I2S_CLK] = &gcc_ultaudio_lpaif_sec_i2s_clk.clkr, + [GCC_ULTAUDIO_LPAIF_AUX_I2S_CLK] = &gcc_ultaudio_lpaif_aux_i2s_clk.clkr, + [GCC_CODEC_DIGCODEC_CLK] = &gcc_codec_digcodec_clk.clkr, +}; + +static struct gdsc *gcc_msm8916_gdscs[] = { + [VENUS_GDSC] = &venus_gdsc, + [MDSS_GDSC] = &mdss_gdsc, + [JPEG_GDSC] = &jpeg_gdsc, + [VFE_GDSC] = &vfe_gdsc, + [OXILI_GDSC] = &oxili_gdsc, }; static const struct qcom_reset_map gcc_msm8916_resets[] = { @@ -2810,6 +3339,8 @@ static const struct qcom_cc_desc gcc_msm8916_desc = { .num_clks = ARRAY_SIZE(gcc_msm8916_clocks), .resets = gcc_msm8916_resets, .num_resets = ARRAY_SIZE(gcc_msm8916_resets), + .gdscs = gcc_msm8916_gdscs, + .num_gdscs = ARRAY_SIZE(gcc_msm8916_gdscs), }; static const struct of_device_id gcc_msm8916_match_table[] = { @@ -2820,19 +3351,6 @@ MODULE_DEVICE_TABLE(of, gcc_msm8916_match_table); static int gcc_msm8916_probe(struct platform_device *pdev) { - struct clk *clk; - struct device *dev = &pdev->dev; - - /* Temporary until RPM clocks supported */ - clk = clk_register_fixed_rate(dev, "xo", NULL, CLK_IS_ROOT, 19200000); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - clk = clk_register_fixed_rate(dev, "sleep_clk_src", NULL, - CLK_IS_ROOT, 32768); - if (IS_ERR(clk)) - return PTR_ERR(clk); - return qcom_cc_probe(pdev, &gcc_msm8916_desc); } diff --git a/drivers/clk/qcom/gcc-msm8960.c b/drivers/clk/qcom/gcc-msm8960.c index eb6a4f9fa107..bd5ab94f83f2 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 = { @@ -86,6 +88,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, @@ -3154,6 +3314,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[] = { @@ -3365,6 +3528,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[] = { @@ -3503,24 +3671,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/gcc-msm8974.c b/drivers/clk/qcom/gcc-msm8974.c index c39d09874e74..125b41f9908a 100644 --- a/drivers/clk/qcom/gcc-msm8974.c +++ b/drivers/clk/qcom/gcc-msm8974.c @@ -31,6 +31,7 @@ #include "clk-rcg.h" #include "clk-branch.h" #include "reset.h" +#include "gdsc.h" enum { P_XO, @@ -2431,6 +2432,13 @@ static struct clk_branch gcc_usb_hsic_system_clk = { }, }; +static struct gdsc usb_hs_hsic_gdsc = { + .gdscr = 0x404, + .pd = { + .name = "usb_hs_hsic", + }, +}; + static struct clk_regmap *gcc_msm8974_clocks[] = { [GPLL0] = &gpll0.clkr, [GPLL0_VOTE] = &gpll0_vote, @@ -2660,6 +2668,10 @@ static const struct qcom_reset_map gcc_msm8974_resets[] = { [GCC_VENUS_RESTART] = { 0x1740 }, }; +static struct gdsc *gcc_msm8974_gdscs[] = { + [USB_HS_HSIC_GDSC] = &usb_hs_hsic_gdsc, +}; + static const struct regmap_config gcc_msm8974_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -2674,6 +2686,8 @@ static const struct qcom_cc_desc gcc_msm8974_desc = { .num_clks = ARRAY_SIZE(gcc_msm8974_clocks), .resets = gcc_msm8974_resets, .num_resets = ARRAY_SIZE(gcc_msm8974_resets), + .gdscs = gcc_msm8974_gdscs, + .num_gdscs = ARRAY_SIZE(gcc_msm8974_gdscs), }; static const struct of_device_id gcc_msm8974_match_table[] = { diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c new file mode 100644 index 000000000000..480ebf6e0657 --- /dev/null +++ b/drivers/clk/qcom/gdsc.c @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2015, 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/delay.h> +#include <linux/err.h> +#include <linux/jiffies.h> +#include <linux/pm_clock.h> +#include <linux/slab.h> +#include "gdsc.h" + +#define PWR_ON_MASK BIT(31) +#define EN_REST_WAIT_MASK GENMASK(23, 20) +#define EN_FEW_WAIT_MASK GENMASK(19, 16) +#define CLK_DIS_WAIT_MASK GENMASK(15, 12) +#define SW_OVERRIDE_MASK BIT(2) +#define HW_CONTROL_MASK BIT(1) +#define SW_COLLAPSE_MASK BIT(0) + +/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */ +#define EN_REST_WAIT_VAL (0x2 << 20) +#define EN_FEW_WAIT_VAL (0x8 << 16) +#define CLK_DIS_WAIT_VAL (0x2 << 12) + +#define TIMEOUT_US 100 + +#define domain_to_gdsc(domain) container_of(domain, struct gdsc, pd) + +static int gdsc_is_enabled(struct gdsc *sc) +{ + u32 val; + int ret; + + ret = regmap_read(sc->regmap, sc->gdscr, &val); + if (ret) + return ret; + + return !!(val & PWR_ON_MASK); +} + +static int gdsc_toggle_logic(struct gdsc *sc, bool en) +{ + int ret; + u32 val = en ? 0 : SW_COLLAPSE_MASK; + u32 check = en ? PWR_ON_MASK : 0; + unsigned long timeout; + + ret = regmap_update_bits(sc->regmap, sc->gdscr, SW_COLLAPSE_MASK, val); + if (ret) + return ret; + + timeout = jiffies + usecs_to_jiffies(TIMEOUT_US); + do { + ret = regmap_read(sc->regmap, sc->gdscr, &val); + if (ret) + return ret; + + if ((val & PWR_ON_MASK) == check) + return 0; + } while (time_before(jiffies, timeout)); + + ret = regmap_read(sc->regmap, sc->gdscr, &val); + if (ret) + return ret; + + if ((val & PWR_ON_MASK) == check) + return 0; + + return -ETIMEDOUT; +} + +static int gdsc_enable(struct generic_pm_domain *domain) +{ + struct gdsc *sc = domain_to_gdsc(domain); + int ret; + + if (sc->root_clk) + clk_prepare_enable(sc->root_clk); + + ret = gdsc_toggle_logic(sc, true); + if (ret) + return ret; + /* + * If clocks to this power domain were already on, they will take an + * additional 4 clock cycles to re-enable after the power domain is + * enabled. Delay to account for this. A delay is also needed to ensure + * clocks are not enabled within 400ns of enabling power to the + * memories. + */ + udelay(1); + + return 0; +} + +static int gdsc_disable(struct generic_pm_domain *domain) +{ + int ret; + struct gdsc *sc = domain_to_gdsc(domain); + + ret = gdsc_toggle_logic(sc, false); + + if (sc->root_clk) + clk_disable_unprepare(sc->root_clk); + + return ret; +} + +static int gdsc_attach(struct generic_pm_domain *domain, struct device *dev) +{ + int ret; + struct gdsc *sc = domain_to_gdsc(domain); + char **con_id; + + if (!sc->con_ids[0]) + return 0; + + ret = pm_clk_create(dev); + if (ret) { + dev_err(dev, "pm_clk_create failed %d\n", ret); + return ret; + } + + for (con_id = sc->con_ids; *con_id; con_id++) { + ret = pm_clk_add(dev, *con_id); + if (ret) { + dev_err(dev, "pm_clk_add failed %d\n", ret); + goto fail; + } + } + + if (sc->root_con_id) { + sc->root_clk = clk_get(dev, sc->root_con_id); + if (IS_ERR(sc->root_clk)) { + dev_err(dev, "failed to get root clock\n"); + return PTR_ERR(sc->root_clk); + } + } + + return 0; +fail: + pm_clk_destroy(dev); + return ret; +}; + +static void gdsc_detach(struct generic_pm_domain *domain, struct device *dev) +{ + struct gdsc *sc = domain_to_gdsc(domain); + + if (!sc->con_ids[0]) + return; + + pm_clk_destroy(dev); + return; +}; + +static int gdsc_init(struct gdsc *sc) +{ + u32 mask, val; + int on, ret; + + /* + * Disable HW trigger: collapse/restore occur based on registers writes. + * Disable SW override: Use hardware state-machine for sequencing. + * Configure wait time between states. + */ + mask = HW_CONTROL_MASK | SW_OVERRIDE_MASK | + EN_REST_WAIT_MASK | EN_FEW_WAIT_MASK | CLK_DIS_WAIT_MASK; + val = EN_REST_WAIT_VAL | EN_FEW_WAIT_VAL | CLK_DIS_WAIT_VAL; + ret = regmap_update_bits(sc->regmap, sc->gdscr, mask, val); + if (ret) + return ret; + + on = gdsc_is_enabled(sc); + if (on < 0) + return on; + + sc->pd.power_off = gdsc_disable; + sc->pd.power_on = gdsc_enable; + sc->pd.attach_dev = gdsc_attach; + sc->pd.detach_dev = gdsc_detach; + sc->pd.flags = GENPD_FLAG_PM_CLK; + pm_genpd_init(&sc->pd, NULL, !on); + + return 0; +} + +int gdsc_register(struct device *dev, struct gdsc **scs, size_t num, + struct regmap *regmap) +{ + int i, ret; + struct genpd_onecell_data *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->domains = devm_kcalloc(dev, num, sizeof(*data->domains), + GFP_KERNEL); + if (!data->domains) + return -ENOMEM; + + data->num_domains = num; + for (i = 0; i < num; i++) { + if (!scs[i]) + continue; + scs[i]->regmap = regmap; + ret = gdsc_init(scs[i]); + if (ret) + return ret; + data->domains[i] = &scs[i]->pd; + } + + return of_genpd_add_provider_onecell(dev->of_node, data); +} + +void gdsc_unregister(struct device *dev) +{ + of_genpd_del_provider(dev->of_node); +} diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h new file mode 100644 index 000000000000..1ad9d53fd776 --- /dev/null +++ b/drivers/clk/qcom/gdsc.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015, 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_GDSC_H__ +#define __QCOM_GDSC_H__ + +#include <linux/clk.h> +#include <linux/pm_domain.h> +#include <linux/regmap.h> + +/** + * struct gdsc - Globally Distributed Switch Controller + * @pd: generic power domain + * @regmap: regmap for MMIO accesses + * @gdscr: gsdc control register + * @con_ids: List of clocks to be controlled for the gdsc + */ +struct gdsc { + struct generic_pm_domain pd; + struct regmap *regmap; + unsigned int gdscr; + char *root_con_id; + struct clk *root_clk; + char *con_ids[]; +}; + +#ifdef CONFIG_QCOM_GDSC +int gdsc_register(struct device *, struct gdsc **, size_t n, struct regmap *); +void gdsc_unregister(struct device *); +#else +static inline int gdsc_register(struct device *d, struct gdsc **g, size_t n, + struct regmap *r) +{ + return -ENOSYS; +} + +static inline void gdsc_unregister(struct device *d) +{}; +#endif /* CONFIG_QCOM_GDSC */ +#endif /* __QCOM_GDSC_H__ */ diff --git a/drivers/clk/qcom/hfpll.c b/drivers/clk/qcom/hfpll.c new file mode 100644 index 000000000000..1492f4c79c35 --- /dev/null +++ b/drivers/clk/qcom/hfpll.c @@ -0,0 +1,109 @@ +/* + * 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 const 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", + .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..abf6bfd053c1 --- /dev/null +++ b/drivers/clk/qcom/kpss-xcc.c @@ -0,0 +1,95 @@ +/* Copyright (c) 2014-2015, 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, + }, +}; +module_platform_driver(kpss_xcc_driver); + +MODULE_DESCRIPTION("Krait Processor Sub System (KPSS) Clock Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:kpss-xcc"); diff --git a/drivers/clk/qcom/krait-cc.c b/drivers/clk/qcom/krait-cc.c new file mode 100644 index 000000000000..f55b5ecd0df8 --- /dev/null +++ b/drivers/clk/qcom/krait-cc.c @@ -0,0 +1,352 @@ +/* Copyright (c) 2013-2015, 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 "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 cpu; + struct clk *clk; + struct clk **clks; + struct clk *l2_pri_mux_clk; + + id = of_match_device(krait_cc_match_table, 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(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(cpu) { + 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(cpu) { + 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(cpu) { + clk = clks[cpu]; + cur_rate = clk_get_rate(clk); + if (cur_rate == 1) { + pr_info("CPU%d @ QSB rate. Forcing new rate.\n", cpu); + 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", cpu, 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 = "krait-cc", + .of_match_table = krait_cc_match_table, + }, +}; +module_platform_driver(krait_cc_driver); + +MODULE_DESCRIPTION("Krait CPU Clock Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:krait-cc"); diff --git a/drivers/clk/qcom/mmcc-apq8084.c b/drivers/clk/qcom/mmcc-apq8084.c index 1b17df2cb0af..5d2860c49179 100644 --- a/drivers/clk/qcom/mmcc-apq8084.c +++ b/drivers/clk/qcom/mmcc-apq8084.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2014-2015, 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 @@ -26,6 +26,7 @@ #include "clk-rcg.h" #include "clk-branch.h" #include "reset.h" +#include "gdsc.h" enum { P_XO, @@ -571,17 +572,11 @@ static struct clk_rcg2 jpeg2_clk_src = { }, }; -static struct freq_tbl pixel_freq_tbl[] = { - { .src = P_DSI0PLL }, - { } -}; - static struct clk_rcg2 pclk0_clk_src = { .cmd_rcgr = 0x2000, .mnd_width = 8, .hid_width = 5, .parent_map = mmcc_xo_dsi_hdmi_edp_gpll0_map, - .freq_tbl = pixel_freq_tbl, .clkr.hw.init = &(struct clk_init_data){ .name = "pclk0_clk_src", .parent_names = mmcc_xo_dsi_hdmi_edp_gpll0, @@ -596,7 +591,6 @@ static struct clk_rcg2 pclk1_clk_src = { .mnd_width = 8, .hid_width = 5, .parent_map = mmcc_xo_dsi_hdmi_edp_gpll0_map, - .freq_tbl = pixel_freq_tbl, .clkr.hw.init = &(struct clk_init_data){ .name = "pclk1_clk_src", .parent_names = mmcc_xo_dsi_hdmi_edp_gpll0, @@ -844,21 +838,15 @@ static struct clk_rcg2 cpp_clk_src = { }, }; -static struct freq_tbl byte_freq_tbl[] = { - { .src = P_DSI0PLL_BYTE }, - { } -}; - static struct clk_rcg2 byte0_clk_src = { .cmd_rcgr = 0x2120, .hid_width = 5, .parent_map = mmcc_xo_dsibyte_hdmi_edp_gpll0_map, - .freq_tbl = byte_freq_tbl, .clkr.hw.init = &(struct clk_init_data){ .name = "byte0_clk_src", .parent_names = mmcc_xo_dsibyte_hdmi_edp_gpll0, .num_parents = 6, - .ops = &clk_byte_ops, + .ops = &clk_byte2_ops, .flags = CLK_SET_RATE_PARENT, }, }; @@ -867,12 +855,11 @@ static struct clk_rcg2 byte1_clk_src = { .cmd_rcgr = 0x2140, .hid_width = 5, .parent_map = mmcc_xo_dsibyte_hdmi_edp_gpll0_map, - .freq_tbl = byte_freq_tbl, .clkr.hw.init = &(struct clk_init_data){ .name = "byte1_clk_src", .parent_names = mmcc_xo_dsibyte_hdmi_edp_gpll0, .num_parents = 6, - .ops = &clk_byte_ops, + .ops = &clk_byte2_ops, .flags = CLK_SET_RATE_PARENT, }, }; @@ -3077,6 +3064,48 @@ static const struct pll_config mmpll3_config = { .aux_output_mask = BIT(1), }; +static struct gdsc venus0_gdsc = { + .gdscr = 0x1024, + .pd = { + .name = "venus0", + }, +}; + +static struct gdsc mdss_gdsc = { + .gdscr = 0x2304, + .pd = { + .name = "mdss", + }, +}; + +static struct gdsc camss_jpeg_gdsc = { + .gdscr = 0x35a4, + .pd = { + .name = "camss_jpeg", + }, +}; + +static struct gdsc camss_vfe_gdsc = { + .gdscr = 0x36a4, + .pd = { + .name = "camss_vfe", + }, +}; + +static struct gdsc oxili_gdsc = { + .gdscr = 0x4024, + .pd = { + .name = "oxili", + }, +}; + +static struct gdsc oxilicx_gdsc = { + .gdscr = 0x4034, + .pd = { + .name = "oxilicx", + }, +}; + static struct clk_regmap *mmcc_apq8084_clocks[] = { [MMSS_AHB_CLK_SRC] = &mmss_ahb_clk_src.clkr, [MMSS_AXI_CLK_SRC] = &mmss_axi_clk_src.clkr, @@ -3294,6 +3323,15 @@ static const struct qcom_reset_map mmcc_apq8084_resets[] = { [MMSSNOCAXI_RESET] = { 0x5060 }, }; +static struct gdsc *mmcc_apq8084_gdscs[] = { + [VENUS0_GDSC] = &venus0_gdsc, + [MDSS_GDSC] = &mdss_gdsc, + [CAMSS_JPEG_GDSC] = &camss_jpeg_gdsc, + [CAMSS_VFE_GDSC] = &camss_vfe_gdsc, + [OXILI_GDSC] = &oxili_gdsc, + [OXILICX_GDSC] = &oxilicx_gdsc, +}; + static const struct regmap_config mmcc_apq8084_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -3308,6 +3346,8 @@ static const struct qcom_cc_desc mmcc_apq8084_desc = { .num_clks = ARRAY_SIZE(mmcc_apq8084_clocks), .resets = mmcc_apq8084_resets, .num_resets = ARRAY_SIZE(mmcc_apq8084_resets), + .gdscs = mmcc_apq8084_gdscs, + .num_gdscs = ARRAY_SIZE(mmcc_apq8084_gdscs), }; static const struct of_device_id mmcc_apq8084_match_table[] = { diff --git a/drivers/clk/qcom/mmcc-msm8974.c b/drivers/clk/qcom/mmcc-msm8974.c index 07f4cc159ad3..cfdd0bf8f82d 100644 --- a/drivers/clk/qcom/mmcc-msm8974.c +++ b/drivers/clk/qcom/mmcc-msm8974.c @@ -31,6 +31,7 @@ #include "clk-rcg.h" #include "clk-branch.h" #include "reset.h" +#include "gdsc.h" enum { P_XO, @@ -522,17 +523,11 @@ static struct clk_rcg2 jpeg2_clk_src = { }, }; -static struct freq_tbl pixel_freq_tbl[] = { - { .src = P_DSI0PLL }, - { } -}; - static struct clk_rcg2 pclk0_clk_src = { .cmd_rcgr = 0x2000, .mnd_width = 8, .hid_width = 5, .parent_map = mmcc_xo_dsi_hdmi_edp_gpll0_map, - .freq_tbl = pixel_freq_tbl, .clkr.hw.init = &(struct clk_init_data){ .name = "pclk0_clk_src", .parent_names = mmcc_xo_dsi_hdmi_edp_gpll0, @@ -547,7 +542,6 @@ static struct clk_rcg2 pclk1_clk_src = { .mnd_width = 8, .hid_width = 5, .parent_map = mmcc_xo_dsi_hdmi_edp_gpll0_map, - .freq_tbl = pixel_freq_tbl, .clkr.hw.init = &(struct clk_init_data){ .name = "pclk1_clk_src", .parent_names = mmcc_xo_dsi_hdmi_edp_gpll0, @@ -785,7 +779,7 @@ static struct clk_rcg2 byte0_clk_src = { .name = "byte0_clk_src", .parent_names = mmcc_xo_dsibyte_hdmi_edp_gpll0, .num_parents = 6, - .ops = &clk_byte_ops, + .ops = &clk_byte2_ops, .flags = CLK_SET_RATE_PARENT, }, }; @@ -799,7 +793,7 @@ static struct clk_rcg2 byte1_clk_src = { .name = "byte1_clk_src", .parent_names = mmcc_xo_dsibyte_hdmi_edp_gpll0, .num_parents = 6, - .ops = &clk_byte_ops, + .ops = &clk_byte2_ops, .flags = CLK_SET_RATE_PARENT, }, }; @@ -2349,6 +2343,48 @@ static struct pll_config mmpll3_config = { .aux_output_mask = BIT(1), }; +static struct gdsc venus0_gdsc = { + .gdscr = 0x1024, + .pd = { + .name = "venus0", + }, +}; + +static struct gdsc mdss_gdsc = { + .gdscr = 0x2304, + .pd = { + .name = "mdss", + }, +}; + +static struct gdsc camss_jpeg_gdsc = { + .gdscr = 0x35a4, + .pd = { + .name = "camss_jpeg", + }, +}; + +static struct gdsc camss_vfe_gdsc = { + .gdscr = 0x36a4, + .pd = { + .name = "camss_vfe", + }, +}; + +static struct gdsc oxili_gdsc = { + .gdscr = 0x4024, + .pd = { + .name = "oxili", + }, +}; + +static struct gdsc oxilicx_gdsc = { + .gdscr = 0x4034, + .pd = { + .name = "oxilicx", + }, +}; + static struct clk_regmap *mmcc_msm8974_clocks[] = { [MMSS_AHB_CLK_SRC] = &mmss_ahb_clk_src.clkr, [MMSS_AXI_CLK_SRC] = &mmss_axi_clk_src.clkr, @@ -2525,6 +2561,15 @@ static const struct qcom_reset_map mmcc_msm8974_resets[] = { [OCMEMNOC_RESET] = { 0x50b0 }, }; +static struct gdsc *mmcc_msm8974_gdscs[] = { + [VENUS0_GDSC] = &venus0_gdsc, + [MDSS_GDSC] = &mdss_gdsc, + [CAMSS_JPEG_GDSC] = &camss_jpeg_gdsc, + [CAMSS_VFE_GDSC] = &camss_vfe_gdsc, + [OXILI_GDSC] = &oxili_gdsc, + [OXILICX_GDSC] = &oxilicx_gdsc, +}; + static const struct regmap_config mmcc_msm8974_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -2539,6 +2584,8 @@ static const struct qcom_cc_desc mmcc_msm8974_desc = { .num_clks = ARRAY_SIZE(mmcc_msm8974_clocks), .resets = mmcc_msm8974_resets, .num_resets = ARRAY_SIZE(mmcc_msm8974_resets), + .gdscs = mmcc_msm8974_gdscs, + .num_gdscs = ARRAY_SIZE(mmcc_msm8974_gdscs), }; static const struct of_device_id mmcc_msm8974_match_table[] = { diff --git a/drivers/clk/qcom/rpmcc-apq8064.c b/drivers/clk/qcom/rpmcc-apq8064.c new file mode 100644 index 000000000000..392cca7d69c1 --- /dev/null +++ b/drivers/clk/qcom/rpmcc-apq8064.c @@ -0,0 +1,347 @@ +#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, ret; + + 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; + } + + ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, + data); + if (ret) + return ret; + + /* Hold and active set vote */ + clk_set_rate(afab_clk.hw.clk, INT_MAX); + clk_prepare_enable(afab_clk.hw.clk); + + return 0; +} + +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, +}; + +static int __init rpm_clk_init(void) +{ + return platform_driver_register(&rpm_clk_driver); +} +subsys_initcall(rpm_clk_init); + +static void __exit rpm_clk_exit(void) +{ + 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/rpmcc-msm8916.c b/drivers/clk/qcom/rpmcc-msm8916.c new file mode 100644 index 000000000000..0cb3195ca68f --- /dev/null +++ b/drivers/clk/qcom/rpmcc-msm8916.c @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2015, Linaro Limited + * 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/clk.h> +#include <linux/clk-provider.h> +#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/module.h> + +#include "clk-rpm.h" +#include <dt-bindings/clock/qcom,rpmcc-msm8916.h> + +#define CXO_ID 0x0 +#define QDSS_ID 0x1 +#define BUS_SCALING 0x2 + +#define PCNOC_ID 0x0 +#define SNOC_ID 0x1 +#define BIMC_ID 0x0 + +#define BB_CLK1_ID 1 +#define BB_CLK2_ID 2 +#define RF_CLK1_ID 4 +#define RF_CLK2_ID 5 + +struct rpm_cc { + struct clk_onecell_data data; + struct clk *clks[]; +}; + +/* SMD clocks */ +DEFINE_CLK_RPM_SMD(pcnoc_clk, pcnoc_a_clk, QCOM_SMD_RPM_BUS_CLK, PCNOC_ID, NULL); +DEFINE_CLK_RPM_SMD(snoc_clk, snoc_a_clk, QCOM_SMD_RPM_BUS_CLK, SNOC_ID, NULL); +DEFINE_CLK_RPM_SMD(bimc_clk, bimc_a_clk, QCOM_SMD_RPM_MEM_CLK, BIMC_ID, NULL); + +DEFINE_CLK_RPM_SMD_BRANCH(xo, xo_a, QCOM_SMD_RPM_MISC_CLK, CXO_ID, 19200000); +DEFINE_CLK_RPM_SMD_QDSS(qdss_clk, qdss_a_clk, QCOM_SMD_RPM_MISC_CLK, QDSS_ID); + +/* SMD_XO_BUFFER */ +DEFINE_CLK_RPM_SMD_XO_BUFFER(bb_clk1, bb_clk1_a, BB_CLK1_ID); +DEFINE_CLK_RPM_SMD_XO_BUFFER(bb_clk2, bb_clk2_a, BB_CLK2_ID); +DEFINE_CLK_RPM_SMD_XO_BUFFER(rf_clk1, rf_clk1_a, RF_CLK1_ID); +DEFINE_CLK_RPM_SMD_XO_BUFFER(rf_clk2, rf_clk2_a, RF_CLK2_ID); + +DEFINE_CLK_RPM_SMD_XO_BUFFER_PINCTRL(bb_clk1_pin, bb_clk1_a_pin, BB_CLK1_ID); +DEFINE_CLK_RPM_SMD_XO_BUFFER_PINCTRL(bb_clk2_pin, bb_clk2_a_pin, BB_CLK2_ID); +DEFINE_CLK_RPM_SMD_XO_BUFFER_PINCTRL(rf_clk1_pin, rf_clk1_a_pin, RF_CLK1_ID); +DEFINE_CLK_RPM_SMD_XO_BUFFER_PINCTRL(rf_clk2_pin, rf_clk2_a_pin, RF_CLK2_ID); + +static struct clk_rpm *rpmcc_msm8916_clks[] = { + [RPM_XO_CLK_SRC] = &xo, + [RPM_XO_A_CLK_SRC] = &xo_a, + [RPM_PCNOC_CLK] = &pcnoc_clk, + [RPM_PCNOC_A_CLK] = &pcnoc_a_clk, + [RPM_SNOC_CLK] = &snoc_clk, + [RPM_SNOC_A_CLK] = &snoc_a_clk, + [RPM_BIMC_CLK] = &bimc_clk, + [RPM_BIMC_A_CLK] = &bimc_a_clk, + [RPM_QDSS_CLK] = &qdss_clk, + [RPM_QDSS_A_CLK] = &qdss_a_clk, + [RPM_BB_CLK1] = &bb_clk1, + [RPM_BB_CLK1_A] = &bb_clk1_a, + [RPM_BB_CLK2] = &bb_clk2, + [RPM_BB_CLK2_A] = &bb_clk2_a, + [RPM_RF_CLK1] = &rf_clk1, + [RPM_RF_CLK1_A] = &rf_clk1_a, + [RPM_RF_CLK2] = &rf_clk2, + [RPM_RF_CLK2_A] = &rf_clk2_a, + [RPM_BB_CLK1_PIN] = &bb_clk1_pin, + [RPM_BB_CLK1_A_PIN] = &bb_clk1_a_pin, + [RPM_BB_CLK2_PIN] = &bb_clk2_pin, + [RPM_BB_CLK2_A_PIN] = &bb_clk2_a_pin, + [RPM_RF_CLK1_PIN] = &rf_clk1_pin, + [RPM_RF_CLK1_A_PIN] = &rf_clk1_a_pin, + [RPM_RF_CLK2_PIN] = &rf_clk2_pin, + [RPM_RF_CLK2_A_PIN] = &rf_clk2_a_pin, +}; + +static int rpmcc_msm8916_probe(struct platform_device *pdev) +{ + struct clk **clks; + struct clk *clk; + struct rpm_cc *rcc; + struct qcom_smd_rpm *rpm; + struct clk_onecell_data *data; + int num_clks = ARRAY_SIZE(rpmcc_msm8916_clks); + int ret, i; + + if (!pdev->dev.of_node) + return -ENODEV; + + rpm = dev_get_drvdata(pdev->dev.parent); + if (!rpm) { + dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n"); + return -ENODEV; + } + + ret = clk_rpm_enable_scaling(rpm); + if (ret) + return ret; + + rcc = devm_kzalloc(&pdev->dev, sizeof(*rcc) + sizeof(*clks) * num_clks, + GFP_KERNEL); + if (!rcc) + return -ENOMEM; + + clks = rcc->clks; + data = &rcc->data; + data->clks = clks; + data->clk_num = num_clks; + + clk = clk_register_fixed_rate(&pdev->dev, "sleep_clk_src", NULL, + CLK_IS_ROOT, 32768); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + for (i = 0; i < num_clks; i++) { + if (!rpmcc_msm8916_clks[i]) { + clks[i] = ERR_PTR(-ENOENT); + continue; + } + + rpmcc_msm8916_clks[i]->rpm = rpm; + clk = devm_clk_register(&pdev->dev, &rpmcc_msm8916_clks[i]->hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + clks[i] = clk; + } + + ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, + data); + if (ret) + return ret; + + /* Hold an active set vote */ + clk_set_rate(bimc_a_clk.hw.clk, INT_MAX); + clk_prepare_enable(bimc_a_clk.hw.clk); + clk_set_rate(bimc_clk.hw.clk, INT_MAX); + clk_prepare_enable(bimc_clk.hw.clk); + clk_set_rate(snoc_clk.hw.clk, INT_MAX); + clk_prepare_enable(snoc_clk.hw.clk); + clk_prepare_enable(xo.hw.clk); + + return 0; +} + +static int rpmcc_msm8916_remove(struct platform_device *pdev) +{ + of_clk_del_provider(pdev->dev.of_node); + return 0; +} + +static const struct of_device_id rpmcc_msm8916_of_match[] = { + { .compatible = "qcom,rpmcc-msm8916" }, + { }, +}; + +static struct platform_driver rpmcc_msm8916_driver = { + .driver = { + .name = "qcom-rpmcc-msm8916", + .of_match_table = rpmcc_msm8916_of_match, + }, + .probe = rpmcc_msm8916_probe, + .remove = rpmcc_msm8916_remove, +}; + +module_platform_driver(rpmcc_msm8916_driver); +core_initcall(rpmcc_msm8916_driver_init); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Qualcomm MSM8916 RPM Clock Controller Driver"); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index cc8a71c267b8..29cf87171b4d 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -135,6 +135,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 2169bf792db7..6e70a14b2dfd 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -65,6 +65,7 @@ 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_PXA3xx) += pxa3xx-cpufreq.o +obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o diff --git a/drivers/cpufreq/qcom-cpufreq.c b/drivers/cpufreq/qcom-cpufreq.c new file mode 100644 index 000000000000..c9f86a062cdd --- /dev/null +++ b/drivers/cpufreq/qcom-cpufreq.c @@ -0,0 +1,204 @@ +/* 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/cpu.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_opp.h> +#include <linux/slab.h> +#include <linux/cpufreq-dt.h> + +static void __init get_krait_bin_format_a(int *speed, int *pvs, int *pvs_ver) +{ + void __iomem *base; + u32 pte_efuse; + + *speed = *pvs = *pvs_ver = 0; + + base = ioremap(0x007000c0, 4); + if (!base) { + pr_warn("Unable to read efuse data. Defaulting to 0!\n"); + return; + } + + pte_efuse = readl_relaxed(base); + iounmap(base); + + *speed = pte_efuse & 0xf; + if (*speed == 0xf) + *speed = (pte_efuse >> 4) & 0xf; + + if (*speed == 0xf) { + *speed = 0; + pr_warn("Speed bin: Defaulting to %d\n", *speed); + } else { + pr_info("Speed bin: %d\n", *speed); + } + + *pvs = (pte_efuse >> 10) & 0x7; + if (*pvs == 0x7) + *pvs = (pte_efuse >> 13) & 0x7; + + if (*pvs == 0x7) { + *pvs = 0; + pr_warn("PVS bin: Defaulting to %d\n", *pvs); + } else { + pr_info("PVS bin: %d\n", *pvs); + } +} + +static void __init get_krait_bin_format_b(int *speed, int *pvs, int *pvs_ver) +{ + u32 pte_efuse, redundant_sel; + void __iomem *base; + + *speed = 0; + *pvs = 0; + *pvs_ver = 0; + + base = ioremap(0xfc4b80b0, 8); + if (!base) { + pr_warn("Unable to read efuse data. Defaulting to 0!\n"); + return; + } + + pte_efuse = readl_relaxed(base); + redundant_sel = (pte_efuse >> 24) & 0x7; + *speed = pte_efuse & 0x7; + /* 4 bits of PVS are in efuse register bits 31, 8-6. */ + *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7); + *pvs_ver = (pte_efuse >> 4) & 0x3; + + switch (redundant_sel) { + case 1: + *speed = (pte_efuse >> 27) & 0xf; + break; + case 2: + *pvs = (pte_efuse >> 27) & 0xf; + break; + } + + /* Check SPEED_BIN_BLOW_STATUS */ + if (pte_efuse & BIT(3)) { + pr_info("Speed bin: %d\n", *speed); + } else { + pr_warn("Speed bin not set. Defaulting to 0!\n"); + *speed = 0; + } + + /* Check PVS_BLOW_STATUS */ + pte_efuse = readl_relaxed(base + 0x4) & BIT(21); + if (pte_efuse) { + pr_info("PVS bin: %d\n", *pvs); + } else { + pr_warn("PVS bin not set. Defaulting to 0!\n"); + *pvs = 0; + } + + pr_info("PVS version: %d\n", *pvs_ver); + iounmap(base); +} + +static int __init qcom_cpufreq_populate_opps(void) +{ + int len, rows, cols, i, k, speed, pvs, pvs_ver; + char table_name[] = "qcom,speedXX-pvsXX-bin-vXX"; + struct device_node *np; + struct device *dev; + int cpu = 0; + + np = of_find_node_by_name(NULL, "qcom,pvs"); + if (!np) + return -ENODEV; + + if (of_property_read_bool(np, "qcom,pvs-format-a")) { + get_krait_bin_format_a(&speed, &pvs, &pvs_ver); + cols = 2; + } else if (of_property_read_bool(np, "qcom,pvs-format-b")) { + get_krait_bin_format_b(&speed, &pvs, &pvs_ver); + cols = 3; + } else { + return -ENODEV; + } + + snprintf(table_name, sizeof(table_name), + "qcom,speed%d-pvs%d-bin-v%d", speed, pvs, pvs_ver); + + if (!of_find_property(np, table_name, &len)) + return -EINVAL; + + len /= sizeof(u32); + if (len % cols || len == 0) + return -EINVAL; + + rows = len / cols; + + for (i = 0, k = 0; i < 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 */ + for (cpu = 0; cpu < num_possible_cpus(); cpu++) { + dev = get_cpu_device(cpu); + if (!dev) + return -ENODEV; + if (dev_pm_opp_add(dev, freq, volt)) + pr_warn("failed to add OPP %u\n", freq); + } + } + + return 0; +} + +static int __init qcom_cpufreq_driver_init(void) +{ + struct cpufreq_dt_platform_data pdata = { .independent_clocks = true }; + struct platform_device_info devinfo = { + .name = "cpufreq-dt", + .data = &pdata, + .size_data = sizeof(pdata), + }; + struct device *cpu_dev; + struct device_node *np; + int ret; + + 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); + + ret = qcom_cpufreq_populate_opps(); + if (ret) + return ret; + + return PTR_ERR_OR_ZERO(platform_device_register_full(&devinfo)); +} +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 21340e0be73e..325be7fe4e8d 100644 --- a/drivers/cpuidle/Kconfig.arm +++ b/drivers/cpuidle/Kconfig.arm @@ -74,3 +74,10 @@ config ARM_MVEBU_V7_CPUIDLE depends on ARCH_MVEBU && !ARM64 help Select this to enable cpuidle on Armada 370, 38x and XP processors. + +config ARM_QCOM_CPUIDLE + bool "CPU Idle Driver for QCOM processors" + depends on ARCH_QCOM + select ARM_CPUIDLE + help + Select this to enable cpuidle on QCOM processors. diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 76157ab9faf3..6d280e2723dc 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -124,22 +124,34 @@ static int find_cable_index_by_id(struct extcon_dev *edev, const unsigned int id return -EINVAL; } -static int find_cable_index_by_name(struct extcon_dev *edev, const char *name) +static int find_cable_id_by_name(struct extcon_dev *edev, const char *name) { - unsigned int id = EXTCON_NONE; + unsigned int id = -EINVAL; int i = 0; - if (edev->max_supported == 0) - return -EINVAL; - - /* Find the the number of extcon cable */ + /* Find the id of extcon cable */ while (extcon_name[i]) { if (!strncmp(extcon_name[i], name, CABLE_NAME_MAX)) { id = i; break; } + + i++; } + return id; +}; + +static int find_cable_index_by_name(struct extcon_dev *edev, const char *name) +{ + unsigned int id = EXTCON_NONE; + + if (edev->max_supported == 0) + return -EINVAL; + + /* Find the the number of extcon cable */ + id = find_cable_id_by_name(edev, name); + if (id == EXTCON_NONE) return -EINVAL; @@ -228,9 +240,11 @@ static ssize_t cable_state_show(struct device *dev, struct extcon_cable *cable = container_of(attr, struct extcon_cable, attr_state); + int i = cable->cable_index; + return sprintf(buf, "%d\n", extcon_get_cable_state_(cable->edev, - cable->cable_index)); + cable->edev->supported_cable[i])); } /** @@ -263,19 +277,25 @@ int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) spin_lock_irqsave(&edev->lock, flags); if (edev->state != ((edev->state & ~mask) | (state & mask))) { + u32 old_state; + if (check_mutually_exclusive(edev, (edev->state & ~mask) | (state & mask))) { spin_unlock_irqrestore(&edev->lock, flags); return -EPERM; } + old_state = edev->state; + edev->state &= ~mask; + edev->state |= state & mask; + for (index = 0; index < edev->max_supported; index++) { - if (is_extcon_changed(edev->state, state, index, &attached)) - raw_notifier_call_chain(&edev->nh[index], attached, edev); + if (is_extcon_changed(old_state, edev->state, index, + &attached)) + raw_notifier_call_chain(&edev->nh[index], + attached, edev); } - edev->state &= ~mask; - edev->state |= state & mask; /* This could be in interrupt handler */ prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); @@ -341,6 +361,9 @@ int extcon_get_cable_state_(struct extcon_dev *edev, const unsigned int id) { int index; + if (id == EXTCON_NONE) + return -EINVAL; + index = find_cable_index_by_id(edev, id); if (index < 0) return index; @@ -361,7 +384,7 @@ EXPORT_SYMBOL_GPL(extcon_get_cable_state_); */ int extcon_get_cable_state(struct extcon_dev *edev, const char *cable_name) { - return extcon_get_cable_state_(edev, find_cable_index_by_name + return extcon_get_cable_state_(edev, find_cable_id_by_name (edev, cable_name)); } EXPORT_SYMBOL_GPL(extcon_get_cable_state); @@ -380,6 +403,9 @@ int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, u32 state; int index; + if (id == EXTCON_NONE) + return -EINVAL; + index = find_cable_index_by_id(edev, id); if (index < 0) return index; @@ -404,7 +430,7 @@ EXPORT_SYMBOL_GPL(extcon_set_cable_state_); int extcon_set_cable_state(struct extcon_dev *edev, const char *cable_name, bool cable_state) { - return extcon_set_cable_state_(edev, find_cable_index_by_name + return extcon_set_cable_state_(edev, find_cable_id_by_name (edev, cable_name), cable_state); } EXPORT_SYMBOL_GPL(extcon_set_cable_state); diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 4a4b897f9314..fa809136294e 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -12,7 +12,11 @@ obj-$(CONFIG_ISCSI_IBFT_FIND) += iscsi_ibft_find.o obj-$(CONFIG_ISCSI_IBFT) += iscsi_ibft.o obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o obj-$(CONFIG_QCOM_SCM) += qcom_scm.o +ifdef CONFIG_64BIT +obj-$(CONFIG_QCOM_SCM) += qcom_scm-64.o +else obj-$(CONFIG_QCOM_SCM) += qcom_scm-32.o +endif CFLAGS_qcom_scm-32.o :=$(call as-instr,.arch_extension sec,-DREQUIRES_SEC=1) obj-y += broadcom/ diff --git a/drivers/firmware/qcom_scm-32.c b/drivers/firmware/qcom_scm-32.c index 1bd6f9c34331..d78560260361 100644 --- a/drivers/firmware/qcom_scm-32.c +++ b/drivers/firmware/qcom_scm-32.c @@ -1,4 +1,5 @@ /* Copyright (c) 2010,2015, The Linux Foundation. All rights reserved. +>>>>>>> firmware: qcom: scm: Split out 32-bit specific SCM code * Copyright (C) 2015 Linaro Ltd. * * This program is free software; you can redistribute it and/or modify @@ -29,6 +30,13 @@ #include "qcom_scm.h" +#define QCOM_SCM_ENOMEM -5 +#define QCOM_SCM_EOPNOTSUPP -4 +#define QCOM_SCM_EINVAL_ADDR -3 +#define QCOM_SCM_EINVAL_ARG -2 +#define QCOM_SCM_ERROR -1 +#define QCOM_SCM_INTERRUPTED 1 + #define QCOM_SCM_FLAG_COLDBOOT_CPU0 0x00 #define QCOM_SCM_FLAG_COLDBOOT_CPU1 0x01 #define QCOM_SCM_FLAG_COLDBOOT_CPU2 0x08 diff --git a/drivers/firmware/qcom_scm-64.c b/drivers/firmware/qcom_scm-64.c new file mode 100644 index 000000000000..f32e2f153a03 --- /dev/null +++ b/drivers/firmware/qcom_scm-64.c @@ -0,0 +1,501 @@ +/* Copyright (c) 2014-2015, 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. + * + * 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. + */ + +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/qcom_scm.h> + +#include <asm/cacheflush.h> +#include <asm/compiler.h> +#include <asm/smp_plat.h> + +#include "qcom_scm.h" + +#define QCOM_SCM_SIP_FNID(s, c) (((((s) & 0xFF) << 8) | ((c) & 0xFF)) | 0x02000000) + +#define MAX_QCOM_SCM_ARGS 10 +#define MAX_QCOM_SCM_RETS 3 + +#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\ + (((a) & 0xff) << 4) | \ + (((b) & 0xff) << 6) | \ + (((c) & 0xff) << 8) | \ + (((d) & 0xff) << 10) | \ + (((e) & 0xff) << 12) | \ + (((f) & 0xff) << 14) | \ + (((g) & 0xff) << 16) | \ + (((h) & 0xff) << 18) | \ + (((i) & 0xff) << 20) | \ + (((j) & 0xff) << 22) | \ + (num & 0xffff)) + +#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + +/** + * struct qcom_scm_desc + * @arginfo: Metadata describing the arguments in args[] + * @args: The array of arguments for the secure syscall + * @ret: The values returned by the secure syscall + * @extra_arg_buf: The buffer containing extra arguments + (that don't fit in available registers) + * @x5: The 4rd argument to the secure syscall or physical address of + extra_arg_buf + */ +struct qcom_scm_desc { + u32 arginfo; + u64 args[MAX_QCOM_SCM_ARGS]; + u64 ret[MAX_QCOM_SCM_RETS]; + + /* private */ + void *extra_arg_buf; + u64 x5; +}; + + +#define QCOM_SCM_ENOMEM -5 +#define QCOM_SCM_EOPNOTSUPP -4 +#define QCOM_SCM_EINVAL_ADDR -3 +#define QCOM_SCM_EINVAL_ARG -2 +#define QCOM_SCM_ERROR -1 +#define QCOM_SCM_INTERRUPTED 1 +#define QCOM_SCM_EBUSY -55 +#define QCOM_SCM_V2_EBUSY -12 + +static DEFINE_MUTEX(qcom_scm_lock); + +#define QCOM_SCM_EBUSY_WAIT_MS 30 +#define QCOM_SCM_EBUSY_MAX_RETRY 20 + +#define N_EXT_QCOM_SCM_ARGS 7 +#define FIRST_EXT_ARG_IDX 3 +#define SMC_ATOMIC_SYSCALL 31 +#define N_REGISTER_ARGS (MAX_QCOM_SCM_ARGS - N_EXT_QCOM_SCM_ARGS + 1) +#define SMC64_MASK 0x40000000 +#define SMC_ATOMIC_MASK 0x80000000 +#define IS_CALL_AVAIL_CMD 1 + +#define R0_STR "x0" +#define R1_STR "x1" +#define R2_STR "x2" +#define R3_STR "x3" +#define R4_STR "x4" +#define R5_STR "x5" + +static int qcom_scm_remap_error(int err) +{ + switch (err) { + case QCOM_SCM_ERROR: + return -EIO; + case QCOM_SCM_EINVAL_ADDR: + case QCOM_SCM_EINVAL_ARG: + return -EINVAL; + case QCOM_SCM_EOPNOTSUPP: + return -EOPNOTSUPP; + case QCOM_SCM_ENOMEM: + return -ENOMEM; + case QCOM_SCM_EBUSY: + return QCOM_SCM_EBUSY; + case QCOM_SCM_V2_EBUSY: + return QCOM_SCM_V2_EBUSY; + } + return -EINVAL; +} + +int __qcom_scm_call_armv8_64(u64 x0, u64 x1, u64 x2, u64 x3, u64 x4, u64 x5, + u64 *ret1, u64 *ret2, u64 *ret3) +{ + register u64 r0 asm("r0") = x0; + register u64 r1 asm("r1") = x1; + register u64 r2 asm("r2") = x2; + register u64 r3 asm("r3") = x3; + register u64 r4 asm("r4") = x4; + register u64 r5 asm("r5") = x5; + + do { + asm volatile( + __asmeq("%0", R0_STR) + __asmeq("%1", R1_STR) + __asmeq("%2", R2_STR) + __asmeq("%3", R3_STR) + __asmeq("%4", R0_STR) + __asmeq("%5", R1_STR) + __asmeq("%6", R2_STR) + __asmeq("%7", R3_STR) + __asmeq("%8", R4_STR) + __asmeq("%9", R5_STR) +#ifdef REQUIRES_SEC + ".arch_extension sec\n" +#endif + "smc #0\n" + : "=r" (r0), "=r" (r1), "=r" (r2), "=r" (r3) + : "r" (r0), "r" (r1), "r" (r2), "r" (r3), "r" (r4), + "r" (r5) + : "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", + "x14", "x15", "x16", "x17"); + } while (r0 == QCOM_SCM_INTERRUPTED); + + if (ret1) + *ret1 = r1; + if (ret2) + *ret2 = r2; + if (ret3) + *ret3 = r3; + + return r0; +} + +int __qcom_scm_call_armv8_32(u32 w0, u32 w1, u32 w2, u32 w3, u32 w4, u32 w5, + u64 *ret1, u64 *ret2, u64 *ret3) +{ + register u32 r0 asm("r0") = w0; + register u32 r1 asm("r1") = w1; + register u32 r2 asm("r2") = w2; + register u32 r3 asm("r3") = w3; + register u32 r4 asm("r4") = w4; + register u32 r5 asm("r5") = w5; + + do { + asm volatile( + __asmeq("%0", R0_STR) + __asmeq("%1", R1_STR) + __asmeq("%2", R2_STR) + __asmeq("%3", R3_STR) + __asmeq("%4", R0_STR) + __asmeq("%5", R1_STR) + __asmeq("%6", R2_STR) + __asmeq("%7", R3_STR) + __asmeq("%8", R4_STR) + __asmeq("%9", R5_STR) +#ifdef REQUIRES_SEC + ".arch_extension sec\n" +#endif + "smc #0\n" + : "=r" (r0), "=r" (r1), "=r" (r2), "=r" (r3) + : "r" (r0), "r" (r1), "r" (r2), "r" (r3), "r" (r4), + "r" (r5) + : "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", + "x14", "x15", "x16", "x17"); + + } while (r0 == QCOM_SCM_INTERRUPTED); + + if (ret1) + *ret1 = r1; + if (ret2) + *ret2 = r2; + if (ret3) + *ret3 = r3; + + return r0; +} + +struct qcom_scm_extra_arg { + union { + u32 args32[N_EXT_QCOM_SCM_ARGS]; + u64 args64[N_EXT_QCOM_SCM_ARGS]; + }; +}; + +static enum qcom_scm_interface_version { + QCOM_SCM_UNKNOWN, + QCOM_SCM_LEGACY, + QCOM_SCM_ARMV8_32, + QCOM_SCM_ARMV8_64, +} qcom_scm_version = QCOM_SCM_UNKNOWN; + +/* This will be set to specify SMC32 or SMC64 */ +static u32 qcom_scm_version_mask; + +/* + * If there are more than N_REGISTER_ARGS, allocate a buffer and place + * the additional arguments in it. The extra argument buffer will be + * pointed to by X5. + */ +static int allocate_extra_arg_buffer(struct qcom_scm_desc *desc, gfp_t flags) +{ + int i, j; + struct qcom_scm_extra_arg *argbuf; + int arglen = desc->arginfo & 0xf; + size_t argbuflen = PAGE_ALIGN(sizeof(struct qcom_scm_extra_arg)); + + desc->x5 = desc->args[FIRST_EXT_ARG_IDX]; + + if (likely(arglen <= N_REGISTER_ARGS)) { + desc->extra_arg_buf = NULL; + return 0; + } + + argbuf = kzalloc(argbuflen, flags); + if (!argbuf) { + pr_err("qcom_scm_call: failed to alloc mem for extended argument buffer\n"); + return -ENOMEM; + } + + desc->extra_arg_buf = argbuf; + + j = FIRST_EXT_ARG_IDX; + if (qcom_scm_version == QCOM_SCM_ARMV8_64) + for (i = 0; i < N_EXT_QCOM_SCM_ARGS; i++) + argbuf->args64[i] = desc->args[j++]; + else + for (i = 0; i < N_EXT_QCOM_SCM_ARGS; i++) + argbuf->args32[i] = desc->args[j++]; + desc->x5 = virt_to_phys(argbuf); + __flush_dcache_area(argbuf, argbuflen); + + return 0; +} + +/** + * qcom_scm_call() - Invoke a syscall in the secure world + * @svc_id: service identifier + * @cmd_id: command identifier + * @fn_id: The function ID for this syscall + * @desc: Descriptor structure containing arguments and return values + * + * Sends a command to the SCM and waits for the command to finish processing. + * This should *only* be called in pre-emptible context. + * + * A note on cache maintenance: + * Note that any buffers that are expected to be accessed by the secure world + * must be flushed before invoking qcom_scm_call and invalidated in the cache + * immediately after qcom_scm_call returns. An important point that must be noted + * is that on ARMV8 architectures, invalidation actually also causes a dirty + * cache line to be cleaned (flushed + unset-dirty-bit). Therefore it is of + * paramount importance that the buffer be flushed before invoking qcom_scm_call, + * even if you don't care about the contents of that buffer. + * + * Note that cache maintenance on the argument buffer (desc->args) is taken care + * of by qcom_scm_call; however, callers are responsible for any other cached + * buffers passed over to the secure world. +*/ +static int qcom_scm_call(u32 svc_id, u32 cmd_id, struct qcom_scm_desc *desc) +{ + int arglen = desc->arginfo & 0xf; + int ret, retry_count = 0; + u32 fn_id = QCOM_SCM_SIP_FNID(svc_id, cmd_id); + u64 x0; + + ret = allocate_extra_arg_buffer(desc, GFP_KERNEL); + if (ret) + return ret; + + x0 = fn_id | qcom_scm_version_mask; + + do { + mutex_lock(&qcom_scm_lock); + + desc->ret[0] = desc->ret[1] = desc->ret[2] = 0; + + pr_debug("qcom_scm_call: func id %#llx, args: %#x, %#llx, %#llx, %#llx, %#llx\n", + x0, desc->arginfo, desc->args[0], desc->args[1], + desc->args[2], desc->x5); + + if (qcom_scm_version == QCOM_SCM_ARMV8_64) + ret = __qcom_scm_call_armv8_64(x0, desc->arginfo, + desc->args[0], desc->args[1], + desc->args[2], desc->x5, + &desc->ret[0], &desc->ret[1], + &desc->ret[2]); + else + ret = __qcom_scm_call_armv8_32(x0, desc->arginfo, + desc->args[0], desc->args[1], + desc->args[2], desc->x5, + &desc->ret[0], &desc->ret[1], + &desc->ret[2]); + mutex_unlock(&qcom_scm_lock); + + if (ret == QCOM_SCM_V2_EBUSY) + msleep(QCOM_SCM_EBUSY_WAIT_MS); + } while (ret == QCOM_SCM_V2_EBUSY && (retry_count++ < QCOM_SCM_EBUSY_MAX_RETRY)); + + if (ret < 0) + pr_err("qcom_scm_call failed: func id %#llx, arginfo: %#x, args: %#llx, %#llx, %#llx, %#llx, ret: %d, syscall returns: %#llx, %#llx, %#llx\n", + x0, desc->arginfo, desc->args[0], desc->args[1], + desc->args[2], desc->x5, ret, desc->ret[0], + desc->ret[1], desc->ret[2]); + + if (arglen > N_REGISTER_ARGS) + kfree(desc->extra_arg_buf); + if (ret < 0) + return qcom_scm_remap_error(ret); + return 0; +} + +/** + * qcom_scm_call_atomic() - Invoke a syscall in the secure world + * + * Similar to qcom_scm_call except that this can be invoked in atomic context. + * There is also no retry mechanism implemented. Please ensure that the + * secure world syscall can be executed in such a context and can complete + * in a timely manner. + */ +static int qcom_scm_call_atomic(u32 s, u32 c, struct qcom_scm_desc *desc) +{ + int arglen = desc->arginfo & 0xf; + int ret; + u32 fn_id = QCOM_SCM_SIP_FNID(s, c); + u64 x0; + + ret = allocate_extra_arg_buffer(desc, GFP_ATOMIC); + if (ret) + return ret; + + x0 = fn_id | BIT(SMC_ATOMIC_SYSCALL) | qcom_scm_version_mask; + + pr_debug("qcom_scm_call: func id %#llx, args: %#x, %#llx, %#llx, %#llx, %#llx\n", + x0, desc->arginfo, desc->args[0], desc->args[1], + desc->args[2], desc->x5); + + if (qcom_scm_version == QCOM_SCM_ARMV8_64) + ret = __qcom_scm_call_armv8_64(x0, desc->arginfo, desc->args[0], + desc->args[1], desc->args[2], + desc->x5, &desc->ret[0], + &desc->ret[1], &desc->ret[2]); + else + ret = __qcom_scm_call_armv8_32(x0, desc->arginfo, desc->args[0], + desc->args[1], desc->args[2], + desc->x5, &desc->ret[0], + &desc->ret[1], &desc->ret[2]); + if (ret < 0) + pr_err("qcom_scm_call failed: func id %#llx, arginfo: %#x, args: %#llx, %#llx, %#llx, %#llx, ret: %d, syscall returns: %#llx, %#llx, %#llx\n", + x0, desc->arginfo, desc->args[0], desc->args[1], + desc->args[2], desc->x5, ret, desc->ret[0], + desc->ret[1], desc->ret[2]); + + if (arglen > N_REGISTER_ARGS) + kfree(desc->extra_arg_buf); + if (ret < 0) + return qcom_scm_remap_error(ret); + return ret; +} + +static int qcom_scm_set_boot_addr(void *entry, const cpumask_t *cpus, int flags) +{ + struct qcom_scm_desc desc = {0}; + unsigned int cpu = cpumask_first(cpus); + u64 mpidr_el1 = cpu_logical_map(cpu); + + /* For now we assume only a single cpu is set in the mask */ + WARN_ON(cpumask_weight(cpus) != 1); + + if (mpidr_el1 & ~MPIDR_HWID_BITMASK) { + pr_err("CPU%d:Failed to set boot address\n", cpu); + return -ENOSYS; + } + + desc.args[0] = virt_to_phys(entry); + desc.args[1] = BIT(MPIDR_AFFINITY_LEVEL(mpidr_el1, 0)); + desc.args[2] = BIT(MPIDR_AFFINITY_LEVEL(mpidr_el1, 1)); + desc.args[3] = BIT(MPIDR_AFFINITY_LEVEL(mpidr_el1, 2)); + desc.args[4] = ~0ULL; + desc.args[5] = QCOM_SCM_FLAG_HLOS | flags; + desc.arginfo = QCOM_SCM_ARGS(6); + + return qcom_scm_call(QCOM_SCM_SVC_BOOT, QCOM_SCM_BOOT_ADDR_MC, &desc); +} + +int __qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus) +{ + int flags = QCOM_SCM_FLAG_COLDBOOT_MC; + + return qcom_scm_set_boot_addr(entry, cpus, flags); +} + +int __qcom_scm_set_warm_boot_addr(void *entry, const cpumask_t *cpus) +{ + int flags = QCOM_SCM_FLAG_WARMBOOT_MC; + + return qcom_scm_set_boot_addr(entry, cpus, flags); +} + +void __qcom_scm_cpu_power_down(u32 flags) +{ + struct qcom_scm_desc desc = {0}; + desc.args[0] = QCOM_SCM_CMD_CORE_HOTPLUGGED | + (flags & QCOM_SCM_FLUSH_FLAG_MASK); + desc.arginfo = QCOM_SCM_ARGS(1); + + qcom_scm_call_atomic(QCOM_SCM_SVC_BOOT, QCOM_SCM_CMD_TERMINATE_PC, &desc); +} + +int __qcom_scm_is_call_available(u32 svc_id, u32 cmd_id) +{ + int ret; + struct qcom_scm_desc desc = {0}; + + desc.arginfo = QCOM_SCM_ARGS(1); + desc.args[0] = QCOM_SCM_SIP_FNID(svc_id, cmd_id); + + ret = qcom_scm_call(QCOM_SCM_SVC_INFO, QCOM_IS_CALL_AVAIL_CMD, &desc); + + if (ret) + return ret; + + return desc.ret[0]; +} + +int __qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp) +{ + int ret, i, j; + struct qcom_scm_desc desc = {0}; + + if (req_cnt > QCOM_SCM_HDCP_MAX_REQ_CNT) + return -ERANGE; + + for (i = 0, j = 0; i < req_cnt; i++) { + desc.args[j++] = req[i].addr; + desc.args[j++] = req[i].val; + } + desc.arginfo = QCOM_SCM_ARGS(j); + + ret = qcom_scm_call(QCOM_SCM_SVC_HDCP, QCOM_SCM_CMD_HDCP, &desc); + *resp = desc.ret[0]; + + return ret; +} +#define QCOM_SCM_SVC_INFO 0x6 +static int __init qcom_scm_init(void) +{ + int ret; + u64 ret1 = 0, x0; + + /* First try a SMC64 call */ + qcom_scm_version = QCOM_SCM_ARMV8_64; + x0 = QCOM_SCM_SIP_FNID(QCOM_SCM_SVC_INFO, IS_CALL_AVAIL_CMD) | SMC_ATOMIC_MASK; + ret = __qcom_scm_call_armv8_64(x0 | SMC64_MASK, QCOM_SCM_ARGS(1), x0, 0, 0, 0, + &ret1, NULL, NULL); + if (ret || !ret1) { + /* Try SMC32 call */ + ret1 = 0; + ret = __qcom_scm_call_armv8_32(x0, QCOM_SCM_ARGS(1), x0, 0, 0, + 0, &ret1, NULL, NULL); + if (ret || !ret1) + qcom_scm_version = QCOM_SCM_LEGACY; + else + qcom_scm_version = QCOM_SCM_ARMV8_32; + } else + qcom_scm_version_mask = SMC64_MASK; + + pr_debug("qcom_scm_call: qcom_scm version is %x, mask is %x\n", + qcom_scm_version, qcom_scm_version_mask); + + return 0; +} +early_initcall(qcom_scm_init); diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index 22c7ed63a001..a4e1685153a9 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -7,6 +7,14 @@ config DRM_I2C_ADV7511 help Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders. +config DRM_I2C_ADV7511_SLAVE_ENCODER + tristate "Use ADV7511 as an I2C slave encoder" + default y + depends on DRM_I2C_ADV7511 + help + Configure ADV7511 as an I2C slave encoder. Selecting 'n' will + configure the driver as a drm bridge driver + config DRM_I2C_CH7006 tristate "Chrontel ch7006 TV encoder" default m if DRM_NOUVEAU diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index 2c72eb584ab7..46cc9fb18367 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -1,6 +1,6 @@ ccflags-y := -Iinclude/drm -obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o +obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o adv7511_audio.o ch7006-y := ch7006_drv.o ch7006_mode.o obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o diff --git a/drivers/gpu/drm/i2c/adv7511.c b/drivers/gpu/drm/i2c/adv7511.c index 2aaa3c88999e..895d55c0c2c4 100644 --- a/drivers/gpu/drm/i2c/adv7511.c +++ b/drivers/gpu/drm/i2c/adv7511.c @@ -17,41 +17,13 @@ #include <drm/drm_crtc_helper.h> #include <drm/drm_edid.h> #include <drm/drm_encoder_slave.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <linux/of_irq.h> #include "adv7511.h" -struct adv7511 { - struct i2c_client *i2c_main; - struct i2c_client *i2c_edid; - - struct regmap *regmap; - struct regmap *packet_memory_regmap; - enum drm_connector_status status; - bool powered; - - unsigned int f_tmds; - - unsigned int current_edid_segment; - uint8_t edid_buf[256]; - bool edid_read; - - wait_queue_head_t wq; - struct drm_encoder *encoder; - - bool embedded_sync; - enum adv7511_sync_polarity vsync_polarity; - enum adv7511_sync_polarity hsync_polarity; - bool rgb; - - struct edid *edid; - - struct gpio_desc *gpio_pd; -}; - -static struct adv7511 *encoder_to_adv7511(struct drm_encoder *encoder) -{ - return to_encoder_slave(encoder)->slave_priv; -} /* ADI recommended values for proper operation. */ static const struct reg_default adv7511_fixed_registers[] = { @@ -66,6 +38,24 @@ static const struct reg_default adv7511_fixed_registers[] = { { 0x55, 0x02 }, }; +/* ADI recommended values for proper operation. */ +static const struct reg_default adv7533_fixed_registers[] = { + { 0x16, 0x20 }, + { 0x9a, 0xe0 }, + { 0xba, 0x70 }, + { 0xde, 0x82 }, + { 0xe4, 0x40 }, + { 0xe5, 0x80 }, +}; + +static const struct reg_default adv7533_cec_fixed_registers[] = { + { 0x15, 0xd0 }, + { 0x17, 0xd0 }, + { 0x24, 0x20 }, + { 0x57, 0x11 }, + { 0x05, 0xc8 }, +}; + /* ----------------------------------------------------------------------------- * Register access */ @@ -158,6 +148,15 @@ static const struct regmap_config adv7511_regmap_config = { .volatile_reg = adv7511_register_volatile, }; +static const struct regmap_config adv7533_cec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, +}; + + /* ----------------------------------------------------------------------------- * Hardware configuration */ @@ -193,7 +192,7 @@ static void adv7511_set_colormap(struct adv7511 *adv7511, bool enable, ADV7511_CSC_UPDATE_MODE, 0); } -static int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet) +int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet) { if (packet & 0xff) regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, @@ -208,7 +207,7 @@ static int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet) return 0; } -static int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet) +int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet) { if (packet & 0xff) regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, @@ -325,6 +324,17 @@ static void adv7511_set_link_config(struct adv7511 *adv7511, unsigned int color_depth; unsigned int input_id; + adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; + + /* + * TODO: some of the below configurations might be needed for ADV7533 + * too, check in ADV7533 spec. + */ + if (adv7511->type == ADV7533) { + adv7511->num_dsi_lanes = config->num_dsi_lanes; + return; + } + clock_delay = (config->clock_delay + 1200) / 400; color_depth = config->input_color_depth == 8 ? 3 : (config->input_color_depth == 10 ? 1 : 2); @@ -355,17 +365,75 @@ static void adv7511_set_link_config(struct adv7511 *adv7511, adv7511->embedded_sync = config->embedded_sync; adv7511->hsync_polarity = config->hsync_polarity; adv7511->vsync_polarity = config->vsync_polarity; - adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; +} + +static void adv7511_dsi_config_tgen(struct adv7511 *adv7511) +{ + struct drm_display_mode *mode = adv7511->curr_mode; + unsigned int hsw, hfp, hbp, vsw, vfp, vbp; + + hsw = mode->hsync_end - mode->hsync_start; + hfp = mode->hsync_start - mode->hdisplay; + hbp = mode->htotal - mode->hsync_end; + vsw = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + /* set pixel clock divider mode to auto */ + regmap_write(adv7511->regmap_cec, 0x16, 0x00); + + /* horizontal porch params */ + regmap_write(adv7511->regmap_cec, 0x28, mode->htotal >> 4); + regmap_write(adv7511->regmap_cec, 0x29, (mode->htotal << 4) & 0xff); + regmap_write(adv7511->regmap_cec, 0x2a, hsw >> 4); + regmap_write(adv7511->regmap_cec, 0x2b, (hsw << 4) & 0xff); + regmap_write(adv7511->regmap_cec, 0x2c, hfp >> 4); + regmap_write(adv7511->regmap_cec, 0x2d, (hfp << 4) & 0xff); + regmap_write(adv7511->regmap_cec, 0x2e, hbp >> 4); + regmap_write(adv7511->regmap_cec, 0x2f, (hbp << 4) & 0xff); + + /* vertical porch params */ + regmap_write(adv7511->regmap_cec, 0x30, mode->vtotal >> 4); + regmap_write(adv7511->regmap_cec, 0x31, (mode->vtotal << 4) & 0xff); + regmap_write(adv7511->regmap_cec, 0x32, vsw >> 4); + regmap_write(adv7511->regmap_cec, 0x33, (vsw << 4) & 0xff); + regmap_write(adv7511->regmap_cec, 0x34, vfp >> 4); + regmap_write(adv7511->regmap_cec, 0x35, (vfp << 4) & 0xff); + regmap_write(adv7511->regmap_cec, 0x36, vbp >> 4); + regmap_write(adv7511->regmap_cec, 0x37, (vbp << 4) & 0xff); +} + +static void adv7511_dsi_receiver_dpms(struct adv7511 *adv7511) +{ + if (adv7511->type != ADV7533) + return; + + if (adv7511->powered) { + adv7511_dsi_config_tgen(adv7511); + + /* set number of dsi lanes */ + regmap_write(adv7511->regmap_cec, 0x1c, adv7511->num_dsi_lanes << 4); + + /* reset internal timing generator */ + regmap_write(adv7511->regmap_cec, 0x27, 0xcb); + regmap_write(adv7511->regmap_cec, 0x27, 0x8b); + regmap_write(adv7511->regmap_cec, 0x27, 0xcb); + + /* enable hdmi */ + regmap_write(adv7511->regmap_cec, 0x03, 0x89); + + /* explicitly disable test mode */ + regmap_write(adv7511->regmap_cec, 0x55, 0x00); + } else { + regmap_write(adv7511->regmap_cec, 0x03, 0x0b); + regmap_write(adv7511->regmap_cec, 0x27, 0x0b); + } } static void adv7511_power_on(struct adv7511 *adv7511) { adv7511->current_edid_segment = -1; - regmap_write(adv7511->regmap, ADV7511_REG_INT(0), - ADV7511_INT0_EDID_READY); - regmap_write(adv7511->regmap, ADV7511_REG_INT(1), - ADV7511_INT1_DDC_ERROR); regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, ADV7511_POWER_POWER_DOWN, 0); @@ -386,7 +454,13 @@ static void adv7511_power_on(struct adv7511 *adv7511) */ regcache_sync(adv7511->regmap); + if (adv7511->type == ADV7533) + regmap_register_patch(adv7511->regmap_cec, + adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); adv7511->powered = true; + + adv7511_dsi_receiver_dpms(adv7511); } static void adv7511_power_off(struct adv7511 *adv7511) @@ -398,6 +472,8 @@ static void adv7511_power_off(struct adv7511 *adv7511) regcache_mark_dirty(adv7511->regmap); adv7511->powered = false; + + adv7511_dsi_receiver_dpms(adv7511); } /* ----------------------------------------------------------------------------- @@ -438,13 +514,13 @@ static int adv7511_irq_process(struct adv7511 *adv7511) regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0); regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1); - if (irq0 & ADV7511_INT0_HDP && adv7511->encoder) - drm_helper_hpd_irq_event(adv7511->encoder->dev); + //if (adv7511->encoder && (irq0 & ADV7511_INT0_HDP)) + //drm_helper_hpd_irq_event(adv7511->encoder->dev); if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { adv7511->edid_read = true; - if (adv7511->i2c_main->irq) + if (adv7511->irq) wake_up_all(&adv7511->wq); } @@ -468,7 +544,7 @@ static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout) { int ret; - if (adv7511->i2c_main->irq) { + if (adv7511->irq) { ret = wait_event_interruptible_timeout(adv7511->wq, adv7511->edid_read, msecs_to_jiffies(timeout)); } else { @@ -509,11 +585,24 @@ static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block, if (status != 2) { adv7511->edid_read = false; + + /* enable DDC interrupts */ + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), + ADV7511_INT0_EDID_READY); + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR); + regmap_write(adv7511->regmap, ADV7511_REG_EDID_SEGMENT, block); ret = adv7511_wait_for_edid(adv7511, 200); if (ret < 0) return ret; + + /* disable DDC interrupts */ + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), + 0); + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), + 0); } /* Break this apart, hopefully more I2C controllers will @@ -555,18 +644,19 @@ static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block, } /* ----------------------------------------------------------------------------- - * Encoder operations + * ADV75xx helpers */ - -static int adv7511_get_modes(struct drm_encoder *encoder, - struct drm_connector *connector) +static int adv7511_get_modes(struct adv7511 *adv7511, + struct drm_connector *connector) { - struct adv7511 *adv7511 = encoder_to_adv7511(encoder); struct edid *edid; unsigned int count; /* Reading the EDID only works if the device is powered */ if (!adv7511->powered) { + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HDP_SRC_MASK, + ADV7511_REG_POWER2_HDP_SRC_NONE); regmap_write(adv7511->regmap, ADV7511_REG_INT(0), ADV7511_INT0_EDID_READY); regmap_write(adv7511->regmap, ADV7511_REG_INT(1), @@ -596,21 +686,10 @@ static int adv7511_get_modes(struct drm_encoder *encoder, return count; } -static void adv7511_encoder_dpms(struct drm_encoder *encoder, int mode) -{ - struct adv7511 *adv7511 = encoder_to_adv7511(encoder); - - if (mode == DRM_MODE_DPMS_ON) - adv7511_power_on(adv7511); - else - adv7511_power_off(adv7511); -} - static enum drm_connector_status -adv7511_encoder_detect(struct drm_encoder *encoder, +adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector) { - struct adv7511 *adv7511 = encoder_to_adv7511(encoder); enum drm_connector_status status; unsigned int val; bool hpd; @@ -624,7 +703,7 @@ adv7511_encoder_detect(struct drm_encoder *encoder, status = connector_status_connected; else status = connector_status_disconnected; - +#if 0 hpd = adv7511_hpd(adv7511); /* The chip resets itself when the cable is disconnected, so in case @@ -634,7 +713,7 @@ adv7511_encoder_detect(struct drm_encoder *encoder, if (status == connector_status_connected && hpd && adv7511->powered) { regcache_mark_dirty(adv7511->regmap); adv7511_power_on(adv7511); - adv7511_get_modes(encoder, connector); + adv7511_get_modes(adv7511, connector); if (adv7511->status == connector_status_connected) status = connector_status_disconnected; } else { @@ -643,25 +722,16 @@ adv7511_encoder_detect(struct drm_encoder *encoder, ADV7511_REG_POWER2_HDP_SRC_MASK, ADV7511_REG_POWER2_HDP_SRC_BOTH); } - +#endif adv7511->status = status; - return status; -} - -static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, - struct drm_display_mode *mode) -{ - if (mode->clock > 165000) - return MODE_CLOCK_HIGH; - return MODE_OK; + return status; } -static void adv7511_encoder_mode_set(struct drm_encoder *encoder, +static void adv7511_mode_set(struct adv7511 *adv7511, struct drm_display_mode *mode, struct drm_display_mode *adj_mode) { - struct adv7511 *adv7511 = encoder_to_adv7511(encoder); unsigned int low_refresh_rate; unsigned int hsync_polarity = 0; unsigned int vsync_polarity = 0; @@ -744,6 +814,7 @@ static void adv7511_encoder_mode_set(struct drm_encoder *encoder, regmap_update_bits(adv7511->regmap, 0x17, 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); + adv7511->curr_mode = adj_mode; /* * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is * supposed to give better results. @@ -752,26 +823,216 @@ static void adv7511_encoder_mode_set(struct drm_encoder *encoder, adv7511->f_tmds = mode->clock; } +#ifdef CONFIG_DRM_I2C_ADV7511_SLAVE_ENCODER +/* ----------------------------------------------------------------------------- + * Encoder i2c slave functions + */ + +static struct adv7511 *encoder_to_adv7511(struct drm_encoder *encoder) +{ + return to_encoder_slave(encoder)->slave_priv; +} + +static int adv7511_encoder_get_modes(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + return adv7511_get_modes(adv7511, connector); +} + +static void adv7511_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + if (mode == DRM_MODE_DPMS_ON) + adv7511_power_on(adv7511); + else + adv7511_power_off(adv7511); +} + +static enum drm_connector_status +adv7511_encoder_detect(struct drm_encoder *encoder, + struct drm_connector *connector) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + return adv7511_detect(adv7511, connector); +} + +static int adv7511_encoder_mode_valid(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void adv7511_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct adv7511 *adv7511 = encoder_to_adv7511(encoder); + + adv7511_mode_set(adv7511, mode, adj_mode); +} + static struct drm_encoder_slave_funcs adv7511_encoder_funcs = { .dpms = adv7511_encoder_dpms, .mode_valid = adv7511_encoder_mode_valid, .mode_set = adv7511_encoder_mode_set, .detect = adv7511_encoder_detect, - .get_modes = adv7511_get_modes, + .get_modes = adv7511_encoder_get_modes, +}; +#else +/* ----------------------------------------------------------------------------- + * Bridge and connector functions + */ + +static struct adv7511 *connector_to_adv7511(struct drm_connector *connector) +{ + return container_of(connector, struct adv7511, connector); +} + +/* connector helper functions */ +static int adv7511_connector_get_modes(struct drm_connector *connector) +{ + struct adv7511 *adv7511 = connector_to_adv7511(connector); + + return adv7511_get_modes(adv7511, connector); +} + +static struct drm_encoder *adv7511_connector_best_encoder(struct drm_connector *connector) +{ + struct adv7511 *adv7511 = connector_to_adv7511(connector); + + return adv7511->bridge.encoder; +} + +static enum drm_mode_status +adv7511_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + if (mode->clock > 165000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static struct drm_connector_helper_funcs adv7511_connector_helper_funcs = { + .get_modes = adv7511_connector_get_modes, + .best_encoder = adv7511_connector_best_encoder, + .mode_valid = adv7511_connector_mode_valid, +}; + +static enum drm_connector_status +adv7511_connector_detect(struct drm_connector *connector, bool force) +{ + struct adv7511 *adv7511 = connector_to_adv7511(connector); + + return adv7511_detect(adv7511, connector); +} + +static struct drm_connector_funcs adv7511_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = adv7511_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; +/* bridge funcs */ +static struct adv7511 *bridge_to_adv7511(struct drm_bridge *bridge) +{ + return container_of(bridge, struct adv7511, bridge); +} + +static void adv7511_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_power_on(adv7511); +} + +static void adv7511_bridge_post_disable(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + if (!adv7511->powered) + return; + + adv7511_power_off(adv7511); +} + +static void adv7511_bridge_enable(struct drm_bridge *bridge) +{ +} + +static void adv7511_bridge_disable(struct drm_bridge *bridge) +{ +} + +static void adv7511_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_mode_set(adv7511, mode, adj_mode); +} + +static int adv7511_bridge_attach(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + int ret; + + adv7511->encoder = bridge->encoder; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + //adv7511->connector.polled = DRM_CONNECTOR_POLL_HPD; + ret = drm_connector_init(bridge->dev, &adv7511->connector, + &adv7511_connector_funcs, DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + drm_connector_helper_add(&adv7511->connector, + &adv7511_connector_helper_funcs); + drm_connector_register(&adv7511->connector); + drm_mode_connector_attach_encoder(&adv7511->connector, adv7511->encoder); + + //drm_helper_hpd_irq_event(adv7511->connector.dev); + + return ret; +} + +static struct drm_bridge_funcs adv7511_bridge_funcs = { + .pre_enable = adv7511_bridge_pre_enable, + .enable = adv7511_bridge_enable, + .disable = adv7511_bridge_disable, + .post_disable = adv7511_bridge_post_disable, + .attach = adv7511_bridge_attach, + .mode_set = adv7511_bridge_mode_set, +}; +#endif + /* ----------------------------------------------------------------------------- * Probe & remove */ -static int adv7511_parse_dt(struct device_node *np, +static int adv7511_parse_dt(struct adv7511 *adv7511, struct device_node *np, struct adv7511_link_config *config) { const char *str; int ret; - memset(config, 0, sizeof(*config)); - of_property_read_u32(np, "adi,input-depth", &config->input_color_depth); if (config->input_color_depth != 8 && config->input_color_depth != 10 && config->input_color_depth != 12) @@ -790,6 +1051,16 @@ static int adv7511_parse_dt(struct device_node *np, else return -EINVAL; + if (adv7511->type == ADV7533) { + of_property_read_u32(np, "adi,dsi-lanes", + &config->num_dsi_lanes); + + if (config->num_dsi_lanes < 1 || config->num_dsi_lanes > 4) + return -EINVAL; + + return 0; + } + ret = of_property_read_string(np, "adi,input-clock", &str); if (ret < 0) return ret; @@ -852,7 +1123,18 @@ static int adv7511_parse_dt(struct device_node *np, static const int edid_i2c_addr = 0x7e; static const int packet_i2c_addr = 0x70; static const int cec_i2c_addr = 0x78; +static const int main_i2c_addr = 0x72; + +static const struct of_device_id adv7511_of_ids[] = { + { .compatible = "adi,adv7511", .data = (void *)ADV7511 }, + { .compatible = "adi,adv7511w", .data = (void *)ADV7511 }, + { .compatible = "adi,adv7513", .data= (void *)ADV7511 }, + { .compatible = "adi,adv7533", .data = (void *)ADV7533 }, + { } +}; +MODULE_DEVICE_TABLE(of, adv7511_of_ids); +#ifdef CONFIG_DRM_I2C_ADV7511_SLAVE_ENCODER static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct adv7511_link_config link_config; @@ -871,7 +1153,17 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) adv7511->powered = false; adv7511->status = connector_status_disconnected; - ret = adv7511_parse_dt(dev->of_node, &link_config); + if (dev->of_node) { + const struct of_device_id *of_id; + of_id = of_match_node(adv7511_of_ids, dev->of_node); + adv7511->type = (unsigned long)of_id->data; + } else { + adv7511->type = id->driver_data; + } + + memset(&link_config, 0, sizeof(link_config)); + + ret = adv7511_parse_dt(adv7511, dev->of_node, &link_config); if (ret) return ret; @@ -897,10 +1189,17 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) return ret; dev_dbg(dev, "Rev. %d\n", val); - ret = regmap_register_patch(adv7511->regmap, adv7511_fixed_registers, + if (adv7511->type == ADV7511) { + ret = regmap_register_patch(adv7511->regmap, adv7511_fixed_registers, ARRAY_SIZE(adv7511_fixed_registers)); - if (ret) - return ret; + if (ret) + return ret; + } else { + ret = regmap_register_patch(adv7511->regmap, adv7533_fixed_registers, + ARRAY_SIZE(adv7533_fixed_registers)); + if (ret) + return ret; + } regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, edid_i2c_addr); regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR, @@ -913,6 +1212,26 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) if (!adv7511->i2c_edid) return -ENOMEM; + adv7511->i2c_cec = i2c_new_dummy(i2c->adapter, cec_i2c_addr >> 1); + if (!adv7511->i2c_cec) { + ret = -ENOMEM; + goto err_i2c_unregister_edid; + } + + adv7511->regmap_cec = devm_regmap_init_i2c(adv7511->i2c_cec, + &adv7533_cec_regmap_config); + if (IS_ERR(adv7511->regmap_cec)) { + ret = PTR_ERR(adv7511->regmap_cec); + goto err_i2c_unregister_cec; + } + + if (adv7511->type == ADV7533) { + ret = regmap_register_patch(adv7511->regmap_cec, adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); + if (ret) + return ret; + } + if (i2c->irq) { init_waitqueue_head(&adv7511->wq); @@ -921,7 +1240,9 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) IRQF_ONESHOT, dev_name(dev), adv7511); if (ret) - goto err_i2c_unregister_device; + goto err_i2c_unregister_cec; + + adv7511->irq = i2c->irq; } /* CEC is unused for now */ @@ -932,11 +1253,15 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) i2c_set_clientdata(i2c, adv7511); + adv7511_audio_init(dev); + adv7511_set_link_config(adv7511, &link_config); return 0; -err_i2c_unregister_device: +err_i2c_unregister_cec: + i2c_unregister_device(adv7511->i2c_cec); +err_i2c_unregister_edid: i2c_unregister_device(adv7511->i2c_edid); return ret; @@ -946,6 +1271,8 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + adv7511_audio_exit(&i2c->dev); + i2c_unregister_device(adv7511->i2c_cec); i2c_unregister_device(adv7511->i2c_edid); kfree(adv7511->edid); @@ -953,6 +1280,15 @@ static int adv7511_remove(struct i2c_client *i2c) return 0; } +static const struct i2c_device_id adv7511_i2c_ids[] = { + { "adv7511", ADV7511 }, + { "adv7511w", ADV7511 }, + { "adv7513", ADV7511 }, + { "adv7533", ADV7533 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); + static int adv7511_encoder_init(struct i2c_client *i2c, struct drm_device *dev, struct drm_encoder_slave *encoder) { @@ -967,22 +1303,6 @@ static int adv7511_encoder_init(struct i2c_client *i2c, struct drm_device *dev, return 0; } -static const struct i2c_device_id adv7511_i2c_ids[] = { - { "adv7511", 0 }, - { "adv7511w", 0 }, - { "adv7513", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); - -static const struct of_device_id adv7511_of_ids[] = { - { .compatible = "adi,adv7511", }, - { .compatible = "adi,adv7511w", }, - { .compatible = "adi,adv7513", }, - { } -}; -MODULE_DEVICE_TABLE(of, adv7511_of_ids); - static struct drm_i2c_encoder_driver adv7511_driver = { .i2c_driver = { .driver = { @@ -1008,6 +1328,193 @@ static void __exit adv7511_exit(void) drm_i2c_encoder_unregister(&adv7511_driver); } module_exit(adv7511_exit); +#else + +static int adv7533_probe(struct mipi_dsi_device *dsi) +{ + struct device *dev = &dsi->dev; + struct adv7511 *adv; + struct adv7511_link_config link_config; + struct i2c_adapter *adapter; + struct device_node *adapter_node; + unsigned int val; + int irq; + int ret; + + adv = devm_kzalloc(dev, sizeof(struct adv7511), GFP_KERNEL); + if (!adv) + return -ENOMEM; + + mipi_dsi_set_drvdata(dsi, adv); + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE + | MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE; + + adv->type = ADV7533; + adv->powered = false; + adv->status = connector_status_disconnected; + + memset(&link_config, 0, sizeof(link_config)); + + ret = adv7511_parse_dt(adv, dev->of_node, &link_config); + if (ret) + return ret; + + adv->gpio_pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH); + if (IS_ERR(adv->gpio_pd)) + return PTR_ERR(adv->gpio_pd); + + if (adv->gpio_pd) { + mdelay(5); + gpiod_set_value_cansleep(adv->gpio_pd, 0); + } + + adapter_node = of_parse_phandle(dev->of_node, "i2c-bus", 0); + if (adapter_node) { + adapter = of_find_i2c_adapter_by_node(adapter_node); + if (adapter == NULL) + return -ENODEV; + } else { + return -ENODEV; + } + + adv->i2c_main = i2c_new_dummy(adapter, main_i2c_addr >> 1); + if (!adv->i2c_main) + return -ENOMEM; + + adv->regmap = devm_regmap_init_i2c(adv->i2c_main, &adv7511_regmap_config); + if (IS_ERR(adv->regmap)) + return PTR_ERR(adv->regmap); + + ret = regmap_read(adv->regmap, ADV7511_REG_CHIP_REVISION, &val); + if (ret) + return ret; + + dev_dbg(dev, "Rev. %d\n", val); + + ret = regmap_register_patch(adv->regmap, adv7533_fixed_registers, + ARRAY_SIZE(adv7533_fixed_registers)); + if (ret) + return ret; + + regmap_write(adv->regmap, ADV7511_REG_EDID_I2C_ADDR, edid_i2c_addr); + regmap_write(adv->regmap, ADV7511_REG_PACKET_I2C_ADDR, + packet_i2c_addr); + regmap_write(adv->regmap, ADV7511_REG_CEC_I2C_ADDR, cec_i2c_addr); + adv7511_packet_disable(adv, 0xffff); + + adv->i2c_edid = i2c_new_dummy(adapter, edid_i2c_addr >> 1); + if (!adv->i2c_edid) { + ret = -ENOMEM; + goto err_i2c_unregister_main; + } + + adv->i2c_cec = i2c_new_dummy(adapter, cec_i2c_addr >> 1); + if (!adv->i2c_cec) { + ret = -ENOMEM; + goto err_i2c_unregister_edid; + } + + adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, + &adv7533_cec_regmap_config); + if (IS_ERR(adv->regmap_cec)) { + ret = PTR_ERR(adv->regmap_cec); + goto err_i2c_unregister_cec; + } + + ret = regmap_register_patch(adv->regmap_cec, + adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); + if (ret) { + goto err_i2c_unregister_cec; + } + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (irq) { + init_waitqueue_head(&adv->wq); + + ret = devm_request_threaded_irq(dev, irq, NULL, + adv7511_irq_handler, + IRQF_ONESHOT, dev_name(dev), + adv); + if (ret) + goto err_i2c_unregister_cec; + + adv->irq = irq; + } + + /* CEC is unused for now */ + regmap_write(adv->regmap, ADV7511_REG_CEC_CTRL, + ADV7511_CEC_CTRL_POWER_DOWN); + + adv7511_power_off(adv); + + adv7511_audio_init(dev); + + adv7511_set_link_config(adv, &link_config); + + adv->bridge.funcs = &adv7511_bridge_funcs; + adv->bridge.of_node = dev->of_node; + + ret = drm_bridge_add(&adv->bridge); + if (ret) { + DRM_ERROR("Failed to add adv7533 bridge\n"); + return ret; + } + + ret = mipi_dsi_attach(dsi); + if (ret < 0) + goto err_bridge_remove; + + return 0; + +err_bridge_remove: + drm_bridge_remove(&adv->bridge); +err_i2c_unregister_cec: + i2c_unregister_device(adv->i2c_cec); +err_i2c_unregister_edid: + i2c_unregister_device(adv->i2c_edid); +err_i2c_unregister_main: + i2c_unregister_device(adv->i2c_main); + + return ret; +} + +static int adv7533_remove(struct mipi_dsi_device *dsi) +{ + struct adv7511 *adv = mipi_dsi_get_drvdata(dsi); + + adv7511_audio_exit(&dsi->dev); + + i2c_unregister_device(adv->i2c_main); + i2c_unregister_device(adv->i2c_cec); + i2c_unregister_device(adv->i2c_edid); + + mipi_dsi_detach(dsi); + drm_bridge_remove(&adv->bridge); + kfree(adv->edid); + + return 0; +} + +static struct of_device_id adv7533_of_match[] = { + { .compatible = "adi,adv7533" }, + { } +}; +MODULE_DEVICE_TABLE(of, adv7533_of_match); + +static struct mipi_dsi_driver adv7533_driver = { + .probe = adv7533_probe, + .remove = adv7533_remove, + .driver = { + .name = "adv7533", + .of_match_table = adv7533_of_match, + }, +}; +module_mipi_dsi_driver(adv7533_driver); +#endif MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); diff --git a/drivers/gpu/drm/i2c/adv7511.h b/drivers/gpu/drm/i2c/adv7511.h index 6599ed538426..26c9045f6b46 100644 --- a/drivers/gpu/drm/i2c/adv7511.h +++ b/drivers/gpu/drm/i2c/adv7511.h @@ -10,6 +10,16 @@ #define __DRM_I2C_ADV7511_H__ #include <linux/hdmi.h> +#include <drm/drm_crtc_helper.h> + +struct regmap; +struct adv7511; + +int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet); +int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet); + +int adv7511_audio_init(struct device *dev); +void adv7511_audio_exit(struct device *dev); #define ADV7511_REG_CHIP_REVISION 0x00 #define ADV7511_REG_N0 0x01 @@ -229,6 +239,54 @@ enum adv7511_sync_polarity { ADV7511_SYNC_POLARITY_HIGH, }; +enum adv7511_type { + ADV7511, + ADV7533, +}; + +struct adv7511 { + struct i2c_client *i2c_main; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_cec; + + int irq; + + struct regmap *regmap; + struct regmap *regmap_cec; + enum drm_connector_status status; + bool powered; + + struct drm_display_mode *curr_mode; + + unsigned int f_tmds; + unsigned int f_audio; + unsigned int audio_source; + + unsigned int current_edid_segment; + uint8_t edid_buf[256]; + bool edid_read; + struct drm_encoder *encoder; + + wait_queue_head_t wq; + +#ifndef CONFIG_DRM_I2C_ADV7511_SLAVE_ENCODER + struct drm_connector connector; + struct drm_bridge bridge; +#endif + + bool embedded_sync; + enum adv7511_sync_polarity vsync_polarity; + enum adv7511_sync_polarity hsync_polarity; + bool rgb; + u8 num_dsi_lanes; + + struct edid *edid; + + struct gpio_desc *gpio_pd; + + enum adv7511_type type; +}; + /** * struct adv7511_link_config - Describes adv7511 hardware configuration * @input_color_depth: Number of bits per color component (8, 10 or 12) @@ -241,6 +299,7 @@ enum adv7511_sync_polarity { * @sync_pulse: Select the sync pulse * @vsync_polarity: vsync input signal configuration * @hsync_polarity: hsync input signal configuration + * @num_dsi_lanes: number of dsi lanes in use(for ADV7533) */ struct adv7511_link_config { unsigned int input_color_depth; @@ -255,6 +314,8 @@ struct adv7511_link_config { enum adv7511_input_sync_pulse sync_pulse; enum adv7511_sync_polarity vsync_polarity; enum adv7511_sync_polarity hsync_polarity; + + unsigned int num_dsi_lanes; }; /** diff --git a/drivers/gpu/drm/i2c/adv7511_audio.c b/drivers/gpu/drm/i2c/adv7511_audio.c new file mode 100644 index 000000000000..41734038625c --- /dev/null +++ b/drivers/gpu/drm/i2c/adv7511_audio.c @@ -0,0 +1,311 @@ +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + * + * Licensed under the GPL-2. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "adv7511.h" + +static const struct snd_soc_dapm_widget adv7511_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("TMDS"), + SND_SOC_DAPM_AIF_IN("AIFIN", "Playback", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route adv7511_routes[] = { + { "TMDS", NULL, "AIFIN" }, +}; + +static void adv7511_calc_cts_n(unsigned int f_tmds, unsigned int fs, + unsigned int *cts, unsigned int *n) +{ + switch (fs) { + case 32000: + *n = 4096; + break; + case 44100: + *n = 6272; + break; + case 48000: + *n = 6144; + break; + } + + *cts = ((f_tmds * *n) / (128 * fs)) * 1000; +} + +static int adv7511_update_cts_n(struct adv7511 *adv7511) +{ + unsigned int cts = 0; + unsigned int n = 0; + + adv7511_calc_cts_n(adv7511->f_tmds, adv7511->f_audio, &cts, &n); + + regmap_write(adv7511->regmap, ADV7511_REG_N0, (n >> 16) & 0xf); + regmap_write(adv7511->regmap, ADV7511_REG_N1, (n >> 8) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_N2, n & 0xff); + + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL0, + (cts >> 16) & 0xf); + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL1, + (cts >> 8) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL2, + cts & 0xff); + + return 0; +} + +static int adv7511_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct adv7511 *adv7511 = snd_soc_codec_get_drvdata(codec); + unsigned int rate; + unsigned int len; + switch (params_rate(params)) { + case 32000: + rate = ADV7511_SAMPLE_FREQ_32000; + break; + case 44100: + rate = ADV7511_SAMPLE_FREQ_44100; + break; + case 48000: + rate = ADV7511_SAMPLE_FREQ_48000; + break; + case 88200: + rate = ADV7511_SAMPLE_FREQ_88200; + break; + case 96000: + rate = ADV7511_SAMPLE_FREQ_96000; + break; + case 176400: + rate = ADV7511_SAMPLE_FREQ_176400; + break; + case 192000: + rate = ADV7511_SAMPLE_FREQ_192000; + break; + default: + return -EINVAL; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + len = ADV7511_I2S_SAMPLE_LEN_16; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + len = ADV7511_I2S_SAMPLE_LEN_18; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + len = ADV7511_I2S_SAMPLE_LEN_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + len = ADV7511_I2S_SAMPLE_LEN_24; + break; + default: + return -EINVAL; + } + + adv7511->f_audio = params_rate(params); + + adv7511_update_cts_n(adv7511); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CFG3, + ADV7511_AUDIO_CFG3_LEN_MASK, len); + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, + ADV7511_I2C_FREQ_ID_CFG_RATE_MASK, rate << 4); + regmap_write(adv7511->regmap, 0x73, 0x1); + + return 0; +} + +static int adv7511_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct adv7511 *adv7511 = snd_soc_codec_get_drvdata(codec); + unsigned int audio_source, i2s_format = 0; + unsigned int invert_clock; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_LEFT_J; + break; +// case SND_SOC_DAIFMT_SPDIF: +// audio_source = ADV7511_AUDIO_SOURCE_SPDIF; +// break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_clock = 0; + break; + case SND_SOC_DAIFMT_IB_NF: + invert_clock = 1; + break; + default: + return -EINVAL; + } + + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_SOURCE, 0x70, + audio_source << 4); + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, BIT(6), + invert_clock << 6); + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2S_CONFIG, 0x03, + i2s_format); + + adv7511->audio_source = audio_source; + + return 0; +} + +static int adv7511_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct adv7511 *adv7511 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + switch (adv7511->audio_source) { + case ADV7511_AUDIO_SOURCE_I2S: + break; + case ADV7511_AUDIO_SOURCE_SPDIF: + regmap_update_bits(adv7511->regmap, + ADV7511_REG_AUDIO_CONFIG, BIT(7), + BIT(7)); + break; + } + break; + case SND_SOC_BIAS_PREPARE: + if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) { + adv7511_packet_enable(adv7511, + ADV7511_PACKET_ENABLE_AUDIO_SAMPLE); + adv7511_packet_enable(adv7511, + ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME); + adv7511_packet_enable(adv7511, + ADV7511_PACKET_ENABLE_N_CTS); + } else { + adv7511_packet_disable(adv7511, + ADV7511_PACKET_ENABLE_AUDIO_SAMPLE); + adv7511_packet_disable(adv7511, + ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME); + adv7511_packet_disable(adv7511, + ADV7511_PACKET_ENABLE_N_CTS); + } + break; + case SND_SOC_BIAS_STANDBY: + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), 0); + break; + case SND_SOC_BIAS_OFF: + break; + } + codec->dapm.bias_level = level; + return 0; +} + +#define ADV7511_RATES (SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |\ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) + +#define ADV7511_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE |\ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops adv7511_dai_ops = { + .hw_params = adv7511_hw_params, + /*.set_sysclk = adv7511_set_dai_sysclk,*/ + .set_fmt = adv7511_set_dai_fmt, +}; + +static struct snd_soc_dai_driver adv7511_dai = { + .name = "adv7511", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ADV7511_RATES, + .formats = ADV7511_FORMATS, + }, + .ops = &adv7511_dai_ops, +}; + +static int adv7511_suspend(struct snd_soc_codec *codec) +{ + return adv7511_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int adv7511_resume(struct snd_soc_codec *codec) +{ + return adv7511_set_bias_level(codec, SND_SOC_BIAS_STANDBY); +} + +static int adv7511_probe(struct snd_soc_codec *codec) +{ + return adv7511_set_bias_level(codec, SND_SOC_BIAS_STANDBY); +} + +static int adv7511_remove(struct snd_soc_codec *codec) +{ + adv7511_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static struct snd_soc_codec_driver adv7511_codec_driver = { + .probe = adv7511_probe, + .remove = adv7511_remove, + .suspend = adv7511_suspend, + .resume = adv7511_resume, + .set_bias_level = adv7511_set_bias_level, + + .dapm_widgets = adv7511_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adv7511_dapm_widgets), + .dapm_routes = adv7511_routes, + .num_dapm_routes = ARRAY_SIZE(adv7511_routes), +}; + +int adv7511_audio_init(struct device *dev) +{ + return snd_soc_register_codec(dev, &adv7511_codec_driver, + &adv7511_dai, 1); +} + +void adv7511_audio_exit(struct device *dev) +{ + snd_soc_unregister_codec(dev); +} diff --git a/drivers/gpu/drm/msm/dsi/dsi.c b/drivers/gpu/drm/msm/dsi/dsi.c index 1f2561e2ff71..8de8d5722762 100644 --- a/drivers/gpu/drm/msm/dsi/dsi.c +++ b/drivers/gpu/drm/msm/dsi/dsi.c @@ -223,12 +223,38 @@ int msm_dsi_modeset_init(struct msm_dsi *msm_dsi, struct drm_device *dev, msm_dsi->encoders[i] = encoders[i]; } - msm_dsi->connector = msm_dsi_manager_connector_init(msm_dsi->id); - if (IS_ERR(msm_dsi->connector)) { - ret = PTR_ERR(msm_dsi->connector); - dev_err(dev->dev, "failed to create dsi connector: %d\n", ret); - msm_dsi->connector = NULL; - goto fail; + msm_dsi->ext_bridge = msm_dsi_host_get_ext_bridge(msm_dsi->host); + + /* + * skip making connector if we have a bridge. the bridge driver will take + * care of creating a connector + */ + if (msm_dsi->ext_bridge) { + struct drm_encoder *encoder = encoders[MSM_DSI_VIDEO_ENCODER_ID]; + struct drm_connector *connector; + struct drm_bridge *int_bridge = msm_dsi->bridge; + struct drm_bridge *ext_bridge = msm_dsi->ext_bridge; + + int_bridge->next = ext_bridge; + ext_bridge->encoder = encoder; + + drm_bridge_attach(msm_dsi->dev, ext_bridge); + + /* TODO: don't just pick up the first connector, pick the one the bridge + * driver just created + */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector) + msm_dsi->connector = connector; + } + } else { + msm_dsi->connector = msm_dsi_manager_connector_init(msm_dsi->id); + if (IS_ERR(msm_dsi->connector)) { + ret = PTR_ERR(msm_dsi->connector); + dev_err(dev->dev, "failed to create dsi connector: %d\n", ret); + msm_dsi->connector = NULL; + goto fail; + } } priv->bridges[priv->num_bridges++] = msm_dsi->bridge; diff --git a/drivers/gpu/drm/msm/dsi/dsi.h b/drivers/gpu/drm/msm/dsi/dsi.h index 92d697de4858..fbfbc31a2d7a 100644 --- a/drivers/gpu/drm/msm/dsi/dsi.h +++ b/drivers/gpu/drm/msm/dsi/dsi.h @@ -27,18 +27,6 @@ #define DSI_1 1 #define DSI_MAX 2 -#define DSI_CLOCK_MASTER DSI_0 -#define DSI_CLOCK_SLAVE DSI_1 - -#define DSI_LEFT DSI_0 -#define DSI_RIGHT DSI_1 - -/* According to the current drm framework sequence, take the encoder of - * DSI_1 as master encoder - */ -#define DSI_ENCODER_MASTER DSI_1 -#define DSI_ENCODER_SLAVE DSI_0 - enum msm_dsi_phy_type { MSM_DSI_PHY_28NM_HPM, MSM_DSI_PHY_28NM_LP, @@ -66,6 +54,7 @@ struct msm_dsi { struct platform_device *pdev; struct drm_connector *connector; + /* internal bridge to link to mdp5 encoder */ struct drm_bridge *bridge; struct mipi_dsi_host *host; @@ -73,7 +62,11 @@ struct msm_dsi { struct drm_panel *panel; unsigned long panel_flags; + /* dsi host is connected to a bridge/encoder chip */ + struct drm_bridge *ext_bridge; + struct device *phy_dev; + bool phy_enabled; /* the encoders we are hooked to (outside of dsi block) */ @@ -140,6 +133,7 @@ int msm_dsi_host_set_display_mode(struct mipi_dsi_host *host, struct drm_display_mode *mode); struct drm_panel *msm_dsi_host_get_panel(struct mipi_dsi_host *host, unsigned long *panel_flags); +struct drm_bridge *msm_dsi_host_get_ext_bridge(struct mipi_dsi_host *host); int msm_dsi_host_register(struct mipi_dsi_host *host, bool check_defer); void msm_dsi_host_unregister(struct mipi_dsi_host *host); int msm_dsi_host_set_src_pll(struct mipi_dsi_host *host, @@ -153,7 +147,7 @@ int msm_dsi_host_init(struct msm_dsi *msm_dsi); struct msm_dsi_phy; void msm_dsi_phy_driver_register(void); void msm_dsi_phy_driver_unregister(void); -int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, +int msm_dsi_phy_enable(struct msm_dsi_phy *phy, int src_pll_id, const unsigned long bit_rate, const unsigned long esc_rate); int msm_dsi_phy_disable(struct msm_dsi_phy *phy); void msm_dsi_phy_get_clk_pre_post(struct msm_dsi_phy *phy, diff --git a/drivers/gpu/drm/msm/dsi/dsi_host.c b/drivers/gpu/drm/msm/dsi/dsi_host.c index de0400923303..932efabeef2e 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_host.c +++ b/drivers/gpu/drm/msm/dsi/dsi_host.c @@ -219,6 +219,9 @@ struct msm_dsi_host { enum mipi_dsi_pixel_format format; unsigned long mode_flags; + /* external bridge info */ + struct device_node *ext_bridge_node; + u32 dma_cmd_ctrl_restore; bool registered; @@ -1372,6 +1375,7 @@ static int dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi) { struct msm_dsi_host *msm_host = to_msm_dsi_host(host); + struct device_node *bridge, *np = msm_host->pdev->dev.of_node; int ret; msm_host->channel = dsi->channel; @@ -1379,6 +1383,13 @@ static int dsi_host_attach(struct mipi_dsi_host *host, msm_host->format = dsi->format; msm_host->mode_flags = dsi->mode_flags; + /* if we have an external bridge, don't populate panel data */ + bridge = of_get_child_by_name(np, "bridge"); + if (bridge) { + of_node_put(bridge); + return 0; + } + msm_host->panel_node = dsi->dev.of_node; /* Some gpios defined in panel DT need to be controlled by host */ @@ -1579,11 +1590,26 @@ int msm_dsi_host_register(struct mipi_dsi_host *host, bool check_defer) * create framebuffer. */ if (check_defer) { + /* + * first look for a child panel node, if a panel node + * exists but the corresponding panel device isn't + * probed, defer msm dsi's probe + * + * if a child panel isn't specified at all, try to look + * for a bridge phandle, defer probe if it there is a + * bridge phandle but the bridge device isn't added yet + */ node = of_get_child_by_name(msm_host->pdev->dev.of_node, "panel"); if (node) { if (!of_drm_find_panel(node)) return -EPROBE_DEFER; + } else { + struct drm_bridge *bridge; + + bridge = msm_dsi_host_get_ext_bridge(host); + if (!bridge) + return -EPROBE_DEFER; } } } @@ -1882,7 +1908,6 @@ int msm_dsi_host_power_on(struct mipi_dsi_host *host) DBG("dsi host already on"); goto unlock_ret; } - ret = dsi_calc_clk_rate(msm_host); if (ret) { pr_err("%s: unable to calc clk rate, %d\n", __func__, ret); @@ -1905,7 +1930,7 @@ int msm_dsi_host_power_on(struct mipi_dsi_host *host) dsi_phy_sw_reset(msm_host); ret = msm_dsi_manager_phy_enable(msm_host->id, msm_host->byte_clk_rate * 8, - clk_get_rate(msm_host->esc_clk), + 19200000,/*clk_get_rate(msm_host->esc_clk),*/ &clk_pre, &clk_post); dsi_bus_clk_disable(msm_host); if (ret) { @@ -2000,3 +2025,21 @@ struct drm_panel *msm_dsi_host_get_panel(struct mipi_dsi_host *host, return panel; } +struct drm_bridge *msm_dsi_host_get_ext_bridge(struct mipi_dsi_host *host) +{ + struct msm_dsi_host *msm_host = to_msm_dsi_host(host); + struct drm_bridge *bridge; + struct device_node *np = msm_host->pdev->dev.of_node; + + msm_host->ext_bridge_node = of_get_child_by_name(np, "bridge"); + if (!msm_host->ext_bridge_node) { + dev_err(&msm_host->pdev->dev, "bridge not found\n"); + return NULL; + } + + bridge = of_drm_find_bridge(msm_host->ext_bridge_node); + + of_node_put(msm_host->ext_bridge_node); + + return bridge; +} diff --git a/drivers/gpu/drm/msm/dsi/dsi_manager.c b/drivers/gpu/drm/msm/dsi/dsi_manager.c index 87ac6612b6f8..829ed76ae3b7 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_manager.c +++ b/drivers/gpu/drm/msm/dsi/dsi_manager.c @@ -14,19 +14,31 @@ #include "msm_kms.h" #include "dsi.h" +#define DSI_CLOCK_MASTER DSI_0 +#define DSI_CLOCK_SLAVE DSI_1 + +#define DSI_LEFT DSI_0 +#define DSI_RIGHT DSI_1 + +/* According to the current drm framework sequence, take the encoder of + * DSI_1 as master encoder + */ +#define DSI_ENCODER_MASTER DSI_1 +#define DSI_ENCODER_SLAVE DSI_0 + struct msm_dsi_manager { struct msm_dsi *dsi[DSI_MAX]; - bool is_dual_panel; + bool is_dual_dsi; bool is_sync_needed; - int master_panel_id; + int master_dsi_link_id; }; static struct msm_dsi_manager msm_dsim_glb; -#define IS_DUAL_PANEL() (msm_dsim_glb.is_dual_panel) +#define IS_DUAL_DSI() (msm_dsim_glb.is_dual_dsi) #define IS_SYNC_NEEDED() (msm_dsim_glb.is_sync_needed) -#define IS_MASTER_PANEL(id) (msm_dsim_glb.master_panel_id == id) +#define IS_MASTER_DSI_LINK(id) (msm_dsim_glb.master_dsi_link_id == id) static inline struct msm_dsi *dsi_mgr_get_dsi(int id) { @@ -38,23 +50,23 @@ static inline struct msm_dsi *dsi_mgr_get_other_dsi(int id) return msm_dsim_glb.dsi[(id + 1) % DSI_MAX]; } -static int dsi_mgr_parse_dual_panel(struct device_node *np, int id) +static int dsi_mgr_parse_dual_dsi(struct device_node *np, int id) { struct msm_dsi_manager *msm_dsim = &msm_dsim_glb; - /* We assume 2 dsi nodes have the same information of dual-panel and + /* We assume 2 dsi nodes have the same information of dual-dsi and * sync-mode, and only one node specifies master in case of dual mode. */ - if (!msm_dsim->is_dual_panel) - msm_dsim->is_dual_panel = of_property_read_bool( - np, "qcom,dual-panel-mode"); + if (!msm_dsim->is_dual_dsi) + msm_dsim->is_dual_dsi = of_property_read_bool( + np, "qcom,dual-dsi-mode"); - if (msm_dsim->is_dual_panel) { - if (of_property_read_bool(np, "qcom,master-panel")) - msm_dsim->master_panel_id = id; + if (msm_dsim->is_dual_dsi) { + if (of_property_read_bool(np, "qcom,master-dsi")) + msm_dsim->master_dsi_link_id = id; if (!msm_dsim->is_sync_needed) msm_dsim->is_sync_needed = of_property_read_bool( - np, "qcom,sync-dual-panel"); + np, "qcom,sync-dual-dsi"); } return 0; @@ -68,7 +80,7 @@ static int dsi_mgr_host_register(int id) struct msm_dsi_pll *src_pll; int ret; - if (!IS_DUAL_PANEL()) { + if (!IS_DUAL_DSI()) { ret = msm_dsi_host_register(msm_dsi->host, true); if (ret) return ret; @@ -78,9 +90,9 @@ static int dsi_mgr_host_register(int id) } else if (!other_dsi) { ret = 0; } else { - struct msm_dsi *mdsi = IS_MASTER_PANEL(id) ? + struct msm_dsi *mdsi = IS_MASTER_DSI_LINK(id) ? msm_dsi : other_dsi; - struct msm_dsi *sdsi = IS_MASTER_PANEL(id) ? + struct msm_dsi *sdsi = IS_MASTER_DSI_LINK(id) ? other_dsi : msm_dsi; /* Register slave host first, so that slave DSI device * has a chance to probe, and do not block the master @@ -147,23 +159,23 @@ static enum drm_connector_status dsi_mgr_connector_detect( &msm_dsi->panel_flags); /* There is only 1 panel in the global panel list - * for dual panel mode. Therefore slave dsi should get + * for dual DSI mode. Therefore slave dsi should get * the drm_panel instance from master dsi, and * keep using the panel flags got from the current DSI link. */ - if (!msm_dsi->panel && IS_DUAL_PANEL() && - !IS_MASTER_PANEL(id) && other_dsi) + if (!msm_dsi->panel && IS_DUAL_DSI() && + !IS_MASTER_DSI_LINK(id) && other_dsi) msm_dsi->panel = msm_dsi_host_get_panel( other_dsi->host, NULL); - if (msm_dsi->panel && IS_DUAL_PANEL()) + if (msm_dsi->panel && IS_DUAL_DSI()) drm_object_attach_property(&connector->base, connector->dev->mode_config.tile_property, 0); - /* Set split display info to kms once dual panel is connected - * to both hosts + /* Set split display info to kms once dual DSI panel is + * connected to both hosts. */ - if (msm_dsi->panel && IS_DUAL_PANEL() && + if (msm_dsi->panel && IS_DUAL_DSI() && other_dsi && other_dsi->panel) { bool cmd_mode = !(msm_dsi->panel_flags & MIPI_DSI_MODE_VIDEO); @@ -176,7 +188,7 @@ static enum drm_connector_status dsi_mgr_connector_detect( kms->funcs->set_split_display(kms, encoder, slave_enc, cmd_mode); else - pr_err("mdp does not support dual panel\n"); + pr_err("mdp does not support dual DSI\n"); } } @@ -273,7 +285,7 @@ static int dsi_mgr_connector_get_modes(struct drm_connector *connector) if (!num) return 0; - if (IS_DUAL_PANEL()) { + if (IS_DUAL_DSI()) { /* report half resolution to user */ dsi_dual_connector_fix_modes(connector); ret = dsi_dual_connector_tile_init(connector, id); @@ -321,18 +333,43 @@ dsi_mgr_connector_best_encoder(struct drm_connector *connector) return msm_dsi_get_encoder(msm_dsi); } -static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge) +static void pre_enable_bridge(struct drm_bridge *bridge) +{ + int id = dsi_mgr_bridge_get_id(bridge); + struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); + struct mipi_dsi_host *host = msm_dsi->host; + struct drm_bridge *ext_bridge = msm_dsi->ext_bridge; + int ret; + + if (!ext_bridge) + return; + + ret = msm_dsi_host_power_on(host); + if (ret) { + pr_err("%s: power on host %d failed, %d\n", __func__, id, ret); + return; + } + + ret = msm_dsi_host_enable(host); + if (ret) { + pr_err("%s: enable host %d failed, %d\n", __func__, id, ret); + msm_dsi_host_power_off(host); + } +} + +static void pre_enable_panel(struct drm_bridge *bridge) { int id = dsi_mgr_bridge_get_id(bridge); struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); struct msm_dsi *msm_dsi1 = dsi_mgr_get_dsi(DSI_1); struct mipi_dsi_host *host = msm_dsi->host; struct drm_panel *panel = msm_dsi->panel; - bool is_dual_panel = IS_DUAL_PANEL(); + bool is_dual_dsi = IS_DUAL_DSI(); int ret; DBG("id=%d", id); - if (!panel || (is_dual_panel && (DSI_1 == id))) + + if (!panel || (is_dual_dsi && (DSI_1 == id))) return; ret = msm_dsi_host_power_on(host); @@ -341,7 +378,7 @@ static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge) goto host_on_fail; } - if (is_dual_panel && msm_dsi1) { + if (is_dual_dsi && msm_dsi1) { ret = msm_dsi_host_power_on(msm_dsi1->host); if (ret) { pr_err("%s: power on host1 failed, %d\n", @@ -365,7 +402,7 @@ static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge) goto host_en_fail; } - if (is_dual_panel && msm_dsi1) { + if (is_dual_dsi && msm_dsi1) { ret = msm_dsi_host_enable(msm_dsi1->host); if (ret) { pr_err("%s: enable host1 failed, %d\n", __func__, ret); @@ -382,14 +419,14 @@ static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge) return; panel_en_fail: - if (is_dual_panel && msm_dsi1) + if (is_dual_dsi && msm_dsi1) msm_dsi_host_disable(msm_dsi1->host); host1_en_fail: msm_dsi_host_disable(host); host_en_fail: drm_panel_unprepare(panel); panel_prep_fail: - if (is_dual_panel && msm_dsi1) + if (is_dual_dsi && msm_dsi1) msm_dsi_host_power_off(msm_dsi1->host); host1_on_fail: msm_dsi_host_power_off(host); @@ -397,29 +434,39 @@ host_on_fail: return; } -static void dsi_mgr_bridge_enable(struct drm_bridge *bridge) +static void post_disable_bridge(struct drm_bridge *bridge) { - DBG(""); -} + int id = dsi_mgr_bridge_get_id(bridge); + struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); + struct mipi_dsi_host *host = msm_dsi->host; + struct drm_bridge *ext_bridge = msm_dsi->ext_bridge; + int ret; -static void dsi_mgr_bridge_disable(struct drm_bridge *bridge) -{ - DBG(""); + if (!ext_bridge) + return; + + ret = msm_dsi_host_disable(host); + if (ret) + pr_err("%s: host %d disable failed, %d\n", __func__, id, ret); + + ret = msm_dsi_host_power_off(host); + if (ret) + pr_err("%s: host %d power off failed,%d\n", __func__, id, ret); } -static void dsi_mgr_bridge_post_disable(struct drm_bridge *bridge) +static void post_disable_panel(struct drm_bridge *bridge) { int id = dsi_mgr_bridge_get_id(bridge); struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); struct msm_dsi *msm_dsi1 = dsi_mgr_get_dsi(DSI_1); struct mipi_dsi_host *host = msm_dsi->host; struct drm_panel *panel = msm_dsi->panel; - bool is_dual_panel = IS_DUAL_PANEL(); + bool is_dual_dsi = IS_DUAL_DSI(); int ret; DBG("id=%d", id); - if (!panel || (is_dual_panel && (DSI_1 == id))) + if (!panel || (is_dual_dsi && (DSI_1 == id))) return; ret = drm_panel_disable(panel); @@ -430,7 +477,7 @@ static void dsi_mgr_bridge_post_disable(struct drm_bridge *bridge) if (ret) pr_err("%s: host %d disable failed, %d\n", __func__, id, ret); - if (is_dual_panel && msm_dsi1) { + if (is_dual_dsi && msm_dsi1) { ret = msm_dsi_host_disable(msm_dsi1->host); if (ret) pr_err("%s: host1 disable failed, %d\n", __func__, ret); @@ -444,7 +491,7 @@ static void dsi_mgr_bridge_post_disable(struct drm_bridge *bridge) if (ret) pr_err("%s: host %d power off failed,%d\n", __func__, id, ret); - if (is_dual_panel && msm_dsi1) { + if (is_dual_dsi && msm_dsi1) { ret = msm_dsi_host_power_off(msm_dsi1->host); if (ret) pr_err("%s: host1 power off failed, %d\n", @@ -452,7 +499,21 @@ static void dsi_mgr_bridge_post_disable(struct drm_bridge *bridge) } } -static void dsi_mgr_bridge_mode_set(struct drm_bridge *bridge, +static void mode_set_bridge(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + int id = dsi_mgr_bridge_get_id(bridge); + struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); + struct mipi_dsi_host *host = msm_dsi->host; + + if (!msm_dsi->ext_bridge) + return; + + msm_dsi_host_set_display_mode(host, adjusted_mode); +} + +static void mode_set_panel(struct drm_bridge *bridge, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { @@ -460,7 +521,57 @@ static void dsi_mgr_bridge_mode_set(struct drm_bridge *bridge, struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); struct msm_dsi *other_dsi = dsi_mgr_get_other_dsi(id); struct mipi_dsi_host *host = msm_dsi->host; - bool is_dual_panel = IS_DUAL_PANEL(); + bool is_dual_dsi = IS_DUAL_DSI(); + + if (is_dual_dsi && (DSI_1 == id)) + return; + + msm_dsi_host_set_display_mode(host, adjusted_mode); + if (is_dual_dsi && other_dsi) + msm_dsi_host_set_display_mode(other_dsi->host, adjusted_mode); +} +/* + * for now, the dsi bridge functions ignore dual dsi mode if an external + * bridge IC is connected to it + */ +static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge) +{ + int id = dsi_mgr_bridge_get_id(bridge); + struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); + + if (msm_dsi->panel) + pre_enable_panel(bridge); + else + pre_enable_bridge(bridge); +} + +static void dsi_mgr_bridge_enable(struct drm_bridge *bridge) +{ + DBG(""); +} + +static void dsi_mgr_bridge_disable(struct drm_bridge *bridge) +{ + DBG(""); +} + +static void dsi_mgr_bridge_post_disable(struct drm_bridge *bridge) +{ + int id = dsi_mgr_bridge_get_id(bridge); + struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); + + if (msm_dsi->panel) + post_disable_panel(bridge); + else + post_disable_bridge(bridge); +} + +static void dsi_mgr_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + int id = dsi_mgr_bridge_get_id(bridge); + struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", mode->base.id, mode->name, @@ -471,12 +582,10 @@ static void dsi_mgr_bridge_mode_set(struct drm_bridge *bridge, mode->vsync_end, mode->vtotal, mode->type, mode->flags); - if (is_dual_panel && (DSI_1 == id)) - return; - - msm_dsi_host_set_display_mode(host, adjusted_mode); - if (is_dual_panel && other_dsi) - msm_dsi_host_set_display_mode(other_dsi->host, adjusted_mode); + if (msm_dsi->panel) + mode_set_panel(bridge, mode, adjusted_mode); + else + mode_set_bridge(bridge, mode, adjusted_mode); } static const struct drm_connector_funcs dsi_mgr_connector_funcs = { @@ -598,9 +707,10 @@ int msm_dsi_manager_phy_enable(int id, { struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id); struct msm_dsi_phy *phy = msm_dsi->phy; + int src_pll_id = IS_DUAL_DSI() ? DSI_CLOCK_MASTER : id; int ret; - ret = msm_dsi_phy_enable(phy, IS_DUAL_PANEL(), bit_rate, esc_rate); + ret = msm_dsi_phy_enable(phy, src_pll_id, bit_rate, esc_rate); if (ret) return ret; @@ -622,7 +732,7 @@ void msm_dsi_manager_phy_disable(int id) * first controller only when the second controller is disabled. */ msm_dsi->phy_enabled = false; - if (IS_DUAL_PANEL() && mdsi && sdsi) { + if (IS_DUAL_DSI() && mdsi && sdsi) { if (!mdsi->phy_enabled && !sdsi->phy_enabled) { msm_dsi_phy_disable(sdsi->phy); msm_dsi_phy_disable(mdsi->phy); @@ -713,9 +823,9 @@ int msm_dsi_manager_register(struct msm_dsi *msm_dsi) msm_dsim->dsi[id] = msm_dsi; - ret = dsi_mgr_parse_dual_panel(msm_dsi->pdev->dev.of_node, id); + ret = dsi_mgr_parse_dual_dsi(msm_dsi->pdev->dev.of_node, id); if (ret) { - pr_err("%s: failed to parse dual panel info\n", __func__); + pr_err("%s: failed to parse dual DSI info\n", __func__); goto fail; } diff --git a/drivers/gpu/drm/msm/dsi/dsi_phy.c b/drivers/gpu/drm/msm/dsi/dsi_phy.c index 2d3b33ce1cc5..52b463e51202 100644 --- a/drivers/gpu/drm/msm/dsi/dsi_phy.c +++ b/drivers/gpu/drm/msm/dsi/dsi_phy.c @@ -21,7 +21,7 @@ #define dsi_phy_write(offset, data) msm_writel((data), (offset)) struct dsi_phy_ops { - int (*enable)(struct msm_dsi_phy *phy, bool is_dual_panel, + int (*enable)(struct msm_dsi_phy *phy, int src_pll_id, const unsigned long bit_rate, const unsigned long esc_rate); int (*disable)(struct msm_dsi_phy *phy); }; @@ -30,6 +30,12 @@ struct dsi_phy_cfg { enum msm_dsi_phy_type type; struct dsi_reg_config reg_cfg; struct dsi_phy_ops ops; + + /* Each cell {phy_id, pll_id} of the truth table indicates + * if the source PLL is on the right side of the PHY. + * Fill default H/W values in illegal cells, eg. cell {0, 1}. + */ + bool src_pll_truthtable[DSI_MAX][DSI_MAX]; }; struct dsi_dphy_timing { @@ -149,6 +155,19 @@ fail: return ret; } +static void dsi_phy_set_src_pll(struct msm_dsi_phy *phy, int pll_id, u32 reg) +{ + int phy_id = phy->id; + + if ((phy_id >= DSI_MAX) || (pll_id >= DSI_MAX)) + return; + + if (phy->cfg->src_pll_truthtable[phy_id][pll_id]) + dsi_phy_write(phy->base + reg, 0x01); + else + dsi_phy_write(phy->base + reg, 0x00); +} + #define S_DIV_ROUND_UP(n, d) \ (((n) >= 0) ? (((n) + (d) - 1) / (d)) : (((n) - (d) + 1) / (d))) @@ -295,7 +314,7 @@ static void dsi_28nm_phy_regulator_ctrl(struct msm_dsi_phy *phy, bool enable) dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_4, 0x20); } -static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, +static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy, int src_pll_id, const unsigned long bit_rate, const unsigned long esc_rate) { struct dsi_dphy_timing *timing = &phy->timing; @@ -368,10 +387,7 @@ static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, dsi_phy_write(base + REG_DSI_28nm_PHY_CTRL_0, 0x5f); - if (is_dual_panel && (phy->id != DSI_CLOCK_MASTER)) - dsi_phy_write(base + REG_DSI_28nm_PHY_GLBL_TEST_CTRL, 0x00); - else - dsi_phy_write(base + REG_DSI_28nm_PHY_GLBL_TEST_CTRL, 0x01); + dsi_phy_set_src_pll(phy, src_pll_id, REG_DSI_28nm_PHY_GLBL_TEST_CTRL); return 0; } @@ -414,6 +430,7 @@ static void dsi_phy_disable_resource(struct msm_dsi_phy *phy) static const struct dsi_phy_cfg dsi_phy_cfgs[MSM_DSI_PHY_MAX] = { [MSM_DSI_PHY_28NM_HPM] = { .type = MSM_DSI_PHY_28NM_HPM, + .src_pll_truthtable = { {true, true}, {false, true} }, .reg_cfg = { .num = 1, .regs = { @@ -427,6 +444,7 @@ static const struct dsi_phy_cfg dsi_phy_cfgs[MSM_DSI_PHY_MAX] = { }, [MSM_DSI_PHY_28NM_LP] = { .type = MSM_DSI_PHY_28NM_LP, + .src_pll_truthtable = { {true, true}, {true, true} }, .reg_cfg = { .num = 1, .regs = { @@ -557,7 +575,7 @@ void __exit msm_dsi_phy_driver_unregister(void) platform_driver_unregister(&dsi_phy_platform_driver); } -int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, +int msm_dsi_phy_enable(struct msm_dsi_phy *phy, int src_pll_id, const unsigned long bit_rate, const unsigned long esc_rate) { int ret; @@ -572,7 +590,7 @@ int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel, return ret; } - return phy->cfg->ops.enable(phy, is_dual_panel, bit_rate, esc_rate); + return phy->cfg->ops.enable(phy, src_pll_id, bit_rate, esc_rate); } int msm_dsi_phy_disable(struct msm_dsi_phy *phy) diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c index 206f758f7d64..336b50fc0beb 100644 --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c @@ -63,7 +63,7 @@ static int mdp5_hw_init(struct msm_kms *kms) mdp5_ctlm_hw_reset(mdp5_kms->ctlm); - pm_runtime_put_sync(dev->dev); + //pm_runtime_put_sync(dev->dev); return 0; } @@ -526,7 +526,7 @@ struct msm_kms *mdp5_kms_init(struct drm_device *dev) continue; mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0); } - mdp5_disable(mdp5_kms); + //mdp5_disable(mdp5_kms); mdelay(16); if (config->platform.iommu) { diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c index b7ef56ed8d1c..d6b318d37733 100644 --- a/drivers/gpu/drm/msm/msm_drv.c +++ b/drivers/gpu/drm/msm/msm_drv.c @@ -345,7 +345,7 @@ static int msm_load(struct drm_device *dev, unsigned long flags) pm_runtime_get_sync(dev->dev); ret = drm_irq_install(dev, platform_get_irq(dev->platformdev, 0)); - pm_runtime_put_sync(dev->dev); + //pm_runtime_put_sync(dev->dev); if (ret < 0) { dev_err(dev->dev, "failed to install IRQ handler\n"); goto fail; diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c index fdcbdab808e9..cab9e467f8c9 100644 --- a/drivers/i2c/busses/i2c-qup.c +++ b/drivers/i2c/busses/i2c-qup.c @@ -24,6 +24,12 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/scatterlist.h> +#include <linux/atomic.h> +#include <linux/dmaengine.h> +#include <linux/dmapool.h> /* QUP Registers */ #define QUP_CONFIG 0x000 @@ -33,6 +39,7 @@ #define QUP_OPERATIONAL 0x018 #define QUP_ERROR_FLAGS 0x01c #define QUP_ERROR_FLAGS_EN 0x020 +#define QUP_OPERATIONAL_MASK 0x028 #define QUP_HW_VERSION 0x030 #define QUP_MX_OUTPUT_CNT 0x100 #define QUP_OUT_FIFO_BASE 0x110 @@ -42,6 +49,7 @@ #define QUP_IN_FIFO_BASE 0x218 #define QUP_I2C_CLK_CTL 0x400 #define QUP_I2C_STATUS 0x404 +#define QUP_I2C_MASTER_GEN 0x408 /* QUP States and reset values */ #define QUP_RESET_STATE 0 @@ -51,6 +59,7 @@ #define QUP_STATE_VALID BIT(2) #define QUP_I2C_MAST_GEN BIT(4) +#define QUP_I2C_FLUSH BIT(6) #define QUP_OPERATIONAL_RESET 0x000ff0 #define QUP_I2C_STATUS_RESET 0xfffffc @@ -69,28 +78,50 @@ #define QUP_CLOCK_AUTO_GATE BIT(13) #define I2C_MINI_CORE (2 << 8) #define I2C_N_VAL 15 +#define I2C_N_VAL_V2 7 + /* Most significant word offset in FIFO port */ #define QUP_MSW_SHIFT (I2C_N_VAL + 1) /* Packing/Unpacking words in FIFOs, and IO modes */ #define QUP_OUTPUT_BLK_MODE (1 << 10) +#define QUP_OUTPUT_BAM_MODE (3 << 10) #define QUP_INPUT_BLK_MODE (1 << 12) +#define QUP_INPUT_BAM_MODE (3 << 12) +#define QUP_BAM_MODE (QUP_OUTPUT_BAM_MODE | QUP_INPUT_BAM_MODE) #define QUP_UNPACK_EN BIT(14) #define QUP_PACK_EN BIT(15) #define QUP_REPACK_EN (QUP_UNPACK_EN | QUP_PACK_EN) +#define QUP_V2_TAGS_EN 1 + #define QUP_OUTPUT_BLOCK_SIZE(x)(((x) >> 0) & 0x03) #define QUP_OUTPUT_FIFO_SIZE(x) (((x) >> 2) & 0x07) #define QUP_INPUT_BLOCK_SIZE(x) (((x) >> 5) & 0x03) #define QUP_INPUT_FIFO_SIZE(x) (((x) >> 7) & 0x07) -/* QUP tags */ +/* QUP V1 tags */ #define QUP_TAG_START (1 << 8) #define QUP_TAG_DATA (2 << 8) #define QUP_TAG_STOP (3 << 8) #define QUP_TAG_REC (4 << 8) +/* QUP v2 tags */ +#define QUP_TAG_V2_HS 0xff +#define QUP_TAG_V2_START 0x81 +#define QUP_TAG_V2_DATAWR 0x82 +#define QUP_TAG_V2_DATAWR_STOP 0x83 +#define QUP_TAG_V2_DATARD 0x85 +#define QUP_TAG_V2_DATARD_STOP 0x87 + +/* QUP BAM v2 tags */ +#define QUP_BAM_INPUT_EOT 0x93 +#define QUP_BAM_FLUSH_STOP 0x96 + +/* frequency definitions for high speed and max speed */ +#define I2C_QUP_CLK_FAST_FREQ 1000000 + /* Status, Error flags */ #define I2C_STATUS_WR_BUFFER_FULL BIT(0) #define I2C_STATUS_BUS_ACTIVE BIT(8) @@ -98,6 +129,41 @@ #define QUP_STATUS_ERROR_FLAGS 0x7c #define QUP_READ_LIMIT 256 +#define SET_BIT 0x1 +#define RESET_BIT 0x0 +#define ONE_BYTE 0x1 + +#define QUP_I2C_MX_CONFIG_DURING_RUN BIT(31) + +#define MX_TX_RX_LEN SZ_64K +#define MX_BLOCKS (MX_TX_RX_LEN / QUP_READ_LIMIT) + +/* Max timeout in ms for 32k bytes */ +#define TOUT_MAX 300 + +struct qup_i2c_block { + int blocks; + u8 *block_tag_len; + int *block_data_len; + int block_pos; +}; + +struct qup_i2c_tag { + u8 *start; + dma_addr_t addr; +}; + +struct qup_i2c_bam_rx { + struct qup_i2c_tag scratch_tag; + struct dma_chan *dma_rx; + struct scatterlist *sg_rx; +}; + +struct qup_i2c_bam_tx { + struct qup_i2c_tag footer_tag; + struct dma_chan *dma_tx; + struct scatterlist *sg_tx; +}; struct qup_i2c_dev { struct device *dev; @@ -112,9 +178,17 @@ struct qup_i2c_dev { int in_fifo_sz; int out_blk_sz; int in_blk_sz; - + struct qup_i2c_block blk; unsigned long one_byte_t; + int is_hs; + bool use_v2_tags; + + int tx_tag_len; + int rx_tag_len; + u8 *tags; + int tags_pos; + struct i2c_msg *msg; /* Current posion in user message buffer */ int pos; @@ -123,6 +197,12 @@ struct qup_i2c_dev { /* QUP core errors */ u32 qup_err; + /* dma parameters */ + bool is_dma; + struct dma_pool *dpool; + struct qup_i2c_tag start_tag; + struct qup_i2c_bam_rx brx; + struct qup_i2c_bam_tx btx; struct completion xfer; }; @@ -199,6 +279,14 @@ static int qup_i2c_poll_state(struct qup_i2c_dev *qup, u32 req_state) return qup_i2c_poll_state_mask(qup, req_state, QUP_STATE_MASK); } +static void qup_i2c_flush(struct qup_i2c_dev *qup) +{ + u32 val = readl(qup->base + QUP_STATE); + + val |= QUP_I2C_FLUSH; + writel(val, qup->base + QUP_STATE); +} + static int qup_i2c_poll_state_valid(struct qup_i2c_dev *qup) { return qup_i2c_poll_state_mask(qup, 0, 0); @@ -221,33 +309,58 @@ static int qup_i2c_change_state(struct qup_i2c_dev *qup, u32 state) return 0; } -static int qup_i2c_wait_writeready(struct qup_i2c_dev *qup) +/** + * qup_i2c_wait_ready - wait for a give number of bytes in tx/rx path + * @qup: The qup_i2c_dev device + * @op: The bit/event to wait on + * @val: value of the bit to wait on, 0 or 1 + * @len: The length the bytes to be transferred + */ +static int qup_i2c_wait_ready(struct qup_i2c_dev *qup, int op, bool val, + int len) { unsigned long timeout; u32 opflags; u32 status; + u32 shift = __ffs(op); - timeout = jiffies + HZ; + len *= qup->one_byte_t; + /* timeout after a wait of twice the max time */ + timeout = jiffies + len * 4; for (;;) { opflags = readl(qup->base + QUP_OPERATIONAL); status = readl(qup->base + QUP_I2C_STATUS); - if (!(opflags & QUP_OUT_NOT_EMPTY) && - !(status & I2C_STATUS_BUS_ACTIVE)) - return 0; + if (((opflags & op) >> shift) == val) { + if (op == QUP_OUT_NOT_EMPTY) { + if (!(status & I2C_STATUS_BUS_ACTIVE)) + return 0; + } else { + return 0; + } + } if (time_after(jiffies, timeout)) return -ETIMEDOUT; - usleep_range(qup->one_byte_t, qup->one_byte_t * 2); + usleep_range(len, len * 2); } } -static void qup_i2c_set_write_mode(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static void qup_i2c_set_write_mode(struct qup_i2c_dev *qup, struct i2c_msg *msg, + int run) { - /* Number of entries to shift out, including the start */ - int total = msg->len + 1; + /* Total Number of entries to shift out, including the tags */ + int total; + + if (qup->use_v2_tags) { + total = msg->len + qup->tx_tag_len; + if (run) + total |= QUP_I2C_MX_CONFIG_DURING_RUN; + } else { + total = msg->len + 1; /* plus start tag */ + } if (total < qup->out_fifo_sz) { /* FIFO mode */ @@ -261,7 +374,7 @@ static void qup_i2c_set_write_mode(struct qup_i2c_dev *qup, struct i2c_msg *msg) } } -static void qup_i2c_issue_write(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static void qup_i2c_issue_write_v1(struct qup_i2c_dev *qup, struct i2c_msg *msg) { u32 addr = msg->addr << 1; u32 qup_tag; @@ -302,7 +415,137 @@ static void qup_i2c_issue_write(struct qup_i2c_dev *qup, struct i2c_msg *msg) } } -static int qup_i2c_write_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static void qup_i2c_create_tag_v2(struct qup_i2c_dev *qup, + struct i2c_msg *msg, int last) +{ + u16 addr = (msg->addr << 1) | ((msg->flags & I2C_M_RD) == I2C_M_RD); + int len = 0, prev_len = 0; + int blocks = 0; + int rem; + int block_len = 0; + int data_len; + + qup->blk.block_pos = 0; + qup->pos = 0; + qup->blk.blocks = (msg->len + QUP_READ_LIMIT - 1) / QUP_READ_LIMIT; + rem = msg->len % QUP_READ_LIMIT; + + /* 2 tag bytes for each block + 2 extra bytes for first block */ + qup->tags = kzalloc((qup->blk.blocks * 2) + 2, GFP_KERNEL); + qup->blk.block_tag_len = kzalloc(qup->blk.blocks, GFP_KERNEL); + qup->blk.block_data_len = kzalloc(sizeof(*qup->blk.block_data_len) * + qup->blk.blocks, GFP_KERNEL); + + while (blocks < qup->blk.blocks) { + /* 0 is used to specify a READ_LIMIT of 256 bytes */ + data_len = (blocks < (qup->blk.blocks - 1)) ? 0 : rem; + + /* Send START and ADDR bytes only for the first block */ + if (!blocks) { + qup->tags[len++] = QUP_TAG_V2_START; + + if (qup->is_hs) { + qup->tags[len++] = QUP_TAG_V2_HS; + qup->tags[len++] = QUP_TAG_V2_START; + } + + qup->tags[len++] = addr & 0xff; + if (msg->flags & I2C_M_TEN) + qup->tags[len++] = addr >> 8; + } + + /* Send _STOP commands for the last block */ + if ((blocks == (qup->blk.blocks - 1)) && last) { + if (msg->flags & I2C_M_RD) + qup->tags[len++] = QUP_TAG_V2_DATARD_STOP; + else + qup->tags[len++] = QUP_TAG_V2_DATAWR_STOP; + } else { + if (msg->flags & I2C_M_RD) + qup->tags[len++] = QUP_TAG_V2_DATARD; + else + qup->tags[len++] = QUP_TAG_V2_DATAWR; + } + + qup->tags[len++] = data_len; + block_len = len - prev_len; + prev_len = len; + qup->blk.block_tag_len[blocks] = block_len; + + if (!data_len) + qup->blk.block_data_len[blocks] = QUP_READ_LIMIT; + else + qup->blk.block_data_len[blocks] = data_len; + + qup->tags_pos = 0; + blocks++; + } + + qup->tx_tag_len = len; + + if (msg->flags & I2C_M_RD) + qup->rx_tag_len = (qup->blk.blocks * 2); + else + qup->rx_tag_len = 0; +} + +static u32 qup_i2c_send_data(struct qup_i2c_dev *qup, int tlen, u8 *tbuf, + int dlen, u8 *dbuf) +{ + u32 val = 0, idx = 0, pos = 0, i = 0, t; + int len = tlen + dlen; + u8 *buf = tbuf; + + while (len > 0) { + if (qup_i2c_wait_ready(qup, QUP_OUT_FULL, RESET_BIT, + 4 * ONE_BYTE)) { + dev_err(qup->dev, "timeout for fifo out full"); + break; + } + + t = (len >= 4) ? 4 : len; + + while (idx < t) { + if (!i && (pos >= tlen)) { + buf = dbuf; + pos = 0; + i = 1; + } + val |= buf[pos++] << (idx++ * 8); + } + + writel(val, qup->base + QUP_OUT_FIFO_BASE); + idx = 0; + val = 0; + len -= 4; + } + + return 0; +} + +static void qup_i2c_issue_xfer_v2(struct qup_i2c_dev *qup, struct i2c_msg *msg) +{ + u32 data_len = 0, tag_len; + + tag_len = qup->blk.block_tag_len[qup->blk.block_pos]; + + if (!(msg->flags & I2C_M_RD)) + data_len = qup->blk.block_data_len[qup->blk.block_pos]; + + qup_i2c_send_data(qup, tag_len, qup->tags, data_len, msg->buf); +} + +static void qup_i2c_issue_write(struct qup_i2c_dev *qup, struct i2c_msg + *msg) +{ + if (qup->use_v2_tags) + qup_i2c_issue_xfer_v2(qup, msg); + else + qup_i2c_issue_write_v1(qup, msg); +} + +static int qup_i2c_write_one(struct qup_i2c_dev *qup, struct i2c_msg *msg, + int run, int last) { unsigned long left; int ret; @@ -310,13 +553,20 @@ static int qup_i2c_write_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) qup->msg = msg; qup->pos = 0; + if (qup->use_v2_tags) + qup_i2c_create_tag_v2(qup, msg, last); + else + qup->blk.blocks = 0; + enable_irq(qup->irq); - qup_i2c_set_write_mode(qup, msg); + qup_i2c_set_write_mode(qup, msg, run); - ret = qup_i2c_change_state(qup, QUP_RUN_STATE); - if (ret) - goto err; + if (!run) { + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) + goto err; + } writel(qup->clk_ctl, qup->base + QUP_I2C_CLK_CTL); @@ -344,10 +594,13 @@ static int qup_i2c_write_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) ret = -EIO; goto err; } - } while (qup->pos < msg->len); + qup->blk.block_pos++; + } while (qup->blk.block_pos < qup->blk.blocks); /* Wait for the outstanding data in the fifo to drain */ - ret = qup_i2c_wait_writeready(qup); + if (last) + ret = qup_i2c_wait_ready(qup, QUP_OUT_NOT_EMPTY, RESET_BIT, + ONE_BYTE); err: disable_irq(qup->irq); @@ -356,8 +609,17 @@ err: return ret; } -static void qup_i2c_set_read_mode(struct qup_i2c_dev *qup, int len) +static void qup_i2c_set_read_mode(struct qup_i2c_dev *qup, int len, int run) { + if (qup->use_v2_tags) { + len += qup->rx_tag_len; + if (run) { + len |= QUP_I2C_MX_CONFIG_DURING_RUN; + qup->tx_tag_len |= QUP_I2C_MX_CONFIG_DURING_RUN; + } + writel(qup->tx_tag_len, qup->base + QUP_MX_WRITE_CNT); + } + if (len < qup->in_fifo_sz) { /* FIFO mode */ writel(QUP_REPACK_EN, qup->base + QUP_IO_MODE); @@ -370,7 +632,8 @@ static void qup_i2c_set_read_mode(struct qup_i2c_dev *qup, int len) } } -static void qup_i2c_issue_read(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static void qup_i2c_issue_read_v1(struct qup_i2c_dev *qup, + struct i2c_msg *msg) { u32 addr, len, val; @@ -383,20 +646,29 @@ static void qup_i2c_issue_read(struct qup_i2c_dev *qup, struct i2c_msg *msg) writel(val, qup->base + QUP_OUT_FIFO_BASE); } +static void qup_i2c_issue_read(struct qup_i2c_dev *qup, struct i2c_msg + *msg) +{ + if (qup->use_v2_tags) + qup_i2c_issue_xfer_v2(qup, msg); + else + qup_i2c_issue_read_v1(qup, msg); +} -static void qup_i2c_read_fifo(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static void qup_i2c_read_fifo_v1(struct qup_i2c_dev *qup, struct i2c_msg *msg) { - u32 opflags; u32 val = 0; int idx; + int len = msg->len + qup->rx_tag_len; - for (idx = 0; qup->pos < msg->len; idx++) { + for (idx = 0; qup->pos < len; idx++) { if ((idx & 1) == 0) { /* Check that FIFO have data */ - opflags = readl(qup->base + QUP_OPERATIONAL); - if (!(opflags & QUP_IN_NOT_EMPTY)) + if (qup_i2c_wait_ready(qup, QUP_IN_NOT_EMPTY, SET_BIT, + 4 * ONE_BYTE)) { + dev_err(qup->dev, "timeout for fifo not empty"); break; - + } /* Reading 2 words at time */ val = readl(qup->base + QUP_IN_FIFO_BASE); @@ -407,35 +679,92 @@ static void qup_i2c_read_fifo(struct qup_i2c_dev *qup, struct i2c_msg *msg) } } -static int qup_i2c_read_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) +static void qup_i2c_read_fifo_v2(struct qup_i2c_dev *qup, + struct i2c_msg *msg) +{ + u32 val; + int idx; + int pos = 0; + /* 2 extra bytes for read tags */ + int total = qup->blk.block_data_len[qup->blk.block_pos] + 2; + + while (pos < total) { + /* Check that FIFO have data */ + if (qup_i2c_wait_ready(qup, QUP_IN_NOT_EMPTY, SET_BIT, + 4 * ONE_BYTE)) { + dev_err(qup->dev, "timeout for fifo not empty"); + break; + } + val = readl(qup->base + QUP_IN_FIFO_BASE); + + for (idx = 0; idx < 4; idx++, val >>= 8, pos++) { + /* first 2 bytes are tag bytes */ + if (pos < 2) + continue; + + if (pos >= total) + return; + + msg->buf[qup->pos++] = val & 0xff; + } + } +} + +static void qup_i2c_read_fifo(struct qup_i2c_dev *qup, + struct i2c_msg *msg) +{ + if (qup->use_v2_tags) + qup_i2c_read_fifo_v2(qup, msg); + else + qup_i2c_read_fifo_v1(qup, msg); +} + +static int qup_i2c_read_one(struct qup_i2c_dev *qup, struct i2c_msg *msg, + int run, int last) { unsigned long left; int ret; + /* + * The QUP block will issue a NACK and STOP on the bus when reaching + * the end of the read, the length of the read is specified as one byte + * which limits the possible read to 256 (QUP_READ_LIMIT) bytes. + */ + if (msg->len > QUP_READ_LIMIT) { + dev_err(qup->dev, "HW not capable of reads over %d bytes\n", + QUP_READ_LIMIT); + return -EINVAL; + } qup->msg = msg; qup->pos = 0; + if (qup->use_v2_tags) + qup_i2c_create_tag_v2(qup, msg, last); + else + qup->blk.blocks = 0; enable_irq(qup->irq); - qup_i2c_set_read_mode(qup, msg->len); + qup_i2c_set_read_mode(qup, msg->len, run); - ret = qup_i2c_change_state(qup, QUP_RUN_STATE); - if (ret) - goto err; + if (!run) { + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) + goto err; + } writel(qup->clk_ctl, qup->base + QUP_I2C_CLK_CTL); - ret = qup_i2c_change_state(qup, QUP_PAUSE_STATE); - if (ret) - goto err; + do { + ret = qup_i2c_change_state(qup, QUP_PAUSE_STATE); + if (ret) + goto err; - qup_i2c_issue_read(qup, msg); + qup_i2c_issue_read(qup, msg); - ret = qup_i2c_change_state(qup, QUP_RUN_STATE); - if (ret) - goto err; + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) + goto err; - do { left = wait_for_completion_timeout(&qup->xfer, HZ); if (!left) { writel(1, qup->base + QUP_SW_RESET); @@ -451,7 +780,8 @@ static int qup_i2c_read_one(struct qup_i2c_dev *qup, struct i2c_msg *msg) } qup_i2c_read_fifo(qup, msg); - } while (qup->pos < msg->len); + qup->blk.block_pos++; + } while (qup->blk.block_pos < qup->blk.blocks); err: disable_irq(qup->irq); @@ -460,12 +790,309 @@ err: return ret; } +static void qup_i2c_bam_cb(void *data) +{ + struct qup_i2c_dev *qup = data; + + complete(&qup->xfer); +} + +static int get_tag_code(struct i2c_msg *msg, int last) +{ + u8 op; + + /* always issue stop for each read block */ + if (last) { + if (msg->flags & I2C_M_RD) + op = QUP_TAG_V2_DATARD_STOP; + else + op = QUP_TAG_V2_DATAWR_STOP; + } else { + if (msg->flags & I2C_M_RD) + op = QUP_TAG_V2_DATARD; + else + op = QUP_TAG_V2_DATAWR; + } + + return op; +} + +static int get_start_tag(u8 *tag, struct i2c_msg *msg, int first, int last, + int blen) +{ + u16 addr = (msg->addr << 1) | ((msg->flags & I2C_M_RD) == I2C_M_RD); + u8 op; + int len = 0; + + op = get_tag_code(msg, last); + + if (first) { + tag[len++] = QUP_TAG_V2_START; + tag[len++] = addr & 0xff; + if (msg->flags & I2C_M_TEN) + tag[len++] = addr >> 8; + } + tag[len++] = op; + + /* Length > 0 implies 256 bytes */ + if (blen > QUP_READ_LIMIT) + blen = 0; + + tag[len++] = blen; + + if (msg->flags & I2C_M_RD & last) { + tag[len++] = QUP_BAM_INPUT_EOT; + tag[len++] = QUP_BAM_FLUSH_STOP; + } + + return len; +} + +void qup_sg_set_buf(struct scatterlist *sg, void *buf, struct qup_i2c_tag *tg, + unsigned int buflen, struct qup_i2c_dev *qup, + int map, int dir) +{ + sg_set_buf(sg, buf, buflen); + dma_map_sg(qup->dev, sg, 1, dir); + + if (!map) + sg_dma_address(sg) = tg->addr + ((u8 *)buf - tg->start); +} + +static void qup_i2c_rel_dma(struct qup_i2c_dev *qup) +{ + if (qup->btx.dma_tx) + dma_release_channel(qup->btx.dma_tx); + if (qup->brx.dma_rx) + dma_release_channel(qup->brx.dma_rx); + qup->btx.dma_tx = NULL; + qup->brx.dma_rx = NULL; +} + +static int qup_i2c_req_dma(struct qup_i2c_dev *qup) +{ + if (!qup->btx.dma_tx) { + qup->btx.dma_tx = dma_request_slave_channel(qup->dev, "tx"); + if (!qup->btx.dma_tx) { + dev_err(qup->dev, "\n tx channel not available"); + return -ENODEV; + } + } + + if (!qup->brx.dma_rx) { + qup->brx.dma_rx = dma_request_slave_channel(qup->dev, "rx"); + if (!qup->brx.dma_rx) { + dev_err(qup->dev, "\n rx channel not available"); + qup_i2c_rel_dma(qup); + return -ENODEV; + } + } + return 0; +} + +static int bam_do_xfer(struct qup_i2c_dev *qup, struct i2c_msg *msg, int num) +{ + struct dma_async_tx_descriptor *txd, *rxd = NULL; + int ret = 0; + dma_cookie_t cookie_rx, cookie_tx; + u32 rx_nents = 0, tx_nents = 0, len, blocks, rem, last; + u32 cur_rx_nents, cur_tx_nents; + u32 tlen, i, tx_len, tx_buf = 0, rx_buf = 0, off = 0; + + while (num) { + blocks = (msg->len + QUP_READ_LIMIT) / QUP_READ_LIMIT; + rem = msg->len % QUP_READ_LIMIT; + i = 0, tx_len = 0, len = 0; + + if (msg->flags & I2C_M_RD) { + cur_tx_nents = 1; + cur_rx_nents = (blocks * 2) + 1; + rx_nents += cur_rx_nents; + tx_nents += cur_tx_nents; + + while (i < blocks) { + /* transfer length set to '0' implies 256 + bytes */ + tlen = (i == (blocks - 1)) ? rem : 0; + last = (i == (blocks - 1)) && !(num - 1); + len += get_start_tag(&qup->start_tag.start[off + + len], + msg, !i, last, tlen); + + qup_sg_set_buf(&qup->brx.sg_rx[rx_buf++], + &qup->brx.scratch_tag.start[0], + &qup->brx.scratch_tag, + 2, qup, 0, 0); + + qup_sg_set_buf(&qup->brx.sg_rx[rx_buf++], + &msg->buf[QUP_READ_LIMIT * i], + NULL, tlen, qup, + 1, DMA_FROM_DEVICE); + i++; + } + qup_sg_set_buf(&qup->btx.sg_tx[tx_buf++], + &qup->start_tag.start[off], + &qup->start_tag, len, qup, 0, 0); + off += len; + qup_sg_set_buf(&qup->brx.sg_rx[rx_buf++], + &qup->brx.scratch_tag.start[1], + &qup->brx.scratch_tag, 2, + qup, 0, 0); + } else { + cur_tx_nents = (blocks * 2); + tx_nents += cur_tx_nents; + + while (i < blocks) { + tlen = (i == (blocks - 1)) ? rem : 0; + last = (i == (blocks - 1)) && !(num - 1); + len = get_start_tag(&qup->start_tag.start[off + + tx_len], + msg, !i, last, tlen); + + qup_sg_set_buf(&qup->btx.sg_tx[tx_buf++], + &qup->start_tag.start[off + + tx_len], + &qup->start_tag, len, + qup, 0, 0); + + tx_len += len; + qup_sg_set_buf(&qup->btx.sg_tx[tx_buf++], + &msg->buf[QUP_READ_LIMIT * i], + NULL, tlen, qup, 1, + DMA_TO_DEVICE); + i++; + } + off += tx_len; + + if (!(num - 1)) { + qup->btx.footer_tag.start[0] = + QUP_BAM_FLUSH_STOP; + qup->btx.footer_tag.start[1] = + QUP_BAM_FLUSH_STOP; + qup_sg_set_buf(&qup->btx.sg_tx[tx_buf++], + &qup->btx.footer_tag.start[0], + &qup->btx.footer_tag, 2, + qup, 0, 0); + tx_nents += 1; + } + } + msg++; + num--; + } + + txd = dmaengine_prep_slave_sg(qup->btx.dma_tx, qup->btx.sg_tx, tx_nents, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_PREP_FENCE); + if (!txd) { + dev_err(qup->dev, "failed to get tx desc\n"); + ret = -EINVAL; + goto desc_err; + } + + if (!rx_nents) { + txd->callback = qup_i2c_bam_cb; + txd->callback_param = qup; + } + + cookie_tx = dmaengine_submit(txd); + dma_async_issue_pending(qup->btx.dma_tx); + + if (rx_nents) { + rxd = dmaengine_prep_slave_sg(qup->brx.dma_rx, qup->brx.sg_rx, + rx_nents, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + + if (!rxd) { + dev_err(qup->dev, "failed to get rx desc\n"); + ret = -EINVAL; + + /* abort TX descriptors */ + dmaengine_terminate_all(qup->btx.dma_tx); + goto desc_err; + } + + rxd->callback = qup_i2c_bam_cb; + rxd->callback_param = qup; + cookie_rx = dmaengine_submit(rxd); + dma_async_issue_pending(qup->brx.dma_rx); + } + + if (!wait_for_completion_timeout(&qup->xfer, TOUT_MAX * HZ)) { + dev_err(qup->dev, "normal trans timed out\n"); + ret = -ETIMEDOUT; + } + + if (ret || qup->bus_err || qup->qup_err) { + if (qup->bus_err & QUP_I2C_NACK_FLAG) + msg--; + dev_err(qup->dev, "NACK from %x\n", msg->addr); + ret = -EIO; + + if (qup_i2c_change_state(qup, QUP_RUN_STATE)) { + dev_err(qup->dev, "change to run state timed out"); + return ret; + } + + writel(QUP_BAM_INPUT_EOT, qup->base + QUP_OUT_FIFO_BASE); + writel(QUP_BAM_FLUSH_STOP, qup->base + QUP_OUT_FIFO_BASE); + writel(QUP_BAM_FLUSH_STOP, qup->base + QUP_OUT_FIFO_BASE); + writel(QUP_BAM_FLUSH_STOP, qup->base + QUP_OUT_FIFO_BASE); + qup_i2c_flush(qup); + + /* wait for remaining interrupts to occur */ + if (!wait_for_completion_timeout(&qup->xfer, HZ)) + dev_err(qup->dev, "flush timed out\n"); + + qup_i2c_rel_dma(qup); + } + +desc_err: + return ret; +} + +static int qup_bam_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num) +{ + struct qup_i2c_dev *qup = i2c_get_adapdata(adap); + int ret = 0; + + enable_irq(qup->irq); + if (qup_i2c_req_dma(qup)) + goto out; + + qup->bus_err = 0; + qup->qup_err = 0; + + writel(0, qup->base + QUP_MX_INPUT_CNT); + writel(0, qup->base + QUP_MX_OUTPUT_CNT); + + /* set BAM mode */ + writel(QUP_REPACK_EN | QUP_BAM_MODE, qup->base + QUP_IO_MODE); + + /* mask fifo irqs */ + writel((0x3 << 8), qup->base + QUP_OPERATIONAL_MASK); + + /* set RUN STATE */ + ret = qup_i2c_change_state(qup, QUP_RUN_STATE); + if (ret) + goto out; + + writel(qup->clk_ctl, qup->base + QUP_I2C_CLK_CTL); + + qup->msg = msg; + ret = bam_do_xfer(qup, qup->msg, num); +out: + disable_irq(qup->irq); + + qup->msg = NULL; + return ret; +} + static int qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) { struct qup_i2c_dev *qup = i2c_get_adapdata(adap); - int ret, idx; + int ret, idx, last, use_dma = 0, len = 0; ret = pm_runtime_get_sync(qup->dev); if (ret < 0) @@ -477,36 +1104,71 @@ static int qup_i2c_xfer(struct i2c_adapter *adap, goto out; /* Configure QUP as I2C mini core */ - writel(I2C_MINI_CORE | I2C_N_VAL, qup->base + QUP_CONFIG); + if (qup->use_v2_tags) { + writel(I2C_MINI_CORE | I2C_N_VAL_V2, qup->base + QUP_CONFIG); + writel(QUP_V2_TAGS_EN, qup->base + QUP_I2C_MASTER_GEN); + } else { + writel(I2C_MINI_CORE | I2C_N_VAL, qup->base + QUP_CONFIG); + } - for (idx = 0; idx < num; idx++) { - if (msgs[idx].len == 0) { - ret = -EINVAL; - goto out; + if ((qup->is_dma)) { + /* All i2c_msgs should be transferred using either dma or cpu */ + for (idx = 0; idx < num; idx++) { + if (msgs[idx].len == 0) { + ret = -EINVAL; + goto out; + } + + if (!len) + len = ((&msgs[idx])->len) > qup->out_fifo_sz; + + if ((!is_vmalloc_addr((&msgs[idx])->buf)) && len) { + use_dma = 1; + } else { + use_dma = 0; + break; + } } + } + for (idx = 0; idx < num; idx++) { if (qup_i2c_poll_state_i2c_master(qup)) { ret = -EIO; goto out; } - if (msgs[idx].flags & I2C_M_RD) - ret = qup_i2c_read_one(qup, &msgs[idx]); - else - ret = qup_i2c_write_one(qup, &msgs[idx]); + reinit_completion(&qup->xfer); - if (ret) - break; + if (use_dma) { + ret = qup_bam_xfer(adap, &msgs[idx], num); + idx = num; + } else { + last = (idx == (num - 1)); + if (msgs[idx].flags & I2C_M_RD) + ret = qup_i2c_read_one(qup, &msgs[idx], idx, + last); + else + ret = qup_i2c_write_one(qup, &msgs[idx], idx, + last); + } - ret = qup_i2c_change_state(qup, QUP_RESET_STATE); if (ret) break; } + if (!ret) + ret = qup_i2c_change_state(qup, QUP_RESET_STATE); + if (ret == 0) ret = num; out: + if (qup->use_v2_tags) { + kfree(qup->tags); + kfree(qup->blk.block_tag_len); + kfree(qup->blk.block_data_len); + } + pm_runtime_mark_last_busy(qup->dev); pm_runtime_put_autosuspend(qup->dev); @@ -538,6 +1200,14 @@ static void qup_i2c_enable_clocks(struct qup_i2c_dev *qup) clk_prepare_enable(qup->pclk); } +static const struct of_device_id qup_i2c_dt_match[] = { + { .compatible = "qcom,i2c-qup-v1.1.1"}, + { .compatible = "qcom,i2c-qup-v2.1.1"}, + { .compatible = "qcom,i2c-qup-v2.2.1"}, + {} +}; +MODULE_DEVICE_TABLE(of, qup_i2c_dt_match); + static void qup_i2c_disable_clocks(struct qup_i2c_dev *qup) { u32 config; @@ -559,8 +1229,10 @@ static int qup_i2c_probe(struct platform_device *pdev) struct resource *res; u32 io_mode, hw_ver, size; int ret, fs_div, hs_div; - int src_clk_freq; + int src_clk_freq, clk_freq_max; u32 clk_freq = 100000; + const struct of_device_id *of_id; + int blocks; qup = devm_kzalloc(&pdev->dev, sizeof(*qup), GFP_KERNEL); if (!qup) @@ -572,8 +1244,71 @@ static int qup_i2c_probe(struct platform_device *pdev) of_property_read_u32(node, "clock-frequency", &clk_freq); - /* We support frequencies up to FAST Mode (400KHz) */ - if (!clk_freq || clk_freq > 400000) { + of_id = of_match_node(qup_i2c_dt_match, node); + + if (of_device_is_compatible(pdev->dev.of_node, + "qcom,i2c-qup-v1.1.1")) { + qup->use_v2_tags = 0; + clk_freq_max = 400000; + } else { + qup->use_v2_tags = 1; + clk_freq_max = 3400000; + + if (qup_i2c_req_dma(qup)) + goto nodma; + + blocks = (MX_BLOCKS << 1) + 1; + qup->btx.sg_tx = devm_kzalloc(&pdev->dev, + sizeof(*qup->btx.sg_tx) * blocks, + GFP_KERNEL); + if (!qup->btx.sg_tx) { + ret = -ENOMEM; + goto fail; + } + sg_init_table(qup->btx.sg_tx, blocks); + + qup->brx.sg_rx = devm_kzalloc(&pdev->dev, + sizeof(*qup->btx.sg_tx) * blocks, + GFP_KERNEL); + if (!qup->brx.sg_rx) { + ret = -ENOMEM; + goto fail; + } + sg_init_table(qup->brx.sg_rx, blocks); + + size = sizeof(struct qup_i2c_tag) * (blocks + 3); + qup->dpool = dma_pool_create("qup_i2c-dma-pool", &pdev->dev, + size, 4, 0); + + qup->start_tag.start = dma_pool_alloc(qup->dpool, GFP_KERNEL, + &qup->start_tag.addr); + if (!qup->start_tag.start) { + ret = -ENOMEM; + goto fail; + } + + qup->brx.scratch_tag.start = dma_pool_alloc(qup->dpool, + GFP_KERNEL, + &qup->brx.scratch_tag.addr); + + if (!qup->brx.scratch_tag.start) { + ret = -ENOMEM; + goto fail; + } + + qup->btx.footer_tag.start = dma_pool_alloc(qup->dpool, + GFP_KERNEL, + &qup->btx.footer_tag.addr); + if (!qup->btx.footer_tag.start) { + ret = -ENOMEM; + goto fail; + } + qup->is_dma = 1; + } + +nodma: + /* We support frequencies up to HIGH SPEED Mode (3400KHz) */ + if (!clk_freq || clk_freq > clk_freq_max) { dev_err(qup->dev, "clock frequency not supported %d\n", clk_freq); return -EINVAL; @@ -651,8 +1386,17 @@ static int qup_i2c_probe(struct platform_device *pdev) qup->in_fifo_sz = qup->in_blk_sz * (2 << size); src_clk_freq = clk_get_rate(qup->clk); - fs_div = ((src_clk_freq / clk_freq) / 2) - 3; - hs_div = 3; + if (clk_freq > I2C_QUP_CLK_FAST_FREQ) { + fs_div = I2C_QUP_CLK_FAST_FREQ; + hs_div = (src_clk_freq / clk_freq) / 3; + + qup->is_hs = 1; + } else { + fs_div = ((src_clk_freq / clk_freq) / 2) - 3; + hs_div = 3; + } + + hs_div = min_t(int, hs_div, 0x7); qup->clk_ctl = (hs_div << 8) | (fs_div & 0xff); /* @@ -688,6 +1432,11 @@ fail_runtime: pm_runtime_disable(qup->dev); pm_runtime_set_suspended(qup->dev); fail: + if (qup->btx.dma_tx) + dma_release_channel(qup->btx.dma_tx); + if (qup->brx.dma_rx) + dma_release_channel(qup->brx.dma_rx); + qup_i2c_disable_clocks(qup); return ret; } @@ -696,6 +1445,17 @@ static int qup_i2c_remove(struct platform_device *pdev) { struct qup_i2c_dev *qup = platform_get_drvdata(pdev); + if (qup->is_dma) { + dma_pool_free(qup->dpool, qup->start_tag.start, + qup->start_tag.addr); + dma_pool_free(qup->dpool, qup->brx.scratch_tag.start, + qup->brx.scratch_tag.addr); + dma_pool_free(qup->dpool, qup->btx.footer_tag.start, + qup->btx.footer_tag.addr); + dma_pool_destroy(qup->dpool); + dma_release_channel(qup->btx.dma_tx); + dma_release_channel(qup->brx.dma_rx); + } disable_irq(qup->irq); qup_i2c_disable_clocks(qup); i2c_del_adapter(&qup->adap); @@ -750,14 +1510,6 @@ static const struct dev_pm_ops qup_i2c_qup_pm_ops = { NULL) }; -static const struct of_device_id qup_i2c_dt_match[] = { - { .compatible = "qcom,i2c-qup-v1.1.1" }, - { .compatible = "qcom,i2c-qup-v2.1.1" }, - { .compatible = "qcom,i2c-qup-v2.2.1" }, - {} -}; -MODULE_DEVICE_TABLE(of, qup_i2c_dt_match); - static struct platform_driver qup_i2c_driver = { .probe = qup_i2c_probe, .remove = qup_i2c_remove, diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index f1fb1d3ccc56..40d1f5877774 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -59,6 +59,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 c6dcc513d711..39ec4aca5b78 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o obj-$(CONFIG_IOMMU_IOVA) += iova.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 15a2063812fa..40036a393c3a 100644 --- a/drivers/iommu/msm_iommu.c +++ b/drivers/iommu/msm_iommu.c @@ -424,12 +424,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..e2323efae80b --- /dev/null +++ b/drivers/iommu/qcom_iommu_v0.c @@ -0,0 +1,1234 @@ +/* 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 struct qcom_domain_priv *to_qcom_domain(struct iommu_domain *dom) +{ + return container_of(dom, struct qcom_domain_priv, domain); +} + +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_alloc(unsigned type) +{ + + struct iommu_domain *domain; + + if (type != IOMMU_DOMAIN_UNMANAGED) + return NULL; + + 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; + domain = &priv->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 domain; + +fail_nomem: + kfree(priv); + return -ENOMEM; +} + +static void qcom_iommu_domain_free(struct iommu_domain *domain) +{ + struct qcom_domain_priv *priv; + unsigned long *fl_table; + int i; + + mutex_lock(&qcom_iommu_lock); + priv = to_qcom_domain(domain); +// 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 = to_qcom_domain(domain); + 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 = to_qcom_domain(domain); + 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 = to_qcom_domain(domain); + 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 = to_qcom_domain(domain); + 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 = to_qcom_domain(domain); + 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_alloc = qcom_iommu_domain_alloc, + .domain_free = qcom_iommu_domain_free, + .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 653815950aa2..666c81272c94 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -656,6 +656,20 @@ config MFD_QCOM_RPM 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_QCOM_SMD_RPM + tristate "Qualcomm Resource Power Manager (RPM) over SMD" + depends on QCOM_SMD && OF + help + If you say yes to this option, support will be included for the + Resource Power Manager system found in the Qualcomm 8974 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-smd-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 ea40e076cb61..289bd24222e6 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -156,6 +156,7 @@ obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o +obj-$(CONFIG_MFD_QCOM_SMD_RPM) += qcom-smd-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..5f81ba0c50f5 100644 --- a/drivers/mfd/pm8921-core.c +++ b/drivers/mfd/pm8921-core.c @@ -236,11 +236,54 @@ static int pm8xxx_irq_set_type(struct irq_data *d, unsigned int flow_type) return pm8xxx_config_irq(chip, block, config); } +static int pm8xxx_irq_get_irqchip_state(struct irq_data *d, + enum irqchip_irq_state which, + bool *state) +{ + 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; + } + + if (which != IRQCHIP_STATE_LINE_LEVEL) + 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); + goto bail; + } + + *state = !!(bits & BIT(irq_bit)); +bail: + spin_unlock(&chip->pm_irq_lock); + + return rc ? rc : 0; +} + static struct irq_chip pm8xxx_irq_chip = { .name = "pm8xxx", .irq_mask_ack = pm8xxx_irq_mask_ack, .irq_unmask = pm8xxx_irq_unmask, .irq_set_type = pm8xxx_irq_set_type, + .irq_get_irqchip_state = pm8xxx_irq_get_irqchip_state, .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE, }; diff --git a/drivers/mfd/qcom-smd-rpm.c b/drivers/mfd/qcom-smd-rpm.c new file mode 100644 index 000000000000..9088b9bec422 --- /dev/null +++ b/drivers/mfd/qcom-smd-rpm.c @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2015, Sony Mobile Communications AB. + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * 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/soc/qcom/smd.h> +#include <linux/mfd/qcom-smd-rpm.h> + +#include <dt-bindings/mfd/qcom-smd-rpm.h> + +#define RPM_REQUEST_TIMEOUT (5 * HZ) + +/** + * struct qcom_smd_rpm - state of the rpm device driver + * @rpm_channel: reference to the smd channel + * @ack: completion for acks + * @lock: mutual exclusion around the send/complete pair + * @ack_status: result of the rpm request + */ +struct qcom_smd_rpm { + struct qcom_smd_channel *rpm_channel; + + struct completion ack; + struct mutex lock; + int ack_status; +}; + +/** + * struct qcom_rpm_header - header for all rpm requests and responses + * @service_type: identifier of the service + * @length: length of the payload + */ +struct qcom_rpm_header { + u32 service_type; + u32 length; +}; + +/** + * struct qcom_rpm_request - request message to the rpm + * @msg_id: identifier of the outgoing message + * @flags: active/sleep state flags + * @type: resource type + * @id: resource id + * @data_len: length of the payload following this header + */ +struct qcom_rpm_request { + u32 msg_id; + u32 flags; + u32 type; + u32 id; + u32 data_len; +}; + +/** + * struct qcom_rpm_message - response message from the rpm + * @msg_type: indicator of the type of message + * @length: the size of this message, including the message header + * @msg_id: message id + * @message: textual message from the rpm + * + * Multiple of these messages can be stacked in an rpm message. + */ +struct qcom_rpm_message { + u32 msg_type; + u32 length; + union { + u32 msg_id; + u8 message[0]; + }; +}; + +#define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */ + +#define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */ +#define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */ + +/** + * qcom_rpm_smd_write - write @buf to @type:@id + * @rpm: rpm handle + * @type: resource type + * @id: resource identifier + * @buf: the data to be written + * @count: number of bytes in @buf + */ +int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, + int state, + u32 type, u32 id, + void *buf, + size_t count) +{ + static unsigned msg_id = 1; + int left; + int ret; + + struct { + struct qcom_rpm_header hdr; + struct qcom_rpm_request req; + u8 payload[count]; + } pkt; + + /* SMD packets to the RPM may not exceed 256 bytes */ + if (WARN_ON(sizeof(pkt) >= 256)) + return -EINVAL; + + mutex_lock(&rpm->lock); + + pkt.hdr.service_type = RPM_SERVICE_TYPE_REQUEST; + pkt.hdr.length = sizeof(struct qcom_rpm_request) + count; + + pkt.req.msg_id = msg_id++; + pkt.req.flags = BIT(state); + pkt.req.type = type; + pkt.req.id = id; + pkt.req.data_len = count; + memcpy(pkt.payload, buf, count); + + ret = qcom_smd_send(rpm->rpm_channel, &pkt, sizeof(pkt)); + if (ret) + goto out; + + left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT); + if (!left) + ret = -ETIMEDOUT; + else + ret = rpm->ack_status; + +out: + mutex_unlock(&rpm->lock); + return ret; +} +EXPORT_SYMBOL(qcom_rpm_smd_write); + +#define RPM_ERR_INVALID_RESOURCE "resource does not exist" + +static int qcom_smd_rpm_callback(struct qcom_smd_device *qsdev, + const void *data, + size_t count) +{ + const struct qcom_rpm_header *hdr = data; + const struct qcom_rpm_message *msg; + const size_t inv_res_len = sizeof(RPM_ERR_INVALID_RESOURCE) - 1; + struct qcom_smd_rpm *rpm = dev_get_drvdata(&qsdev->dev); + const u8 *buf = data + sizeof(struct qcom_rpm_header); + const u8 *end = buf + hdr->length; + int status = 0; + + if (hdr->service_type != RPM_SERVICE_TYPE_REQUEST || + hdr->length < sizeof(struct qcom_rpm_message)) { + dev_err(&qsdev->dev, "invalid request\n"); + return 0; + } + + while (buf < end) { + msg = (struct qcom_rpm_message *)buf; + switch (msg->msg_type) { + case RPM_MSG_TYPE_MSG_ID: + break; + case RPM_MSG_TYPE_ERR: + if (msg->length == inv_res_len && + !memcmp(msg->message, + RPM_ERR_INVALID_RESOURCE, + inv_res_len)) + status = -ENXIO; + else + status = -EIO; + break; + } + + buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg->length, 4); + } + + rpm->ack_status = status; + complete(&rpm->ack); + return 0; +} + +static int qcom_smd_rpm_probe(struct qcom_smd_device *sdev) +{ + struct qcom_smd_rpm *rpm; + + rpm = devm_kzalloc(&sdev->dev, sizeof(*rpm), GFP_KERNEL); + if (!rpm) + return -ENOMEM; + + mutex_init(&rpm->lock); + init_completion(&rpm->ack); + + rpm->rpm_channel = sdev->channel; + + dev_set_drvdata(&sdev->dev, rpm); + + return of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev); +} + +static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev) +{ + of_platform_depopulate(&sdev->dev); +} + +static const struct of_device_id qcom_smd_rpm_of_match[] = { + { .compatible = "qcom,rpm-msm8974" }, + { .compatible = "qcom,rpm-msm8916" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match); + +static struct qcom_smd_driver qcom_smd_rpm_driver = { + .probe = qcom_smd_rpm_probe, + .remove = qcom_smd_rpm_remove, + .callback = qcom_smd_rpm_callback, + .driver = { + .name = "qcom_smd_rpm", + .owner = THIS_MODULE, + .of_match_table = qcom_smd_rpm_of_match, + }, +}; + +module_qcom_smd_driver(qcom_smd_rpm_driver); + +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); +MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mfd/qcom_rpm.c b/drivers/mfd/qcom_rpm.c index 12e324319573..660fe319b5bf 100644 --- a/drivers/mfd/qcom_rpm.c +++ b/drivers/mfd/qcom_rpm.c @@ -72,6 +72,10 @@ struct qcom_rpm { #define RPM_SIGNAL BIT(2) +static const struct qcom_rpm_resource msm8916_rpm_resource_table[] = { + +}; + 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 }, @@ -372,6 +376,42 @@ static const struct of_device_id qcom_rpm_of_match[] = { }; 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, 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/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 9536852fd4c6..04f2e1fa9dd1 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -96,17 +96,4 @@ config EEPROM_DIGSY_MTC_CFG If unsure, say N. -config EEPROM_SUNXI_SID - tristate "Allwinner sunxi security ID support" - depends on ARCH_SUNXI && SYSFS - help - This is a driver for the 'security ID' available on various Allwinner - devices. - - Due to the potential risks involved with changing e-fuses, - this driver is read-only. - - This driver can also be built as a module. If so, the module - will be called sunxi_sid. - endmenu diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile index 9507aec95e94..fc1e81d29267 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile @@ -4,5 +4,4 @@ obj-$(CONFIG_EEPROM_LEGACY) += eeprom.o obj-$(CONFIG_EEPROM_MAX6875) += max6875.o obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o -obj-$(CONFIG_EEPROM_SUNXI_SID) += sunxi_sid.o obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o diff --git a/drivers/misc/eeprom/sunxi_sid.c b/drivers/misc/eeprom/sunxi_sid.c deleted file mode 100644 index 8385177ff32b..000000000000 --- a/drivers/misc/eeprom/sunxi_sid.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> - * http://www.linux-sunxi.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - * - * This driver exposes the Allwinner security ID, efuses exported in byte- - * sized chunks. - */ - -#include <linux/compiler.h> -#include <linux/device.h> -#include <linux/err.h> -#include <linux/export.h> -#include <linux/fs.h> -#include <linux/io.h> -#include <linux/kernel.h> -#include <linux/kobject.h> -#include <linux/module.h> -#include <linux/of_device.h> -#include <linux/platform_device.h> -#include <linux/random.h> -#include <linux/slab.h> -#include <linux/stat.h> -#include <linux/sysfs.h> -#include <linux/types.h> - -#define DRV_NAME "sunxi-sid" - -struct sunxi_sid_data { - void __iomem *reg_base; - unsigned int keysize; -}; - -/* We read the entire key, due to a 32 bit read alignment requirement. Since we - * want to return the requested byte, this results in somewhat slower code and - * uses 4 times more reads as needed but keeps code simpler. Since the SID is - * only very rarely probed, this is not really an issue. - */ -static u8 sunxi_sid_read_byte(const struct sunxi_sid_data *sid_data, - const unsigned int offset) -{ - u32 sid_key; - - if (offset >= sid_data->keysize) - return 0; - - sid_key = ioread32be(sid_data->reg_base + round_down(offset, 4)); - sid_key >>= (offset % 4) * 8; - - return sid_key; /* Only return the last byte */ -} - -static ssize_t sid_read(struct file *fd, struct kobject *kobj, - struct bin_attribute *attr, char *buf, - loff_t pos, size_t size) -{ - struct platform_device *pdev; - struct sunxi_sid_data *sid_data; - int i; - - pdev = to_platform_device(kobj_to_dev(kobj)); - sid_data = platform_get_drvdata(pdev); - - if (pos < 0 || pos >= sid_data->keysize) - return 0; - if (size > sid_data->keysize - pos) - size = sid_data->keysize - pos; - - for (i = 0; i < size; i++) - buf[i] = sunxi_sid_read_byte(sid_data, pos + i); - - return i; -} - -static struct bin_attribute sid_bin_attr = { - .attr = { .name = "eeprom", .mode = S_IRUGO, }, - .read = sid_read, -}; - -static int sunxi_sid_remove(struct platform_device *pdev) -{ - device_remove_bin_file(&pdev->dev, &sid_bin_attr); - dev_dbg(&pdev->dev, "driver unloaded\n"); - - return 0; -} - -static const struct of_device_id sunxi_sid_of_match[] = { - { .compatible = "allwinner,sun4i-a10-sid", .data = (void *)16}, - { .compatible = "allwinner,sun7i-a20-sid", .data = (void *)512}, - {/* sentinel */}, -}; -MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); - -static int sunxi_sid_probe(struct platform_device *pdev) -{ - struct sunxi_sid_data *sid_data; - struct resource *res; - const struct of_device_id *of_dev_id; - u8 *entropy; - unsigned int i; - - sid_data = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_sid_data), - GFP_KERNEL); - if (!sid_data) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - sid_data->reg_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(sid_data->reg_base)) - return PTR_ERR(sid_data->reg_base); - - of_dev_id = of_match_device(sunxi_sid_of_match, &pdev->dev); - if (!of_dev_id) - return -ENODEV; - sid_data->keysize = (int)of_dev_id->data; - - platform_set_drvdata(pdev, sid_data); - - sid_bin_attr.size = sid_data->keysize; - if (device_create_bin_file(&pdev->dev, &sid_bin_attr)) - return -ENODEV; - - entropy = kzalloc(sizeof(u8) * sid_data->keysize, GFP_KERNEL); - for (i = 0; i < sid_data->keysize; i++) - entropy[i] = sunxi_sid_read_byte(sid_data, i); - add_device_randomness(entropy, sid_data->keysize); - kfree(entropy); - - dev_dbg(&pdev->dev, "loaded\n"); - - return 0; -} - -static struct platform_driver sunxi_sid_driver = { - .probe = sunxi_sid_probe, - .remove = sunxi_sid_remove, - .driver = { - .name = DRV_NAME, - .of_match_table = sunxi_sid_of_match, - }, -}; -module_platform_driver(sunxi_sid_driver); - -MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); -MODULE_DESCRIPTION("Allwinner sunxi security id driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index fb266745f824..70805ba9577c 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; @@ -804,7 +809,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/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4a09f7608c66..353a018e9165 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -489,6 +489,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto pclk_disable; } + /* Vote for maximum clock rate for maximum performance */ + ret = clk_set_rate(msm_host->clk, INT_MAX); + if (ret) + dev_warn(&pdev->dev, "core clock boost falied\n"); + ret = clk_prepare_enable(msm_host->clk); if (ret) goto pclk_disable; @@ -517,6 +522,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Set HC_MODE_EN bit in HC_MODE register */ writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); + host->quirks |= SDHCI_QUIRK_NO_CARD_NO_RESET; host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index bc1445238fb3..912a3bb191cc 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -207,8 +207,7 @@ EXPORT_SYMBOL_GPL(sdhci_reset); static void sdhci_do_reset(struct sdhci_host *host, u8 mask) { if (host->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) { - if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & - SDHCI_CARD_PRESENT)) + if (!sdhci_do_get_cd(host)) return; } @@ -1601,15 +1600,18 @@ static int sdhci_do_get_cd(struct sdhci_host *host) if (host->flags & SDHCI_DEVICE_DEAD) return 0; + /* + * Try slot gpio detect, if defined it take precedence + * over build in controller functionality + */ + if (!IS_ERR_VALUE(gpio_cd)) + return !!gpio_cd; + /* If polling/nonremovable, assume that the card is always present. */ if ((host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) || (host->mmc->caps & MMC_CAP_NONREMOVABLE)) return 1; - /* Try slot gpio detect */ - if (!IS_ERR_VALUE(gpio_cd)) - return !!gpio_cd; - /* Host native card detect */ return !!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT); } @@ -3118,7 +3120,8 @@ int sdhci_add_host(struct sdhci_host *host) mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED; if ((host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) && - !(mmc->caps & MMC_CAP_NONREMOVABLE)) + !(mmc->caps & MMC_CAP_NONREMOVABLE) && + IS_ERR_VALUE(mmc_gpio_get_cd(host->mmc))) mmc->caps |= MMC_CAP_NEEDS_POLL; /* If there are external regulators, get them */ diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig new file mode 100644 index 000000000000..b3a640510fed --- /dev/null +++ b/drivers/nvmem/Kconfig @@ -0,0 +1,36 @@ +menuconfig NVMEM + tristate "NVMEM Support" + select REGMAP + help + Support for NVMEM devices. + + This framework is designed to provide a generic interface to NVMEM + from both the Linux Kernel and the userspace. + + If unsure, say no. + +if NVMEM + +config QCOM_QFPROM + tristate "QCOM QFPROM Support" + depends on ARCH_QCOM || COMPILE_TEST + select REGMAP_MMIO + help + Say y here to enable QFPROM support. The QFPROM provides access + functions for QFPROM data to rest of the drivers via nvmem interface. + + This driver can also be built as a module. If so, the module + will be called nvmem-qfprom. + +config NVMEM_SUNXI_SID + tristate "Allwinner SoCs SID support" + depends on ARCH_SUNXI + select REGMAP_MMIO + help + This is a driver for the 'security ID' available on various Allwinner + devices. + + This driver can also be built as a module. If so, the module + will be called eeprom-sunxi-sid. + +endif diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile new file mode 100644 index 000000000000..7d18489fcee6 --- /dev/null +++ b/drivers/nvmem/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for nvmem drivers. +# + +obj-$(CONFIG_NVMEM) += nvmem_core.o +nvmem_core-y := core.o + +# Devices +obj-$(CONFIG_QCOM_QFPROM) += nvmem_qfprom.o +nvmem_qfprom-y := qfprom.o +obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem-sunxi-sid.o +nvmem-sunxi-sid-y := sunxi-sid.o diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c new file mode 100644 index 000000000000..ac152f054fe5 --- /dev/null +++ b/drivers/nvmem/core.c @@ -0,0 +1,1069 @@ +/* + * nvmem framework core. + * + * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.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/device.h> +#include <linux/nvmem-provider.h> +#include <linux/nvmem-consumer.h> +#include <linux/export.h> +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/regmap.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> + +struct nvmem_device { + const char *name; + struct regmap *regmap; + struct module *owner; + struct device dev; + int stride; + int word_size; + int ncells; + int id; + int users; + size_t size; + bool read_only; +}; + +struct nvmem_cell { + const char *name; + int offset; + int bytes; + int bit_offset; + int nbits; + struct nvmem_device *nvmem; + struct list_head node; +}; + +static DEFINE_MUTEX(nvmem_mutex); +static DEFINE_IDA(nvmem_ida); + +static LIST_HEAD(nvmem_cells); +static DEFINE_MUTEX(nvmem_cells_mutex); + +#define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) + +static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t pos, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + int rc; + + /* Stop the user from reading */ + if (pos > nvmem->size) + return 0; + + if (pos + count > nvmem->size) + count = nvmem->size - pos; + + count = count/nvmem->word_size * nvmem->word_size; + + rc = regmap_raw_read(nvmem->regmap, pos, buf, count); + + if (IS_ERR_VALUE(rc)) + return rc; + + return count; +} + +static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t pos, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + int rc; + + /* Stop the user from writing */ + if (pos > nvmem->size) + return 0; + + if (pos + count > nvmem->size) + count = nvmem->size - pos; + + count = count/nvmem->word_size * nvmem->word_size; + + rc = regmap_raw_write(nvmem->regmap, pos, buf, count); + + if (IS_ERR_VALUE(rc)) + return rc; + + return count; +} + +/* default read/write permissions */ +static struct bin_attribute bin_attr_nvmem = { + .attr = { + .name = "nvmem", + .mode = S_IWUSR | S_IRUGO, + }, + .read = bin_attr_nvmem_read, + .write = bin_attr_nvmem_write, +}; + +static struct bin_attribute *nvmem_bin_attributes[] = { + &bin_attr_nvmem, + NULL, +}; + +static const struct attribute_group nvmem_bin_group = { + .bin_attrs = nvmem_bin_attributes, +}; + +static const struct attribute_group *nvmem_dev_groups[] = { + &nvmem_bin_group, + NULL, +}; + +/* read only permission */ +static struct bin_attribute bin_attr_ro_nvmem = { + .attr = { + .name = "nvmem", + .mode = S_IRUGO, + }, + .read = bin_attr_nvmem_read, +}; + +static struct bin_attribute *nvmem_bin_ro_attributes[] = { + &bin_attr_ro_nvmem, + NULL, +}; +static const struct attribute_group nvmem_bin_ro_group = { + .bin_attrs = nvmem_bin_ro_attributes, +}; + +static void nvmem_release(struct device *dev) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + + ida_simple_remove(&nvmem_ida, nvmem->id); + kfree(nvmem); +} + +static struct class nvmem_class = { + .name = "nvmem", + .dev_groups = nvmem_dev_groups, + .dev_release = nvmem_release, +}; + +static int of_nvmem_match(struct device *dev, const void *nvmem_np) +{ + return dev->of_node == nvmem_np; +} + +static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np) +{ + struct device *d; + + if (!nvmem_np) + return NULL; + + d = class_find_device(&nvmem_class, NULL, nvmem_np, of_nvmem_match); + + return d ? to_nvmem_device(d) : NULL; +} + +static struct nvmem_cell *nvmem_find_cell(const char *cell_id) +{ + struct nvmem_cell *p; + + list_for_each_entry(p, &nvmem_cells, node) + if (p && !strcmp(p->name, cell_id)) + return p; + + return NULL; +} + +static void nvmem_cell_drop(struct nvmem_cell *cell) +{ + mutex_lock(&nvmem_cells_mutex); + list_del(&cell->node); + mutex_unlock(&nvmem_cells_mutex); + kfree(cell); +} + +static void nvmem_device_remove_all_cells(struct nvmem_device *nvmem) +{ + struct nvmem_cell *cell; + struct list_head *p, *n; + + list_for_each_safe(p, n, &nvmem_cells) { + cell = list_entry(p, struct nvmem_cell, node); + if (cell->nvmem == nvmem) + nvmem_cell_drop(cell); + } +} + +static void nvmem_cell_add(struct nvmem_cell *cell) +{ + mutex_lock(&nvmem_cells_mutex); + list_add_tail(&cell->node, &nvmem_cells); + mutex_unlock(&nvmem_cells_mutex); +} + +static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, + struct nvmem_cell *cell) +{ + cell->nvmem = nvmem; + cell->offset = info->offset; + cell->bytes = info->bytes; + cell->name = info->name; + + cell->bit_offset = info->bit_offset; + cell->nbits = info->nbits; + + if (cell->nbits) + cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset, + BITS_PER_BYTE); + + if (!IS_ALIGNED(cell->offset, nvmem->stride)) { + dev_err(&nvmem->dev, + "cell %s unaligned to nvmem stride %d\n", + cell->name, nvmem->stride); + return -EINVAL; + } + + return 0; +} + +static int nvmem_add_cells(struct nvmem_device *nvmem, + struct nvmem_config *cfg) +{ + struct nvmem_cell **cells; + struct nvmem_cell_info *info = cfg->cells; + int i, rval; + + cells = kzalloc(sizeof(*cells) * cfg->ncells, GFP_KERNEL); + if (!cells) + return -ENOMEM; + + for (i = 0; i < cfg->ncells; i++) { + cells[i] = kzalloc(sizeof(**cells), GFP_KERNEL); + if (!cells[i]) { + rval = -ENOMEM; + goto err; + } + + rval = nvmem_cell_info_to_nvmem_cell(nvmem, &info[i], cells[i]); + if (IS_ERR_VALUE(rval)) { + kfree(cells[i]); + goto err; + } + + nvmem_cell_add(cells[i]); + } + + nvmem->ncells = cfg->ncells; + /* remove tmp array */ + kfree(cells); + + return 0; +err: + while (--i) + nvmem_cell_drop(cells[i]); + + return rval; +} + +/** + * nvmem_register() - Register a nvmem device for given nvmem_config. + * Also creates an binary entry in /sys/class/nvmem/dev-name/nvmem + * + * @config: nvmem device configuration with which nvmem device is created. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to nvmem_device. + */ + +struct nvmem_device *nvmem_register(struct nvmem_config *config) +{ + struct nvmem_device *nvmem; + struct regmap *rm; + int rval; + + if (!config->dev) + return ERR_PTR(-EINVAL); + + rm = dev_get_regmap(config->dev, NULL); + if (!rm) { + dev_err(config->dev, "Regmap not found\n"); + return ERR_PTR(-EINVAL); + } + + nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL); + if (!nvmem) + return ERR_PTR(-ENOMEM); + + nvmem->id = ida_simple_get(&nvmem_ida, 0, 0, GFP_KERNEL); + if (nvmem->id < 0) { + kfree(nvmem); + return ERR_PTR(nvmem->id); + } + + nvmem->regmap = rm; + nvmem->owner = config->owner; + nvmem->stride = regmap_get_reg_stride(rm); + nvmem->word_size = regmap_get_val_bytes(rm); + nvmem->size = regmap_get_max_register(rm) + nvmem->stride; + nvmem->dev.class = &nvmem_class; + nvmem->dev.parent = config->dev; + nvmem->dev.of_node = config->dev->of_node; + dev_set_name(&nvmem->dev, "%s%d", + config->name ? : "nvmem", config->id); + + nvmem->read_only = nvmem->dev.of_node ? + of_property_read_bool(nvmem->dev.of_node, + "read-only") : + config->read_only; + + device_initialize(&nvmem->dev); + + dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", + dev_name(&nvmem->dev)); + + rval = device_add(&nvmem->dev); + if (rval) { + ida_simple_remove(&nvmem_ida, nvmem->id); + kfree(nvmem); + return ERR_PTR(rval); + } + + /* update sysfs attributes */ + if (nvmem->read_only) + sysfs_update_group(&nvmem->dev.kobj, &nvmem_bin_ro_group); + + if (config->cells) + nvmem_add_cells(nvmem, config); + + return nvmem; +} +EXPORT_SYMBOL_GPL(nvmem_register); + +/** + * nvmem_unregister() - Unregister previously registered nvmem device + * + * @nvmem: Pointer to previously registered nvmem device. + * + * The return value will be an non zero on error or a zero on success. + */ +int nvmem_unregister(struct nvmem_device *nvmem) +{ + mutex_lock(&nvmem_mutex); + if (nvmem->users) { + mutex_unlock(&nvmem_mutex); + return -EBUSY; + } + mutex_unlock(&nvmem_mutex); + + nvmem_device_remove_all_cells(nvmem); + device_del(&nvmem->dev); + + return 0; +} +EXPORT_SYMBOL_GPL(nvmem_unregister); + +static struct nvmem_device *__nvmem_device_get(struct device_node *np, + struct nvmem_cell **cellp, + const char *cell_id) +{ + struct nvmem_device *nvmem = NULL; + + mutex_lock(&nvmem_mutex); + + if (np) { + nvmem = of_nvmem_find(np); + if (!nvmem) { + mutex_unlock(&nvmem_mutex); + return ERR_PTR(-EPROBE_DEFER); + } + } else { + struct nvmem_cell *cell = nvmem_find_cell(cell_id); + + if (cell) { + nvmem = cell->nvmem; + *cellp = cell; + } + + if (!nvmem) { + mutex_unlock(&nvmem_mutex); + return ERR_PTR(-ENOENT); + } + } + + nvmem->users++; + mutex_unlock(&nvmem_mutex); + + if (!try_module_get(nvmem->owner)) { + dev_err(&nvmem->dev, + "could not increase module refcount for cell %s\n", + nvmem->name); + + mutex_lock(&nvmem_mutex); + nvmem->users--; + mutex_unlock(&nvmem_mutex); + + return ERR_PTR(-EINVAL); + } + + return nvmem; +} + +static void __nvmem_device_put(struct nvmem_device *nvmem) +{ + module_put(nvmem->owner); + mutex_lock(&nvmem_mutex); + nvmem->users--; + mutex_unlock(&nvmem_mutex); +} + +static int nvmem_match(struct device *dev, const void *data) +{ + return !strcmp(dev_name(dev), data); +} + +static struct nvmem_device *nvmem_find(const char *name) +{ + struct device *d; + + d = class_find_device(&nvmem_class, NULL, name, nvmem_match); + + return d ? to_nvmem_device(d) : NULL; +} + +/** + * of_nvmem_device_get() - Get nvmem device from a given id + * + * @dev node: Device tree node that uses the nvmem device + * @id: nvmem name from nvmem-names property. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_device. + */ +struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id) +{ + + struct device_node *nvmem_np; + int index; + + index = of_property_match_string(np, "nvmem-names", id); + + nvmem_np = of_parse_phandle(np, "nvmem", index); + if (!nvmem_np) + return ERR_PTR(-EINVAL); + + return __nvmem_device_get(nvmem_np, NULL, NULL); + +} +EXPORT_SYMBOL_GPL(of_nvmem_device_get); + + +/** + * nvmem_device_get() - Get nvmem device from a given id + * + * @dev : Device that uses the nvmem device + * @id: nvmem name from nvmem-names property. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_device. + */ +struct nvmem_device *nvmem_device_get(struct device *dev, const char *dev_name) +{ + if (dev->of_node) { /* try dt first */ + struct nvmem_device *nvmem; + + nvmem = of_nvmem_device_get(dev->of_node, dev_name); + + if (!IS_ERR(nvmem) || PTR_ERR(nvmem) == -EPROBE_DEFER) + return nvmem; + + } + + return nvmem_find(dev_name); + +} +EXPORT_SYMBOL_GPL(nvmem_device_get); + +static int devm_nvmem_device_match(struct device *dev, void *res, void *data) +{ + struct nvmem_device **nvmem = res; + if (!nvmem || !*nvmem) { + WARN_ON(!nvmem || !*nvmem); + return 0; + } + return *nvmem == data; +} + +static void devm_nvmem_device_release(struct device *dev, void *res) +{ + nvmem_device_put(*(struct nvmem_device **)res); +} + +/** + * devm_nvmem_device_put() - put alredy got nvmem device + * + * @nvmem: pointer to nvmem device allocated by devm_nvmem_cell_get(), + * that needs to be released. + */ +void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem) +{ + int ret; + + ret = devres_release(dev, devm_nvmem_device_release, + devm_nvmem_device_match, nvmem); + + WARN_ON(ret); + +} +EXPORT_SYMBOL_GPL(devm_nvmem_device_put); + +/** + * nvmem_device_put() - put alredy got nvmem device + * + * @nvmem: pointer to nvmem device that needs to be released. + */ +void nvmem_device_put(struct nvmem_device *nvmem) +{ + __nvmem_device_put(nvmem); +} +EXPORT_SYMBOL_GPL(nvmem_device_put); + +/** + * devm_nvmem_device_get() - Get nvmem cell of device form a given id + * + * @dev node: Device tree node that uses the nvmem cell + * @id: nvmem name in nvmems property. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * automatically once the device is freed. + */ +struct nvmem_device *devm_nvmem_device_get(struct device *dev, const char *id) +{ + struct nvmem_device **ptr, *nvmem; + + ptr = devres_alloc(devm_nvmem_device_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + nvmem = nvmem_device_get(dev, id); + if (!IS_ERR(nvmem)) { + *ptr = nvmem; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return nvmem; +} +EXPORT_SYMBOL_GPL(devm_nvmem_device_get); + +static struct nvmem_cell *nvmem_cell_get_from_list(const char *cell_id) +{ + struct nvmem_cell *cell = NULL; + struct nvmem_device *nvmem; + + nvmem = __nvmem_device_get(NULL, &cell, cell_id); + if (IS_ERR(nvmem)) + return ERR_CAST(nvmem); + + + return cell; + +} +/** + * of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id + * + * @dev node: Device tree node that uses the nvmem cell + * @id: nvmem cell name from nvmem-cell-names property. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * nvmem_cell_put(). + */ +struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, + const char *name) +{ + struct device_node *cell_np, *nvmem_np; + struct nvmem_cell *cell; + struct nvmem_device *nvmem; + const __be32 *addr; + int rval, len, index; + + index = of_property_match_string(np, "nvmem-cell-names", name); + + cell_np = of_parse_phandle(np, "nvmem-cell", index); + if (!cell_np) + return ERR_PTR(-EINVAL); + + nvmem_np = of_get_parent(cell_np); + if (!nvmem_np) + return ERR_PTR(-EINVAL); + + nvmem = __nvmem_device_get(nvmem_np, NULL, NULL); + if (IS_ERR(nvmem)) + return ERR_CAST(nvmem); + + addr = of_get_property(cell_np, "reg", &len); + if (!addr || (len < 2 * sizeof(int))) { + dev_err(&nvmem->dev, "nvmem: invalid reg on %s\n", + cell_np->full_name); + rval = -EINVAL; + goto err_mem; + } + + cell = kzalloc(sizeof(*cell), GFP_KERNEL); + if (!cell) { + rval = -ENOMEM; + goto err_mem; + } + + cell->nvmem = nvmem; + cell->offset = be32_to_cpup(addr++); + cell->bytes = be32_to_cpup(addr); + cell->name = cell_np->name; + + of_property_read_u32(cell_np, "bit-offset", &cell->bit_offset); + of_property_read_u32(cell_np, "nbits", &cell->nbits); + + if (cell->nbits) + cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset, + BITS_PER_BYTE); + + if (!IS_ALIGNED(cell->offset, nvmem->stride)) { + dev_err(&nvmem->dev, + "cell %s unaligned to nvmem stride %d\n", + cell->name, nvmem->stride); + rval = -EINVAL; + goto err_sanity; + } + + nvmem_cell_add(cell); + + return cell; + +err_sanity: + kfree(cell); + +err_mem: + __nvmem_device_put(nvmem); + + return ERR_PTR(rval); + +} +EXPORT_SYMBOL_GPL(of_nvmem_cell_get); + +/** + * nvmem_cell_get() - Get nvmem cell of device form a given cell name + * + * @dev node: Device tree node that uses the nvmem cell + * @id: nvmem cell name to get. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * nvmem_cell_put(). + */ +struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *cell_id) +{ + struct nvmem_cell *cell; + + if (dev->of_node) { /* try dt first */ + cell = of_nvmem_cell_get(dev->of_node, cell_id); + if (!IS_ERR(cell) || PTR_ERR(cell) == -EPROBE_DEFER) + return cell; + } + + return nvmem_cell_get_from_list(cell_id); + +} +EXPORT_SYMBOL_GPL(nvmem_cell_get); + +static void devm_nvmem_cell_release(struct device *dev, void *res) +{ + nvmem_cell_put(*(struct nvmem_cell **)res); +} + +/** + * devm_nvmem_cell_get() - Get nvmem cell of device form a given id + * + * @dev node: Device tree node that uses the nvmem cell + * @id: nvmem id in nvmem-names property. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * automatically once the device is freed. + */ +struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *id) +{ + struct nvmem_cell **ptr, *cell; + + ptr = devres_alloc(devm_nvmem_cell_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + cell = nvmem_cell_get(dev, id); + if (!IS_ERR(cell)) { + *ptr = cell; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return cell; +} +EXPORT_SYMBOL_GPL(devm_nvmem_cell_get); + +static int devm_nvmem_cell_match(struct device *dev, void *res, void *data) +{ + struct nvmem_cell **c = res; + + if (!c || !*c) { + WARN_ON(!c || !*c); + return 0; + } + return *c == data; +} + +/** + * devm_nvmem_cell_put() - Release previously allocated nvmem cell + * from devm_nvmem_cell_get. + * + * @cell: Previously allocated nvmem cell by devm_nvmem_cell_get() + */ +void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell) +{ + int ret; + + ret = devres_release(dev, devm_nvmem_cell_release, devm_nvmem_cell_match, cell); + + WARN_ON(ret); +} +EXPORT_SYMBOL(devm_nvmem_cell_put); + +/** + * nvmem_cell_put() - Release previously allocated nvmem cell. + * + * @cell: Previously allocated nvmem cell by nvmem_cell_get() + */ +void nvmem_cell_put(struct nvmem_cell *cell) +{ + struct nvmem_device *nvmem = cell->nvmem; + + __nvmem_device_put(nvmem); + nvmem_cell_drop(cell); +} +EXPORT_SYMBOL_GPL(nvmem_cell_put); + +static inline void nvmem_shift_read_buffer_in_place(struct nvmem_cell *cell, + void *buf) +{ + u8 *p, *b; + int i, bit_offset = cell->bit_offset; + + p = b = buf; + if (bit_offset) { + /* First shift */ + *b++ >>= bit_offset; + + /* setup rest of the bytes if any */ + for (i = 1; i < cell->bytes; i++) { + /* Get bits from next byte and shift them towards msb */ + *p |= *b << (BITS_PER_BYTE - bit_offset); + + p = b; + *b++ >>= bit_offset; + } + + /* result fits in less bytes */ + if (cell->bytes != DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE)) + *p-- = 0; + } + /* clear msb bits if any leftover in the last byte */ + *p &= GENMASK((cell->nbits%BITS_PER_BYTE) - 1, 0); +} + +static int __nvmem_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell *cell, + void *buf, ssize_t *len) +{ + int rc; + + rc = regmap_raw_read(nvmem->regmap, cell->offset, buf, cell->bytes); + + if (IS_ERR_VALUE(rc)) + return rc; + + /* shift bits in-place */ + if (cell->bit_offset || cell->bit_offset) + nvmem_shift_read_buffer_in_place(cell, buf); + + *len = cell->bytes; + + return cell->bytes; +} +/** + * nvmem_cell_read() - Read a given nvmem cell + * + * @cell: nvmem cell to be read. + * @len: pointer to length of cell which will be populated on successful read. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a char * bufffer. The buffer should be freed by the consumer with a + * kfree(). + */ +void *nvmem_cell_read(struct nvmem_cell *cell, ssize_t *len) +{ + struct nvmem_device *nvmem = cell->nvmem; + u8 *buf; + int rc; + + if (!nvmem || !nvmem->regmap) + return ERR_PTR(-EINVAL); + + buf = kzalloc(cell->bytes, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + rc = __nvmem_cell_read(nvmem, cell, buf, len); + if (IS_ERR_VALUE(rc)) { + kfree(buf); + return ERR_PTR(rc); + } + + return buf; +} +EXPORT_SYMBOL_GPL(nvmem_cell_read); + +static inline void *nvmem_cell_prepare_write_buffer(struct nvmem_cell *cell, + u8 *_buf, int len) +{ + struct nvmem_device *nvmem = cell->nvmem; + int i, rc, nbits, bit_offset = cell->bit_offset; + u8 v, *p, *buf, *b, pbyte, pbits; + + nbits = cell->nbits; + buf = kzalloc(cell->bytes, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + memcpy(buf, _buf, len); + p = b = buf; + + if (bit_offset) { + pbyte = *b; + *b <<= bit_offset; + + /* setup the first byte with lsb bits from nvmem */ + rc = regmap_raw_read(nvmem->regmap, cell->offset, &v, 1); + *b++ |= GENMASK(bit_offset - 1, 0) & v; + + /* setup rest of the byte if any */ + for (i = 1; i < cell->bytes; i++) { + /* Get last byte bits and shift them towards lsb */ + pbits = pbyte >> (BITS_PER_BYTE - 1 - bit_offset); + pbyte = *b; + p = b; + *b <<= bit_offset; + *b++ |= pbits; + } + } + + /* if it's not end on byte boundary */ + if ((nbits + bit_offset) % BITS_PER_BYTE) { + /* setup the last byte with msb bits from nvmem */ + rc = regmap_raw_read(nvmem->regmap, + cell->offset + cell->bytes - 1, &v, 1); + *p |= GENMASK(7, (nbits + bit_offset) % BITS_PER_BYTE) & v; + + } + + return buf; +} + +/** + * nvmem_cell_write() - Write to a given nvmem cell + * + * @cell: nvmem cell to be written. + * @buf: Buffer to be written. + * @len: length of buffer to be written to nvmem cell. + * + * The return value will be an length of bytes written or non zero on failure. + */ +int nvmem_cell_write(struct nvmem_cell *cell, void *buf, ssize_t len) +{ + struct nvmem_device *nvmem = cell->nvmem; + int rc; + void *wbuf = buf; + + if (!nvmem || !nvmem->regmap || nvmem->read_only || + (cell->bit_offset == 0 && len != cell->bytes)) + return -EINVAL; + + if (cell->bit_offset || cell->nbits) { + wbuf = nvmem_cell_prepare_write_buffer(cell, buf, len); + if (IS_ERR(wbuf)) + return PTR_ERR(wbuf); + } + + rc = regmap_raw_write(nvmem->regmap, cell->offset, wbuf, cell->bytes); + + /* free the tmp buffer */ + if (cell->bit_offset) + kfree(wbuf); + + if (IS_ERR_VALUE(rc)) + return rc; + + return len; +} +EXPORT_SYMBOL_GPL(nvmem_cell_write); + +/** + * nvmem_device_cell_read() - Read a given nvmem device and cell + * + * @nvmem: nvmem device to read from. + * @info: nvmem cell info to be read. + * @buf: buffer pointer which will be populated on successful read. + * + * The return value would be length of successful bytes read on success + * and error code in error cases. + */ +int nvmem_device_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf) +{ + struct nvmem_cell cell; + int rc, len; + + if (!nvmem || !nvmem->regmap) + return -EINVAL; + + rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell); + if (IS_ERR_VALUE(rc)) + return rc; + + rc = __nvmem_cell_read(nvmem, &cell, buf, &len); + if (IS_ERR_VALUE(rc)) + return rc; + + return len; +} +EXPORT_SYMBOL_GPL(nvmem_device_cell_read); + +/** + * nvmem_device_cell_write() - Write cell to a given nvmem device + * + * @nvmem: nvmem device to be written to. + * @info: nvmem cell info to be written + * @buf: buffer to be written to cell. + * + * The return value will be an length of bytes written or non zero on failure. + * */ +int nvmem_device_cell_write(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf) +{ + struct nvmem_cell cell; + int rc; + + if (!nvmem || !nvmem->regmap) + return -EINVAL; + + rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell); + if (IS_ERR_VALUE(rc)) + return rc; + + return nvmem_cell_write(&cell, buf, cell.bytes); +} +EXPORT_SYMBOL_GPL(nvmem_device_cell_write); + +/** + * nvmem_device_read() - Read from a given nvmem device + * + * @nvmem: nvmem device to read from. + * @offset: offset in nvmem device. + * @bytes: number of bytes to read. + * @buf: buffer pointer which will be populated on successful read. + * + * The return value would be length of successful bytes read on success + * and error code in error cases. + */ +int nvmem_device_read(struct nvmem_device *nvmem, + unsigned int offset, + size_t bytes, void *buf) +{ + int rc; + + if (!nvmem || !nvmem->regmap) + return -EINVAL; + + rc = regmap_raw_read(nvmem->regmap, offset, buf, bytes); + + if (IS_ERR_VALUE(rc)) + return rc; + + return bytes; +} +EXPORT_SYMBOL_GPL(nvmem_device_read); + +/** + * nvmem_device_write() - Write cell to a given nvmem device + * + * @nvmem: nvmem device to be written to. + * @offset: offset in nvmem device. + * @bytes: number of bytes to write. + * @buf: buffer to be written. + * + * The return value will be an length of bytes written or non zero on failure. + * */ +int nvmem_device_write(struct nvmem_device *nvmem, + unsigned int offset, + size_t bytes, void *buf) +{ + int rc; + + if (!nvmem || !nvmem->regmap) + return -EINVAL; + + rc = regmap_raw_write(nvmem->regmap, offset, buf, bytes); + + if (IS_ERR_VALUE(rc)) + return rc; + + + return bytes; +} +EXPORT_SYMBOL_GPL(nvmem_device_write); + +static int __init nvmem_init(void) +{ + return class_register(&nvmem_class); +} + +static void __exit nvmem_exit(void) +{ + class_unregister(&nvmem_class); +} + +subsys_initcall(nvmem_init); +module_exit(nvmem_exit); + +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com"); +MODULE_DESCRIPTION("nvmem Driver Core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/qfprom.c b/drivers/nvmem/qfprom.c new file mode 100644 index 000000000000..7f7a82fc0951 --- /dev/null +++ b/drivers/nvmem/qfprom.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + * + * 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/platform_device.h> +#include <linux/nvmem-provider.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/of.h> + +static struct regmap_config qfprom_regmap_config = { + .reg_bits = 32, + .val_bits = 8, + .reg_stride = 1, +}; + +static struct nvmem_config econfig = { + .name = "qfprom", + .owner = THIS_MODULE, +}; + +static int qfprom_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static int qfprom_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct nvmem_device *nvmem; + struct regmap *regmap; + void __iomem *base; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + qfprom_regmap_config.max_register = resource_size(res) - 1; + + regmap = devm_regmap_init_mmio(dev, base, &qfprom_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } + econfig.dev = dev; + nvmem = nvmem_register(&econfig); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static const struct of_device_id qfprom_of_match[] = { + { .compatible = "qcom,qfprom",}, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, qfprom_of_match); + +static struct platform_driver qfprom_driver = { + .probe = qfprom_probe, + .remove = qfprom_remove, + .driver = { + .name = "qcom,qfprom", + .of_match_table = qfprom_of_match, + }, +}; +module_platform_driver(qfprom_driver); +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org>"); +MODULE_DESCRIPTION("Qualcomm QFPROM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/sunxi-sid.c b/drivers/nvmem/sunxi-sid.c new file mode 100644 index 000000000000..4b39716516df --- /dev/null +++ b/drivers/nvmem/sunxi-sid.c @@ -0,0 +1,160 @@ +/* + * Allwinner sunXi SoCs Security ID support. + * + * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> + * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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/platform_device.h> +#include <linux/nvmem-provider.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/of.h> + + +static struct nvmem_config econfig = { + .name = "sunxi-sid", + .read_only = true, + .owner = THIS_MODULE, +}; + +struct sunxi_sid { + void __iomem *base; +}; + +/* We read the entire key, due to a 32 bit read alignment requirement. Since we + * want to return the requested byte, this results in somewhat slower code and + * uses 4 times more reads as needed but keeps code simpler. Since the SID is + * only very rarely probed, this is not really an issue. + */ +static u8 sunxi_sid_read_byte(const struct sunxi_sid *sid, + const unsigned int offset) +{ + u32 sid_key; + + sid_key = ioread32be(sid->base + round_down(offset, 4)); + sid_key >>= (offset % 4) * 8; + + return sid_key; /* Only return the last byte */ +} + +static int sunxi_sid_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct sunxi_sid *sid = context; + unsigned int offset = *(u32 *)reg; + u8 *buf = val; + + while (val_size) { + *buf++ = sunxi_sid_read_byte(sid, offset); + val_size--; + offset++; + } + + return 0; +} + +static int sunxi_sid_write(void *context, const void *data, size_t count) +{ + /* Unimplemented */ + return 0; +} + +static struct regmap_bus sunxi_sid_bus = { + .read = sunxi_sid_read, + .write = sunxi_sid_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static bool sunxi_sid_writeable_reg(struct device *dev, unsigned int reg) +{ + return false; +} + +static struct regmap_config sunxi_sid_regmap_config = { + .reg_bits = 32, + .val_bits = 8, + .reg_stride = 1, + .writeable_reg = sunxi_sid_writeable_reg, +}; + +static int sunxi_sid_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct nvmem_device *nvmem; + struct regmap *regmap; + struct sunxi_sid *sid; + + sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); + if (!sid) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sid->base = devm_ioremap_resource(dev, res); + if (IS_ERR(sid->base)) + return PTR_ERR(sid->base); + + sunxi_sid_regmap_config.max_register = resource_size(res) - 1; + + regmap = devm_regmap_init(dev, &sunxi_sid_bus, sid, + &sunxi_sid_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } + econfig.dev = dev; + nvmem = nvmem_register(&econfig); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static int sunxi_sid_remove(struct platform_device *pdev) +{ + struct nvmem_device *nvmem = platform_get_drvdata(pdev); + + return nvmem_unregister(nvmem); +} + +static const struct of_device_id sunxi_sid_of_match[] = { + { .compatible = "allwinner,sun4i-a10-sid" }, + { .compatible = "allwinner,sun7i-a20-sid" }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); + +static struct platform_driver sunxi_sid_driver = { + .probe = sunxi_sid_probe, + .remove = sunxi_sid_remove, + .driver = { + .name = "eeprom-sunxi-sid", + .of_match_table = sunxi_sid_of_match, + }, +}; +module_platform_driver(sunxi_sid_driver); + +MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); +MODULE_DESCRIPTION("Allwinner sunxi security id driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig index c132bddc03f3..ba5c5fc401de 100644 --- a/drivers/pci/host/Kconfig +++ b/drivers/pci/host/Kconfig @@ -30,6 +30,11 @@ config PCI_IMX6 select PCIEPORTBUS select PCIE_DW +config PCI_QCOM + bool "Qualcomm PCIe controller" + default y + depends on ARCH_QCOM && !ARM64 + config PCI_TEGRA bool "NVIDIA Tegra PCIe controller" depends on ARCH_TEGRA && !ARM64 diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile index 140d66f796e4..451d49e3df44 100644 --- a/drivers/pci/host/Makefile +++ b/drivers/pci/host/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o obj-$(CONFIG_PCIE_IPROC) += pcie-iproc.o obj-$(CONFIG_PCIE_IPROC_PLATFORM) += pcie-iproc-platform.o obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-iproc-bcma.o +obj-$(CONFIG_PCI_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..c1150d575535 --- /dev/null +++ b/drivers/pci/host/pci-qcom.c @@ -0,0 +1,968 @@ +/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * QCOM MSM PCIe controller driver. + */ + +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/of_gpio.h> +#include <linux/msi.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/of_address.h> +#include <linux/clk.h> +#include <linux/reset.h> +#include <linux/delay.h> + +#define INT_PCI_MSI_NR (8 * 32) +#define MSM_PCIE_MSI_PHY 0xa0000000 + +#define PCIE20_MSI_CTRL_ADDR (0x820) +#define PCIE20_MSI_CTRL_UPPER_ADDR (0x824) +#define PCIE20_MSI_CTRL_INTR_EN (0x828) +#define PCIE20_MSI_CTRL_INTR_MASK (0x82C) +#define PCIE20_MSI_CTRL_INTR_STATUS (0x830) + +#define PCIE20_MSI_CTRL_MAX 8 +/* Root Complex Port vendor/device IDs */ +#define PCIE_VENDOR_ID_RCP 0x17cb +#define PCIE_DEVICE_ID_RCP 0x0101 + +#define __set(v, a, b) (((v) << (b)) & GENMASK(a, b)) + +#define PCIE20_PARF_PCS_DEEMPH 0x34 +#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN1(x) __set(x, 21, 16) +#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(x) __set(x, 13, 8) +#define PCIE20_PARF_PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(x) __set(x, 5, 0) + +#define PCIE20_PARF_PCS_SWING 0x38 +#define PCIE20_PARF_PCS_SWING_TX_SWING_FULL(x) __set(x, 14, 8) +#define PCIE20_PARF_PCS_SWING_TX_SWING_LOW(x) __set(x, 6, 0) + +#define PCIE20_PARF_PHY_CTRL 0x40 +#define PCIE20_PARF_PHY_CTRL_PHY_TX0_TERM_OFFST(x) __set(x, 20, 16) +#define PCIE20_PARF_PHY_CTRL_PHY_LOS_LEVEL(x) __set(x, 12, 8) +#define PCIE20_PARF_PHY_CTRL_PHY_RTUNE_REQ (1 << 4) +#define PCIE20_PARF_PHY_CTRL_PHY_TEST_BURNIN (1 << 2) +#define PCIE20_PARF_PHY_CTRL_PHY_TEST_BYPASS (1 << 1) +#define PCIE20_PARF_PHY_CTRL_PHY_TEST_PWR_DOWN (1 << 0) + +#define PCIE20_PARF_PHY_REFCLK 0x4C +#define PCIE20_PARF_CONFIG_BITS 0x50 + +#define PCIE20_ELBI_SYS_CTRL 0x04 +#define PCIE20_ELBI_SYS_CTRL_LTSSM_EN 0x01 + +#define PCIE20_CAP 0x70 +#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) + +#define PCIE20_COMMAND_STATUS 0x04 +#define PCIE20_BUSNUMBERS 0x18 +#define PCIE20_MEMORY_BASE_LIMIT 0x20 + +#define PCIE20_AXI_MSTR_RESP_COMP_CTRL0 0x818 +#define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c +#define PCIE20_PLR_IATU_VIEWPORT 0x900 +#define PCIE20_PLR_IATU_CTRL1 0x904 +#define PCIE20_PLR_IATU_CTRL2 0x908 +#define PCIE20_PLR_IATU_LBAR 0x90C +#define PCIE20_PLR_IATU_UBAR 0x910 +#define PCIE20_PLR_IATU_LAR 0x914 +#define PCIE20_PLR_IATU_LTAR 0x918 +#define PCIE20_PLR_IATU_UTAR 0x91c + +#define MSM_PCIE_DEV_CFG_ADDR 0x01000000 + +#define RD 0 +#define WR 1 + +#define MAX_RC_NUM 3 +#define PCIE_BUS_PRIV_DATA(pdev) \ + (((struct pci_sys_data *)pdev->bus->sysdata)->private_data) + +/* PCIe TLP types that we are interested in */ +#define PCI_CFG0_RDWR 0x4 +#define PCI_CFG1_RDWR 0x5 + +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \ +({ \ + unsigned long timeout = jiffies + usecs_to_jiffies(timeout_us); \ + might_sleep_if(timeout_us); \ + for (;;) { \ + (val) = readl(addr); \ + if (cond) \ + break; \ + if (timeout_us && time_after(jiffies, timeout)) { \ + (val) = readl(addr); \ + break; \ + } \ + if (sleep_us) \ + usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \ + } \ + (cond) ? 0 : -ETIMEDOUT; \ +}) + +struct qcom_msi { + struct msi_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] = { + { + .ops = &qcom_pcie_ops, + .nr_controllers = 1, + .swizzle = pci_common_swizzle, + .setup = qcom_pcie_setup, + .map_irq = qcom_pcie_map_irq, + }, + { + .ops = &qcom_pcie_ops, + .nr_controllers = 1, + .swizzle = pci_common_swizzle, + .setup = qcom_pcie_setup, + .map_irq = qcom_pcie_map_irq, + }, + { + .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); + pci_common_init_dev(&pdev->dev, 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 58f5632b27f4..8eef820b216e 100644 --- a/drivers/pinctrl/qcom/Kconfig +++ b/drivers/pinctrl/qcom/Kconfig @@ -76,4 +76,16 @@ config PINCTRL_QCOM_SPMI_PMIC which are using SPMI for communication with SoC. Example PMIC's devices are pm8841, pm8941 and pma8084. +config PINCTRL_QCOM_SSBI_PMIC + tristate "Qualcomm SSBI PMIC pin controller driver" + depends on GPIOLIB && OF + select PINMUX + select PINCONF + select GENERIC_PINCONF + help + This is the pinctrl, pinmux, pinconf and gpiolib driver for the + Qualcomm GPIO and MPP blocks found in the Qualcomm PMIC's chips, + which are using SSBI for communication with SoC. Example PMIC's + devices are pm8058 and pm8921. + endif diff --git a/drivers/pinctrl/qcom/Makefile b/drivers/pinctrl/qcom/Makefile index 3666c703ce88..e321f7ab325b 100644 --- a/drivers/pinctrl/qcom/Makefile +++ b/drivers/pinctrl/qcom/Makefile @@ -9,3 +9,5 @@ obj-$(CONFIG_PINCTRL_MSM8X74) += pinctrl-msm8x74.o obj-$(CONFIG_PINCTRL_MSM8916) += pinctrl-msm8916.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-gpio.o obj-$(CONFIG_PINCTRL_QCOM_SPMI_PMIC) += pinctrl-spmi-mpp.o +obj-$(CONFIG_PINCTRL_QCOM_SSBI_PMIC) += pinctrl-ssbi-gpio.o +obj-$(CONFIG_PINCTRL_QCOM_SSBI_PMIC) += pinctrl-ssbi-mpp.o diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-mpp.c b/drivers/pinctrl/qcom/pinctrl-spmi-mpp.c index 3121de9b6331..8a280ef6304e 100644 --- a/drivers/pinctrl/qcom/pinctrl-spmi-mpp.c +++ b/drivers/pinctrl/qcom/pinctrl-spmi-mpp.c @@ -61,7 +61,9 @@ #define PMIC_MPP_REG_DIG_PULL_CTL 0x42 #define PMIC_MPP_REG_DIG_IN_CTL 0x43 #define PMIC_MPP_REG_EN_CTL 0x46 +#define PMIC_MPP_REG_AOUT_CTL 0x48 #define PMIC_MPP_REG_AIN_CTL 0x4a +#define PMIC_MPP_REG_SINK_CTL 0x4c /* PMIC_MPP_REG_MODE_CTL */ #define PMIC_MPP_REG_MODE_VALUE_MASK 0x1 @@ -85,11 +87,25 @@ #define PMIC_MPP_REG_AIN_ROUTE_SHIFT 0 #define PMIC_MPP_REG_AIN_ROUTE_MASK 0x7 +#define PMIC_MPP_MODE_DIGITAL_INPUT 0 +#define PMIC_MPP_MODE_DIGITAL_OUTPUT 1 +#define PMIC_MPP_MODE_DIGITAL_BIDIR 2 +#define PMIC_MPP_MODE_ANALOG_BIDIR 3 +#define PMIC_MPP_MODE_ANALOG_INPUT 4 +#define PMIC_MPP_MODE_ANALOG_OUTPUT 5 +#define PMIC_MPP_MODE_CURRENT_SINK 6 + +#define PMIC_MPP_SELECTOR_NORMAL 0 +#define PMIC_MPP_SELECTOR_PAIRED 1 +#define PMIC_MPP_SELECTOR_DTEST_FIRST 4 + #define PMIC_MPP_PHYSICAL_OFFSET 1 /* Qualcomm specific pin configurations */ #define PMIC_MPP_CONF_AMUX_ROUTE (PIN_CONFIG_END + 1) -#define PMIC_MPP_CONF_ANALOG_MODE (PIN_CONFIG_END + 2) +#define PMIC_MPP_CONF_ANALOG_LEVEL (PIN_CONFIG_END + 2) +#define PMIC_MPP_CONF_DTEST_SELECTOR (PIN_CONFIG_END + 3) +#define PMIC_MPP_CONF_PAIRED (PIN_CONFIG_END + 4) /** * struct pmic_mpp_pad - keep current MPP settings @@ -99,13 +115,15 @@ * @out_value: Cached pin output value. * @output_enabled: Set to true if MPP output logic is enabled. * @input_enabled: Set to true if MPP input buffer logic is enabled. - * @analog_mode: Set to true when MPP should operate in Analog Input, Analog - * Output or Bidirectional Analog mode. + * @paired: Pin operates in paired mode * @num_sources: Number of power-sources supported by this MPP. * @power_source: Current power-source used. * @amux_input: Set the source for analog input. + * @aout_level: Analog output level * @pullup: Pullup resistor value. Valid in Bidirectional mode only. * @function: See pmic_mpp_functions[]. + * @drive_strength: Amount of current in sink mode + * @dtest: DTEST route selector */ struct pmic_mpp_pad { u16 base; @@ -114,12 +132,15 @@ struct pmic_mpp_pad { bool out_value; bool output_enabled; bool input_enabled; - bool analog_mode; + bool paired; unsigned int num_sources; unsigned int power_source; unsigned int amux_input; + unsigned int aout_level; unsigned int pullup; unsigned int function; + unsigned int drive_strength; + unsigned int dtest; }; struct pmic_mpp_state { @@ -129,25 +150,32 @@ struct pmic_mpp_state { struct gpio_chip chip; }; -struct pmic_mpp_bindings { - const char *property; - unsigned param; +static const struct pinconf_generic_params pmic_mpp_bindings[] = { + {"qcom,amux-route", PMIC_MPP_CONF_AMUX_ROUTE, 0}, + {"qcom,analog-level", PMIC_MPP_CONF_ANALOG_LEVEL, 0}, + {"qcom,dtest", PMIC_MPP_CONF_DTEST_SELECTOR, 0}, + {"qcom,paired", PMIC_MPP_CONF_PAIRED, 0}, }; -static struct pmic_mpp_bindings pmic_mpp_bindings[] = { - {"qcom,amux-route", PMIC_MPP_CONF_AMUX_ROUTE}, - {"qcom,analog-mode", PMIC_MPP_CONF_ANALOG_MODE}, +#ifdef CONFIG_DEBUG_FS +static const struct pin_config_item pmic_conf_items[] = { + PCONFDUMP(PMIC_MPP_CONF_AMUX_ROUTE, "analog mux", NULL, true), + PCONFDUMP(PMIC_MPP_CONF_ANALOG_LEVEL, "analog level", NULL, true), + PCONFDUMP(PMIC_MPP_CONF_DTEST_SELECTOR, "dtest", NULL, true), + PCONFDUMP(PMIC_MPP_CONF_PAIRED, "paired", NULL, false), }; +#endif static const char *const pmic_mpp_groups[] = { "mpp1", "mpp2", "mpp3", "mpp4", "mpp5", "mpp6", "mpp7", "mpp8", }; +#define PMIC_MPP_DIGITAL 0 +#define PMIC_MPP_ANALOG 1 +#define PMIC_MPP_SINK 2 + static const char *const pmic_mpp_functions[] = { - PMIC_MPP_FUNC_NORMAL, PMIC_MPP_FUNC_PAIRED, - "reserved1", "reserved2", - PMIC_MPP_FUNC_DTEST1, PMIC_MPP_FUNC_DTEST2, - PMIC_MPP_FUNC_DTEST3, PMIC_MPP_FUNC_DTEST4, + "digital", "analog", "sink" }; static inline struct pmic_mpp_state *to_mpp_state(struct gpio_chip *chip) @@ -204,118 +232,11 @@ static int pmic_mpp_get_group_pins(struct pinctrl_dev *pctldev, return 0; } -static int pmic_mpp_parse_dt_config(struct device_node *np, - struct pinctrl_dev *pctldev, - unsigned long **configs, - unsigned int *nconfs) -{ - struct pmic_mpp_bindings *par; - unsigned long cfg; - int ret, i; - u32 val; - - for (i = 0; i < ARRAY_SIZE(pmic_mpp_bindings); i++) { - par = &pmic_mpp_bindings[i]; - ret = of_property_read_u32(np, par->property, &val); - - /* property not found */ - if (ret == -EINVAL) - continue; - - /* use zero as default value, when no value is specified */ - if (ret) - val = 0; - - dev_dbg(pctldev->dev, "found %s with value %u\n", - par->property, val); - - cfg = pinconf_to_config_packed(par->param, val); - - ret = pinctrl_utils_add_config(pctldev, configs, nconfs, cfg); - if (ret) - return ret; - } - - return 0; -} - -static int pmic_mpp_dt_subnode_to_map(struct pinctrl_dev *pctldev, - struct device_node *np, - struct pinctrl_map **map, - unsigned *reserv, unsigned *nmaps, - enum pinctrl_map_type type) -{ - unsigned long *configs = NULL; - unsigned nconfs = 0; - struct property *prop; - const char *group; - int ret; - - ret = pmic_mpp_parse_dt_config(np, pctldev, &configs, &nconfs); - if (ret < 0) - return ret; - - if (!nconfs) - return 0; - - ret = of_property_count_strings(np, "pins"); - if (ret < 0) - goto exit; - - ret = pinctrl_utils_reserve_map(pctldev, map, reserv, nmaps, ret); - if (ret < 0) - goto exit; - - of_property_for_each_string(np, "pins", prop, group) { - ret = pinctrl_utils_add_map_configs(pctldev, map, - reserv, nmaps, group, - configs, nconfs, type); - if (ret < 0) - break; - } -exit: - kfree(configs); - return ret; -} - -static int pmic_mpp_dt_node_to_map(struct pinctrl_dev *pctldev, - struct device_node *np_config, - struct pinctrl_map **map, unsigned *nmaps) -{ - struct device_node *np; - enum pinctrl_map_type type; - unsigned reserv; - int ret; - - ret = 0; - *map = NULL; - *nmaps = 0; - reserv = 0; - type = PIN_MAP_TYPE_CONFIGS_GROUP; - - for_each_child_of_node(np_config, np) { - ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map, - &reserv, nmaps, type); - if (ret) - break; - - ret = pmic_mpp_dt_subnode_to_map(pctldev, np, map, &reserv, - nmaps, type); - if (ret) - break; - } - - if (ret < 0) - pinctrl_utils_dt_free_map(pctldev, *map, *nmaps); - - return ret; -} - static const struct pinctrl_ops pmic_mpp_pinctrl_ops = { .get_groups_count = pmic_mpp_get_groups_count, .get_group_name = pmic_mpp_get_group_name, .get_group_pins = pmic_mpp_get_group_pins, - .dt_node_to_map = pmic_mpp_dt_node_to_map, + .dt_node_to_map = pinconf_generic_dt_node_to_map_group, .dt_free_map = pinctrl_utils_dt_free_map, }; @@ -340,6 +261,53 @@ static int pmic_mpp_get_function_groups(struct pinctrl_dev *pctldev, return 0; } +static int pmic_mpp_write_mode_ctl(struct pmic_mpp_state *state, + struct pmic_mpp_pad *pad) +{ + unsigned int mode; + unsigned int sel; + unsigned int val; + unsigned int en; + + switch (pad->function) { + case PMIC_MPP_ANALOG: + if (pad->input_enabled && pad->output_enabled) + mode = PMIC_MPP_MODE_ANALOG_BIDIR; + else if (pad->input_enabled) + mode = PMIC_MPP_MODE_ANALOG_INPUT; + else + mode = PMIC_MPP_MODE_ANALOG_OUTPUT; + break; + case PMIC_MPP_DIGITAL: + if (pad->input_enabled && pad->output_enabled) + mode = PMIC_MPP_MODE_DIGITAL_BIDIR; + else if (pad->input_enabled) + mode = PMIC_MPP_MODE_DIGITAL_INPUT; + else + mode = PMIC_MPP_MODE_DIGITAL_OUTPUT; + break; + case PMIC_MPP_SINK: + default: + mode = PMIC_MPP_MODE_CURRENT_SINK; + break; + } + + if (pad->dtest) + sel = PMIC_MPP_SELECTOR_DTEST_FIRST + pad->dtest - 1; + else if (pad->paired) + sel = PMIC_MPP_SELECTOR_PAIRED; + else + sel = PMIC_MPP_SELECTOR_NORMAL; + + en = !!pad->out_value; + + val = mode << PMIC_MPP_REG_MODE_DIR_SHIFT | + sel << PMIC_MPP_REG_MODE_FUNCTION_SHIFT | + en; + + return pmic_mpp_write(state, pad, PMIC_MPP_REG_MODE_CTL, val); +} + static int pmic_mpp_set_mux(struct pinctrl_dev *pctldev, unsigned function, unsigned pin) { @@ -352,31 +320,7 @@ static int pmic_mpp_set_mux(struct pinctrl_dev *pctldev, unsigned function, pad->function = function; - if (!pad->analog_mode) { - val = 0; /* just digital input */ - if (pad->output_enabled) { - if (pad->input_enabled) - val = 2; /* digital input and output */ - else - val = 1; /* just digital output */ - } - } else { - val = 4; /* just analog input */ - if (pad->output_enabled) { - if (pad->input_enabled) - val = 3; /* analog input and output */ - else - val = 5; /* just analog output */ - } - } - - val = val << PMIC_MPP_REG_MODE_DIR_SHIFT; - val |= pad->function << PMIC_MPP_REG_MODE_FUNCTION_SHIFT; - val |= pad->out_value & PMIC_MPP_REG_MODE_VALUE_MASK; - - ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_MODE_CTL, val); - if (ret < 0) - return ret; + ret = pmic_mpp_write_mode_ctl(state, pad); val = pad->is_enabled << PMIC_MPP_REG_MASTER_EN_SHIFT; @@ -433,11 +377,20 @@ static int pmic_mpp_config_get(struct pinctrl_dev *pctldev, case PIN_CONFIG_OUTPUT: arg = pad->out_value; break; + case PMIC_MPP_CONF_DTEST_SELECTOR: + arg = pad->dtest; + break; case PMIC_MPP_CONF_AMUX_ROUTE: arg = pad->amux_input; break; - case PMIC_MPP_CONF_ANALOG_MODE: - arg = pad->analog_mode; + case PMIC_MPP_CONF_PAIRED: + arg = pad->paired; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + arg = pad->drive_strength; + break; + case PMIC_MPP_CONF_ANALOG_LEVEL: + arg = pad->aout_level; break; default: return -EINVAL; @@ -459,6 +412,9 @@ static int pmic_mpp_config_set(struct pinctrl_dev *pctldev, unsigned int pin, pad = pctldev->desc->pins[pin].drv_data; + /* Make it possible to enable the pin, by not setting high impedance */ + pad->is_enabled = true; + for (i = 0; i < nconfs; i++) { param = pinconf_to_config_param(configs[i]); arg = pinconf_to_config_argument(configs[i]); @@ -497,13 +453,22 @@ static int pmic_mpp_config_set(struct pinctrl_dev *pctldev, unsigned int pin, pad->output_enabled = true; pad->out_value = arg; break; + case PMIC_MPP_CONF_DTEST_SELECTOR: + pad->dtest = arg; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + arg = pad->drive_strength; + break; case PMIC_MPP_CONF_AMUX_ROUTE: if (arg >= PMIC_MPP_AMUX_ROUTE_ABUS4) return -EINVAL; pad->amux_input = arg; break; - case PMIC_MPP_CONF_ANALOG_MODE: - pad->analog_mode = true; + case PMIC_MPP_CONF_ANALOG_LEVEL: + pad->aout_level = arg; + break; + case PMIC_MPP_CONF_PAIRED: + pad->paired = !!arg; break; default: return -EINVAL; @@ -528,29 +493,17 @@ static int pmic_mpp_config_set(struct pinctrl_dev *pctldev, unsigned int pin, if (ret < 0) return ret; - if (!pad->analog_mode) { - val = 0; /* just digital input */ - if (pad->output_enabled) { - if (pad->input_enabled) - val = 2; /* digital input and output */ - else - val = 1; /* just digital output */ - } - } else { - val = 4; /* just analog input */ - if (pad->output_enabled) { - if (pad->input_enabled) - val = 3; /* analog input and output */ - else - val = 5; /* just analog output */ - } - } + ret = pmic_mpp_write(state, pad, PMIC_MPP_REG_AOUT_CTL, pad->aout_level); + if (ret < 0) + return ret; - val = val << PMIC_MPP_REG_MODE_DIR_SHIFT; - val |= pad->function << PMIC_MPP_REG_MODE_FUNCTION_SHIFT; - val |= pad->out_value & PMIC_MPP_REG_MODE_VALUE_MASK; + ret = pmic_mpp_write_mode_ctl(state, pad); + if (ret < 0) + return ret; - return pmic_mpp_write(state, pad, PMIC_MPP_REG_MODE_CTL, val); + val = pad->is_enabled << PMIC_MPP_REG_MASTER_EN_SHIFT; + + return pmic_mpp_write(state, pad, PMIC_MPP_REG_EN_CTL, val); } static void pmic_mpp_config_dbg_show(struct pinctrl_dev *pctldev, @@ -558,20 +511,17 @@ static void pmic_mpp_config_dbg_show(struct pinctrl_dev *pctldev, { struct pmic_mpp_state *state = pinctrl_dev_get_drvdata(pctldev); struct pmic_mpp_pad *pad; - int ret, val; + int ret; static const char *const biases[] = { "0.6kOhm", "10kOhm", "30kOhm", "Disabled" }; - pad = pctldev->desc->pins[pin].drv_data; seq_printf(s, " mpp%-2d:", pin + PMIC_MPP_PHYSICAL_OFFSET); - val = pmic_mpp_read(state, pad, PMIC_MPP_REG_EN_CTL); - - if (val < 0 || !(val >> PMIC_MPP_REG_MASTER_EN_SHIFT)) { + if (!pad->is_enabled) { seq_puts(s, " ---"); } else { @@ -585,15 +535,20 @@ static void pmic_mpp_config_dbg_show(struct pinctrl_dev *pctldev, } seq_printf(s, " %-4s", pad->output_enabled ? "out" : "in"); - seq_printf(s, " %-4s", pad->analog_mode ? "ana" : "dig"); seq_printf(s, " %-7s", pmic_mpp_functions[pad->function]); seq_printf(s, " vin-%d", pad->power_source); + seq_printf(s, " %d", pad->aout_level); seq_printf(s, " %-8s", biases[pad->pullup]); seq_printf(s, " %-4s", pad->out_value ? "high" : "low"); + if (pad->dtest) + seq_printf(s, " dtest%d", pad->dtest); + if (pad->paired) + seq_puts(s, " paired"); } } static const struct pinconf_ops pmic_mpp_pinconf_ops = { + .is_generic = true, .pin_config_group_get = pmic_mpp_config_get, .pin_config_group_set = pmic_mpp_config_set, .pin_config_group_dbg_show = pmic_mpp_config_dbg_show, @@ -709,6 +664,7 @@ static int pmic_mpp_populate(struct pmic_mpp_state *state, struct pmic_mpp_pad *pad) { int type, subtype, val, dir; + unsigned int sel; type = pmic_mpp_read(state, pad, PMIC_MPP_REG_TYPE); if (type < 0) @@ -751,43 +707,53 @@ static int pmic_mpp_populate(struct pmic_mpp_state *state, dir &= PMIC_MPP_REG_MODE_DIR_MASK; switch (dir) { - case 0: + case PMIC_MPP_MODE_DIGITAL_INPUT: pad->input_enabled = true; pad->output_enabled = false; - pad->analog_mode = false; + pad->function = PMIC_MPP_DIGITAL; break; - case 1: + case PMIC_MPP_MODE_DIGITAL_OUTPUT: pad->input_enabled = false; pad->output_enabled = true; - pad->analog_mode = false; + pad->function = PMIC_MPP_DIGITAL; break; - case 2: + case PMIC_MPP_MODE_DIGITAL_BIDIR: pad->input_enabled = true; pad->output_enabled = true; - pad->analog_mode = false; + pad->function = PMIC_MPP_DIGITAL; break; - case 3: + case PMIC_MPP_MODE_ANALOG_BIDIR: pad->input_enabled = true; pad->output_enabled = true; - pad->analog_mode = true; + pad->function = PMIC_MPP_ANALOG; break; - case 4: + case PMIC_MPP_MODE_ANALOG_INPUT: pad->input_enabled = true; pad->output_enabled = false; - pad->analog_mode = true; + pad->function = PMIC_MPP_ANALOG; break; - case 5: + case PMIC_MPP_MODE_ANALOG_OUTPUT: pad->input_enabled = false; pad->output_enabled = true; - pad->analog_mode = true; + pad->function = PMIC_MPP_ANALOG; + break; + case PMIC_MPP_MODE_CURRENT_SINK: + pad->input_enabled = false; + pad->output_enabled = true; + pad->function = PMIC_MPP_SINK; break; default: dev_err(state->dev, "unknown MPP direction\n"); return -ENODEV; } - pad->function = val >> PMIC_MPP_REG_MODE_FUNCTION_SHIFT; - pad->function &= PMIC_MPP_REG_MODE_FUNCTION_MASK; + sel = val >> PMIC_MPP_REG_MODE_FUNCTION_SHIFT; + sel &= PMIC_MPP_REG_MODE_FUNCTION_MASK; + + if (sel >= PMIC_MPP_SELECTOR_DTEST_FIRST) + pad->dtest = sel + 1; + else if (sel == PMIC_MPP_SELECTOR_PAIRED) + pad->paired = true; val = pmic_mpp_read(state, pad, PMIC_MPP_REG_DIG_VIN_CTL); if (val < 0) @@ -810,8 +776,22 @@ static int pmic_mpp_populate(struct pmic_mpp_state *state, pad->amux_input = val >> PMIC_MPP_REG_AIN_ROUTE_SHIFT; pad->amux_input &= PMIC_MPP_REG_AIN_ROUTE_MASK; - /* Pin could be disabled with PIN_CONFIG_BIAS_HIGH_IMPEDANCE */ - pad->is_enabled = true; + val = pmic_mpp_read(state, pad, PMIC_MPP_REG_SINK_CTL); + if (val < 0) + return val; + + pad->drive_strength = val; + + val = pmic_mpp_read(state, pad, PMIC_MPP_REG_AOUT_CTL); + if (val < 0) + return val; + + val = pmic_mpp_read(state, pad, PMIC_MPP_REG_EN_CTL); + if (val < 0) + return val; + + pad->is_enabled = !!val; + return 0; } @@ -866,6 +846,12 @@ static int pmic_mpp_probe(struct platform_device *pdev) pctrldesc->pins = pindesc; pctrldesc->npins = npins; + pctrldesc->num_custom_params = ARRAY_SIZE(pmic_mpp_bindings); + pctrldesc->custom_params = pmic_mpp_bindings; +#ifdef CONFIG_DEBUG_FS + pctrldesc->custom_conf_items = pmic_conf_items; +#endif + for (i = 0; i < npins; i++, pindesc++) { pad = &pads[i]; pindesc->drv_data = pad; diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c b/drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c new file mode 100644 index 000000000000..fb8e9164b16c --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-gpio.c @@ -0,0 +1,795 @@ +/* + * Copyright (c) 2015, 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/interrupt.h> + +#include <dt-bindings/pinctrl/qcom,pmic-gpio.h> + +#include "../core.h" +#include "../pinctrl-utils.h" + +/* mode */ +#define PM8XXX_GPIO_MODE_ENABLED BIT(0) +#define PM8XXX_GPIO_MODE_INPUT 0 +#define PM8XXX_GPIO_MODE_OUTPUT 2 + +/* 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_BANK_WRITE BIT(7) + +#define PM8XXX_MAX_GPIOS 44 + +/* custom pinconf parameters */ +#define PM8XXX_QCOM_DRIVE_STRENGH (PIN_CONFIG_END + 1) +#define PM8XXX_QCOM_PULL_UP_STRENGTH (PIN_CONFIG_END + 2) + +/** + * struct pm8xxx_pin_data - dynamic configuration for a pin + * @reg: address of the control register + * @irq: IRQ from the PMIC interrupt controller + * @power_source: logical selected voltage source, mapping in static data + * is used translate to register values + * @mode: operating mode for the pin (input/output) + * @open_drain: output buffer configured as open-drain (vs push-pull) + * @output_value: configured output value + * @bias: register view of configured bias + * @pull_up_strength: placeholder for selected pull up strength + * only used to configure bias when pull up is selected + * @output_strength: selector of output-strength + * @disable: pin disabled / configured as tristate + * @function: pinmux selector + * @inverted: pin logic is inverted + */ +struct pm8xxx_pin_data { + unsigned reg; + int irq; + u8 power_source; + u8 mode; + bool open_drain; + bool output_value; + u8 bias; + u8 pull_up_strength; + u8 output_strength; + bool disable; + u8 function; + bool inverted; +}; + +struct pm8xxx_gpio { + struct device *dev; + struct regmap *regmap; + struct pinctrl_dev *pctrl; + struct gpio_chip chip; + + struct pinctrl_desc desc; + unsigned npins; +}; + +static const struct pinconf_generic_params pm8xxx_gpio_bindings[] = { + {"qcom,drive-strength", PM8XXX_QCOM_DRIVE_STRENGH, 0}, + {"qcom,pull-up-strength", PM8XXX_QCOM_PULL_UP_STRENGTH, 0}, +}; + +#ifdef CONFIG_DEBUG_FS +static const struct pin_config_item pm8xxx_conf_items[ARRAY_SIZE(pm8xxx_gpio_bindings)] = { + PCONFDUMP(PM8XXX_QCOM_DRIVE_STRENGH, "drive-strength", NULL, true), + PCONFDUMP(PM8XXX_QCOM_PULL_UP_STRENGTH, "pull up strength", NULL, true), +}; +#endif + +static const char * const pm8xxx_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_read_bank(struct pm8xxx_gpio *pctrl, + struct pm8xxx_pin_data *pin, int bank) +{ + unsigned int val = bank << 4; + int ret; + + ret = regmap_write(pctrl->regmap, pin->reg, val); + if (ret) { + dev_err(pctrl->dev, "failed to select bank %d\n", bank); + return ret; + } + + ret = regmap_read(pctrl->regmap, pin->reg, &val); + if (ret) { + dev_err(pctrl->dev, "failed to read register %d\n", bank); + return ret; + } + + return val; +} + +static int pm8xxx_write_bank(struct pm8xxx_gpio *pctrl, + struct pm8xxx_pin_data *pin, + int bank, + u8 val) +{ + int ret; + + val |= PM8XXX_BANK_WRITE; + val |= bank << 4; + + ret = regmap_write(pctrl->regmap, pin->reg, val); + if (ret) + dev_err(pctrl->dev, "failed to write register\n"); + + return ret; +} + +static int pm8xxx_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return pctrl->npins; +} + +static const char *pm8xxx_get_group_name(struct pinctrl_dev *pctldev, + unsigned group) +{ + return pm8xxx_groups[group]; +} + + +static int pm8xxx_get_group_pins(struct pinctrl_dev *pctldev, + unsigned group, + const unsigned **pins, + unsigned *num_pins) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + + *pins = &pctrl->desc.pins[group].number; + *num_pins = 1; + + return 0; +} + +static const struct pinctrl_ops pm8xxx_pinctrl_ops = { + .get_groups_count = pm8xxx_get_groups_count, + .get_group_name = pm8xxx_get_group_name, + .get_group_pins = pm8xxx_get_group_pins, + .dt_node_to_map = pinconf_generic_dt_node_to_map_group, + .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_groups; + *num_groups = pctrl->npins; + return 0; +} + +static int pm8xxx_pinmux_set_mux(struct pinctrl_dev *pctldev, + unsigned function, + unsigned group) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[group].drv_data; + u8 val; + + pin->function = function; + val = pin->function << 1; + + pm8xxx_write_bank(pctrl, pin, 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_set_mux, +}; + +static int pm8xxx_pin_config_get(struct pinctrl_dev *pctldev, + unsigned int offset, + unsigned long *config) +{ + struct pm8xxx_gpio *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + 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 PIN_CONFIG_BIAS_PULL_UP: + arg = pin->bias <= PM8XXX_GPIO_BIAS_PU_1P5_30; + break; + case PM8XXX_QCOM_PULL_UP_STRENGTH: + arg = pin->pull_up_strength; + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + arg = pin->disable; + break; + case PIN_CONFIG_INPUT_ENABLE: + arg = pin->mode == PM8XXX_GPIO_MODE_INPUT; + break; + case PIN_CONFIG_OUTPUT: + if (pin->mode & PM8XXX_GPIO_MODE_OUTPUT) + arg = pin->output_value; + else + arg = 0; + break; + case PIN_CONFIG_POWER_SOURCE: + arg = pin->power_source; + break; + case PM8XXX_QCOM_DRIVE_STRENGH: + arg = pin->output_strength; + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + arg = !pin->open_drain; + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + arg = pin->open_drain; + break; + default: + return -EINVAL; + } + + *config = pinconf_to_config_packed(param, arg); + + return 0; +} + +static int pm8xxx_pin_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_pin_data *pin = pctrl->desc.pins[offset].drv_data; + 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_QCOM_PULL_UP_STRENGTH: + if (arg > PM8XXX_GPIO_BIAS_PU_1P5_30) { + dev_err(pctrl->dev, "invalid pull-up strength\n"); + return -EINVAL; + } + pin->pull_up_strength = arg; + /* FALLTHROUGH */ + case PIN_CONFIG_BIAS_PULL_UP: + pin->bias = pin->pull_up_strength; + 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->mode = PM8XXX_GPIO_MODE_INPUT; + banks |= BIT(0) | BIT(1); + break; + case PIN_CONFIG_OUTPUT: + pin->mode = PM8XXX_GPIO_MODE_OUTPUT; + pin->output_value = !!arg; + banks |= BIT(0) | BIT(1); + break; + case PIN_CONFIG_POWER_SOURCE: + pin->power_source = arg; + banks |= BIT(0); + break; + case PM8XXX_QCOM_DRIVE_STRENGH: + 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->open_drain = 0; + banks |= BIT(1); + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + pin->open_drain = 1; + banks |= BIT(1); + break; + default: + dev_err(pctrl->dev, + "unsupported config parameter: %x\n", + param); + return -EINVAL; + } + } + + if (banks & BIT(0)) { + val = pin->power_source << 1; + val |= PM8XXX_GPIO_MODE_ENABLED; + pm8xxx_write_bank(pctrl, pin, 0, val); + } + + if (banks & BIT(1)) { + val = pin->mode << 2; + val |= pin->open_drain << 1; + val |= pin->output_value; + pm8xxx_write_bank(pctrl, pin, 1, val); + } + + if (banks & BIT(2)) { + val = pin->bias << 1; + pm8xxx_write_bank(pctrl, pin, 2, val); + } + + if (banks & BIT(3)) { + val = pin->output_strength << 2; + val |= pin->disable; + pm8xxx_write_bank(pctrl, pin, 3, val); + } + + if (banks & BIT(4)) { + val = pin->function << 1; + pm8xxx_write_bank(pctrl, pin, 4, val); + } + + if (banks & BIT(5)) { + val = 0; + if (!pin->inverted) + val |= BIT(3); + pm8xxx_write_bank(pctrl, pin, 5, val); + } + + return 0; +} + +static const struct pinconf_ops pm8xxx_pinconf_ops = { + .is_generic = true, + .pin_config_group_get = pm8xxx_pin_config_get, + .pin_config_group_set = pm8xxx_pin_config_set, +}; + +static struct pinctrl_desc pm8xxx_pinctrl_desc = { + .name = "pm8xxx_gpio", + .pctlops = &pm8xxx_pinctrl_ops, + .pmxops = &pm8xxx_pinmux_ops, + .confops = &pm8xxx_pinconf_ops, + .owner = THIS_MODULE, +}; + +static int pm8xxx_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct pm8xxx_gpio *pctrl = container_of(chip, struct pm8xxx_gpio, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + u8 val; + + pin->mode = PM8XXX_GPIO_MODE_INPUT; + val = pin->mode << 2; + + pm8xxx_write_bank(pctrl, pin, 1, val); + + return 0; +} + +static int pm8xxx_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, + int value) +{ + struct pm8xxx_gpio *pctrl = container_of(chip, struct pm8xxx_gpio, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + u8 val; + + pin->mode = PM8XXX_GPIO_MODE_OUTPUT; + pin->output_value = !!value; + + val = pin->mode << 2; + val |= pin->open_drain << 1; + val |= pin->output_value; + + pm8xxx_write_bank(pctrl, pin, 1, val); + + return 0; +} + +static int pm8xxx_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_gpio *pctrl = container_of(chip, struct pm8xxx_gpio, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + bool state; + int ret; + + if (pin->mode == PM8XXX_GPIO_MODE_OUTPUT) { + ret = pin->output_value; + } else { + ret = irq_get_irqchip_state(pin->irq, IRQCHIP_STATE_LINE_LEVEL, &state); + if (!ret) + ret = !!state; + } + + return ret; +} + +static void pm8xxx_gpio_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct pm8xxx_gpio *pctrl = container_of(chip, struct pm8xxx_gpio, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + u8 val; + + pin->output_value = !!value; + + val = pin->mode << 2; + val |= pin->open_drain << 1; + val |= pin->output_value; + + pm8xxx_write_bank(pctrl, pin, 1, val); +} + +static int pm8xxx_gpio_of_xlate(struct gpio_chip *chip, + const struct of_phandle_args *gpio_desc, + u32 *flags) +{ + if (chip->of_gpio_n_cells < 2) + return -EINVAL; + + if (flags) + *flags = gpio_desc->args[1]; + + return gpio_desc->args[0] - 1; +} + + +static int pm8xxx_gpio_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_gpio *pctrl = container_of(chip, struct pm8xxx_gpio, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + + 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 = container_of(chip, struct pm8xxx_gpio, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + + static const char * const modes[] = { + "in", "both", "out", "off" + }; + 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", modes[pin->mode]); + 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->open_drain]); + seq_printf(s, " %-4s", pin->output_value ? "high" : "low"); + seq_printf(s, " %-7s", strengths[pin->output_strength]); + if (pin->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, + .of_xlate = pm8xxx_gpio_of_xlate, + .to_irq = pm8xxx_gpio_to_irq, + .dbg_show = pm8xxx_gpio_dbg_show, + .owner = THIS_MODULE, +}; + +static int pm8xxx_pin_populate(struct pm8xxx_gpio *pctrl, + struct pm8xxx_pin_data *pin) +{ + int val; + + val = pm8xxx_read_bank(pctrl, pin, 0); + if (val < 0) + return val; + + pin->power_source = (val >> 1) & 0x7; + + val = pm8xxx_read_bank(pctrl, pin, 1); + if (val < 0) + return val; + + pin->mode = (val >> 2) & 0x3; + pin->open_drain = !!(val & BIT(1)); + pin->output_value = val & BIT(0); + + val = pm8xxx_read_bank(pctrl, pin, 2); + if (val < 0) + return val; + + pin->bias = (val >> 1) & 0x7; + if (pin->bias <= PM8XXX_GPIO_BIAS_PU_1P5_30) + pin->pull_up_strength = pin->bias; + else + pin->pull_up_strength = PM8XXX_GPIO_BIAS_PU_30; + + val = pm8xxx_read_bank(pctrl, pin, 3); + if (val < 0) + return val; + + pin->output_strength = (val >> 2) & 0x3; + pin->disable = val & BIT(0); + + val = pm8xxx_read_bank(pctrl, pin, 4); + if (val < 0) + return val; + + pin->function = (val >> 1) & 0x7; + + val = pm8xxx_read_bank(pctrl, pin, 5); + if (val < 0) + return val; + + pin->inverted = !(val & BIT(3)); + + return 0; +} + +static const struct of_device_id pm8xxx_gpio_of_match[] = { + { .compatible = "qcom,pm8018-gpio", .data = (void *)6 }, + { .compatible = "qcom,pm8038-gpio", .data = (void *)12 }, + { .compatible = "qcom,pm8058-gpio", .data = (void *)40 }, + { .compatible = "qcom,pm8917-gpio", .data = (void *)38 }, + { .compatible = "qcom,pm8921-gpio", .data = (void *)44 }, + { }, +}; +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_pin_data *pin_data; + struct pinctrl_pin_desc *pins; + 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->npins = (unsigned)match->data; + + pctrl->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!pctrl->regmap) { + dev_err(&pdev->dev, "parent regmap unavailable\n"); + return -ENXIO; + } + + pctrl->desc = pm8xxx_pinctrl_desc; + pctrl->desc.npins = pctrl->npins; + + pins = devm_kcalloc(&pdev->dev, + pctrl->desc.npins, + sizeof(struct pinctrl_pin_desc), + GFP_KERNEL); + if (!pins) + return -ENOMEM; + + pin_data = devm_kcalloc(&pdev->dev, + pctrl->desc.npins, + sizeof(struct pm8xxx_pin_data), + GFP_KERNEL); + if (!pin_data) + return -ENOMEM; + + for (i = 0; i < pctrl->desc.npins; i++) { + pin_data[i].reg = SSBI_REG_ADDR_GPIO(i); + pin_data[i].irq = platform_get_irq(pdev, i); + if (pin_data[i].irq < 0) { + dev_err(&pdev->dev, + "missing interrupts for pin %d\n", i); + return pin_data[i].irq; + } + + ret = pm8xxx_pin_populate(pctrl, &pin_data[i]); + if (ret) + return ret; + + pins[i].number = i; + pins[i].name = pm8xxx_groups[i]; + pins[i].drv_data = &pin_data[i]; + } + pctrl->desc.pins = pins; + + pctrl->desc.num_custom_params = ARRAY_SIZE(pm8xxx_gpio_bindings); + pctrl->desc.custom_params = pm8xxx_gpio_bindings; +#ifdef CONFIG_DEBUG_FS + pctrl->desc.custom_conf_items = pm8xxx_conf_items; +#endif + + pctrl->pctrl = pinctrl_register(&pctrl->desc, &pdev->dev, pctrl); + if (!pctrl->pctrl) { + dev_err(&pdev->dev, "couldn't register pm8xxx gpio driver\n"); + return -ENODEV; + } + + pctrl->chip = pm8xxx_gpio_template; + pctrl->chip.base = -1; + pctrl->chip.dev = &pdev->dev; + pctrl->chip.of_node = pdev->dev.of_node; + pctrl->chip.of_gpio_n_cells = 2; + pctrl->chip.label = dev_name(pctrl->dev); + pctrl->chip.ngpio = pctrl->npins; + 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), + 0, 0, pctrl->chip.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_gpiochip: + gpiochip_remove(&pctrl->chip); + +unregister_pinctrl: + pinctrl_unregister(pctrl->pctrl); + + 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 = "pm8xxx_gpio", + .of_match_table = pm8xxx_gpio_of_match, + }, + .probe = pm8xxx_gpio_probe, + .remove = pm8xxx_gpio_remove, +}; + +module_platform_driver(pm8xxx_gpio_driver); + +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); +MODULE_DESCRIPTION("Qualcomm PM8xxx GPIO driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pinctrl/qcom/pinctrl-ssbi-mpp.c b/drivers/pinctrl/qcom/pinctrl-ssbi-mpp.c new file mode 100644 index 000000000000..2b82d373c057 --- /dev/null +++ b/drivers/pinctrl/qcom/pinctrl-ssbi-mpp.c @@ -0,0 +1,886 @@ +/* + * Copyright (c) 2015, 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/interrupt.h> + +#include <dt-bindings/pinctrl/qcom,pmic-mpp.h> + +#include "../core.h" +#include "../pinctrl-utils.h" + +/* MPP registers */ +#define SSBI_REG_ADDR_MPP_BASE 0x50 +#define SSBI_REG_ADDR_MPP(n) (SSBI_REG_ADDR_MPP_BASE + n) + +/* MPP Type: type */ +#define PM8XXX_MPP_TYPE_D_INPUT 0 +#define PM8XXX_MPP_TYPE_D_OUTPUT 1 +#define PM8XXX_MPP_TYPE_D_BI_DIR 2 +#define PM8XXX_MPP_TYPE_A_INPUT 3 +#define PM8XXX_MPP_TYPE_A_OUTPUT 4 +#define PM8XXX_MPP_TYPE_SINK 5 +#define PM8XXX_MPP_TYPE_DTEST_SINK 6 +#define PM8XXX_MPP_TYPE_DTEST_OUTPUT 7 + +/* Digital Input: control */ +#define PM8XXX_MPP_DIN_TO_INT 0 +#define PM8XXX_MPP_DIN_TO_DBUS1 1 +#define PM8XXX_MPP_DIN_TO_DBUS2 2 +#define PM8XXX_MPP_DIN_TO_DBUS3 3 + +/* Digital Output: control */ +#define PM8XXX_MPP_DOUT_CTRL_LOW 0 +#define PM8XXX_MPP_DOUT_CTRL_HIGH 1 +#define PM8XXX_MPP_DOUT_CTRL_MPP 2 +#define PM8XXX_MPP_DOUT_CTRL_INV_MPP 3 + +/* Bidirectional: control */ +#define PM8XXX_MPP_BI_PULLUP_1KOHM 0 +#define PM8XXX_MPP_BI_PULLUP_OPEN 1 +#define PM8XXX_MPP_BI_PULLUP_10KOHM 2 +#define PM8XXX_MPP_BI_PULLUP_30KOHM 3 + +/* Analog Output: control */ +#define PM8XXX_MPP_AOUT_CTRL_DISABLE 0 +#define PM8XXX_MPP_AOUT_CTRL_ENABLE 1 +#define PM8XXX_MPP_AOUT_CTRL_MPP_HIGH_EN 2 +#define PM8XXX_MPP_AOUT_CTRL_MPP_LOW_EN 3 + +/* Current Sink: control */ +#define PM8XXX_MPP_CS_CTRL_DISABLE 0 +#define PM8XXX_MPP_CS_CTRL_ENABLE 1 +#define PM8XXX_MPP_CS_CTRL_MPP_HIGH_EN 2 +#define PM8XXX_MPP_CS_CTRL_MPP_LOW_EN 3 + +/* DTEST Current Sink: control */ +#define PM8XXX_MPP_DTEST_CS_CTRL_EN1 0 +#define PM8XXX_MPP_DTEST_CS_CTRL_EN2 1 +#define PM8XXX_MPP_DTEST_CS_CTRL_EN3 2 +#define PM8XXX_MPP_DTEST_CS_CTRL_EN4 3 + +/* DTEST Digital Output: control */ +#define PM8XXX_MPP_DTEST_DBUS1 0 +#define PM8XXX_MPP_DTEST_DBUS2 1 +#define PM8XXX_MPP_DTEST_DBUS3 2 +#define PM8XXX_MPP_DTEST_DBUS4 3 + +/* custom pinconf parameters */ +#define PM8XXX_CONFIG_AMUX (PIN_CONFIG_END + 1) +#define PM8XXX_CONFIG_DTEST_SELECTOR (PIN_CONFIG_END + 2) +#define PM8XXX_CONFIG_ALEVEL (PIN_CONFIG_END + 3) +#define PM8XXX_CONFIG_PAIRED (PIN_CONFIG_END + 4) + +/** + * struct pm8xxx_pin_data - dynamic configuration for a pin + * @reg: address of the control register + * @irq: IRQ from the PMIC interrupt controller + * @mode: operating mode for the pin (digital, analog or current sink) + * @input: pin is input + * @output: pin is output + * @high_z: pin is floating + * @paired: mpp operates in paired mode + * @output_value: logical output value of the mpp + * @power_source: selected power source + * @dtest: DTEST route selector + * @amux: input muxing in analog mode + * @aout_level: selector of the output in analog mode + * @drive_strength: drive strength of the current sink + * @pullup: pull up value, when in digital bidirectional mode + */ +struct pm8xxx_pin_data { + unsigned reg; + int irq; + + u8 mode; + + bool input; + bool output; + bool high_z; + bool paired; + bool output_value; + + u8 power_source; + u8 dtest; + u8 amux; + u8 aout_level; + u8 drive_strength; + unsigned pullup; +}; + +struct pm8xxx_mpp { + struct device *dev; + struct regmap *regmap; + struct pinctrl_dev *pctrl; + struct gpio_chip chip; + + struct pinctrl_desc desc; + unsigned npins; +}; + +static const struct pinconf_generic_params pm8xxx_mpp_bindings[] = { + {"qcom,amux-route", PM8XXX_CONFIG_AMUX, 0}, + {"qcom,analog-level", PM8XXX_CONFIG_ALEVEL, 0}, + {"qcom,dtest", PM8XXX_CONFIG_DTEST_SELECTOR, 0}, + {"qcom,paired", PM8XXX_CONFIG_PAIRED, 0}, +}; + +#ifdef CONFIG_DEBUG_FS +static const struct pin_config_item pm8xxx_conf_items[] = { + PCONFDUMP(PM8XXX_CONFIG_AMUX, "analog mux", NULL, true), + PCONFDUMP(PM8XXX_CONFIG_ALEVEL, "analog level", NULL, true), + PCONFDUMP(PM8XXX_CONFIG_DTEST_SELECTOR, "dtest", NULL, true), + PCONFDUMP(PM8XXX_CONFIG_PAIRED, "paired", NULL, false), +}; +#endif + +#define PM8XXX_MAX_MPPS 12 +static const char * const pm8xxx_groups[PM8XXX_MAX_MPPS] = { + "mpp1", "mpp2", "mpp3", "mpp4", "mpp5", "mpp6", "mpp7", "mpp8", + "mpp9", "mpp10", "mpp11", "mpp12", +}; + +#define PM8XXX_MPP_DIGITAL 0 +#define PM8XXX_MPP_ANALOG 1 +#define PM8XXX_MPP_SINK 2 + +static const char * const pm8xxx_mpp_functions[] = { + "digital", "analog", "sink", +}; + +static int pm8xxx_mpp_update(struct pm8xxx_mpp *pctrl, + struct pm8xxx_pin_data *pin) +{ + unsigned level; + unsigned ctrl; + unsigned type; + int ret; + u8 val; + + switch (pin->mode) { + case PM8XXX_MPP_DIGITAL: + if (pin->dtest) { + type = PM8XXX_MPP_TYPE_DTEST_OUTPUT; + ctrl = pin->dtest - 1; + } else if (pin->input && pin->output) { + type = PM8XXX_MPP_TYPE_D_BI_DIR; + if (pin->high_z) + ctrl = PM8XXX_MPP_BI_PULLUP_OPEN; + else if (pin->pullup == 600) + ctrl = PM8XXX_MPP_BI_PULLUP_1KOHM; + else if (pin->pullup == 10000) + ctrl = PM8XXX_MPP_BI_PULLUP_10KOHM; + else + ctrl = PM8XXX_MPP_BI_PULLUP_30KOHM; + } else if (pin->input) { + type = PM8XXX_MPP_TYPE_D_INPUT; + if (pin->dtest) + ctrl = pin->dtest; + else + ctrl = PM8XXX_MPP_DIN_TO_INT; + } else { + type = PM8XXX_MPP_TYPE_D_OUTPUT; + ctrl = !!pin->output_value; + if (pin->paired) + ctrl |= BIT(1); + } + + level = pin->power_source; + break; + case PM8XXX_MPP_ANALOG: + if (pin->output) { + type = PM8XXX_MPP_TYPE_A_OUTPUT; + level = pin->aout_level; + ctrl = pin->output_value; + if (pin->paired) + ctrl |= BIT(1); + } else { + type = PM8XXX_MPP_TYPE_A_INPUT; + level = pin->amux; + ctrl = 0; + } + break; + case PM8XXX_MPP_SINK: + level = (pin->drive_strength / 5) - 1; + if (pin->dtest) { + type = PM8XXX_MPP_TYPE_DTEST_SINK; + ctrl = pin->dtest - 1; + } else { + type = PM8XXX_MPP_TYPE_SINK; + ctrl = pin->output_value; + if (pin->paired) + ctrl |= BIT(1); + } + break; + default: + return -EINVAL; + } + + val = type << 5 | level << 2 | ctrl; + ret = regmap_write(pctrl->regmap, pin->reg, val); + if (ret) + dev_err(pctrl->dev, "failed to write register\n"); + + return ret; +} + +static int pm8xxx_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct pm8xxx_mpp *pctrl = pinctrl_dev_get_drvdata(pctldev); + + return pctrl->npins; +} + +static const char *pm8xxx_get_group_name(struct pinctrl_dev *pctldev, + unsigned group) +{ + return pm8xxx_groups[group]; +} + + +static int pm8xxx_get_group_pins(struct pinctrl_dev *pctldev, + unsigned group, + const unsigned **pins, + unsigned *num_pins) +{ + struct pm8xxx_mpp *pctrl = pinctrl_dev_get_drvdata(pctldev); + + *pins = &pctrl->desc.pins[group].number; + *num_pins = 1; + + return 0; +} + +static const struct pinctrl_ops pm8xxx_pinctrl_ops = { + .get_groups_count = pm8xxx_get_groups_count, + .get_group_name = pm8xxx_get_group_name, + .get_group_pins = pm8xxx_get_group_pins, + .dt_node_to_map = pinconf_generic_dt_node_to_map_group, + .dt_free_map = pinctrl_utils_dt_free_map, +}; + +static int pm8xxx_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(pm8xxx_mpp_functions); +} + +static const char *pm8xxx_get_function_name(struct pinctrl_dev *pctldev, + unsigned function) +{ + return pm8xxx_mpp_functions[function]; +} + +static int pm8xxx_get_function_groups(struct pinctrl_dev *pctldev, + unsigned function, + const char * const **groups, + unsigned * const num_groups) +{ + struct pm8xxx_mpp *pctrl = pinctrl_dev_get_drvdata(pctldev); + + *groups = pm8xxx_groups; + *num_groups = pctrl->npins; + return 0; +} + +static int pm8xxx_pinmux_set_mux(struct pinctrl_dev *pctldev, + unsigned function, + unsigned group) +{ + struct pm8xxx_mpp *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[group].drv_data; + + pin->mode = function; + pm8xxx_mpp_update(pctrl, pin); + + 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_set_mux, +}; + +static int pm8xxx_pin_config_get(struct pinctrl_dev *pctldev, + unsigned int offset, + unsigned long *config) +{ + struct pm8xxx_mpp *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + unsigned param = pinconf_to_config_param(*config); + unsigned arg; + + switch (param) { + case PIN_CONFIG_BIAS_PULL_UP: + arg = pin->pullup; + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + arg = pin->high_z; + break; + case PIN_CONFIG_INPUT_ENABLE: + arg = pin->input; + break; + case PIN_CONFIG_OUTPUT: + arg = pin->output_value; + break; + case PIN_CONFIG_POWER_SOURCE: + arg = pin->power_source; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + arg = pin->drive_strength; + break; + case PM8XXX_CONFIG_DTEST_SELECTOR: + arg = pin->dtest; + break; + case PM8XXX_CONFIG_AMUX: + arg = pin->amux; + break; + case PM8XXX_CONFIG_ALEVEL: + arg = pin->aout_level; + break; + case PM8XXX_CONFIG_PAIRED: + arg = pin->paired; + break; + default: + return -EINVAL; + } + + *config = pinconf_to_config_packed(param, arg); + + return 0; +} + +static int pm8xxx_pin_config_set(struct pinctrl_dev *pctldev, + unsigned int offset, + unsigned long *configs, + unsigned num_configs) +{ + struct pm8xxx_mpp *pctrl = pinctrl_dev_get_drvdata(pctldev); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + unsigned param; + unsigned arg; + unsigned i; + + 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_PULL_UP: + pin->pullup = arg; + break; + case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + pin->high_z = true; + break; + case PIN_CONFIG_INPUT_ENABLE: + pin->input = true; + break; + case PIN_CONFIG_OUTPUT: + pin->output = true; + pin->output_value = !!arg; + break; + case PIN_CONFIG_POWER_SOURCE: + pin->power_source = arg; + break; + case PIN_CONFIG_DRIVE_STRENGTH: + pin->drive_strength = arg; + break; + case PM8XXX_CONFIG_DTEST_SELECTOR: + pin->dtest = arg; + break; + case PM8XXX_CONFIG_AMUX: + pin->amux = arg; + break; + case PM8XXX_CONFIG_ALEVEL: + pin->aout_level = arg; + break; + case PM8XXX_CONFIG_PAIRED: + pin->paired = !!arg; + break; + default: + dev_err(pctrl->dev, + "unsupported config parameter: %x\n", + param); + return -EINVAL; + } + } + + pm8xxx_mpp_update(pctrl, pin); + + return 0; +} + +static const struct pinconf_ops pm8xxx_pinconf_ops = { + .is_generic = true, + .pin_config_group_get = pm8xxx_pin_config_get, + .pin_config_group_set = pm8xxx_pin_config_set, +}; + +static struct pinctrl_desc pm8xxx_pinctrl_desc = { + .name = "pm8xxx_mpp", + .pctlops = &pm8xxx_pinctrl_ops, + .pmxops = &pm8xxx_pinmux_ops, + .confops = &pm8xxx_pinconf_ops, + .owner = THIS_MODULE, +}; + +static int pm8xxx_mpp_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct pm8xxx_mpp *pctrl = container_of(chip, struct pm8xxx_mpp, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + + switch (pin->mode) { + case PM8XXX_MPP_DIGITAL: + pin->input = true; + break; + case PM8XXX_MPP_ANALOG: + pin->input = true; + pin->output = true; + break; + case PM8XXX_MPP_SINK: + return -EINVAL; + } + + pm8xxx_mpp_update(pctrl, pin); + + return 0; +} + +static int pm8xxx_mpp_direction_output(struct gpio_chip *chip, + unsigned offset, + int value) +{ + struct pm8xxx_mpp *pctrl = container_of(chip, struct pm8xxx_mpp, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + + switch (pin->mode) { + case PM8XXX_MPP_DIGITAL: + pin->output = true; + break; + case PM8XXX_MPP_ANALOG: + pin->input = false; + pin->output = true; + break; + case PM8XXX_MPP_SINK: + pin->input = false; + pin->output = true; + break; + } + + pm8xxx_mpp_update(pctrl, pin); + + return 0; +} + +static int pm8xxx_mpp_get(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_mpp *pctrl = container_of(chip, struct pm8xxx_mpp, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + bool state; + int ret; + + if (!pin->input) + return pin->output_value; + + ret = irq_get_irqchip_state(pin->irq, IRQCHIP_STATE_LINE_LEVEL, &state); + if (!ret) + ret = !!state; + + return ret; +} + +static void pm8xxx_mpp_set(struct gpio_chip *chip, unsigned offset, int value) +{ + struct pm8xxx_mpp *pctrl = container_of(chip, struct pm8xxx_mpp, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + + pin->output_value = !!value; + + pm8xxx_mpp_update(pctrl, pin); +} + +static int pm8xxx_mpp_of_xlate(struct gpio_chip *chip, + const struct of_phandle_args *gpio_desc, + u32 *flags) +{ + if (chip->of_gpio_n_cells < 2) + return -EINVAL; + + if (flags) + *flags = gpio_desc->args[1]; + + return gpio_desc->args[0] - 1; +} + + +static int pm8xxx_mpp_to_irq(struct gpio_chip *chip, unsigned offset) +{ + struct pm8xxx_mpp *pctrl = container_of(chip, struct pm8xxx_mpp, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + + return pin->irq; +} + +#ifdef CONFIG_DEBUG_FS +#include <linux/seq_file.h> + +static void pm8xxx_mpp_dbg_show_one(struct seq_file *s, + struct pinctrl_dev *pctldev, + struct gpio_chip *chip, + unsigned offset, + unsigned gpio) +{ + struct pm8xxx_mpp *pctrl = container_of(chip, struct pm8xxx_mpp, chip); + struct pm8xxx_pin_data *pin = pctrl->desc.pins[offset].drv_data; + + static const char * const aout_lvls[] = { + "1v25", "1v25_2", "0v625", "0v3125", "mpp", "abus1", "abus2", + "abus3" + }; + + static const char * const amuxs[] = { + "amux5", "amux6", "amux7", "amux8", "amux9", "abus1", "abus2", + "abus3", + }; + + seq_printf(s, " mpp%-2d:", offset + 1); + + switch (pin->mode) { + case PM8XXX_MPP_DIGITAL: + seq_puts(s, " digital "); + if (pin->dtest) { + seq_printf(s, "dtest%d\n", pin->dtest); + } else if (pin->input && pin->output) { + if (pin->high_z) + seq_puts(s, "bi-dir high-z"); + else + seq_printf(s, "bi-dir %dOhm", pin->pullup); + } else if (pin->input) { + if (pin->dtest) + seq_printf(s, "in dtest%d", pin->dtest); + else + seq_puts(s, "in gpio"); + } else if (pin->output) { + seq_puts(s, "out "); + + if (!pin->paired) { + seq_puts(s, pin->output_value ? + "high" : "low"); + } else { + seq_puts(s, pin->output_value ? + "inverted" : "follow"); + } + } + break; + case PM8XXX_MPP_ANALOG: + seq_puts(s, " analog "); + if (pin->output) { + seq_printf(s, "out %s ", aout_lvls[pin->aout_level]); + if (!pin->paired) { + seq_puts(s, pin->output_value ? + "high" : "low"); + } else { + seq_puts(s, pin->output_value ? + "inverted" : "follow"); + } + } else { + seq_printf(s, "input mux %s", amuxs[pin->amux]); + } + break; + case PM8XXX_MPP_SINK: + seq_printf(s, " sink %dmA ", pin->drive_strength); + if (pin->dtest) { + seq_printf(s, "dtest%d", pin->dtest); + } else { + if (!pin->paired) { + seq_puts(s, pin->output_value ? + "high" : "low"); + } else { + seq_puts(s, pin->output_value ? + "inverted" : "follow"); + } + } + break; + } + +} + +static void pm8xxx_mpp_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_mpp_dbg_show_one(s, NULL, chip, i, gpio); + seq_puts(s, "\n"); + } +} + +#else +#define msm_mpp_dbg_show NULL +#endif + +static struct gpio_chip pm8xxx_mpp_template = { + .direction_input = pm8xxx_mpp_direction_input, + .direction_output = pm8xxx_mpp_direction_output, + .get = pm8xxx_mpp_get, + .set = pm8xxx_mpp_set, + .of_xlate = pm8xxx_mpp_of_xlate, + .to_irq = pm8xxx_mpp_to_irq, + .dbg_show = pm8xxx_mpp_dbg_show, + .owner = THIS_MODULE, +}; + +static int pm8xxx_pin_populate(struct pm8xxx_mpp *pctrl, + struct pm8xxx_pin_data *pin) +{ + unsigned int val; + unsigned level; + unsigned ctrl; + unsigned type; + int ret; + + ret = regmap_read(pctrl->regmap, pin->reg, &val); + if (ret) { + dev_err(pctrl->dev, "failed to read register\n"); + return ret; + } + + type = (val >> 5) & 7; + level = (val >> 2) & 7; + ctrl = (val) & 3; + + switch (type) { + case PM8XXX_MPP_TYPE_D_INPUT: + pin->mode = PM8XXX_MPP_DIGITAL; + pin->input = true; + pin->power_source = level; + pin->dtest = ctrl; + break; + case PM8XXX_MPP_TYPE_D_OUTPUT: + pin->mode = PM8XXX_MPP_DIGITAL; + pin->output = true; + pin->power_source = level; + pin->output_value = !!(ctrl & BIT(0)); + pin->paired = !!(ctrl & BIT(1)); + break; + case PM8XXX_MPP_TYPE_D_BI_DIR: + pin->mode = PM8XXX_MPP_DIGITAL; + pin->input = true; + pin->output = true; + pin->power_source = level; + switch (ctrl) { + case PM8XXX_MPP_BI_PULLUP_1KOHM: + pin->pullup = 600; + break; + case PM8XXX_MPP_BI_PULLUP_OPEN: + pin->high_z = true; + break; + case PM8XXX_MPP_BI_PULLUP_10KOHM: + pin->pullup = 10000; + break; + case PM8XXX_MPP_BI_PULLUP_30KOHM: + pin->pullup = 30000; + break; + } + break; + case PM8XXX_MPP_TYPE_A_INPUT: + pin->mode = PM8XXX_MPP_ANALOG; + pin->input = true; + pin->amux = level; + break; + case PM8XXX_MPP_TYPE_A_OUTPUT: + pin->mode = PM8XXX_MPP_ANALOG; + pin->output = true; + pin->aout_level = level; + pin->output_value = !!(ctrl & BIT(0)); + pin->paired = !!(ctrl & BIT(1)); + break; + case PM8XXX_MPP_TYPE_SINK: + pin->mode = PM8XXX_MPP_SINK; + pin->drive_strength = 5 * (level + 1); + pin->output_value = !!(ctrl & BIT(0)); + pin->paired = !!(ctrl & BIT(1)); + break; + case PM8XXX_MPP_TYPE_DTEST_SINK: + pin->mode = PM8XXX_MPP_SINK; + pin->dtest = ctrl + 1; + pin->drive_strength = 5 * (level + 1); + break; + case PM8XXX_MPP_TYPE_DTEST_OUTPUT: + pin->mode = PM8XXX_MPP_DIGITAL; + pin->power_source = level; + if (ctrl >= 1) + pin->dtest = ctrl; + break; + } + + return 0; +} + +static const struct of_device_id pm8xxx_mpp_of_match[] = { + { .compatible = "qcom,pm8018-mpp", .data = (void *)6 }, + { .compatible = "qcom,pm8038-mpp", .data = (void *)6 }, + { .compatible = "qcom,pm8917-mpp", .data = (void *)10 }, + { .compatible = "qcom,pm8821-mpp", .data = (void *)4 }, + { .compatible = "qcom,pm8921-mpp", .data = (void *)12 }, + { }, +}; +MODULE_DEVICE_TABLE(of, pm8xxx_mpp_of_match); + +static int pm8xxx_mpp_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct pm8xxx_pin_data *pin_data; + struct pinctrl_pin_desc *pins; + struct pm8xxx_mpp *pctrl; + int ret; + int i; + + match = of_match_node(pm8xxx_mpp_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->npins = (unsigned)match->data; + + pctrl->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!pctrl->regmap) { + dev_err(&pdev->dev, "parent regmap unavailable\n"); + return -ENXIO; + } + + pctrl->desc = pm8xxx_pinctrl_desc; + pctrl->desc.npins = pctrl->npins; + + pins = devm_kcalloc(&pdev->dev, + pctrl->desc.npins, + sizeof(struct pinctrl_pin_desc), + GFP_KERNEL); + if (!pins) + return -ENOMEM; + + pin_data = devm_kcalloc(&pdev->dev, + pctrl->desc.npins, + sizeof(struct pm8xxx_pin_data), + GFP_KERNEL); + if (!pin_data) + return -ENOMEM; + + for (i = 0; i < pctrl->desc.npins; i++) { + pin_data[i].reg = SSBI_REG_ADDR_MPP(i); + pin_data[i].irq = platform_get_irq(pdev, i); + if (pin_data[i].irq < 0) { + dev_err(&pdev->dev, + "missing interrupts for pin %d\n", i); + return pin_data[i].irq; + } + + ret = pm8xxx_pin_populate(pctrl, &pin_data[i]); + if (ret) + return ret; + + pins[i].number = i; + pins[i].name = pm8xxx_groups[i]; + pins[i].drv_data = &pin_data[i]; + } + pctrl->desc.pins = pins; + + pctrl->desc.num_custom_params = ARRAY_SIZE(pm8xxx_mpp_bindings); + pctrl->desc.custom_params = pm8xxx_mpp_bindings; +#ifdef CONFIG_DEBUG_FS + pctrl->desc.custom_conf_items = pm8xxx_conf_items; +#endif + + pctrl->pctrl = pinctrl_register(&pctrl->desc, &pdev->dev, pctrl); + if (!pctrl->pctrl) { + dev_err(&pdev->dev, "couldn't register pm8xxx mpp driver\n"); + return -ENODEV; + } + + pctrl->chip = pm8xxx_mpp_template; + pctrl->chip.base = -1; + pctrl->chip.dev = &pdev->dev; + pctrl->chip.of_node = pdev->dev.of_node; + pctrl->chip.of_gpio_n_cells = 2; + pctrl->chip.label = dev_name(pctrl->dev); + pctrl->chip.ngpio = pctrl->npins; + 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), + 0, 0, pctrl->chip.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 mpp driver probed\n"); + + return 0; + +unregister_gpiochip: + gpiochip_remove(&pctrl->chip); + +unregister_pinctrl: + pinctrl_unregister(pctrl->pctrl); + + return ret; +} + +static int pm8xxx_mpp_remove(struct platform_device *pdev) +{ + struct pm8xxx_mpp *pctrl = platform_get_drvdata(pdev); + + gpiochip_remove(&pctrl->chip); + + pinctrl_unregister(pctrl->pctrl); + + return 0; +} + +static struct platform_driver pm8xxx_mpp_driver = { + .driver = { + .name = "pm8xxx_mpp", + .of_match_table = pm8xxx_mpp_of_match, + }, + .probe = pm8xxx_mpp_probe, + .remove = pm8xxx_mpp_remove, +}; + +module_platform_driver(pm8xxx_mpp_driver); + +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); +MODULE_DESCRIPTION("Qualcomm PM8xxx MPP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index bef3bde6971b..876e03eb03c8 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -522,6 +522,18 @@ config REGULATOR_QCOM_RPM Qualcomm RPM as a module. The module will be named "qcom_rpm-regulator". +config REGULATOR_QCOM_SMD_RPM + tristate "Qualcomm SMD based RPM regulator driver" + depends on MFD_QCOM_SMD_RPM + help + If you say yes to this option, support will be included for the + regulators exposed by the Resource Power Manager found in Qualcomm + 8974 based devices. + + Say M here if you want to include support for the regulators on the + Qualcomm RPM as a module. The module will be named + "qcom_smd-regulator". + config REGULATOR_QCOM_SPMI tristate "Qualcomm SPMI regulator driver" depends on SPMI || COMPILE_TEST diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 91bf76267404..abf70a7171b9 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o +obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index c9f72019bd68..c203f3ebe951 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -1240,7 +1240,7 @@ static struct regulator *create_regulator(struct regulator_dev *rdev, regulator->debugfs = debugfs_create_dir(regulator->supply_name, rdev->debugfs); if (!regulator->debugfs) { - rdev_warn(rdev, "Failed to create debugfs directory\n"); + rdev_dbg(rdev, "Failed to create debugfs directory\n"); } else { debugfs_create_u32("uA_load", 0444, regulator->debugfs, ®ulator->uA_load); diff --git a/drivers/regulator/qcom_smd-regulator.c b/drivers/regulator/qcom_smd-regulator.c new file mode 100644 index 000000000000..d7db719da9a7 --- /dev/null +++ b/drivers/regulator/qcom_smd-regulator.c @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2015, Sony Mobile Communications AB. + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * 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/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/qcom_smd-regulator.h> +#include <linux/mfd/qcom-smd-rpm.h> +#include <dt-bindings/mfd/qcom-smd-rpm.h> + +#include "internal.h" + +struct qcom_rpm_reg { + struct device *dev; + + struct qcom_smd_rpm *rpm; + + u32 type; + u32 id; + + struct regulator_desc desc; + + int is_enabled; + int uV; +}; + +struct rpm_regulator_req { + u32 key; + u32 nbytes; + u32 value; +}; + +#define RPM_KEY_SWEN 0x6e657773 /* "swen" */ +#define RPM_KEY_UV 0x00007675 /* "uv" */ +#define RPM_KEY_MA 0x0000616d /* "ma" */ +#define RPM_KEY_FLOOR 0x00636676 /* "vfc" */ +#define RPM_KEY_CORNER 0x6e726f63 /* "corn" */ + +#define RPM_MIN_FLOOR_CORNER 0 +#define RPM_MAX_FLOOR_CORNER 6 + +static int rpm_reg_write_active(struct qcom_rpm_reg *vreg, + struct rpm_regulator_req *req, + size_t size) +{ + return qcom_rpm_smd_write(vreg->rpm, + QCOM_SMD_RPM_ACTIVE_STATE, + vreg->type, + vreg->id, + req, size); +} + +int qcom_rpm_set_floor(struct regulator *regulator, int floor) +{ + struct regulator_dev *rdev = regulator->rdev; + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + int ret; + + req.key = RPM_KEY_FLOOR; + req.nbytes = sizeof(u32); + req.value = floor; + + if (floor < RPM_MIN_FLOOR_CORNER || floor > RPM_MAX_FLOOR_CORNER) + return -EINVAL; + + ret = rpm_reg_write_active(vreg, &req, sizeof(req)); + if (ret) + dev_err(rdev_get_dev(rdev), "Failed to set floor %d\n", floor); + + return ret; +} +EXPORT_SYMBOL(qcom_rpm_set_floor); + +int qcom_rpm_set_corner(struct regulator *regulator, int corner) +{ + struct regulator_dev *rdev = regulator->rdev; + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + int ret; + + req.key = RPM_KEY_CORNER; + req.nbytes = sizeof(u32); + req.value = corner; + + if (corner < RPM_MIN_FLOOR_CORNER || corner > RPM_MAX_FLOOR_CORNER) + return -EINVAL; + + pr_info("Set corner to %d\n", corner); + ret = rpm_reg_write_active(vreg, &req, sizeof(req)); + if (ret) + dev_err(rdev_get_dev(rdev), "Failed to set corner %d\n", corner); + + return ret; +} +EXPORT_SYMBOL(qcom_rpm_set_corner); + +static int rpm_reg_enable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + int ret; + + req.key = RPM_KEY_SWEN; + req.nbytes = sizeof(u32); + req.value = 1; + + ret = rpm_reg_write_active(vreg, &req, sizeof(req)); + if (!ret) + vreg->is_enabled = 1; + + return ret; +} + +static int rpm_reg_is_enabled(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + + return vreg->is_enabled; +} + +static int rpm_reg_disable(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + int ret; + + req.key = RPM_KEY_SWEN; + req.nbytes = sizeof(u32); + req.value = 0; + + ret = rpm_reg_write_active(vreg, &req, sizeof(req)); + if (!ret) + vreg->is_enabled = 0; + + return ret; +} + +static int rpm_reg_get_voltage(struct regulator_dev *rdev) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + + return vreg->uV; +} + +static int rpm_reg_set_voltage(struct regulator_dev *rdev, + int min_uV, + int max_uV, + unsigned *selector) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + int ret = 0; + + req.key = RPM_KEY_UV; + req.nbytes = sizeof(u32); + req.value = min_uV; + + ret = rpm_reg_write_active(vreg, &req, sizeof(req)); + if (!ret) + vreg->uV = min_uV; + + return ret; +} + +static int rpm_reg_set_load(struct regulator_dev *rdev, int load_uA) +{ + struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev); + struct rpm_regulator_req req; + + req.key = RPM_KEY_MA; + req.nbytes = sizeof(u32); + req.value = load_uA; + + return rpm_reg_write_active(vreg, &req, sizeof(req)); +} + +static const struct regulator_ops rpm_smps_ldo_ops = { + .enable = rpm_reg_enable, + .disable = rpm_reg_disable, + .is_enabled = rpm_reg_is_enabled, + + .get_voltage = rpm_reg_get_voltage, + .set_voltage = rpm_reg_set_voltage, + + .set_load = rpm_reg_set_load, +}; + +static const struct regulator_ops rpm_switch_ops = { + .enable = rpm_reg_enable, + .disable = rpm_reg_disable, + .is_enabled = rpm_reg_is_enabled, +}; + +static const struct regulator_desc pm8x41_hfsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE( 375000, 0, 95, 12500), + REGULATOR_LINEAR_RANGE(1550000, 96, 158, 25000), + }, + .n_linear_ranges = 2, + .n_voltages = 159, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8841_ftsmps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(350000, 0, 184, 5000), + REGULATOR_LINEAR_RANGE(700000, 185, 339, 10000), + }, + .n_linear_ranges = 2, + .n_voltages = 340, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_boost = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(4000000, 0, 15, 100000), + }, + .n_linear_ranges = 1, + .n_voltages = 16, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_pldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE( 750000, 0, 30, 25000), + REGULATOR_LINEAR_RANGE(1500000, 31, 99, 50000), + }, + .n_linear_ranges = 2, + .n_voltages = 100, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_nldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(750000, 0, 63, 12500), + }, + .n_linear_ranges = 1, + .n_voltages = 64, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_lnldo = { + .fixed_uV = 1740000, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8941_switch = { + .ops = &rpm_switch_ops, +}; + +static const struct regulator_desc pm8916_pldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(750000, 0, 208, 12500), + }, + .n_linear_ranges = 1, + .n_voltages = 209, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8916_nldo = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(375000, 0, 93, 12500), + }, + .n_linear_ranges = 1, + .n_voltages = 94, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8916_buck_lvo_smps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(375000, 0, 95, 12500), + REGULATOR_LINEAR_RANGE(750000, 96, 127, 25000), + }, + .n_linear_ranges = 2, + .n_voltages = 128, + .ops = &rpm_smps_ldo_ops, +}; + +static const struct regulator_desc pm8916_buck_hvo_smps = { + .linear_ranges = (struct regulator_linear_range[]) { + REGULATOR_LINEAR_RANGE(1550000, 0, 31, 25000), + }, + .n_linear_ranges = 1, + .n_voltages = 32, + .ops = &rpm_smps_ldo_ops, +}; + +struct rpm_regulator_data { + const char *name; + u32 type; + u32 id; + const struct regulator_desc *desc; + const char *supply; +}; + +static const struct rpm_regulator_data rpm_pm8841_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPB, 1, &pm8x41_hfsmps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPB, 2, &pm8841_ftsmps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPB, 3, &pm8x41_hfsmps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_SMPB, 4, &pm8841_ftsmps, "vdd_s4" }, + { "s5", QCOM_SMD_RPM_SMPB, 5, &pm8841_ftsmps, "vdd_s5" }, + { "s6", QCOM_SMD_RPM_SMPB, 6, &pm8841_ftsmps, "vdd_s6" }, + { "s7", QCOM_SMD_RPM_SMPB, 7, &pm8841_ftsmps, "vdd_s7" }, + { "s8", QCOM_SMD_RPM_SMPB, 8, &pm8841_ftsmps, "vdd_s8" }, + {} +}; + +static const struct rpm_regulator_data rpm_pm8941_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPA, 1, &pm8x41_hfsmps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPA, 2, &pm8x41_hfsmps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPA, 3, &pm8x41_hfsmps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_BOOST, 1, &pm8941_boost }, + + { "l1", QCOM_SMD_RPM_LDOA, 1, &pm8941_nldo, "vdd_l1_l3" }, + { "l2", QCOM_SMD_RPM_LDOA, 2, &pm8941_nldo, "vdd_l2_lvs1_2_3" }, + { "l3", QCOM_SMD_RPM_LDOA, 3, &pm8941_nldo, "vdd_l1_l3" }, + { "l4", QCOM_SMD_RPM_LDOA, 4, &pm8941_nldo, "vdd_l4_l11" }, + { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8941_lnldo, "vdd_l5_l7" }, + { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8941_pldo, "vdd_l6_l12_l14_l15" }, + { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8941_lnldo, "vdd_l5_l7" }, + { "l8", QCOM_SMD_RPM_LDOA, 8, &pm8941_pldo, "vdd_l8_l16_l18_l19" }, + { "l9", QCOM_SMD_RPM_LDOA, 9, &pm8941_pldo, "vdd_l9_l10_l17_l22" }, + { "l10", QCOM_SMD_RPM_LDOA, 10, &pm8941_pldo, "vdd_l9_l10_l17_l22" }, + { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8941_nldo, "vdd_l4_l11" }, + { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8941_pldo, "vdd_l6_l12_l14_l15" }, + { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8941_pldo, "vdd_l13_l20_l23_l24" }, + { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8941_pldo, "vdd_l6_l12_l14_l15" }, + { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8941_pldo, "vdd_l6_l12_l14_l15" }, + { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8941_pldo, "vdd_l8_l16_l18_l19" }, + { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8941_pldo, "vdd_l9_l10_l17_l22" }, + { "l18", QCOM_SMD_RPM_LDOA, 18, &pm8941_pldo, "vdd_l8_l16_l18_l19" }, + { "l19", QCOM_SMD_RPM_LDOA, 19, &pm8941_pldo, "vdd_l8_l16_l18_l19" }, + { "l20", QCOM_SMD_RPM_LDOA, 20, &pm8941_pldo, "vdd_l13_l20_l23_l24" }, + { "l21", QCOM_SMD_RPM_LDOA, 21, &pm8941_pldo, "vdd_l21" }, + { "l22", QCOM_SMD_RPM_LDOA, 22, &pm8941_pldo, "vdd_l9_l10_l17_l22" }, + { "l23", QCOM_SMD_RPM_LDOA, 23, &pm8941_pldo, "vdd_l13_l20_l23_l24" }, + { "l24", QCOM_SMD_RPM_LDOA, 24, &pm8941_pldo, "vdd_l13_l20_l23_l24" }, + + { "lvs1", QCOM_SMD_RPM_VSA, 1, &pm8941_switch, "vdd_l2_lvs1_2_3" }, + { "lvs2", QCOM_SMD_RPM_VSA, 2, &pm8941_switch, "vdd_l2_lvs1_2_3" }, + { "lvs3", QCOM_SMD_RPM_VSA, 3, &pm8941_switch, "vdd_l2_lvs1_2_3" }, + + { "5vs1", QCOM_SMD_RPM_VSA, 4, &pm8941_switch, "vin_5vs" }, + { "5vs2", QCOM_SMD_RPM_VSA, 5, &pm8941_switch, "vin_5vs" }, + + {} +}; + +static const struct rpm_regulator_data rpm_pm8916_regulators[] = { + { "s1", QCOM_SMD_RPM_SMPA, 1, &pm8916_buck_lvo_smps, "vdd_s1" }, + { "s2", QCOM_SMD_RPM_SMPA, 2, &pm8916_buck_lvo_smps, "vdd_s2" }, + { "s3", QCOM_SMD_RPM_SMPA, 3, &pm8916_buck_lvo_smps, "vdd_s3" }, + { "s4", QCOM_SMD_RPM_SMPA, 4, &pm8916_buck_hvo_smps, "vdd_s4" }, + { "l1", QCOM_SMD_RPM_LDOA, 1, &pm8916_nldo, "vdd_l1_l2_l3" }, + { "l2", QCOM_SMD_RPM_LDOA, 2, &pm8916_nldo, "vdd_l1_l2_l3" }, + { "l3", QCOM_SMD_RPM_LDOA, 3, &pm8916_nldo, "vdd_l1_l2_l3" }, + { "l4", QCOM_SMD_RPM_LDOA, 4, &pm8916_pldo, "vdd_l4_l5_l6" }, + { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8916_pldo, "vdd_l4_l5_l6" }, + { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8916_pldo, "vdd_l4_l5_l6" }, + { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8916_pldo, "vdd_l7" }, + { "l8", QCOM_SMD_RPM_LDOA, 8, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18" }, + { "l9", QCOM_SMD_RPM_LDOA, 9, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18" }, + { "l10", QCOM_SMD_RPM_LDOA, 10, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + { "l18", QCOM_SMD_RPM_LDOA, 18, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"}, + {} +}; + +static const struct of_device_id rpm_of_match[] = { + { .compatible = "qcom,rpm-pm8841-regulators", .data = &rpm_pm8841_regulators }, + { .compatible = "qcom,rpm-pm8941-regulators", .data = &rpm_pm8941_regulators }, + { .compatible = "qcom,rpm-pm8916-regulators", .data = &rpm_pm8916_regulators }, + {} +}; +MODULE_DEVICE_TABLE(of, rpm_of_match); + +static int rpm_reg_probe(struct platform_device *pdev) +{ + const struct rpm_regulator_data *reg; + const struct of_device_id *match; + struct regulator_config config = { }; + struct regulator_dev *rdev; + struct qcom_rpm_reg *vreg; + struct qcom_smd_rpm *rpm; + + rpm = dev_get_drvdata(pdev->dev.parent); + if (!rpm) { + dev_err(&pdev->dev, "unable to retrieve handle to rpm\n"); + return -ENODEV; + } + + match = of_match_device(rpm_of_match, &pdev->dev); + for (reg = match->data; reg->name; reg++) { + vreg = devm_kzalloc(&pdev->dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) + return -ENOMEM; + + vreg->dev = &pdev->dev; + vreg->type = reg->type; + vreg->id = reg->id; + vreg->rpm = rpm; + + memcpy(&vreg->desc, reg->desc, sizeof(vreg->desc)); + + vreg->desc.id = -1; + vreg->desc.owner = THIS_MODULE; + vreg->desc.type = REGULATOR_VOLTAGE; + vreg->desc.name = reg->name; + vreg->desc.supply_name = reg->supply; + vreg->desc.of_match = reg->name; + + config.dev = &pdev->dev; + config.driver_data = vreg; + rdev = devm_regulator_register(&pdev->dev, &vreg->desc, &config); + if (IS_ERR(rdev)) { + dev_err(&pdev->dev, "failed to register %s\n", reg->name); + return PTR_ERR(rdev); + } + } + + return 0; +} + +static struct platform_driver rpm_reg_driver = { + .probe = rpm_reg_probe, + .driver = { + .name = "qcom_rpm_smd_regulator", + .of_match_table = rpm_of_match, + }, +}; + +static int __init rpm_reg_init(void) +{ + return platform_driver_register(&rpm_reg_driver); +} +subsys_initcall(rpm_reg_init); + +static void __exit rpm_reg_exit(void) +{ + platform_driver_unregister(&rpm_reg_driver); +} +module_exit(rpm_reg_exit) + +MODULE_DESCRIPTION("Qualcomm RPM regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 5eea374c8fa6..5c1a067b6677 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -17,3 +17,19 @@ config QCOM_PM 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_SMD + tristate "Qualcomm Shared Memory Driver (SMD)" + depends on QCOM_SMEM + help + Say y here to enable support for the Qualcomm Shared Memory Driver + providing communication channels to remote processors in Qualcomm + platforms. + +config QCOM_SMEM + tristate "Qualcomm Shared Memory Manager (SMEM)" + depends on ARCH_QCOM + help + Say y here to enable support for the Qualcomm Shared Memory Manager. + The driver provides an interface to items in a heap shared among all + processors in a Qualcomm platform. diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 931d385386c5..9036d570395e 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,2 +1,5 @@ +obj-$(CONFIG_ARM64) += cpu_ops.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_PM) += spm.o +obj-$(CONFIG_QCOM_SMD) += smd.o +obj-$(CONFIG_QCOM_SMEM) += smem.o diff --git a/drivers/soc/qcom/cpu_ops.c b/drivers/soc/qcom/cpu_ops.c new file mode 100644 index 000000000000..d831cb071d3d --- /dev/null +++ b/drivers/soc/qcom/cpu_ops.c @@ -0,0 +1,343 @@ +/* Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2013 ARM Ltd. + * + * 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. + */ + +/* MSM ARMv8 CPU Operations + * Based on arch/arm64/kernel/smp_spin_table.c + */ + +#include <linux/bitops.h> +#include <linux/cpu.h> +#include <linux/cpumask.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/smp.h> +#include <linux/qcom_scm.h> + +#include <asm/barrier.h> +#include <asm/cacheflush.h> +#include <asm/cpu_ops.h> +#include <asm/cputype.h> +#include <asm/smp_plat.h> + +static DEFINE_RAW_SPINLOCK(boot_lock); + +DEFINE_PER_CPU(int, cold_boot_done); + +#if 0 +static int cold_boot_flags[] = { + 0, + QCOM_SCM_FLAG_COLDBOOT_CPU1, + QCOM_SCM_FLAG_COLDBOOT_CPU2, + QCOM_SCM_FLAG_COLDBOOT_CPU3, +}; +#endif + +/* CPU power domain register offsets */ +#define CPU_PWR_CTL 0x4 +#define CPU_PWR_GATE_CTL 0x14 +#define LDO_BHS_PWR_CTL 0x28 + +/* L2 power domain register offsets */ +#define L2_PWR_CTL_OVERRIDE 0xc +#define L2_PWR_CTL 0x14 +#define L2_PWR_STATUS 0x18 +#define L2_CORE_CBCR 0x58 +#define L1_RST_DIS 0x284 + +#define L2_SPM_STS 0xc +#define L2_VREG_CTL 0x1c + +#define SCM_IO_READ 1 +#define SCM_IO_WRITE 2 + +/* + * struct msm_l2ccc_of_info: represents of data for l2 cache clock controller. + * @compat: compat string for l2 cache clock controller + * @l2_pon: l2 cache power on routine + */ +struct msm_l2ccc_of_info { + const char *compat; + int (*l2_power_on) (struct device_node *dn, u32 l2_mask, int cpu); + u32 l2_power_on_mask; +}; + + +static int power_on_l2_msm8916(struct device_node *l2ccc_node, u32 pon_mask, + int cpu) +{ + u32 pon_status; + void __iomem *l2_base; + + l2_base = of_iomap(l2ccc_node, 0); + if (!l2_base) + return -ENOMEM; + + /* Skip power-on sequence if l2 cache is already powered up*/ + pon_status = (__raw_readl(l2_base + L2_PWR_STATUS) & pon_mask) + == pon_mask; + if (pon_status) { + iounmap(l2_base); + return 0; + } + + /* Close L2/SCU Logic GDHS and power up the cache */ + writel_relaxed(0x10D700, l2_base + L2_PWR_CTL); + + /* Assert PRESETDBGn */ + writel_relaxed(0x400000, l2_base + L2_PWR_CTL_OVERRIDE); + mb(); + udelay(2); + + /* De-assert L2/SCU memory Clamp */ + writel_relaxed(0x101700, l2_base + L2_PWR_CTL); + + /* Wakeup L2/SCU RAMs by deasserting sleep signals */ + writel_relaxed(0x101703, l2_base + L2_PWR_CTL); + mb(); + udelay(2); + + /* Enable clocks via SW_CLK_EN */ + writel_relaxed(0x01, l2_base + L2_CORE_CBCR); + + /* De-assert L2/SCU logic clamp */ + writel_relaxed(0x101603, l2_base + L2_PWR_CTL); + mb(); + udelay(2); + + /* De-assert PRESSETDBg */ + writel_relaxed(0x0, l2_base + L2_PWR_CTL_OVERRIDE); + + /* De-assert L2/SCU Logic reset */ + writel_relaxed(0x100203, l2_base + L2_PWR_CTL); + mb(); + udelay(54); + + /* Turn on the PMIC_APC */ + writel_relaxed(0x10100203, l2_base + L2_PWR_CTL); + + /* Set H/W clock control for the cpu CBC block */ + writel_relaxed(0x03, l2_base + L2_CORE_CBCR); + mb(); + iounmap(l2_base); + + return 0; +} + +static const struct msm_l2ccc_of_info l2ccc_info[] = { + { + .compat = "qcom,8916-l2ccc", + .l2_power_on = power_on_l2_msm8916, + .l2_power_on_mask = BIT(9), + }, +}; + +static int power_on_l2_cache(struct device_node *l2ccc_node, int cpu) +{ + int ret, i; + const char *compat; + + ret = of_property_read_string(l2ccc_node, "compatible", &compat); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(l2ccc_info); i++) { + const struct msm_l2ccc_of_info *ptr = &l2ccc_info[i]; + + if (!of_compat_cmp(ptr->compat, compat, strlen(compat))) + return ptr->l2_power_on(l2ccc_node, + ptr->l2_power_on_mask, cpu); + } + pr_err("Compat string not found for L2CCC node\n"); + return -EIO; +} + +static int msm_unclamp_secondary_arm_cpu(unsigned int cpu) +{ + + int ret = 0; + struct device_node *cpu_node, *acc_node, *l2_node, *l2ccc_node; + void __iomem *reg; + + cpu_node = of_get_cpu_node(cpu, NULL); + if (!cpu_node) + return -ENODEV; + + acc_node = of_parse_phandle(cpu_node, "qcom,acc", 0); + if (!acc_node) { + ret = -ENODEV; + goto out_acc; + } + + l2_node = of_parse_phandle(cpu_node, "next-level-cache", 0); + if (!l2_node) { + ret = -ENODEV; + goto out_l2; + } + + l2ccc_node = of_parse_phandle(l2_node, "power-domain", 0); + if (!l2ccc_node) { + ret = -ENODEV; + goto out_l2; + } + + /* Ensure L2-cache of the CPU is powered on before + * unclamping cpu power rails. + */ + ret = power_on_l2_cache(l2ccc_node, cpu); + if (ret) { + pr_err("L2 cache power up failed for CPU%d\n", cpu); + goto out_l2ccc; + } + + reg = of_iomap(acc_node, 0); + if (!reg) { + ret = -ENOMEM; + goto out_acc_reg; + } + + /* Assert Reset on cpu-n */ + writel_relaxed(0x00000033, reg + CPU_PWR_CTL); + mb(); + + /*Program skew to 16 X0 clock cycles*/ + writel_relaxed(0x10000001, reg + CPU_PWR_GATE_CTL); + mb(); + udelay(2); + + /* De-assert coremem clamp */ + writel_relaxed(0x00000031, reg + CPU_PWR_CTL); + mb(); + + /* Close coremem array gdhs */ + writel_relaxed(0x00000039, reg + CPU_PWR_CTL); + mb(); + udelay(2); + + /* De-assert cpu-n clamp */ + writel_relaxed(0x00020038, reg + CPU_PWR_CTL); + mb(); + udelay(2); + + /* De-assert cpu-n reset */ + writel_relaxed(0x00020008, reg + CPU_PWR_CTL); + mb(); + + /* Assert PWRDUP signal on core-n */ + writel_relaxed(0x00020088, reg + CPU_PWR_CTL); + mb(); + + /* Secondary CPU-N is now alive */ + iounmap(reg); +out_acc_reg: + of_node_put(l2ccc_node); +out_l2ccc: + of_node_put(l2_node); +out_l2: + of_node_put(acc_node); +out_acc: + of_node_put(cpu_node); + + return ret; +} + +static void write_pen_release(u64 val) +{ + void *start = (void *)&secondary_holding_pen_release; + unsigned long size = sizeof(secondary_holding_pen_release); + + secondary_holding_pen_release = val; + smp_wmb(); + __flush_dcache_area(start, size); +} + +static int secondary_pen_release(unsigned int cpu) +{ + unsigned long timeout; + + /* + * Set synchronisation state between this boot processor + * and the secondary one + */ + raw_spin_lock(&boot_lock); + write_pen_release(cpu_logical_map(cpu)); + + timeout = jiffies + (1 * HZ); + while (time_before(jiffies, timeout)) { + if (secondary_holding_pen_release == INVALID_HWID) + break; + udelay(10); + } + raw_spin_unlock(&boot_lock); + + return secondary_holding_pen_release != INVALID_HWID ? -ENOSYS : 0; +} + +static int __init msm_cpu_init(struct device_node *dn, unsigned int cpu) +{ + /* Mark CPU0 cold boot flag as done */ + if (!cpu && !per_cpu(cold_boot_done, cpu)) + per_cpu(cold_boot_done, cpu) = true; + + return 0; +} + +static int __init msm_cpu_prepare(unsigned int cpu) +{ + const cpumask_t *mask = cpumask_of(cpu); + + if (qcom_scm_set_cold_boot_addr(secondary_holding_pen, mask)) { + pr_warn("CPU%d:Failed to set boot address\n", cpu); + return -ENOSYS; + } + + return 0; +} + +static int msm_cpu_boot(unsigned int cpu) +{ + int ret = 0; + + if (per_cpu(cold_boot_done, cpu) == false) { + ret = msm_unclamp_secondary_arm_cpu(cpu); + if (ret) + return ret; + per_cpu(cold_boot_done, cpu) = true; + } + return secondary_pen_release(cpu); +} + +void msm_cpu_postboot(void) +{ + /* + * Let the primary processor know we're out of the pen. + */ + write_pen_release(INVALID_HWID); + + /* + * Synchronise with the boot thread. + */ + raw_spin_lock(&boot_lock); + raw_spin_unlock(&boot_lock); +} + +static const struct cpu_operations msm_cortex_a_ops = { + .name = "qcom,arm-cortex-acc", + .cpu_init = msm_cpu_init, + .cpu_prepare = msm_cpu_prepare, + .cpu_boot = msm_cpu_boot, + .cpu_postboot = msm_cpu_postboot, +}; +CPU_METHOD_OF_DECLARE(msm_cortex_a_ops, &msm_cortex_a_ops); diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c new file mode 100644 index 000000000000..a1fc882255e0 --- /dev/null +++ b/drivers/soc/qcom/smd.c @@ -0,0 +1,1324 @@ +/* + * Copyright (c) 2015, Sony Mobile Communications AB. + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * 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/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/soc/qcom/smd.h> +#include <linux/soc/qcom/smem.h> +#include <linux/wait.h> + +/* + * The Qualcomm Shared Memory communication solution provides point-to-point + * channels for clients to send and receive streaming or packet based data. + * + * Each channel consists of a control item (channel info) and a ring buffer + * pair. The channel info carry information related to channel state, flow + * control and the offsets within the ring buffer. + * + * All allocated channels are listed in an allocation table, identifying the + * pair of items by name, type and remote processor. + * + * Upon creating a new channel the remote processor allocates channel info and + * ring buffer items from the smem heap and populate the allocation table. An + * interrupt is sent to the other end of the channel and a scan for new + * channels should be done. A channel never goes away, it will only change + * state. + * + * The remote processor signals it intent for bring up the communication + * channel by setting the state of its end of the channel to "opening" and + * sends out an interrupt. We detect this change and register a smd device to + * consume the channel. Upon finding a consumer we finish the handshake and the + * channel is up. + * + * Upon closing a channel, the remote processor will update the state of its + * end of the channel and signal us, we will then unregister any attached + * device and close our end of the channel. + * + * Devices attached to a channel can use the qcom_smd_send function to push + * data to the channel, this is done by copying the data into the tx ring + * buffer, updating the pointers in the channel info and signaling the remote + * processor. + * + * The remote processor does the equivalent when it transfer data and upon + * receiving the interrupt we check the channel info for new data and delivers + * this to the attached device. If the device is not ready to receive the data + * we leave it in the ring buffer for now. + */ + +struct smd_channel_info; +struct smd_channel_info_word; + +#define SMD_ALLOC_TBL_COUNT 2 +#define SMD_ALLOC_TBL_SIZE 64 + +/* + * This lists the various smem heap items relevant for the allocation table and + * smd channel entries. + */ +static const struct { + unsigned alloc_tbl_id; + unsigned info_base_id; + unsigned fifo_base_id; +} smem_items[SMD_ALLOC_TBL_COUNT] = { + { + .alloc_tbl_id = 13, + .info_base_id = 14, + .fifo_base_id = 338 + }, + { + .alloc_tbl_id = 14, + .info_base_id = 266, + .fifo_base_id = 202, + }, +}; + +/** + * struct qcom_smd_edge - representing a remote processor + * @smd: handle to qcom_smd + * @of_node: of_node handle for information related to this edge + * @edge_id: identifier of this edge + * @irq: interrupt for signals on this edge + * @ipc_regmap: regmap handle holding the outgoing ipc register + * @ipc_offset: offset within @ipc_regmap of the register for ipc + * @ipc_bit: bit in the register at @ipc_offset of @ipc_regmap + * @channels: list of all channels detected on this edge + * @channels_lock: guard for modifications of @channels + * @need_rescan: flag that the @work needs to scan smem for new channels + * @smem_available: last available amount of smem triggering a channel scan + * @work: work item for edge house keeping + */ +struct qcom_smd_edge { + struct qcom_smd *smd; + struct device_node *of_node; + unsigned edge_id; + + int irq; + + struct regmap *ipc_regmap; + int ipc_offset; + int ipc_bit; + + struct list_head channels; + spinlock_t channels_lock; + + bool need_rescan; + unsigned smem_available; + + struct work_struct work; +}; + +/* + * SMD channel states. + */ +enum smd_channel_state { + SMD_CHANNEL_CLOSED, + SMD_CHANNEL_OPENING, + SMD_CHANNEL_OPENED, + SMD_CHANNEL_FLUSHING, + SMD_CHANNEL_CLOSING, + SMD_CHANNEL_RESET, + SMD_CHANNEL_RESET_OPENING +}; + +/** + * struct qcom_smd_channel - smd channel struct + * @edge: qcom_smd_edge this channel is living on + * @qsdev: reference to a associated smd client device + * @name: name of the channel + * @state: local state of the channel + * @remote_state: remote state of the channel + * @tx_info: byte aligned outgoing channel info + * @rx_info: byte aligned incoming channel info + * @tx_info_word: word aligned outgoing channel info + * @rx_info_word: word aligned incoming channel info + * @tx_lock: lock to make writes to the channel mutually exclusive + * @fblockread_event: wakeup event tied to tx fBLOCKREADINTR + * @tx_fifo: pointer to the outgoing ring buffer + * @rx_fifo: pointer to the incoming ring buffer + * @fifo_size: size of each ring buffer + * @bounce_buffer: bounce buffer for reading wrapped packets + * @cb: callback function registered for this channel + * @recv_lock: guard for rx info modifications and cb pointer + * @pkt_size: size of the currently handled packet + * @list: lite entry for @channels in qcom_smd_edge + */ +struct qcom_smd_channel { + struct qcom_smd_edge *edge; + + struct qcom_smd_device *qsdev; + + char *name; + enum smd_channel_state state; + enum smd_channel_state remote_state; + + struct smd_channel_info *tx_info; + struct smd_channel_info *rx_info; + + struct smd_channel_info_word *tx_info_word; + struct smd_channel_info_word *rx_info_word; + + struct mutex tx_lock; + wait_queue_head_t fblockread_event; + + void *tx_fifo; + void *rx_fifo; + int fifo_size; + + void *bounce_buffer; + int (*cb)(struct qcom_smd_device *, const void *, size_t); + + spinlock_t recv_lock; + + int pkt_size; + + struct list_head list; +}; + +/** + * struct qcom_smd - smd struct + * @dev: device struct + * @allocated: bitmap representing already allocated channels + * @alloc_tbl: pointer(s) to the allocation table(s) + * @alloc_tbl_entries: number of actual allocation entries found + * @num_edges: number of entries in @edges + * @edges: array of edges to be handled + */ +struct qcom_smd { + struct device *dev; + + DECLARE_BITMAP(allocated, SMD_ALLOC_TBL_COUNT * SMD_ALLOC_TBL_SIZE); + struct qcom_smd_alloc_entry *alloc_tbl[SMD_ALLOC_TBL_COUNT]; + unsigned alloc_tbl_entries; + + unsigned num_edges; + struct qcom_smd_edge edges[0]; +}; + +/* + * Format of the smd_info smem items, for byte aligned channels. + */ +struct smd_channel_info { + u32 state; + u8 fDSR; + u8 fCTS; + u8 fCD; + u8 fRI; + u8 fHEAD; + u8 fTAIL; + u8 fSTATE; + u8 fBLOCKREADINTR; + u32 tail; + u32 head; +}; + +/* + * Format of the smd_info smem items, for word aligned channels. + */ +struct smd_channel_info_word { + u32 state; + u32 fDSR; + u32 fCTS; + u32 fCD; + u32 fRI; + u32 fHEAD; + u32 fTAIL; + u32 fSTATE; + u32 fBLOCKREADINTR; + u32 tail; + u32 head; +}; + +#define GET_RX_CHANNEL_INFO(channel, param) \ + (channel->rx_info_word ? \ + channel->rx_info_word->param : \ + channel->rx_info->param) + +#define SET_RX_CHANNEL_INFO(channel, param, value) \ + (channel->rx_info_word ? \ + (channel->rx_info_word->param = value) : \ + (channel->rx_info->param = value)) + +#define GET_TX_CHANNEL_INFO(channel, param) \ + (channel->rx_info_word ? \ + channel->tx_info_word->param : \ + channel->tx_info->param) + +#define SET_TX_CHANNEL_INFO(channel, param, value) \ + (channel->rx_info_word ? \ + (channel->tx_info_word->param = value) : \ + (channel->tx_info->param = value)) + +/** + * struct qcom_smd_alloc_entry - channel allocation entry + * @name: channel name + * @cid: channel index + * @flags: channel flags and edge id + * @ref_count: reference count of the channel + */ +struct qcom_smd_alloc_entry { + u8 name[20]; + u32 cid; + u32 flags; + u32 ref_count; +} __packed; + +#define SMD_CHANNEL_FLAGS_EDGE_MASK 0xff +#define SMD_CHANNEL_FLAGS_STREAM BIT(8) +#define SMD_CHANNEL_FLAGS_PACKET BIT(9) + +/* + * Each smd packet contains a 20 byte header, with the first 4 being the length + * of the packet. + */ +#define SMD_PACKET_HEADER_LEN 20 + +/* + * Signal the remote processor associated with 'channel'. + */ +static void qcom_smd_signal_channel(struct qcom_smd_channel *channel) +{ + struct qcom_smd_edge *edge = channel->edge; + + regmap_write(edge->ipc_regmap, edge->ipc_offset, BIT(edge->ipc_bit)); +} + +/* + * Initialize the tx channel info + */ +static void qcom_smd_channel_reset(struct qcom_smd_channel *channel) +{ + SET_TX_CHANNEL_INFO(channel, state, SMD_CHANNEL_CLOSED); + SET_TX_CHANNEL_INFO(channel, fDSR, 0); + SET_TX_CHANNEL_INFO(channel, fCTS, 0); + SET_TX_CHANNEL_INFO(channel, fCD, 0); + SET_TX_CHANNEL_INFO(channel, fRI, 0); + SET_TX_CHANNEL_INFO(channel, fHEAD, 0); + SET_TX_CHANNEL_INFO(channel, fTAIL, 0); + SET_TX_CHANNEL_INFO(channel, fSTATE, 1); + SET_TX_CHANNEL_INFO(channel, fBLOCKREADINTR, 0); + SET_TX_CHANNEL_INFO(channel, head, 0); + SET_TX_CHANNEL_INFO(channel, tail, 0); + + qcom_smd_signal_channel(channel); + + channel->state = SMD_CHANNEL_CLOSED; + channel->pkt_size = 0; +} + +/* + * Calculate the amount of data available in the rx fifo + */ +static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel) +{ + unsigned head; + unsigned tail; + + head = GET_RX_CHANNEL_INFO(channel, head); + tail = GET_RX_CHANNEL_INFO(channel, tail); + + return (head - tail) & (channel->fifo_size - 1); +} + +/* + * Set tx channel state and inform the remote processor + */ +static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel, + int state) +{ + struct qcom_smd_edge *edge = channel->edge; + bool is_open = state == SMD_CHANNEL_OPENED; + + if (channel->state == state) + return; + + dev_dbg(edge->smd->dev, "set_state(%s, %d)\n", channel->name, state); + + SET_TX_CHANNEL_INFO(channel, fDSR, is_open); + SET_TX_CHANNEL_INFO(channel, fCTS, is_open); + SET_TX_CHANNEL_INFO(channel, fCD, is_open); + + SET_TX_CHANNEL_INFO(channel, state, state); + SET_TX_CHANNEL_INFO(channel, fSTATE, 1); + + channel->state = state; + qcom_smd_signal_channel(channel); +} + +/* + * Copy count bytes of data using 32bit accesses, if that's required. + */ +static void smd_copy_to_fifo(void __iomem *_dst, + const void *_src, + size_t count, + bool word_aligned) +{ + u32 *dst = (u32 *)_dst; + u32 *src = (u32 *)_src; + + if (word_aligned) { + count /= sizeof(u32); + while (count--) + writel_relaxed(*src++, dst++); + } else { + memcpy(_dst, _src, count); + } +} + +/* + * Copy count bytes of data using 32bit accesses, if that is required. + */ +static void smd_copy_from_fifo(void *_dst, + const void __iomem *_src, + size_t count, + bool word_aligned) +{ + u32 *dst = (u32 *)_dst; + u32 *src = (u32 *)_src; + + if (word_aligned) { + count /= sizeof(u32); + while (count--) + *dst++ = readl_relaxed(src++); + } else { + memcpy(_dst, _src, count); + } +} + +/* + * Read count bytes of data from the rx fifo into buf, but don't advance the + * tail. + */ +static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel, + void *buf, size_t count) +{ + bool word_aligned; + unsigned tail; + size_t len; + + word_aligned = channel->rx_info_word != NULL; + tail = GET_RX_CHANNEL_INFO(channel, tail); + + len = min_t(size_t, count, channel->fifo_size - tail); + if (len) { + smd_copy_from_fifo(buf, + channel->rx_fifo + tail, + len, + word_aligned); + } + + if (len != count) { + smd_copy_from_fifo(buf + len, + channel->rx_fifo, + count - len, + word_aligned); + } + + return count; +} + +/* + * Advance the rx tail by count bytes. + */ +static void qcom_smd_channel_advance(struct qcom_smd_channel *channel, + size_t count) +{ + unsigned tail; + + tail = GET_RX_CHANNEL_INFO(channel, tail); + tail += count; + tail &= (channel->fifo_size - 1); + SET_RX_CHANNEL_INFO(channel, tail, tail); +} + +/* + * Read out a single packet from the rx fifo and deliver it to the device + */ +static int qcom_smd_channel_recv_single(struct qcom_smd_channel *channel) +{ + struct qcom_smd_device *qsdev = channel->qsdev; + unsigned tail; + size_t len; + void *ptr; + int ret; + + if (!channel->cb) + return 0; + + tail = GET_RX_CHANNEL_INFO(channel, tail); + + /* Use bounce buffer if the data wraps */ + if (tail + channel->pkt_size >= channel->fifo_size) { + ptr = channel->bounce_buffer; + len = qcom_smd_channel_peek(channel, ptr, channel->pkt_size); + } else { + ptr = channel->rx_fifo + tail; + len = channel->pkt_size; + } + + ret = channel->cb(qsdev, ptr, len); + if (ret < 0) + return ret; + + /* Only forward the tail if the client consumed the data */ + qcom_smd_channel_advance(channel, len); + + channel->pkt_size = 0; + + return 0; +} + +/* + * Per channel interrupt handling + */ +static bool qcom_smd_channel_intr(struct qcom_smd_channel *channel) +{ + bool need_state_scan = false; + int remote_state; + u32 pktlen; + int avail; + int ret; + + /* Handle state changes */ + remote_state = GET_RX_CHANNEL_INFO(channel, state); + if (remote_state != channel->remote_state) { + channel->remote_state = remote_state; + need_state_scan = true; + } + /* Indicate that we have seen any state change */ + SET_RX_CHANNEL_INFO(channel, fSTATE, 0); + + /* Signal waiting qcom_smd_send() about the interrupt */ + if (!GET_TX_CHANNEL_INFO(channel, fBLOCKREADINTR)) + wake_up_interruptible(&channel->fblockread_event); + + /* Don't consume any data until we've opened the channel */ + if (channel->state != SMD_CHANNEL_OPENED) + goto out; + + /* Indicate that we've seen the new data */ + SET_RX_CHANNEL_INFO(channel, fHEAD, 0); + + /* Consume data */ + for (;;) { + avail = qcom_smd_channel_get_rx_avail(channel); + + if (!channel->pkt_size && avail >= SMD_PACKET_HEADER_LEN) { + qcom_smd_channel_peek(channel, &pktlen, sizeof(pktlen)); + qcom_smd_channel_advance(channel, SMD_PACKET_HEADER_LEN); + channel->pkt_size = pktlen; + } else if (channel->pkt_size && avail >= channel->pkt_size) { + ret = qcom_smd_channel_recv_single(channel); + if (ret) + break; + } else { + break; + } + } + + /* Indicate that we have seen and updated tail */ + SET_RX_CHANNEL_INFO(channel, fTAIL, 1); + + /* Signal the remote that we've consumed the data (if requested) */ + if (!GET_RX_CHANNEL_INFO(channel, fBLOCKREADINTR)) { + /* Ensure ordering of channel info updates */ + wmb(); + + qcom_smd_signal_channel(channel); + } + +out: + return need_state_scan; +} + +/* + * The edge interrupts are triggered by the remote processor on state changes, + * channel info updates or when new channels are created. + */ +static irqreturn_t qcom_smd_edge_intr(int irq, void *data) +{ + struct qcom_smd_edge *edge = data; + struct qcom_smd_channel *channel; + unsigned available; + bool kick_worker = false; + + /* + * Handle state changes or data on each of the channels on this edge + */ + spin_lock(&edge->channels_lock); + list_for_each_entry(channel, &edge->channels, list) { + spin_lock(&channel->recv_lock); + kick_worker |= qcom_smd_channel_intr(channel); + spin_unlock(&channel->recv_lock); + } + spin_unlock(&edge->channels_lock); + + /* + * Creating a new channel requires allocating an smem entry, so we only + * have to scan if the amount of available space in smem have changed + * since last scan. + */ + available = qcom_smem_get_free_space(edge->edge_id); + if (available != edge->smem_available) { + edge->smem_available = available; + edge->need_rescan = true; + kick_worker = true; + } + + if (kick_worker) + schedule_work(&edge->work); + + return IRQ_HANDLED; +} + +/* + * Delivers any outstanding packets in the rx fifo, can be used after probe of + * the clients to deliver any packets that wasn't delivered before the client + * was setup. + */ +static void qcom_smd_channel_resume(struct qcom_smd_channel *channel) +{ + unsigned long flags; + + spin_lock_irqsave(&channel->recv_lock, flags); + qcom_smd_channel_intr(channel); + spin_unlock_irqrestore(&channel->recv_lock, flags); +} + +/* + * Calculate how much space is available in the tx fifo. + */ +static size_t qcom_smd_get_tx_avail(struct qcom_smd_channel *channel) +{ + unsigned head; + unsigned tail; + unsigned mask = channel->fifo_size - 1; + + head = GET_TX_CHANNEL_INFO(channel, head); + tail = GET_TX_CHANNEL_INFO(channel, tail); + + return mask - ((head - tail) & mask); +} + +/* + * Write count bytes of data into channel, possibly wrapping in the ring buffer + */ +static int qcom_smd_write_fifo(struct qcom_smd_channel *channel, + const void *data, + size_t count) +{ + bool word_aligned; + unsigned head; + size_t len; + + word_aligned = channel->tx_info_word != NULL; + head = GET_TX_CHANNEL_INFO(channel, head); + + len = min_t(size_t, count, channel->fifo_size - head); + if (len) { + smd_copy_to_fifo(channel->tx_fifo + head, + data, + len, + word_aligned); + } + + if (len != count) { + smd_copy_to_fifo(channel->tx_fifo, + data + len, + count - len, + word_aligned); + } + + head += count; + head &= (channel->fifo_size - 1); + SET_TX_CHANNEL_INFO(channel, head, head); + + return count; +} + +/** + * qcom_smd_send - write data to smd channel + * @channel: channel handle + * @data: buffer of data to write + * @len: number of bytes to write + * + * This is a blocking write of len bytes into the channel's tx ring buffer and + * signal the remote end. It will sleep until there is enough space available + * in the tx buffer, utilizing the fBLOCKREADINTR signaling mechanism to avoid + * polling. + */ +int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len) +{ + u32 hdr[5] = {len,}; + int tlen = sizeof(hdr) + len; + int ret; + + /* Word aligned channels only accept word size aligned data */ + if (channel->rx_info_word != NULL && len % 4) + return -EINVAL; + + ret = mutex_lock_interruptible(&channel->tx_lock); + if (ret) + return ret; + + while (qcom_smd_get_tx_avail(channel) < tlen) { + if (channel->state != SMD_CHANNEL_OPENED) { + ret = -EPIPE; + goto out; + } + + SET_TX_CHANNEL_INFO(channel, fBLOCKREADINTR, 1); + + ret = wait_event_interruptible(channel->fblockread_event, + qcom_smd_get_tx_avail(channel) >= tlen || + channel->state != SMD_CHANNEL_OPENED); + if (ret) + goto out; + + SET_TX_CHANNEL_INFO(channel, fBLOCKREADINTR, 0); + } + + SET_TX_CHANNEL_INFO(channel, fTAIL, 0); + + qcom_smd_write_fifo(channel, hdr, sizeof(hdr)); + qcom_smd_write_fifo(channel, data, len); + + SET_TX_CHANNEL_INFO(channel, fHEAD, 1); + + /* Ensure ordering of channel info updates */ + wmb(); + + qcom_smd_signal_channel(channel); + +out: + mutex_unlock(&channel->tx_lock); + + return ret; +} +EXPORT_SYMBOL(qcom_smd_send); + +static struct qcom_smd_device *to_smd_device(struct device *dev) +{ + return container_of(dev, struct qcom_smd_device, dev); +} + +static struct qcom_smd_driver *to_smd_driver(struct device *dev) +{ + struct qcom_smd_device *qsdev = to_smd_device(dev); + + return container_of(qsdev->dev.driver, struct qcom_smd_driver, driver); +} + +static int qcom_smd_dev_match(struct device *dev, struct device_driver *drv) +{ + return of_driver_match_device(dev, drv); +} + +/* + * Probe the smd client. + * + * The remote side have indicated that it want the channel to be opened, so + * complete the state handshake and probe our client driver. + */ +static int qcom_smd_dev_probe(struct device *dev) +{ + struct qcom_smd_device *qsdev = to_smd_device(dev); + struct qcom_smd_driver *qsdrv = to_smd_driver(dev); + struct qcom_smd_channel *channel = qsdev->channel; + size_t bb_size; + int ret; + + /* + * Packets are maximum 4k, but reduce if the fifo is smaller + */ + bb_size = min(channel->fifo_size, SZ_4K); + channel->bounce_buffer = kmalloc(bb_size, GFP_KERNEL); + if (!channel->bounce_buffer) + return -ENOMEM; + + channel->cb = qsdrv->callback; + + qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING); + + qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED); + + ret = qsdrv->probe(qsdev); + if (ret) + goto err; + + qcom_smd_channel_resume(channel); + + return 0; + +err: + dev_err(&qsdev->dev, "probe failed\n"); + + channel->cb = NULL; + kfree(channel->bounce_buffer); + channel->bounce_buffer = NULL; + + qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); + return ret; +} + +/* + * Remove the smd client. + * + * The channel is going away, for some reason, so remove the smd client and + * reset the channel state. + */ +static int qcom_smd_dev_remove(struct device *dev) +{ + struct qcom_smd_device *qsdev = to_smd_device(dev); + struct qcom_smd_driver *qsdrv = to_smd_driver(dev); + struct qcom_smd_channel *channel = qsdev->channel; + unsigned long flags; + + qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSING); + + /* + * Make sure we don't race with the code receiving data. + */ + spin_lock_irqsave(&channel->recv_lock, flags); + channel->cb = NULL; + spin_unlock_irqrestore(&channel->recv_lock, flags); + + /* Wake up any sleepers in qcom_smd_send() */ + wake_up_interruptible(&channel->fblockread_event); + + /* + * We expect that the client might block in remove() waiting for any + * outstanding calls to qcom_smd_send() to wake up and finish. + */ + if (qsdrv->remove) + qsdrv->remove(qsdev); + + /* + * The client is now gone, cleanup and reset the channel state. + */ + channel->qsdev = NULL; + kfree(channel->bounce_buffer); + channel->bounce_buffer = NULL; + + qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); + + qcom_smd_channel_reset(channel); + + return 0; +} + +static struct bus_type qcom_smd_bus = { + .name = "qcom_smd", + .match = qcom_smd_dev_match, + .probe = qcom_smd_dev_probe, + .remove = qcom_smd_dev_remove, +}; + +/* + * Release function for the qcom_smd_device object. + */ +static void qcom_smd_release_device(struct device *dev) +{ + struct qcom_smd_device *qsdev = to_smd_device(dev); + + kfree(qsdev); +} + +/* + * Finds the device_node for the smd child interested in this channel. + */ +static struct device_node *qcom_smd_match_channel(struct device_node *edge_node, + const char *channel) +{ + struct device_node *child; + const char *name; + const char *key; + int ret; + + for_each_available_child_of_node(edge_node, child) { + key = "qcom,smd-channels"; + ret = of_property_read_string(child, key, &name); + if (ret) { + of_node_put(child); + continue; + } + + if (strcmp(name, channel) == 0) + return child; + } + + return NULL; +} + +/* + * Create a smd client device for channel that is being opened. + */ +static int qcom_smd_create_device(struct qcom_smd_channel *channel) +{ + struct qcom_smd_device *qsdev; + struct qcom_smd_edge *edge = channel->edge; + struct device_node *node; + struct qcom_smd *smd = edge->smd; + int ret; + + if (channel->qsdev) + return -EEXIST; + + node = qcom_smd_match_channel(edge->of_node, channel->name); + if (!node) { + dev_dbg(smd->dev, "no match for '%s'\n", channel->name); + return -ENXIO; + } + + dev_dbg(smd->dev, "registering '%s'\n", channel->name); + + qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL); + if (!qsdev) + return -ENOMEM; + + dev_set_name(&qsdev->dev, "%s.%s", edge->of_node->name, node->name); + qsdev->dev.parent = smd->dev; + qsdev->dev.bus = &qcom_smd_bus; + qsdev->dev.release = qcom_smd_release_device; + qsdev->dev.of_node = node; + + qsdev->channel = channel; + + channel->qsdev = qsdev; + + ret = device_register(&qsdev->dev); + if (ret) { + dev_err(smd->dev, "device_register failed: %d\n", ret); + put_device(&qsdev->dev); + } + + return ret; +} + +/* + * Destroy a smd client device for a channel that's going away. + */ +static void qcom_smd_destroy_device(struct qcom_smd_channel *channel) +{ + struct device *dev; + + BUG_ON(!channel->qsdev); + + dev = &channel->qsdev->dev; + + device_unregister(dev); + of_node_put(dev->of_node); + put_device(dev); +} + +/** + * qcom_smd_driver_register - register a smd driver + * @qsdrv: qcom_smd_driver struct + */ +int qcom_smd_driver_register(struct qcom_smd_driver *qsdrv) +{ + qsdrv->driver.bus = &qcom_smd_bus; + return driver_register(&qsdrv->driver); +} +EXPORT_SYMBOL(qcom_smd_driver_register); + +/** + * qcom_smd_driver_unregister - unregister a smd driver + * @qsdrv: qcom_smd_driver struct + */ +void qcom_smd_driver_unregister(struct qcom_smd_driver *qsdrv) +{ + driver_unregister(&qsdrv->driver); +} +EXPORT_SYMBOL(qcom_smd_driver_unregister); + +/* + * Allocate the qcom_smd_channel object for a newly found smd channel, + * retrieving and validating the smem items involved. + */ +static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *edge, + unsigned smem_info_item, + unsigned smem_fifo_item, + char *name) +{ + struct qcom_smd_channel *channel; + struct qcom_smd *smd = edge->smd; + size_t fifo_size; + size_t info_size; + void *fifo_base; + void *info; + int ret; + + channel = devm_kzalloc(smd->dev, sizeof(*channel), GFP_KERNEL); + if (!channel) + return ERR_PTR(-ENOMEM); + + channel->edge = edge; + channel->name = devm_kstrdup(smd->dev, name, GFP_KERNEL); + if (!channel->name) + return ERR_PTR(-ENOMEM); + + mutex_init(&channel->tx_lock); + spin_lock_init(&channel->recv_lock); + init_waitqueue_head(&channel->fblockread_event); + + ret = qcom_smem_get(edge->edge_id, smem_info_item, (void **)&info, &info_size); + if (ret) + goto free_name_and_channel; + + /* + * Use the size of the item to figure out which channel info struct to + * use. + */ + if (info_size == 2 * sizeof(struct smd_channel_info_word)) { + channel->tx_info_word = info; + channel->rx_info_word = info + sizeof(struct smd_channel_info_word); + } else if (info_size == 2 * sizeof(struct smd_channel_info)) { + channel->tx_info = info; + channel->rx_info = info + sizeof(struct smd_channel_info); + } else { + dev_err(smd->dev, + "channel info of size %zu not supported\n", info_size); + ret = -EINVAL; + goto free_name_and_channel; + } + + ret = qcom_smem_get(edge->edge_id, smem_fifo_item, &fifo_base, &fifo_size); + if (ret) + goto free_name_and_channel; + + /* The channel consist of a rx and tx fifo of equal size */ + fifo_size /= 2; + + dev_dbg(smd->dev, "new channel '%s' info-size: %d fifo-size: %zu\n", + name, info_size, fifo_size); + + channel->tx_fifo = fifo_base; + channel->rx_fifo = fifo_base + fifo_size; + channel->fifo_size = fifo_size; + + qcom_smd_channel_reset(channel); + + return channel; + +free_name_and_channel: + devm_kfree(smd->dev, channel->name); + devm_kfree(smd->dev, channel); + + return ERR_PTR(ret); +} + +/* + * Scans the allocation table for any newly allocated channels, calls + * qcom_smd_create_channel() to create representations of these and add + * them to the edge's list of channels. + */ +static void qcom_discover_channels(struct qcom_smd_edge *edge) +{ + struct qcom_smd_alloc_entry *entry; + struct qcom_smd_channel *channel; + struct qcom_smd *smd = edge->smd; + unsigned long flags; + unsigned fifo_id; + unsigned info_id; + int tbl; + int i; + + for (i = 0; i < smd->alloc_tbl_entries; i++) { + tbl = i / SMD_ALLOC_TBL_SIZE; + + entry = &smd->alloc_tbl[tbl][i]; + if (test_bit(i, smd->allocated)) + continue; + + if (entry->ref_count == 0) + continue; + + if (!entry->name[0]) + continue; + + if (!(entry->flags & SMD_CHANNEL_FLAGS_PACKET)) + continue; + + if ((entry->flags & SMD_CHANNEL_FLAGS_EDGE_MASK) != edge->edge_id) + continue; + + info_id = smem_items[tbl].info_base_id + entry->cid; + fifo_id = smem_items[tbl].fifo_base_id + entry->cid; + + channel = qcom_smd_create_channel(edge, info_id, fifo_id, entry->name); + if (IS_ERR(channel)) + continue; + + spin_lock_irqsave(&edge->channels_lock, flags); + list_add(&channel->list, &edge->channels); + spin_unlock_irqrestore(&edge->channels_lock, flags); + + dev_dbg(smd->dev, "new channel found: '%s'\n", channel->name); + set_bit(i, smd->allocated); + } + + schedule_work(&edge->work); +} + +/* + * This per edge worker scans smem for any new channels and register these. It + * then scans all registered channels for state changes that should be handled + * by creating or destroying smd client devices for the registered channels. + * + * LOCKING: edge->channels_lock is not needed to be held during the traversal + * of the channels list as it's done synchronously with the only writer. + */ +static void qcom_channel_state_worker(struct work_struct *work) +{ + struct qcom_smd_channel *channel; + struct qcom_smd_edge *edge = container_of(work, + struct qcom_smd_edge, + work); + unsigned remote_state; + + /* + * Rescan smem if we have reason to belive that there are new channels. + */ + if (edge->need_rescan) { + edge->need_rescan = false; + qcom_discover_channels(edge); + } + + /* + * Register a device for any closed channel where the remote processor + * is showing interest in opening the channel. + */ + list_for_each_entry(channel, &edge->channels, list) { + if (channel->state != SMD_CHANNEL_CLOSED) + continue; + + remote_state = GET_RX_CHANNEL_INFO(channel, state); + if (remote_state != SMD_CHANNEL_OPENING && + remote_state != SMD_CHANNEL_OPENED) + continue; + + qcom_smd_create_device(channel); + } + + /* + * Unregister the device for any channel that is opened where the + * remote processor is closing the channel. + */ + list_for_each_entry(channel, &edge->channels, list) { + if (channel->state != SMD_CHANNEL_OPENING && + channel->state != SMD_CHANNEL_OPENED) + continue; + + remote_state = GET_RX_CHANNEL_INFO(channel, state); + if (remote_state == SMD_CHANNEL_OPENING || + remote_state == SMD_CHANNEL_OPENED) + continue; + + qcom_smd_destroy_device(channel); + } +} + +/* + * Parses an of_node describing an edge. + */ +static int qcom_smd_parse_edge(struct device *dev, + struct device_node *node, + struct qcom_smd_edge *edge) +{ + struct device_node *syscon_np; + const char *key; + int irq; + int ret; + + INIT_LIST_HEAD(&edge->channels); + spin_lock_init(&edge->channels_lock); + + INIT_WORK(&edge->work, qcom_channel_state_worker); + + edge->of_node = of_node_get(node); + + irq = irq_of_parse_and_map(node, 0); + if (irq < 0) { + dev_err(dev, "required smd interrupt missing\n"); + return -EINVAL; + } + + ret = devm_request_irq(dev, irq, + qcom_smd_edge_intr, IRQF_TRIGGER_RISING, + node->name, edge); + if (ret) { + dev_err(dev, "failed to request smd irq\n"); + return ret; + } + + edge->irq = irq; + + key = "qcom,smd-edge"; + ret = of_property_read_u32(node, key, &edge->edge_id); + if (ret) { + dev_err(dev, "edge missing %s property\n", key); + return -EINVAL; + } + + syscon_np = of_parse_phandle(node, "qcom,ipc", 0); + if (!syscon_np) { + dev_err(dev, "no qcom,ipc node\n"); + return -ENODEV; + } + + edge->ipc_regmap = syscon_node_to_regmap(syscon_np); + if (IS_ERR(edge->ipc_regmap)) + return PTR_ERR(edge->ipc_regmap); + + key = "qcom,ipc"; + ret = of_property_read_u32_index(node, key, 1, &edge->ipc_offset); + if (ret < 0) { + dev_err(dev, "no offset in %s\n", key); + return -EINVAL; + } + + ret = of_property_read_u32_index(node, key, 2, &edge->ipc_bit); + if (ret < 0) { + dev_err(dev, "no bit in %s\n", key); + return -EINVAL; + } + + return 0; +} + +static int qcom_smd_probe(struct platform_device *pdev) +{ + struct qcom_smd_edge *edge; + struct device_node *node; + struct qcom_smd *smd; + size_t array_size; + int num_edges; + int ret; + int i = 0; + + num_edges = of_get_available_child_count(pdev->dev.of_node); + array_size = sizeof(*smd) + num_edges * sizeof(struct qcom_smd_edge); + smd = devm_kzalloc(&pdev->dev, array_size, GFP_KERNEL); + if (!smd) + return -ENOMEM; + smd->dev = &pdev->dev; + + ret = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[0].alloc_tbl_id, + (void **)&smd->alloc_tbl[0], NULL); + if (ret < 0) { + dev_err(smd->dev, "primary allocation table not available\n"); + return ret; + }; + + smd->alloc_tbl_entries = SMD_ALLOC_TBL_SIZE; + + ret = qcom_smem_get(QCOM_SMEM_HOST_ANY, smem_items[1].alloc_tbl_id, + (void **)&smd->alloc_tbl[1], NULL); + if (ret >= 0) + smd->alloc_tbl_entries += SMD_ALLOC_TBL_SIZE; + + smd->num_edges = num_edges; + for_each_available_child_of_node(pdev->dev.of_node, node) { + edge = &smd->edges[i++]; + edge->smd = smd; + + ret = qcom_smd_parse_edge(&pdev->dev, node, edge); + if (ret) + continue; + + edge->need_rescan = true; + schedule_work(&edge->work); + } + + platform_set_drvdata(pdev, smd); + + return 0; +} + +/* + * Shut down all smd clients by making sure that each edge stops processing + * events and scanning for new channels, then call destroy on the devices. + */ +static int qcom_smd_remove(struct platform_device *pdev) +{ + struct qcom_smd_channel *channel; + struct qcom_smd_edge *edge; + struct qcom_smd *smd = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < smd->num_edges; i++) { + edge = &smd->edges[i]; + + disable_irq(edge->irq); + cancel_work_sync(&edge->work); + + list_for_each_entry(channel, &edge->channels, list) { + if (!channel->qsdev) + continue; + + qcom_smd_destroy_device(channel); + } + } + + return 0; +} + +static const struct of_device_id qcom_smd_of_match[] = { + { .compatible = "qcom,smd" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_smd_of_match); + +static struct platform_driver qcom_smd_driver = { + .probe = qcom_smd_probe, + .remove = qcom_smd_remove, + .driver = { + .name = "qcom-smd", + .of_match_table = qcom_smd_of_match, + }, +}; + +static int __init qcom_smd_init(void) +{ + int ret; + + ret = bus_register(&qcom_smd_bus); + if (ret) { + pr_err("failed to register smd bus: %d\n", ret); + return ret; + } + + return platform_driver_register(&qcom_smd_driver); +} +arch_initcall(qcom_smd_init); + +static void __exit qcom_smd_exit(void) +{ + platform_driver_unregister(&qcom_smd_driver); + bus_unregister(&qcom_smd_bus); +} +module_exit(qcom_smd_exit); + +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); +MODULE_DESCRIPTION("Qualcomm Shared Memory Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c new file mode 100644 index 000000000000..7c2c324c4b10 --- /dev/null +++ b/drivers/soc/qcom/smem.c @@ -0,0 +1,775 @@ +/* + * Copyright (c) 2015, Sony Mobile Communications AB. + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * 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/hwspinlock.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/soc/qcom/smem.h> + +/* + * The Qualcomm shared memory system is a allocate only heap structure that + * consists of one of more memory areas that can be accessed by the processors + * in the SoC. + * + * All systems contains a global heap, accessible by all processors in the SoC, + * with a table of contents data structure (@smem_header) at the beginning of + * the main shared memory block. + * + * The global header contains meta data for allocations as well as a fixed list + * of 512 entries (@smem_global_entry) that can be initialized to reference + * parts of the shared memory space. + * + * + * In addition to this global heap a set of "private" heaps can be set up at + * boot time with access restrictions so that only certain processor pairs can + * access the data. + * + * These partitions are referenced from an optional partition table + * (@smem_ptable), that is found 4kB from the end of the main smem region. The + * partition table entries (@smem_ptable_entry) lists the involved processors + * (or hosts) and their location in the main shared memory region. + * + * Each partition starts with a header (@smem_partition_header) that identifies + * the partition and holds properties for the two internal memory regions. The + * two regions are cached and non-cached memory respectively. Each region + * contain a link list of allocation headers (@smem_private_entry) followed by + * their data. + * + * Items in the non-cached region are allocated from the start of the partition + * while items in the cached region are allocated from the end. The free area + * is hence the region between the cached and non-cached offsets. + * + * + * To synchronize allocations in the shared memory heaps a remote spinlock must + * be held - currently lock number 3 of the sfpb or tcsr is used for this on all + * platforms. + * + */ + +/* + * Item 3 of the global heap contains an array of versions for the various + * software components in the SoC. We verify that the boot loader version is + * what the expected version (SMEM_EXPECTED_VERSION) as a sanity check. + */ +#define SMEM_ITEM_VERSION 3 +#define SMEM_MASTER_SBL_VERSION_INDEX 7 +#define SMEM_EXPECTED_VERSION 11 + +/* + * The first 8 items are only to be allocated by the boot loader while + * initializing the heap. + */ +#define SMEM_ITEM_LAST_FIXED 8 + +/* Highest accepted item number, for both global and private heaps */ +#define SMEM_ITEM_COUNT 512 + +/* Processor/host identifier for the application processor */ +#define SMEM_HOST_APPS 0 + +/* Max number of processors/hosts in a system */ +#define SMEM_HOST_COUNT 9 + +/** + * struct smem_proc_comm - proc_comm communication struct (legacy) + * @command: current command to be executed + * @status: status of the currently requested command + * @params: parameters to the command + */ +struct smem_proc_comm { + u32 command; + u32 status; + u32 params[2]; +}; + +/** + * struct smem_global_entry - entry to reference smem items on the heap + * @allocated: boolean to indicate if this entry is used + * @offset: offset to the allocated space + * @size: size of the allocated space, 8 byte aligned + * @aux_base: base address for the memory region used by this unit, or 0 for + * the default region. bits 0,1 are reserved + */ +struct smem_global_entry { + u32 allocated; + u32 offset; + u32 size; + u32 aux_base; /* bits 1:0 reserved */ +}; +#define AUX_BASE_MASK 0xfffffffc + +/** + * struct smem_header - header found in beginning of primary smem region + * @proc_comm: proc_comm communication interface (legacy) + * @version: array of versions for the various subsystems + * @initialized: boolean to indicate that smem is initialized + * @free_offset: index of the first unallocated byte in smem + * @available: number of bytes available for allocation + * @reserved: reserved field, must be 0 + * toc: array of references to items + */ +struct smem_header { + struct smem_proc_comm proc_comm[4]; + u32 version[32]; + u32 initialized; + u32 free_offset; + u32 available; + u32 reserved; + struct smem_global_entry toc[SMEM_ITEM_COUNT]; +}; + +/** + * struct smem_ptable_entry - one entry in the @smem_ptable list + * @offset: offset, within the main shared memory region, of the partition + * @size: size of the partition + * @flags: flags for the partition (currently unused) + * @host0: first processor/host with access to this partition + * @host1: second processor/host with access to this partition + * @reserved: reserved entries for later use + */ +struct smem_ptable_entry { + u32 offset; + u32 size; + u32 flags; + u16 host0; + u16 host1; + u32 reserved[8]; +}; + +/** + * struct smem_ptable - partition table for the private partitions + * @magic: magic number, must be SMEM_PTABLE_MAGIC + * @version: version of the partition table + * @num_entries: number of partitions in the table + * @reserved: for now reserved entries + * @entry: list of @smem_ptable_entry for the @num_entries partitions + */ +struct smem_ptable { + u32 magic; + u32 version; + u32 num_entries; + u32 reserved[5]; + struct smem_ptable_entry entry[]; +}; +#define SMEM_PTABLE_MAGIC 0x434f5424 /* "$TOC" */ + +/** + * struct smem_partition_header - header of the partitions + * @magic: magic number, must be SMEM_PART_MAGIC + * @host0: first processor/host with access to this partition + * @host1: second processor/host with access to this partition + * @size: size of the partition + * @offset_free_uncached: offset to the first free byte of uncached memory in + * this partition + * @offset_free_cached: offset to the first free byte of cached memory in this + * partition + * @reserved: for now reserved entries + */ +struct smem_partition_header { + u32 magic; + u16 host0; + u16 host1; + u32 size; + u32 offset_free_uncached; + u32 offset_free_cached; + u32 reserved[3]; +}; +#define SMEM_PART_MAGIC 0x54525024 /* "$PRT" */ + +/** + * struct smem_private_entry - header of each item in the private partition + * @canary: magic number, must be SMEM_PRIVATE_CANARY + * @item: identifying number of the smem item + * @size: size of the data, including padding bytes + * @padding_data: number of bytes of padding of data + * @padding_hdr: number of bytes of padding between the header and the data + * @reserved: for now reserved entry + */ +struct smem_private_entry { + u16 canary; + u16 item; + u32 size; /* includes padding bytes */ + u16 padding_data; + u16 padding_hdr; + u32 reserved; +}; +#define SMEM_PRIVATE_CANARY 0xa5a5 + +/** + * struct smem_region - representation of a chunk of memory used for smem + * @aux_base: identifier of aux_mem base + * @virt_base: virtual base address of memory with this aux_mem identifier + * @size: size of the memory region + */ +struct smem_region { + u32 aux_base; + void __iomem *virt_base; + size_t size; +}; + +/** + * struct qcom_smem - device data for the smem device + * @dev: device pointer + * @hwlock: reference to a hwspinlock + * @partitions: list of pointers to partitions affecting the current + * processor/host + * @num_regions: number of @regions + * @regions: list of the memory regions defining the shared memory + */ +struct qcom_smem { + struct device *dev; + + struct hwspinlock *hwlock; + + struct smem_partition_header *partitions[SMEM_HOST_COUNT]; + + unsigned num_regions; + struct smem_region regions[0]; +}; + +/* Pointer to the one and only smem handle */ +static struct qcom_smem *__smem; + +/* Timeout (ms) for the trylock of remote spinlocks */ +#define HWSPINLOCK_TIMEOUT 1000 + +static int qcom_smem_alloc_private(struct qcom_smem *smem, + unsigned host, + unsigned item, + size_t size) +{ + struct smem_partition_header *phdr; + struct smem_private_entry *hdr; + size_t alloc_size; + void *p; + + /* We're not going to find it if there's no matching partition */ + if (host >= SMEM_HOST_COUNT || !smem->partitions[host]) + return -ENOENT; + + phdr = smem->partitions[host]; + + p = (void *)phdr + sizeof(*phdr); + while (p < (void *)phdr + phdr->offset_free_uncached) { + hdr = p; + + if (hdr->canary != SMEM_PRIVATE_CANARY) { + dev_err(smem->dev, + "Found invalid canary in host %d partition\n", + host); + return -EINVAL; + } + + if (hdr->item == item) + return -EEXIST; + + p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; + } + + /* Check that we don't grow into the cached region */ + alloc_size = sizeof(*hdr) + ALIGN(size, 8); + if (p + alloc_size >= (void *)phdr + phdr->offset_free_cached) { + dev_err(smem->dev, "Out of memory\n"); + return -ENOSPC; + } + + hdr = p; + hdr->canary = SMEM_PRIVATE_CANARY; + hdr->item = item; + hdr->size = ALIGN(size, 8); + hdr->padding_data = hdr->size - size; + hdr->padding_hdr = 0; + + /* + * Ensure the header is written before we advance the free offset, so + * that remote processors that does not take the remote spinlock still + * gets a consistent view of the linked list. + */ + wmb(); + phdr->offset_free_uncached += alloc_size; + + return 0; +} + +static int qcom_smem_alloc_global(struct qcom_smem *smem, + unsigned item, + size_t size) +{ + struct smem_header *header; + struct smem_global_entry *entry; + + if (WARN_ON(item >= SMEM_ITEM_COUNT)) + return -EINVAL; + + header = smem->regions[0].virt_base; + entry = &header->toc[item]; + if (entry->allocated) + return -EEXIST; + + size = ALIGN(size, 8); + if (WARN_ON(size > header->available)) + return -ENOMEM; + + entry->offset = header->free_offset; + entry->size = size; + + /* + * Ensure the header is consistent before we mark the item allocated, + * so that remote processors will get a consistent view of the item + * even though they do not take the spinlock on read. + */ + wmb(); + entry->allocated = 1; + + header->free_offset += size; + header->available -= size; + + return 0; +} + +/** + * qcom_smem_alloc() - allocate space for a smem item + * @host: remote processor id, or -1 + * @item: smem item handle + * @size: number of bytes to be allocated + * + * Allocate space for a given smem item of size @size, given that the item is + * not yet allocated. + */ +int qcom_smem_alloc(unsigned host, unsigned item, size_t size) +{ + unsigned long flags; + int ret; + + if (!__smem) + return -EPROBE_DEFER; + + if (item < SMEM_ITEM_LAST_FIXED) { + dev_err(__smem->dev, + "Rejecting allocation of static entry %d\n", item); + return -EINVAL; + } + + ret = hwspin_lock_timeout_irqsave(__smem->hwlock, + HWSPINLOCK_TIMEOUT, + &flags); + if (ret) + return ret; + + ret = qcom_smem_alloc_private(__smem, host, item, size); + if (ret == -ENOENT) + ret = qcom_smem_alloc_global(__smem, item, size); + + hwspin_unlock_irqrestore(__smem->hwlock, &flags); + + return ret; +} +EXPORT_SYMBOL(qcom_smem_alloc); + +static int qcom_smem_get_global(struct qcom_smem *smem, + unsigned item, + void **ptr, + size_t *size) +{ + struct smem_header *header; + struct smem_region *area; + struct smem_global_entry *entry; + u32 aux_base; + unsigned i; + + if (WARN_ON(item >= SMEM_ITEM_COUNT)) + return -EINVAL; + + header = smem->regions[0].virt_base; + entry = &header->toc[item]; + if (!entry->allocated) + return -ENXIO; + + if (ptr != NULL) { + aux_base = entry->aux_base & AUX_BASE_MASK; + + for (i = 0; i < smem->num_regions; i++) { + area = &smem->regions[i]; + + if (area->aux_base == aux_base || !aux_base) { + *ptr = area->virt_base + entry->offset; + break; + } + } + } + if (size != NULL) + *size = entry->size; + + return 0; +} + +static int qcom_smem_get_private(struct qcom_smem *smem, + unsigned host, + unsigned item, + void **ptr, + size_t *size) +{ + struct smem_partition_header *phdr; + struct smem_private_entry *hdr; + void *p; + + /* We're not going to find it if there's no matching partition */ + if (host >= SMEM_HOST_COUNT || !smem->partitions[host]) + return -ENOENT; + + phdr = smem->partitions[host]; + + p = (void *)phdr + sizeof(*phdr); + while (p < (void *)phdr + phdr->offset_free_uncached) { + hdr = p; + + if (hdr->canary != SMEM_PRIVATE_CANARY) { + dev_err(smem->dev, + "Found invalid canary in host %d partition\n", + host); + return -EINVAL; + } + + if (hdr->item == item) { + if (ptr != NULL) + *ptr = p + sizeof(*hdr) + hdr->padding_hdr; + + if (size != NULL) + *size = hdr->size - hdr->padding_data; + + return 0; + } + + p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; + } + + return -ENOENT; +} + +/** + * qcom_smem_get() - resolve ptr of size of a smem item + * @host: the remote processor, or -1 + * @item: smem item handle + * @ptr: pointer to be filled out with address of the item + * @size: pointer to be filled out with size of the item + * + * Looks up pointer and size of a smem item. + */ +int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size) +{ + unsigned long flags; + int ret; + + if (!__smem) + return -EPROBE_DEFER; + + ret = hwspin_lock_timeout_irqsave(__smem->hwlock, + HWSPINLOCK_TIMEOUT, + &flags); + if (ret) + return ret; + + ret = qcom_smem_get_private(__smem, host, item, ptr, size); + if (ret == -ENOENT) + ret = qcom_smem_get_global(__smem, item, ptr, size); + + hwspin_unlock_irqrestore(__smem->hwlock, &flags); + return ret; + +} +EXPORT_SYMBOL(qcom_smem_get); + +/** + * qcom_smem_get_free_space() - retrieve amount of free space in a partition + * @host: the remote processor identifying a partition, or -1 + * + * To be used by smem clients as a quick way to determine if any new + * allocations has been made. + */ +int qcom_smem_get_free_space(unsigned host) +{ + struct smem_partition_header *phdr; + struct smem_header *header; + unsigned ret; + + if (!__smem) + return -EPROBE_DEFER; + + if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { + phdr = __smem->partitions[host]; + ret = phdr->offset_free_cached - phdr->offset_free_uncached; + } else { + header = __smem->regions[0].virt_base; + ret = header->available; + } + + return ret; +} +EXPORT_SYMBOL(qcom_smem_get_free_space); + +static int qcom_smem_get_sbl_version(struct qcom_smem *smem) +{ + unsigned *versions; + size_t size; + int ret; + + ret = qcom_smem_get_global(smem, SMEM_ITEM_VERSION, + (void **)&versions, &size); + if (ret < 0) { + dev_err(smem->dev, "Unable to read the version item\n"); + return -ENOENT; + } + + if (size < sizeof(unsigned) * SMEM_MASTER_SBL_VERSION_INDEX) { + dev_err(smem->dev, "Version item is too small\n"); + return -EINVAL; + } + + return versions[SMEM_MASTER_SBL_VERSION_INDEX]; +} + +static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, + unsigned local_host) +{ + struct smem_partition_header *header; + struct smem_ptable_entry *entry; + struct smem_ptable *ptable; + unsigned remote_host; + int i; + + ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; + if (ptable->magic != SMEM_PTABLE_MAGIC) + return 0; + + if (ptable->version != 1) { + dev_err(smem->dev, + "Unsupported partition header version %d\n", + ptable->version); + return -EINVAL; + } + + for (i = 0; i < ptable->num_entries; i++) { + entry = &ptable->entry[i]; + + if (entry->host0 != local_host && entry->host1 != local_host) + continue; + + if (!entry->offset) + continue; + + if (!entry->size) + continue; + + if (entry->host0 == local_host) + remote_host = entry->host1; + else + remote_host = entry->host0; + + if (remote_host >= SMEM_HOST_COUNT) { + dev_err(smem->dev, + "Invalid remote host %d\n", + remote_host); + return -EINVAL; + } + + if (smem->partitions[remote_host]) { + dev_err(smem->dev, + "Already found a partition for host %d\n", + remote_host); + return -EINVAL; + } + + header = smem->regions[0].virt_base + entry->offset; + + if (header->magic != SMEM_PART_MAGIC) { + dev_err(smem->dev, + "Partition %d has invalid magic\n", i); + return -EINVAL; + } + + if (header->host0 != local_host && header->host1 != local_host) { + dev_err(smem->dev, + "Partition %d hosts are invalid\n", i); + return -EINVAL; + } + + if (header->host0 != remote_host && header->host1 != remote_host) { + dev_err(smem->dev, + "Partition %d hosts are invalid\n", i); + return -EINVAL; + } + + if (header->size != entry->size) { + dev_err(smem->dev, + "Partition %d has invalid size\n", i); + return -EINVAL; + } + + if (header->offset_free_uncached > header->size) { + dev_err(smem->dev, + "Partition %d has invalid free pointer\n", i); + return -EINVAL; + } + + smem->partitions[remote_host] = header; + } + + return 0; +} + +static int qcom_smem_count_mem_regions(struct platform_device *pdev) +{ + struct resource *res; + int num_regions = 0; + int i; + + for (i = 0; i < pdev->num_resources; i++) { + res = &pdev->resource[i]; + + if (resource_type(res) == IORESOURCE_MEM) + num_regions++; + } + + return num_regions; +} + +static int qcom_smem_probe(struct platform_device *pdev) +{ + struct smem_header *header; + struct device_node *np; + struct qcom_smem *smem; + struct resource *res; + struct resource r; + size_t array_size; + int num_regions = 0; + int hwlock_id; + u32 version; + int ret; + int i; + + num_regions = qcom_smem_count_mem_regions(pdev) + 1; + + array_size = num_regions * sizeof(struct smem_region); + smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); + if (!smem) + return -ENOMEM; + + smem->dev = &pdev->dev; + smem->num_regions = num_regions; + + np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); + if (!np) { + dev_err(&pdev->dev, "No memory-region specified\n"); + return -EINVAL; + } + + ret = of_address_to_resource(np, 0, &r); + of_node_put(np); + if (ret) + return ret; + + smem->regions[0].aux_base = (u32)r.start; + smem->regions[0].size = resource_size(&r); + smem->regions[0].virt_base = devm_ioremap_nocache(&pdev->dev, + r.start, + resource_size(&r)); + if (!smem->regions[0].virt_base) + return -ENOMEM; + + for (i = 1; i < num_regions; i++) { + res = platform_get_resource(pdev, IORESOURCE_MEM, i - 1); + + smem->regions[i].aux_base = (u32)res->start; + smem->regions[i].size = resource_size(res); + smem->regions[i].virt_base = devm_ioremap_nocache(&pdev->dev, + res->start, + resource_size(res)); + if (!smem->regions[i].virt_base) + return -ENOMEM; + } + + header = smem->regions[0].virt_base; + if (header->initialized != 1 || header->reserved) { + dev_err(&pdev->dev, "SMEM is not initialized by SBL\n"); + return -EINVAL; + } + + version = qcom_smem_get_sbl_version(smem); + if (version >> 16 != SMEM_EXPECTED_VERSION) { + dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version); + return -EINVAL; + } + + ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); + if (ret < 0) + return ret; + + hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); + if (hwlock_id < 0) { + dev_err(&pdev->dev, "failed to retrieve hwlock\n"); + return hwlock_id; + } + + smem->hwlock = hwspin_lock_request_specific(hwlock_id); + if (!smem->hwlock) + return -ENXIO; + + __smem = smem; + + return 0; +} + +static int qcom_smem_remove(struct platform_device *pdev) +{ + __smem = NULL; + hwspin_lock_free(__smem->hwlock); + + return 0; +} + +static const struct of_device_id qcom_smem_of_match[] = { + { .compatible = "qcom,smem" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_smem_of_match); + +static struct platform_driver qcom_smem_driver = { + .probe = qcom_smem_probe, + .remove = qcom_smem_remove, + .driver = { + .name = "qcom-smem", + .of_match_table = qcom_smem_of_match, + .suppress_bind_attrs = true, + }, +}; + +static int __init qcom_smem_init(void) +{ + return platform_driver_register(&qcom_smem_driver); +} +arch_initcall(qcom_smem_init); + +static void __exit qcom_smem_exit(void) +{ + platform_driver_unregister(&qcom_smem_driver); +} +module_exit(qcom_smem_exit) + +MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); +MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig index 982580af1d16..0d3b70b3bda8 100644 --- a/drivers/spmi/Kconfig +++ b/drivers/spmi/Kconfig @@ -12,7 +12,7 @@ if SPMI config SPMI_MSM_PMIC_ARB tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)" - depends on IRQ_DOMAIN + select IRQ_DOMAIN depends on ARCH_QCOM || COMPILE_TEST depends on HAS_IOMEM default ARCH_QCOM diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 118938ee8552..8f47021d7505 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -365,4 +365,9 @@ config QCOM_SPMI_TEMP_ALARM real time die temperature if an ADC is present or an estimate of the temperature based upon the over temperature stage value. +menu "Qualcomm thermal drivers" +depends on ARCH_QCOM && OF +source "drivers/thermal/qcom/Kconfig" +endmenu + endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 535dfee1496f..011fa57a88f3 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -42,5 +42,6 @@ obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL) += intel_quark_dts_thermal.o obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ obj-$(CONFIG_ST_THERMAL) += st/ +obj-$(CONFIG_QCOM_TSENS) += qcom/ obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig new file mode 100644 index 000000000000..9bfde94ed23f --- /dev/null +++ b/drivers/thermal/qcom/Kconfig @@ -0,0 +1,10 @@ +config QCOM_TSENS + tristate "Qualcomm TSENS Temperature Alarm" + depends on THERMAL + depends on QCOM_QFPROM + help + This enables the thermal sysfs driver for the Tsens device. It shows + up in Sysfs as a thermal zone with multiple trip points. Disabling the + thermal zone device via the mode file results in disabling the sensor. + Also able to set threshold temperature for both hot and cold and update + when a threshold is reached. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile new file mode 100644 index 000000000000..f3cefd19e898 --- /dev/null +++ b/drivers/thermal/qcom/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o +qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o diff --git a/drivers/thermal/qcom/tsens-8916.c b/drivers/thermal/qcom/tsens-8916.c new file mode 100644 index 000000000000..a69aea35bf81 --- /dev/null +++ b/drivers/thermal/qcom/tsens-8916.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015, 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/platform_device.h> +#include "tsens.h" + +/* eeprom layout data for 8916 */ +#define BASE0_MASK 0x0000007f +#define BASE1_MASK 0xfe000000 +#define BASE0_SHIFT 0 +#define BASE1_SHIFT 25 + +#define S0_P1_MASK 0x00000f80 +#define S1_P1_MASK 0x003e0000 +#define S2_P1_MASK 0xf8000000 +#define S3_P1_MASK 0x000003e0 +#define S4_P1_MASK 0x000f8000 + +#define S0_P2_MASK 0x0001f000 +#define S1_P2_MASK 0x07c00000 +#define S2_P2_MASK 0x0000001f +#define S3_P2_MASK 0x00007c00 +#define S4_P2_MASK 0x01f00000 + +#define S0_P1_SHIFT 7 +#define S1_P1_SHIFT 17 +#define S2_P1_SHIFT 27 +#define S3_P1_SHIFT 5 +#define S4_P1_SHIFT 15 + +#define S0_P2_SHIFT 12 +#define S1_P2_SHIFT 22 +#define S2_P2_SHIFT 0 +#define S3_P2_SHIFT 10 +#define S4_P2_SHIFT 20 + +#define CAL_SEL_MASK 0xe0000000 +#define CAL_SEL_SHIFT 29 + +static int calibrate_8916(struct tsens_device *tmdev) +{ + int base0 = 0, base1 = 0, i; + u32 p1[5], p2[5]; + int mode = 0; + u32 *qfprom_cdata, *qfprom_csel; + + qfprom_cdata = (u32 *)qfprom_read(tmdev->dev, "calib"); + if (IS_ERR(qfprom_cdata)) + return PTR_ERR(qfprom_cdata); + + qfprom_csel = (u32 *)qfprom_read(tmdev->dev, "calib_sel"); + if (IS_ERR(qfprom_csel)) + return PTR_ERR(qfprom_csel); + + mode = (qfprom_csel[0] & CAL_SEL_MASK) >> CAL_SEL_SHIFT; + dev_dbg(tmdev->dev, "calibration mode is %d\n", mode); + + switch (mode) { + case TWO_PT_CALIB: + base1 = (qfprom_cdata[1] & BASE1_MASK) >> BASE1_SHIFT; + p2[0] = (qfprom_cdata[0] & S0_P2_MASK) >> S0_P2_SHIFT; + p2[1] = (qfprom_cdata[0] & S1_P2_MASK) >> S1_P2_SHIFT; + p2[2] = (qfprom_cdata[1] & S2_P2_MASK) >> S2_P2_SHIFT; + p2[3] = (qfprom_cdata[1] & S3_P2_MASK) >> S3_P2_SHIFT; + p2[4] = (qfprom_cdata[1] & S4_P2_MASK) >> S4_P2_SHIFT; + for (i = 0; i < tmdev->num_sensors; i++) + p2[i] = ((base1 + p2[i]) << 3); + /* Fall through */ + case ONE_PT_CALIB2: + base0 = (qfprom_cdata[0] & BASE0_MASK); + p1[0] = (qfprom_cdata[0] & S0_P1_MASK) >> S0_P1_SHIFT; + p1[1] = (qfprom_cdata[0] & S1_P1_MASK) >> S1_P1_SHIFT; + p1[2] = (qfprom_cdata[0] & S2_P1_MASK) >> S2_P1_SHIFT; + p1[3] = (qfprom_cdata[1] & S3_P1_MASK) >> S3_P1_SHIFT; + p1[4] = (qfprom_cdata[1] & S4_P1_MASK) >> S4_P1_SHIFT; + for (i = 0; i < tmdev->num_sensors; i++) + p1[i] = (((base0) + p1[i]) << 3); + break; + default: + for (i = 0; i < tmdev->num_sensors; i++) { + p1[i] = 500; + p2[i] = 780; + } + break; + } + + compute_intercept_slope(tmdev, p1, p2, mode); + + return 0; +} + +const struct tsens_ops ops_8916 = { + .init = init_common, + .calibrate = calibrate_8916, + .get_temp = get_temp_common, +}; diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c new file mode 100644 index 000000000000..1b98e2220188 --- /dev/null +++ b/drivers/thermal/qcom/tsens-8960.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2015, 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/platform_device.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/regmap.h> +#include "tsens.h" + +#define CAL_MDEGC 30000 + +#define CONFIG_ADDR 0x3640 +#define CONFIG_ADDR_8660 0x3620 +/* CONFIG_ADDR bitmasks */ +#define CONFIG 0x9b +#define CONFIG_MASK 0xf +#define CONFIG_8660 1 +#define CONFIG_SHIFT_8660 28 +#define CONFIG_MASK_8660 (3 << CONFIG_SHIFT_8660) + +#define STATUS_CNTL_ADDR_8064 0x3660 +#define CNTL_ADDR 0x3620 +/* CNTL_ADDR bitmasks */ +#define EN BIT(0) +#define SW_RST BIT(1) +#define SENSOR0_EN BIT(3) +#define SLP_CLK_ENA BIT(26) +#define SLP_CLK_ENA_8660 BIT(24) +#define MEASURE_PERIOD 1 +#define SENSOR0_SHIFT 3 + +/* INT_STATUS_ADDR bitmasks */ +#define MIN_STATUS_MASK BIT(0) +#define LOWER_STATUS_CLR BIT(1) +#define UPPER_STATUS_CLR BIT(2) +#define MAX_STATUS_MASK BIT(3) + +#define THRESHOLD_ADDR 0x3624 +/* THRESHOLD_ADDR bitmasks */ +#define THRESHOLD_MAX_LIMIT_SHIFT 24 +#define THRESHOLD_MIN_LIMIT_SHIFT 16 +#define THRESHOLD_UPPER_LIMIT_SHIFT 8 +#define THRESHOLD_LOWER_LIMIT_SHIFT 0 + +/* Initial temperature threshold values */ +#define LOWER_LIMIT_TH 0x50 +#define UPPER_LIMIT_TH 0xdf +#define MIN_LIMIT_TH 0x0 +#define MAX_LIMIT_TH 0xff + +#define S0_STATUS_ADDR 0x3628 +#define INT_STATUS_ADDR 0x363c +#define TRDY_MASK BIT(7) + +static int suspend_8960(struct tsens_device *tmdev) +{ + int ret; + unsigned int mask; + struct regmap *map = tmdev->map; + + ret = regmap_read(map, THRESHOLD_ADDR, &tmdev->ctx.threshold); + if (ret) + return ret; + + ret = regmap_read(map, CNTL_ADDR, &tmdev->ctx.control); + if (ret) + return ret; + + if (tmdev->num_sensors > 1) + mask = SLP_CLK_ENA | EN; + else + mask = SLP_CLK_ENA_8660 | EN; + + ret = regmap_update_bits(map, CNTL_ADDR, mask, 0); + if (ret) + return ret; + + tmdev->trdy = false; + + return 0; +} + +static int resume_8960(struct tsens_device *tmdev) +{ + int ret; + unsigned long reg_cntl; + struct regmap *map = tmdev->map; + + ret = regmap_update_bits(map, CNTL_ADDR, SW_RST, SW_RST); + if (ret) + return ret; + + /* + * Separate CONFIG restore is not needed only for 8660 as + * config is part of CTRL Addr and its restored as such + */ + if (tmdev->num_sensors > 1) { + ret = regmap_update_bits(map, CONFIG_ADDR, CONFIG_MASK, CONFIG); + if (ret) + return ret; + } + + ret = regmap_write(map, THRESHOLD_ADDR, tmdev->ctx.threshold); + if (ret) + return ret; + + ret = regmap_write(map, CNTL_ADDR, tmdev->ctx.control); + if (ret) + return ret; + + reg_cntl = tmdev->ctx.control; + reg_cntl >>= SENSOR0_SHIFT; + + return 0; +} + +static int enable_8960(struct tsens_device *tmdev, int id) +{ + int ret; + u32 reg, mask; + + ret = regmap_read(tmdev->map, CNTL_ADDR, ®); + if (ret) + return ret; + + mask = BIT(id + SENSOR0_SHIFT); + ret = regmap_write(tmdev->map, CNTL_ADDR, reg | SW_RST); + if (ret) + return ret; + + if (tmdev->num_sensors > 1) + reg |= mask | SLP_CLK_ENA | EN; + else + reg |= mask | SLP_CLK_ENA_8660 | EN; + + tmdev->trdy = false; + ret = regmap_write(tmdev->map, CNTL_ADDR, reg); + if (ret) + return ret; + + return 0; +} + +static void disable_8960(struct tsens_device *tmdev) +{ + int ret; + u32 reg_cntl; + u32 mask; + + mask = GENMASK(tmdev->num_sensors - 1, 0); + mask <<= SENSOR0_SHIFT; + mask |= EN; + + ret = regmap_read(tmdev->map, CNTL_ADDR, ®_cntl); + if (ret) + return; + + reg_cntl &= ~mask; + + if (tmdev->num_sensors > 1) + reg_cntl &= ~SLP_CLK_ENA; + else + reg_cntl &= ~SLP_CLK_ENA_8660; + + regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); +} + +static int init_8960(struct tsens_device *tmdev) +{ + int ret, i; + u32 reg_cntl; + + tmdev->map = dev_get_regmap(tmdev->dev->parent, NULL); + if (!tmdev->map) + return -ENODEV; + + /* + * The status registers for each sensor are discontiguous + * because some SoCs have 5 sensors while others have more + * but the control registers stay in the same place, i.e + * directly after the first 5 status registers. + */ + for (i = 0; i < tmdev->num_sensors; i++) { + if (i >= 5) + tmdev->sensor[i].status = S0_STATUS_ADDR + 40; + tmdev->sensor[i].status += i * 4; + } + + reg_cntl = SW_RST; + ret = regmap_update_bits(tmdev->map, CNTL_ADDR, SW_RST, reg_cntl); + if (ret) + return ret; + + if (tmdev->num_sensors > 1) { + reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18); + reg_cntl &= ~SW_RST; + ret = regmap_update_bits(tmdev->map, CONFIG_ADDR, + CONFIG_MASK, CONFIG); + } else { + reg_cntl |= SLP_CLK_ENA_8660 | (MEASURE_PERIOD << 16); + reg_cntl &= ~CONFIG_MASK_8660; + reg_cntl |= CONFIG_8660 << CONFIG_SHIFT_8660; + } + + reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << SENSOR0_SHIFT; + ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); + if (ret) + return ret; + + reg_cntl |= EN; + ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); + if (ret) + return ret; + + return 0; +} + +static int calibrate_8960(struct tsens_device *tmdev) +{ + int i; + char *data; + + ssize_t num_read = tmdev->num_sensors; + struct tsens_sensor *s = tmdev->sensor; + + data = qfprom_read(tmdev->dev, "calib"); + if (IS_ERR(data)) + data = qfprom_read(tmdev->dev, "calib_backup"); + if (IS_ERR(data)) + return PTR_ERR(data); + + for (i = 0; i < num_read; i++, s++) + s->offset = CAL_MDEGC - s->slope * data[i]; + + return 0; +} + +/* Temperature on y axis and ADC-code on x-axis */ +static inline int code_to_mdegC(u32 adc_code, const struct tsens_sensor *s) +{ + return adc_code * s->slope + s->offset; +} + +static int get_temp_8960(struct tsens_device *tmdev, int id, long *temp) +{ + int ret; + u32 code, trdy; + const struct tsens_sensor *s = &tmdev->sensor[id]; + + if (!tmdev->trdy) { + ret = regmap_read(tmdev->map, INT_STATUS_ADDR, &trdy); + if (ret) + return ret; + while (!(trdy & TRDY_MASK)) { + usleep_range(1000, 1100); + regmap_read(tmdev->map, INT_STATUS_ADDR, &trdy); + } + tmdev->trdy = true; + } + + ret = regmap_read(tmdev->map, s->status, &code); + if (ret) + return ret; + + *temp = code_to_mdegC(code, s); + + dev_dbg(tmdev->dev, "Sensor%d temp is: %ld", id, *temp); + + return 0; +} + +const struct tsens_ops ops_8960 = { + .init = init_8960, + .calibrate = calibrate_8960, + .get_temp = get_temp_8960, + .enable = enable_8960, + .disable = disable_8960, + .suspend = suspend_8960, + .resume = resume_8960, +}; diff --git a/drivers/thermal/qcom/tsens-8974.c b/drivers/thermal/qcom/tsens-8974.c new file mode 100644 index 000000000000..19d9258a439f --- /dev/null +++ b/drivers/thermal/qcom/tsens-8974.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2015, 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/platform_device.h> +#include "tsens.h" + +/* eeprom layout data for 8974 */ +#define BASE1_MASK 0xff +#define S0_P1_MASK 0x3f00 +#define S1_P1_MASK 0xfc000 +#define S2_P1_MASK 0x3f00000 +#define S3_P1_MASK 0xfc000000 +#define S4_P1_MASK 0x3f +#define S5_P1_MASK 0xfc0 +#define S6_P1_MASK 0x3f000 +#define S7_P1_MASK 0xfc0000 +#define S8_P1_MASK 0x3f000000 +#define S8_P1_MASK_BKP 0x3f +#define S9_P1_MASK 0x3f +#define S9_P1_MASK_BKP 0xfc0 +#define S10_P1_MASK 0xfc0 +#define S10_P1_MASK_BKP 0x3f000 +#define CAL_SEL_0_1 0xc0000000 +#define CAL_SEL_2 0x40000000 +#define CAL_SEL_SHIFT 30 +#define CAL_SEL_SHIFT_2 28 + +#define S0_P1_SHIFT 8 +#define S1_P1_SHIFT 14 +#define S2_P1_SHIFT 20 +#define S3_P1_SHIFT 26 +#define S5_P1_SHIFT 6 +#define S6_P1_SHIFT 12 +#define S7_P1_SHIFT 18 +#define S8_P1_SHIFT 24 +#define S9_P1_BKP_SHIFT 6 +#define S10_P1_SHIFT 6 +#define S10_P1_BKP_SHIFT 12 + +#define BASE2_SHIFT 12 +#define BASE2_BKP_SHIFT 18 +#define S0_P2_SHIFT 20 +#define S0_P2_BKP_SHIFT 26 +#define S1_P2_SHIFT 26 +#define S2_P2_BKP_SHIFT 6 +#define S3_P2_SHIFT 6 +#define S3_P2_BKP_SHIFT 12 +#define S4_P2_SHIFT 12 +#define S4_P2_BKP_SHIFT 18 +#define S5_P2_SHIFT 18 +#define S5_P2_BKP_SHIFT 24 +#define S6_P2_SHIFT 24 +#define S7_P2_BKP_SHIFT 6 +#define S8_P2_SHIFT 6 +#define S8_P2_BKP_SHIFT 12 +#define S9_P2_SHIFT 12 +#define S9_P2_BKP_SHIFT 18 +#define S10_P2_SHIFT 18 +#define S10_P2_BKP_SHIFT 24 + +#define BASE2_MASK 0xff000 +#define BASE2_BKP_MASK 0xfc0000 +#define S0_P2_MASK 0x3f00000 +#define S0_P2_BKP_MASK 0xfc000000 +#define S1_P2_MASK 0xfc000000 +#define S1_P2_BKP_MASK 0x3f +#define S2_P2_MASK 0x3f +#define S2_P2_BKP_MASK 0xfc0 +#define S3_P2_MASK 0xfc0 +#define S3_P2_BKP_MASK 0x3f000 +#define S4_P2_MASK 0x3f000 +#define S4_P2_BKP_MASK 0xfc0000 +#define S5_P2_MASK 0xfc0000 +#define S5_P2_BKP_MASK 0x3f000000 +#define S6_P2_MASK 0x3f000000 +#define S6_P2_BKP_MASK 0x3f +#define S7_P2_MASK 0x3f +#define S7_P2_BKP_MASK 0xfc0 +#define S8_P2_MASK 0xfc0 +#define S8_P2_BKP_MASK 0x3f000 +#define S9_P2_MASK 0x3f000 +#define S9_P2_BKP_MASK 0xfc0000 +#define S10_P2_MASK 0xfc0000 +#define S10_P2_BKP_MASK 0x3f000000 + +#define BKP_SEL 0x3 +#define BKP_REDUN_SEL 0xe0000000 +#define BKP_REDUN_SHIFT 29 + +#define BIT_APPEND 0x3 + +static int calibrate_8974(struct tsens_device *tmdev) +{ + int base1 = 0, base2 = 0, i; + u32 p1[11], p2[11]; + int mode = 0; + u32 *calib, *bkp; + u32 calib_redun_sel; + + calib = (u32 *)qfprom_read(tmdev->dev, "calib"); + if (IS_ERR(calib)) + return PTR_ERR(calib); + + bkp = (u32 *)qfprom_read(tmdev->dev, "calib_backup"); + if (IS_ERR(bkp)) + return PTR_ERR(bkp); + + calib_redun_sel = bkp[1] & BKP_REDUN_SEL; + calib_redun_sel >>= BKP_REDUN_SHIFT; + + if (calib_redun_sel == BKP_SEL) { + mode = (calib[4] & CAL_SEL_0_1) >> CAL_SEL_SHIFT; + mode |= (calib[5] & CAL_SEL_2) >> CAL_SEL_SHIFT_2; + + switch (mode) { + case TWO_PT_CALIB: + base2 = (bkp[2] & BASE2_BKP_MASK) >> BASE2_BKP_SHIFT; + p2[0] = (bkp[2] & S0_P2_BKP_MASK) >> S0_P2_BKP_SHIFT; + p2[1] = (bkp[3] & S1_P2_BKP_MASK); + p2[2] = (bkp[3] & S2_P2_BKP_MASK) >> S2_P2_BKP_SHIFT; + p2[3] = (bkp[3] & S3_P2_BKP_MASK) >> S3_P2_BKP_SHIFT; + p2[4] = (bkp[3] & S4_P2_BKP_MASK) >> S4_P2_BKP_SHIFT; + p2[5] = (calib[4] & S5_P2_BKP_MASK) >> S5_P2_BKP_SHIFT; + p2[6] = (calib[5] & S6_P2_BKP_MASK); + p2[7] = (calib[5] & S7_P2_BKP_MASK) >> S7_P2_BKP_SHIFT; + p2[8] = (calib[5] & S8_P2_BKP_MASK) >> S8_P2_BKP_SHIFT; + p2[9] = (calib[5] & S9_P2_BKP_MASK) >> S9_P2_BKP_SHIFT; + p2[10] = (calib[5] & S10_P2_BKP_MASK) >> S10_P2_BKP_SHIFT; + /* Fall through */ + case ONE_PT_CALIB: + case ONE_PT_CALIB2: + base1 = bkp[0] & BASE1_MASK; + p1[0] = (bkp[0] & S0_P1_MASK) >> S0_P1_SHIFT; + p1[1] = (bkp[0] & S1_P1_MASK) >> S1_P1_SHIFT; + p1[2] = (bkp[0] & S2_P1_MASK) >> S2_P1_SHIFT; + p1[3] = (bkp[0] & S3_P1_MASK) >> S3_P1_SHIFT; + p1[4] = (bkp[1] & S4_P1_MASK); + p1[5] = (bkp[1] & S5_P1_MASK) >> S5_P1_SHIFT; + p1[6] = (bkp[1] & S6_P1_MASK) >> S6_P1_SHIFT; + p1[7] = (bkp[1] & S7_P1_MASK) >> S7_P1_SHIFT; + p1[8] = (bkp[2] & S8_P1_MASK_BKP) >> S8_P1_SHIFT; + p1[9] = (bkp[2] & S9_P1_MASK_BKP) >> S9_P1_BKP_SHIFT; + p1[10] = (bkp[2] & S10_P1_MASK_BKP) >> S10_P1_BKP_SHIFT; + break; + } + } else { + mode = (calib[1] & CAL_SEL_0_1) >> CAL_SEL_SHIFT; + mode |= (calib[3] & CAL_SEL_2) >> CAL_SEL_SHIFT_2; + + switch (mode) { + case TWO_PT_CALIB: + base2 = (calib[2] & BASE2_MASK) >> BASE2_SHIFT; + p2[0] = (calib[2] & S0_P2_MASK) >> S0_P2_SHIFT; + p2[1] = (calib[2] & S1_P2_MASK) >> S1_P2_SHIFT; + p2[2] = (calib[3] & S2_P2_MASK); + p2[3] = (calib[3] & S3_P2_MASK) >> S3_P2_SHIFT; + p2[4] = (calib[3] & S4_P2_MASK) >> S4_P2_SHIFT; + p2[5] = (calib[3] & S5_P2_MASK) >> S5_P2_SHIFT; + p2[6] = (calib[3] & S6_P2_MASK) >> S6_P2_SHIFT; + p2[7] = (calib[4] & S7_P2_MASK); + p2[8] = (calib[4] & S8_P2_MASK) >> S8_P2_SHIFT; + p2[9] = (calib[4] & S9_P2_MASK) >> S9_P2_SHIFT; + p2[10] = (calib[4] & S10_P2_MASK) >> S10_P2_SHIFT; + /* Fall through */ + case ONE_PT_CALIB: + case ONE_PT_CALIB2: + base1 = calib[0] & BASE1_MASK; + p1[0] = (calib[0] & S0_P1_MASK) >> S0_P1_SHIFT; + p1[1] = (calib[0] & S1_P1_MASK) >> S1_P1_SHIFT; + p1[2] = (calib[0] & S2_P1_MASK) >> S2_P1_SHIFT; + p1[3] = (calib[0] & S3_P1_MASK) >> S3_P1_SHIFT; + p1[4] = (calib[1] & S4_P1_MASK); + p1[5] = (calib[1] & S5_P1_MASK) >> S5_P1_SHIFT; + p1[6] = (calib[1] & S6_P1_MASK) >> S6_P1_SHIFT; + p1[7] = (calib[1] & S7_P1_MASK) >> S7_P1_SHIFT; + p1[8] = (calib[1] & S8_P1_MASK) >> S8_P1_SHIFT; + p1[9] = (calib[2] & S9_P1_MASK); + p1[10] = (calib[2] & S10_P1_MASK) >> S10_P1_SHIFT; + break; + } + } + + switch (mode) { + case ONE_PT_CALIB: + for (i = 0; i < tmdev->num_sensors; i++) + p1[i] += (base1 << 2) | BIT_APPEND; + break; + case TWO_PT_CALIB: + for (i = 0; i < tmdev->num_sensors; i++) { + p2[i] += base2; + p2[i] <<= 2; + p2[i] |= BIT_APPEND; + } + /* Fall through */ + case ONE_PT_CALIB2: + for (i = 0; i < tmdev->num_sensors; i++) { + p1[i] += base1; + p1[i] <<= 2; + p1[i] |= BIT_APPEND; + } + break; + default: + for (i = 0; i < tmdev->num_sensors; i++) + p2[i] = 780; + p1[0] = 502; + p1[1] = 509; + p1[2] = 503; + p1[3] = 509; + p1[4] = 505; + p1[5] = 509; + p1[6] = 507; + p1[7] = 510; + p1[8] = 508; + p1[9] = 509; + p1[10] = 508; + break; + } + + compute_intercept_slope(tmdev, p1, p2, mode); + + return 0; +} + +const struct tsens_ops ops_8974 = { + .init = init_common, + .calibrate = calibrate_8974, + .get_temp = get_temp_common, +}; diff --git a/drivers/thermal/qcom/tsens-common.c b/drivers/thermal/qcom/tsens-common.c new file mode 100644 index 000000000000..5fcabd19f62e --- /dev/null +++ b/drivers/thermal/qcom/tsens-common.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2015, 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/platform_device.h> +#include <linux/nvmem-consumer.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include "tsens.h" + +#define S0_ST_ADDR 0x1030 +#define SN_ADDR_OFFSET 0x4 +#define SN_ST_TEMP_MASK 0x3ff +#define CAL_DEGC_PT1 30 +#define CAL_DEGC_PT2 120 +#define SLOPE_FACTOR 1000 + +char *qfprom_read(struct device *dev, const char *cname) +{ + struct nvmem_cell *cell; + ssize_t data; + char *ret; + + cell = nvmem_cell_get(dev, cname); + if (IS_ERR(cell)) + return ERR_CAST(cell); + + ret = nvmem_cell_read(cell, &data); + nvmem_cell_put(cell); + return ret; +} + +void compute_intercept_slope(struct tsens_device *tmdev, u32 *p1, + u32 *p2, u32 mode) +{ + int i; + int num, den; + + for (i = 0; i < tmdev->num_sensors; i++) { + dev_dbg(tmdev->dev, + "sensor%d - data_point1:%#x data_point2:%#x\n", + i, p1[i], p2[i]); + + if (mode == TWO_PT_CALIB) { + /* slope (m) = adc_code2 - adc_code1 (y2 - y1)/ + temp_120_degc - temp_30_degc (x2 - x1) */ + num = p2[i] - p1[i]; + num *= SLOPE_FACTOR; + den = CAL_DEGC_PT2 - CAL_DEGC_PT1; + tmdev->sensor[i].slope = num / den; + } + + tmdev->sensor[i].offset = (p1[i] * SLOPE_FACTOR) - + (CAL_DEGC_PT1 * + tmdev->sensor[i].slope); + dev_dbg(tmdev->dev, "offset:%d\n", tmdev->sensor[i].offset); + } +} + +static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) +{ + int degc, num, den; + + num = (adc_code * SLOPE_FACTOR) - s->offset; + den = s->slope; + + if (num > 0) + degc = num + (den / 2); + else if (num < 0) + degc = num - (den / 2); + else + degc = num; + + degc /= den; + + return degc; +} + +int get_temp_common(struct tsens_device *tmdev, int id, long *temp) +{ + struct tsens_sensor *s = &tmdev->sensor[id]; + u32 code; + unsigned int sensor_addr; + int last_temp = 0, ret; + + sensor_addr = S0_ST_ADDR + s->hw_id * SN_ADDR_OFFSET; + ret = regmap_read(tmdev->map, sensor_addr, &code); + if (ret) + return ret; + last_temp = code & SN_ST_TEMP_MASK; + + *temp = code_to_degc(last_temp, s) * 1000; + + return 0; +} + +static const struct regmap_config tsens_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +int init_common(struct tsens_device *tmdev) +{ + void __iomem *base; + + base = of_iomap(tmdev->dev->of_node, 0); + if (IS_ERR(base)) + return -EINVAL; + + tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config); + if (!tmdev->map) + return -ENODEV; + + return 0; +} diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c new file mode 100644 index 000000000000..8d938d405ed5 --- /dev/null +++ b/drivers/thermal/qcom/tsens.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2015, 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/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/thermal.h> +#include "tsens.h" + +static int tsens_get_temp(void *data, long *temp) +{ + const struct tsens_sensor *s = data; + struct tsens_device *tmdev = s->tmdev; + + return tmdev->ops->get_temp(tmdev, s->id, temp); +} + +static int tsens_get_trend(void *data, long *temp) +{ + const struct tsens_sensor *s = data; + struct tsens_device *tmdev = s->tmdev; + + if (tmdev->ops->get_trend) + return tmdev->ops->get_trend(tmdev, s->id, temp); + + return -ENOSYS; +} + +#ifdef CONFIG_PM +static int tsens_suspend(struct device *dev) +{ + struct tsens_device *tmdev = dev_get_drvdata(dev); + + if (tmdev->ops && tmdev->ops->suspend) + return tmdev->ops->suspend(tmdev); + + return 0; +} + +static int tsens_resume(struct device *dev) +{ + struct tsens_device *tmdev = dev_get_drvdata(dev); + + if (tmdev->ops && tmdev->ops->resume) + return tmdev->ops->resume(tmdev); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); +#define TSENS_PM_OPS (&tsens_pm_ops) + +#else /* CONFIG_PM_SLEEP */ +#define TSENS_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static const struct of_device_id tsens_table[] = { + { + .compatible = "qcom,msm8960-tsens", + .data = &ops_8960, + }, { + .compatible = "qcom,msm8916-tsens", + .data = &ops_8916, + }, { + .compatible = "qcom,msm8974-tsens", + .data = &ops_8974, + }, + {} +}; +MODULE_DEVICE_TABLE(of, tsens_table); + +static const struct thermal_zone_of_device_ops tsens_of_ops = { + .get_temp = tsens_get_temp, + .get_trend = tsens_get_trend, +}; + +static int tsens_register(struct tsens_device *tmdev) +{ + int i, ret; + struct thermal_zone_device *tzd; + u32 *hw_id, n = tmdev->num_sensors; + struct device_node *np = tmdev->dev->of_node; + + hw_id = devm_kcalloc(tmdev->dev, n, sizeof(u32), GFP_KERNEL); + if (!hw_id) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "qcom,sensor-id", hw_id, n); + if (ret) + for (i = 0; i < tmdev->num_sensors; i++) + tmdev->sensor[i].hw_id = i; + else + for (i = 0; i < tmdev->num_sensors; i++) + tmdev->sensor[i].hw_id = hw_id[i]; + + for (i = 0; i < tmdev->num_sensors; i++) { + tmdev->sensor[i].tmdev = tmdev; + tmdev->sensor[i].id = i; + tzd = thermal_zone_of_sensor_register(tmdev->dev, i, + &tmdev->sensor[i], + &tsens_of_ops); + if (IS_ERR(tzd)) + continue; + tmdev->sensor[i].tzd = tzd; + if (tmdev->ops->enable) + tmdev->ops->enable(tmdev, i); + } + return 0; +} + +static int tsens_probe(struct platform_device *pdev) +{ + int ret, i, num; + struct device_node *np = pdev->dev.of_node; + struct tsens_sensor *s; + struct tsens_device *tmdev; + const struct of_device_id *id; + + num = of_property_count_u32_elems(np, "qcom,tsens-slopes"); + if (num <= 0) { + dev_err(&pdev->dev, "invalid tsens slopes\n"); + return -EINVAL; + } + + tmdev = devm_kzalloc(&pdev->dev, sizeof(*tmdev) + + num * sizeof(*s), GFP_KERNEL); + if (!tmdev) + return -ENOMEM; + + tmdev->dev = &pdev->dev; + tmdev->num_sensors = num; + + for (i = 0, s = tmdev->sensor; i < tmdev->num_sensors; i++, s++) + of_property_read_u32_index(np, "qcom,tsens-slopes", i, + &s->slope); + + id = of_match_node(tsens_table, np); + if (!id) + return -ENODEV; + + tmdev->ops = id->data; + if (!tmdev->ops || !tmdev->ops->init || !tmdev->ops->calibrate || + !tmdev->ops->get_temp) + return -EINVAL; + + ret = tmdev->ops->init(tmdev); + if (ret < 0) { + dev_err(&pdev->dev, "tsens init failed\n"); + return ret; + } + + ret = tmdev->ops->calibrate(tmdev); + if (ret < 0) { + dev_err(&pdev->dev, "tsens calibration failed\n"); + return ret; + } + + ret = tsens_register(tmdev); + + platform_set_drvdata(pdev, tmdev); + + return ret; +} + +static int tsens_remove(struct platform_device *pdev) +{ + int i; + struct tsens_device *tmdev = platform_get_drvdata(pdev); + struct thermal_zone_device *tzd; + + if (tmdev->ops->disable) + tmdev->ops->disable(tmdev); + + for (i = 0; i < tmdev->num_sensors; i++) { + tzd = tmdev->sensor[i].tzd; + thermal_zone_of_sensor_unregister(&pdev->dev, tzd); + } + + return 0; +} + +static struct platform_driver tsens_driver = { + .probe = tsens_probe, + .remove = tsens_remove, + .driver = { + .name = "qcom-tsens", + .pm = TSENS_PM_OPS, + .of_match_table = tsens_table, + }, +}; +module_platform_driver(tsens_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("QCOM Temperature Sensor driver"); +MODULE_ALIAS("platform:qcom-tsens"); diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h new file mode 100644 index 000000000000..f6598065dbcc --- /dev/null +++ b/drivers/thermal/qcom/tsens.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015, 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_TSENS_H__ +#define __QCOM_TSENS_H__ + +#define ONE_PT_CALIB 0x1 +#define ONE_PT_CALIB2 0x2 +#define TWO_PT_CALIB 0x3 + +struct tsens_device; + +struct tsens_sensor { + struct tsens_device *tmdev; + struct thermal_zone_device *tzd; + int offset; + int id; + int hw_id; + u32 slope; + u32 status; +}; + +struct tsens_ops { + /* mandatory callbacks */ + int (*init)(struct tsens_device *); + int (*calibrate)(struct tsens_device *); + int (*get_temp)(struct tsens_device *, int, long *); + /* optional callbacks */ + int (*enable)(struct tsens_device *, int); + void (*disable)(struct tsens_device *); + int (*suspend)(struct tsens_device *); + int (*resume)(struct tsens_device *); + int (*get_trend)(struct tsens_device *, int, long *); +}; + +/* Registers to be saved/restored across a context loss */ +struct tsens_context { + int threshold; + int control; +}; + +struct tsens_device { + struct device *dev; + u32 num_sensors; + struct regmap *map; + struct regmap_field *status_field; + struct tsens_context ctx; + bool trdy; + const struct tsens_ops *ops; + struct tsens_sensor sensor[0]; +}; + +char *qfprom_read(struct device *, const char *); +void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32); +int init_common(struct tsens_device *); +int get_temp_common(struct tsens_device *, int, long *); + +extern const struct tsens_ops ops_8960, ops_8916, ops_8974; + +#endif /* __QCOM_TSENS_H__ */ diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 5ce3f1d6a6ed..5619b8ca3bf3 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -1,6 +1,7 @@ config USB_CHIPIDEA tristate "ChipIdea Highspeed Dual Role Controller" depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA + select EXTCON help Say Y here if your system has a dual role high speed USB controller based on ChipIdea silicon IP. Currently, only the diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 74fea4fa41b1..2ae2c0902eab 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -47,6 +47,7 @@ #include <linux/delay.h> #include <linux/device.h> #include <linux/dma-mapping.h> +#include <linux/extcon.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> #include <linux/module.h> @@ -557,9 +558,47 @@ static irqreturn_t ci_irq(int irq, void *data) return ret; } +static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = vbus->ci; + + if (event) + vbus->state = true; + else + vbus->state = false; + + vbus->changed = true; + + ci_irq(ci->irq, ci); + return NOTIFY_DONE; +} + +static int ci_id_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc_cable *id = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = id->ci; + + if (event) + id->state = false; + else + id->state = true; + + id->changed = true; + + ci_irq(ci->irq, ci); + return NOTIFY_DONE; +} + static int ci_get_platdata(struct device *dev, struct ci_hdrc_platform_data *platdata) { + struct extcon_dev *ext_vbus, *ext_id; + struct ci_hdrc_cable *cable; + int ret; + if (!platdata->phy_mode) platdata->phy_mode = of_usb_get_phy_mode(dev->of_node); @@ -591,9 +630,89 @@ static int ci_get_platdata(struct device *dev, if (of_usb_get_maximum_speed(dev->of_node) == USB_SPEED_FULL) platdata->flags |= CI_HDRC_FORCE_FULLSPEED; + ext_id = ERR_PTR(-ENODEV); + ext_vbus = ERR_PTR(-ENODEV); + if (of_property_read_bool(dev->of_node, "extcon")) { + /* Each one of them is not mandatory */ + ext_vbus = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV) + return PTR_ERR(ext_vbus); + + ext_id = extcon_get_edev_by_phandle(dev, 1); + if (IS_ERR(ext_id) && PTR_ERR(ext_id) != -ENODEV) + return PTR_ERR(ext_id); + } + + cable = &platdata->vbus_extcon; + cable->nb.notifier_call = ci_vbus_notifier; + cable->edev = ext_vbus; + + if (!IS_ERR(ext_vbus)) { + ret = extcon_get_cable_state(cable->edev, "USB"); + if (ret) + cable->state = true; + else + cable->state = false; + } + + cable = &platdata->id_extcon; + cable->nb.notifier_call = ci_id_notifier; + cable->edev = ext_id; + + if (!IS_ERR(ext_id)) { + ret = extcon_get_cable_state(cable->edev, "USB-HOST"); + if (ret) + cable->state = false; + else + cable->state = true; + } + return 0; +} + +static int ci_extcon_register(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *id, *vbus; + int ret; + + id = &ci->platdata->id_extcon; + id->ci = ci; + if (!IS_ERR(id->edev)) { + ret = extcon_register_interest(&id->conn, id->edev->name, + "USB-HOST", &id->nb); + if (ret < 0) { + dev_err(ci->dev, "register ID failed\n"); + return ret; + } + } + + vbus = &ci->platdata->vbus_extcon; + vbus->ci = ci; + if (!IS_ERR(vbus->edev)) { + ret = extcon_register_interest(&vbus->conn, vbus->edev->name, + "USB", &vbus->nb); + if (ret < 0) { + extcon_unregister_interest(&id->conn); + dev_err(ci->dev, "register VBUS failed\n"); + return ret; + } + } + return 0; } +static void ci_extcon_unregister(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *cable; + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev)) + extcon_unregister_interest(&cable->conn); + + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev)) + extcon_unregister_interest(&cable->conn); +} + static DEFINE_IDA(ci_ida); struct platform_device *ci_hdrc_add_device(struct device *dev, @@ -817,6 +936,10 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; + ret = ci_extcon_register(ci); + if (ret) + goto stop; + if (ci->supports_runtime_pm) { pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -834,6 +957,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (!ret) return 0; + ci_extcon_unregister(ci); stop: ci_role_destroy(ci); deinit_phy: @@ -853,6 +977,7 @@ static int ci_hdrc_remove(struct platform_device *pdev) } dbg_remove_files(ci); + ci_extcon_unregister(ci); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); ci_usb_phy_exit(ci); diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index ad6c87a4653c..77fe2dfaf382 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -30,7 +30,41 @@ */ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) { - return hw_read(ci, OP_OTGSC, mask); + struct ci_hdrc_cable *cable; + u32 val = hw_read(ci, OP_OTGSC, mask); + + /* + * If using extcon framework for VBUS and/or ID signal + * detection overwrite OTGSC resiter value + */ + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev)) { + if (cable->changed) + val |= OTGSC_BSVIS; + else + val &= ~OTGSC_BSVIS; + + if (cable->state) + val |= OTGSC_BSV; + else + val &= ~OTGSC_BSV; + } + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev)) { + if (cable->changed) + val |= OTGSC_IDIS; + else + val &= ~OTGSC_IDIS; + + if (cable->state) + val |= OTGSC_ID; + else + val &= ~OTGSC_ID; + } + + val &= mask; + return val; } /** @@ -40,7 +74,24 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) */ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) { - hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data); + struct ci_hdrc_cable *cable; + + mask |= OTGSC_INT_STATUS_BITS; + + /* clear artificial bits */ + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev)) { + if (data & OTGSC_BSVIS) + cable->changed = false; + } + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev)) { + if (data & OTGSC_IDIS) + cable->changed = false; + } + + hw_write(ci, OP_OTGSC, mask, data); } /** diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c index 00c49bb1bd29..eb41b156a9ec 100644 --- a/drivers/usb/phy/phy-msm-usb.c +++ b/drivers/usb/phy/phy-msm-usb.c @@ -18,6 +18,7 @@ #include <linux/module.h> #include <linux/device.h> +#include <linux/gpio/consumer.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/slab.h> @@ -32,6 +33,7 @@ #include <linux/pm_runtime.h> #include <linux/of.h> #include <linux/of_device.h> +#include <linux/reboot.h> #include <linux/reset.h> #include <linux/usb.h> @@ -74,6 +76,9 @@ static int msm_hsusb_init_vddcx(struct msm_otg *motg, int init) { int ret = 0; + if (IS_ERR(motg->vddcx)) + return 0; + if (init) { ret = regulator_set_voltage(motg->vddcx, motg->vdd_levels[VDD_LEVEL_MIN], @@ -755,14 +760,8 @@ static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host) otg->host = host; dev_dbg(otg->usb_phy->dev, "host driver registered w/ tranceiver\n"); - /* - * Kick the state machine work, if peripheral is not supported - * or peripheral is already registered with us. - */ - if (motg->pdata->mode == USB_DR_MODE_HOST || otg->gadget) { - pm_runtime_get_sync(otg->usb_phy->dev); - schedule_work(&motg->sm_work); - } + pm_runtime_get_sync(otg->usb_phy->dev); + schedule_work(&motg->sm_work); return 0; } @@ -825,14 +824,8 @@ static int msm_otg_set_peripheral(struct usb_otg *otg, dev_dbg(otg->usb_phy->dev, "peripheral driver registered w/ tranceiver\n"); - /* - * Kick the state machine work, if host is not supported - * or host is already registered with us. - */ - if (motg->pdata->mode == USB_DR_MODE_PERIPHERAL || otg->host) { - pm_runtime_get_sync(otg->usb_phy->dev); - schedule_work(&motg->sm_work); - } + pm_runtime_get_sync(otg->usb_phy->dev); + schedule_work(&motg->sm_work); return 0; } @@ -1471,6 +1464,14 @@ static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event, else clear_bit(B_SESS_VLD, &motg->inputs); + if (test_bit(B_SESS_VLD, &motg->inputs)) { + /* Switch D+/D- lines to Device connector */ + gpiod_set_value_cansleep(motg->switch_gpio, 0); + } else { + /* Switch D+/D- lines to Hub */ + gpiod_set_value_cansleep(motg->switch_gpio, 1); + } + schedule_work(&motg->sm_work); return NOTIFY_DONE; @@ -1546,6 +1547,11 @@ static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg) motg->manual_pullup = of_property_read_bool(node, "qcom,manual-pullup"); + motg->switch_gpio = devm_gpiod_get_optional(&pdev->dev, "switch", + GPIOD_OUT_LOW); + if (IS_ERR(motg->switch_gpio)) + return PTR_ERR(motg->switch_gpio); + ext_id = ERR_PTR(-ENODEV); ext_vbus = ERR_PTR(-ENODEV); if (of_property_read_bool(node, "extcon")) { @@ -1615,9 +1621,22 @@ static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg) return 0; } +static int msm_otg_reboot_notify(struct notifier_block *this, + unsigned long code, void *unused) +{ + struct msm_otg *motg = container_of(this, struct msm_otg, reboot); + + /* + * Ensure that D+/D- lines are routed to uB connector, so + * we could load bootloader/kernel at next reboot + */ + gpiod_set_value_cansleep(motg->switch_gpio, 0); + return NOTIFY_DONE; +} + static int msm_otg_probe(struct platform_device *pdev) { - struct regulator_bulk_data regs[3]; + struct regulator_bulk_data regs[2]; int ret = 0; struct device_node *np = pdev->dev.of_node; struct msm_otg_platform_data *pdata; @@ -1646,6 +1665,8 @@ static int msm_otg_probe(struct platform_device *pdev) phy = &motg->phy; phy->dev = &pdev->dev; + INIT_WORK(&motg->sm_work, msm_otg_sm_work); + INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); motg->clk = devm_clk_get(&pdev->dev, np ? "core" : "usb_hs_clk"); if (IS_ERR(motg->clk)) { @@ -1701,17 +1722,21 @@ static int msm_otg_probe(struct platform_device *pdev) return motg->irq; } - regs[0].supply = "vddcx"; - regs[1].supply = "v3p3"; - regs[2].supply = "v1p8"; + regs[0].supply = "v3p3"; + regs[1].supply = "v1p8"; ret = devm_regulator_bulk_get(motg->phy.dev, ARRAY_SIZE(regs), regs); - if (ret) + if (ret) { + dev_err(&pdev->dev, "no v3p3 or v1p8\n"); return ret; + } + + motg->v3p3 = regs[0].consumer; + motg->v1p8 = regs[1].consumer; - motg->vddcx = regs[0].consumer; - motg->v3p3 = regs[1].consumer; - motg->v1p8 = regs[2].consumer; + motg->vddcx = devm_regulator_get_optional(motg->phy.dev, "vddcx"); + if (IS_ERR(motg->vddcx)) + dev_info(&pdev->dev, "no vddcx\n"); clk_set_rate(motg->clk, 60000000); @@ -1741,8 +1766,6 @@ static int msm_otg_probe(struct platform_device *pdev) writel(0, USB_USBINTR); writel(0, USB_OTGSC); - INIT_WORK(&motg->sm_work, msm_otg_sm_work); - INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work); ret = devm_request_irq(&pdev->dev, motg->irq, msm_otg_irq, IRQF_SHARED, "msm_otg", motg); if (ret) { @@ -1779,8 +1802,18 @@ static int msm_otg_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "Can not create mode change file\n"); } + if (test_bit(B_SESS_VLD, &motg->inputs)) { + /* Switch D+/D- lines to Device connector */ + gpiod_set_value_cansleep(motg->switch_gpio, 0); + } else { + /* Switch D+/D- lines to Hub */ + gpiod_set_value_cansleep(motg->switch_gpio, 1); + } + + motg->reboot.notifier_call = msm_otg_reboot_notify; + register_reboot_notifier(&motg->reboot); + pm_runtime_set_active(&pdev->dev); - pm_runtime_enable(&pdev->dev); return 0; @@ -1805,11 +1838,19 @@ static int msm_otg_remove(struct platform_device *pdev) if (phy->otg->host || phy->otg->gadget) return -EBUSY; + unregister_reboot_notifier(&motg->reboot); + if (motg->id.conn.edev) extcon_unregister_interest(&motg->id.conn); if (motg->vbus.conn.edev) extcon_unregister_interest(&motg->vbus.conn); + /* + * Ensure that D+/D- lines are routed to uB connector, so + * we could load bootloader/kernel at next reboot + */ + gpiod_set_value_cansleep(motg->switch_gpio, 0); + msm_otg_debugfs_cleanup(); cancel_delayed_work_sync(&motg->chg_work); cancel_work_sync(&motg->sm_work); diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index aadb72828834..2553aa8b608d 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -504,7 +504,7 @@ __read_extent_tree_block(const char *function, unsigned int line, struct buffer_head *bh; int err; - bh = sb_getblk(inode->i_sb, pblk); + bh = sb_getblk_gfp(inode->i_sb, pblk, __GFP_MOVABLE | GFP_NOFS); if (unlikely(!bh)) return ERR_PTR(-ENOMEM); @@ -1089,7 +1089,7 @@ static int ext4_ext_split(handle_t *handle, struct inode *inode, err = -EIO; goto cleanup; } - bh = sb_getblk(inode->i_sb, newblock); + bh = sb_getblk_gfp(inode->i_sb, newblock, __GFP_MOVABLE | GFP_NOFS); if (unlikely(!bh)) { err = -ENOMEM; goto cleanup; @@ -1283,7 +1283,7 @@ static int ext4_ext_grow_indepth(handle_t *handle, struct inode *inode, if (newblock == 0) return err; - bh = sb_getblk(inode->i_sb, newblock); + bh = sb_getblk_gfp(inode->i_sb, newblock, __GFP_MOVABLE | GFP_NOFS); if (unlikely(!bh)) return -ENOMEM; lock_buffer(bh); diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 41f8e55afcd1..cecf9aa10811 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -1323,7 +1323,7 @@ static void ext4_da_page_release_reservation(struct page *page, unsigned int offset, unsigned int length) { - int to_release = 0; + int to_release = 0, contiguous_blks = 0; struct buffer_head *head, *bh; unsigned int curr_off = 0; struct inode *inode = page->mapping->host; @@ -1344,14 +1344,23 @@ static void ext4_da_page_release_reservation(struct page *page, if ((offset <= curr_off) && (buffer_delay(bh))) { to_release++; + contiguous_blks++; clear_buffer_delay(bh); + } else if (contiguous_blks) { + lblk = page->index << + (PAGE_CACHE_SHIFT - inode->i_blkbits); + lblk += (curr_off >> inode->i_blkbits) - + contiguous_blks; + ext4_es_remove_extent(inode, lblk, contiguous_blks); + contiguous_blks = 0; } curr_off = next_off; } while ((bh = bh->b_this_page) != head); - if (to_release) { + if (contiguous_blks) { lblk = page->index << (PAGE_CACHE_SHIFT - inode->i_blkbits); - ext4_es_remove_extent(inode, lblk, to_release); + lblk += (curr_off >> inode->i_blkbits) - contiguous_blks; + ext4_es_remove_extent(inode, lblk, contiguous_blks); } /* If we have released all the blocks belonging to a cluster, then we @@ -4344,7 +4353,12 @@ static void ext4_update_other_inodes_time(struct super_block *sb, int inode_size = EXT4_INODE_SIZE(sb); oi.orig_ino = orig_ino; - ino = (orig_ino & ~(inodes_per_block - 1)) + 1; + /* + * Calculate the first inode in the inode table block. Inode + * numbers are one-based. That is, the first inode in a block + * (assuming 4k blocks and 256 byte inodes) is (n*16 + 1). + */ + ino = ((orig_ino - 1) & ~(inodes_per_block - 1)) + 1; for (i = 0; i < inodes_per_block; i++, ino++, buf += inode_size) { if (ino == orig_ino) continue; diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index f6aedf88da43..34b610ea5030 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c @@ -4816,18 +4816,12 @@ do_more: /* * blocks being freed are metadata. these blocks shouldn't * be used until this transaction is committed + * + * We use __GFP_NOFAIL because ext4_free_blocks() is not allowed + * to fail. */ - retry: - new_entry = kmem_cache_alloc(ext4_free_data_cachep, GFP_NOFS); - if (!new_entry) { - /* - * We use a retry loop because - * ext4_free_blocks() is not allowed to fail. - */ - cond_resched(); - congestion_wait(BLK_RW_ASYNC, HZ/50); - goto retry; - } + new_entry = kmem_cache_alloc(ext4_free_data_cachep, + GFP_NOFS|__GFP_NOFAIL); new_entry->efd_start_cluster = bit; new_entry->efd_group = block_group; new_entry->efd_count = count_clusters; diff --git a/fs/ext4/migrate.c b/fs/ext4/migrate.c index b52374e42102..6163ad21cb0e 100644 --- a/fs/ext4/migrate.c +++ b/fs/ext4/migrate.c @@ -620,6 +620,7 @@ int ext4_ind_migrate(struct inode *inode) struct ext4_inode_info *ei = EXT4_I(inode); struct ext4_extent *ex; unsigned int i, len; + ext4_lblk_t start, end; ext4_fsblk_t blk; handle_t *handle; int ret; @@ -633,6 +634,14 @@ int ext4_ind_migrate(struct inode *inode) EXT4_FEATURE_RO_COMPAT_BIGALLOC)) return -EOPNOTSUPP; + /* + * In order to get correct extent info, force all delayed allocation + * blocks to be allocated, otherwise delayed allocation blocks may not + * be reflected and bypass the checks on extent header. + */ + if (test_opt(inode->i_sb, DELALLOC)) + ext4_alloc_da_blocks(inode); + handle = ext4_journal_start(inode, EXT4_HT_MIGRATE, 1); if (IS_ERR(handle)) return PTR_ERR(handle); @@ -650,11 +659,13 @@ int ext4_ind_migrate(struct inode *inode) goto errout; } if (eh->eh_entries == 0) - blk = len = 0; + blk = len = start = end = 0; else { len = le16_to_cpu(ex->ee_len); blk = ext4_ext_pblock(ex); - if (len > EXT4_NDIR_BLOCKS) { + start = le32_to_cpu(ex->ee_block); + end = start + len - 1; + if (end >= EXT4_NDIR_BLOCKS) { ret = -EOPNOTSUPP; goto errout; } @@ -662,7 +673,7 @@ int ext4_ind_migrate(struct inode *inode) ext4_clear_inode_flag(inode, EXT4_INODE_EXTENTS); memset(ei->i_data, 0, sizeof(ei->i_data)); - for (i=0; i < len; i++) + for (i = start; i <= end; i++) ei->i_data[i] = cpu_to_le32(blk++); ext4_mark_inode_dirty(handle, inode); errout: diff --git a/include/dt-bindings/arm/qcom-ids.h b/include/dt-bindings/arm/qcom-ids.h new file mode 100644 index 000000000000..a18f34e7d965 --- /dev/null +++ b/include/dt-bindings/arm/qcom-ids.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2015, 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 __DT_BINDINGS_QCOM_IDS_H +#define __DT_BINDINGS_QCOM_IDS_H + +/* qcom,msm-id */ +#define QCOM_ID_MSM8916 206 +#define QCOM_ID_APQ8016 247 +#define QCOM_ID_MSM8216 248 +#define QCOM_ID_MSM8116 249 +#define QCOM_ID_MSM8616 250 + +/* qcom,board-id */ +#define QCOM_BRD_ID(a, major, minor) \ + (((major & 0xff) << 16) | ((minor & 0xff) << 8) | QCOM_BRD_ID_##a) + +#define QCOM_BRD_ID_MTP 8 +#define QCOM_BRD_ID_DRAGONBRD 10 +#define QCOM_BRD_ID_SBC 24 + +#define QCOM_BRD_SUBTYPE_DEFAULT 0 +#define QCOM_BRD_SUBTYPE_MTP8916_SMB1360 1 + +#endif diff --git a/include/dt-bindings/clock/qcom,gcc-apq8084.h b/include/dt-bindings/clock/qcom,gcc-apq8084.h index 2c0da566c46a..5aa7ebeae411 100644 --- a/include/dt-bindings/clock/qcom,gcc-apq8084.h +++ b/include/dt-bindings/clock/qcom,gcc-apq8084.h @@ -348,4 +348,10 @@ #define GCC_PCIE_1_PIPE_CLK 331 #define GCC_PCIE_1_SLV_AXI_CLK 332 +/* gdscs */ +#define USB_HS_HSIC_GDSC 0 +#define PCIE0_GDSC 1 +#define PCIE1_GDSC 2 +#define USB30_GDSC 3 + #endif diff --git a/include/dt-bindings/clock/qcom,gcc-msm8916.h b/include/dt-bindings/clock/qcom,gcc-msm8916.h index e430f644dd6c..257e2fbedd94 100644 --- a/include/dt-bindings/clock/qcom,gcc-msm8916.h +++ b/include/dt-bindings/clock/qcom,gcc-msm8916.h @@ -152,5 +152,35 @@ #define GCC_VENUS0_AHB_CLK 135 #define GCC_VENUS0_AXI_CLK 136 #define GCC_VENUS0_VCODEC0_CLK 137 +#define BIMC_DDR_CLK_SRC 138 +#define GCC_APSS_TCU_CLK 139 +#define GCC_GFX_TCU_CLK 140 +#define BIMC_GPU_CLK_SRC 141 +#define GCC_BIMC_GFX_CLK 142 +#define GCC_BIMC_GPU_CLK 143 +#define ULTAUDIO_LPAIF_PRI_I2S_CLK_SRC 144 +#define ULTAUDIO_LPAIF_SEC_I2S_CLK_SRC 145 +#define ULTAUDIO_LPAIF_AUX_I2S_CLK_SRC 146 +#define ULTAUDIO_XO_CLK_SRC 147 +#define ULTAUDIO_AHBFABRIC_CLK_SRC 148 +#define CODEC_DIGCODEC_CLK_SRC 149 +#define GCC_ULTAUDIO_PCNOC_MPORT_CLK 150 +#define GCC_ULTAUDIO_PCNOC_SWAY_CLK 151 +#define GCC_ULTAUDIO_AVSYNC_XO_CLK 152 +#define GCC_ULTAUDIO_STC_XO_CLK 153 +#define GCC_ULTAUDIO_AHBFABRIC_IXFABRIC_CLK 154 +#define GCC_ULTAUDIO_AHBFABRIC_IXFABRIC_LPM_CLK 155 +#define GCC_ULTAUDIO_LPAIF_PRI_I2S_CLK 156 +#define GCC_ULTAUDIO_LPAIF_SEC_I2S_CLK 157 +#define GCC_ULTAUDIO_LPAIF_AUX_I2S_CLK 158 +#define GCC_CODEC_DIGCODEC_CLK 159 + +/* Indexes for GDSCs */ +#define BIMC_GDSC 0 +#define VENUS_GDSC 1 +#define MDSS_GDSC 2 +#define JPEG_GDSC 3 +#define VFE_GDSC 4 +#define OXILI_GDSC 5 #endif 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,gcc-msm8974.h b/include/dt-bindings/clock/qcom,gcc-msm8974.h index 51e51c860fe6..81d32f639190 100644 --- a/include/dt-bindings/clock/qcom,gcc-msm8974.h +++ b/include/dt-bindings/clock/qcom,gcc-msm8974.h @@ -321,4 +321,7 @@ #define GCC_SDCC1_CDCCAL_SLEEP_CLK 304 #define GCC_SDCC1_CDCCAL_FF_CLK 305 +/* gdscs */ +#define USB_HS_HSIC_GDSC 0 + #endif diff --git a/include/dt-bindings/clock/qcom,mmcc-apq8084.h b/include/dt-bindings/clock/qcom,mmcc-apq8084.h index d72b5b35f15e..21fec5dbe616 100644 --- a/include/dt-bindings/clock/qcom,mmcc-apq8084.h +++ b/include/dt-bindings/clock/qcom,mmcc-apq8084.h @@ -180,4 +180,12 @@ #define VPU_SLEEP_CLK 163 #define VPU_VDP_CLK 164 +/* GDSCs */ +#define VENUS0_GDSC 0 +#define MDSS_GDSC 1 +#define CAMSS_JPEG_GDSC 2 +#define CAMSS_VFE_GDSC 3 +#define OXILI_GDSC 4 +#define OXILICX_GDSC 5 + #endif diff --git a/include/dt-bindings/clock/qcom,mmcc-msm8974.h b/include/dt-bindings/clock/qcom,mmcc-msm8974.h index 032ed87ef0f3..28651e54c9ae 100644 --- a/include/dt-bindings/clock/qcom,mmcc-msm8974.h +++ b/include/dt-bindings/clock/qcom,mmcc-msm8974.h @@ -158,4 +158,12 @@ #define SPDM_RM_AXI 141 #define SPDM_RM_OCMEMNOC 142 +/* gdscs */ +#define VENUS0_GDSC 0 +#define MDSS_GDSC 1 +#define CAMSS_JPEG_GDSC 2 +#define CAMSS_VFE_GDSC 3 +#define OXILI_GDSC 4 +#define OXILICX_GDSC 5 + #endif diff --git a/include/dt-bindings/clock/qcom,rpmcc-msm8916.h b/include/dt-bindings/clock/qcom,rpmcc-msm8916.h new file mode 100644 index 000000000000..62d63940896a --- /dev/null +++ b/include/dt-bindings/clock/qcom,rpmcc-msm8916.h @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Linaro Limited + * + * 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_MSM_RPMCC_8916_H +#define _DT_BINDINGS_CLK_MSM_RPMCC_8916_H + +#define RPM_XO_CLK_SRC 0 +#define RPM_XO_A_CLK_SRC 1 +#define RPM_PCNOC_CLK 2 +#define RPM_PCNOC_A_CLK 3 +#define RPM_SNOC_CLK 4 +#define RPM_SNOC_A_CLK 6 +#define RPM_BIMC_CLK 7 +#define RPM_BIMC_A_CLK 8 +#define RPM_QDSS_CLK 9 +#define RPM_QDSS_A_CLK 10 +#define RPM_BB_CLK1 11 +#define RPM_BB_CLK1_A 12 +#define RPM_BB_CLK2 13 +#define RPM_BB_CLK2_A 14 +#define RPM_RF_CLK1 15 +#define RPM_RF_CLK1_A 16 +#define RPM_RF_CLK2 17 +#define RPM_RF_CLK2_A 18 +#define RPM_BB_CLK1_PIN 19 +#define RPM_BB_CLK1_A_PIN 20 +#define RPM_BB_CLK2_PIN 21 +#define RPM_BB_CLK2_A_PIN 22 +#define RPM_RF_CLK1_PIN 23 +#define RPM_RF_CLK1_A_PIN 24 +#define RPM_RF_CLK2_PIN 25 +#define RPM_RF_CLK2_A_PIN 26 + +#endif diff --git a/include/dt-bindings/mfd/qcom-smd-rpm.h b/include/dt-bindings/mfd/qcom-smd-rpm.h new file mode 100644 index 000000000000..890ca5205e4f --- /dev/null +++ b/include/dt-bindings/mfd/qcom-smd-rpm.h @@ -0,0 +1,28 @@ +/* + * This header provides constants for the Qualcomm RPM bindings. + */ + +#ifndef _DT_BINDINGS_MFD_QCOM_SMD_RPM_H +#define _DT_BINDINGS_MFD_QCOM_SMD_RPM_H + +/* + * Constants used for addressing resources in the RPM. + */ +#define QCOM_SMD_RPM_BUS_CLK 0x316b6c63 +#define QCOM_SMD_RPM_BUS_MASTER 0x73616d62 +#define QCOM_SMD_RPM_BUS_SLAVE 0x766c7362 +#define QCOM_SMD_RPM_CLK_BUF_A 0x616B6C63 +#define QCOM_SMD_RPM_LDOA 0x616f646c +#define QCOM_SMD_RPM_LDOB 0x626F646C +#define QCOM_SMD_RPM_MEM_CLK 0x326b6c63 +#define QCOM_SMD_RPM_MISC_CLK 0x306b6c63 +#define QCOM_SMD_RPM_NCPA 0x6170636E +#define QCOM_SMD_RPM_NCPB 0x6270636E +#define QCOM_SMD_RPM_OCMEM_PWR 0x706d636f +#define QCOM_SMD_RPM_QPIC_CLK 0x63697071 +#define QCOM_SMD_RPM_SMPA 0x61706d73 +#define QCOM_SMD_RPM_SMPB 0x62706d73 +#define QCOM_SMD_RPM_SPDM 0x63707362 +#define QCOM_SMD_RPM_VSA 0x00617376 + +#endif diff --git a/include/dt-bindings/pinctrl/qcom,pmic-mpp.h b/include/dt-bindings/pinctrl/qcom,pmic-mpp.h index c10205491f8d..a15c1704d0ec 100644 --- a/include/dt-bindings/pinctrl/qcom,pmic-mpp.h +++ b/include/dt-bindings/pinctrl/qcom,pmic-mpp.h @@ -7,6 +7,47 @@ #define _DT_BINDINGS_PINCTRL_QCOM_PMIC_MPP_H /* power-source */ + +/* Digital Input/Output: level [PM8058] */ +#define PM8058_MPP_VPH 0 +#define PM8058_MPP_S3 1 +#define PM8058_MPP_L2 2 +#define PM8058_MPP_L3 3 + +/* Digital Input/Output: level [PM8901] */ +#define PM8901_MPP_MSMIO 0 +#define PM8901_MPP_DIG 1 +#define PM8901_MPP_L5 2 +#define PM8901_MPP_S4 3 +#define PM8901_MPP_VPH 4 + +/* Digital Input/Output: level [PM8921] */ +#define PM8921_MPP_S4 1 +#define PM8921_MPP_L15 3 +#define PM8921_MPP_L17 4 +#define PM8921_MPP_VPH 7 + +/* Digital Input/Output: level [PM8821] */ +#define PM8821_MPP_1P8 0 +#define PM8821_MPP_VPH 7 + +/* Digital Input/Output: level [PM8018] */ +#define PM8018_MPP_L4 0 +#define PM8018_MPP_L14 1 +#define PM8018_MPP_S3 2 +#define PM8018_MPP_L6 3 +#define PM8018_MPP_L2 4 +#define PM8018_MPP_L5 5 +#define PM8018_MPP_VPH 7 + +/* Digital Input/Output: level [PM8038] */ +#define PM8038_MPP_L20 0 +#define PM8038_MPP_L11 1 +#define PM8038_MPP_L5 2 +#define PM8038_MPP_L15 3 +#define PM8038_MPP_L17 4 +#define PM8038_MPP_VPH 7 + #define PM8841_MPP_VPH 0 #define PM8841_MPP_S3 2 @@ -37,6 +78,16 @@ #define PMIC_MPP_AMUX_ROUTE_ABUS3 6 #define PMIC_MPP_AMUX_ROUTE_ABUS4 7 +/* Analog Output: level */ +#define PMIC_MPP_AOUT_LVL_1V25 0 +#define PMIC_MPP_AOUT_LVL_1V25_2 1 +#define PMIC_MPP_AOUT_LVL_0V625 2 +#define PMIC_MPP_AOUT_LVL_0V3125 3 +#define PMIC_MPP_AOUT_LVL_MPP 4 +#define PMIC_MPP_AOUT_LVL_ABUS1 5 +#define PMIC_MPP_AOUT_LVL_ABUS2 6 +#define PMIC_MPP_AOUT_LVL_ABUS3 7 + /* To be used with "function" */ #define PMIC_MPP_FUNC_NORMAL "normal" #define PMIC_MPP_FUNC_PAIRED "paired" diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 73b45225a7ca..e6797ded700e 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -317,6 +317,13 @@ sb_getblk(struct super_block *sb, sector_t block) return __getblk_gfp(sb->s_bdev, block, sb->s_blocksize, __GFP_MOVABLE); } + +static inline struct buffer_head * +sb_getblk_gfp(struct super_block *sb, sector_t block, gfp_t gfp) +{ + return __getblk_gfp(sb->s_bdev, block, sb->s_blocksize, gfp); +} + static inline struct buffer_head * sb_find_get_block(struct super_block *sb, sector_t block) { diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h index 78842f46f152..44a62d1ebc2b 100644 --- a/include/linux/clk-provider.h +++ b/include/linux/clk-provider.h @@ -184,6 +184,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_hw *(*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, @@ -410,7 +411,7 @@ void clk_unregister_divider(struct clk *clk); struct clk_mux { struct clk_hw hw; void __iomem *reg; - u32 *table; + unsigned int *table; u32 mask; u8 shift; u8 flags; @@ -426,6 +427,11 @@ struct clk_mux { 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 * const *parent_names, u8 num_parents, unsigned long flags, @@ -436,7 +442,7 @@ struct clk *clk_register_mux_table(struct device *dev, const char *name, const char * const *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); diff --git a/include/linux/memblock.h b/include/linux/memblock.h index cc4b01972060..22be48641244 100644 --- a/include/linux/memblock.h +++ b/include/linux/memblock.h @@ -322,6 +322,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/qcom-smd-rpm.h b/include/linux/mfd/qcom-smd-rpm.h new file mode 100644 index 000000000000..2a53dcaeeeed --- /dev/null +++ b/include/linux/mfd/qcom-smd-rpm.h @@ -0,0 +1,35 @@ +#ifndef __QCOM_SMD_RPM_H__ +#define __QCOM_SMD_RPM_H__ + +struct qcom_smd_rpm; + +#define QCOM_SMD_RPM_ACTIVE_STATE 0 +#define QCOM_SMD_RPM_SLEEP_STATE 1 + +/* + * Constants used for addressing resources in the RPM. + */ +#define QCOM_SMD_RPM_BOOST 0x61747362 +#define QCOM_SMD_RPM_BUS_CLK 0x316b6c63 +#define QCOM_SMD_RPM_BUS_MASTER 0x73616d62 +#define QCOM_SMD_RPM_BUS_SLAVE 0x766c7362 +#define QCOM_SMD_RPM_CLK_BUF_A 0x616B6C63 +#define QCOM_SMD_RPM_LDOA 0x616f646c +#define QCOM_SMD_RPM_LDOB 0x626F646C +#define QCOM_SMD_RPM_MEM_CLK 0x326b6c63 +#define QCOM_SMD_RPM_MISC_CLK 0x306b6c63 +#define QCOM_SMD_RPM_NCPA 0x6170636E +#define QCOM_SMD_RPM_NCPB 0x6270636E +#define QCOM_SMD_RPM_OCMEM_PWR 0x706d636f +#define QCOM_SMD_RPM_QPIC_CLK 0x63697071 +#define QCOM_SMD_RPM_SMPA 0x61706d73 +#define QCOM_SMD_RPM_SMPB 0x62706d73 +#define QCOM_SMD_RPM_SPDM 0x63707362 +#define QCOM_SMD_RPM_VSA 0x00617376 + +int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, + int state, + u32 resource_type, u32 resource_id, + void *buf, size_t count); + +#endif diff --git a/include/linux/mfd/qcom_rpm.h b/include/linux/mfd/qcom_rpm.h index 742ebf1b76ca..a7af2edf0f64 100644 --- a/include/linux/mfd/qcom_rpm.h +++ b/include/linux/mfd/qcom_rpm.h @@ -9,5 +9,6 @@ struct qcom_rpm; #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/nvmem-consumer.h b/include/linux/nvmem-consumer.h new file mode 100644 index 000000000000..7e1631439939 --- /dev/null +++ b/include/linux/nvmem-consumer.h @@ -0,0 +1,152 @@ +/* + * nvmem framework consumer. + * + * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef _LINUX_NVMEM_CONSUMER_H +#define _LINUX_NVMEM_CONSUMER_H + +struct device; +/* consumer cookie */ +struct nvmem_cell; +struct nvmem_device; + +struct nvmem_cell_info { + const char *name; + int offset; + int bytes; + int bit_offset; + int nbits; +}; + +#if IS_ENABLED(CONFIG_NVMEM) + +/* Cell based interface */ +struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *name); +struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *name); +struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, + const char *name); +void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell); +void *nvmem_cell_read(struct nvmem_cell *cell, ssize_t *len); +int nvmem_cell_write(struct nvmem_cell *cell, void *buf, ssize_t len); + +/* direct nvmem device read/write interface */ +struct nvmem_device *nvmem_device_get(struct device *dev, const char *name); +struct nvmem_device *devm_nvmem_device_get(struct device *dev, + const char *name); +struct nvmem_device *of_nvmem_device_get(struct device_node *np, + const char *name); +void nvmem_cell_put(struct nvmem_cell *cell); +void nvmem_device_put(struct nvmem_device *nvmem); +void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem); +int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset, + size_t bytes, void *buf); +int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset, + size_t bytes, void *buf); +int nvmem_device_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf); +int nvmem_device_cell_write(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf); + +#else + +static inline struct nvmem_cell *nvmem_cell_get(struct device *dev, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} + +static inline struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void devm_nvmem_cell_put(struct device *dev, + struct nvmem_cell *cell) +{ + +} +static inline void nvmem_cell_put(struct nvmem_cell *cell) +{ +} + +static inline char *nvmem_cell_read(struct nvmem_cell *cell, ssize_t *len) +{ + return ERR_PTR(-ENOSYS); +} + +static inline int nvmem_cell_write(struct nvmem_cell *cell, + const char *buf, ssize_t len) +{ + return -ENOSYS; +} + +static inline struct nvmem_device *nvmem_device_get(struct device *dev, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} + +static inline struct nvmem_device *devm_nvmem_device_get(struct device *dev, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void nvmem_device_put(struct nvmem_device *nvmem) +{ +} + +static inline void devm_nvmem_device_put(struct device *dev, + struct nvmem_device *nvmem) +{ +} + +static inline int nvmem_device_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, + void *buf) +{ + return -ENOSYS; +} + +static inline int nvmem_device_cell_write(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, + void *buf) +{ + return -ENOSYS; +} + +static inline int nvmem_device_read(struct nvmem_device *nvmem, + unsigned int offset, size_t bytes, void *buf) +{ + return -ENOSYS; +} + +static inline int nvmem_device_write(struct nvmem_device *nvmem, + unsigned int offset, size_t bytes, void *buf) +{ + return -ENOSYS; +} + +static inline struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} + +static inline struct nvmem_device *of_nvmem_device_get(struct device_node *np, + const char *name) +{ + return ERR_PTR(-ENOSYS); +} +#endif /* CONFIG_NVMEM */ + + +#endif /* ifndef _LINUX_NVMEM_CONSUMER_H */ diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h new file mode 100644 index 000000000000..74eed425f884 --- /dev/null +++ b/include/linux/nvmem-provider.h @@ -0,0 +1,48 @@ +/* + * nvmem framework provider. + * + * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef _LINUX_NVMEM_PROVIDER_H +#define _LINUX_NVMEM_PROVIDER_H + +#include <linux/nvmem-consumer.h> + +struct nvmem_device; + +struct nvmem_config { + struct device *dev; + const char *name; + int id; + struct module *owner; + struct nvmem_cell_info *cells; + int ncells; + bool read_only; +}; + +#if IS_ENABLED(CONFIG_NVMEM) + +struct nvmem_device *nvmem_register(struct nvmem_config *cfg); +int nvmem_unregister(struct nvmem_device *nvmem); + +#else + +static inline struct nvmem_device *nvmem_register(struct nvmem_config *cfg) +{ + return ERR_PTR(-ENOSYS); +} + +static inline int nvmem_unregister(struct nvmem_device *nvmem) +{ + return -ENOSYS; +} + +#endif /* CONFIG_NVMEM */ + +#endif /* ifndef _LINUX_NVMEM_PROVIDER_H */ diff --git a/include/linux/regulator/qcom_smd-regulator.h b/include/linux/regulator/qcom_smd-regulator.h new file mode 100644 index 000000000000..a71b6adc64ba --- /dev/null +++ b/include/linux/regulator/qcom_smd-regulator.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2015, 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_SMD_REGULATOR_H_ +#define __QCOM_SMD_REGULATOR_H_ + +#ifdef CONFIG_REGULATOR_QCOM_SMD_RPM +int qcom_rpm_set_floor(struct regulator *regulator, int floor); +int qcom_rpm_set_corner(struct regulator *regulator, int corner); +#else +static inline int qcom_rpm_set_floor(struct regulator *regulator, int floor) +{ + return -EINVAL; +} + +static inline int qcom_rpm_set_corner(struct regulator *regulator, int corner) +{ + return -EINVAL; +} +#endif + +#endif diff --git a/include/linux/soc/qcom/smd.h b/include/linux/soc/qcom/smd.h new file mode 100644 index 000000000000..22cf3bfbc7a8 --- /dev/null +++ b/include/linux/soc/qcom/smd.h @@ -0,0 +1,46 @@ +#ifndef __LINUX_QCOM_SMEM_H__ +#define __LINUX_QCOM_SMEM_H__ + +#include <linux/device.h> +#include <linux/mod_devicetable.h> + +struct qcom_smd; +struct qcom_smd_channel; +struct qcom_smd_lookup; + +/** + * struct qcom_smd_device - smd device struct + * @dev: the device struct + * @channel: handle to the smd channel for this device + */ +struct qcom_smd_device { + struct device dev; + struct qcom_smd_channel *channel; +}; + +/** + * struct qcom_smd_driver - smd driver struct + * @driver: underlying device driver + * @probe: invoked when the smd channel is found + * @remove: invoked when the smd channel is closed + * @callback: invoked when an inbound message is received on the channel, + * should return 0 on success or -EBUSY if the data cannot be + * consumed at this time + */ +struct qcom_smd_driver { + struct device_driver driver; + int (*probe)(struct qcom_smd_device *dev); + void (*remove)(struct qcom_smd_device *dev); + int (*callback)(struct qcom_smd_device *, const void *, size_t); +}; + +int qcom_smd_driver_register(struct qcom_smd_driver *drv); +void qcom_smd_driver_unregister(struct qcom_smd_driver *drv); + +#define module_qcom_smd_driver(__smd_driver) \ + module_driver(__smd_driver, qcom_smd_driver_register, \ + qcom_smd_driver_unregister) + +int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len); + +#endif diff --git a/include/linux/soc/qcom/smem.h b/include/linux/soc/qcom/smem.h new file mode 100644 index 000000000000..bc9630d3aced --- /dev/null +++ b/include/linux/soc/qcom/smem.h @@ -0,0 +1,11 @@ +#ifndef __QCOM_SMEM_H__ +#define __QCOM_SMEM_H__ + +#define QCOM_SMEM_HOST_ANY -1 + +int qcom_smem_alloc(unsigned host, unsigned item, size_t size); +int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size); + +int qcom_smem_get_free_space(unsigned host); + +#endif diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index ab94f78c4dd1..05915cb1031c 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -5,9 +5,29 @@ #ifndef __LINUX_USB_CHIPIDEA_H #define __LINUX_USB_CHIPIDEA_H +#include <linux/extcon.h> #include <linux/usb/otg.h> struct ci_hdrc; + +/** + * struct ci_hdrc_cable - structure for external connector cable state tracking + * @state: current state of the line + * @changed: set to true when extcon event happen + * @edev: device which generate events + * @ci: driver state of the chipidea device + * @nb: hold event notification callback + * @conn: used for notification registration + */ +struct ci_hdrc_cable { + bool state; + bool changed; + struct extcon_dev *edev; + struct ci_hdrc *ci; + struct notifier_block nb; + struct extcon_specific_cable_nb conn; +}; + struct ci_hdrc_platform_data { const char *name; /* offset of the capability registers */ @@ -35,6 +55,10 @@ struct ci_hdrc_platform_data { void (*notify_event) (struct ci_hdrc *ci, unsigned event); struct regulator *reg_vbus; bool tpl_support; + + /* VBUS and ID signal state tracking, using extcon framework */ + struct ci_hdrc_cable vbus_extcon; + struct ci_hdrc_cable id_extcon; }; /* Default offset of capability registers */ diff --git a/include/linux/usb/msm_hsusb.h b/include/linux/usb/msm_hsusb.h index e55a1504266e..536a72c2e490 100644 --- a/include/linux/usb/msm_hsusb.h +++ b/include/linux/usb/msm_hsusb.h @@ -155,6 +155,10 @@ struct msm_usb_cable { * starting controller using usbcmd run/stop bit. * @vbus: VBUS signal state trakining, using extcon framework * @id: ID signal state trakining, using extcon framework + * @switch_gpio: Descriptor for GPIO used to control external Dual + * SPDT USB Switch. + * @reboot: Used to inform the driver to route USB D+/D- line to Device + * connector */ struct msm_otg { struct usb_phy phy; @@ -188,6 +192,9 @@ struct msm_otg { struct msm_usb_cable vbus; struct msm_usb_cable id; + + struct gpio_desc *switch_gpio; + struct notifier_block reboot; }; #endif diff --git a/mm/memblock.c b/mm/memblock.c index 87108e77e476..248f3b337c2a 100644 --- a/mm/memblock.c +++ b/mm/memblock.c @@ -1554,6 +1554,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 506eac8b38af..0f62a2a32497 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -5371,6 +5371,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; @@ -5387,6 +5389,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); @@ -5394,7 +5397,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 /* @@ -5402,10 +5405,12 @@ 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) { + mem_map -= offset; + VM_BUG_ON(page_to_pfn(mem_map) != pgdat->node_start_pfn); + } +#endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP || CONFIG_FLATMEM */ } #endif #endif /* CONFIG_FLAT_NODE_MEM_MAP */ |