diff options
71 files changed, 7264 insertions, 247 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/display/virtual-encoder.txt b/Documentation/devicetree/bindings/display/virtual-encoder.txt new file mode 100644 index 000000000000..3a9b8221c3ec --- /dev/null +++ b/Documentation/devicetree/bindings/display/virtual-encoder.txt @@ -0,0 +1,74 @@ +DRM Virtual Encoder + +The DRM Virtual Encoder is a component-based basic encoder that fetches +the display timings information from the device tree and "discovers" a +DRM output with the given data. It is helpful in a simulated environment +where there is no actual hardware to be probed and the configuration of +the display happens outside the kernel world. + +Required properties: + - compatible: should be "drm,virtual-encoder" + +Required sub-nodes: + - display-timings: node describing the virtual output timings information, + as specified in panel/display-timing.txt file. + - port: the input port connection as modelled using the OF graph bindings + specified in Documentation/devicetree/bindings/graph.txt + + +Example: + +/ { + ... + + vencoder { + compatible = "drm,virtual-encoder"; + display-timings { + native-mode = <&timing1>; + timing0: timing@0 { + /* 640x480 framebuffer */ + clock-frequency = <23750>; + hactive = <640>; + vactive = <480>; + hfront-porch = <48>; + hback-porch = <16>; + hsync-len = <96>; + vfront-porch = <33>; + vback-porch = <9>; + vsync-len = <3>; + }; + timing1: timing@1 { + /* 1280x720 framebuffer */ + clock-frequency = <74440000>; + hactive = <1280>; + vactive = <720>; + hfront-porch = <56>; + hback-porch = <192>; + hsync-len = <136>; + vfront-porch = <1>; + vback-porch = <22>; + vsync-len = <3>; + }; + }; + + port { + vencoder_in: endpoint { + remote-endpoint = <&driver_out>; + }; + }; + }; + + drm_driver: driver@f00bad { + ... + + port { + driver_out: endpoint { + remote-endpoint = <&vencoder_in>; + }; + }; + + ... + }; + + ... +}; 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/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index bd003dba8696..4e76baa05021 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -962,6 +962,9 @@ dtb-$(CONFIG_ARCH_VERSATILE) += \ versatile-ab.dtb \ versatile-pb.dtb dtb-$(CONFIG_ARCH_VEXPRESS) += \ + juno.dtb \ + juno-r1.dtb \ + juno-r2.dtb \ fvp-base-aemv8a-aemv8a.dtb \ fvp-base-aemv8a-aemv8a-t1.dtb \ vexpress-v2p-ca5s.dtb \ diff --git a/arch/arm/boot/dts/juno-base.dtsi b/arch/arm/boot/dts/juno-base.dtsi new file mode 120000 index 000000000000..5bf5772d7718 --- /dev/null +++ b/arch/arm/boot/dts/juno-base.dtsi @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno-base.dtsi
\ No newline at end of file diff --git a/arch/arm/boot/dts/juno-clocks.dtsi b/arch/arm/boot/dts/juno-clocks.dtsi new file mode 120000 index 000000000000..d26c206771d4 --- /dev/null +++ b/arch/arm/boot/dts/juno-clocks.dtsi @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno-clocks.dtsi
\ No newline at end of file diff --git a/arch/arm/boot/dts/juno-cs-r1r2.dtsi b/arch/arm/boot/dts/juno-cs-r1r2.dtsi new file mode 120000 index 000000000000..08c059e69464 --- /dev/null +++ b/arch/arm/boot/dts/juno-cs-r1r2.dtsi @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno-cs-r1r2.dtsi
\ No newline at end of file diff --git a/arch/arm/boot/dts/juno-motherboard.dtsi b/arch/arm/boot/dts/juno-motherboard.dtsi new file mode 120000 index 000000000000..a4e1f71b8533 --- /dev/null +++ b/arch/arm/boot/dts/juno-motherboard.dtsi @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno-motherboard.dtsi
\ No newline at end of file diff --git a/arch/arm/boot/dts/juno-r1.dts b/arch/arm/boot/dts/juno-r1.dts new file mode 120000 index 000000000000..f0bf74937285 --- /dev/null +++ b/arch/arm/boot/dts/juno-r1.dts @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno-r1.dts
\ No newline at end of file diff --git a/arch/arm/boot/dts/juno-r2.dts b/arch/arm/boot/dts/juno-r2.dts new file mode 120000 index 000000000000..aba7e1fac440 --- /dev/null +++ b/arch/arm/boot/dts/juno-r2.dts @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno-r2.dts
\ No newline at end of file diff --git a/arch/arm/boot/dts/juno-sched-energy.dtsi b/arch/arm/boot/dts/juno-sched-energy.dtsi new file mode 120000 index 000000000000..3508c8b8438f --- /dev/null +++ b/arch/arm/boot/dts/juno-sched-energy.dtsi @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno-sched-energy.dtsi
\ No newline at end of file diff --git a/arch/arm/boot/dts/juno.dts b/arch/arm/boot/dts/juno.dts new file mode 120000 index 000000000000..186e53545ef2 --- /dev/null +++ b/arch/arm/boot/dts/juno.dts @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno.dts
\ No newline at end of file diff --git a/arch/arm/boot/dts/juno_r2-sched-energy.dtsi b/arch/arm/boot/dts/juno_r2-sched-energy.dtsi new file mode 120000 index 000000000000..0d000be54a83 --- /dev/null +++ b/arch/arm/boot/dts/juno_r2-sched-energy.dtsi @@ -0,0 +1 @@ +../../../arm64/boot/dts/arm/juno_r2-sched-energy.dtsi
\ No newline at end of file diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index 35714ff6f467..4582c33b152f 100644 --- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi @@ -222,6 +222,9 @@ dvi-transmitter@39 { compatible = "sil,sii9022-tpi", "sil,sii9022"; reg = <0x39>; + + v2m_dvi_port: port { + }; }; dvi-transmitter@60 { @@ -245,7 +248,7 @@ reg-shift = <2>; }; - clcd@1f0000 { + v2m_clcd: clcd@1f0000 { compatible = "arm,pl111", "arm,primecell"; reg = <0x1f0000 0x1000>; interrupt-names = "combined"; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts index 0c8de0ca73ee..cedef43b3944 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts @@ -57,6 +57,22 @@ interrupts = <0 85 4>; clocks = <&hdlcd_clk>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2b0a0000 { @@ -294,3 +310,17 @@ <0 3 &gic 0 39 4>; }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 65ecf206388c..aec0df2b6d40 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -116,6 +116,22 @@ interrupts = <0 85 4>; clocks = <&hdlcd_clk>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2b0a0000 { @@ -657,3 +673,17 @@ <0 3 &gic 0 39 4>; }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts index 6e69b8e6c1a7..8bc8f71c7d28 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts @@ -59,6 +59,22 @@ interrupts = <0 85 4>; clocks = <&hdlcd_clk>; clock-names = "pxlclk"; + + port { + hdlcd0_output: endpoint@0 { + remote-endpoint = <&sii9022_0_input>; + }; + }; + }; + + hdmi0: connector@0 { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi0_connector_output: endpoint { + remote-endpoint = <&sii9022_0_output>; + }; + }; }; memory-controller@2a150000 { @@ -264,3 +280,17 @@ <0 3 &gic 0 39 4>; }; }; + +&v2m_clcd { + status = "disabled"; +}; + +&v2m_dvi_port { + sii9022_0_input: endpoint@0 { + remote-endpoint = <&hdlcd0_output>; + }; + + sii9022_0_output: endpoint@1 { + remote-endpoint = <&hdmi0_connector_output>; + }; +}; diff --git a/arch/arm/mach-vexpress/Kconfig b/arch/arm/mach-vexpress/Kconfig index ed579382d41f..834084feccbb 100644 --- a/arch/arm/mach-vexpress/Kconfig +++ b/arch/arm/mach-vexpress/Kconfig @@ -23,6 +23,7 @@ menuconfig ARCH_VEXPRESS select VEXPRESS_CONFIG select VEXPRESS_SYSCFG select MFD_VEXPRESS_SYSREG + select PM_GENERIC_DOMAINS help This option enables support for systems using Cortex processor based ARM core and logic (FPGA) tiles on the Versatile Express motherboard, diff --git a/arch/arm64/boot/dts/arm/juno-base.dtsi b/arch/arm64/boot/dts/arm/juno-base.dtsi index bfe7d683a42e..c55e1edf2986 100644 --- a/arch/arm64/boot/dts/arm/juno-base.dtsi +++ b/arch/arm64/boot/dts/arm/juno-base.dtsi @@ -1,4 +1,5 @@ #include "juno-clocks.dtsi" +#include <dt-bindings/display/tda998x.h> / { /* @@ -22,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"; }; @@ -52,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 { @@ -94,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>; @@ -123,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; @@ -139,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>; @@ -178,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; @@ -195,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>; @@ -222,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>; @@ -252,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>; @@ -266,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>; @@ -286,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>; @@ -330,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>; @@ -344,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>; @@ -358,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>; @@ -378,7 +433,7 @@ clocks = <&soc_smc50mhz>; clock-names = "apb_pclk"; - power-domains = <&scpi_devpd 0>; + power-domains = <&scmi_devpd 8>; ports { #address-cells = <1>; @@ -419,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 { @@ -452,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","gpuclk"; - }; - scpi_clk: scpi-clk { - compatible = "arm,scpi-variable-clocks"; - #clock-cells = <1>; - clock-indices = <3>; - clock-output-names = "pxlclk"; - }; + 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>; }; }; @@ -490,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"; }; }; @@ -546,7 +600,6 @@ <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>; #iommu-cells = <1>; #global-interrupts = <1>; - status = "disabled"; }; smmu_hdlcd0: iommu@7fb20000 { @@ -556,7 +609,6 @@ <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>; #iommu-cells = <1>; #global-interrupts = <1>; - status = "disabled"; }; smmu_usb: iommu@7fb30000 { @@ -567,10 +619,9 @@ #iommu-cells = <1>; #global-interrupts = <1>; dma-coherent; - status = "disabled"; }; - dma@7ff00000 { + dma0: dma@7ff00000 { compatible = "arm,pl330", "arm,primecell"; reg = <0x0 0x7ff00000 0 0x1000>; #dma-cells = <1>; @@ -597,13 +648,13 @@ clocks = <&soc_faxiclk>; clock-names = "apb_pclk"; }; - +/* hdlcd@7ff50000 { compatible = "arm,hdlcd"; 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 { @@ -612,13 +663,13 @@ }; }; }; - +*/ hdlcd@7ff60000 { compatible = "arm,hdlcd"; 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 { @@ -646,16 +697,18 @@ i2c-sda-hold-time-ns = <500>; clocks = <&soc_smc50mhz>; - hdmi-transmitter@70 { + hdmi_transmitter0: hdmi-transmitter@70 { compatible = "nxp,tda998x"; reg = <0x70>; + #sound-dai-cells = <0>; + audio-ports = <TDA998x_I2S 0x03>; port { tda998x_0_input: tda998x-0-endpoint { remote-endpoint = <&hdlcd0_output>; }; }; }; - +/* hdmi-transmitter@71 { compatible = "nxp,tda998x"; reg = <0x71>; @@ -665,7 +718,7 @@ }; }; }; - }; +*/ }; ohci@7ffb0000 { compatible = "generic-ohci"; @@ -699,6 +752,35 @@ <0x00000008 0x80000000 0x1 0x80000000>; }; + soc_i2s: i2s@7ff90000 { + compatible = "snps,designware-i2s"; + reg = <0x0 0x7ff90000 0x0 0x1000>; + clocks = <&scmi_clk 5>, <&soc_refclk100mhz>; + clock-names = "i2sclk", "apb_pclk"; + #sound-dai-cells = <0>; + dmas = <&dma0 5>; + dma-names = "tx"; + }; + + hdmi_audio: hdmi_audio@0 { + compatible = "linux,hdmi-audio"; + #sound-dai-cells = <0>; + }; + + sound { + compatible = "simple-audio-card"; + + simple-audio-card,format = "i2s"; + + simple-audio-card,cpu { + sound-dai = <&soc_i2s>; + }; + + simple-audio-card,codec { + sound-dai = <&hdmi_transmitter0>; + }; + }; + smb@8000000 { compatible = "simple-bus"; #address-cells = <2>; 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 0e8943ab94d7..8b2501e73cbe 100644 --- a/arch/arm64/boot/dts/arm/juno-r1.dts +++ b/arch/arm64/boot/dts/arm/juno-r1.dts @@ -14,7 +14,7 @@ / { model = "ARM Juno development board (r1)"; - compatible = "arm,juno-r1", "arm,juno", "arm,vexpress"; + compatible = "arm,juno-r1", "arm,juno"; interrupt-parent = <&gic>; #address-cells = <2>; #size-cells = <2>; @@ -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 405e2fba025b..dab2e49befef 100644 --- a/arch/arm64/boot/dts/arm/juno-r2.dts +++ b/arch/arm64/boot/dts/arm/juno-r2.dts @@ -14,7 +14,7 @@ / { model = "ARM Juno development board (r2)"; - compatible = "arm,juno-r2", "arm,juno", "arm,vexpress"; + compatible = "arm,juno-r2", "arm,juno"; interrupt-parent = <&gic>; #address-cells = <2>; #size-cells = <2>; @@ -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 0220494c9b80..1d05029687ff 100644 --- a/arch/arm64/boot/dts/arm/juno.dts +++ b/arch/arm64/boot/dts/arm/juno.dts @@ -13,7 +13,7 @@ / { model = "ARM Juno development board (r0)"; - compatible = "arm,juno", "arm,vexpress"; + compatible = "arm,juno"; interrupt-parent = <&gic>; #address-cells = <2>; #size-cells = <2>; @@ -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 6e4ed5a9c6fd..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 @@ -48,6 +82,10 @@ config ARM_SCPI_POWER_DOMAIN This enables support for the SCPI power domains which can be enabled or disabled via the SCP firmware +config ARM_SCPI_PROTOCOL_TEST + tristate "Test code for SCPI Message Protocol" + default ARM_SCPI_PROTOCOL + config EDD tristate "BIOS Enhanced Disk Drive calls determine boot disk" depends on X86 diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index a37f12e8d137..102b722e2404 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_ARM_PSCI_FW) += psci.o obj-$(CONFIG_ARM_PSCI_CHECKER) += psci_checker.o obj-$(CONFIG_ARM_SCPI_PROTOCOL) += arm_scpi.o obj-$(CONFIG_ARM_SCPI_POWER_DOMAIN) += scpi_pm_domain.o +obj-$(CONFIG_ARM_SCPI_PROTOCOL_TEST) += arm_scpi_test.o obj-$(CONFIG_DMI) += dmi_scan.o obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o obj-$(CONFIG_EDD) += edd.o @@ -23,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 f6cfc31d34c7..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> @@ -87,8 +88,6 @@ #define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS) #define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK) -#define MAX_RX_TIMEOUT (msecs_to_jiffies(30)) - enum scpi_error_codes { SCPI_SUCCESS = 0, /* Success */ SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */ @@ -480,6 +479,18 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) mem->command = cpu_to_le32(t->cmd); } +static void scpi_tx_done(struct mbox_client *c, void *msg, int result) +{ + struct scpi_xfer *t = msg; + + if (!t->rx_buf) + complete(&t->done); + /* + * Messages with rx_buf are expecting a reply and will be on the + * rx_pending list, so leave them alone. + */ +} + static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch) { struct scpi_xfer *t; @@ -541,17 +552,24 @@ static int scpi_send_message(u8 idx, void *tx_buf, unsigned int tx_len, reinit_completion(&msg->done); ret = mbox_send_message(scpi_chan->chan, msg); - if (ret < 0 || !rx_buf) - goto out; + if (ret >= 0) { + /* + * Wait for message to be processed. If we end up having to wait + * for a very long time then there is a serious bug, probably in + * the firmware. + * + * IMPORTANT: We must not try and continue after the timeout + * because this driver and the mailbox framework still has data + * structures referring to the failed request and further + * serious bugs will result. + */ + if (!wait_for_completion_timeout(&msg->done, msecs_to_jiffies(10000))) + BUG(); - if (!wait_for_completion_timeout(&msg->done, MAX_RX_TIMEOUT)) - ret = -ETIMEDOUT; - else /* first status word */ - ret = msg->status; -out: - if (ret < 0 && rx_buf) /* remove entry from the list if timed-out */ - scpi_process_cmd(scpi_chan, msg->cmd); + if (rx_buf) + ret = le32_to_cpu(msg->status); + } put_scpi_xfer(msg, scpi_chan); /* SCPI error codes > 0, translate them to Linux scale*/ @@ -642,6 +660,8 @@ static int opp_cmp_func(const void *opp1, const void *opp2) return t1->freq - t2->freq; } +static bool juno_cpufreq_limit_hack = 0; + static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) { struct scpi_dvfs_info *info; @@ -680,10 +700,77 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) sort(info->opps, info->count, sizeof(*opp), opp_cmp_func, NULL); + /* + * Juno silicon doesn't seem to be able to run the big cluster + * (domain == 0) at max frequency in AArch32 mode (it produces + * random and weird crashes) so drop the highest OPP in that case... + */ + if (juno_cpufreq_limit_hack && domain == 0) + --info->count; + scpi_info->dvfs[domain] = info; 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; @@ -765,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, @@ -893,6 +983,10 @@ static int scpi_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; + if (IS_ENABLED(CONFIG_ARM) && of_find_compatible_node(NULL,NULL,"arm,juno")) { + juno_cpufreq_limit_hack = true; + } + scpi_info = devm_kzalloc(dev, sizeof(*scpi_info), GFP_KERNEL); if (!scpi_info) return -ENOMEM; @@ -935,8 +1029,7 @@ static int scpi_probe(struct platform_device *pdev) cl->dev = dev; cl->rx_callback = scpi_handle_remote_msg; cl->tx_prepare = scpi_tx_prepare; - cl->tx_block = true; - cl->tx_tout = 20; + cl->tx_done = scpi_tx_done; cl->knows_txdone = false; /* controller can't ack */ INIT_LIST_HEAD(&pchan->rx_pending); diff --git a/drivers/firmware/arm_scpi_test.c b/drivers/firmware/arm_scpi_test.c new file mode 100644 index 000000000000..c21b35e6f4b8 --- /dev/null +++ b/drivers/firmware/arm_scpi_test.c @@ -0,0 +1,568 @@ +/* + * Test code for System Control and Power Interface (SCPI) + * + * Copyright (C) 2015 Linaro 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/>. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/scpi_protocol.h> + + +static int stress_time; +module_param(stress_time, int, 0644); +MODULE_PARM_DESC(stress_time, "Number of seconds to run each stress test for, overides each test's default."); + +static int run; +static struct kernel_param_ops run_ops; +module_param_cb(run, &run_ops, &run, 0644); +MODULE_PARM_DESC(run, "The number of the test case to run, or -1 for all, 0 to stop tests."); + + +static struct scpi_ops *scpi; + +static struct task_struct *main_thread; + +static DEFINE_MUTEX(main_thread_lock); + + +#define MAX_TEST_THREADS 4 + +static struct test_thread { + struct task_struct *task; + int thread_num; +} test_threads[MAX_TEST_THREADS]; + +static DEFINE_MUTEX(thread_lock); + + +#define MAX_POWER_DOMAINS 8 + +static u16 num_sensors; +static u8 num_pd; +static u8 num_opps[MAX_POWER_DOMAINS]; +static struct mutex pd_lock[MAX_POWER_DOMAINS]; +static u8 num_devices_with_power_states; + + +#define FLAG_SERIAL_DVFS (1<<0) +#define FLAG_SERIAL_PD (1<<1) + +static int test_flags; + + +static int sensor_pmic = -1; + + +static u32 random_seed; + +static u32 random(u32 range) +{ + random_seed = random_seed*69069+1; + + return ((u64)random_seed * (u64)range) >> 32; +} + + +static atomic_t passes; +static atomic_t failures; + +static bool fail_on(bool fail) +{ + if (fail) + atomic_inc(&failures); + else + atomic_inc(&passes); + return fail; +} + +static void show_results(const char *title) +{ + int fail = atomic_xchg(&failures, 0); + int pass = atomic_xchg(&passes, 0); + + if (fail) + pr_err("Results for '%s' is %d/%d (pass/fail)\n", title, pass, fail); + else + pr_info("Results for '%s' is %d/%d (pass/fail)\n", title, pass, fail); +} + + +static bool check_name(const char *name) +{ + char c; + + if (!isalpha(*name++)) + return false; + + while ((c = *name++)) + if (!isalnum(c) && c != '_') + return false; + + return true; +} + +static u64 get_sensor(u16 id) +{ + u64 val; + int ret; + + ret = scpi->sensor_get_value(id, &val); + if (fail_on(ret < 0)) + pr_err("FAILED sensor_get_value %d (%d)\n", id, ret); + + return val; +} + +static void init_sensors(void) +{ + u16 id; + int ret; + + ret = scpi->sensor_get_capability(&num_sensors); + if (fail_on(ret)) + pr_err("FAILED sensor_get_capability (%d)\n", ret); + + pr_info("num_sensors: %d\n", num_sensors); + + for (id = 0; id < num_sensors; id++) { + + struct scpi_sensor_info info; + char name[sizeof(info.name) + 1]; + + ret = scpi->sensor_get_info(id, &info); + if (fail_on(ret)) { + pr_err("FAILED sensor_get_info (%d)\n", ret); + continue; + } + + /* Get sensor name, guarding against missing NUL terminator */ + memcpy(name, info.name, sizeof(info.name)); + name[sizeof(info.name)] = 0; + + pr_info("sensor[%d] id=%d class=%d trigger=%d name=%s\n", id, + info.sensor_id, info.class, info.trigger_type, name); + + if (fail_on(id != info.sensor_id)) + pr_err("FAILED bad sensor id\n"); + if (fail_on(info.class > 4)) + pr_err("FAILED bad sensor class\n"); + if (fail_on(info.trigger_type > 3)) + pr_err("FAILED bad sensor trigger type\n"); + if (fail_on(strlen(name) >= sizeof(info.name) || !check_name(name))) + pr_err("FAILED bad name\n"); + + pr_info("sensor[%d] value is %llu\n", id, get_sensor(id)); + if (strstr(name, "PMIC")) + sensor_pmic = id; + } +} + +static int get_dvfs(u8 pd) +{ + int ret = scpi->dvfs_get_idx(pd); + + if (fail_on(ret < 0)) + pr_err("FAILED get_dvfs %d (%d)\n", pd, ret); + else if (fail_on(ret >= num_opps[pd])) + pr_err("FAILED get_dvfs %d returned out of range index (%d)\n", pd, ret); + + return ret; +} + +static int set_dvfs(u8 pd, u8 opp) +{ + int ret; + + if (test_flags & FLAG_SERIAL_DVFS) + mutex_lock(&pd_lock[0]); + else if (test_flags & FLAG_SERIAL_PD) + mutex_lock(&pd_lock[pd]); + + ret = scpi->dvfs_set_idx(pd, opp); + + if (test_flags & FLAG_SERIAL_DVFS) + mutex_unlock(&pd_lock[0]); + else if (test_flags & FLAG_SERIAL_PD) + mutex_unlock(&pd_lock[pd]); + + if (fail_on(ret < 0)) + pr_err("FAILED set_dvfs %d %d (%d)\n", pd, opp, ret); + + return ret; +} + +static void init_dvfs(void) +{ + u8 pd; + + for (pd = 0; pd < MAX_POWER_DOMAINS; ++pd) { + struct scpi_dvfs_info *info; + int opp; + + info = scpi->dvfs_get_info(pd); + if (IS_ERR(info)) { + pr_info("dvfs_get_info %d failed with %d assume because no more power domains\n", + pd, (int)PTR_ERR(info)); + break; + } + + num_opps[pd] = info->count; + mutex_init(&pd_lock[pd]); + pr_info("pd[%d] count=%u latency=%u\n", + pd, info->count, info->latency); + + opp = get_dvfs(pd); + pr_info("pd[%d] current opp=%d\n", pd, opp); + + for (opp = 0; opp < info->count; ++opp) { + pr_info("pd[%d].opp[%d] freq=%u m_volt=%u\n", pd, opp, + info->opps[opp].freq, info->opps[opp].m_volt); + /* + * Try setting each opp. Note, failure is not necessarily + * an error because cpufreq may be setting values too. + */ + set_dvfs(pd, opp); + if (get_dvfs(pd) == opp) + pr_info("pd[%d] set to opp %d OK\n", pd, opp); + else + pr_warn("pd[%d] failed to set opp to %d\n", pd, opp); + } + } + + if (!pd) { + /* Assume device should have at least one DVFS power domain */ + pr_err("FAILED no power domains\n"); + fail_on(true); + } + num_pd = pd; +} + +static int device_get_power_state(u16 dev_id) +{ + int ret; + + ret = scpi->device_get_power_state(dev_id); + if (fail_on(ret < 0)) + pr_err("FAILED device_get_power_state %d (%d)\n", dev_id, ret); + + return ret; +} + +static int device_set_power_state(u16 dev_id, u8 pstate) +{ + int ret; + + ret = scpi->device_set_power_state(dev_id, pstate); + if (fail_on(ret < 0)) + pr_err("FAILED device_get_power_state %d (%d)\n", dev_id, ret); + + return ret; +} + +static void init_device_power_states(void) +{ + u16 dev_id; + + for (dev_id = 0; dev_id < 0xffff; ++dev_id) { + int state = scpi->device_get_power_state(dev_id); + + if (state < 0) { + pr_info("device_get_power_state %d failed with %d assume because no more devices\n", + dev_id, state); + break; + } + + pr_info("device[%d] current state=%d\n", dev_id, state); + + device_set_power_state(dev_id, state); + if (device_get_power_state(dev_id) == state) + pr_info("device[%d] set state to %d OK\n", dev_id, state); + else + pr_warn("device[%d] failed set state to %d\n", dev_id, state); + } + + if (!dev_id) { + /* Assume device should have at least one device power state */ + pr_err("FAILED no devices with power states\n"); + fail_on(true); + } + num_devices_with_power_states = dev_id; +} + +static int stress_pmic(void *data) +{ + int sensor, pd, opp; + + while (!kthread_should_stop()) { + sensor = sensor_pmic; + pd = random(num_pd); + opp = random(num_opps[pd]); + + switch (random(3)) { + case 0: + if (sensor >= 0) { + get_sensor(sensor); + break; + } + /* If no sensor, do DFVS... */ + case 1: + set_dvfs(pd, opp); + break; + default: + msleep(random(20)); + break; + } + } + + return 0; +} + +static int stress_all(void *data) +{ + int sensor, pd, opp; + + while (!kthread_should_stop()) { + sensor = random(num_sensors); + pd = random(num_pd); + opp = random(num_opps[pd]); + + switch (random(4)) { + case 0: + set_dvfs(pd, opp); + break; + case 1: + opp = get_dvfs(pd); + break; + case 2: + get_sensor(sensor); + break; + default: + msleep(random(20)); + break; + } + } + + return 0; +} + +struct test { + const char *title; + int (*thread_fn)(void *); + int flags; + int num_threads; + int duration; +}; + +static void stop_test_threads(void) +{ + int t, ret; + + for (t = 0; t < MAX_TEST_THREADS; ++t) { + struct test_thread *thread = &test_threads[t]; + + mutex_lock(&thread_lock); + if (thread->task) { + ret = kthread_stop(thread->task); + thread->task = NULL; + if (ret) + pr_warn("Test thread %d exited with status %d\n", t, ret); + } + mutex_unlock(&thread_lock); + } +} + +static void run_test(struct test *test) +{ + int num_threads = min(test->num_threads, MAX_TEST_THREADS); + int duration = stress_time; + int t; + + if (test->duration <= 0) + duration = 0; + else if (duration <= 0) + duration = test->duration; + + pr_info("Running test '%s' for %d seconds\n", test->title, duration); + + test_flags = test->flags; + + for (t = 0; t < num_threads; ++t) { + struct test_thread *thread = &test_threads[t]; + struct task_struct *task; + + mutex_lock(&thread_lock); + thread->thread_num = t; + task = kthread_run(test->thread_fn, thread, "scpi-test-%d", t); + if (IS_ERR(task)) + pr_warn("Failed to create test thread %d\n", t); + else + thread->task = task; + mutex_unlock(&thread_lock); + } + + schedule_timeout_interruptible(msecs_to_jiffies(duration * 1000)); + + stop_test_threads(); + + show_results(test->title); +} + +static struct test tests[] = { + {"Stress All, concurrent DVFS", + stress_all, 0, MAX_TEST_THREADS, 60}, + {"Stress All, concurrent DVFS on different PDs", + stress_all, FLAG_SERIAL_PD, MAX_TEST_THREADS, 60}, + {"Stress All, no concurrent DVFS", + stress_all, FLAG_SERIAL_DVFS, MAX_TEST_THREADS, 60}, + {"Stress PMIC, concurrent DVFS", + stress_pmic, 0, MAX_TEST_THREADS, 60}, + {"Stress PMIC, concurrent DVFS on different PDs", + stress_pmic, FLAG_SERIAL_PD, MAX_TEST_THREADS, 60}, + {"Stress PMIC, no concurrent DVFS", + stress_pmic, FLAG_SERIAL_DVFS, MAX_TEST_THREADS, 60}, + {} +}; + +static int main_thread_fn(void *data) +{ + struct test *test = tests; + int i = 1; + + for (; test->title && !kthread_should_stop(); ++test, ++i) + if (run < 0 || run == i) + run_test(test); + + run = 0; + return 0; +} + +static DEFINE_MUTEX(setup_lock); + +static int setup(void) +{ + int ret = 0; + + mutex_lock(&setup_lock); + + if (!scpi) { + int tries = 12; + + pr_info("Initial setup\n"); + while ((scpi = get_scpi_ops()) == 0 && --tries) { + pr_info("Waiting for get_scpi_ops\n"); + msleep(5000); + } + + if (scpi) { + init_sensors(); + init_dvfs(); + init_device_power_states(); + show_results("Initial setup"); + } else { + pr_err("Given up on get_scpi_ops\n"); + ret = -ENODEV; + } + } + + mutex_unlock(&setup_lock); + + return ret; +} + +static int start_tests(void) +{ + struct task_struct *task; + int ret; + + ret = setup(); + if (ret) { + run = 0; + return ret; + } + + pr_info("Creating main thread\n"); + mutex_lock(&main_thread_lock); + if (main_thread) { + ret = -EBUSY; + } else { + task = kthread_run(main_thread_fn, 0, "scpi-test-main"); + if (IS_ERR(task)) + ret = PTR_ERR(task); + else + main_thread = task; + } + mutex_unlock(&main_thread_lock); + + if (ret) { + pr_err("Failed to create main thread (%d)\n", ret); + run = 0; + } + + return ret; +} + +static void stop_tests(void) +{ + pr_info("Stopping tests\n"); + mutex_lock(&main_thread_lock); + if (main_thread) + kthread_stop(main_thread); + main_thread = NULL; + mutex_unlock(&main_thread_lock); +} + +static int param_set_running(const char *val, const struct kernel_param *kp) +{ + int ret; + + ret = param_set_int(val, kp); + if (!ret) { + if (run) + ret = start_tests(); + else + stop_tests(); + } + + return ret; +} + +static struct kernel_param_ops run_ops = { + .set = param_set_running, + .get = param_get_int, +}; + + +static int scpi_test_init(void) +{ + return 0; +} + +static void scpi_test_exit(void) +{ + stop_tests(); +} + +module_init(scpi_test_init); +module_exit(scpi_test_exit); + + +MODULE_AUTHOR("Jon Medhurst (Tixy) <tixy@linaro.org>"); +MODULE_DESCRIPTION("ARM SCPI driver tests"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 78d7fc0ebb57..7d7e0fd19edf 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -203,6 +203,14 @@ config DRM_VGEM as used by Mesa's software renderer for enhanced performance. If M is selected the module will be called vgem. +config DRM_VIRT_ENCODER + tristate "Virtual OF-based encoder" + depends on DRM && OF + help + Choose this option to get a virtual encoder and its associated + connector that will use the device tree to read the display + timings information. If M is selected the module will be called + drm_vencoder. source "drivers/gpu/drm/exynos/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 59f0f9b696eb..4eb316e8fa2b 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -44,6 +44,9 @@ obj-$(CONFIG_DRM_DEBUG_MM_SELFTEST) += selftests/ CFLAGS_drm_trace_points.o := -I$(src) +drm_vencoder-y := drm_virtual_encoder.o +obj-$(CONFIG_DRM_VIRT_ENCODER) += drm_vencoder.o + obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-$(CONFIG_DRM_ARM) += arm/ diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile index bb8b158ff90d..0ade075b3568 100644 --- a/drivers/gpu/drm/arm/Makefile +++ b/drivers/gpu/drm/arm/Makefile @@ -1,4 +1,4 @@ -hdlcd-y := hdlcd_drv.o hdlcd_crtc.o +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o hdlcd_fb_helper.o obj-$(CONFIG_DRM_HDLCD) += hdlcd.o mali-dp-y := malidp_drv.o malidp_hw.o malidp_planes.o malidp_crtc.o obj-$(CONFIG_DRM_MALI_DISPLAY) += mali-dp.o diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c index 1a3359c0f6cd..9cbb8ca2459e 100644 --- a/drivers/gpu/drm/arm/hdlcd_crtc.c +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -15,7 +15,7 @@ #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_helper.h> -#include <drm/drm_fb_cma_helper.h> +#include "hdlcd_fb_helper.h" #include <drm/drm_gem_cma_helper.h> #include <drm/drm_of.h> #include <drm/drm_plane_helper.h> @@ -83,6 +83,8 @@ static int hdlcd_set_pxl_fmt(struct drm_crtc *crtc) struct hdlcd_drm_private *hdlcd = crtc_to_hdlcd_priv(crtc); const struct drm_framebuffer *fb = crtc->primary->state->fb; uint32_t pixel_format; + bool swap_red_blue = false; + u32 hbi; struct simplefb_format *format = NULL; int i; @@ -110,16 +112,36 @@ static int hdlcd_set_pxl_fmt(struct drm_crtc *crtc) * pixel is outside the visible frame area or when there is a * buffer underrun. */ - hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset | + if (of_property_read_u32(of_root, "arm,hbi", &hbi) == 0) { + /* + * This is a hack to swap read and blue when building for some + * Versatile Express CoreTiles because they seem to be wired up + * differently. + */ + if (hbi == 0x249) /* TC2 */ + swap_red_blue = true; + } + if(!swap_red_blue) { + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->red.offset | #ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN - 0x00ff0000 | /* show underruns in red */ + 0x00ff0000 | /* show underruns in red */ #endif - ((format->red.length & 0xf) << 8)); - hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | - ((format->green.length & 0xf) << 8)); - hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset | - ((format->blue.length & 0xf) << 8)); - + ((format->red.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | + ((format->green.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->blue.offset | + ((format->blue.length & 0xf) << 8)); + } else { + hdlcd_write(hdlcd, HDLCD_REG_BLUE_SELECT, format->red.offset | +#ifdef CONFIG_DRM_HDLCD_SHOW_UNDERRUN + 0x00ff0000 | /* show underruns in red */ +#endif + ((format->red.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_GREEN_SELECT, format->green.offset | + ((format->green.length & 0xf) << 8)); + hdlcd_write(hdlcd, HDLCD_REG_RED_SELECT, format->blue.offset | + ((format->blue.length & 0xf) << 8)); + } return 0; } @@ -271,7 +293,7 @@ static void hdlcd_plane_atomic_update(struct drm_plane *plane, src_x = plane->state->src.x1 >> 16; src_y = plane->state->src.y1 >> 16; dest_h = drm_rect_height(&plane->state->dst); - gem = drm_fb_cma_get_gem_obj(fb, 0); + gem = hdlcd_fb_get_gem_obj(fb, 0); scanout_start = gem->paddr + fb->offsets[0] + src_y * fb->pitches[0] + diff --git a/drivers/gpu/drm/arm/hdlcd_drv.c b/drivers/gpu/drm/arm/hdlcd_drv.c index 0f49c4b12772..ea19a4781ba9 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.c +++ b/drivers/gpu/drm/arm/hdlcd_drv.c @@ -23,7 +23,7 @@ #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> #include <drm/drm_fb_helper.h> -#include <drm/drm_fb_cma_helper.h> +#include "hdlcd_fb_helper.h" #include <drm/drm_gem_cma_helper.h> #include <drm/drm_of.h> @@ -67,6 +67,12 @@ static int hdlcd_load(struct drm_device *drm, unsigned long flags) (version & HDLCD_VERSION_MAJOR_MASK) >> 8, version & HDLCD_VERSION_MINOR_MASK); + /* Make sure hardware is in a safe reset state */ + hdlcd_write(hdlcd, HDLCD_REG_COMMAND, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, 0); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR,~0); + hdlcd_write(hdlcd, HDLCD_REG_INT_RAWSTAT, 0); + /* Get the optional framebuffer memory resource */ ret = of_reserved_mem_device_init(drm->dev); if (ret && ret != -ENODEV) @@ -88,6 +94,9 @@ static int hdlcd_load(struct drm_device *drm, unsigned long flags) goto irq_fail; } + spin_lock_init(&hdlcd->frame_completion_lock); + init_completion(&hdlcd->frame_completion); + return 0; irq_fail: @@ -102,11 +111,11 @@ static void hdlcd_fb_output_poll_changed(struct drm_device *drm) { struct hdlcd_drm_private *hdlcd = drm->dev_private; - drm_fbdev_cma_hotplug_event(hdlcd->fbdev); + hdlcd_drm_fbdev_hotplug_event(hdlcd->fbdev); } static const struct drm_mode_config_funcs hdlcd_mode_config_funcs = { - .fb_create = drm_fb_cma_create, + .fb_create = hdlcd_fb_create, .output_poll_changed = hdlcd_fb_output_poll_changed, .atomic_check = drm_atomic_helper_check, .atomic_commit = drm_atomic_helper_commit, @@ -126,7 +135,7 @@ static void hdlcd_lastclose(struct drm_device *drm) { struct hdlcd_drm_private *hdlcd = drm->dev_private; - drm_fbdev_cma_restore_mode(hdlcd->fbdev); + hdlcd_drm_fbdev_restore_mode(hdlcd->fbdev); } static irqreturn_t hdlcd_irq(int irq, void *arg) @@ -134,6 +143,7 @@ static irqreturn_t hdlcd_irq(int irq, void *arg) struct drm_device *drm = arg; struct hdlcd_drm_private *hdlcd = drm->dev_private; unsigned long irq_status; + unsigned long flags; irq_status = hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS); @@ -154,6 +164,16 @@ static irqreturn_t hdlcd_irq(int irq, void *arg) if (irq_status & HDLCD_INTERRUPT_VSYNC) drm_crtc_handle_vblank(&hdlcd->crtc); + spin_lock_irqsave(&hdlcd->frame_completion_lock, flags); + if (hdlcd_read(hdlcd, HDLCD_REG_INT_STATUS) & HDLCD_INTERRUPT_DMA_END) { + /* Clear DMA_END interrupt here, under frame_completion_lock */ + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END); + irq_status &= ~HDLCD_INTERRUPT_DMA_END; + /* Wake up everyone waiting for frame completion */ + complete_all(&hdlcd->frame_completion); + } + spin_unlock_irqrestore(&hdlcd->frame_completion_lock, flags); + /* acknowledge interrupt(s) */ hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, irq_status); @@ -170,15 +190,18 @@ static void hdlcd_irq_preinstall(struct drm_device *drm) static int hdlcd_irq_postinstall(struct drm_device *drm) { -#ifdef CONFIG_DEBUG_FS struct hdlcd_drm_private *hdlcd = drm->dev_private; unsigned long irq_mask = hdlcd_read(hdlcd, HDLCD_REG_INT_MASK); +#ifdef CONFIG_DEBUG_FS /* enable debug interrupts */ irq_mask |= HDLCD_DEBUG_INT_MASK; +#endif + /* enable DMA completion interrupts */ + irq_mask |= HDLCD_INTERRUPT_DMA_END; hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); -#endif + return 0; } @@ -193,12 +216,34 @@ static void hdlcd_irq_uninstall(struct drm_device *drm) irq_mask &= ~HDLCD_DEBUG_INT_MASK; #endif - /* disable vsync interrupts */ - irq_mask &= ~HDLCD_INTERRUPT_VSYNC; + /* disable vsync and dma interrupts */ + irq_mask &= ~(HDLCD_INTERRUPT_VSYNC | HDLCD_INTERRUPT_DMA_END); hdlcd_write(hdlcd, HDLCD_REG_INT_MASK, irq_mask); } +void hdlcd_wait_for_frame_completion(struct drm_device *drm) +{ + struct hdlcd_drm_private *hdlcd = drm->dev_private; + + if (drm_crtc_vblank_get(&hdlcd->crtc)) + return; /* vblank interrupts not available so don't try and wait */ + + /* + * Clear pending interrupts and completions so we won't get signalled + * for any earlier frames, + */ + spin_lock_irq(&hdlcd->frame_completion_lock); + hdlcd_write(hdlcd, HDLCD_REG_INT_CLEAR, HDLCD_INTERRUPT_DMA_END); + reinit_completion(&hdlcd->frame_completion); + spin_unlock_irq(&hdlcd->frame_completion_lock); + + /* Wait for end of current frame */ + wait_for_completion_interruptible_timeout(&hdlcd->frame_completion, HZ / 10); + + drm_crtc_vblank_put(&hdlcd->crtc); +} + #ifdef CONFIG_DEBUG_FS static int hdlcd_show_underrun_count(struct seq_file *m, void *arg) { @@ -229,7 +274,7 @@ static int hdlcd_show_pxlclock(struct seq_file *m, void *arg) static struct drm_info_list hdlcd_debugfs_list[] = { { "interrupt_count", hdlcd_show_underrun_count, 0 }, { "clocks", hdlcd_show_pxlclock, 0 }, - { "fb", drm_fb_cma_debugfs_show, 0 }, + { "fb", hdlcd_fb_debugfs_show, 0 }, }; static int hdlcd_debugfs_init(struct drm_minor *minor) @@ -280,6 +325,8 @@ static int hdlcd_drm_bind(struct device *dev) struct drm_device *drm; struct hdlcd_drm_private *hdlcd; int ret; + struct device_node *node; + int preferred_bpp; hdlcd = devm_kzalloc(dev, sizeof(*hdlcd), GFP_KERNEL); if (!hdlcd) @@ -318,7 +365,15 @@ static int hdlcd_drm_bind(struct device *dev) drm_mode_config_reset(drm); drm_kms_helper_poll_init(drm); - hdlcd->fbdev = drm_fbdev_cma_init(drm, 32, + /* Try to pick the colour depth that Android user-side is hard-coded for */ + preferred_bpp = 16; + node = of_find_compatible_node(NULL,NULL,"arm,mali-midgard"); + if (node) { + of_node_put(node); + preferred_bpp = 32; /* If Mali present, assume 32bpp */ + } + + hdlcd->fbdev = hdlcd_drm_fbdev_init(drm, preferred_bpp, drm->mode_config.num_connector); if (IS_ERR(hdlcd->fbdev)) { @@ -335,7 +390,7 @@ static int hdlcd_drm_bind(struct device *dev) err_register: if (hdlcd->fbdev) { - drm_fbdev_cma_fini(hdlcd->fbdev); + hdlcd_drm_fbdev_fini(hdlcd->fbdev); hdlcd->fbdev = NULL; } err_fbdev: @@ -363,7 +418,7 @@ static void hdlcd_drm_unbind(struct device *dev) drm_dev_unregister(drm); if (hdlcd->fbdev) { - drm_fbdev_cma_fini(hdlcd->fbdev); + hdlcd_drm_fbdev_fini(hdlcd->fbdev); hdlcd->fbdev = NULL; } drm_kms_helper_poll_fini(drm); @@ -465,7 +520,19 @@ static struct platform_driver hdlcd_platform_driver = { }, }; -module_platform_driver(hdlcd_platform_driver); +static int __init hdlcd_init(void) +{ + return platform_driver_register(&hdlcd_platform_driver); +} + +static void __exit hdlcd_exit(void) +{ + platform_driver_unregister(&hdlcd_platform_driver); +} + +/* need late_initcall() so we load after i2c driver */ +late_initcall(hdlcd_init); +module_exit(hdlcd_exit); MODULE_AUTHOR("Liviu Dudau"); MODULE_DESCRIPTION("ARM HDLCD DRM driver"); diff --git a/drivers/gpu/drm/arm/hdlcd_drv.h b/drivers/gpu/drm/arm/hdlcd_drv.h index e3950a071152..e76d99cfeada 100644 --- a/drivers/gpu/drm/arm/hdlcd_drv.h +++ b/drivers/gpu/drm/arm/hdlcd_drv.h @@ -8,10 +8,12 @@ struct hdlcd_drm_private { void __iomem *mmio; struct clk *clk; - struct drm_fbdev_cma *fbdev; + struct hdlcd_drm_fbdev *fbdev; struct drm_crtc crtc; struct drm_plane *plane; struct drm_atomic_state *state; + spinlock_t frame_completion_lock; + struct completion frame_completion; #ifdef CONFIG_DEBUG_FS atomic_t buffer_underrun_count; atomic_t bus_error_count; @@ -36,4 +38,6 @@ static inline u32 hdlcd_read(struct hdlcd_drm_private *hdlcd, unsigned int reg) int hdlcd_setup_crtc(struct drm_device *dev); void hdlcd_set_scanout(struct hdlcd_drm_private *hdlcd); +void hdlcd_wait_for_frame_completion(struct drm_device *drm); + #endif /* __HDLCD_DRV_H__ */ diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.c b/drivers/gpu/drm/arm/hdlcd_fb_helper.c new file mode 100644 index 000000000000..1963dbc9fd3d --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.c @@ -0,0 +1,855 @@ +/* + * drm kms/fb cma (contiguous memory allocator) helper functions + * + * Copyright (C) 2012 Analog Device Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Based on udl_fbdev.c + * Copyright (C) 2012 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include "hdlcd_fb_helper.h" +#include <linux/dma-buf.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/reservation.h> + +#include "hdlcd_drv.h" +#include "hdlcd_regs.h" + +#define DEFAULT_FBDEFIO_DELAY_MS 50 + +#define MAX_FRAMES 2 + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg); + +/****************************************************************************** + * Code copied from drivers/gpu/drm/drm_fb_helper.c as of Linux 4.11-rc4 + ******************************************************************************/ + +/** + * Copy of drm_fb_helper_check_var modified to allow MAX_FRAMES * height + */ +static int hdlcd_fb_helper_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct drm_fb_helper *fb_helper = info->par; + struct drm_framebuffer *fb = fb_helper->fb; + int depth; + + if (var->pixclock != 0 || in_dbg_master()) + return -EINVAL; + + /* + * Changes struct fb_var_screeninfo are currently not pushed back + * to KMS, hence fail if different settings are requested. + */ + if (var->bits_per_pixel != fb->format->cpp[0] * 8 || + var->xres > fb->width || var->yres > fb->height || + var->xres_virtual > fb->width || var->yres_virtual > fb->height * MAX_FRAMES) { + DRM_DEBUG("fb requested width/height/bpp can't fit in current fb " + "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n", + var->xres, var->yres, var->bits_per_pixel, + var->xres_virtual, var->yres_virtual, + fb->width, fb->height, fb->format->cpp[0] * 8); + return -EINVAL; + } + + switch (var->bits_per_pixel) { + case 16: + depth = (var->green.length == 6) ? 16 : 15; + break; + case 32: + depth = (var->transp.length > 0) ? 32 : 24; + break; + default: + depth = var->bits_per_pixel; + break; + } + + switch (depth) { + case 8: + var->red.offset = 0; + var->green.offset = 0; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 15: + var->red.offset = 10; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 5; + var->blue.length = 5; + var->transp.length = 1; + var->transp.offset = 15; + break; + case 16: + var->red.offset = 11; + var->green.offset = 5; + var->blue.offset = 0; + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 24: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 0; + var->transp.offset = 0; + break; + case 32: + var->red.offset = 16; + var->green.offset = 8; + var->blue.offset = 0; + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + var->transp.length = 8; + var->transp.offset = 24; + break; + default: + return -EINVAL; + } + return 0; +} + +/****************************************************************************** + * Code copied from drivers/gpu/drm/drm_fb_cma_helper.c as of Linux 4.4 + ******************************************************************************/ + +struct hdlcd_fb { + struct drm_framebuffer fb; + struct drm_gem_cma_object *obj[4]; +}; + +struct hdlcd_drm_fbdev { + struct drm_fb_helper fb_helper; + struct hdlcd_fb *fb; + const struct drm_framebuffer_funcs *fb_funcs; +}; + +/** + * DOC: framebuffer cma helper functions + * + * Provides helper functions for creating a cma (contiguous memory allocator) + * backed framebuffer. + * + * hdlcd_fb_create() is used in the &drm_mode_config_funcs.fb_create + * callback function to create a cma backed framebuffer. + * + * An fbdev framebuffer backed by cma is also available by calling + * hdlcd_drm_fbdev_init(). hdlcd_drm_fbdev_fini() tears it down. + * If the &drm_framebuffer_funcs.dirty callback is set, fb_deferred_io will be + * set up automatically. &drm_framebuffer_funcs.dirty is called by + * drm_fb_helper_deferred_io() in process context (&struct delayed_work). + * + * Example fbdev deferred io code:: + * + * static int driver_fb_dirty(struct drm_framebuffer *fb, + * struct drm_file *file_priv, + * unsigned flags, unsigned color, + * struct drm_clip_rect *clips, + * unsigned num_clips) + * { + * struct drm_gem_cma_object *cma = hdlcd_fb_get_gem_obj(fb, 0); + * ... push changes ... + * return 0; + * } + * + * static struct drm_framebuffer_funcs driver_fb_funcs = { + * .destroy = hdlcd_fb_destroy, + * .create_handle = hdlcd_fb_create_handle, + * .dirty = driver_fb_dirty, + * }; + * + * Initialize:: + * + * fbdev = hdlcd_drm_fbdev_init_with_funcs(dev, 16, + * dev->mode_config.num_crtc, + * dev->mode_config.num_connector, + * &driver_fb_funcs); + * + */ + +static inline struct hdlcd_drm_fbdev *to_hdlcd_fbdev(struct drm_fb_helper *helper) +{ + return container_of(helper, struct hdlcd_drm_fbdev, fb_helper); +} + +static inline struct hdlcd_fb *to_hdlcd_fb(struct drm_framebuffer *fb) +{ + return container_of(fb, struct hdlcd_fb, fb); +} + +void hdlcd_fb_destroy(struct drm_framebuffer *fb) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + int i; + + for (i = 0; i < 4; i++) { + if (hdlcd_fb->obj[i]) + drm_gem_object_put_unlocked(&hdlcd_fb->obj[i]->base); + } + + drm_framebuffer_cleanup(fb); + kfree(hdlcd_fb); +} +EXPORT_SYMBOL(hdlcd_fb_destroy); + +int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned int *handle) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + + return drm_gem_handle_create(file_priv, + &hdlcd_fb->obj[0]->base, handle); +} +EXPORT_SYMBOL(hdlcd_fb_create_handle); + +static struct drm_framebuffer_funcs hdlcd_fb_funcs = { + .destroy = hdlcd_fb_destroy, + .create_handle = hdlcd_fb_create_handle, +}; + +static struct hdlcd_fb *hdlcd_fb_alloc(struct drm_device *dev, + const struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_cma_object **obj, + unsigned int num_planes, const struct drm_framebuffer_funcs *funcs) +{ + struct hdlcd_fb *hdlcd_fb; + int ret; + int i; + + hdlcd_fb = kzalloc(sizeof(*hdlcd_fb), GFP_KERNEL); + if (!hdlcd_fb) + return ERR_PTR(-ENOMEM); + + drm_helper_mode_fill_fb_struct(dev, &hdlcd_fb->fb, mode_cmd); + + for (i = 0; i < num_planes; i++) + hdlcd_fb->obj[i] = obj[i]; + + ret = drm_framebuffer_init(dev, &hdlcd_fb->fb, funcs); + if (ret) { + dev_err(dev->dev, "Failed to initialize framebuffer: %d\n", ret); + kfree(hdlcd_fb); + return ERR_PTR(ret); + } + + return hdlcd_fb; +} + +/** + * hdlcd_fb_create_with_funcs() - helper function for the + * &drm_mode_config_funcs.fb_create + * callback + * @dev: DRM device + * @file_priv: drm file for the ioctl call + * @mode_cmd: metadata from the userspace fb creation request + * @funcs: vtable to be used for the new framebuffer object + * + * This can be used to set &drm_framebuffer_funcs for drivers that need the + * &drm_framebuffer_funcs.dirty callback. Use hdlcd_fb_create() if you don't + * need to change &drm_framebuffer_funcs. + */ +struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd, + const struct drm_framebuffer_funcs *funcs) +{ + const struct drm_format_info *info; + struct hdlcd_fb *hdlcd_fb; + struct drm_gem_cma_object *objs[4]; + struct drm_gem_object *obj; + int ret; + int i; + + info = drm_get_format_info(dev, mode_cmd); + if (!info) + return ERR_PTR(-EINVAL); + + for (i = 0; i < info->num_planes; i++) { + unsigned int width = mode_cmd->width / (i ? info->hsub : 1); + unsigned int height = mode_cmd->height / (i ? info->vsub : 1); + unsigned int min_size; + + obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]); + if (!obj) { + dev_err(dev->dev, "Failed to lookup GEM object\n"); + ret = -ENXIO; + goto err_gem_object_put; + } + + min_size = (height - 1) * mode_cmd->pitches[i] + + width * info->cpp[i] + + mode_cmd->offsets[i]; + + if (obj->size < min_size) { + drm_gem_object_put_unlocked(obj); + ret = -EINVAL; + goto err_gem_object_put; + } + objs[i] = to_drm_gem_cma_obj(obj); + } + + hdlcd_fb = hdlcd_fb_alloc(dev, mode_cmd, objs, i, funcs); + if (IS_ERR(hdlcd_fb)) { + ret = PTR_ERR(hdlcd_fb); + goto err_gem_object_put; + } + + return &hdlcd_fb->fb; + +err_gem_object_put: + for (i--; i >= 0; i--) + drm_gem_object_put_unlocked(&objs[i]->base); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(hdlcd_fb_create_with_funcs); + +/** + * hdlcd_fb_create() - &drm_mode_config_funcs.fb_create callback function + * @dev: DRM device + * @file_priv: drm file for the ioctl call + * @mode_cmd: metadata from the userspace fb creation request + * + * If your hardware has special alignment or pitch requirements these should be + * checked before calling this function. Use hdlcd_fb_create_with_funcs() if + * you need to set &drm_framebuffer_funcs.dirty. + */ +struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd) +{ + return hdlcd_fb_create_with_funcs(dev, file_priv, mode_cmd, + &hdlcd_fb_funcs); +} +EXPORT_SYMBOL_GPL(hdlcd_fb_create); + +/** + * hdlcd_fb_get_gem_obj() - Get CMA GEM object for framebuffer + * @fb: The framebuffer + * @plane: Which plane + * + * Return the CMA GEM object for given framebuffer. + * + * This function will usually be called from the CRTC callback functions. + */ +struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + + if (plane >= 4) + return NULL; + + return hdlcd_fb->obj[plane]; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_get_gem_obj); + +/** + * hdlcd_fb_prepare_fb() - Prepare CMA framebuffer + * @plane: Which plane + * @state: Plane state attach fence to + * + * This should be set as the &struct drm_plane_helper_funcs.prepare_fb hook. + * + * This function checks if the plane FB has an dma-buf attached, extracts + * the exclusive fence and attaches it to plane state for the atomic helper + * to wait on. + * + * There is no need for cleanup_fb for CMA based framebuffer drivers. + */ +int hdlcd_fb_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct dma_buf *dma_buf; + struct dma_fence *fence; + + if ((plane->state->fb == state->fb) || !state->fb) + return 0; + + dma_buf = hdlcd_fb_get_gem_obj(state->fb, 0)->base.dma_buf; + if (dma_buf) { + fence = reservation_object_get_excl_rcu(dma_buf->resv); + drm_atomic_set_fence_for_plane(state, fence); + } + + return 0; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_prepare_fb); + +#ifdef CONFIG_DEBUG_FS +static void hdlcd_fb_describe(struct drm_framebuffer *fb, struct seq_file *m) +{ + struct hdlcd_fb *hdlcd_fb = to_hdlcd_fb(fb); + int i; + + seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height, + (char *)&fb->format->format); + + for (i = 0; i < fb->format->num_planes; i++) { + seq_printf(m, " %d: offset=%d pitch=%d, obj: ", + i, fb->offsets[i], fb->pitches[i]); + drm_gem_cma_describe(hdlcd_fb->obj[i], m); + } +} + +/** + * hdlcd_fb_debugfs_show() - Helper to list CMA framebuffer objects + * in debugfs. + * @m: output file + * @arg: private data for the callback + */ +int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct drm_framebuffer *fb; + + mutex_lock(&dev->mode_config.fb_lock); + drm_for_each_fb(fb, dev) + hdlcd_fb_describe(fb, m); + mutex_unlock(&dev->mode_config.fb_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(hdlcd_fb_debugfs_show); +#endif + +static int hdlcd_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + return dma_mmap_writecombine(info->device, vma, info->screen_base, + info->fix.smem_start, info->fix.smem_len); +} + +static int hdlcd_fb_helper_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct drm_fb_helper *helper = info->par; + struct drm_framebuffer *fb = helper->fb; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct drm_gem_cma_object *gem; + dma_addr_t scanout_start; + int ret; + + ret = hdlcd_fb_helper_check_var(var, info); + if (ret) + return ret; + + gem = hdlcd_fb_get_gem_obj(fb, 0); + + scanout_start = gem->paddr + fb->offsets[0] + + (var->yoffset * fb->pitches[0]) + (var->xoffset * fb->format->cpp[0]); + + hdlcd_write(hdlcd, HDLCD_REG_FB_BASE, scanout_start); + + hdlcd_wait_for_frame_completion(helper->dev); + + return 0; +} + +static struct fb_ops hdlcd_drm_fbdev_ops = { + .owner = THIS_MODULE, + DRM_FB_HELPER_DEFAULT_OPS, + .fb_fillrect = drm_fb_helper_sys_fillrect, + .fb_copyarea = drm_fb_helper_sys_copyarea, + .fb_imageblit = drm_fb_helper_sys_imageblit, + .fb_mmap = hdlcd_fb_mmap, + .fb_ioctl = hdlcd_fb_ioctl, + .fb_compat_ioctl= hdlcd_fb_ioctl, +}; + +static int hdlcd_drm_fbdev_deferred_io_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + fb_deferred_io_mmap(info, vma); + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + return 0; +} + +static int hdlcd_drm_fbdev_defio_init(struct fb_info *fbi, + struct drm_gem_cma_object *cma_obj) +{ + struct fb_deferred_io *fbdefio; + struct fb_ops *fbops; + + /* + * Per device structures are needed because: + * fbops: fb_deferred_io_cleanup() clears fbops.fb_mmap + * fbdefio: individual delays + */ + fbdefio = kzalloc(sizeof(*fbdefio), GFP_KERNEL); + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); + if (!fbdefio || !fbops) { + kfree(fbdefio); + kfree(fbops); + return -ENOMEM; + } + + /* can't be offset from vaddr since dirty() uses cma_obj */ + fbi->screen_buffer = cma_obj->vaddr; + /* fb_deferred_io_fault() needs a physical address */ + fbi->fix.smem_start = page_to_phys(virt_to_page(fbi->screen_buffer)); + + *fbops = *fbi->fbops; + fbi->fbops = fbops; + + fbdefio->delay = msecs_to_jiffies(DEFAULT_FBDEFIO_DELAY_MS); + fbdefio->deferred_io = drm_fb_helper_deferred_io; + fbi->fbdefio = fbdefio; + fb_deferred_io_init(fbi); + fbi->fbops->fb_mmap = hdlcd_drm_fbdev_deferred_io_mmap; + + return 0; +} + +static void hdlcd_drm_fbdev_defio_fini(struct fb_info *fbi) +{ + if (!fbi->fbdefio) + return; + + fb_deferred_io_cleanup(fbi); + kfree(fbi->fbdefio); + kfree(fbi->fbops); +} + +static int +hdlcd_drm_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct hdlcd_drm_fbdev *hdlcd_fbdev = to_hdlcd_fbdev(helper); + struct drm_mode_fb_cmd2 mode_cmd = { 0 }; + struct drm_device *dev = helper->dev; + struct drm_gem_cma_object *obj; + struct drm_framebuffer *fb; + unsigned int bytes_per_pixel; + unsigned long offset; + struct fb_info *fbi; + size_t size; + int ret; + + DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n", + sizes->surface_width, sizes->surface_height, + sizes->surface_bpp); + + bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel; + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + size = mode_cmd.pitches[0] * mode_cmd.height * MAX_FRAMES; + obj = drm_gem_cma_create(dev, size); + if (IS_ERR(obj)) + return -ENOMEM; + + fbi = drm_fb_helper_alloc_fbi(helper); + if (IS_ERR(fbi)) { + ret = PTR_ERR(fbi); + goto err_gem_free_object; + } + + hdlcd_fbdev->fb = hdlcd_fb_alloc(dev, &mode_cmd, &obj, 1, + hdlcd_fbdev->fb_funcs); + if (IS_ERR(hdlcd_fbdev->fb)) { + dev_err(dev->dev, "Failed to allocate DRM framebuffer.\n"); + ret = PTR_ERR(hdlcd_fbdev->fb); + goto err_fb_info_destroy; + } + + fb = &hdlcd_fbdev->fb->fb; + helper->fb = fb; + + fbi->par = helper; + fbi->flags = FBINFO_FLAG_DEFAULT; + hdlcd_drm_fbdev_ops.fb_check_var = hdlcd_fb_helper_check_var; + hdlcd_drm_fbdev_ops.fb_pan_display = hdlcd_fb_helper_pan_display; + fbi->fbops = &hdlcd_drm_fbdev_ops; + + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth); + drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); + + offset = fbi->var.xoffset * bytes_per_pixel; + offset += fbi->var.yoffset * fb->pitches[0]; + + dev->mode_config.fb_base = (resource_size_t)obj->paddr; + fbi->screen_base = obj->vaddr + offset; + fbi->fix.smem_start = (unsigned long)(obj->paddr + offset); + fbi->screen_size = size; + fbi->fix.smem_len = size; + fbi->var.yres_virtual = fbi->var.yres * MAX_FRAMES; + + if (hdlcd_fbdev->fb_funcs->dirty) { + ret = hdlcd_drm_fbdev_defio_init(fbi, obj); + if (ret) + goto err_cma_destroy; + } + + return 0; + +err_cma_destroy: + drm_framebuffer_remove(&hdlcd_fbdev->fb->fb); +err_fb_info_destroy: + drm_fb_helper_fini(helper); +err_gem_free_object: + drm_gem_object_put_unlocked(&obj->base); + return ret; +} + +static const struct drm_fb_helper_funcs hdlcd_fb_helper_funcs = { + .fb_probe = hdlcd_drm_fbdev_create, +}; + +/** + * hdlcd_drm_fbdev_init_with_funcs() - Allocate and initializes a hdlcd_drm_fbdev struct + * @dev: DRM device + * @preferred_bpp: Preferred bits per pixel for the device + * @max_conn_count: Maximum number of connectors + * @funcs: fb helper functions, in particular a custom dirty() callback + * + * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR. + */ +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int max_conn_count, + const struct drm_framebuffer_funcs *funcs) +{ + struct hdlcd_drm_fbdev *hdlcd_fbdev; + struct drm_fb_helper *helper; + int ret; + + hdlcd_fbdev = kzalloc(sizeof(*hdlcd_fbdev), GFP_KERNEL); + if (!hdlcd_fbdev) { + dev_err(dev->dev, "Failed to allocate drm fbdev.\n"); + return ERR_PTR(-ENOMEM); + } + hdlcd_fbdev->fb_funcs = funcs; + + helper = &hdlcd_fbdev->fb_helper; + + drm_fb_helper_prepare(dev, helper, &hdlcd_fb_helper_funcs); + + ret = drm_fb_helper_init(dev, helper, max_conn_count); + if (ret < 0) { + dev_err(dev->dev, "Failed to initialize drm fb helper.\n"); + goto err_free; + } + + ret = drm_fb_helper_single_add_all_connectors(helper); + if (ret < 0) { + dev_err(dev->dev, "Failed to add connectors.\n"); + goto err_drm_fb_helper_fini; + + } + + ret = drm_fb_helper_initial_config(helper, preferred_bpp); + if (ret < 0) { + dev_err(dev->dev, "Failed to set initial hw configuration.\n"); + goto err_drm_fb_helper_fini; + } + + return hdlcd_fbdev; + +err_drm_fb_helper_fini: + drm_fb_helper_fini(helper); +err_free: + kfree(hdlcd_fbdev); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init_with_funcs); + +/** + * hdlcd_drm_fbdev_init() - Allocate and initializes a hdlcd_drm_fbdev struct + * @dev: DRM device + * @preferred_bpp: Preferred bits per pixel for the device + * @max_conn_count: Maximum number of connectors + * + * Returns a newly allocated hdlcd_drm_fbdev struct or a ERR_PTR. + */ +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int max_conn_count) +{ + return hdlcd_drm_fbdev_init_with_funcs(dev, preferred_bpp, + max_conn_count, + &hdlcd_fb_funcs); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_init); + +/** + * hdlcd_drm_fbdev_fini() - Free hdlcd_drm_fbdev struct + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct + */ +void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + drm_fb_helper_unregister_fbi(&hdlcd_fbdev->fb_helper); + if (hdlcd_fbdev->fb_helper.fbdev) + hdlcd_drm_fbdev_defio_fini(hdlcd_fbdev->fb_helper.fbdev); + + if (hdlcd_fbdev->fb) + drm_framebuffer_remove(&hdlcd_fbdev->fb->fb); + + drm_fb_helper_fini(&hdlcd_fbdev->fb_helper); + kfree(hdlcd_fbdev); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_fini); + +/** + * hdlcd_drm_fbdev_restore_mode() - Restores initial framebuffer mode + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * + * This function is usually called from the &drm_driver.lastclose callback. + */ +void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + if (hdlcd_fbdev) + drm_fb_helper_restore_fbdev_mode_unlocked(&hdlcd_fbdev->fb_helper); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_restore_mode); + +/** + * hdlcd_drm_fbdev_hotplug_event() - Poll for hotpulug events + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * + * This function is usually called from the &drm_mode_config.output_poll_changed + * callback. + */ +void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev) +{ + if (hdlcd_fbdev) + drm_fb_helper_hotplug_event(&hdlcd_fbdev->fb_helper); +} +EXPORT_SYMBOL_GPL(hdlcd_drm_fbdev_hotplug_event); + +/** + * hdlcd_drm_fbdev_set_suspend - wrapper around drm_fb_helper_set_suspend + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * @state: desired state, zero to resume, non-zero to suspend + * + * Calls drm_fb_helper_set_suspend, which is a wrapper around + * fb_set_suspend implemented by fbdev core. + */ +void hdlcd_drm_fbdev_set_suspend(struct hdlcd_drm_fbdev *hdlcd_fbdev, int state) +{ + if (hdlcd_fbdev) + drm_fb_helper_set_suspend(&hdlcd_fbdev->fb_helper, state); +} +EXPORT_SYMBOL(hdlcd_drm_fbdev_set_suspend); + +/** + * hdlcd_drm_fbdev_set_suspend_unlocked - wrapper around + * drm_fb_helper_set_suspend_unlocked + * @hdlcd_fbdev: The hdlcd_drm_fbdev struct, may be NULL + * @state: desired state, zero to resume, non-zero to suspend + * + * Calls drm_fb_helper_set_suspend, which is a wrapper around + * fb_set_suspend implemented by fbdev core. + */ +void hdlcd_drm_fbdev_set_suspend_unlocked(struct hdlcd_drm_fbdev *hdlcd_fbdev, + int state) +{ + if (hdlcd_fbdev) + drm_fb_helper_set_suspend_unlocked(&hdlcd_fbdev->fb_helper, + state); +} +EXPORT_SYMBOL(hdlcd_drm_fbdev_set_suspend_unlocked); + +/****************************************************************************** + * IOCTL Interface + ******************************************************************************/ + +/* + * Used for sharing buffers with Mali userspace + */ +struct fb_dmabuf_export { + uint32_t fd; + uint32_t flags; +}; + +#define FBIOGET_DMABUF _IOR('F', 0x21, struct fb_dmabuf_export) + +static int hdlcd_get_dmabuf_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct fb_dmabuf_export ebuf; + struct drm_fb_helper *helper = info->par; + struct hdlcd_drm_private *hdlcd = helper->dev->dev_private; + struct drm_gem_cma_object *obj = hdlcd->fbdev->fb->obj[0]; + struct dma_buf *dma_buf; + uint32_t fd; + + if (copy_from_user(&ebuf, argp, sizeof(ebuf))) + return -EFAULT; + + /* + * We need a reference on the gem object. This will be released by + * drm_gem_dmabuf_release when the file descriptor is closed. + */ + drm_gem_object_reference(&obj->base); + + dma_buf = drm_gem_prime_export(helper->dev, &obj->base, ebuf.flags | O_RDWR); + if (!dma_buf) { + dev_info(info->dev, "Failed to export DMA buffer\n"); + goto err_export; + } + + fd = dma_buf_fd(dma_buf, O_CLOEXEC); + if (fd < 0) { + dev_info(info->dev, "Failed to get file descriptor for DMA buffer\n"); + goto err_export_fd; + } + ebuf.fd = fd; + + if (copy_to_user(argp, &ebuf, sizeof(ebuf))) + goto err_export_fd; + + return 0; + +err_export_fd: + dma_buf_put(dma_buf); +err_export: + drm_gem_object_unreference(&obj->base); + return -EFAULT; +} + +static int hdlcd_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case FBIOGET_DMABUF: + return hdlcd_get_dmabuf_ioctl(info, cmd, arg); + case FBIO_WAITFORVSYNC: + return 0; /* Nothing to do as we wait when page flipping anyway */ + default: + printk(KERN_INFO "HDLCD FB does not handle ioctl 0x%x\n", cmd); + } + + return -EFAULT; +} diff --git a/drivers/gpu/drm/arm/hdlcd_fb_helper.h b/drivers/gpu/drm/arm/hdlcd_fb_helper.h new file mode 100644 index 000000000000..aee0d9037561 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_fb_helper.h @@ -0,0 +1,54 @@ +#ifndef __DRM_FB_CMA_HELPER_H__ +#define __DRM_FB_CMA_HELPER_H__ + +struct hdlcd_drm_fbdev; +struct drm_gem_cma_object; + +struct drm_fb_helper_surface_size; +struct drm_framebuffer_funcs; +struct drm_fb_helper_funcs; +struct drm_framebuffer; +struct drm_fb_helper; +struct drm_device; +struct drm_file; +struct drm_mode_fb_cmd2; +struct drm_plane; +struct drm_plane_state; + +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init_with_funcs(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int max_conn_count, + const struct drm_framebuffer_funcs *funcs); +struct hdlcd_drm_fbdev *hdlcd_drm_fbdev_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int max_conn_count); +void hdlcd_drm_fbdev_fini(struct hdlcd_drm_fbdev *hdlcd_fbdev); + +void hdlcd_drm_fbdev_restore_mode(struct hdlcd_drm_fbdev *hdlcd_fbdev); +void hdlcd_drm_fbdev_hotplug_event(struct hdlcd_drm_fbdev *hdlcd_fbdev); +void hdlcd_drm_fbdev_set_suspend(struct hdlcd_drm_fbdev *hdlcd_fbdev, int state); +void hdlcd_drm_fbdev_set_suspend_unlocked(struct hdlcd_drm_fbdev *hdlcd_fbdev, + int state); + +void hdlcd_fb_destroy(struct drm_framebuffer *fb); +int hdlcd_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned int *handle); + +struct drm_framebuffer *hdlcd_fb_create_with_funcs(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd, + const struct drm_framebuffer_funcs *funcs); +struct drm_framebuffer *hdlcd_fb_create(struct drm_device *dev, + struct drm_file *file_priv, const struct drm_mode_fb_cmd2 *mode_cmd); + +struct drm_gem_cma_object *hdlcd_fb_get_gem_obj(struct drm_framebuffer *fb, + unsigned int plane); + +int hdlcd_fb_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *state); + +#ifdef CONFIG_DEBUG_FS +struct seq_file; + +int hdlcd_fb_debugfs_show(struct seq_file *m, void *arg); +#endif + +#endif + diff --git a/drivers/gpu/drm/drm_virtual_encoder.c b/drivers/gpu/drm/drm_virtual_encoder.c new file mode 100644 index 000000000000..d5e24dfc27ea --- /dev/null +++ b/drivers/gpu/drm/drm_virtual_encoder.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2016 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * Dummy encoder and connector that use the OF to "discover" the attached + * display timings. Can be used in situations where the encoder and connector's + * functionality are emulated and no setup steps are needed, or to describe + * attached panels for which no driver exists but can be used without + * additional hardware setup. + * + * The encoder also uses the component framework so that it can be a quick + * replacement for existing drivers when testing in an emulated environment. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <linux/component.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +struct drm_virt_priv { + struct drm_connector connector; + struct drm_encoder encoder; + struct display_timings *timings; +}; + +#define connector_to_drm_virt_priv(x) \ + container_of(x, struct drm_virt_priv, connector) + +#define encoder_to_drm_virt_priv(x) \ + container_of(x, struct drm_virt_priv, encoder) + +static void drm_virtcon_destroy(struct drm_connector *connector) +{ + struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector); + + drm_connector_cleanup(connector); + display_timings_release(conn->timings); +} + +static enum drm_connector_status +drm_virtcon_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs drm_virtcon_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = drm_virtcon_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_virtcon_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int drm_virtcon_get_modes(struct drm_connector *connector) +{ + struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector); + struct display_timings *timings = conn->timings; + int i; + + for (i = 0; i < timings->num_timings; i++) { + struct drm_display_mode *mode = drm_mode_create(connector->dev); + struct videomode vm; + + if (videomode_from_timings(timings, &vm, i)) + break; + + drm_display_mode_from_videomode(&vm, mode); + mode->type = DRM_MODE_TYPE_DRIVER; + if (timings->native_mode == i) + mode->type = DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int drm_virtcon_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +struct drm_encoder *drm_virtcon_best_encoder(struct drm_connector *connector) +{ + struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector); + + return &priv->encoder; +} + +struct drm_encoder * +drm_virtcon_atomic_best_encoder(struct drm_connector *connector, + struct drm_connector_state *connector_state) +{ + struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector); + + return &priv->encoder; +} + +static const struct drm_connector_helper_funcs drm_virtcon_helper_funcs = { + .get_modes = drm_virtcon_get_modes, + .mode_valid = drm_virtcon_mode_valid, + .best_encoder = drm_virtcon_best_encoder, + .atomic_best_encoder = drm_virtcon_atomic_best_encoder, +}; + +static void drm_vencoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs drm_vencoder_funcs = { + .destroy = drm_vencoder_destroy, +}; + +static void drm_vencoder_dpms(struct drm_encoder *encoder, int mode) +{ + /* nothing needed */ +} + +static bool drm_vencoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void drm_vencoder_prepare(struct drm_encoder *encoder) +{ + drm_vencoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void drm_vencoder_commit(struct drm_encoder *encoder) +{ + drm_vencoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void drm_vencoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ +} + +static const struct drm_encoder_helper_funcs drm_vencoder_helper_funcs = { + .dpms = drm_vencoder_dpms, + .mode_fixup = drm_vencoder_mode_fixup, + .prepare = drm_vencoder_prepare, + .commit = drm_vencoder_commit, + .mode_set = drm_vencoder_mode_set, +}; + +static int drm_vencoder_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_encoder *encoder; + struct drm_virt_priv *con; + struct drm_connector *connector; + struct drm_device *drm = data; + u32 crtcs = 0; + int ret; + + con = devm_kzalloc(dev, sizeof(*con), GFP_KERNEL); + if (!con) + return -ENOMEM; + + dev_set_drvdata(dev, con); + connector = &con->connector; + encoder = &con->encoder; + + if (dev->of_node) { + struct drm_bridge *bridge; + crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + bridge = of_drm_find_bridge(dev->of_node); + if (bridge) { + ret = drm_bridge_attach(encoder, bridge, NULL); + if (ret) { + DRM_ERROR("Failed to initialize bridge\n"); + return ret; + } + encoder->bridge = bridge; + } + con->timings = of_get_display_timings(dev->of_node); + if (!con->timings) { + dev_err(dev, "failed to get display panel timings\n"); + return ENXIO; + } + } + + /* If no CRTCs were found, fall back to the old encoder's behaviour */ + if (crtcs == 0) { + dev_warn(dev, "Falling back to first CRTC\n"); + crtcs = 1 << 0; + } + + encoder->possible_crtcs = crtcs ? crtcs : 1; + encoder->possible_clones = 0; + + ret = drm_encoder_init(drm, encoder, &drm_vencoder_funcs, + DRM_MODE_ENCODER_VIRTUAL, "virtual-encoder"); + if (ret) + goto encoder_init_err; + + drm_encoder_helper_add(encoder, &drm_vencoder_helper_funcs); + + /* bogus values, pretend we're a 24" screen for DPI calculations */ + connector->display_info.width_mm = 519; + connector->display_info.height_mm = 324; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + + ret = drm_connector_init(drm, connector, &drm_virtcon_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto connector_init_err; + + drm_connector_helper_add(connector, &drm_virtcon_helper_funcs); + + drm_connector_register(connector); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto attach_err; + + return ret; + +attach_err: + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +connector_init_err: + drm_encoder_cleanup(encoder); +encoder_init_err: + display_timings_release(con->timings); + + return ret; +}; + +static void drm_vencoder_unbind(struct device *dev, struct device *master, + void *data) +{ + struct drm_virt_priv *con = dev_get_drvdata(dev); + + drm_connector_unregister(&con->connector); + drm_connector_cleanup(&con->connector); + drm_encoder_cleanup(&con->encoder); + display_timings_release(con->timings); +} + +static const struct component_ops drm_vencoder_ops = { + .bind = drm_vencoder_bind, + .unbind = drm_vencoder_unbind, +}; + +static int drm_vencoder_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &drm_vencoder_ops); +} + +static int drm_vencoder_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &drm_vencoder_ops); + return 0; +} + +static const struct of_device_id drm_vencoder_of_match[] = { + { .compatible = "drm,virtual-encoder", }, + {}, +}; +MODULE_DEVICE_TABLE(of, drm_vencoder_of_match); + +static struct platform_driver drm_vencoder_driver = { + .probe = drm_vencoder_probe, + .remove = drm_vencoder_remove, + .driver = { + .name = "drm_vencoder", + .of_match_table = drm_vencoder_of_match, + }, +}; + +module_platform_driver(drm_vencoder_driver); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("Virtual DRM Encoder"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/i2c/Kconfig b/drivers/gpu/drm/i2c/Kconfig index a6c92beb410a..23e0e8784e49 100644 --- a/drivers/gpu/drm/i2c/Kconfig +++ b/drivers/gpu/drm/i2c/Kconfig @@ -11,6 +11,9 @@ config DRM_I2C_CH7006 This driver is currently only useful if you're also using the nouveau driver. +config DRM_I2C_DUMMY + tristate "Dummy stub driver" + config DRM_I2C_SIL164 tristate "Silicon Image sil164 TMDS transmitter" default m if DRM_NOUVEAU diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index 43aa33baebed..658751730d50 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -3,6 +3,8 @@ ccflags-y := -Iinclude/drm ch7006-y := ch7006_drv.o ch7006_mode.o obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o +obj-$(CONFIG_DRM_I2C_DUMMY) += dummy_drm_i2c_drv.o + sil164-y := sil164_drv.o obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o diff --git a/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c new file mode 100644 index 000000000000..2958f57940e8 --- /dev/null +++ b/drivers/gpu/drm/i2c/dummy_drm_i2c_drv.c @@ -0,0 +1,264 @@ +/* + * This file was originally based on tda998x_drv.c which has the following + * copyright and licence... + * + * Copyright (C) 2012 Texas Instruments + * Author: Rob Clark <robdclark@gmail.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 in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/component.h> +#include <linux/module.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> + +struct dummy_priv { + struct drm_encoder encoder; + struct drm_connector connector; +}; + +#define conn_to_dummy_priv(x) \ + container_of(x, struct dummy_priv, connector); + +/* DRM encoder functions */ + +static void dummy_encoder_dpms(struct drm_encoder *encoder, int mode) +{ +} + +static bool +dummy_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static int dummy_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void +dummy_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static enum drm_connector_status +dummy_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const u8 edid_1024x768[] = { + /* + * These values are a copy of Documentation/EDID/1024x768.c + * produced by executing "make -C Documentation/EDID" + */ + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x31, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x16, 0x01, 0x03, 0x6d, 0x23, 0x1a, 0x78, + 0xea, 0x5e, 0xc0, 0xa4, 0x59, 0x4a, 0x98, 0x25, + 0x20, 0x50, 0x54, 0x00, 0x08, 0x00, 0x61, 0x40, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x64, 0x19, + 0x00, 0x40, 0x41, 0x00, 0x26, 0x30, 0x08, 0x90, + 0x36, 0x00, 0x63, 0x0a, 0x11, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0xff, 0x00, 0x4c, 0x69, 0x6e, + 0x75, 0x78, 0x20, 0x23, 0x30, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x3b, + 0x3d, 0x2f, 0x31, 0x07, 0x00, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0xfc, + 0x00, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x58, + 0x47, 0x41, 0x0a, 0x20, 0x20, 0x20, 0x00, 0x55 +}; + +static int dummy_read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length) +{ + memcpy(buf, edid_1024x768, min(sizeof(edid_1024x768),length)); + return 0; +} + +static int dummy_connector_get_modes(struct drm_connector *connector) +{ + struct edid *edid; + int n; + + edid = drm_do_get_edid(connector, dummy_read_edid_block, NULL); + if (!edid) + return 0; + + drm_mode_connector_update_edid_property(connector, edid); + n = drm_add_edid_modes(connector, edid); + kfree(edid); + + return n; +} + +/* I2C driver functions */ + +static void dummy_encoder_prepare(struct drm_encoder *encoder) +{ +} + +static void dummy_encoder_commit(struct drm_encoder *encoder) +{ +} + +static const struct drm_encoder_helper_funcs dummy_encoder_helper_funcs = { + .dpms = dummy_encoder_dpms, + .mode_fixup = dummy_encoder_mode_fixup, + .prepare = dummy_encoder_prepare, + .commit = dummy_encoder_commit, + .mode_set = dummy_encoder_mode_set, +}; + +static void dummy_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs dummy_encoder_funcs = { + .destroy = dummy_encoder_destroy, +}; + +static struct drm_encoder * +dummy_connector_best_encoder(struct drm_connector *connector) +{ + struct dummy_priv *priv = conn_to_dummy_priv(connector); + + return &priv->encoder; +} + +static +const struct drm_connector_helper_funcs dummy_connector_helper_funcs = { + .get_modes = dummy_connector_get_modes, + .mode_valid = dummy_connector_mode_valid, + .best_encoder = dummy_connector_best_encoder, +}; + +static void dummy_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs dummy_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = dummy_connector_detect, + .destroy = dummy_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int dummy_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm = data; + struct dummy_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(dev, priv); + + priv->connector.interlace_allowed = 1; + priv->encoder.possible_crtcs = 1 << 0; + + drm_encoder_helper_add(&priv->encoder, &dummy_encoder_helper_funcs); + ret = drm_encoder_init(drm, &priv->encoder, &dummy_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + goto err_encoder; + + drm_connector_helper_add(&priv->connector, + &dummy_connector_helper_funcs); + ret = drm_connector_init(drm, &priv->connector, + &dummy_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) + goto err_connector; + + drm_mode_connector_attach_encoder(&priv->connector, &priv->encoder); + + return 0; + +err_connector: + drm_encoder_cleanup(&priv->encoder); +err_encoder: + return ret; +} + +static void dummy_unbind(struct device *dev, struct device *master, + void *data) +{ + struct dummy_priv *priv = dev_get_drvdata(dev); + + drm_connector_cleanup(&priv->connector); + drm_encoder_cleanup(&priv->encoder); +} + +static const struct component_ops dummy_ops = { + .bind = dummy_bind, + .unbind = dummy_unbind, +}; + +static int +dummy_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + return component_add(&client->dev, &dummy_ops); +} + +static int dummy_remove(struct i2c_client *client) +{ + component_del(&client->dev, &dummy_ops); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id dummy_of_ids[] = { + { .compatible = "sil,sii9022-tpi", }, + { } +}; +MODULE_DEVICE_TABLE(of, dummy_of_ids); +#endif + +static struct i2c_device_id dummy_ids[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, dummy_ids); + +static struct i2c_driver dummy_driver = { + .probe = dummy_probe, + .remove = dummy_remove, + .driver = { + .name = "dummy_drm_i2c", + .of_match_table = dummy_of_ids + }, + .id_table = dummy_ids, +}; + +module_i2c_driver(dummy_driver); + +MODULE_LICENSE("GPL"); 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/drivers/net/ethernet/marvell/sky2.c b/drivers/net/ethernet/marvell/sky2.c index 1145cde2274a..bf397e3c5195 100644 --- a/drivers/net/ethernet/marvell/sky2.c +++ b/drivers/net/ethernet/marvell/sky2.c @@ -101,6 +101,10 @@ static int legacy_pme = 0; module_param(legacy_pme, int, 0); MODULE_PARM_DESC(legacy_pme, "Legacy power management"); +/* Ugh! Let the firmware tell us the hardware address */ +static int mac_address[ETH_ALEN] = { 0, }; +module_param_array(mac_address, int, NULL, 0); + static const struct pci_device_id sky2_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9000) }, /* SK-9Sxx */ { PCI_DEVICE(PCI_VENDOR_ID_SYSKONNECT, 0x9E00) }, /* SK-9Exx */ @@ -3909,6 +3913,18 @@ static void sky2_get_stats(struct net_device *dev, unsigned int start; u64 _bytes, _packets; + /* Try and check if device if off. If it is, abort gathering stats as + * any attempt to read hardware registers will generate a bus fault. + * This test is hacky and racy as there's nothing stopping the device + * being powered off immediately after the test. + */ + if (hw->pdev->pm_cap) { + u16 pmcsr; + int ret = pci_read_config_word(hw->pdev, hw->pdev->pm_cap + PCI_PM_CTRL, &pmcsr); + if (ret || (pmcsr & PCI_PM_CTRL_STATE_MASK) > PCI_D2) + return; /* Can't read power state or it's state D3 (off) */ + } + do { start = u64_stats_fetch_begin_irq(&sky2->rx_stats.syncp); _bytes = sky2->rx_stats.bytes; @@ -4819,13 +4835,21 @@ static struct net_device *sky2_init_netdev(struct sky2_hw *hw, unsigned port, /* try to get mac address in the following order: * 1) from device tree data * 2) from internal registers set by bootloader + * 3) from the command line parameter */ iap = of_get_mac_address(hw->pdev->dev.of_node); if (iap) memcpy(dev->dev_addr, iap, ETH_ALEN); - else + else { memcpy_fromio(dev->dev_addr, hw->regs + B2_MAC_1 + port * 8, ETH_ALEN); + if (!is_valid_ether_addr(&dev->dev_addr[0])) { + int i; + + for (i = 0; i < ETH_ALEN; i++) + dev->dev_addr[i] = mac_address[i]; + } + } /* if the address is invalid, use a random value */ if (!is_valid_ether_addr(dev->dev_addr)) { diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index a8176a836e25..57a11bba6e3b 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -895,6 +895,8 @@ int drm_crtc_force_disable_all(struct drm_device *dev); int drm_mode_set_config_internal(struct drm_mode_set *set); struct drm_crtc *drm_crtc_from_index(struct drm_device *dev, int idx); +extern int drm_create_virtual_connector(struct drm_device *dev); + /** * drm_crtc_find - look up a CRTC object from its ID * @dev: DRM device 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/android.conf b/linaro/configs/android.conf index 637c36580a45..c9309874c1de 100644 --- a/linaro/configs/android.conf +++ b/linaro/configs/android.conf @@ -3,6 +3,7 @@ # CONFIG_INET_LRO is not set # CONFIG_MODULES is not set # CONFIG_OABI_COMPAT is not set +# CONFIG_SYSVIPC is not set CONFIG_ANDROID=y CONFIG_ANDROID_BINDER_IPC=y CONFIG_ANDROID_LOW_MEMORY_KILLER=y @@ -19,13 +20,16 @@ CONFIG_CGROUP_SCHED=y CONFIG_CP15_BARRIER_EMULATION=y CONFIG_DM_CRYPT=y CONFIG_DM_VERITY=y +CONFIG_DM_VERITY_FEC=y CONFIG_EMBEDDED=y CONFIG_FB=y +CONFIG_HARDENED_USERCOPY=y CONFIG_HIGH_RES_TIMERS=y CONFIG_INET6_AH=y CONFIG_INET6_ESP=y CONFIG_INET6_IPCOMP=y CONFIG_INET=y +CONFIG_INET_DIAG_DESTROY=y CONFIG_INET_ESP=y CONFIG_INET_XFRM_MODE_TUNNEL=y CONFIG_IP6_NF_FILTER=y @@ -33,7 +37,6 @@ CONFIG_IP6_NF_IPTABLES=y CONFIG_IP6_NF_MANGLE=y CONFIG_IP6_NF_RAW=y CONFIG_IP6_NF_TARGET_REJECT=y -CONFIG_IP6_NF_TARGET_REJECT_SKERR=y CONFIG_IPV6=y CONFIG_IPV6_MIP6=y CONFIG_IPV6_MULTIPLE_TABLES=y @@ -42,6 +45,7 @@ CONFIG_IPV6_PRIVACY=y CONFIG_IPV6_ROUTER_PREF=y CONFIG_IPV6_ROUTE_INFO=y CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTICAST=y CONFIG_IP_MULTIPLE_TABLES=y CONFIG_IP_NF_ARPFILTER=y CONFIG_IP_NF_ARPTABLES=y @@ -52,13 +56,13 @@ CONFIG_IP_NF_MANGLE=y CONFIG_IP_NF_MATCH_AH=y CONFIG_IP_NF_MATCH_ECN=y CONFIG_IP_NF_MATCH_TTL=y +CONFIG_IP_NF_NAT=y CONFIG_IP_NF_RAW=y CONFIG_IP_NF_SECURITY=y CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_IP_NF_TARGET_NETMAP=y CONFIG_IP_NF_TARGET_REDIRECT=y CONFIG_IP_NF_TARGET_REJECT=y -CONFIG_IP_NF_TARGET_REJECT_SKERR=y CONFIG_NET=y CONFIG_NETDEVICES=y CONFIG_NETFILTER=y @@ -133,19 +137,26 @@ CONFIG_PPP_BSDCOMP=y CONFIG_PPP_DEFLATE=y CONFIG_PPP_MPPE=y CONFIG_PREEMPT=y -CONFIG_RESOURCE_COUNTERS=y +CONFIG_PROFILING=y +CONFIG_QFMT_V2=y +CONFIG_QUOTA=y +CONFIG_QUOTA_NETLINK_INTERFACE=y +CONFIG_QUOTA_TREE=y +CONFIG_QUOTACTL=y +CONFIG_RANDOMIZE_BASE=y CONFIG_RTC_CLASS=y CONFIG_RT_GROUP_SCHED=y +CONFIG_SECCOMP=y CONFIG_SECURITY=y CONFIG_SECURITY_NETWORK=y +CONFIG_SECURITY_PERF_EVENTS_RESTRICT=y CONFIG_SECURITY_SELINUX=y CONFIG_SETEND_EMULATION=y CONFIG_STAGING=y -CONFIG_SWITCH=y CONFIG_SWP_EMULATION=y CONFIG_SYNC=y -CONFIG_SYSVIPC=y CONFIG_TUN=y +CONFIG_UID_CPUTIME=y CONFIG_UNIX=y CONFIG_USB_GADGET=y CONFIG_USB_CONFIGFS=y @@ -155,8 +166,10 @@ CONFIG_USB_CONFIGFS_F_PTP=y CONFIG_USB_CONFIGFS_F_ACC=y CONFIG_USB_CONFIGFS_F_AUDIO_SRC=y CONFIG_USB_CONFIGFS_UEVENT=y +CONFIG_USB_CONFIGFS_F_MIDI=y CONFIG_USB_OTG_WAKELOCK=y CONFIG_XFRM_USER=y +# CONFIG_AIO is not set # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set # CONFIG_INPUT_MOUSE is not set # CONFIG_LEGACY_PTYS is not set @@ -164,11 +177,14 @@ CONFIG_XFRM_USER=y # CONFIG_PM_WAKELOCKS_GC is not set # CONFIG_VT is not set CONFIG_ANDROID_TIMED_GPIO=y +CONFIG_ARM_KERNMEM_PERMS=y CONFIG_BACKLIGHT_LCD_SUPPORT=y CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_RAM=y CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_CC_STACKPROTECTOR_STRONG=y CONFIG_COMPACTION=y +CONFIG_DEBUG_RODATA=y CONFIG_DM_UEVENT=y CONFIG_DRAGONRISE_FF=y CONFIG_ENABLE_DEFAULT_TRACERS=y @@ -266,14 +282,19 @@ CONFIG_TABLET_USB_AIPTEK=y CONFIG_TABLET_USB_GTCO=y CONFIG_TABLET_USB_HANWANG=y CONFIG_TABLET_USB_KBTAB=y -CONFIG_TABLET_USB_WACOM=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_TASK_XACCT=y CONFIG_TIMER_STATS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y CONFIG_UHID=y -CONFIG_UID_STAT=y +CONFIG_MEMORY_STATE_TIME=y CONFIG_USB_ANNOUNCE_NEW_DEVICES=y CONFIG_USB_EHCI_HCD=y CONFIG_USB_HIDDEV=y CONFIG_USB_USBNET=y CONFIG_VFAT_FS=y +CONFIG_CPUSETS=y +CONFIG_PROC_PID_CPUSET=y diff --git a/linaro/configs/big-LITTLE-MP.conf b/linaro/configs/big-LITTLE-MP.conf new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/linaro/configs/big-LITTLE-MP.conf diff --git a/linaro/configs/vexpress.conf b/linaro/configs/vexpress.conf index 562212a29b6e..74b53428e7b0 100644 --- a/linaro/configs/vexpress.conf +++ b/linaro/configs/vexpress.conf @@ -14,10 +14,13 @@ CONFIG_ARM_BIG_LITTLE_CPUFREQ=y CONFIG_ARM_VEXPRESS_SPC_CPUFREQ=y CONFIG_PM_OPP=y CONFIG_CPU_FREQ=y -CONFIG_CPU_FREQ_GOV_ONDEMAND=y CONFIG_CPU_FREQ_GOV_PERFORMANCE=y -CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_INTERACTIVE=y +CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE=y CONFIG_CMDLINE="console=ttyAMA0,38400n8 root=/dev/mmcblk0p2 rootwait mmci.fmax=4000000" +CONFIG_SECURITY_SELINUX_BOOTPARAM=y CONFIG_VFP=y CONFIG_NEON=y CONFIG_SCSI=y @@ -25,12 +28,22 @@ CONFIG_BLK_DEV_SD=y CONFIG_SMSC911X=y CONFIG_SMC91X=y CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_MOUSE=y +CONFIG_MOUSE_PS2=y CONFIG_SERIO_AMBAKMI=y CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y CONFIG_FB=y CONFIG_FB_ARMCLCD=y -CONFIG_FB_ARMHDLCD=y +CONFIG_DRM=y +CONFIG_DRM_ARM=y +CONFIG_DRM_HDLCD=y +CONFIG_CMA=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_SEL_MBYTES=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_I2C_VERSATILE=y +CONFIG_DRM_I2C_DUMMY=y CONFIG_LOGO=y # CONFIG_LOGO_LINUX_MONO is not set # CONFIG_LOGO_LINUX_VGA16 is not set diff --git a/linaro/configs/vexpress64.conf b/linaro/configs/vexpress64.conf index 1eafbc7f0cce..949dc7b2478c 100644 --- a/linaro/configs/vexpress64.conf +++ b/linaro/configs/vexpress64.conf @@ -1,12 +1,14 @@ CONFIG_ARCH_VEXPRESS=y CONFIG_SMP=y -CONFIG_NR_CPUS=8 CONFIG_HOTPLUG_CPU=y CONFIG_PREEMPT=y CONFIG_CMDLINE="console=ttyAMA0" +CONFIG_SECURITY_SELINUX_BOOTPARAM=y CONFIG_COMPAT=y CONFIG_SMC91X=y CONFIG_INPUT_EVDEV=y +CONFIG_INPUT_MOUSE=y +CONFIG_MOUSE_PS2=y CONFIG_SERIO_AMBAKMI=y CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y @@ -46,19 +48,6 @@ CONFIG_USB_EHCI_HCD=y CONFIG_USB_EHCI_HCD_PLATFORM=y CONFIG_NOP_USB_XCEIV=y CONFIG_USB_OHCI_HCD=y -CONFIG_MAILBOX=y -CONFIG_ARM_MHU=y -CONFIG_ARM_SCPI_PROTOCOL=y -CONFIG_SENSORS_ARM_SCPI=y -CONFIG_COMMON_CLK_SCPI=y -CONFIG_ARM_SCPI_CPUFREQ=y -CONFIG_ARM_BIG_LITTLE_CPUFREQ=y -CONFIG_ARM_DT_BL_CPUFREQ=y -CONFIG_CPU_FREQ_GOV_PERFORMANCE=y -CONFIG_CPU_FREQ_GOV_USERSPACE=y -CONFIG_CPU_FREQ_GOV_ONDEMAND=y -CONFIG_CPU_FREQ_GOV_INTERACTIVE=y -CONFIG_CPU_FREQ_DEFAULT_GOV_INTERACTIVE=y CONFIG_PCI=y CONFIG_PCI_MSI=y CONFIG_PCI_REALLOC_ENABLE_AUTO=y @@ -74,3 +63,42 @@ CONFIG_CONNECTOR=y CONFIG_ATA=y CONFIG_SATA_SIL24=y CONFIG_SKY2=y +CONFIG_ARM_TIMER_SP804=y +CONFIG_ARM_CPUIDLE=y +CONFIG_DRM=y +CONFIG_DRM_ARM=y +CONFIG_DRM_HDLCD=y +CONFIG_DRM_VIRT_ENCODER=y +CONFIG_CMA=y +CONFIG_DMA_CMA=y +CONFIG_CMA_SIZE_SEL_MBYTES=y +CONFIG_CMA_SIZE_MBYTES=32 +CONFIG_DRM_I2C_NXP_TDA998X=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_SEQUENCER=y +CONFIG_SND_SEQ_DUMMY=y +CONFIG_SND_MIXER_OSS=y +CONFIG_SND_PCM_OSS=y +CONFIG_SND_SEQUENCER_OSS=y +# CONFIG_SND_USB is not set +CONFIG_SND_SOC=y +CONFIG_SND_DESIGNWARE_I2S=y +CONFIG_SND_SOC_SPDIF=y +CONFIG_SND_SIMPLE_CARD=y +CONFIG_DMADEVICES=y +CONFIG_PL330_DMA=y +CONFIG_PM_OPP=y +CONFIG_MAILBOX=y +CONFIG_ARM_MHU=y +CONFIG_PLATFORM_MHU=y +CONFIG_ARM_SCMI_PROTOCOL=y +CONFIG_SENSORS_ARM_SCMI=y +CONFIG_COMMON_CLK_SCMI=y +CONFIG_ARM_SCMI_CPUFREQ=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 |