aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Harkin <ryan.harkin@linaro.org>2017-07-21 12:57:43 +0100
committerRyan Harkin <ryan.harkin@linaro.org>2017-07-21 12:57:43 +0100
commit4a84cd3e2433cfb0f208af2f418797b10a451ff3 (patch)
treea9c351b5ae6f9d007ca67447ca180b8efe371dfd
parent87f363cf7fd8ab70e5c197dc4f6530c068d4d99c (diff)
parentf372f4af24443019060fdd05282efb02af0e91db (diff)
Merge branch 'latest-armlt-scmi' into latest-armltlatest-armlt
Signed-off-by: Ryan Harkin <ryan.harkin@linaro.org> # Conflicts: # arch/arm64/boot/dts/arm/juno-base.dtsi # linaro/configs/vexpress64.conf
-rw-r--r--Documentation/devicetree/bindings/arm/arm,scmi.txt175
-rw-r--r--Documentation/devicetree/bindings/mailbox/arm-mhu.txt39
-rw-r--r--Documentation/devicetree/bindings/mailbox/mailbox.txt28
-rw-r--r--arch/arm64/boot/dts/arm/juno-base.dtsi162
-rw-r--r--arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi6
-rw-r--r--arch/arm64/boot/dts/arm/juno-r1.dts36
-rw-r--r--arch/arm64/boot/dts/arm/juno-r2.dts36
-rw-r--r--arch/arm64/boot/dts/arm/juno.dts36
-rw-r--r--arch/arm64/configs/defconfig10
-rw-r--r--drivers/clk/Kconfig10
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk-scmi.c216
-rw-r--r--drivers/cpufreq/Kconfig.arm11
-rw-r--r--drivers/cpufreq/Makefile1
-rw-r--r--drivers/cpufreq/scmi-cpufreq.c268
-rw-r--r--drivers/cpufreq/scpi-cpufreq.c38
-rw-r--r--drivers/firmware/Kconfig34
-rw-r--r--drivers/firmware/Makefile1
-rw-r--r--drivers/firmware/arm_scmi/Makefile3
-rw-r--r--drivers/firmware/arm_scmi/base.c293
-rw-r--r--drivers/firmware/arm_scmi/clock.c339
-rw-r--r--drivers/firmware/arm_scmi/common.h126
-rw-r--r--drivers/firmware/arm_scmi/driver.c869
-rw-r--r--drivers/firmware/arm_scmi/perf.c511
-rw-r--r--drivers/firmware/arm_scmi/power.c242
-rw-r--r--drivers/firmware/arm_scmi/scmi_pm_domain.c134
-rw-r--r--drivers/firmware/arm_scmi/sensors.c286
-rw-r--r--drivers/firmware/arm_scpi.c63
-rw-r--r--drivers/hwmon/Kconfig12
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/scmi-hwmon.c259
-rw-r--r--drivers/mailbox/arm_mhu.c388
-rw-r--r--drivers/mailbox/mailbox-test.c11
-rw-r--r--drivers/mailbox/mailbox.c20
-rw-r--r--include/linux/scmi_protocol.h215
-rw-r--r--include/linux/scpi_protocol.h3
-rw-r--r--linaro/configs/vexpress64.conf10
37 files changed, 4718 insertions, 175 deletions
diff --git a/Documentation/devicetree/bindings/arm/arm,scmi.txt b/Documentation/devicetree/bindings/arm/arm,scmi.txt
new file mode 100644
index 000000000000..29363d9c4d29
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/arm,scmi.txt
@@ -0,0 +1,175 @@
+System Control and Management Interface (SCMI) Message Protocol
+----------------------------------------------------------
+
+The SCMI is intended to allow agents such as OSPM to manage various functions
+that are provided by the hardware platform it is running on, including power
+and performance functions.
+
+This binding is intended to define the interface the firmware implementing
+the SCMI as described in ARM document number ARM DUI 0922B ("ARM System Control
+and Management Interface Platform Design Document")[0] provide for OSPM in
+the device tree.
+
+Required properties:
+
+- compatible : shall be "arm,scmi"
+- method : The method of calling the SCMI firmware. Only permitted value
+ currently is:
+ "mailbox-doorbell" : When mailbox doorbell is used as a mechanism
+ to alert the presence of a messages and/or
+ notification
+- mboxes: List of phandle and mailbox channel specifiers. It should contain
+ exactly one or two mailboxes, one for transmitting messages("tx")
+ and another optional for receiving the notifications("rx") if
+ supported.
+- mbox-names: shall be "tx" or "rx"
+- shmem : List of phandle pointing to the shared memory(SHM) area as per
+ generic mailbox client binding.
+
+See Documentation/devicetree/bindings/mailbox/mailbox.txt for more details
+about the generic mailbox controller and client driver bindings.
+
+Each protocol supported shall have a sub-node with corresponding compatible
+as described in the following sections. If the platform supports dedicated
+communication channel for a particular protocol, the 3 properties namely:
+mboxes, mbox-names and shmem shall be present in the sub-node corresponding
+to that protocol.
+
+Clock/Performance bindings for the clocks/OPPs based on SCMI Message Protocol
+------------------------------------------------------------
+
+This binding uses the common clock binding[1].
+
+Required properties:
+- #clock-cells : Should be 1. Contains the Clock ID value used by SCMI commands.
+
+Power domain bindings for the power domains based on SCMI Message Protocol
+------------------------------------------------------------
+
+This binding uses the generic power domain binding[4].
+
+PM domain providers
+===================
+
+Required properties:
+ - #power-domain-cells : Should be 1. Contains the device or the power
+ domain ID value used by SCMI commands.
+
+PM domain consumers
+===================
+
+Required properties:
+ - power-domains : A phandle and PM domain specifier as defined by bindings of
+ the power controller specified by phandle.
+
+Sensor bindings for the sensors based on SCMI Message Protocol
+--------------------------------------------------------------
+SCMI provides an API to access the various sensors on the SoC.
+
+Required properties:
+- #thermal-sensor-cells: should be set to 1. This property follows the
+ thermal device tree bindings[2].
+
+ Valid cell values are raw identifiers (Sensor ID)
+ as used by the firmware. Refer to platform details
+ for your implementation for the IDs to use.
+
+SRAM and Shared Memory for SCMI
+-------------------------------
+
+A small area of SRAM is reserved for SCMI communication between application
+processors and SCP.
+
+The properties should follow the generic mmio-sram description found in [3]
+
+Each sub-node represents the reserved area for SCMI.
+
+Required sub-node properties:
+- reg : The base offset and size of the reserved area with the SRAM
+- compatible : should be "arm,scmi-shmem" for Non-secure SRAM based
+ shared memory
+
+[0] http://infocenter.arm.com/help/topic/com.arm.doc.den0056a/index.html
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+[2] Documentation/devicetree/bindings/thermal/thermal.txt
+[3] Documentation/devicetree/bindings/sram/sram.txt
+[4] Documentation/devicetree/bindings/power/power_domain.txt
+
+Example:
+
+sram: sram@50000000 {
+ compatible = "mmio-sram";
+ reg = <0x0 0x50000000 0x0 0x10000>;
+
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0 0x0 0x50000000 0x10000>;
+
+ cpu_scp_lpri: scp-shmem@0 {
+ compatible = "arm,scmi-shmem";
+ reg = <0x0 0x200>;
+ };
+
+ cpu_scp_hpri: scp-shmem@200 {
+ compatible = "arm,scmi-shmem";
+ reg = <0x200 0x200>;
+ };
+};
+
+mailbox: mailbox0@40000000 {
+ ....
+ #mbox-cells = <1>;
+};
+
+scmi_protocol: scmi@2e000000 {
+ compatible = "arm,scmi";
+ method = "mailbox-doorbell";
+ mboxes = <&mailbox 0 &mailbox 1>;
+ shmem = <&cpu_scp_lpri &cpu_scp_hpri>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ scmi_devpd: protocol@11 {
+ reg = <0x11>;
+ #power-domain-cells = <1>;
+ };
+
+ scmi_dvfs: protocol@13 {
+ reg = <0x13>;
+ #clock-cells = <1>;
+ };
+
+ scmi_clk: protocol@14 {
+ reg = <0x14>;
+ #clock-cells = <1>;
+ };
+
+ scmi_sensors0: protocol@15 {
+ reg = <0x15>;
+ #thermal-sensor-cells = <1>;
+ };
+};
+
+cpu@0 {
+ ...
+ reg = <0 0>;
+ clocks = <&scmi_dvfs 0>;
+};
+
+hdlcd@7ff60000 {
+ ...
+ reg = <0 0x7ff60000 0 0x1000>;
+ clocks = <&scmi_clk 4>;
+ power-domains = <&scmi_devpd 1>;
+};
+
+thermal-zones {
+ soc_thermal {
+ polling-delay-passive = <100>;
+ polling-delay = <1000>;
+
+ /* sensor ID */
+ thermal-sensors = <&scmi_sensors0 3>;
+ ...
+ };
+};
diff --git a/Documentation/devicetree/bindings/mailbox/arm-mhu.txt b/Documentation/devicetree/bindings/mailbox/arm-mhu.txt
index 4971f03f0b33..ba659bcc7109 100644
--- a/Documentation/devicetree/bindings/mailbox/arm-mhu.txt
+++ b/Documentation/devicetree/bindings/mailbox/arm-mhu.txt
@@ -10,6 +10,15 @@ STAT register and the remote clears it after having read the data.
The last channel is specified to be a 'Secure' resource, hence can't be
used by Linux running NS.
+The MHU drives the interrupt signal using a 32-bit register, with all
+32-bits logically ORed together. It provides a set of registers to
+enable software to set, clear and check the status of each of the bits
+of this register independently. The use of 32 bits per interrupt line
+enables software to provide more information about the source of the
+interrupt. For example, each bit of the register can be associated with
+a type of event that can contribute to raising the interrupt. Each of
+the 32-bits can be used as "doorbell" to alert the remote processor.
+
Mailbox Device Node:
====================
@@ -18,13 +27,21 @@ Required properties:
- compatible: Shall be "arm,mhu" & "arm,primecell"
- reg: Contains the mailbox register address range (base
address and length)
-- #mbox-cells Shall be 1 - the index of the channel needed.
+- #mbox-cells Shall be 1 - the index of the channel needed when
+ not used as set of doorbell bits.
+ Shall be 2 - the index of the channel needed, and
+ the index of the doorbell bit within the channel
+ when used in doorbell mode.
- interrupts: Contains the interrupt information corresponding to
- each of the 3 links of MHU.
+ each of the 3 physical channels of MHU namely low
+ priority non-secure, high priority non-secure and
+ secure channels.
Example:
--------
+1. Controller which doesn't support doorbells
+
mhu: mailbox@2b1f0000 {
#mbox-cells = <1>;
compatible = "arm,mhu", "arm,primecell";
@@ -41,3 +58,21 @@ Example:
reg = <0 0x2e000000 0x4000>;
mboxes = <&mhu 1>; /* HP-NonSecure */
};
+
+2. Controller which supports doorbells
+
+ mhu: mailbox@2b1f0000 {
+ #mbox-cells = <2>;
+ compatible = "arm,mhu", "arm,primecell";
+ reg = <0 0x2b1f0000 0x1000>;
+ interrupts = <0 36 4>, /* LP-NonSecure */
+ <0 35 4>; /* HP-NonSecure */
+ clocks = <&clock 0 2 1>;
+ clock-names = "apb_pclk";
+ };
+
+ mhu_client: scb@2e000000 {
+ compatible = "arm,scpi";
+ reg = <0 0x2e000000 0x200>;
+ mboxes = <&mhu 1 4>; /* HP-NonSecure 5th doorbell bit */
+ };
diff --git a/Documentation/devicetree/bindings/mailbox/mailbox.txt b/Documentation/devicetree/bindings/mailbox/mailbox.txt
index be05b9746c69..af8ecee2ac68 100644
--- a/Documentation/devicetree/bindings/mailbox/mailbox.txt
+++ b/Documentation/devicetree/bindings/mailbox/mailbox.txt
@@ -23,6 +23,11 @@ Required property:
Optional property:
- mbox-names: List of identifier strings for each mailbox channel.
+- shmem : List of phandle pointing to the shared memory(SHM) area between the
+ users of these mailboxes for IPC, one for each mailbox. This shared
+ memory can be part of any memory reserved for the purpose of this
+ communication between the mailbox client and the remote.
+
Example:
pwr_cntrl: power {
@@ -30,3 +35,26 @@ Example:
mbox-names = "pwr-ctrl", "rpc";
mboxes = <&mailbox 0 &mailbox 1>;
};
+
+Example with shared memory(shmem):
+
+ sram: sram@50000000 {
+ compatible = "mmio-sram";
+ reg = <0x50000000 0x10000>;
+
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0 0x50000000 0x10000>;
+
+ cl_shmem: shmem@0 {
+ compatible = "client-shmem";
+ reg = <0x0 0x200>;
+ };
+ };
+
+ client@2e000000 {
+ ...
+ mboxes = <&mailbox 0>;
+ shmem = <&cl_shmem>;
+ ..
+ };
diff --git a/arch/arm64/boot/dts/arm/juno-base.dtsi b/arch/arm64/boot/dts/arm/juno-base.dtsi
index eea734b96de1..c55e1edf2986 100644
--- a/arch/arm64/boot/dts/arm/juno-base.dtsi
+++ b/arch/arm64/boot/dts/arm/juno-base.dtsi
@@ -23,13 +23,14 @@
};
mailbox: mhu@2b1f0000 {
- compatible = "arm,mhu", "arm,primecell";
+ compatible = "arm,mhu-doorbell", "arm,primecell";
reg = <0x0 0x2b1f0000 0x0 0x1000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "mhu_lpri_rx",
"mhu_hpri_rx";
- #mbox-cells = <1>;
+ #mbox-cells = <2>;
+ mbox-name = "ARM-MHU";
clocks = <&soc_refclk100mhz>;
clock-names = "apb_pclk";
};
@@ -53,8 +54,7 @@
#iommu-cells = <1>;
#global-interrupts = <1>;
dma-coherent;
- power-domains = <&scpi_devpd 0>;
- status = "disabled";
+ power-domains = <&scmi_devpd 8>;
};
gic: interrupt-controller@2c010000 {
@@ -95,7 +95,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
ports {
#address-cells = <1>;
#size-cells = <0>;
@@ -124,7 +124,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
tpiu_in_port: endpoint {
slave-mode;
@@ -140,7 +140,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
ports {
#address-cells = <1>;
#size-cells = <0>;
@@ -179,7 +179,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
etr_in_port: endpoint {
slave-mode;
@@ -196,20 +196,29 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
stm_out_port: endpoint {
};
};
};
+ cpu_debug0: cpu_debug@22010000 {
+ compatible = "arm,coresight-cpu-debug", "arm,primecell";
+ reg = <0x0 0x22010000 0x0 0x1000>;
+
+ clocks = <&soc_smc50mhz>;
+ clock-names = "apb_pclk";
+ power-domains = <&scmi_devpd 8>;
+ };
+
etm0: etm@22040000 {
compatible = "arm,coresight-etm4x", "arm,primecell";
reg = <0 0x22040000 0 0x1000>;
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
cluster0_etm0_out_port: endpoint {
remote-endpoint = <&cluster0_funnel_in_port0>;
@@ -223,7 +232,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
ports {
#address-cells = <1>;
#size-cells = <0>;
@@ -253,13 +262,22 @@
};
};
+ cpu_debug1: cpu_debug@22110000 {
+ compatible = "arm,coresight-cpu-debug", "arm,primecell";
+ reg = <0x0 0x22110000 0x0 0x1000>;
+
+ clocks = <&soc_smc50mhz>;
+ clock-names = "apb_pclk";
+ power-domains = <&scmi_devpd 8>;
+ };
+
etm1: etm@22140000 {
compatible = "arm,coresight-etm4x", "arm,primecell";
reg = <0 0x22140000 0 0x1000>;
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
cluster0_etm1_out_port: endpoint {
remote-endpoint = <&cluster0_funnel_in_port1>;
@@ -267,13 +285,22 @@
};
};
+ cpu_debug2: cpu_debug@23010000 {
+ compatible = "arm,coresight-cpu-debug", "arm,primecell";
+ reg = <0x0 0x23010000 0x0 0x1000>;
+
+ clocks = <&soc_smc50mhz>;
+ clock-names = "apb_pclk";
+ power-domains = <&scmi_devpd 8>;
+ };
+
etm2: etm@23040000 {
compatible = "arm,coresight-etm4x", "arm,primecell";
reg = <0 0x23040000 0 0x1000>;
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
cluster1_etm0_out_port: endpoint {
remote-endpoint = <&cluster1_funnel_in_port0>;
@@ -287,7 +314,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
ports {
#address-cells = <1>;
#size-cells = <0>;
@@ -331,13 +358,22 @@
};
};
+ cpu_debug3: cpu_debug@23110000 {
+ compatible = "arm,coresight-cpu-debug", "arm,primecell";
+ reg = <0x0 0x23110000 0x0 0x1000>;
+
+ clocks = <&soc_smc50mhz>;
+ clock-names = "apb_pclk";
+ power-domains = <&scmi_devpd 8>;
+ };
+
etm3: etm@23140000 {
compatible = "arm,coresight-etm4x", "arm,primecell";
reg = <0 0x23140000 0 0x1000>;
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
cluster1_etm1_out_port: endpoint {
remote-endpoint = <&cluster1_funnel_in_port1>;
@@ -345,13 +381,22 @@
};
};
+ cpu_debug4: cpu_debug@23210000 {
+ compatible = "arm,coresight-cpu-debug", "arm,primecell";
+ reg = <0x0 0x23210000 0x0 0x1000>;
+
+ clocks = <&soc_smc50mhz>;
+ clock-names = "apb_pclk";
+ power-domains = <&scmi_devpd 8>;
+ };
+
etm4: etm@23240000 {
compatible = "arm,coresight-etm4x", "arm,primecell";
reg = <0 0x23240000 0 0x1000>;
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
cluster1_etm2_out_port: endpoint {
remote-endpoint = <&cluster1_funnel_in_port2>;
@@ -359,13 +404,22 @@
};
};
+ cpu_debug5: cpu_debug@23310000 {
+ compatible = "arm,coresight-cpu-debug", "arm,primecell";
+ reg = <0x0 0x23310000 0x0 0x1000>;
+
+ clocks = <&soc_smc50mhz>;
+ clock-names = "apb_pclk";
+ power-domains = <&scmi_devpd 8>;
+ };
+
etm5: etm@23340000 {
compatible = "arm,coresight-etm4x", "arm,primecell";
reg = <0 0x23340000 0 0x1000>;
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
port {
cluster1_etm3_out_port: endpoint {
remote-endpoint = <&cluster1_funnel_in_port3>;
@@ -379,7 +433,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
ports {
#address-cells = <1>;
@@ -420,13 +474,14 @@
cpu_scp_lpri: scp-shmem@0 {
compatible = "arm,juno-scp-shmem";
- reg = <0x0 0x200>;
+ reg = <0x0 0x80>;
};
cpu_scp_hpri: scp-shmem@200 {
compatible = "arm,juno-scp-shmem";
- reg = <0x200 0x200>;
+ reg = <0x100 0x80>;
};
+
};
pcie_ctlr: pcie@40000000 {
@@ -453,36 +508,34 @@
iommu-map = <0x0 &smmu_pcie 0x0 0x1>;
};
- scpi {
- compatible = "arm,scpi";
- mboxes = <&mailbox 1>;
- shmem = <&cpu_scp_hpri>;
+ scmi {
+ compatible = "arm,scmi";
+ method = "mailbox-doorbell";
+ mbox-names = "tx";
+ mboxes = <&mailbox 0 0>;
+ shmem = <&cpu_scp_lpri>;
+ #address-cells = <1>;
+ #size-cells = <0>;
- clocks {
- compatible = "arm,scpi-clocks";
+ scmi_devpd: protocol@11 {
+ reg = <0x11>;
+ #power-domain-cells = <1>;
+ };
- scpi_dvfs: scpi-dvfs {
- compatible = "arm,scpi-dvfs-clocks";
- #clock-cells = <1>;
- clock-indices = <0>, <1>, <2>;
- clock-output-names = "atlclk", "aplclk","clk_mali";
- };
- scpi_clk: scpi-clk {
- compatible = "arm,scpi-variable-clocks";
- #clock-cells = <1>;
- clock-indices = <3>, <4>, <5>;
- clock-output-names = "pxlclk", "pxlclk1", "i2sclk";
- };
+ scmi_dvfs: protocol@13 {
+ reg = <0x13>;
+ #clock-cells = <1>;
+ mboxes = <&mailbox 1 0>;
+ shmem = <&cpu_scp_hpri>;
};
- scpi_devpd: scpi-power-domains {
- compatible = "arm,scpi-power-domains";
- num-domains = <2>;
- #power-domain-cells = <1>;
+ scmi_clk: protocol@14 {
+ reg = <0x14>;
+ #clock-cells = <1>;
};
- scpi_sensors0: sensors {
- compatible = "arm,scpi-sensors";
+ scmi_sensors0: protocol@15 {
+ reg = <0x15>;
#thermal-sensor-cells = <1>;
};
};
@@ -491,40 +544,40 @@
pmic {
polling-delay = <1000>;
polling-delay-passive = <100>;
- thermal-sensors = <&scpi_sensors0 0>;
+ thermal-sensors = <&scmi_sensors0 0>;
};
soc {
polling-delay = <1000>;
polling-delay-passive = <100>;
- thermal-sensors = <&scpi_sensors0 3>;
+ thermal-sensors = <&scmi_sensors0 3>;
};
big_cluster_thermal_zone: big_cluster {
polling-delay = <1000>;
polling-delay-passive = <100>;
- thermal-sensors = <&scpi_sensors0 21>;
+ thermal-sensors = <&scmi_sensors0 21>;
status = "disabled";
};
little_cluster_thermal_zone: little_cluster {
polling-delay = <1000>;
polling-delay-passive = <100>;
- thermal-sensors = <&scpi_sensors0 22>;
+ thermal-sensors = <&scmi_sensors0 22>;
status = "disabled";
};
gpu0_thermal_zone: gpu0 {
polling-delay = <1000>;
polling-delay-passive = <100>;
- thermal-sensors = <&scpi_sensors0 23>;
+ thermal-sensors = <&scmi_sensors0 23>;
status = "disabled";
};
gpu1_thermal_zone: gpu1 {
polling-delay = <1000>;
polling-delay-passive = <100>;
- thermal-sensors = <&scpi_sensors0 24>;
+ thermal-sensors = <&scmi_sensors0 24>;
status = "disabled";
};
};
@@ -547,7 +600,6 @@
<GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>;
#iommu-cells = <1>;
#global-interrupts = <1>;
- status = "disabled";
};
smmu_hdlcd0: iommu@7fb20000 {
@@ -557,7 +609,6 @@
<GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
#iommu-cells = <1>;
#global-interrupts = <1>;
- status = "disabled";
};
smmu_usb: iommu@7fb30000 {
@@ -568,7 +619,6 @@
#iommu-cells = <1>;
#global-interrupts = <1>;
dma-coherent;
- status = "disabled";
};
dma0: dma@7ff00000 {
@@ -604,7 +654,7 @@
reg = <0 0x7ff50000 0 0x1000>;
interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
iommus = <&smmu_hdlcd1 0>;
- clocks = <&scpi_clk 3>;
+ clocks = <&scmi_clk 3>;
clock-names = "pxlclk";
port {
@@ -619,7 +669,7 @@
reg = <0 0x7ff60000 0 0x1000>;
interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
iommus = <&smmu_hdlcd0 0>;
- clocks = <&scpi_clk 3>;
+ clocks = <&scmi_clk 3>;
clock-names = "pxlclk";
port {
@@ -705,7 +755,7 @@
soc_i2s: i2s@7ff90000 {
compatible = "snps,designware-i2s";
reg = <0x0 0x7ff90000 0x0 0x1000>;
- clocks = <&scpi_clk 5>, <&soc_refclk100mhz>;
+ clocks = <&scmi_clk 5>, <&soc_refclk100mhz>;
clock-names = "i2sclk", "apb_pclk";
#sound-dai-cells = <0>;
dmas = <&dma0 5>;
diff --git a/arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi b/arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi
index aa03050dd7df..3d0912a4fbf9 100644
--- a/arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi
+++ b/arch/arm64/boot/dts/arm/juno-cs-r1r2.dtsi
@@ -5,7 +5,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
ports {
#address-cells = <1>;
#size-cells = <0>;
@@ -35,7 +35,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
ports {
#address-cells = <1>;
#size-cells = <0>;
@@ -65,7 +65,7 @@
clocks = <&soc_smc50mhz>;
clock-names = "apb_pclk";
- power-domains = <&scpi_devpd 0>;
+ power-domains = <&scmi_devpd 8>;
ports {
#address-cells = <1>;
#size-cells = <0>;
diff --git a/arch/arm64/boot/dts/arm/juno-r1.dts b/arch/arm64/boot/dts/arm/juno-r1.dts
index 88c83d1d7076..8b2501e73cbe 100644
--- a/arch/arm64/boot/dts/arm/juno-r1.dts
+++ b/arch/arm64/boot/dts/arm/juno-r1.dts
@@ -96,7 +96,7 @@
d-cache-line-size = <64>;
d-cache-sets = <256>;
next-level-cache = <&A57_L2>;
- clocks = <&scpi_dvfs 0>;
+ clocks = <&scmi_dvfs 0>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <1024>;
};
@@ -113,7 +113,7 @@
d-cache-line-size = <64>;
d-cache-sets = <256>;
next-level-cache = <&A57_L2>;
- clocks = <&scpi_dvfs 0>;
+ clocks = <&scmi_dvfs 0>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <1024>;
};
@@ -130,7 +130,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <578>;
};
@@ -147,7 +147,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <578>;
};
@@ -164,7 +164,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <578>;
};
@@ -181,7 +181,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <578>;
};
@@ -281,3 +281,27 @@
&stm_out_port {
remote-endpoint = <&csys1_funnel_in_port0>;
};
+
+&cpu_debug0 {
+ cpu = <&A57_0>;
+};
+
+&cpu_debug1 {
+ cpu = <&A57_1>;
+};
+
+&cpu_debug2 {
+ cpu = <&A53_0>;
+};
+
+&cpu_debug3 {
+ cpu = <&A53_1>;
+};
+
+&cpu_debug4 {
+ cpu = <&A53_2>;
+};
+
+&cpu_debug5 {
+ cpu = <&A53_3>;
+};
diff --git a/arch/arm64/boot/dts/arm/juno-r2.dts b/arch/arm64/boot/dts/arm/juno-r2.dts
index 5c194b4c830a..dab2e49befef 100644
--- a/arch/arm64/boot/dts/arm/juno-r2.dts
+++ b/arch/arm64/boot/dts/arm/juno-r2.dts
@@ -96,7 +96,7 @@
d-cache-line-size = <64>;
d-cache-sets = <256>;
next-level-cache = <&A72_L2>;
- clocks = <&scpi_dvfs 0>;
+ clocks = <&scmi_dvfs 0>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <1024>;
};
@@ -113,7 +113,7 @@
d-cache-line-size = <64>;
d-cache-sets = <256>;
next-level-cache = <&A72_L2>;
- clocks = <&scpi_dvfs 0>;
+ clocks = <&scmi_dvfs 0>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <1024>;
};
@@ -130,7 +130,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <485>;
};
@@ -147,7 +147,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <485>;
};
@@ -164,7 +164,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <485>;
};
@@ -181,7 +181,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <485>;
};
@@ -281,3 +281,27 @@
&stm_out_port {
remote-endpoint = <&csys1_funnel_in_port0>;
};
+
+&cpu_debug0 {
+ cpu = <&A72_0>;
+};
+
+&cpu_debug1 {
+ cpu = <&A72_1>;
+};
+
+&cpu_debug2 {
+ cpu = <&A53_0>;
+};
+
+&cpu_debug3 {
+ cpu = <&A53_1>;
+};
+
+&cpu_debug4 {
+ cpu = <&A53_2>;
+};
+
+&cpu_debug5 {
+ cpu = <&A53_3>;
+};
diff --git a/arch/arm64/boot/dts/arm/juno.dts b/arch/arm64/boot/dts/arm/juno.dts
index 69aa396a8aad..1d05029687ff 100644
--- a/arch/arm64/boot/dts/arm/juno.dts
+++ b/arch/arm64/boot/dts/arm/juno.dts
@@ -95,7 +95,7 @@
d-cache-line-size = <64>;
d-cache-sets = <256>;
next-level-cache = <&A57_L2>;
- clocks = <&scpi_dvfs 0>;
+ clocks = <&scmi_dvfs 0>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <1024>;
};
@@ -112,7 +112,7 @@
d-cache-line-size = <64>;
d-cache-sets = <256>;
next-level-cache = <&A57_L2>;
- clocks = <&scpi_dvfs 0>;
+ clocks = <&scmi_dvfs 0>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <1024>;
};
@@ -129,7 +129,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <578>;
};
@@ -146,7 +146,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <578>;
};
@@ -163,7 +163,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <578>;
};
@@ -180,7 +180,7 @@
d-cache-line-size = <64>;
d-cache-sets = <128>;
next-level-cache = <&A53_L2>;
- clocks = <&scpi_dvfs 1>;
+ clocks = <&scmi_dvfs 1>;
cpu-idle-states = <&CPU_SLEEP_0 &CLUSTER_SLEEP_0>;
capacity-dmips-mhz = <578>;
};
@@ -268,3 +268,27 @@
};
};
};
+
+&cpu_debug0 {
+ cpu = <&A57_0>;
+};
+
+&cpu_debug1 {
+ cpu = <&A57_1>;
+};
+
+&cpu_debug2 {
+ cpu = <&A53_0>;
+};
+
+&cpu_debug3 {
+ cpu = <&A53_1>;
+};
+
+&cpu_debug4 {
+ cpu = <&A53_2>;
+};
+
+&cpu_debug5 {
+ cpu = <&A53_3>;
+};
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 97c123e09e45..7e9606d0e383 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -87,8 +87,15 @@ CONFIG_COMPAT=y
CONFIG_HIBERNATION=y
CONFIG_ARM_CPUIDLE=y
CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=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_CPU_FREQ_GOV_SCHEDUTIL=y
CONFIG_CPUFREQ_DT=y
CONFIG_ARM_BIG_LITTLE_CPUFREQ=y
+CONFIG_ARM_SCMI_CPUFREQ=y
CONFIG_ARM_SCPI_CPUFREQ=y
CONFIG_NET=y
CONFIG_PACKET=y
@@ -289,6 +296,7 @@ CONFIG_POWER_RESET_MSM=y
CONFIG_POWER_RESET_XGENE=y
CONFIG_POWER_RESET_SYSCON=y
CONFIG_BATTERY_BQ27XXX=y
+CONFIG_SENSORS_ARM_SCMI=y
CONFIG_SENSORS_ARM_SCPI=y
CONFIG_SENSORS_LM90=m
CONFIG_SENSORS_INA2XX=m
@@ -450,6 +458,7 @@ CONFIG_VIRTIO_MMIO=y
CONFIG_XEN_GNTDEV=y
CONFIG_XEN_GRANT_DEV_ALLOC=y
CONFIG_COMMON_CLK_RK808=y
+CONFIG_COMMON_CLK_SCMI=y
CONFIG_COMMON_CLK_SCPI=y
CONFIG_COMMON_CLK_CS2000_CP=y
CONFIG_COMMON_CLK_S2MPS11=y
@@ -496,6 +505,7 @@ CONFIG_PHY_ROCKCHIP_EMMC=y
CONFIG_PHY_ROCKCHIP_PCIE=m
CONFIG_PHY_XGENE=y
CONFIG_PHY_TEGRA_XUSB=y
+CONFIG_ARM_SCMI_PROTOCOL=y
CONFIG_ARM_SCPI_PROTOCOL=y
CONFIG_RASPBERRYPI_FIRMWARE=y
CONFIG_ACPI=y
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 36cfea38135f..69e72df104d1 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -55,6 +55,16 @@ config COMMON_CLK_HI655X
multi-function device has one fixed-rate oscillator, clocked
at 32KHz.
+config COMMON_CLK_SCMI
+ tristate "Clock driver controlled via SCMI interface"
+ depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
+ ---help---
+ This driver provides support for clocks that are controlled
+ by firmware that implements the SCMI interface.
+
+ This driver uses SCMI Message Protocol to interact with the
+ firmware providing all the clock controls.
+
config COMMON_CLK_SCPI
tristate "Clock driver controlled via SCPI interface"
depends on ARM_SCPI_PROTOCOL || COMPILE_TEST
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index c19983afcb81..6c4cbe124e21 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o
obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o
obj-$(CONFIG_COMMON_CLK_HI655X) += clk-hi655x.o
obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
+obj-$(CONFIG_COMMON_CLK_SCMI) += clk-scmi.o
obj-$(CONFIG_COMMON_CLK_SCPI) += clk-scpi.o
obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o
diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
new file mode 100644
index 000000000000..37f98a6439a0
--- /dev/null
+++ b/drivers/clk/clk-scmi.c
@@ -0,0 +1,216 @@
+/*
+ * System Control and Power Interface (SCMI) Protocol based clock driver
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/scmi_protocol.h>
+
+struct scmi_clk {
+ u32 id;
+ struct clk_hw hw;
+ const struct scmi_clock_info *info;
+ const struct scmi_handle *handle;
+};
+
+#define to_scmi_clk(clk) container_of(clk, struct scmi_clk, hw)
+
+static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ int ret;
+ u64 rate;
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ ret = clk->handle->clk_ops->rate_get(clk->handle, clk->id, &rate);
+ if (ret)
+ return 0;
+ return rate;
+}
+
+static long scmi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ u64 fmin, fmax, ftmp;
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ /*
+ * We can't figure out what rate it will be, so just return the
+ * rate back to the caller. scmi_clk_recalc_rate() will be called
+ * after the rate is set and we'll know what rate the clock is
+ * running at then.
+ */
+ if (clk->info->rate_discrete)
+ return rate;
+
+ fmin = clk->info->range.min_rate;
+ fmax = clk->info->range.max_rate;
+ for (ftmp = fmin; ftmp <= fmax; ftmp += clk->info->range.step_size) {
+ if (ftmp >= rate) {
+ if (ftmp <= fmax)
+ fmax = ftmp;
+ break;
+ } else if (ftmp >= fmin) {
+ fmin = ftmp;
+ }
+ }
+ return fmax != clk->info->range.max_rate ? fmax : fmin;
+}
+
+static int scmi_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ return clk->handle->clk_ops->rate_set(clk->handle, clk->id, 0, rate);
+}
+
+static int scmi_clk_enable(struct clk_hw *hw)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ return clk->handle->clk_ops->enable(clk->handle, clk->id);
+}
+
+static void scmi_clk_disable(struct clk_hw *hw)
+{
+ struct scmi_clk *clk = to_scmi_clk(hw);
+
+ clk->handle->clk_ops->disable(clk->handle, clk->id);
+}
+
+static const struct clk_ops scmi_clk_ops = {
+ .recalc_rate = scmi_clk_recalc_rate,
+ .round_rate = scmi_clk_round_rate,
+ .set_rate = scmi_clk_set_rate,
+ /*
+ * We can't provide enable/disable callback as we can't perform the same
+ * in atomic context. Since the clock framework provides standard API
+ * clk_prepare_enable that helps cases using clk_enable in non-atomic
+ * context, it should be fine providing prepare/unprepare.
+ */
+ .prepare = scmi_clk_enable,
+ .unprepare = scmi_clk_disable,
+};
+
+static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk)
+{
+ int ret;
+ struct clk_init_data init;
+
+ init.flags = CLK_GET_RATE_NOCACHE;
+ init.num_parents = 0;
+ init.ops = &scmi_clk_ops;
+ init.name = sclk->info->name;
+ sclk->hw.init = &init;
+
+ ret = devm_clk_hw_register(dev, &sclk->hw);
+ if (!ret)
+ clk_hw_set_rate_range(&sclk->hw, sclk->info->range.min_rate,
+ sclk->info->range.max_rate);
+ return ret;
+}
+
+static int scmi_clk_add(struct device *dev, struct device_node *np,
+ const struct scmi_handle *handle)
+{
+ int idx, count, err;
+ struct clk_hw **hws;
+ struct clk_hw_onecell_data *clk_data;
+
+ count = handle->clk_ops->count_get(handle);
+ if (count < 0) {
+ dev_err(dev, "%s: invalid clock output count\n", np->name);
+ return -EINVAL;
+ }
+
+ clk_data = devm_kzalloc(dev, sizeof(*clk_data) +
+ sizeof(*clk_data->hws) * count, GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ clk_data->num = count;
+ hws = clk_data->hws;
+
+ for (idx = 0; idx < count; idx++) {
+ struct scmi_clk *sclk;
+
+ sclk = devm_kzalloc(dev, sizeof(*sclk), GFP_KERNEL);
+ if (!sclk)
+ return -ENOMEM;
+
+ sclk->info = handle->clk_ops->info_get(handle, idx);
+ if (!sclk->info) {
+ dev_dbg(dev, "invalid clock info for idx %d\n", idx);
+ continue;
+ }
+
+ sclk->id = idx;
+ sclk->handle = handle;
+
+ err = scmi_clk_ops_init(dev, sclk);
+ if (err) {
+ dev_err(dev, "failed to register clock %d\n", idx);
+ devm_kfree(dev, sclk);
+ hws[idx] = NULL;
+ } else {
+ dev_dbg(dev, "Registered clock:%s\n", sclk->info->name);
+ hws[idx] = &sclk->hw;
+ }
+ }
+
+ return of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data);
+}
+
+static int scmi_clocks_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ of_clk_del_provider(np);
+ return 0;
+}
+
+static int scmi_clocks_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ const struct scmi_handle *handle = devm_scmi_handle_get(dev);
+
+ if (IS_ERR_OR_NULL(handle) || !handle->clk_ops)
+ return -EPROBE_DEFER;
+
+ return scmi_clk_add(dev, np, handle);
+}
+
+static struct platform_driver scmi_clocks_driver = {
+ .driver = {
+ .name = "scmi-clocks",
+ },
+ .probe = scmi_clocks_probe,
+ .remove = scmi_clocks_remove,
+};
+module_platform_driver(scmi_clocks_driver);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI clock driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm
index 2011fec2d6ad..c34633855bc7 100644
--- a/drivers/cpufreq/Kconfig.arm
+++ b/drivers/cpufreq/Kconfig.arm
@@ -215,6 +215,17 @@ config ARM_SA1100_CPUFREQ
config ARM_SA1110_CPUFREQ
bool
+config ARM_SCMI_CPUFREQ
+ tristate "SCMI based CPUfreq driver"
+ depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
+ select PM_OPP
+ help
+ This adds the CPUfreq driver support for ARM platforms using SCMI
+ protocol for CPU power management.
+
+ This driver uses SCMI Message Protocol driver to interact with the
+ firmware providing the CPU DVFS functionality.
+
config ARM_SCPI_CPUFREQ
tristate "SCPI based CPUfreq driver"
depends on ARM_BIG_LITTLE_CPUFREQ && ARM_SCPI_PROTOCOL && COMMON_CLK_SCPI
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index ab3a42cd29ef..4810b45568d3 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o
obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o
+obj-$(CONFIG_ARM_SCMI_CPUFREQ) += scmi-cpufreq.o
obj-$(CONFIG_ARM_SCPI_CPUFREQ) += scpi-cpufreq.o
obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o
obj-$(CONFIG_ARM_STI_CPUFREQ) += sti-cpufreq.o
diff --git a/drivers/cpufreq/scmi-cpufreq.c b/drivers/cpufreq/scmi-cpufreq.c
new file mode 100644
index 000000000000..259df4470880
--- /dev/null
+++ b/drivers/cpufreq/scmi-cpufreq.c
@@ -0,0 +1,268 @@
+/*
+ * System Control and Power Interface (SCMI) based CPUFreq Interface driver
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ * Sudeep Holla <sudeep.holla@arm.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 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cpu.h>
+#include <linux/cpufreq.h>
+#include <linux/cpumask.h>
+#include <linux/cpu_cooling.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/scmi_protocol.h>
+#include <linux/types.h>
+
+struct scmi_data {
+ int domain_id;
+ struct device *cpu_dev;
+ struct thermal_cooling_device *cdev;
+ const struct scmi_handle *handle;
+};
+
+static const struct scmi_handle *handle;
+
+unsigned int scmi_cpufreq_get_rate(unsigned int cpu)
+{
+ int ret;
+ unsigned long rate;
+ struct cpufreq_policy *policy = cpufreq_cpu_get_raw(cpu);
+ struct scmi_data *priv = policy->driver_data;
+ struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
+
+ ret = perf_ops->freq_get(priv->handle, priv->domain_id, &rate);
+ if (ret)
+ return 0;
+ return rate / 1000;
+}
+
+static int
+scmi_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int index)
+{
+ struct scmi_data *priv = policy->driver_data;
+ struct scmi_perf_ops *perf_ops = priv->handle->perf_ops;
+
+ return perf_ops->freq_set(priv->handle, priv->domain_id,
+ policy->freq_table[index].frequency * 1000);
+}
+
+static int
+scmi_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask)
+{
+ int cpu, domain, ret = 0;
+ struct device *tcpu_dev;
+
+ domain = handle->perf_ops->device_domain_id(cpu_dev);
+ if (domain < 0)
+ return domain;
+
+ cpumask_set_cpu(cpu_dev->id, cpumask);
+
+ for_each_possible_cpu(cpu) {
+ if (cpu == cpu_dev->id)
+ continue;
+
+ tcpu_dev = get_cpu_device(cpu);
+ if (!tcpu_dev)
+ continue;
+
+ ret = handle->perf_ops->device_domain_id(tcpu_dev);
+ if (ret == domain)
+ cpumask_set_cpu(cpu, cpumask);
+ }
+
+ return 0;
+}
+
+static int scmi_cpufreq_init(struct cpufreq_policy *policy)
+{
+ int ret;
+ unsigned int latency;
+ struct device *cpu_dev;
+ struct scmi_data *priv;
+ struct cpufreq_frequency_table *freq_table;
+
+ cpu_dev = get_cpu_device(policy->cpu);
+ if (!cpu_dev) {
+ pr_err("failed to get cpu%d device\n", policy->cpu);
+ return -ENODEV;
+ }
+
+ ret = handle->perf_ops->add_opps_to_device(cpu_dev);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to add opps to the device\n");
+ return ret;
+ }
+
+ ret = scmi_get_sharing_cpus(cpu_dev, policy->cpus);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to get sharing cpumask\n");
+ return ret;
+ }
+
+ ret = dev_pm_opp_set_sharing_cpus(cpu_dev, policy->cpus);
+ if (ret) {
+ dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /*
+ * But we need OPP table to function so if it is not there let's
+ * give platform code chance to provide it for us.
+ */
+ ret = dev_pm_opp_get_opp_count(cpu_dev);
+ if (ret <= 0) {
+ dev_dbg(cpu_dev, "OPP table is not ready, deferring probe\n");
+ ret = -EPROBE_DEFER;
+ goto out_free_opp;
+ }
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto out_free_opp;
+ }
+
+ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
+ goto out_free_priv;
+ }
+
+ priv->handle = handle;
+ priv->cpu_dev = cpu_dev;
+ priv->domain_id = handle->perf_ops->device_domain_id(cpu_dev);
+
+ policy->driver_data = priv;
+
+ ret = cpufreq_table_validate_and_show(policy, freq_table);
+ if (ret) {
+ dev_err(cpu_dev, "%s: invalid frequency table: %d\n", __func__,
+ ret);
+ goto out_free_cpufreq_table;
+ }
+
+ latency = handle->perf_ops->get_transition_latency(cpu_dev);
+ if (!latency)
+ latency = CPUFREQ_ETERNAL;
+
+ policy->cpuinfo.transition_latency = latency;
+
+ return 0;
+
+out_free_cpufreq_table:
+ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
+out_free_priv:
+ kfree(priv);
+out_free_opp:
+ dev_pm_opp_cpumask_remove_table(policy->cpus);
+
+ return ret;
+}
+
+static int scmi_cpufreq_exit(struct cpufreq_policy *policy)
+{
+ struct scmi_data *priv = policy->driver_data;
+
+ cpufreq_cooling_unregister(priv->cdev);
+ dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
+ dev_pm_opp_cpumask_remove_table(policy->related_cpus);
+ kfree(priv);
+
+ return 0;
+}
+
+static void scmi_cpufreq_ready(struct cpufreq_policy *policy)
+{
+ struct scmi_data *priv = policy->driver_data;
+ struct device_node *np = of_node_get(priv->cpu_dev->of_node);
+
+ if (WARN_ON(!np))
+ return;
+
+ if (of_find_property(np, "#cooling-cells", NULL)) {
+ u32 power_coefficient = 0;
+
+ of_property_read_u32(np, "dynamic-power-coefficient",
+ &power_coefficient);
+
+ priv->cdev = of_cpufreq_power_cooling_register(np,
+ policy->related_cpus, power_coefficient, NULL);
+ if (IS_ERR(priv->cdev)) {
+ dev_err(priv->cpu_dev,
+ "running cpufreq without cooling device: %ld\n",
+ PTR_ERR(priv->cdev));
+
+ priv->cdev = NULL;
+ }
+ }
+
+ of_node_put(np);
+}
+
+static struct cpufreq_driver scmi_cpufreq_driver = {
+ .name = "scmi",
+ .flags = CPUFREQ_STICKY |
+ CPUFREQ_HAVE_GOVERNOR_PER_POLICY |
+ CPUFREQ_NEED_INITIAL_FREQ_CHECK,
+ .verify = cpufreq_generic_frequency_table_verify,
+ .attr = cpufreq_generic_attr,
+ .target_index = scmi_cpufreq_set_target,
+ .get = scmi_cpufreq_get_rate,
+ .init = scmi_cpufreq_init,
+ .exit = scmi_cpufreq_exit,
+ .ready = scmi_cpufreq_ready,
+};
+
+static int scmi_cpufreq_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ handle = devm_scmi_handle_get(&pdev->dev);
+
+ if (IS_ERR_OR_NULL(handle) || !handle->perf_ops)
+ return -EPROBE_DEFER;
+
+ ret = cpufreq_register_driver(&scmi_cpufreq_driver);
+ if (ret) {
+ dev_err(&pdev->dev, "%s: registering cpufreq failed, err: %d\n",
+ __func__, ret);
+ }
+
+ return ret;
+}
+
+static int scmi_cpufreq_remove(struct platform_device *pdev)
+{
+ cpufreq_unregister_driver(&scmi_cpufreq_driver);
+ return 0;
+}
+
+static struct platform_driver scmi_cpufreq_platdrv = {
+ .driver = {
+ .name = "scmi-cpufreq",
+ },
+ .probe = scmi_cpufreq_probe,
+ .remove = scmi_cpufreq_remove,
+};
+module_platform_driver(scmi_cpufreq_platdrv);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI CPUFreq interface driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/cpufreq/scpi-cpufreq.c b/drivers/cpufreq/scpi-cpufreq.c
index ea7a4e1b68c2..8de2364b5995 100644
--- a/drivers/cpufreq/scpi-cpufreq.c
+++ b/drivers/cpufreq/scpi-cpufreq.c
@@ -30,46 +30,20 @@
static struct scpi_ops *scpi_ops;
-static struct scpi_dvfs_info *scpi_get_dvfs_info(struct device *cpu_dev)
-{
- int domain = topology_physical_package_id(cpu_dev->id);
-
- if (domain < 0)
- return ERR_PTR(-EINVAL);
- return scpi_ops->dvfs_get_info(domain);
-}
-
static int scpi_get_transition_latency(struct device *cpu_dev)
{
- struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev);
-
- if (IS_ERR(info))
- return PTR_ERR(info);
- return info->latency;
+ return scpi_ops->get_transition_latency(cpu_dev);
}
static int scpi_init_opp_table(const struct cpumask *cpumask)
{
- int idx, ret;
- struct scpi_opp *opp;
+ int ret;
struct device *cpu_dev = get_cpu_device(cpumask_first(cpumask));
- struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev);
-
- if (IS_ERR(info))
- return PTR_ERR(info);
-
- if (!info->opps)
- return -EIO;
- for (opp = info->opps, idx = 0; idx < info->count; idx++, opp++) {
- ret = dev_pm_opp_add(cpu_dev, opp->freq, opp->m_volt * 1000);
- if (ret) {
- dev_warn(cpu_dev, "failed to add opp %uHz %umV\n",
- opp->freq, opp->m_volt);
- while (idx-- > 0)
- dev_pm_opp_remove(cpu_dev, (--opp)->freq);
- return ret;
- }
+ ret = scpi_ops->add_opps_to_device(cpu_dev);
+ if (ret) {
+ dev_warn(cpu_dev, "failed to add opps to the device\n");
+ return ret;
}
ret = dev_pm_opp_set_sharing_cpus(cpu_dev, cpumask);
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index 33e72ac4fa3b..6d29f991ca33 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -19,6 +19,40 @@ config ARM_PSCI_CHECKER
on and off through hotplug, so for now torture tests and PSCI checker
are mutually exclusive.
+config ARM_SCMI_PROTOCOL
+ tristate "ARM System Control and Management Interface (SCMI) Message Protocol"
+ depends on ARM || ARM64 || COMPILE_TEST
+ depends on MAILBOX
+ help
+ ARM System Control and Management Interface (SCMI) protocol is a
+ set of operating system-independent software interfaces that are
+ used in system management. SCMI is extensible and currently provides
+ interfaces for: Discovery and self-description of the interfaces
+ it supports, Power domain management which is the ability to place
+ a given device or domain into the various power-saving states that
+ it supports, Performance management which is the ability to control
+ the performance of a domain that is composed of compute engines
+ such as application processors and other accelerators, Clock
+ management which is the ability to set and inquire rates on platform
+ managed clocks and Sensor management which is the ability to read
+ sensor data, and be notified of sensor value.
+
+ This protocol library provides interface for all the client drivers
+ making use of the features offered by the SCMI.
+
+config ARM_SCMI_POWER_DOMAIN
+ tristate "SCMI power domain driver"
+ depends on ARM_SCMI_PROTOCOL || (COMPILE_TEST && OF)
+ default y
+ select PM_GENERIC_DOMAINS if PM
+ help
+ This enables support for the SCMI power domains which can be
+ enabled or disabled via the SCP firmware
+
+ This driver can also be built as a module. If so, the module
+ will be called scmi_pm_domain. Note this may needed early in boot
+ before rootfs may be available.
+
config ARM_SCPI_PROTOCOL
tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
depends on ARM || ARM64 || COMPILE_TEST
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index f06daf68e68a..102b722e2404 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_QCOM_SCM_32) += qcom_scm-32.o
CFLAGS_qcom_scm-32.o :=$(call as-instr,.arch armv7-a\n.arch_extension sec,-DREQUIRES_SEC=1) -march=armv7-a
obj-$(CONFIG_TI_SCI_PROTOCOL) += ti_sci.o
+obj-$(CONFIG_ARM_SCMI_PROTOCOL) += arm_scmi/
obj-y += broadcom/
obj-y += meson/
obj-$(CONFIG_GOOGLE_FIRMWARE) += google/
diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile
new file mode 100644
index 000000000000..93e399d8f0ce
--- /dev/null
+++ b/drivers/firmware/arm_scmi/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_ARM_SCMI_PROTOCOL) = arm_scmi.o
+arm_scmi-y = base.o clock.o driver.o perf.o power.o sensors.o
+obj-$(CONFIG_ARM_SCMI_POWER_DOMAIN) += scmi_pm_domain.o
diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c
new file mode 100644
index 000000000000..9bfffbe95c21
--- /dev/null
+++ b/drivers/firmware/arm_scmi/base.c
@@ -0,0 +1,293 @@
+/*
+ * System Control and Management Interface (SCMI) Base Protocol
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+
+enum scmi_base_protocol_cmd {
+ BASE_DISCOVER_VENDOR = 0x3,
+ BASE_DISCOVER_SUB_VENDOR = 0x4,
+ BASE_DISCOVER_IMPLEMENT_VERSION = 0x5,
+ BASE_DISCOVER_LIST_PROTOCOLS = 0x6,
+ BASE_DISCOVER_AGENT = 0x7,
+ BASE_NOTIFY_ERRORS = 0x8,
+};
+
+struct scmi_msg_resp_base_attributes {
+ u8 num_protocols;
+ u8 num_agents;
+ __le16 reserved;
+};
+
+/**
+ * scmi_base_attributes_get() - gets the implementation details
+ * that are associated with the base protocol.
+ *
+ * @handle - SCMI entity handle
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_attributes_get(const struct scmi_handle *handle)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_base_attributes *attr_info;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_BASE, 0, sizeof(*attr_info), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ attr_info = t->rx.buf;
+ rev->num_protocols = attr_info->num_protocols;
+ rev->num_agents = attr_info->num_agents;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_vendor_id_get() - gets vendor/subvendor identifier ASCII string.
+ *
+ * @handle - SCMI entity handle
+ * @sub_vendor - specify true if sub-vendor ID is needed
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int
+scmi_base_vendor_id_get(const struct scmi_handle *handle, bool sub_vendor)
+{
+ u8 cmd;
+ int ret, size;
+ char *vendor_id;
+ struct scmi_xfer *t;
+ struct scmi_revision_info *rev = handle->version;
+
+ if (sub_vendor) {
+ cmd = BASE_DISCOVER_SUB_VENDOR;
+ vendor_id = rev->sub_vendor_id;
+ size = ARRAY_SIZE(rev->sub_vendor_id);
+ } else {
+ cmd = BASE_DISCOVER_VENDOR;
+ vendor_id = rev->vendor_id;
+ size = ARRAY_SIZE(rev->vendor_id);
+ }
+
+ ret = scmi_one_xfer_init(handle, cmd, SCMI_PROTOCOL_BASE, 0, size, &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(vendor_id, t->rx.buf, size);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_implementation_version_get() - gets a vendor-specific
+ * implementation 32-bit version. The format of the version number is
+ * vendor-specific
+ *
+ * @handle - SCMI entity handle
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int
+scmi_base_implementation_version_get(const struct scmi_handle *handle)
+{
+ int ret;
+ u32 *impl_ver;
+ struct scmi_xfer *t;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_IMPLEMENT_VERSION,
+ SCMI_PROTOCOL_BASE, 0, sizeof(*impl_ver), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ impl_ver = t->rx.buf;
+ rev->impl_ver = le32_to_cpu(*impl_ver);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_implementation_list_get() - gets the list of protocols it is
+ * OSPM is allowed to access
+ *
+ * @handle - SCMI entity handle
+ * @protocols_imp - pointer to hold the list of protocol identifiers
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_implementation_list_get(const struct scmi_handle *handle,
+ u8 *protocols_imp)
+{
+ u8 *list;
+ int ret, loop;
+ struct scmi_xfer *t;
+ __le32 *num_skip, *num_ret;
+ u32 tot_num_ret = 0, loop_num_ret;
+ struct device *dev = handle->dev;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_LIST_PROTOCOLS,
+ SCMI_PROTOCOL_BASE, sizeof(*num_skip), 0, &t);
+ if (ret)
+ return ret;
+
+ num_skip = t->tx.buf;
+ num_ret = t->rx.buf;
+ list = t->rx.buf + sizeof(*num_ret);
+
+ do {
+ /* Set the number of protocols to be skipped/already read */
+ *num_skip = cpu_to_le32(tot_num_ret);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ loop_num_ret = le32_to_cpu(*num_ret);
+ if (tot_num_ret + loop_num_ret > MAX_PROTOCOLS_IMP) {
+ dev_err(dev, "No. of Protocol > MAX_PROTOCOLS_IMP");
+ break;
+ }
+
+ for (loop = 0; loop < loop_num_ret; loop++)
+ protocols_imp[tot_num_ret + loop] = *(list + loop);
+
+ tot_num_ret += loop_num_ret;
+ } while (loop_num_ret);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_discover_agent_get() - discover the name of an agent
+ *
+ * @handle - SCMI entity handle
+ * @id - Agent identifier
+ * @name - Agent identifier ASCII string
+ *
+ * An agent id of 0 is reserved to identify the platform itself.
+ * Generally operating system is represented as "OSPM"
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int scmi_base_discover_agent_get(const struct scmi_handle *handle,
+ int id, char *name)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, BASE_DISCOVER_AGENT,
+ SCMI_PROTOCOL_BASE, sizeof(__le32),
+ SCMI_MAX_STR_SIZE, &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(id);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(name, t->rx.buf, SCMI_MAX_STR_SIZE);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+/**
+ * scmi_base_error_notifications_enable() - register/unregister for
+ * notifications of errors in the platform
+ *
+ * @handle - SCMI entity handle
+ * @enable - Enable/Disable the notification
+ *
+ * Return: 0 on success, else appropriate SCMI error.
+ */
+static int
+scmi_base_error_notifications_enable(const struct scmi_handle *handle, bool en)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, BASE_NOTIFY_ERRORS, SCMI_PROTOCOL_BASE,
+ sizeof(__le32), 0, &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(en & BIT(0));
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+int scmi_base_protocol_init(struct scmi_handle *h)
+{
+ int id, ret;
+ u8 *prot_imp;
+ u32 version;
+ char name[SCMI_MAX_STR_SIZE];
+ const struct scmi_handle *handle = h;
+ struct device *dev = handle->dev;
+ struct scmi_revision_info *rev = handle->version;
+
+ ret = scmi_version_get(handle, SCMI_PROTOCOL_BASE, &version);
+ if (ret)
+ return ret;
+
+ prot_imp = devm_kcalloc(dev, MAX_PROTOCOLS_IMP, sizeof(u8), GFP_KERNEL);
+ if (!prot_imp)
+ return -ENOMEM;
+
+ rev->major_ver = PROTOCOL_REV_MAJOR(version),
+ rev->minor_ver = PROTOCOL_REV_MINOR(version);
+
+ scmi_base_attributes_get(handle);
+ scmi_base_vendor_id_get(handle, false);
+ scmi_base_vendor_id_get(handle, true);
+ scmi_base_implementation_version_get(handle);
+ scmi_base_implementation_list_get(handle, prot_imp);
+ scmi_base_error_notifications_enable(handle, true);
+ scmi_setup_protocol_implemented(handle, prot_imp);
+
+ dev_info(dev, "SCMI Protocol v%d.%d '%s:%s' Firmware version 0x%x\n",
+ rev->major_ver, rev->minor_ver, rev->vendor_id,
+ rev->sub_vendor_id, rev->impl_ver);
+ dev_dbg(dev, "Found %d protocol(s) %d agent(s)\n", rev->num_protocols,
+ rev->num_agents);
+
+ for (id = 0; id < rev->num_agents; id++) {
+ scmi_base_discover_agent_get(handle, id, name);
+ dev_dbg(dev, "Agent %d: %s\n", id, name);
+ }
+
+ return 0;
+}
diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c
new file mode 100644
index 000000000000..87d0befab07d
--- /dev/null
+++ b/drivers/firmware/arm_scmi/clock.c
@@ -0,0 +1,339 @@
+/*
+ * System Control and Management Interface (SCMI) Clock Protocol
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+
+enum scmi_clock_protocol_cmd {
+ CLOCK_ATTRIBUTES = 0x3,
+ CLOCK_DESCRIBE_RATES = 0x4,
+ CLOCK_RATE_SET = 0x5,
+ CLOCK_RATE_GET = 0x6,
+ CLOCK_CONFIG_SET = 0x7,
+};
+
+struct scmi_msg_resp_clock_protocol_attributes {
+ __le16 num_clocks;
+ u8 max_async_req;
+ u8 reserved;
+};
+
+struct scmi_msg_resp_clock_attributes {
+ __le32 attributes;
+#define CLOCK_ENABLE BIT(0)
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_clock_set_config {
+ __le32 id;
+ __le32 attributes;
+};
+
+struct scmi_msg_clock_describe_rates {
+ __le32 id;
+ __le32 rate_index;
+};
+
+struct scmi_msg_resp_clock_describe_rates {
+ __le32 num_rates_flags;
+#define NUM_RETURNED(x) ((x) & 0xfff)
+#define RATE_DISCRETE(x) !((x) & BIT(12))
+#define NUM_REMAINING(x) ((x) >> 16)
+ struct {
+ __le32 value_low;
+ __le32 value_high;
+ } rate[0];
+#define RATE_TO_U64(X) \
+({ \
+ typeof(X) x = (X); \
+ le32_to_cpu((x).value_low) | (u64)le32_to_cpu((x).value_high) << 32; \
+})
+};
+
+struct scmi_clock_set_rate {
+ __le32 flags;
+#define CLOCK_SET_ASYNC BIT(0)
+#define CLOCK_SET_DELAYED BIT(1)
+#define CLOCK_SET_ROUND_UP BIT(2)
+#define CLOCK_SET_ROUND_AUTO BIT(3)
+ __le32 id;
+ __le32 value_low;
+ __le32 value_high;
+};
+
+struct clock_info {
+ int num_clocks;
+ int max_async_req;
+ struct scmi_clock_info *clk;
+};
+
+static struct clock_info clocks;
+
+static int scmi_clock_protocol_attributes_get(const struct scmi_handle *handle,
+ struct clock_info *clocks)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_clock_protocol_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_CLOCK, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ clocks->num_clocks = le16_to_cpu(attr->num_clocks);
+ clocks->max_async_req = attr->max_async_req;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_attributes_get(const struct scmi_handle *handle,
+ u32 clk_id, struct scmi_clock_info *clk)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_clock_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_ATTRIBUTES, SCMI_PROTOCOL_CLOCK,
+ sizeof(clk_id), sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(clk_id);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ memcpy(clk->name, attr->name, SCMI_MAX_STR_SIZE);
+ else
+ clk->name[0] = '\0';
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_describe_rates_get(const struct scmi_handle *handle, u32 clk_id,
+ struct scmi_clock_info *clk)
+{
+ u64 *rate;
+ int ret, cnt;
+ bool rate_discrete;
+ u32 tot_rate_cnt = 0, rates_flag;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_msg_clock_describe_rates *clk_desc;
+ struct scmi_msg_resp_clock_describe_rates *rlist;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_DESCRIBE_RATES,
+ SCMI_PROTOCOL_CLOCK, sizeof(*clk_desc), 0, &t);
+ if (ret)
+ return ret;
+
+ clk_desc = t->tx.buf;
+ rlist = t->rx.buf;
+
+ do {
+ clk_desc->id = cpu_to_le32(clk_id);
+ /* Set the number of rates to be skipped/already read */
+ clk_desc->rate_index = cpu_to_le32(tot_rate_cnt);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ rates_flag = le32_to_cpu(rlist->num_rates_flags);
+ num_remaining = NUM_REMAINING(rates_flag);
+ rate_discrete = RATE_DISCRETE(rates_flag);
+ num_returned = NUM_RETURNED(rates_flag);
+
+ if (tot_rate_cnt + num_returned > SCMI_MAX_NUM_RATES) {
+ dev_err(handle->dev, "No. of rates > MAX_NUM_RATES");
+ break;
+ }
+
+ if (!rate_discrete) {
+ clk->range.min_rate = RATE_TO_U64(rlist->rate[0]);
+ clk->range.max_rate = RATE_TO_U64(rlist->rate[1]);
+ clk->range.step_size = RATE_TO_U64(rlist->rate[2]);
+ dev_dbg(handle->dev, "Min %llu Max %llu Step %llu Hz\n",
+ clk->range.min_rate, clk->range.max_rate,
+ clk->range.step_size);
+ break;
+ }
+
+ rate = &clk->list.rates[tot_rate_cnt];
+ for (cnt = 0; cnt < num_returned; cnt++, rate++) {
+ *rate = RATE_TO_U64(rlist->rate[cnt]);
+ dev_dbg(handle->dev, "Rate %llu Hz\n", *rate);
+ }
+
+ tot_rate_cnt += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ if (rate_discrete)
+ clk->list.num_rates = tot_rate_cnt;
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_rate_get(const struct scmi_handle *handle, u32 clk_id, u64 *value)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_RATE_GET, SCMI_PROTOCOL_CLOCK,
+ sizeof(__le32), sizeof(u64), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(clk_id);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ __le32 *pval = t->rx.buf;
+
+ *value = le32_to_cpu(*pval);
+ *value |= (u64)le32_to_cpu(*(pval + 1)) << 32;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_rate_set(const struct scmi_handle *handle, u32 clk_id,
+ u32 config, u64 rate)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_clock_set_rate *cfg;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_RATE_SET, SCMI_PROTOCOL_CLOCK,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->flags = cpu_to_le32(config);
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->value_low = cpu_to_le32(rate & 0xffffffff);
+ cfg->value_high = cpu_to_le32(rate >> 32);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_clock_config_set(const struct scmi_handle *handle, u32 clk_id, u32 config)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_clock_set_config *cfg;
+
+ ret = scmi_one_xfer_init(handle, CLOCK_CONFIG_SET, SCMI_PROTOCOL_CLOCK,
+ sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(clk_id);
+ cfg->attributes = cpu_to_le32(config);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_clock_enable(const struct scmi_handle *handle, u32 clk_id)
+{
+ return scmi_clock_config_set(handle, clk_id, CLOCK_ENABLE);
+}
+
+static int scmi_clock_disable(const struct scmi_handle *handle, u32 clk_id)
+{
+ return scmi_clock_config_set(handle, clk_id, 0);
+}
+
+static int scmi_clock_count_get(const struct scmi_handle *handle)
+{
+ return clocks.num_clocks;
+}
+
+static const struct scmi_clock_info *
+scmi_clock_info_get(const struct scmi_handle *handle, u32 clk_id)
+{
+ struct scmi_clock_info *clk = clocks.clk + clk_id;
+
+ if (!clk->name || !clk->name[0])
+ return NULL;
+
+ return clk;
+}
+
+static struct scmi_clk_ops clk_ops = {
+ .count_get = scmi_clock_count_get,
+ .info_get = scmi_clock_info_get,
+ .rate_get = scmi_clock_rate_get,
+ .rate_set = scmi_clock_rate_set,
+ .enable = scmi_clock_enable,
+ .disable = scmi_clock_disable,
+};
+
+int scmi_clock_protocol_init(struct scmi_handle *handle)
+{
+ int clkid, ret;
+ u32 version;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_CLOCK, &version);
+
+ dev_dbg(handle->dev, "Clock Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ scmi_clock_protocol_attributes_get(handle, &clocks);
+
+ clocks.clk = devm_kcalloc(handle->dev, clocks.num_clocks,
+ sizeof(struct scmi_clock_info), GFP_KERNEL);
+ if (!clocks.clk)
+ return -ENOMEM;
+
+ for (clkid = 0; clkid < clocks.num_clocks; clkid++) {
+ struct scmi_clock_info *clk = clocks.clk + clkid;
+
+ ret = scmi_clock_attributes_get(handle, clkid, clk);
+ if (!ret)
+ scmi_clock_describe_rates_get(handle, clkid, clk);
+ }
+
+ handle->clk_ops = &clk_ops;
+
+ return 0;
+}
diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h
new file mode 100644
index 000000000000..d7c73a8d260b
--- /dev/null
+++ b/drivers/firmware/arm_scmi/common.h
@@ -0,0 +1,126 @@
+/*
+ * System Control and Management Interface (SCMI) Message Protocol
+ * driver common header file containing some definitions, structures
+ * and function prototypes used in all the different SCMI protocols.
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/scmi_protocol.h>
+#include <linux/types.h>
+
+#define PROTOCOL_REV_MINOR_BITS 16
+#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1)
+#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS)
+#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK)
+#define MAX_PROTOCOLS_IMP 16
+#define MAX_OPPS 16
+
+enum scmi_std_protocol {
+ SCMI_PROTOCOL_BASE = 0x10,
+ SCMI_PROTOCOL_POWER = 0x11,
+ SCMI_PROTOCOL_SYSTEM = 0x12,
+ SCMI_PROTOCOL_PERF = 0x13,
+ SCMI_PROTOCOL_CLOCK = 0x14,
+ SCMI_PROTOCOL_SENSOR = 0x15,
+};
+
+enum scmi_common_cmd {
+ PROTOCOL_VERSION = 0x0,
+ PROTOCOL_ATTRIBUTES = 0x1,
+ PROTOCOL_MESSAGE_ATTRIBUTES = 0x2,
+};
+
+/**
+ * struct scmi_msg_resp_prot_version - Response for a message
+ *
+ * @major_version: Major version of the ABI that firmware supports
+ * @minor_version: Minor version of the ABI that firmware supports
+ *
+ * In general, ABI version changes follow the rule that minor version increments
+ * are backward compatible. Major revision changes in ABI may not be
+ * backward compatible.
+ *
+ * Response to a generic message with message type SCMI_MSG_VERSION
+ */
+struct scmi_msg_resp_prot_version {
+ __le16 minor_version;
+ __le16 major_version;
+};
+
+/**
+ * struct scmi_msg_hdr - Message(Tx/Rx) header
+ *
+ * @id: The identifier of the command being sent
+ * @protocol_id: The identifier of the protocol used to send @id command
+ * @seq: The token to identify the message. when a message/command returns,
+ * the platform returns the whole message header unmodified including
+ * the token.
+ */
+struct scmi_msg_hdr {
+ u8 id;
+ u8 protocol_id;
+ u16 seq;
+ u32 status;
+ bool poll_completion;
+};
+
+/**
+ * struct scmi_msg - Message(Tx/Rx) structure
+ *
+ * @buf: Buffer pointer
+ * @len: Length of data in the Buffer
+ */
+struct scmi_msg {
+ void *buf;
+ size_t len;
+};
+
+/**
+ * struct scmi_xfer - Structure representing a message flow
+ *
+ * @hdr: Transmit message header
+ * @tx: Transmit message
+ * @rx: Receive message, the buffer should be pre-allocated to store
+ * message. If request-ACK protocol is used, we can reuse the same
+ * buffer for the rx path as we use for the tx path.
+ * @done: completion event
+ */
+
+struct scmi_xfer {
+ struct scmi_msg_hdr hdr;
+ struct scmi_msg tx;
+ struct scmi_msg rx;
+ struct completion done;
+};
+
+void scmi_one_xfer_put(const struct scmi_handle *h, struct scmi_xfer *xfer);
+int scmi_do_xfer(const struct scmi_handle *h, struct scmi_xfer *xfer);
+int scmi_one_xfer_init(const struct scmi_handle *h, u8 msg_id, u8 prot_id,
+ size_t tx_size, size_t rx_size, struct scmi_xfer **p);
+int scmi_version_get(const struct scmi_handle *h, u8 protocol, u32 *version);
+void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
+ u8 *prot_imp);
+
+typedef int (*scmi_init_fn_t)(struct scmi_handle *);
+int scmi_base_protocol_init(struct scmi_handle *h);
+int scmi_perf_protocol_init(struct scmi_handle *h);
+int scmi_sensors_protocol_init(struct scmi_handle *h);
+int scmi_power_protocol_init(struct scmi_handle *h);
+int scmi_clock_protocol_init(struct scmi_handle *h);
diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c
new file mode 100644
index 000000000000..d3d289f7a174
--- /dev/null
+++ b/drivers/firmware/arm_scmi/driver.c
@@ -0,0 +1,869 @@
+/*
+ * System Control and Management Interface (SCMI) Message Protocol driver
+ *
+ * SCMI Message Protocol is used between the System Control Processor(SCP)
+ * and the Application Processors(AP). The Message Handling Unit(MHU)
+ * provides a mechanism for inter-processor communication between SCP's
+ * Cortex M3 and AP.
+ *
+ * SCP offers control and management of the core/cluster power states,
+ * various power domain DVFS including the core/cluster, certain system
+ * clocks configuration, thermal sensors and many others.
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/export.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+
+#include "common.h"
+
+#define MSG_ID_SHIFT 0
+#define MSG_ID_MASK 0xff
+#define MSG_TYPE_SHIFT 8
+#define MSG_TYPE_MASK 0x3
+#define MSG_PROTOCOL_ID_SHIFT 10
+#define MSG_PROTOCOL_ID_MASK 0xff
+#define MSG_TOKEN_ID_SHIFT 18
+#define MSG_TOKEN_ID_MASK 0x3ff
+#define MSG_XTRACT_TOKEN(header) \
+ (((header) >> MSG_TOKEN_ID_SHIFT) & MSG_TOKEN_ID_MASK)
+
+enum scmi_error_codes {
+ SCMI_SUCCESS = 0, /* Success */
+ SCMI_ERR_SUPPORT = -1, /* Not supported */
+ SCMI_ERR_PARAMS = -2, /* Invalid Parameters */
+ SCMI_ERR_ACCESS = -3, /* Invalid access/permission denied */
+ SCMI_ERR_ENTRY = -4, /* Not found */
+ SCMI_ERR_RANGE = -5, /* Value out of range */
+ SCMI_ERR_BUSY = -6, /* Device busy */
+ SCMI_ERR_COMMS = -7, /* Communication Error */
+ SCMI_ERR_GENERIC = -8, /* Generic Error */
+ SCMI_ERR_HARDWARE = -9, /* Hardware Error */
+ SCMI_ERR_PROTOCOL = -10,/* Protocol Error */
+ SCMI_ERR_MAX
+};
+
+/* List of all SCMI devices active in system */
+static LIST_HEAD(scmi_list);
+/* Protection for the entire list */
+static DEFINE_MUTEX(scmi_list_mutex);
+
+/**
+ * struct scmi_xfers_info - Structure to manage transfer information
+ *
+ * @sem_xfer_count: Counting Semaphore for managing max simultaneous
+ * Messages.
+ * @xfer_block: Preallocated Message array
+ * @xfer_alloc_table: Bitmap table for allocated messages.
+ * Index of this bitmap table is also used for message
+ * sequence identifier.
+ * @xfer_lock: Protection for message allocation
+ */
+struct scmi_xfers_info {
+ struct semaphore sem_xfer_count;
+ struct scmi_xfer *xfer_block;
+ unsigned long *xfer_alloc_table;
+ /* protect transfer allocation */
+ spinlock_t xfer_lock;
+};
+
+/**
+ * struct scmi_desc - Description of SoC integration
+ *
+ * @max_rx_timeout_ms: Timeout for communication with SoC (in Milliseconds)
+ * @max_msg: Maximum number of messages that can be pending
+ * simultaneously in the system
+ * @max_msg_size: Maximum size of data per message that can be handled.
+ */
+struct scmi_desc {
+ int max_rx_timeout_ms;
+ int max_msg;
+ int max_msg_size;
+};
+
+/**
+ * struct scmi_info - Structure representing a SCMI instance
+ *
+ * @dev: Device pointer
+ * @desc: SoC description for this instance
+ * @handle: Instance of SCMI handle to send to clients
+ * @version: SCMI revision information containing protocol version,
+ * implementation version and (sub-)vendor identification.
+ * @cl: Mailbox Client
+ * @tx_chan: Transmit mailbox channel
+ * @rx_chan: Receive mailbox channel
+ * @tx_payload: Transmit mailbox channel payload area
+ * @rx_payload: Receive mailbox channel payload area
+ * @minfo: Message info
+ * @protocols_imp: list of protocols implemented
+ * @node: list head
+ * @users: Number of users of this instance
+ */
+struct scmi_info {
+ struct device *dev;
+ const struct scmi_desc *desc;
+ struct scmi_revision_info version;
+ struct scmi_handle handle;
+ struct mbox_client cl;
+ struct mbox_chan *tx_chan;
+ struct mbox_chan *rx_chan;
+ void __iomem *tx_payload;
+ void __iomem *rx_payload;
+ struct scmi_xfers_info minfo;
+ u8 *protocols_imp;
+ struct list_head node;
+ int users;
+};
+
+#define client_to_scmi_info(c) container_of(c, struct scmi_info, cl)
+#define handle_to_scmi_info(h) container_of(h, struct scmi_info, handle)
+
+/*
+ * The SCP firmware providing SCM interface to OSPM and other agents must
+ * execute only in little-endian mode as per SCMI specification, so any buffers
+ * shared through SCMI should have their contents converted to little-endian
+ */
+struct scmi_shared_mem {
+ __le32 reserved;
+ __le32 channel_status;
+#define SCMI_SHMEM_CHAN_STAT_CHANNEL_ERROR BIT(1)
+#define SCMI_SHMEM_CHAN_STAT_CHANNEL_FREE BIT(0)
+ __le32 reserved1[2];
+ __le32 flags;
+#define SCMI_SHMEM_FLAG_INTR_ENABLED BIT(0)
+ __le32 length;
+ __le32 msg_header;
+ u8 msg_payload[0];
+};
+
+struct scmi_protocol_match {
+ u8 protocol_id;
+ scmi_init_fn_t fn;
+ char name[32];
+};
+
+static int scmi_linux_errmap[] = {
+ /* better than switch case as long as return value is continuous */
+ 0, /* SCMI_SUCCESS */
+ -EOPNOTSUPP, /* SCMI_ERR_SUPPORT */
+ -EINVAL, /* SCMI_ERR_PARAM */
+ -EACCES, /* SCMI_ERR_ACCESS */
+ -ENOENT, /* SCMI_ERR_ENTRY */
+ -ERANGE, /* SCMI_ERR_RANGE */
+ -EBUSY, /* SCMI_ERR_BUSY */
+ -ECOMM, /* SCMI_ERR_COMMS */
+ -EIO, /* SCMI_ERR_GENERIC */
+ -EREMOTEIO, /* SCMI_ERR_HARDWARE */
+ -EPROTO, /* SCMI_ERR_PROTOCOL */
+};
+
+static inline int scmi_to_linux_errno(int errno)
+{
+ if (errno < SCMI_SUCCESS && errno > SCMI_ERR_MAX)
+ return scmi_linux_errmap[-errno];
+ return -EIO;
+}
+
+/**
+ * scmi_dump_header_dbg() - Helper to dump a message header.
+ *
+ * @dev: Device pointer corresponding to the SCMI entity
+ * @hdr: pointer to header.
+ */
+static inline void scmi_dump_header_dbg(struct device *dev,
+ struct scmi_msg_hdr *hdr)
+{
+ dev_dbg(dev, "Command ID: %x Sequence ID: %x Protocol: %x\n",
+ hdr->id, hdr->seq, hdr->protocol_id);
+}
+
+/**
+ * scmi_rx_callback() - mailbox client callback for receive messages
+ *
+ * @cl: client pointer
+ * @m: mailbox message
+ *
+ * Processes one received message to appropriate transfer information and
+ * signals completion of the transfer.
+ *
+ * NOTE: This function will be invoked in IRQ context, hence should be
+ * as optimal as possible.
+ */
+static void scmi_rx_callback(struct mbox_client *cl, void *m)
+{
+ u16 xfer_id;
+ struct scmi_xfer *xfer;
+ struct scmi_info *info = client_to_scmi_info(cl);
+ struct scmi_xfers_info *minfo = &info->minfo;
+ struct device *dev = info->dev;
+ struct scmi_shared_mem *mem = info->tx_payload;
+
+ xfer_id = MSG_XTRACT_TOKEN(mem->msg_header);
+
+ /*
+ * Are we even expecting this?
+ */
+ if (!test_bit(xfer_id, minfo->xfer_alloc_table)) {
+ dev_err(dev, "message for %d is not expected!\n", xfer_id);
+ return;
+ }
+
+ xfer = &minfo->xfer_block[xfer_id];
+
+ scmi_dump_header_dbg(dev, &xfer->hdr);
+ /* Is the message of valid length? */
+ if (xfer->rx.len > info->desc->max_msg_size) {
+ dev_err(dev, "unable to handle %zu xfer(max %d)\n",
+ xfer->rx.len, info->desc->max_msg_size);
+ return;
+ }
+
+ xfer->hdr.status = le32_to_cpu(*(__le32 *)mem->msg_payload);
+ /* Skip the length of header and statues in payload area i.e 8 bytes*/
+ xfer->rx.len = min_t(size_t, xfer->rx.len, mem->length - 8);
+
+ /* Take a copy to the rx buffer.. */
+ memcpy_fromio(xfer->rx.buf, mem->msg_payload + 4, xfer->rx.len);
+ complete(&xfer->done);
+}
+
+/**
+ * pack_scmi_header() - packs and returns 32-bit header
+ *
+ * @hdr: pointer to header containing all the information on message id,
+ * protocol id and sequence id.
+ */
+static inline u32 pack_scmi_header(struct scmi_msg_hdr *hdr)
+{
+ return ((hdr->id & MSG_ID_MASK) << MSG_ID_SHIFT) |
+ ((hdr->seq & MSG_TOKEN_ID_MASK) << MSG_TOKEN_ID_SHIFT) |
+ ((hdr->protocol_id & MSG_PROTOCOL_ID_MASK) << MSG_PROTOCOL_ID_SHIFT);
+}
+
+/**
+ * scmi_tx_prepare() - mailbox client callback to prepare for the transfer
+ *
+ * @cl: client pointer
+ * @m: mailbox message
+ *
+ * This function prepares the shared memory which contains the header and the
+ * payload.
+ */
+static void scmi_tx_prepare(struct mbox_client *cl, void *m)
+{
+ struct scmi_xfer *t = m;
+ struct scmi_info *info = client_to_scmi_info(cl);
+ struct scmi_shared_mem *mem = info->tx_payload;
+
+ mem->channel_status = 0x0; /* Mark channel busy + clear error */
+ mem->flags = t->hdr.poll_completion ? 0 : SCMI_SHMEM_FLAG_INTR_ENABLED;
+ mem->length = sizeof(mem->msg_header) + t->tx.len;
+ mem->msg_header = cpu_to_le32(pack_scmi_header(&t->hdr));
+ if (t->tx.buf)
+ memcpy_toio(mem->msg_payload, t->tx.buf, t->tx.len);
+}
+
+/**
+ * scmi_one_xfer_get() - Allocate one message
+ *
+ * @handle: SCMI entity handle
+ *
+ * Helper function which is used by various command functions that are
+ * exposed to clients of this driver for allocating a message traffic event.
+ *
+ * This function can sleep depending on pending requests already in the system
+ * for the SCMI entity. Further, this also holds a spinlock to maintain
+ * integrity of internal data structures.
+ *
+ * Return: 0 if all went fine, else corresponding error.
+ */
+static struct scmi_xfer *scmi_one_xfer_get(const struct scmi_handle *handle)
+{
+ u16 xfer_id;
+ int ret, timeout;
+ struct scmi_xfer *xfer;
+ unsigned long flags, bit_pos;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct scmi_xfers_info *minfo = &info->minfo;
+
+ /*
+ * Ensure we have only controlled number of pending messages.
+ * Ideally, we might just have to wait a single message, be
+ * conservative and wait 5 times that..
+ */
+ timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms) * 5;
+ ret = down_timeout(&minfo->sem_xfer_count, timeout);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ /* Keep the locked section as small as possible */
+ spin_lock_irqsave(&minfo->xfer_lock, flags);
+ bit_pos = find_first_zero_bit(minfo->xfer_alloc_table,
+ info->desc->max_msg);
+ if (bit_pos == info->desc->max_msg) {
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+ return ERR_PTR(-ENOMEM);
+ }
+ set_bit(bit_pos, minfo->xfer_alloc_table);
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+
+ xfer_id = bit_pos;
+
+ xfer = &minfo->xfer_block[xfer_id];
+ xfer->hdr.seq = xfer_id;
+ reinit_completion(&xfer->done);
+
+ return xfer;
+}
+
+/**
+ * scmi_one_xfer_put() - Release a message
+ *
+ * @minfo: transfer info pointer
+ * @xfer: message that was reserved by scmi_one_xfer_get
+ *
+ * This holds a spinlock to maintain integrity of internal data structures.
+ */
+void scmi_one_xfer_put(const struct scmi_handle *handle, struct scmi_xfer *xfer)
+{
+ unsigned long flags;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct scmi_xfers_info *minfo = &info->minfo;
+
+ /*
+ * Keep the locked section as small as possible
+ * NOTE: we might escape with smp_mb and no lock here..
+ * but just be conservative and symmetric.
+ */
+ spin_lock_irqsave(&minfo->xfer_lock, flags);
+ clear_bit(xfer->hdr.seq, minfo->xfer_alloc_table);
+ spin_unlock_irqrestore(&minfo->xfer_lock, flags);
+
+ /* Increment the count for the next user to get through */
+ up(&minfo->sem_xfer_count);
+}
+
+/**
+ * scmi_do_xfer() - Do one transfer
+ *
+ * @info: Pointer to SCMI entity information
+ * @xfer: Transfer to initiate and wait for response
+ *
+ * Return: -ETIMEDOUT in case of no response, if transmit error,
+ * return corresponding error, else if all goes well,
+ * return 0.
+ */
+int scmi_do_xfer(const struct scmi_handle *handle, struct scmi_xfer *xfer)
+{
+ int ret;
+ int timeout;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct device *dev = info->dev;
+
+ ret = mbox_send_message(info->tx_chan, xfer);
+ if (ret < 0) {
+ dev_dbg(dev, "mbox send fail %d\n", ret);
+ return ret;
+ }
+
+ /* mbox_send_message returns non-negative value on success, so reset */
+ ret = 0;
+
+ /* And we wait for the response. */
+ timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms);
+ if (!wait_for_completion_timeout(&xfer->done, timeout)) {
+ dev_err(dev, "mbox timed out in resp(caller: %pF)\n",
+ (void *)_RET_IP_);
+ ret = -ETIMEDOUT;
+ } else if (xfer->hdr.status) {
+ ret = scmi_to_linux_errno(xfer->hdr.status);
+ }
+ /*
+ * NOTE: we might prefer not to need the mailbox ticker to manage the
+ * transfer queueing since the protocol layer queues things by itself.
+ * Unfortunately, we have to kick the mailbox framework after we have
+ * received our message.
+ */
+ mbox_client_txdone(info->tx_chan, ret);
+
+ return ret;
+}
+
+/**
+ * scmi_one_xfer_init() - Allocate and initialise one message
+ *
+ * @handle: SCMI entity handle
+ * @msg_id: Message identifier
+ * @msg_prot_id: Protocol identifier for the message
+ * @tx_size: transmit message size
+ * @rx_size: receive message size
+ * @p: pointer to the allocated and initialised message
+ *
+ * This function allocates the message using @scmi_one_xfer_get and
+ * initialise the header.
+ *
+ * Return: 0 if all went fine with @p pointing to message, else
+ * corresponding error.
+ */
+int scmi_one_xfer_init(const struct scmi_handle *handle, u8 msg_id, u8 prot_id,
+ size_t tx_size, size_t rx_size, struct scmi_xfer **p)
+{
+ int ret;
+ struct scmi_xfer *xfer;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+ struct device *dev = info->dev;
+
+ /* Ensure we have sane transfer sizes */
+ if (rx_size > info->desc->max_msg_size ||
+ tx_size > info->desc->max_msg_size)
+ return -ERANGE;
+
+ xfer = scmi_one_xfer_get(handle);
+ if (IS_ERR(xfer)) {
+ ret = PTR_ERR(xfer);
+ dev_err(dev, "failed to get free message slot(%d)\n", ret);
+ return ret;
+ }
+
+ xfer->tx.len = tx_size;
+ xfer->rx.len = rx_size ? : info->desc->max_msg_size;
+ xfer->hdr.id = msg_id;
+ xfer->hdr.protocol_id = prot_id;
+
+ *p = xfer;
+ return 0;
+}
+
+/**
+ * scmi_version_get() - command to get the revision of the SCMI entity
+ *
+ * @handle: Handle to SCMI entity information
+ *
+ * Updates the SCMI information in the internal data structure.
+ *
+ * Return: 0 if all went fine, else return appropriate error.
+ */
+int scmi_version_get(const struct scmi_handle *handle, u8 protocol,
+ u32 *version)
+{
+ int ret;
+ __le32 *rev_info;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_VERSION, protocol, 0,
+ sizeof(*version), &t);
+ if (ret)
+ return ret;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ rev_info = t->rx.buf;
+ *version = le32_to_cpu(*rev_info);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+void scmi_setup_protocol_implemented(const struct scmi_handle *handle,
+ u8 *prot_imp)
+{
+ struct scmi_info *info = handle_to_scmi_info(handle);
+
+ info->protocols_imp = prot_imp;
+}
+
+static bool
+scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id)
+{
+ int i;
+ struct scmi_info *info = handle_to_scmi_info(handle);
+
+ if (!info->protocols_imp)
+ return false;
+
+ for (i = 0; i < MAX_PROTOCOLS_IMP; i++)
+ if (info->protocols_imp[i] == prot_id)
+ return true;
+ return false;
+}
+
+/**
+ * scmi_handle_get() - Get the SCMI handle for a device
+ *
+ * @dev: pointer to device for which we want SCMI handle
+ *
+ * NOTE: The function does not track individual clients of the framework
+ * and is expected to be maintained by caller of SCMI protocol library.
+ * scmi_handle_put must be balanced with successful scmi_handle_get
+ *
+ * Return: pointer to handle if successful, else:
+ * -EPROBE_DEFER if the instance is not ready
+ * -ENODEV if the required node handler is missing
+ * -EINVAL if invalid conditions are encountered.
+ */
+const struct scmi_handle *scmi_handle_get(struct device *dev)
+{
+ struct list_head *p;
+ struct scmi_info *info;
+ struct scmi_handle *handle = NULL;
+
+ if (!dev || !dev->parent) {
+ pr_err("missing device or parent pointer\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ mutex_lock(&scmi_list_mutex);
+ list_for_each(p, &scmi_list) {
+ info = list_entry(p, struct scmi_info, node);
+ if (dev->parent == info->dev) {
+ handle = &info->handle;
+ info->users++;
+ break;
+ }
+ }
+ mutex_unlock(&scmi_list_mutex);
+
+ if (!handle)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return handle;
+}
+EXPORT_SYMBOL_GPL(scmi_handle_get);
+
+/**
+ * scmi_handle_put() - Release the handle acquired by scmi_handle_get
+ *
+ * @handle: handle acquired by scmi_handle_get
+ *
+ * NOTE: The function does not track individual clients of the framework
+ * and is expected to be maintained by caller of SCMI protocol library.
+ * scmi_handle_put must be balanced with successful scmi_handle_get
+ *
+ * Return: 0 is successfully released
+ * if an error pointer was passed, it returns the error value back,
+ * if null was passed, it returns -EINVAL;
+ */
+int scmi_handle_put(const struct scmi_handle *handle)
+{
+ struct scmi_info *info;
+
+ if (IS_ERR(handle))
+ return PTR_ERR(handle);
+ if (!handle)
+ return -EINVAL;
+
+ info = handle_to_scmi_info(handle);
+ mutex_lock(&scmi_list_mutex);
+ if (!WARN_ON(!info->users))
+ info->users--;
+ mutex_unlock(&scmi_list_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(scmi_handle_put);
+
+static void devm_scmi_release(struct device *dev, void *res)
+{
+ const struct scmi_handle **ptr = res;
+ const struct scmi_handle *handle = *ptr;
+ int ret;
+
+ ret = scmi_handle_put(handle);
+ if (ret)
+ dev_err(dev, "failed to put handle %d\n", ret);
+}
+
+/**
+ * devm_scmi_handle_get() - Managed get handle
+ * @dev: device for which we want SCMI handle for.
+ *
+ * NOTE: This releases the handle once the device resources are
+ * no longer needed. MUST NOT BE released with scmi_handle_put.
+ * The function does not track individual clients of the framework
+ * and is expected to be maintained by caller of SCMI protocol library.
+ *
+ * Return: 0 if all went fine, else corresponding error.
+ */
+const struct scmi_handle *devm_scmi_handle_get(struct device *dev)
+{
+ const struct scmi_handle **ptr;
+ const struct scmi_handle *handle;
+
+ ptr = devres_alloc(devm_scmi_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+ handle = scmi_handle_get(dev);
+
+ if (!IS_ERR(handle)) {
+ *ptr = handle;
+ devres_add(dev, ptr);
+ } else {
+ devres_free(ptr);
+ }
+
+ return handle;
+}
+EXPORT_SYMBOL_GPL(devm_scmi_handle_get);
+
+static const struct scmi_desc scmi_generic_desc = {
+ .max_rx_timeout_ms = 30, /* we may increase this if required */
+ .max_msg = 20, /* Limited by MBOX_TX_QUEUE_LEN */
+ .max_msg_size = 128,
+};
+
+/* Each compatible listed below must have descriptor associated with it */
+static const struct of_device_id scmi_of_match[] = {
+ { .compatible = "arm,scmi", .data = &scmi_generic_desc },
+ { /* Sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, scmi_of_match);
+
+static int scmi_xfer_info_init(struct scmi_info *sinfo)
+{
+ int i;
+ struct scmi_xfer *xfer;
+ struct device *dev = sinfo->dev;
+ const struct scmi_desc *desc = sinfo->desc;
+ struct scmi_xfers_info *info = &sinfo->minfo;
+
+ /* Pre-allocated messages, no more than what hdr.seq can support */
+ if (WARN_ON(desc->max_msg >= (MSG_TOKEN_ID_MASK + 1))) {
+ dev_err(dev, "Maximum message of %d exceeds supported %d\n",
+ desc->max_msg, MSG_TOKEN_ID_MASK + 1);
+ return -EINVAL;
+ }
+
+ info->xfer_block = devm_kcalloc(dev, desc->max_msg,
+ sizeof(*info->xfer_block), GFP_KERNEL);
+ if (!info->xfer_block)
+ return -ENOMEM;
+
+ info->xfer_alloc_table = devm_kcalloc(dev, BITS_TO_LONGS(desc->max_msg),
+ sizeof(long), GFP_KERNEL);
+ if (!info->xfer_alloc_table)
+ return -ENOMEM;
+
+ bitmap_zero(info->xfer_alloc_table, desc->max_msg);
+
+ /* Pre-initialize the buffer pointer to pre-allocated buffers */
+ for (i = 0, xfer = info->xfer_block; i < desc->max_msg; i++, xfer++) {
+ xfer->rx.buf = devm_kcalloc(dev, sizeof(u8), desc->max_msg_size,
+ GFP_KERNEL);
+ if (!xfer->rx.buf)
+ return -ENOMEM;
+
+ xfer->tx.buf = xfer->rx.buf;
+ init_completion(&xfer->done);
+ }
+
+ spin_lock_init(&info->xfer_lock);
+
+ sema_init(&info->sem_xfer_count, desc->max_msg);
+
+ return 0;
+}
+
+static const struct scmi_protocol_match scmi_protocols[] = {
+ {
+ .protocol_id = SCMI_PROTOCOL_PERF,
+ .fn = scmi_perf_protocol_init,
+ .name = "scmi-cpufreq",
+ }, {
+ .protocol_id = SCMI_PROTOCOL_CLOCK,
+ .fn = scmi_clock_protocol_init,
+ .name = "scmi-clocks",
+ }, {
+ .protocol_id = SCMI_PROTOCOL_POWER,
+ .fn = scmi_power_protocol_init,
+ .name = "scmi-power-domain",
+ }, {
+ .protocol_id = SCMI_PROTOCOL_SENSOR,
+ .fn = scmi_sensors_protocol_init,
+ .name = "scmi-hwmon",
+ },
+ {}
+};
+
+static const struct scmi_protocol_match *scmi_protocol_match_get(u8 protocol_id)
+{
+ int i;
+ const struct scmi_protocol_match *match = NULL, *loop = scmi_protocols;
+
+ for (i = 0; i < ARRAY_SIZE(scmi_protocols); i++, loop++)
+ if (loop->protocol_id == protocol_id) {
+ match = loop;
+ break;
+ }
+
+ return match;
+}
+
+static int scmi_probe(struct platform_device *pdev)
+{
+ int ret = -EINVAL;
+ struct resource res;
+ resource_size_t size;
+ struct mbox_client *cl;
+ struct scmi_handle *handle;
+ const struct scmi_desc *desc;
+ struct scmi_info *info = NULL;
+ struct device *dev = &pdev->dev;
+ struct device_node *child, *shmem, *np = dev->of_node;
+
+ desc = of_match_device(scmi_of_match, dev)->data;
+
+ if (of_property_match_string(np, "method", "mailbox-doorbell") < 0) {
+ dev_err(dev, "invalid method property in %s\n", np->full_name);
+ return -EINVAL;
+ }
+
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = dev;
+ info->desc = desc;
+ INIT_LIST_HEAD(&info->node);
+
+ ret = scmi_xfer_info_init(info);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, info);
+
+ cl = &info->cl;
+ cl->dev = dev;
+ cl->rx_callback = scmi_rx_callback;
+ cl->tx_prepare = scmi_tx_prepare;
+ cl->tx_block = false;
+ cl->knows_txdone = true;
+
+ shmem = of_parse_phandle(np, "shmem", 0);
+ ret = of_address_to_resource(shmem, 0, &res);
+ of_node_put(shmem);
+ if (ret) {
+ dev_err(dev, "failed to get SCMI Tx payload mem resource\n");
+ return ret;
+ }
+
+ size = resource_size(&res);
+ info->tx_payload = devm_ioremap(dev, res.start, size);
+ if (!info->tx_payload) {
+ dev_err(dev, "failed to ioremap SCMI Tx payload\n");
+ ret = -EADDRNOTAVAIL;
+ return ret;
+ }
+
+ info->tx_chan = mbox_request_channel_byname(cl, "tx");
+ if (IS_ERR(info->tx_chan)) {
+ ret = PTR_ERR(info->tx_chan);
+ goto out;
+ }
+
+ handle = &info->handle;
+ handle->dev = info->dev;
+ handle->version = &info->version;
+ ret = scmi_base_protocol_init(handle);
+ if (ret) {
+ dev_err(dev, "unable to communicate with SCMI(%d)\n", ret);
+ goto out;
+ }
+
+ mutex_lock(&scmi_list_mutex);
+ list_add_tail(&info->node, &scmi_list);
+ mutex_unlock(&scmi_list_mutex);
+
+ for_each_available_child_of_node(np, child) {
+ int init_ret;
+ u32 protocol_id;
+ const struct scmi_protocol_match *match;
+
+ if (of_property_read_u32(child, "reg", &protocol_id))
+ continue;
+
+ protocol_id &= MSG_PROTOCOL_ID_MASK;
+
+ if (!scmi_is_protocol_implemented(handle, protocol_id)) {
+ dev_err(dev, "SCMI protocol %d not implemented\n",
+ protocol_id);
+ continue;
+ }
+
+ match = scmi_protocol_match_get(protocol_id);
+ if (match) {
+ init_ret = match->fn(handle);
+ if (!init_ret)
+ of_platform_device_create(child, match->name,
+ dev);
+ else
+ dev_err(dev, "SCMI protocol %d init error %d\n",
+ protocol_id, init_ret);
+ }
+ }
+
+ return 0;
+out:
+ if (!IS_ERR(info->tx_chan))
+ mbox_free_channel(info->tx_chan);
+ return ret;
+}
+
+static int scmi_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct scmi_info *info = platform_get_drvdata(pdev);
+
+ of_platform_depopulate(&pdev->dev);
+
+ mutex_lock(&scmi_list_mutex);
+ if (info->users)
+ ret = -EBUSY;
+ else
+ list_del(&info->node);
+ mutex_unlock(&scmi_list_mutex);
+
+ if (!ret)
+ /* Safe to free channels since no more users */
+ mbox_free_channel(info->tx_chan);
+
+ return ret;
+}
+
+static struct platform_driver scmi_driver = {
+ .driver = {
+ .name = "arm-scmi",
+ .of_match_table = of_match_ptr(scmi_of_match),
+ },
+ .probe = scmi_probe,
+ .remove = scmi_remove,
+};
+
+module_platform_driver(scmi_driver);
+
+MODULE_ALIAS("platform: arm-scmi");
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI protocol driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
new file mode 100644
index 000000000000..1cb4ba705094
--- /dev/null
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -0,0 +1,511 @@
+/*
+ * System Control and Management Interface (SCMI) Performance Protocol
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/sort.h>
+
+#include "common.h"
+
+enum scmi_performance_protocol_cmd {
+ PERF_DOMAIN_ATTRIBUTES = 0x3,
+ PERF_DESCRIBE_LEVELS = 0x4,
+ PERF_LIMITS_SET = 0x5,
+ PERF_LIMITS_GET = 0x6,
+ PERF_LEVEL_SET = 0x7,
+ PERF_LEVEL_GET = 0x8,
+ PERF_NOTIFY_LIMITS = 0x9,
+ PERF_NOTIFY_LEVEL = 0xa,
+};
+
+struct scmi_opp {
+ u32 perf;
+ u32 power;
+ u32 trans_latency_us;
+};
+
+struct scmi_msg_resp_perf_attributes {
+ __le16 num_domains;
+ __le16 flags;
+#define POWER_SCALE_IN_MILLIWATT(x) ((x) & BIT(0))
+ __le32 stats_addr_low;
+ __le32 stats_addr_high;
+ __le32 stats_size;
+};
+
+struct scmi_msg_resp_perf_domain_attributes {
+ __le32 flags;
+#define SUPPORTS_SET_LIMITS(x) ((x) & BIT(31))
+#define SUPPORTS_SET_PERF_LVL(x) ((x) & BIT(30))
+#define SUPPORTS_PERF_LIMIT_NOTIFY(x) ((x) & BIT(29))
+#define SUPPORTS_PERF_LEVEL_NOTIFY(x) ((x) & BIT(28))
+ __le32 rate_limit_us;
+ __le32 sustained_freq_khz;
+ __le32 sustained_perf_level;
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_msg_perf_describe_levels {
+ __le32 domain;
+ __le32 level_index;
+};
+
+struct scmi_perf_set_limits {
+ __le32 domain;
+ __le32 max_level;
+ __le32 min_level;
+};
+
+struct scmi_perf_get_limits {
+ __le32 max_level;
+ __le32 min_level;
+};
+
+struct scmi_perf_set_level {
+ __le32 domain;
+ __le32 level;
+};
+
+struct scmi_perf_notify_level_or_limits {
+ __le32 domain;
+ __le32 notify_enable;
+};
+
+struct scmi_msg_resp_perf_describe_levels {
+ __le16 num_returned;
+ __le16 num_remaining;
+ struct {
+ __le32 perf_val;
+ __le32 power;
+ __le16 transition_latency_us;
+ __le16 reserved;
+ } opp[0];
+};
+
+struct perf_dom_info {
+ bool set_limits;
+ bool set_perf;
+ bool perf_limit_notify;
+ bool perf_level_notify;
+ u32 opp_count;
+ u32 sustained_freq_khz;
+ u32 sustained_perf_level;
+ u32 mult_factor;
+ char name[SCMI_MAX_STR_SIZE];
+ struct scmi_opp opp[MAX_OPPS];
+};
+
+struct scmi_perf_info {
+ int num_domains;
+ bool power_scale_mw;
+ u64 stats_addr;
+ u32 stats_size;
+ struct perf_dom_info *dom_info;
+};
+
+static struct scmi_perf_info perf_info;
+
+static int scmi_perf_attributes_get(const struct scmi_handle *handle,
+ struct scmi_perf_info *perf_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_perf_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_PERF, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u16 flags = le16_to_cpu(attr->flags);
+
+ perf_info->num_domains = le16_to_cpu(attr->num_domains);
+ perf_info->power_scale_mw = POWER_SCALE_IN_MILLIWATT(flags);
+ perf_info->stats_addr = le32_to_cpu(attr->stats_addr_low) |
+ (u64)le32_to_cpu(attr->stats_addr_high) << 32;
+ perf_info->stats_size = le32_to_cpu(attr->stats_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_perf_domain_attributes_get(const struct scmi_handle *handle, u32 domain,
+ struct perf_dom_info *dom_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_perf_domain_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PERF_DOMAIN_ATTRIBUTES,
+ SCMI_PROTOCOL_PERF, sizeof(domain),
+ sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u32 flags = le32_to_cpu(attr->flags);
+
+ dom_info->set_limits = SUPPORTS_SET_LIMITS(flags);
+ dom_info->set_perf = SUPPORTS_SET_PERF_LVL(flags);
+ dom_info->perf_limit_notify = SUPPORTS_PERF_LIMIT_NOTIFY(flags);
+ dom_info->perf_level_notify = SUPPORTS_PERF_LEVEL_NOTIFY(flags);
+ dom_info->sustained_freq_khz =
+ le32_to_cpu(attr->sustained_freq_khz);
+ dom_info->sustained_perf_level =
+ le32_to_cpu(attr->sustained_perf_level);
+ dom_info->mult_factor = (dom_info->sustained_freq_khz * 1000) /
+ dom_info->sustained_perf_level;
+ memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int opp_cmp_func(const void *opp1, const void *opp2)
+{
+ const struct scmi_opp *t1 = opp1, *t2 = opp2;
+
+ return t1->perf - t2->perf;
+}
+
+static int
+scmi_perf_describe_levels_get(const struct scmi_handle *handle, u32 domain,
+ struct perf_dom_info *perf_dom)
+{
+ int ret, cnt;
+ u32 tot_opp_cnt = 0;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_opp *opp;
+ struct scmi_msg_perf_describe_levels *dom_info;
+ struct scmi_msg_resp_perf_describe_levels *level_info;
+
+ ret = scmi_one_xfer_init(handle, PERF_DESCRIBE_LEVELS,
+ SCMI_PROTOCOL_PERF, sizeof(*dom_info), 0, &t);
+ if (ret)
+ return ret;
+
+ dom_info = t->tx.buf;
+ level_info = t->rx.buf;
+
+ do {
+ dom_info->domain = cpu_to_le32(domain);
+ /* Set the number of OPPs to be skipped/already read */
+ dom_info->level_index = cpu_to_le32(tot_opp_cnt);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ num_returned = le16_to_cpu(level_info->num_returned);
+ num_remaining = le16_to_cpu(level_info->num_remaining);
+ if (tot_opp_cnt + num_returned > MAX_OPPS) {
+ dev_err(handle->dev, "No. of OPPs exceeded MAX_OPPS");
+ break;
+ }
+
+ opp = &perf_dom->opp[tot_opp_cnt];
+ for (cnt = 0; cnt < num_returned; cnt++, opp++) {
+ opp->perf = le32_to_cpu(level_info->opp[cnt].perf_val);
+ opp->power = le32_to_cpu(level_info->opp[cnt].power);
+ opp->trans_latency_us = le16_to_cpu(
+ level_info->opp[cnt].transition_latency_us);
+
+ dev_dbg(handle->dev, "Level %d Power %d Latency %dus\n",
+ opp->perf, opp->power, opp->trans_latency_us);
+ }
+
+ tot_opp_cnt += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ perf_dom->opp_count = tot_opp_cnt;
+ scmi_one_xfer_put(handle, t);
+
+ sort(perf_dom->opp, tot_opp_cnt, sizeof(*opp), opp_cmp_func, NULL);
+ return ret;
+}
+
+static int scmi_perf_limits_set(const struct scmi_handle *handle, u32 domain,
+ u32 max_perf, u32 min_perf)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_set_limits *limits;
+
+ ret = scmi_one_xfer_init(handle, PERF_LIMITS_SET, SCMI_PROTOCOL_PERF,
+ sizeof(*limits), 0, &t);
+ if (ret)
+ return ret;
+
+ limits = t->tx.buf;
+ limits->domain = cpu_to_le32(domain);
+ limits->max_level = cpu_to_le32(max_perf);
+ limits->min_level = cpu_to_le32(min_perf);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_perf_limits_get(const struct scmi_handle *handle, u32 domain,
+ u32 *max_perf, u32 *min_perf)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_get_limits *limits;
+
+ ret = scmi_one_xfer_init(handle, PERF_LIMITS_GET, SCMI_PROTOCOL_PERF,
+ sizeof(__le32), 0, &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ limits = t->rx.buf;
+
+ *max_perf = le32_to_cpu(limits->max_level);
+ *min_perf = le32_to_cpu(limits->min_level);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_perf_level_set(const struct scmi_handle *handle, u32 domain, u32 level)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_set_level *lvl;
+
+ ret = scmi_one_xfer_init(handle, PERF_LEVEL_SET, SCMI_PROTOCOL_PERF,
+ sizeof(*lvl), 0, &t);
+ if (ret)
+ return ret;
+
+ lvl = t->tx.buf;
+ lvl->domain = cpu_to_le32(domain);
+ lvl->level = cpu_to_le32(level);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_perf_level_get(const struct scmi_handle *handle, u32 domain, u32 *level)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, PERF_LEVEL_GET, SCMI_PROTOCOL_PERF,
+ sizeof(u32), sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ *level = le32_to_cpu(*(__le32 *)t->rx.buf);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int __scmi_perf_notify_enable(const struct scmi_handle *handle, u32 cmd,
+ u32 domain, bool enable)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_perf_notify_level_or_limits *notify;
+
+ ret = scmi_one_xfer_init(handle, cmd, SCMI_PROTOCOL_PERF,
+ sizeof(*notify), 0, &t);
+ if (ret)
+ return ret;
+
+ notify = t->tx.buf;
+ notify->domain = cpu_to_le32(domain);
+ notify->notify_enable = cpu_to_le32(enable & BIT(0));
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_perf_limits_notify_enable(const struct scmi_handle *handle,
+ u32 domain, bool enable)
+{
+ return __scmi_perf_notify_enable(handle, PERF_NOTIFY_LIMITS,
+ domain, enable);
+}
+
+static int scmi_perf_level_notify_enable(const struct scmi_handle *handle,
+ u32 domain, bool enable)
+{
+ return __scmi_perf_notify_enable(handle, PERF_NOTIFY_LEVEL,
+ domain, enable);
+}
+
+/* Device specific ops */
+static int scmi_dev_domain_id(struct device *dev)
+{
+ struct of_phandle_args clkspec;
+
+ if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells",
+ 0, &clkspec))
+ return -EINVAL;
+
+ return clkspec.args[0];
+}
+
+static int scmi_dvfs_add_opps_to_device(struct device *dev)
+{
+ int idx, ret, domain;
+ unsigned long freq;
+ struct scmi_opp *opp;
+ struct perf_dom_info *dom;
+
+ domain = scmi_dev_domain_id(dev);
+ if (domain < 0)
+ return domain;
+
+ dom = perf_info.dom_info + domain;
+ if (!dom)
+ return -EIO;
+
+ for (opp = dom->opp, idx = 0; idx < dom->opp_count; idx++, opp++) {
+ freq = opp->perf * dom->mult_factor;
+
+ ret = dev_pm_opp_add(dev, freq, opp->power);
+ if (ret) {
+ dev_warn(dev, "failed to add opp %luHz %umV\n",
+ freq, opp->power);
+
+ while (idx-- > 0) {
+ freq = (--opp)->perf * dom->mult_factor;
+ dev_pm_opp_remove(dev, freq);
+ }
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int scmi_dvfs_get_transition_latency(struct device *dev)
+{
+ struct perf_dom_info *dom;
+ int domain = scmi_dev_domain_id(dev);
+
+ if (domain < 0)
+ return domain;
+
+ dom = perf_info.dom_info + domain;
+ if (!dom)
+ return -EIO;
+
+ return dom->opp[dom->opp_count - 1].trans_latency_us;
+}
+
+static int scmi_dvfs_freq_set(const struct scmi_handle *handle, u32 domain,
+ unsigned long freq)
+{
+ struct perf_dom_info *dom = perf_info.dom_info + domain;
+
+ return scmi_perf_level_set(handle, domain, freq / dom->mult_factor);
+}
+
+static int scmi_dvfs_freq_get(const struct scmi_handle *handle, u32 domain,
+ unsigned long *freq)
+{
+ int ret;
+ u32 level;
+ struct perf_dom_info *dom = perf_info.dom_info + domain;
+
+ ret = scmi_perf_level_get(handle, domain, &level);
+ if (!ret)
+ *freq = level * dom->mult_factor;
+
+ return ret;
+}
+
+static struct scmi_perf_ops perf_ops = {
+ .limits_set = scmi_perf_limits_set,
+ .limits_get = scmi_perf_limits_get,
+ .level_set = scmi_perf_level_set,
+ .level_get = scmi_perf_level_get,
+ .limits_notify_enable = scmi_perf_limits_notify_enable,
+ .level_notify_enable = scmi_perf_level_notify_enable,
+ .device_domain_id = scmi_dev_domain_id,
+ .get_transition_latency = scmi_dvfs_get_transition_latency,
+ .add_opps_to_device = scmi_dvfs_add_opps_to_device,
+ .freq_set = scmi_dvfs_freq_set,
+ .freq_get = scmi_dvfs_freq_get,
+};
+
+int scmi_perf_protocol_init(struct scmi_handle *handle)
+{
+ int domain;
+ u32 version;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_PERF, &version);
+
+ dev_dbg(handle->dev, "Performance Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ scmi_perf_attributes_get(handle, &perf_info);
+
+ perf_info.dom_info = devm_kcalloc(handle->dev, perf_info.num_domains,
+ sizeof(struct perf_dom_info),
+ GFP_KERNEL);
+ if (!perf_info.dom_info)
+ return -ENOMEM;
+
+ for (domain = 0; domain < perf_info.num_domains; domain++) {
+ struct perf_dom_info *dom = perf_info.dom_info + domain;
+
+ scmi_perf_domain_attributes_get(handle, domain, dom);
+ scmi_perf_describe_levels_get(handle, domain, dom);
+ }
+
+ handle->perf_ops = &perf_ops;
+
+ return 0;
+}
diff --git a/drivers/firmware/arm_scmi/power.c b/drivers/firmware/arm_scmi/power.c
new file mode 100644
index 000000000000..e73ad77c63c6
--- /dev/null
+++ b/drivers/firmware/arm_scmi/power.c
@@ -0,0 +1,242 @@
+/*
+ * System Control and Management Interface (SCMI) Power Protocol
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+
+enum scmi_power_protocol_cmd {
+ POWER_DOMAIN_ATTRIBUTES = 0x3,
+ POWER_STATE_SET = 0x4,
+ POWER_STATE_GET = 0x5,
+ POWER_STATE_NOTIFY = 0x6,
+};
+
+struct scmi_msg_resp_power_attributes {
+ __le16 num_domains;
+ __le16 reserved;
+ __le32 stats_addr_low;
+ __le32 stats_addr_high;
+ __le32 stats_size;
+};
+
+struct scmi_msg_resp_power_domain_attributes {
+ __le32 flags;
+#define SUPPORTS_STATE_SET_NOTIFY(x) ((x) & BIT(31))
+#define SUPPORTS_STATE_SET_ASYNC(x) ((x) & BIT(30))
+#define SUPPORTS_STATE_SET_SYNC(x) ((x) & BIT(29))
+ u8 name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_power_set_state {
+ __le32 flags;
+#define STATE_SET_ASYNC BIT(0)
+ __le32 domain;
+ __le32 state;
+};
+
+struct scmi_power_state_notify {
+ __le32 domain;
+ __le32 notify_enable;
+};
+
+struct power_dom_info {
+ bool state_set_sync;
+ bool state_set_async;
+ bool state_set_notify;
+ char name[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_power_info {
+ int num_domains;
+ u64 stats_addr;
+ u32 stats_size;
+ struct power_dom_info *dom_info;
+};
+
+static struct scmi_power_info power_info;
+
+static int scmi_power_attributes_get(const struct scmi_handle *handle,
+ struct scmi_power_info *power_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_power_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_POWER, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ power_info->num_domains = le16_to_cpu(attr->num_domains);
+ power_info->stats_addr = le32_to_cpu(attr->stats_addr_low) |
+ (u64)le32_to_cpu(attr->stats_addr_high) << 32;
+ power_info->stats_size = le32_to_cpu(attr->stats_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_domain_attributes_get(const struct scmi_handle *handle, u32 domain,
+ struct power_dom_info *dom_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_power_domain_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, POWER_DOMAIN_ATTRIBUTES,
+ SCMI_PROTOCOL_POWER, sizeof(domain),
+ sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ u32 flags = le32_to_cpu(attr->flags);
+
+ dom_info->state_set_notify = SUPPORTS_STATE_SET_NOTIFY(flags);
+ dom_info->state_set_async = SUPPORTS_STATE_SET_ASYNC(flags);
+ dom_info->state_set_sync = SUPPORTS_STATE_SET_SYNC(flags);
+ memcpy(dom_info->name, attr->name, SCMI_MAX_STR_SIZE);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_state_set(const struct scmi_handle *handle, u32 domain, u32 state)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_power_set_state *st;
+
+ ret = scmi_one_xfer_init(handle, POWER_STATE_SET, SCMI_PROTOCOL_POWER,
+ sizeof(*st), 0, &t);
+ if (ret)
+ return ret;
+
+ st = t->tx.buf;
+ st->flags = cpu_to_le32(0);
+ st->domain = cpu_to_le32(domain);
+ st->state = cpu_to_le32(state);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_power_state_get(const struct scmi_handle *handle, u32 domain, u32 *state)
+{
+ int ret;
+ struct scmi_xfer *t;
+
+ ret = scmi_one_xfer_init(handle, POWER_STATE_GET, SCMI_PROTOCOL_POWER,
+ sizeof(u32), sizeof(u32), &t);
+ if (ret)
+ return ret;
+
+ *(__le32 *)t->tx.buf = cpu_to_le32(domain);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret)
+ *state = le32_to_cpu(*(__le32 *)t->rx.buf);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_power_state_notify_enable(const struct scmi_handle *handle,
+ u32 domain, bool enable)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_power_state_notify *notify;
+
+ ret = scmi_one_xfer_init(handle, POWER_STATE_NOTIFY,
+ SCMI_PROTOCOL_POWER, sizeof(*notify), 0, &t);
+ if (ret)
+ return ret;
+
+ notify = t->tx.buf;
+ notify->domain = cpu_to_le32(domain);
+ notify->notify_enable = cpu_to_le32(enable & BIT(0));
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_power_num_domains_get(const struct scmi_handle *handle)
+{
+ return power_info.num_domains;
+}
+
+static char *scmi_power_name_get(const struct scmi_handle *handle, u32 domain)
+{
+ struct power_dom_info *dom = power_info.dom_info + domain;
+
+ return dom->name;
+}
+
+static struct scmi_power_ops power_ops = {
+ .num_domains_get = scmi_power_num_domains_get,
+ .name_get = scmi_power_name_get,
+ .state_set = scmi_power_state_set,
+ .state_get = scmi_power_state_get,
+ .state_notify_enable = scmi_power_state_notify_enable,
+};
+
+int scmi_power_protocol_init(struct scmi_handle *handle)
+{
+ u32 version;
+ int domain;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_POWER, &version);
+
+ dev_dbg(handle->dev, "Power Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ scmi_power_attributes_get(handle, &power_info);
+
+ power_info.dom_info = devm_kcalloc(handle->dev, power_info.num_domains,
+ sizeof(struct power_dom_info),
+ GFP_KERNEL);
+ if (!power_info.dom_info)
+ return -ENOMEM;
+
+ for (domain = 0; domain < power_info.num_domains; domain++) {
+ struct power_dom_info *dom = power_info.dom_info + domain;
+
+ scmi_power_domain_attributes_get(handle, domain, dom);
+ }
+
+ handle->power_ops = &power_ops;
+
+ return 0;
+}
diff --git a/drivers/firmware/arm_scmi/scmi_pm_domain.c b/drivers/firmware/arm_scmi/scmi_pm_domain.c
new file mode 100644
index 000000000000..e53aa9d0af6e
--- /dev/null
+++ b/drivers/firmware/arm_scmi/scmi_pm_domain.c
@@ -0,0 +1,134 @@
+/*
+ * SCMI Generic power domain support.
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pm_domain.h>
+#include <linux/scmi_protocol.h>
+
+struct scmi_pm_domain {
+ struct generic_pm_domain genpd;
+ const struct scmi_handle *handle;
+ const char *name;
+ u32 domain;
+};
+
+#define to_scmi_pd(gpd) container_of(gpd, struct scmi_pm_domain, genpd)
+
+static int scmi_pd_power(struct generic_pm_domain *domain, bool power_on)
+{
+ int ret;
+ u32 state, ret_state;
+ struct scmi_pm_domain *pd = to_scmi_pd(domain);
+ const struct scmi_power_ops *ops = pd->handle->power_ops;
+
+ if (power_on)
+ state = SCMI_POWER_STATE_GENERIC_ON;
+ else
+ state = SCMI_POWER_STATE_GENERIC_OFF;
+
+ ret = ops->state_set(pd->handle, pd->domain, state);
+ if (!ret)
+ ret = ops->state_get(pd->handle, pd->domain, &ret_state);
+ if (!ret && state != ret_state)
+ return -EIO;
+
+ return ret;
+}
+
+static int scmi_pd_power_on(struct generic_pm_domain *domain)
+{
+ return scmi_pd_power(domain, true);
+}
+
+static int scmi_pd_power_off(struct generic_pm_domain *domain)
+{
+ return scmi_pd_power(domain, false);
+}
+
+static int scmi_pm_domain_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct scmi_pm_domain *scmi_pd;
+ struct genpd_onecell_data *scmi_pd_data;
+ struct generic_pm_domain **domains;
+ int num_domains, i;
+ const struct scmi_handle *handle = devm_scmi_handle_get(dev);
+
+ if (IS_ERR_OR_NULL(handle) || !handle->power_ops)
+ return -EPROBE_DEFER;
+
+ num_domains = handle->power_ops->num_domains_get(handle);
+ if (num_domains < 0) {
+ dev_err(dev, "number of domains not found\n");
+ return num_domains;
+ }
+
+ scmi_pd = devm_kcalloc(dev, num_domains, sizeof(*scmi_pd), GFP_KERNEL);
+ if (!scmi_pd)
+ return -ENOMEM;
+
+ scmi_pd_data = devm_kzalloc(dev, sizeof(*scmi_pd_data), GFP_KERNEL);
+ if (!scmi_pd_data)
+ return -ENOMEM;
+
+ domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
+ if (!domains)
+ return -ENOMEM;
+
+ for (i = 0; i < num_domains; i++, scmi_pd++) {
+ domains[i] = &scmi_pd->genpd;
+
+ scmi_pd->domain = i;
+ scmi_pd->handle = handle;
+ scmi_pd->name = handle->power_ops->name_get(handle, i);
+ scmi_pd->genpd.name = scmi_pd->name;
+ scmi_pd->genpd.power_off = scmi_pd_power_off;
+ scmi_pd->genpd.power_on = scmi_pd_power_on;
+
+ /*
+ * Treat all power domains as off at boot.
+ *
+ * The SCP firmware itself may have switched on some domains,
+ * but for reference counting purpose, keep it this way.
+ */
+ pm_genpd_init(&scmi_pd->genpd, NULL, true);
+ }
+
+ scmi_pd_data->domains = domains;
+ scmi_pd_data->num_domains = num_domains;
+
+ of_genpd_add_provider_onecell(np, scmi_pd_data);
+
+ return 0;
+}
+
+static struct platform_driver scmi_power_domain_driver = {
+ .driver = {
+ .name = "scmi-power-domain",
+ },
+ .probe = scmi_pm_domain_probe,
+};
+module_platform_driver(scmi_power_domain_driver);
+
+MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI power domain driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c
new file mode 100644
index 000000000000..9f1e254e7188
--- /dev/null
+++ b/drivers/firmware/arm_scmi/sensors.c
@@ -0,0 +1,286 @@
+/*
+ * System Control and Management Interface (SCMI) Sensor Protocol
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+
+enum scmi_sensor_protocol_cmd {
+ SENSOR_DESCRIPTION_GET = 0x3,
+ SENSOR_CONFIG_SET = 0x4,
+ SENSOR_TRIP_POINT_SET = 0x5,
+ SENSOR_READING_GET = 0x6,
+};
+
+struct scmi_msg_resp_sensor_attributes {
+ __le16 num_sensors;
+ u8 max_requests;
+ u8 reserved;
+ __le32 reg_addr_low;
+ __le32 reg_addr_high;
+ __le32 reg_size;
+};
+
+struct scmi_msg_resp_sensor_description {
+ __le16 num_returned;
+ __le16 num_remaining;
+ struct {
+ __le32 id;
+ __le32 attributes_low;
+#define SUPPORTS_ASYNC_READ(x) ((x) & BIT(31))
+#define NUM_TRIP_POINTS(x) (((x) >> 4) & 0xff)
+ __le32 attributes_high;
+#define SENSOR_TYPE(x) ((x) & 0xff)
+#define SENSOR_SCALE(x) (((x) >> 11) & 0x3f)
+#define SENSOR_UPDATE_SCALE(x) (((x) >> 22) & 0x1f)
+#define SENSOR_UPDATE_BASE(x) (((x) >> 27) & 0x1f)
+ u8 name[SCMI_MAX_STR_SIZE];
+ } desc[0];
+};
+
+struct scmi_msg_set_sensor_config {
+ __le32 id;
+ __le32 event_control;
+};
+
+struct scmi_msg_set_sensor_trip_point {
+ __le32 id;
+ __le32 event_control;
+#define SENSOR_TP_EVENT_MASK (0x3)
+#define SENSOR_TP_DISABLED 0x0
+#define SENSOR_TP_POSITIVE 0x1
+#define SENSOR_TP_NEGATIVE 0x2
+#define SENSOR_TP_BOTH 0x3
+#define SENSOR_TP_ID(x) (((x) & 0xff) << 4)
+ __le32 value_low;
+ __le32 value_high;
+};
+
+struct scmi_msg_sensor_reading_get {
+ __le32 id;
+ __le32 flags;
+#define SENSOR_READ_ASYNC BIT(0)
+};
+
+struct sensors_info {
+ int num_sensors;
+ int max_requests;
+ u64 reg_addr;
+ u32 reg_size;
+ struct scmi_sensor_info *sensors;
+};
+
+static struct sensors_info sensor_info;
+
+static int scmi_sensor_attributes_get(const struct scmi_handle *handle,
+ struct sensors_info *sensor_info)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_sensor_attributes *attr;
+
+ ret = scmi_one_xfer_init(handle, PROTOCOL_ATTRIBUTES,
+ SCMI_PROTOCOL_SENSOR, 0, sizeof(*attr), &t);
+ if (ret)
+ return ret;
+
+ attr = t->rx.buf;
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ sensor_info->num_sensors = le16_to_cpu(attr->num_sensors);
+ sensor_info->max_requests = le16_to_cpu(attr->max_requests);
+ sensor_info->reg_addr = le32_to_cpu(attr->reg_addr_low) |
+ (u64)le32_to_cpu(attr->reg_addr_high) << 32;
+ sensor_info->reg_size = le32_to_cpu(attr->reg_size);
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_description_get(const struct scmi_handle *handle)
+{
+ int ret, cnt;
+ u32 desc_index = 0;
+ u16 num_returned, num_remaining;
+ struct scmi_xfer *t;
+ struct scmi_msg_resp_sensor_description *buf;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_DESCRIPTION_GET,
+ SCMI_PROTOCOL_SENSOR, sizeof(__le32), 0, &t);
+ if (ret)
+ return ret;
+
+ buf = t->rx.buf;
+
+ do {
+ /* Set the number of sensors to be skipped/already read */
+ *(__le32 *)t->tx.buf = cpu_to_le32(desc_index);
+
+ ret = scmi_do_xfer(handle, t);
+ if (ret)
+ break;
+
+ num_returned = le16_to_cpu(buf->num_returned);
+ num_remaining = le16_to_cpu(buf->num_remaining);
+
+ if (desc_index + num_returned > sensor_info.num_sensors) {
+ dev_err(handle->dev, "No. of sensors can't exceed %d",
+ sensor_info.num_sensors);
+ break;
+ }
+
+ for (cnt = 0; cnt < num_returned; cnt++) {
+ u32 attrh;
+ struct scmi_sensor_info *s;
+
+ attrh = le32_to_cpu(buf->desc[cnt].attributes_high);
+ s = &sensor_info.sensors[desc_index + cnt];
+ s->id = le32_to_cpu(buf->desc[cnt].id);
+ s->type = SENSOR_TYPE(attrh);
+ memcpy(s->name, buf->desc[cnt].name, SCMI_MAX_STR_SIZE);
+ }
+
+ desc_index += num_returned;
+ /*
+ * check for both returned and remaining to avoid infinite
+ * loop due to buggy firmware
+ */
+ } while (num_returned && num_remaining);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int
+scmi_sensor_configuration_set(const struct scmi_handle *handle, u32 sensor_id)
+{
+ int ret;
+ u32 evt_cntl = BIT(0);
+ struct scmi_xfer *t;
+ struct scmi_msg_set_sensor_config *cfg;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_CONFIG_SET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*cfg), 0, &t);
+ if (ret)
+ return ret;
+
+ cfg = t->tx.buf;
+ cfg->id = cpu_to_le32(sensor_id);
+ cfg->event_control = cpu_to_le32(evt_cntl);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_trip_point_set(const struct scmi_handle *handle,
+ u32 sensor_id, u8 trip_id, u64 trip_value)
+{
+ int ret;
+ u32 evt_cntl = SENSOR_TP_BOTH;
+ struct scmi_xfer *t;
+ struct scmi_msg_set_sensor_trip_point *trip;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_TRIP_POINT_SET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*trip), 0, &t);
+ if (ret)
+ return ret;
+
+ trip = t->tx.buf;
+ trip->id = cpu_to_le32(sensor_id);
+ trip->event_control = cpu_to_le32(evt_cntl | SENSOR_TP_ID(trip_id));
+ trip->value_low = cpu_to_le32(trip_value & 0xffffffff);
+ trip->value_high = cpu_to_le32(trip_value >> 32);
+
+ ret = scmi_do_xfer(handle, t);
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static int scmi_sensor_reading_get(const struct scmi_handle *handle,
+ u32 sensor_id, bool async, u64 *value)
+{
+ int ret;
+ struct scmi_xfer *t;
+ struct scmi_msg_sensor_reading_get *sensor;
+
+ ret = scmi_one_xfer_init(handle, SENSOR_READING_GET,
+ SCMI_PROTOCOL_SENSOR, sizeof(*sensor),
+ sizeof(u64), &t);
+ if (ret)
+ return ret;
+
+ sensor = t->tx.buf;
+ sensor->id = cpu_to_le32(sensor_id);
+ sensor->flags = cpu_to_le32(async ? SENSOR_READ_ASYNC : 0);
+
+ ret = scmi_do_xfer(handle, t);
+ if (!ret) {
+ __le32 *pval = t->rx.buf;
+
+ *value = le32_to_cpu(*pval);
+ *value |= (u64)le32_to_cpu(*(pval + 1)) << 32;
+ }
+
+ scmi_one_xfer_put(handle, t);
+ return ret;
+}
+
+static const struct scmi_sensor_info *
+scmi_sensor_info_get(const struct scmi_handle *handle, u32 sensor_id)
+{
+ return sensor_info.sensors + sensor_id;
+}
+
+static int scmi_sensor_count_get(const struct scmi_handle *handle)
+{
+ return sensor_info.num_sensors;
+}
+
+static struct scmi_sensor_ops sensor_ops = {
+ .count_get = scmi_sensor_count_get,
+ .info_get = scmi_sensor_info_get,
+ .configuration_set = scmi_sensor_configuration_set,
+ .trip_point_set = scmi_sensor_trip_point_set,
+ .reading_get = scmi_sensor_reading_get,
+};
+
+int scmi_sensors_protocol_init(struct scmi_handle *handle)
+{
+ u32 version;
+
+ scmi_version_get(handle, SCMI_PROTOCOL_SENSOR, &version);
+
+ dev_dbg(handle->dev, "Sensor Version %d.%d\n",
+ PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version));
+
+ scmi_sensor_attributes_get(handle, &sensor_info);
+
+ sensor_info.sensors = devm_kcalloc(handle->dev, sensor_info.num_sensors,
+ sizeof(struct scmi_sensor_info), GFP_KERNEL);
+ if (!sensor_info.sensors)
+ return -ENOMEM;
+
+ scmi_sensor_description_get(handle);
+
+ handle->sensor_ops = &sensor_ops;
+
+ return 0;
+}
diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c
index 8d39da71d6b9..2235271d82f4 100644
--- a/drivers/firmware/arm_scpi.c
+++ b/drivers/firmware/arm_scpi.c
@@ -39,6 +39,7 @@
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/printk.h>
+#include <linux/pm_opp.h>
#include <linux/scpi_protocol.h>
#include <linux/slab.h>
#include <linux/sort.h>
@@ -711,6 +712,65 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain)
return info;
}
+static int scpi_dev_domain_id(struct device *dev)
+{
+ struct of_phandle_args clkspec;
+
+ if (of_parse_phandle_with_args(dev->of_node, "clocks", "#clock-cells",
+ 0, &clkspec))
+ return -EINVAL;
+
+ return clkspec.args[0];
+}
+
+static struct scpi_dvfs_info *scpi_dvfs_info(struct device *dev)
+{
+ int domain = scpi_dev_domain_id(dev);
+
+ if (domain < 0)
+ return ERR_PTR(domain);
+
+ return scpi_dvfs_get_info(domain);
+}
+
+static int scpi_dvfs_get_transition_latency(struct device *dev)
+{
+ struct scpi_dvfs_info *info = scpi_dvfs_info(dev);
+
+ if (IS_ERR(info))
+ return PTR_ERR(info);
+
+ if (!info->latency)
+ return 0;
+
+ return info->latency;
+}
+
+static int scpi_dvfs_add_opps_to_device(struct device *dev)
+{
+ int idx, ret;
+ struct scpi_opp *opp;
+ struct scpi_dvfs_info *info = scpi_dvfs_info(dev);
+
+ if (IS_ERR(info))
+ return PTR_ERR(info);
+
+ if (!info->opps)
+ return -EIO;
+
+ for (opp = info->opps, idx = 0; idx < info->count; idx++, opp++) {
+ ret = dev_pm_opp_add(dev, opp->freq, opp->m_volt * 1000);
+ if (ret) {
+ dev_warn(dev, "failed to add opp %uHz %umV\n",
+ opp->freq, opp->m_volt);
+ while (idx-- > 0)
+ dev_pm_opp_remove(dev, (--opp)->freq);
+ return ret;
+ }
+ }
+ return 0;
+}
+
static int scpi_sensor_get_capability(u16 *sensors)
{
struct sensor_capabilities cap_buf;
@@ -792,6 +852,9 @@ static struct scpi_ops scpi_ops = {
.dvfs_get_idx = scpi_dvfs_get_idx,
.dvfs_set_idx = scpi_dvfs_set_idx,
.dvfs_get_info = scpi_dvfs_get_info,
+ .device_domain_id = scpi_dev_domain_id,
+ .get_transition_latency = scpi_dvfs_get_transition_latency,
+ .add_opps_to_device = scpi_dvfs_add_opps_to_device,
.sensor_get_capability = scpi_sensor_get_capability,
.sensor_get_info = scpi_sensor_get_info,
.sensor_get_value = scpi_sensor_get_value,
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5ef2814345ef..2bb63af8d674 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -321,6 +321,18 @@ config SENSORS_APPLESMC
Say Y here if you have an applicable laptop and want to experience
the awesome power of applesmc.
+config SENSORS_ARM_SCMI
+ tristate "ARM SCMI Sensors"
+ depends on ARM_SCMI_PROTOCOL
+ depends on THERMAL || !THERMAL_OF
+ help
+ This driver provides support for temperature, voltage, current
+ and power sensors available on SCMI based platforms. The actual
+ number and type of sensors exported depend on the platform.
+
+ This driver can also be built as a module. If so, the module
+ will be called scmi-hwmon.
+
config SENSORS_ARM_SCPI
tristate "ARM SCPI Sensors"
depends on ARM_SCPI_PROTOCOL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d4641a9f16c1..02c3783c319f 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
+obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
diff --git a/drivers/hwmon/scmi-hwmon.c b/drivers/hwmon/scmi-hwmon.c
new file mode 100644
index 000000000000..1e7250fdd940
--- /dev/null
+++ b/drivers/hwmon/scmi-hwmon.c
@@ -0,0 +1,259 @@
+/*
+ * System Control and Management Interface(SCMI) based hwmon sensor driver
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ * Punit Agrawal <punit.agrawal@arm.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 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/scmi_protocol.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+
+struct sensor_data {
+ const struct scmi_sensor_info *info;
+ struct device_attribute dev_attr_input;
+ struct device_attribute dev_attr_label;
+ char input[20];
+ char label[20];
+};
+
+struct scmi_thermal_zone {
+ int sensor_id;
+ struct scmi_sensors *scmi_sensors;
+};
+
+struct scmi_sensors {
+ const struct scmi_handle *handle;
+ struct sensor_data *data;
+ struct list_head thermal_zones;
+ struct attribute **attrs;
+ struct attribute_group group;
+ const struct attribute_group *groups[2];
+};
+
+static int scmi_read_temp(void *dev, int *temp)
+{
+ struct scmi_thermal_zone *zone = dev;
+ struct scmi_sensors *scmi_sensors = zone->scmi_sensors;
+ const struct scmi_handle *handle = scmi_sensors->handle;
+ struct sensor_data *sensor = &scmi_sensors->data[zone->sensor_id];
+ u64 value;
+ int ret;
+
+ ret = handle->sensor_ops->reading_get(handle, sensor->info->id,
+ false, &value);
+ if (ret)
+ return ret;
+
+ *temp = value;
+ return 0;
+}
+
+/* hwmon callback functions */
+static ssize_t
+scmi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);
+ const struct scmi_handle *handle = scmi_sensors->handle;
+ struct sensor_data *sensor;
+ u64 value;
+ int ret;
+
+ sensor = container_of(attr, struct sensor_data, dev_attr_input);
+
+ ret = handle->sensor_ops->reading_get(handle, sensor->info->id,
+ false, &value);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%llu\n", value);
+}
+
+static ssize_t
+scmi_show_label(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct sensor_data *sensor;
+
+ sensor = container_of(attr, struct sensor_data, dev_attr_label);
+
+ return sprintf(buf, "%s\n", sensor->info->name);
+}
+
+static struct thermal_zone_of_device_ops scmi_sensor_ops = {
+ .get_temp = scmi_read_temp,
+};
+
+static int scmi_hwmon_probe(struct platform_device *pdev)
+{
+ int idx;
+ u16 nr_sensors, i;
+ int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0;
+ int num_energy = 0;
+ struct device *hwdev, *dev = &pdev->dev;
+ struct scmi_sensors *scmi_sensors;
+ const struct scmi_handle *handle = devm_scmi_handle_get(dev);
+
+ if (IS_ERR_OR_NULL(handle) || !handle->sensor_ops)
+ return -EPROBE_DEFER;
+
+ nr_sensors = handle->sensor_ops->count_get(handle);
+ if (!nr_sensors)
+ return -EIO;
+
+ scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL);
+ if (!scmi_sensors)
+ return -ENOMEM;
+
+ scmi_sensors->data = devm_kcalloc(dev, nr_sensors,
+ sizeof(*scmi_sensors->data), GFP_KERNEL);
+ if (!scmi_sensors->data)
+ return -ENOMEM;
+
+ scmi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1,
+ sizeof(*scmi_sensors->attrs), GFP_KERNEL);
+ if (!scmi_sensors->attrs)
+ return -ENOMEM;
+
+ scmi_sensors->handle = handle;
+
+ for (i = 0, idx = 0; i < nr_sensors; i++) {
+ struct sensor_data *sensor = &scmi_sensors->data[idx];
+
+ sensor->info = handle->sensor_ops->info_get(handle, i);
+ if (!sensor->info)
+ return PTR_ERR(sensor->info);
+
+ switch (sensor->info->type) {
+ case TEMPERATURE_C:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "temp%d_input", num_temp + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "temp%d_label", num_temp + 1);
+ num_temp++;
+ break;
+ case VOLTAGE:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "in%d_input", num_volt);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "in%d_label", num_volt);
+ num_volt++;
+ break;
+ case CURRENT:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "curr%d_input", num_current + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "curr%d_label", num_current + 1);
+ num_current++;
+ break;
+ case POWER:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "power%d_input", num_power + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "power%d_label", num_power + 1);
+ num_power++;
+ break;
+ case ENERGY:
+ snprintf(sensor->input, sizeof(sensor->input),
+ "energy%d_input", num_energy + 1);
+ snprintf(sensor->label, sizeof(sensor->input),
+ "energy%d_label", num_energy + 1);
+ num_energy++;
+ break;
+ default:
+ continue;
+ }
+
+ sensor->dev_attr_input.attr.mode = S_IRUGO;
+ sensor->dev_attr_input.show = scmi_show_sensor;
+ sensor->dev_attr_input.attr.name = sensor->input;
+
+ sensor->dev_attr_label.attr.mode = S_IRUGO;
+ sensor->dev_attr_label.show = scmi_show_label;
+ sensor->dev_attr_label.attr.name = sensor->label;
+
+ scmi_sensors->attrs[idx << 1] = &sensor->dev_attr_input.attr;
+ scmi_sensors->attrs[(idx << 1) + 1] =
+ &sensor->dev_attr_label.attr;
+
+ sysfs_attr_init(scmi_sensors->attrs[idx << 1]);
+ sysfs_attr_init(scmi_sensors->attrs[(idx << 1) + 1]);
+ idx++;
+ }
+
+ scmi_sensors->group.attrs = scmi_sensors->attrs;
+ scmi_sensors->groups[0] = &scmi_sensors->group;
+
+ platform_set_drvdata(pdev, scmi_sensors);
+
+ hwdev = devm_hwmon_device_register_with_groups(dev, "scmi_sensors",
+ scmi_sensors,
+ scmi_sensors->groups);
+
+ if (IS_ERR(hwdev))
+ return PTR_ERR(hwdev);
+
+ /*
+ * Register the temperature sensors with the thermal framework
+ * to allow their usage in setting up the thermal zones from
+ * device tree.
+ *
+ * NOTE: Not all temperature sensors maybe used for thermal
+ * control
+ */
+ INIT_LIST_HEAD(&scmi_sensors->thermal_zones);
+ for (i = 0; i < nr_sensors; i++) {
+ struct sensor_data *sensor = &scmi_sensors->data[i];
+ const struct scmi_sensor_info *info = sensor->info;
+ struct thermal_zone_device *z;
+ struct scmi_thermal_zone *zone;
+
+ if (info->type != TEMPERATURE_C)
+ continue;
+
+ zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL);
+ if (!zone)
+ return -ENOMEM;
+
+ zone->sensor_id = i;
+ zone->scmi_sensors = scmi_sensors;
+ z = devm_thermal_zone_of_sensor_register(dev, info->id, zone,
+ &scmi_sensor_ops);
+ /*
+ * The call to thermal_zone_of_sensor_register returns
+ * an error for sensors that are not associated with
+ * any thermal zones or if the thermal subsystem is
+ * not configured.
+ */
+ if (IS_ERR(z)) {
+ devm_kfree(dev, zone);
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver scmi_hwmon_platdrv = {
+ .driver = {
+ .name = "scmi-hwmon",
+ },
+ .probe = scmi_hwmon_probe,
+};
+module_platform_driver(scmi_hwmon_platdrv);
+
+MODULE_AUTHOR("Punit Agrawal <punit.agrawal@arm.com>");
+MODULE_DESCRIPTION("ARM SCMI HWMON interface driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mailbox/arm_mhu.c b/drivers/mailbox/arm_mhu.c
index 99befa76e37c..4ed389d8f3dd 100644
--- a/drivers/mailbox/arm_mhu.c
+++ b/drivers/mailbox/arm_mhu.c
@@ -13,16 +13,16 @@
* GNU General Public License for more details.
*/
-#include <linux/interrupt.h>
-#include <linux/spinlock.h>
-#include <linux/mutex.h>
-#include <linux/delay.h>
-#include <linux/slab.h>
+#include <linux/amba/bus.h>
+#include <linux/device.h>
#include <linux/err.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
-#include <linux/module.h>
-#include <linux/amba/bus.h>
+#include <linux/kernel.h>
#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
#define INTR_STAT_OFS 0x0
#define INTR_SET_OFS 0x8
@@ -33,7 +33,8 @@
#define MHU_SEC_OFFSET 0x200
#define TX_REG_OFFSET 0x100
-#define MHU_CHANS 3
+#define MHU_NUM_PCHANS 3 /* Secure, Non-Secure High and Low Priority */
+#define MHU_CHAN_MAX 20 /* Max channels to save on unused RAM */
struct mhu_link {
unsigned irq;
@@ -43,88 +44,341 @@ struct mhu_link {
struct arm_mhu {
void __iomem *base;
- struct mhu_link mlink[MHU_CHANS];
- struct mbox_chan chan[MHU_CHANS];
+ struct mhu_link mlink[MHU_NUM_PCHANS];
struct mbox_controller mbox;
+ struct device *dev;
+ const char *name;
+};
+
+/**
+ * ARM MHU Mailbox platform specific configuration
+ *
+ * @num_pchans: Maximum number of physical channels
+ * @num_doorbells: Maximum number of doorbells per physical channel
+ */
+struct mhu_mbox_pdata {
+ unsigned int num_pchans;
+ unsigned int num_doorbells;
+ bool support_doorbells;
+};
+
+/**
+ * ARM MHU Mailbox allocated channel information
+ *
+ * @mhu: Pointer to parent mailbox device
+ * @pchan: Physical channel within which this doorbell resides in
+ * @doorbell: doorbell number pertaining to this channel
+ */
+struct mhu_channel {
+ struct arm_mhu *mhu;
+ unsigned int pchan;
+ unsigned int doorbell;
};
+static inline struct mbox_chan *
+mhu_mbox_to_channel(struct mbox_controller *mbox,
+ unsigned int pchan, unsigned int doorbell)
+{
+ int i;
+ struct mhu_channel *chan_info;
+
+ for (i = 0; i < mbox->num_chans; i++) {
+ chan_info = mbox->chans[i].con_priv;
+ if (chan_info && chan_info->pchan == pchan &&
+ chan_info->doorbell == doorbell)
+ return &mbox->chans[i];
+ }
+
+ dev_err(mbox->dev,
+ "Channel not registered: physical channel: %d doorbell: %d\n",
+ pchan, doorbell);
+
+ return NULL;
+}
+
+static void mhu_mbox_clear_irq(struct mbox_chan *chan)
+{
+ struct mhu_channel *chan_info = chan->con_priv;
+ void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].rx_reg;
+
+ writel_relaxed(BIT(chan_info->doorbell), base + INTR_CLR_OFS);
+}
+
+static unsigned int mhu_mbox_irq_to_pchan_num(struct arm_mhu *mhu, int irq)
+{
+ unsigned int pchan;
+ struct mhu_mbox_pdata *pdata = dev_get_platdata(mhu->dev);
+
+ for (pchan = 0; pchan < pdata->num_pchans; pchan++)
+ if (mhu->mlink[pchan].irq == irq)
+ break;
+ return pchan;
+}
+
+static struct mbox_chan *mhu_mbox_irq_to_channel(struct arm_mhu *mhu,
+ unsigned int pchan)
+{
+ unsigned long bits;
+ unsigned int doorbell;
+ struct mbox_chan *chan = NULL;
+ struct mbox_controller *mbox = &mhu->mbox;
+ void __iomem *base = mhu->mlink[pchan].rx_reg;
+
+ bits = readl_relaxed(base + INTR_STAT_OFS);
+ if (!bits)
+ /* No IRQs fired in specified physical channel */
+ return NULL;
+
+ /* An IRQ has fired, find the associated channel */
+ for (doorbell = 0; bits; doorbell++) {
+ if (!test_and_clear_bit(doorbell, &bits))
+ continue;
+
+ chan = mhu_mbox_to_channel(mbox, pchan, doorbell);
+ if (chan)
+ break;
+ }
+
+ return chan;
+}
+
+static irqreturn_t mhu_mbox_thread_handler(int irq, void *data)
+{
+ struct mbox_chan *chan;
+ struct arm_mhu *mhu = data;
+ unsigned int pchan = mhu_mbox_irq_to_pchan_num(mhu, irq);
+
+ while (NULL != (chan = mhu_mbox_irq_to_channel(mhu, pchan))) {
+ mbox_chan_received_data(chan, NULL);
+ mhu_mbox_clear_irq(chan);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static bool mhu_doorbell_last_tx_done(struct mbox_chan *chan)
+{
+ struct mhu_channel *chan_info = chan->con_priv;
+ void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg;
+
+ if (readl_relaxed(base + INTR_STAT_OFS) & BIT(chan_info->doorbell))
+ return false;
+
+ return true;
+}
+
+static int mhu_doorbell_send_data(struct mbox_chan *chan, void *data)
+{
+ struct mhu_channel *chan_info = chan->con_priv;
+ void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg;
+
+ /* Send event to co-processor */
+ writel_relaxed(BIT(chan_info->doorbell), base + INTR_SET_OFS);
+
+ return 0;
+}
+
+static int mhu_doorbell_startup(struct mbox_chan *chan)
+{
+ mhu_mbox_clear_irq(chan);
+ return 0;
+}
+
+static void mhu_doorbell_shutdown(struct mbox_chan *chan)
+{
+ struct mhu_channel *chan_info = chan->con_priv;
+ struct mbox_controller *mbox = &chan_info->mhu->mbox;
+ int i;
+
+ for (i = 0; i < mbox->num_chans; i++)
+ if (chan == &mbox->chans[i])
+ break;
+
+ if (mbox->num_chans == i) {
+ dev_warn(mbox->dev, "Request to free non-existent channel\n");
+ return;
+ }
+
+ /* Reset channel */
+ mhu_mbox_clear_irq(chan);
+ chan->con_priv = NULL;
+}
+
+static struct mbox_chan *mhu_mbox_xlate(struct mbox_controller *mbox,
+ const struct of_phandle_args *spec)
+{
+ struct arm_mhu *mhu = dev_get_drvdata(mbox->dev);
+ struct mhu_mbox_pdata *pdata = dev_get_platdata(mhu->dev);
+ struct mhu_channel *chan_info;
+ struct mbox_chan *chan = NULL;
+ unsigned int pchan = spec->args[0];
+ unsigned int doorbell = pdata->support_doorbells ? spec->args[1] : 0;
+ int i;
+
+ /* Bounds checking */
+ if (pchan >= pdata->num_pchans || doorbell >= pdata->num_doorbells) {
+ dev_err(mbox->dev,
+ "Invalid channel requested pchan: %d doorbell: %d\n",
+ pchan, doorbell);
+ return ERR_PTR(-EINVAL);
+ }
+
+ for (i = 0; i < mbox->num_chans; i++) {
+ chan_info = mbox->chans[i].con_priv;
+
+ /* Is requested channel free? */
+ if (chan_info &&
+ mbox->dev == chan_info->mhu->dev &&
+ pchan == chan_info->pchan &&
+ doorbell == chan_info->doorbell) {
+ dev_err(mbox->dev, "Channel in use\n");
+ return ERR_PTR(-EBUSY);
+ }
+
+ /*
+ * Find the first free slot, then continue checking
+ * to see if requested channel is in use
+ */
+ if (!chan && !chan_info)
+ chan = &mbox->chans[i];
+ }
+
+ if (!chan) {
+ dev_err(mbox->dev, "No free channels left\n");
+ return ERR_PTR(-EBUSY);
+ }
+
+ chan_info = devm_kzalloc(mbox->dev, sizeof(*chan_info), GFP_KERNEL);
+ if (!chan_info)
+ return ERR_PTR(-ENOMEM);
+
+ chan_info->mhu = mhu;
+ chan_info->pchan = pchan;
+ chan_info->doorbell = doorbell;
+
+ chan->con_priv = chan_info;
+
+ dev_dbg(mbox->dev, "mbox: %s, created channel phys: %d doorbell: %d\n",
+ mhu->name, pchan, doorbell);
+
+ return chan;
+}
+
static irqreturn_t mhu_rx_interrupt(int irq, void *p)
{
- struct mbox_chan *chan = p;
- struct mhu_link *mlink = chan->con_priv;
+ struct arm_mhu *mhu = p;
+ unsigned int pchan = mhu_mbox_irq_to_pchan_num(mhu, irq);
+ struct mbox_chan *chan = mhu_mbox_to_channel(&mhu->mbox, pchan, 0);
+ void __iomem *base = mhu->mlink[pchan].rx_reg;
u32 val;
- val = readl_relaxed(mlink->rx_reg + INTR_STAT_OFS);
+ val = readl_relaxed(base + INTR_STAT_OFS);
if (!val)
return IRQ_NONE;
mbox_chan_received_data(chan, (void *)&val);
- writel_relaxed(val, mlink->rx_reg + INTR_CLR_OFS);
+ writel_relaxed(val, base + INTR_CLR_OFS);
return IRQ_HANDLED;
}
static bool mhu_last_tx_done(struct mbox_chan *chan)
{
- struct mhu_link *mlink = chan->con_priv;
- u32 val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS);
+ struct mhu_channel *chan_info = chan->con_priv;
+ void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg;
+ u32 val = readl_relaxed(base + INTR_STAT_OFS);
return (val == 0);
}
static int mhu_send_data(struct mbox_chan *chan, void *data)
{
- struct mhu_link *mlink = chan->con_priv;
+ struct mhu_channel *chan_info = chan->con_priv;
+ void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg;
u32 *arg = data;
- writel_relaxed(*arg, mlink->tx_reg + INTR_SET_OFS);
+ writel_relaxed(*arg, base + INTR_SET_OFS);
return 0;
}
static int mhu_startup(struct mbox_chan *chan)
{
- struct mhu_link *mlink = chan->con_priv;
+ struct mhu_channel *chan_info = chan->con_priv;
+ void __iomem *base = chan_info->mhu->mlink[chan_info->pchan].tx_reg;
u32 val;
- int ret;
-
- val = readl_relaxed(mlink->tx_reg + INTR_STAT_OFS);
- writel_relaxed(val, mlink->tx_reg + INTR_CLR_OFS);
- ret = request_irq(mlink->irq, mhu_rx_interrupt,
- IRQF_SHARED, "mhu_link", chan);
- if (ret) {
- dev_err(chan->mbox->dev,
- "Unable to acquire IRQ %d\n", mlink->irq);
- return ret;
- }
+ val = readl_relaxed(base + INTR_STAT_OFS);
+ writel_relaxed(val, base + INTR_CLR_OFS);
return 0;
}
-static void mhu_shutdown(struct mbox_chan *chan)
-{
- struct mhu_link *mlink = chan->con_priv;
-
- free_irq(mlink->irq, chan);
-}
-
static const struct mbox_chan_ops mhu_ops = {
.send_data = mhu_send_data,
.startup = mhu_startup,
- .shutdown = mhu_shutdown,
.last_tx_done = mhu_last_tx_done,
};
+static const struct mbox_chan_ops mhu_doorbell_ops = {
+ .send_data = mhu_doorbell_send_data,
+ .startup = mhu_doorbell_startup,
+ .shutdown = mhu_doorbell_shutdown,
+ .last_tx_done = mhu_doorbell_last_tx_done,
+};
+
+static const struct mhu_mbox_pdata arm_mhu_pdata = {
+ .num_pchans = 3,
+ .num_doorbells = 1,
+ .support_doorbells = false,
+};
+
+static const struct mhu_mbox_pdata arm_mhu_doorbell_pdata = {
+ .num_pchans = 2, /* Secure can't be used */
+ .num_doorbells = 32,
+ .support_doorbells = true,
+};
+
static int mhu_probe(struct amba_device *adev, const struct amba_id *id)
{
- int i, err;
+ u32 cell_count;
+ int i, err, max_chans;
+ irq_handler_t handler;
struct arm_mhu *mhu;
+ struct mbox_chan *chans;
+ struct mhu_mbox_pdata *pdata;
struct device *dev = &adev->dev;
- int mhu_reg[MHU_CHANS] = {MHU_LP_OFFSET, MHU_HP_OFFSET, MHU_SEC_OFFSET};
+ struct device_node *np = dev->of_node;
+ int mhu_reg[MHU_NUM_PCHANS] = {
+ MHU_LP_OFFSET, MHU_HP_OFFSET, MHU_SEC_OFFSET,
+ };
+
+ err = of_property_read_u32(np, "#mbox-cells", &cell_count);
+ if (err) {
+ dev_err(dev, "failed to read #mbox-cells in %s\n",
+ np->full_name);
+ return err;
+ }
+
+ if (cell_count == 1) {
+ max_chans = MHU_NUM_PCHANS;
+ pdata = (struct mhu_mbox_pdata *)&arm_mhu_pdata;
+ } else if (cell_count == 2) {
+ max_chans = MHU_CHAN_MAX;
+ pdata = (struct mhu_mbox_pdata *)&arm_mhu_doorbell_pdata;
+ } else {
+ dev_err(dev, "incorrect value of #mbox-cells in %s\n",
+ np->full_name);
+ return -EINVAL;
+ }
+
+ if (pdata->num_pchans > MHU_NUM_PCHANS) {
+ dev_err(dev, "Number of physical channel can't exceed %d\n",
+ MHU_NUM_PCHANS);
+ return -EINVAL;
+ }
- /* Allocate memory for device */
mhu = devm_kzalloc(dev, sizeof(*mhu), GFP_KERNEL);
if (!mhu)
return -ENOMEM;
@@ -135,30 +389,62 @@ static int mhu_probe(struct amba_device *adev, const struct amba_id *id)
return PTR_ERR(mhu->base);
}
- for (i = 0; i < MHU_CHANS; i++) {
- mhu->chan[i].con_priv = &mhu->mlink[i];
- mhu->mlink[i].irq = adev->irq[i];
- mhu->mlink[i].rx_reg = mhu->base + mhu_reg[i];
- mhu->mlink[i].tx_reg = mhu->mlink[i].rx_reg + TX_REG_OFFSET;
- }
+ err = of_property_read_string(np, "mbox-name", &mhu->name);
+ if (err)
+ mhu->name = np->full_name;
+
+ chans = devm_kcalloc(dev, max_chans, sizeof(*chans), GFP_KERNEL);
+ if (!chans)
+ return -ENOMEM;
+
+ dev->platform_data = pdata;
+ mhu->dev = dev;
mhu->mbox.dev = dev;
- mhu->mbox.chans = &mhu->chan[0];
- mhu->mbox.num_chans = MHU_CHANS;
- mhu->mbox.ops = &mhu_ops;
+ mhu->mbox.chans = chans;
+ mhu->mbox.num_chans = max_chans;
mhu->mbox.txdone_irq = false;
mhu->mbox.txdone_poll = true;
mhu->mbox.txpoll_period = 1;
+ mhu->mbox.of_xlate = mhu_mbox_xlate;
amba_set_drvdata(adev, mhu);
+ if (pdata->support_doorbells) {
+ mhu->mbox.ops = &mhu_doorbell_ops;
+ handler = mhu_mbox_thread_handler;
+ } else {
+ mhu->mbox.ops = &mhu_ops;
+ handler = mhu_rx_interrupt;
+ }
+
err = mbox_controller_register(&mhu->mbox);
if (err) {
dev_err(dev, "Failed to register mailboxes %d\n", err);
return err;
}
- dev_info(dev, "ARM MHU Mailbox registered\n");
+ for (i = 0; i < pdata->num_pchans; i++) {
+ int irq = mhu->mlink[i].irq = adev->irq[i];
+
+ if (irq <= 0) {
+ dev_dbg(dev, "No IRQ found for Channel %d\n", i);
+ continue;
+ }
+
+ mhu->mlink[i].rx_reg = mhu->base + mhu_reg[i];
+ mhu->mlink[i].tx_reg = mhu->mlink[i].rx_reg + TX_REG_OFFSET;
+
+ err = devm_request_threaded_irq(dev, irq, NULL, handler,
+ IRQF_ONESHOT, "mhu_link", mhu);
+ if (err) {
+ dev_err(dev, "Can't claim IRQ %d\n", irq);
+ mbox_controller_unregister(&mhu->mbox);
+ return err;
+ }
+ }
+
+ dev_info(dev, "%s mailbox registered\n", mhu->name);
return 0;
}
diff --git a/drivers/mailbox/mailbox-test.c b/drivers/mailbox/mailbox-test.c
index 97fb956bb6e0..93f3d4d61fa7 100644
--- a/drivers/mailbox/mailbox-test.c
+++ b/drivers/mailbox/mailbox-test.c
@@ -30,6 +30,7 @@
#define MBOX_HEXDUMP_MAX_LEN (MBOX_HEXDUMP_LINE_LEN * \
(MBOX_MAX_MSG_LEN / MBOX_BYTES_PER_LINE))
+static bool mbox_data_ready;
static struct dentry *root_debugfs_dir;
struct mbox_test_device {
@@ -152,16 +153,14 @@ out:
static bool mbox_test_message_data_ready(struct mbox_test_device *tdev)
{
- unsigned char data;
+ bool data_ready;
unsigned long flags;
spin_lock_irqsave(&tdev->lock, flags);
- data = tdev->rx_buffer[0];
+ data_ready = mbox_data_ready;
spin_unlock_irqrestore(&tdev->lock, flags);
- if (data != '\0')
- return true;
- return false;
+ return data_ready;
}
static ssize_t mbox_test_message_read(struct file *filp, char __user *userbuf,
@@ -223,6 +222,7 @@ static ssize_t mbox_test_message_read(struct file *filp, char __user *userbuf,
*(touser + l) = '\0';
memset(tdev->rx_buffer, 0, MBOX_MAX_MSG_LEN);
+ mbox_data_ready = false;
spin_unlock_irqrestore(&tdev->lock, flags);
@@ -292,6 +292,7 @@ static void mbox_test_receive_message(struct mbox_client *client, void *message)
message, MBOX_MAX_MSG_LEN);
memcpy(tdev->rx_buffer, message, MBOX_MAX_MSG_LEN);
}
+ mbox_data_ready = true;
spin_unlock_irqrestore(&tdev->lock, flags);
wake_up_interruptible(&tdev->waitq);
diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c
index 9dfbf7ea10a2..674b35f402f5 100644
--- a/drivers/mailbox/mailbox.c
+++ b/drivers/mailbox/mailbox.c
@@ -351,15 +351,18 @@ struct mbox_chan *mbox_request_channel(struct mbox_client *cl, int index)
init_completion(&chan->tx_complete);
if (chan->txdone_method == TXDONE_BY_POLL && cl->knows_txdone)
- chan->txdone_method |= TXDONE_BY_ACK;
+ chan->txdone_method = TXDONE_BY_ACK;
spin_unlock_irqrestore(&chan->lock, flags);
- ret = chan->mbox->ops->startup(chan);
- if (ret) {
- dev_err(dev, "Unable to startup the chan (%d)\n", ret);
- mbox_free_channel(chan);
- chan = ERR_PTR(ret);
+ if (chan->mbox->ops->startup) {
+ ret = chan->mbox->ops->startup(chan);
+
+ if (ret) {
+ dev_err(dev, "Unable to startup the chan (%d)\n", ret);
+ mbox_free_channel(chan);
+ chan = ERR_PTR(ret);
+ }
}
mutex_unlock(&con_mutex);
@@ -408,13 +411,14 @@ void mbox_free_channel(struct mbox_chan *chan)
if (!chan || !chan->cl)
return;
- chan->mbox->ops->shutdown(chan);
+ if (chan->mbox->ops->shutdown)
+ chan->mbox->ops->shutdown(chan);
/* The queued TX requests are simply aborted, no callbacks are made */
spin_lock_irqsave(&chan->lock, flags);
chan->cl = NULL;
chan->active_req = NULL;
- if (chan->txdone_method == (TXDONE_BY_POLL | TXDONE_BY_ACK))
+ if (chan->txdone_method == TXDONE_BY_ACK)
chan->txdone_method = TXDONE_BY_POLL;
module_put(chan->mbox->dev->driver->owner);
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
new file mode 100644
index 000000000000..d2a34cc9df8b
--- /dev/null
+++ b/include/linux/scmi_protocol.h
@@ -0,0 +1,215 @@
+/*
+ * SCMI Message Protocol driver header
+ *
+ * Copyright (C) 2017 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/types.h>
+
+#define SCMI_MAX_STR_SIZE 16
+#define SCMI_MAX_NUM_RATES 16
+
+/**
+ * struct scmi_revision_info - version information structure
+ *
+ * @major_ver: Major ABI version. Change here implies risk of backward
+ * compatibility break.
+ * @minor_ver: Minor ABI version. Change here implies new feature addition,
+ * or compatible change in ABI.
+ * @num_protocols: Number of protocols that are implemented, excluding the
+ * base protocol.
+ * @num_agents: Number of agents in the system.
+ * @impl_ver: A vendor-specific implementation version.
+ * @vendor_id: A vendor identifier(Null terminated ASCII string)
+ * @sub_vendor_id: A sub-vendor identifier(Null terminated ASCII string)
+ */
+struct scmi_revision_info {
+ u16 major_ver;
+ u16 minor_ver;
+ u8 num_protocols;
+ u8 num_agents;
+ u32 impl_ver;
+ char vendor_id[SCMI_MAX_STR_SIZE];
+ char sub_vendor_id[SCMI_MAX_STR_SIZE];
+};
+
+struct scmi_clock_info {
+ char name[SCMI_MAX_STR_SIZE];
+ bool rate_discrete;
+ union {
+ struct {
+ int num_rates;
+ u64 rates[SCMI_MAX_NUM_RATES];
+ } list;
+ struct {
+ u64 min_rate;
+ u64 max_rate;
+ u64 step_size;
+ } range;
+ };
+};
+
+struct scmi_handle;
+
+/**
+ * struct scmi_clk_ops - represents the various operations provided
+ * by SCMI Clock Protocol
+ *
+ * @count_get: get the count of clocks provided by SCMI
+ * @info_get: get the information of the specified clock
+ * @rate_get: request the current clock rate of a clock
+ * @rate_set: set the clock rate of a clock
+ * @enable: enables the specified clock
+ * @disable: disables the specified clock
+ */
+struct scmi_clk_ops {
+ int (*count_get)(const struct scmi_handle *);
+ const struct scmi_clock_info *(*info_get)(const struct scmi_handle *,
+ u32);
+ int (*rate_get)(const struct scmi_handle *, u32, u64*);
+ int (*rate_set)(const struct scmi_handle *, u32, u32, u64);
+ int (*enable)(const struct scmi_handle *, u32);
+ int (*disable)(const struct scmi_handle *, u32);
+};
+
+/**
+ * struct scmi_perf_ops - represents the various operations provided
+ * by SCMI Performance Protocol
+ *
+ * @limits_set: sets limits on the performance level of a domain
+ * @limits_get: gets limits on the performance level of a domain
+ * @level_set: sets the performance level of a domain
+ * @level_get: gets the performance level of a domain
+ * @limits_notify_enable: requests notifications from the platform for changes
+ * in the allowed maximum and minimum performance levels
+ * @level_notify_enable: requests notifications from the platform when the
+ * performance level for a domain changes in value
+ */
+struct scmi_perf_ops {
+ int (*limits_set)(const struct scmi_handle *, u32, u32, u32);
+ int (*limits_get)(const struct scmi_handle *, u32, u32 *, u32 *);
+ int (*level_set)(const struct scmi_handle *, u32, u32);
+ int (*level_get)(const struct scmi_handle *, u32, u32 *);
+ int (*limits_notify_enable)(const struct scmi_handle *, u32, bool);
+ int (*level_notify_enable)(const struct scmi_handle *, u32, bool);
+ int (*device_domain_id)(struct device *);
+ int (*get_transition_latency)(struct device *);
+ int (*add_opps_to_device)(struct device *);
+ int (*freq_set)(const struct scmi_handle *, u32, unsigned long);
+ int (*freq_get)(const struct scmi_handle *, u32, unsigned long *);
+};
+
+/**
+ * struct scmi_power_ops - represents the various operations provided
+ * by SCMI Power Protocol
+ *
+ * @num_domains_get: get the count of power domains provided by SCMI
+ * @name_get: gets the name of a power domain
+ * @state_set: sets the power state of a power domain
+ * @state_get: gets the power state of a power domain
+ * @state_notify_enable: request notifications from the platform for
+ * state changes in a specific power domain
+ */
+struct scmi_power_ops {
+ int (*num_domains_get)(const struct scmi_handle *);
+ char *(*name_get)(const struct scmi_handle *, u32);
+#define SCMI_POWER_STATE_TYPE_SHIFT 30
+#define SCMI_POWER_STATE_ID_MASK (BIT(28) - 1)
+#define SCMI_POWER_STATE_PARAM(type, id) \
+ ((((type) & BIT(0)) << SCMI_POWER_STATE_TYPE_SHIFT) | \
+ ((id) & SCMI_POWER_STATE_ID_MASK))
+#define SCMI_POWER_STATE_GENERIC_ON SCMI_POWER_STATE_PARAM(0, 0)
+#define SCMI_POWER_STATE_GENERIC_OFF SCMI_POWER_STATE_PARAM(1, 0)
+ int (*state_set)(const struct scmi_handle *, u32, u32);
+ int (*state_get)(const struct scmi_handle *, u32, u32 *);
+ int (*state_notify_enable)(const struct scmi_handle *, u32, bool);
+};
+
+struct scmi_sensor_info {
+ u32 id;
+ u8 type;
+ char name[SCMI_MAX_STR_SIZE];
+};
+
+/*
+ * Partial list from Distributed Management Task Force (DMTF) specification:
+ * DSP0249 (Platform Level Data Model specification)
+ */
+enum scmi_sensor_class {
+ TEMPERATURE_C = 0x2,
+ VOLTAGE = 0x5,
+ CURRENT = 0x6,
+ POWER = 0x7,
+ ENERGY = 0x8,
+};
+
+/**
+ * struct scmi_sensor_ops - represents the various operations provided
+ * by SCMI Sensor Protocol
+ *
+ * @count_get: get the count of sensors provided by SCMI
+ * @info_get: get the information of the specified sensor
+ * @configuration_set: control notifications on cross-over events for
+ * the trip-points
+ * @trip_point_set: selects and configures a trip-point of interest
+ * @reading_get: gets the current value of the sensor
+ */
+struct scmi_sensor_ops {
+ int (*count_get)(const struct scmi_handle *);
+ const struct scmi_sensor_info *(*info_get)(const struct scmi_handle *,
+ u32);
+ int (*configuration_set)(const struct scmi_handle *, u32);
+ int (*trip_point_set)(const struct scmi_handle *, u32, u8, u64);
+ int (*reading_get)(const struct scmi_handle *, u32, bool, u64 *);
+};
+
+/**
+ * struct scmi_handle - Handle returned to ARM SCMI clients for usage.
+ *
+ * @dev: pointer to the SCMI device
+ * @version: pointer to the structure containing SCMI version information
+ * @power_ops: pointer to set of power protocol operations
+ * @perf_ops: pointer to set of performance protocol operations
+ * @clk_ops: pointer to set of clock protocol operations
+ * @sensor_ops: pointer to set of sensor protocol operations
+ */
+struct scmi_handle {
+ struct device *dev;
+ struct scmi_revision_info *version;
+ struct scmi_power_ops *power_ops;
+ struct scmi_perf_ops *perf_ops;
+ struct scmi_clk_ops *clk_ops;
+ struct scmi_sensor_ops *sensor_ops;
+};
+
+#if IS_REACHABLE(CONFIG_ARM_SCMI_PROTOCOL)
+int scmi_handle_put(const struct scmi_handle *handle);
+const struct scmi_handle *scmi_handle_get(struct device *dev);
+const struct scmi_handle *devm_scmi_handle_get(struct device *dev);
+#else
+static inline int scmi_handle_put(const struct scmi_handle *handle)
+{
+ return 0;
+}
+
+static inline const struct scmi_handle *scmi_handle_get(struct device *dev)
+{
+ return NULL;
+}
+
+static inline const struct scmi_handle *devm_scmi_handle_get(struct device *dev)
+{
+ return NULL;
+}
+#endif /* CONFIG_ARM_SCMI_PROTOCOL */
diff --git a/include/linux/scpi_protocol.h b/include/linux/scpi_protocol.h
index dc5f989be226..327d65663dbf 100644
--- a/include/linux/scpi_protocol.h
+++ b/include/linux/scpi_protocol.h
@@ -67,6 +67,9 @@ struct scpi_ops {
int (*dvfs_get_idx)(u8);
int (*dvfs_set_idx)(u8, u8);
struct scpi_dvfs_info *(*dvfs_get_info)(u8);
+ int (*device_domain_id)(struct device *);
+ int (*get_transition_latency)(struct device *);
+ int (*add_opps_to_device)(struct device *);
int (*sensor_get_capability)(u16 *sensors);
int (*sensor_get_info)(u16 sensor_id, struct scpi_sensor_info *);
int (*sensor_get_value)(u16, u64 *);
diff --git a/linaro/configs/vexpress64.conf b/linaro/configs/vexpress64.conf
index 186cb7c2f744..61523c809334 100644
--- a/linaro/configs/vexpress64.conf
+++ b/linaro/configs/vexpress64.conf
@@ -88,3 +88,13 @@ CONFIG_SND_SOC_SPDIF=y
CONFIG_SND_SIMPLE_CARD=y
CONFIG_DMADEVICES=y
CONFIG_PL330_DMA=y
+CONFIG_CPU_FREQ_STAT=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_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_ARM_SCMI_CPUFREQ=y
+CONFIG_SENSORS_ARM_SCMI=y
+CONFIG_COMMON_CLK_SCMI=y
+CONFIG_ARM_SCMI_PROTOCOL=y