diff options
76 files changed, 8401 insertions, 48 deletions
diff --git a/Documentation/devicetree/bindings/arm/altera/fpga-dma.txt b/Documentation/devicetree/bindings/arm/altera/fpga-dma.txt new file mode 100644 index 000000000000..a08e9010d4e5 --- /dev/null +++ b/Documentation/devicetree/bindings/arm/altera/fpga-dma.txt @@ -0,0 +1,25 @@ +Altera FPGA DMA FIFO driver + +Required properties: +- compatible : "altr,fpga-dma"; + +- reg : CSR and DATA register resource definitions (address and length). + +- reg-names : Names of the register resources. Should be "csr", "data". + +- dmas : DMA request lines. Should be <&pdma 0 &pdma 1> + +- dma-names : Names of DMA request lines. Should be "tx", "rx". + +Example: + + fpgadma: fifo { + #address-cells = <1>; + #size-cells = <1>; + compatible = "altr,fpga-dma"; + reg = <0xff230000 0x20>, <0xc0011000 0x400>; + reg-names = "csr", "data"; + dmas = <&pdma 0 &pdma 1>; + dma-names = "tx", "rx"; + }; + diff --git a/Documentation/devicetree/bindings/firmware/intel,stratix10-rsu.txt b/Documentation/devicetree/bindings/firmware/intel,stratix10-rsu.txt new file mode 100644 index 000000000000..9df6f8987dee --- /dev/null +++ b/Documentation/devicetree/bindings/firmware/intel,stratix10-rsu.txt @@ -0,0 +1,28 @@ +Intel Remote System Update (RSU) for Stratix10 SoC FPGAs +============================================ +The Intel Remote System Update (RSU) driver exposes interfaces +accessed through the Intel Service Layer to user space via SysFS +device attribute nodes. The RSU interfaces report/control some of +the optional RSU features of the Stratix 10 SoC FPGA. + +The RSU feature provides a way for customers to update the boot +configuration of a Stratix 10 SoC device with significantly reduced +risk of corrupting the bitstream storage and bricking the system. + +Required properties: +------------------- +The intel-rsu node has the following mandatory properties and must be located +under the firmware/svc node. + +- compatible: "intel,stratix10-rsu" + +Example: +------- + + firmware { + svc { + rsu { + compatible = "intel,stratix10-rsu"; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/firmware/intel,stratix10-svc.txt b/Documentation/devicetree/bindings/firmware/intel,stratix10-svc.txt new file mode 100644 index 000000000000..1fa66065acc6 --- /dev/null +++ b/Documentation/devicetree/bindings/firmware/intel,stratix10-svc.txt @@ -0,0 +1,57 @@ +Intel Service Layer Driver for Stratix10 SoC +============================================ +Intel Stratix10 SoC is composed of a 64 bit quad-core ARM Cortex A53 hard +processor system (HPS) and Secure Device Manager (SDM). When the FPGA is +configured from HPS, there needs to be a way for HPS to notify SDM the +location and size of the configuration data. Then SDM will get the +configuration data from that location and perform the FPGA configuration. + +To meet the whole system security needs and support virtual machine requesting +communication with SDM, only the secure world of software (EL3, Exception +Layer 3) can interface with SDM. All software entities running on other +exception layers must channel through the EL3 software whenever it needs +service from SDM. + +Intel Stratix10 service layer driver, running at privileged exception level +(EL1, Exception Layer 1), interfaces with the service providers and provides +the services for FPGA configuration, QSPI, Crypto and warm reset. Service layer +driver also manages secure monitor call (SMC) to communicate with secure monitor +code running in EL3. + +Required properties: +------------------- +The svc node has the following mandatory properties, must be located under +the firmware node. + +- compatible: "intel,stratix10-svc" +- method: smc or hvc + smc - Secure Monitor Call + hvc - Hypervisor Call +- memory-region: + phandle to the reserved memory node. See + Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt + for details + +Example: +------- + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + service_reserved: svcbuffer@0 { + compatible = "shared-dma-pool"; + reg = <0x0 0x0 0x0 0x1000000>; + alignment = <0x1000>; + no-map; + }; + }; + + firmware { + svc { + compatible = "intel,stratix10-svc"; + method = "smc"; + memory-region = <&service_reserved>; + }; + }; diff --git a/Documentation/devicetree/bindings/fpga/altera-partial-reconfig.txt b/Documentation/devicetree/bindings/fpga/altera-partial-reconfig.txt new file mode 100644 index 000000000000..bbbb9cdb3da7 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/altera-partial-reconfig.txt @@ -0,0 +1,12 @@ +Altera Partial Reconfiguration IP Core + +Required properties: +- compatible : should contain "altr,pr-ip-core" +- reg : base address and size for memory mapped io. + +Example: + + fpga_mgr: fpga-mgr@ff20c000 { + compatible = "altr,pr-ip-core"; + reg = <0xff20c000 0x8>; + }; diff --git a/Documentation/devicetree/bindings/fpga/fpga-region.txt b/Documentation/devicetree/bindings/fpga/fpga-region.txt index 6db8aeda461a..f071306d888d 100644 --- a/Documentation/devicetree/bindings/fpga/fpga-region.txt +++ b/Documentation/devicetree/bindings/fpga/fpga-region.txt @@ -348,6 +348,17 @@ The Device Tree Overlay will contain: * child nodes corresponding to hardware that will be loaded in this region of the FPGA. +The Device Tree Overlay will optionally contain: + * "region-unfreeze-timeout-us" + Maximum time in microseconds to wait for bridges to successfully become + enabled after the region has been programmed. + * "region-freeze-timeout-us" + Maximum time in microseconds to wait for bridges to successfully become + disabled before the region has been programmed. + * "config-complete-timeout-us" + Maximum time in microseconds to wait for the FPGA to go to operating state + after the region has been programmed. + Device Tree Example: Full Reconfiguration without Bridges ========================================================= @@ -382,6 +393,8 @@ fragment@0 { #size-cells = <1>; firmware-name = "zynq-gpio.bin"; + region-unfreeze-timeout-us = <4>; + region-freeze-timeout-us = <4>; gpio1: gpio@40000000 { compatible = "xlnx,xps-gpio-1.00.a"; diff --git a/Documentation/devicetree/bindings/fpga/intel-stratix10-soc-fpga-mgr.txt b/Documentation/devicetree/bindings/fpga/intel-stratix10-soc-fpga-mgr.txt new file mode 100644 index 000000000000..78de68975b47 --- /dev/null +++ b/Documentation/devicetree/bindings/fpga/intel-stratix10-soc-fpga-mgr.txt @@ -0,0 +1,10 @@ +Intel Stratix10 SoC FPGA Manager + +Required properties: +- compatible : should contain "intel,stratix10-soc-fpga-mgr" + +Example: + + fpga_mgr: fpga-mgr@0 { + compatible = "intel,stratix10-soc-fpga-mgr"; + }; diff --git a/Documentation/devicetree/bindings/misc/altera-hwmutex.txt b/Documentation/devicetree/bindings/misc/altera-hwmutex.txt new file mode 100644 index 000000000000..6a583d08ece4 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/altera-hwmutex.txt @@ -0,0 +1,22 @@ +Altera hardware mutex +Altera hardware mutex can provide hardware assistance for synchronization and +mutual exclusion between processors in asymmetric/symmetric multiprocessing +(AMP/SMP) system or multi processes/threads in uniprocessor system. + +Required properties: +- compatible : "altr,mutex-1.0". +- reg : physical base address of the mutex and length of memory mapped + region. + +Example: + mutex0: mutex0@0x100 { + compatible = "altr,hwmutex-1.0"; + reg = <0x100 0x8>; + }; + +Example of mutex's client node that includes mutex phandle. + mclient0: mclient0@0x200 { + compatible = "client-1.0"; + reg = <0x200 0x10>; + mutex = <&mutex0>; + }; diff --git a/Documentation/devicetree/bindings/misc/altera-interrupt-latency-counter.txt b/Documentation/devicetree/bindings/misc/altera-interrupt-latency-counter.txt new file mode 100644 index 000000000000..09f682057616 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/altera-interrupt-latency-counter.txt @@ -0,0 +1,49 @@ +Altera Interrupt Latency Counter soft IP +Altera Interrupt Latency Counter IP core driver provides a sysfs interface +for user to obtain interrupt latency values from Altera Interrupt Latency +Counter soft IP. + +The sysfs interface is located at path, +/sys/bus/platform/devices/{addr}.ilc/ilc_data/{int_#} +with +- {addr} = the base address of the soft ip +- {int_#} = the interrupt number + +Example use case: +# cat /sys/bus/platform/devices/c0010000.ilc/ilc_data/40 + +Required properties: +- compatible : + - "altr,ilc-1.0" +- reg : + - physical base address of the soft ip and length of memory mapped region +- interrupt-parent : + - interrupt source phandle similiar to the interrupt source node +- interrupts : + -interrupt number. The interrupt specifier format depends on the interrupt + controller parent + +Altera specific properties: +- altr,sw-fifo-depth : + - define software fifo depth needed to record latency values + +Note: +- For edge triggered interrupt, the order of loading the ILC driver relative + to driver of the actual interrupt source affects the meaning of the ILC + values. If the ILC driver is loaded first, then the count values represent + the time to the start of the interrupt handler of the of the interrupt source. + If the order is switched, then the counts represent the time to finish the + interrupt handler for the interrupt source. + +- The driver for the interrupt source must be changed to request a shared irq. + +Example: + interrupt_latency_counter_0: intc@0x10000000 { + compatible = "altr,ilc-1.0"; + reg = <0x10000000 0x00000100>; + interrupt-parent = < &interrupt_parent >; + interrupts = < 0 1 4 >; + altr,sw-fifo-depth = < 32 >; + }; + + diff --git a/Documentation/devicetree/bindings/misc/altera_sysid.txt b/Documentation/devicetree/bindings/misc/altera_sysid.txt new file mode 100644 index 000000000000..c3bbd576b74b --- /dev/null +++ b/Documentation/devicetree/bindings/misc/altera_sysid.txt @@ -0,0 +1,11 @@ +Altera Sysid IP core driver + +Required properties: +- compatible: altr,sysid-1.0 + +Example: + +sysid_qsys: sysid@0x10000 { + compatible = "altr,sysid-1.0"; + reg = < 0x10000 0x00000008 >; +}; diff --git a/Documentation/devicetree/bindings/net/micrel-ksz90x1.txt b/Documentation/devicetree/bindings/net/micrel-ksz90x1.txt index e22d8cfea687..35970132d4ff 100644 --- a/Documentation/devicetree/bindings/net/micrel-ksz90x1.txt +++ b/Documentation/devicetree/bindings/net/micrel-ksz90x1.txt @@ -12,7 +12,7 @@ and therefore may overwrite them. KSZ9021: All skew control options are specified in picoseconds. The minimum - value is 0, the maximum value is 3000, and it is incremented by 200ps + value is 0, the maximum value is 1800, and it is incremented by 120ps steps. Optional properties: @@ -37,6 +37,71 @@ KSZ9031: step is 60ps. The default value is the neutral setting, so setting rxc-skew-ps=<0> actually results in -900 picoseconds adjustment. + The KSZ9031 hardware supports a range of skew values from negative to + positive, where the specific range is property dependent. All values + specified in the devicetree are offset by the minimum value so they + can be represented as positive integers in the devicetree since it's + difficult to represent a negative number in the devictree. + + The following 5-bit values table apply to rxc-skew-ps and txc-skew-ps. + + Pad Skew Value Delay (ps) Devicetree Value + ------------------------------------------------------ + 0_0000 -900ps 0 + 0_0001 -840ps 60 + 0_0010 -780ps 120 + 0_0011 -720ps 180 + 0_0100 -660ps 240 + 0_0101 -600ps 300 + 0_0110 -540ps 360 + 0_0111 -480ps 420 + 0_1000 -420ps 480 + 0_1001 -360ps 540 + 0_1010 -300ps 600 + 0_1011 -240ps 660 + 0_1100 -180ps 720 + 0_1101 -120ps 780 + 0_1110 -60ps 840 + 0_1111 0ps 900 + 1_0000 60ps 960 + 1_0001 120ps 1020 + 1_0010 180ps 1080 + 1_0011 240ps 1140 + 1_0100 300ps 1200 + 1_0101 360ps 1260 + 1_0110 420ps 1320 + 1_0111 480ps 1380 + 1_1000 540ps 1440 + 1_1001 600ps 1500 + 1_1010 660ps 1560 + 1_1011 720ps 1620 + 1_1100 780ps 1680 + 1_1101 840ps 1740 + 1_1110 900ps 1800 + 1_1111 960ps 1860 + + The following 4-bit values table apply to the txdX-skew-ps, rxdX-skew-ps + data pads, and the rxdv-skew-ps, txen-skew-ps control pads. + + Pad Skew Value Delay (ps) Devicetree Value + ------------------------------------------------------ + 0000 -420ps 0 + 0001 -360ps 60 + 0010 -300ps 120 + 0011 -240ps 180 + 0100 -180ps 240 + 0101 -120ps 300 + 0110 -60ps 360 + 0111 0ps 420 + 1000 60ps 480 + 1001 120ps 540 + 1010 180ps 600 + 1011 240ps 660 + 1100 300ps 720 + 1101 360ps 780 + 1110 420ps 840 + 1111 480ps 900 + Optional properties: Maximum value of 1860, default value 900: @@ -66,11 +131,21 @@ KSZ9031: Examples: + /* Attach to an Ethernet device with autodetected PHY */ + &enet { + rxc-skew-ps = <1800>; + rxdv-skew-ps = <0>; + txc-skew-ps = <1800>; + txen-skew-ps = <0>; + status = "okay"; + }; + + /* Attach to an explicitly-specified PHY */ mdio { phy0: ethernet-phy@0 { - rxc-skew-ps = <3000>; + rxc-skew-ps = <1800>; rxdv-skew-ps = <0>; - txc-skew-ps = <3000>; + txc-skew-ps = <1800>; txen-skew-ps = <0>; reg = <0>; }; @@ -79,3 +154,20 @@ Examples: phy = <&phy0>; phy-mode = "rgmii-id"; }; + +References + + Micrel ksz9021rl/rn Data Sheet, Revision 1.2. Dated 2/13/2014. + http://www.micrel.com/_PDF/Ethernet/datasheets/ksz9021rl-rn_ds.pdf + + Micrel ksz9031rnx Data Sheet, Revision 2.1. Dated 11/20/2014. + http://www.micrel.com/_PDF/Ethernet/datasheets/KSZ9031RNX.pdf + +Notes: + + Note that a previous version of the Micrel ksz9021rl/rn Data Sheet + was missing extended register 106 (transmit data pad skews), and + incorrectly specified the ps per step as 200ps/step instead of + 120ps/step. The latest update to this document reflects the latest + revision of the Micrel specification even though usage in the kernel + still reflects that incorrect document. diff --git a/Documentation/devicetree/bindings/tty/newhaven_lcd.txt b/Documentation/devicetree/bindings/tty/newhaven_lcd.txt new file mode 100644 index 000000000000..5ff0438640d6 --- /dev/null +++ b/Documentation/devicetree/bindings/tty/newhaven_lcd.txt @@ -0,0 +1,21 @@ +* TTY on a Newhaven NHD‐0216K3Z‐NSW‐BBW LCD connected to I2C + +Required properties: +- compatible: Should be "newhaven,nhd‐0216k3z‐nsw‐bbw"; +- reg: i2c address +- height: should be 2 lines +- width: should be 16 characters +- brightness: backlight brightness. Range is 1 to 8, where + 1=OFF and 8=maximum brightness. + +Example: + +&i2c0 { + lcd: lcd@28 { + compatible = "newhaven,nhd‐0216k3z‐nsw‐bbw"; + reg = <0x28>; + height = <2>; + width = <16>; + brightness = <8>; + }; + diff --git a/Documentation/devicetree/bindings/video/altvipfb.txt b/Documentation/devicetree/bindings/video/altvipfb.txt new file mode 100644 index 000000000000..5e376184ba33 --- /dev/null +++ b/Documentation/devicetree/bindings/video/altvipfb.txt @@ -0,0 +1,22 @@ +Altera Video and Image Processing(VIP) Frame Reader bindings + +Required properties: +- compatible: "altr,vip-frame-reader-9.1" or "altr,vip-frame-reader-1.0" +- reg: Physical base address and length of the framebuffer controller's + registers. +- max-width: The width of the framebuffer in pixels. +- max-height: The height of the framebuffer in pixels. +- bits-per-color: only "8" is currently supported +- mem-word-width = the bus width of the avalon master port on the frame reader + +Example: + +alt_vip_vfr_0: vip@0xff260000 { + compatible = "altr,vip-frame-reader-1.0"; + reg = <0xff260000 0x00000080>; + max-width = <1024>; + max-height = <768>; + bits-per-color = <8>; + mem-word-width = <128>; +}; + diff --git a/Documentation/devicetree/configfs-overlays.txt b/Documentation/devicetree/configfs-overlays.txt new file mode 100644 index 000000000000..185d85ef52e4 --- /dev/null +++ b/Documentation/devicetree/configfs-overlays.txt @@ -0,0 +1,31 @@ +Howto use the configfs overlay interface. + +A device-tree configfs entry is created in /config/device-tree/overlays +and and it is manipulated using standard file system I/O. +Note that this is a debug level interface, for use by developers and +not necessarily something accessed by normal users due to the +security implications of having direct access to the kernel's device tree. + +* To create an overlay you mkdir the directory: + + # mkdir /config/device-tree/overlays/foo + +* Either you echo the overlay firmware file to the path property file. + + # echo foo.dtbo >/config/device-tree/overlays/foo/path + +* Or you cat the contents of the overlay to the dtbo file + + # cat foo.dtbo >/config/device-tree/overlays/foo/dtbo + +The overlay file will be applied, and devices will be created/destroyed +as required. + +To remove it simply rmdir the directory. + + # rmdir /config/device-tree/overlays/foo + +The rationale for the dual interface (firmware & direct copy) is that each is +better suited to different use patterns. The firmware interface is what's +intended to be used by hardware managers in the kernel, while the copy interface +make sense for developers (since it avoids problems with namespaces). diff --git a/Documentation/fpga/debugfs.txt b/Documentation/fpga/debugfs.txt new file mode 100644 index 000000000000..b01950f76e20 --- /dev/null +++ b/Documentation/fpga/debugfs.txt @@ -0,0 +1,39 @@ +FPGA Manager DebugFS interface for FPGA reprogramming. + +Alan Tull 2016 + +Each FPGA gets its own directory such as <debugfs>/fpga_manager/fpga0 and +three files: + + - [RW] flags: flags as defined in fpga-mgr.h. For example: + + $ echo 1 > /sys/kernel/debug/fpga_manager/fpga0/flags + + - [RW] config_complete_timeout_us: time out in microseconds to wait for + FPGA to go to operating state after + region has been programmed. + + $ echo 4 > /sys/kernel/debug/fpga_manager/fpga0/config_complete_timeout_us + + - [RW] firmware_name: Name of an FPGA image firmware file. Writing initiates + a complete FPGA programming cycle. Note that the image + file must be in a directory on the firmware search path + such as /lib/firmware. + + $ echo image.rbf > /sys/kernel/debug/fpga_manager/fpga0/firmware_name + + - [WO] image: Raw FPGA image data. Writing the FPGA image data will + initiate a complete FPGA programming cycle. Data must + be written in one chunk, for example: + + $ dd bs=10M if=./image.rbf of=/sys/kernel/debug/fpga_manager/fpga0/image + (where image.rbf < 10M) + +To program the FPGA, write the flags (if needed), then use either the +firmware_name or image file to program. + +This interface does not handle bridges or loading/unloading of soft IP device +drivers. This makes it really easy to mess things up by doing things like +reprogramming the hardware out from under a driver or reprogramming while a +bridge is enabled, causing gunk to go out on a cpu bus. It should go without +saying that this interface is for debug only. Not intended for production use. diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 37a3de760d40..8382337df6b2 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -868,12 +868,14 @@ dtb-$(CONFIG_ARCH_SOCFPGA) += \ socfpga_arria10_socdk_nand.dtb \ socfpga_arria10_socdk_qspi.dtb \ socfpga_arria10_socdk_sdmmc.dtb \ + socfpga_arria10_swvp.dtb \ socfpga_cyclone5_mcvevk.dtb \ socfpga_cyclone5_socdk.dtb \ socfpga_cyclone5_de0_sockit.dtb \ socfpga_cyclone5_sockit.dtb \ socfpga_cyclone5_socrates.dtb \ socfpga_cyclone5_sodia.dtb \ + socfpga_cyclone5_trcom.dtb \ socfpga_cyclone5_vining_fpga.dtb \ socfpga_vt.dtb dtb-$(CONFIG_ARCH_SPEAR13XX) += \ diff --git a/arch/arm/boot/dts/socfpga.dtsi b/arch/arm/boot/dts/socfpga.dtsi index b38f8c240558..28ecb4bdf5aa 100644 --- a/arch/arm/boot/dts/socfpga.dtsi +++ b/arch/arm/boot/dts/socfpga.dtsi @@ -101,7 +101,7 @@ }; }; - base_fpga_region { + base_fpga_region: base-fpga-region { compatible = "fpga-region"; fpga-mgr = <&fpgamgr0>; diff --git a/arch/arm/boot/dts/socfpga_arria10.dtsi b/arch/arm/boot/dts/socfpga_arria10.dtsi index bd1985694bca..a2326687b27a 100644 --- a/arch/arm/boot/dts/socfpga_arria10.dtsi +++ b/arch/arm/boot/dts/socfpga_arria10.dtsi @@ -79,6 +79,7 @@ #dma-requests = <32>; clocks = <&l4_main_clk>; clock-names = "apb_pclk"; + microcode-cached; }; }; @@ -748,7 +749,7 @@ timer@ffffc600 { compatible = "arm,cortex-a9-twd-timer"; reg = <0xffffc600 0x100>; - interrupts = <1 13 0xf04>; + interrupts = <1 13 0xf01>; clocks = <&mpu_periph_clk>; }; diff --git a/arch/arm/boot/dts/socfpga_arria10_socdk.dtsi b/arch/arm/boot/dts/socfpga_arria10_socdk.dtsi index 64cc86a98771..2a7466891d0e 100644 --- a/arch/arm/boot/dts/socfpga_arria10_socdk.dtsi +++ b/arch/arm/boot/dts/socfpga_arria10_socdk.dtsi @@ -126,6 +126,10 @@ compatible = "altr,a10sr-reset"; #reset-cells = <1>; }; + + ps_alarm { + compatible = "altr,a10sr-hwmon"; + }; }; }; @@ -140,6 +144,14 @@ i2c-sda-falling-time-ns = <6000>; i2c-scl-falling-time-ns = <6000>; + lcd: lcd@28 { + compatible = "newhaven,nhd-0216k3z-nsw-bbw"; + reg = <0x28>; + height = <2>; + width = <16>; + brightness = <8>; + }; + eeprom@51 { compatible = "atmel,24c32"; reg = <0x51>; @@ -151,6 +163,11 @@ reg = <0x68>; }; + max@4c { + compatible = "max1619"; + reg = <0x4c>; + }; + ltc@5c { compatible = "ltc2977"; reg = <0x5c>; diff --git a/arch/arm/boot/dts/socfpga_arria10_socdk_qspi.dts b/arch/arm/boot/dts/socfpga_arria10_socdk_qspi.dts index beb2fc6b9eb6..0939fa7f6943 100644 --- a/arch/arm/boot/dts/socfpga_arria10_socdk_qspi.dts +++ b/arch/arm/boot/dts/socfpga_arria10_socdk_qspi.dts @@ -30,7 +30,7 @@ m25p,fast-read; cdns,page-size = <256>; cdns,block-size = <16>; - cdns,read-delay = <4>; + cdns,read-delay = <3>; cdns,tshsl-ns = <50>; cdns,tsd2d-ns = <50>; cdns,tchsh-ns = <4>; diff --git a/arch/arm/boot/dts/socfpga_arria10_swvp.dts b/arch/arm/boot/dts/socfpga_arria10_swvp.dts new file mode 100644 index 000000000000..c1a315524ba1 --- /dev/null +++ b/arch/arm/boot/dts/socfpga_arria10_swvp.dts @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2015 Altera Corporation. All rights reserved. + * + * 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/>. + */ + +/dts-v1/; + +/ { + #address-cells = <0x1>; + #size-cells = <0x1>; + model = "Altera SOCFPGA Arria 10"; + compatible = "altr,socfpga-arria10", "altr,socfpga"; + + chosen { + bootargs = "console=ttyS1,115200 rootwait earlyprintk root=/dev/mmcblk0p2"; + }; + + aliases { + ethernet0 = "/soc/ethernet@ff800000"; + serial0 = "/soc/serial0@ffc02000"; + serial1 = "/soc/serial1@ffc02100"; + timer0 = "/soc/timer0@ffc02700"; + timer1 = "/soc/timer1@ffc02800"; + timer2 = "/soc/timer2@ffd00000"; + timer3 = "/soc/timer3@ffd00100"; + }; + + memory { + device_type = "memory"; + reg = <0x0 0x40000000>; + }; + + cpus { + #address-cells = <0x1>; + #size-cells = <0x0>; + + cpu@0 { + compatible = "arm,cortex-a9"; + device_type = "cpu"; + reg = <0x0>; + next-level-cache = <0x1>; + }; + + cpu@1 { + compatible = "arm,cortex-a9"; + device_type = "cpu"; + reg = <0x1>; + next-level-cache = <0x1>; + }; + }; + + intc@ffffd000 { + compatible = "arm,cortex-a9-gic"; + #interrupt-cells = <0x3>; + interrupt-controller; + reg = <0xffffd000 0x1000 0xffffc100 0x100>; + linux,phandle = <0x2>; + phandle = <0x2>; + }; + + soc { + #address-cells = <0x1>; + #size-cells = <0x1>; + compatible = "simple-bus"; + device_type = "soc"; + interrupt-parent = <0x2>; + ranges; + + clkmgr@ffd04000 { + compatible = "altr,clk-mgr"; + reg = <0xffd04000 0x1000>; + + clocks { + #address-cells = <0x1>; + #size-cells = <0x0>; + + cb_intosc_hs_div2_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + linux,phandle = <0xb>; + phandle = <0xb>; + }; + + cb_intosc_ls_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + linux,phandle = <0x4>; + phandle = <0x4>; + }; + + f2s_free_clk { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + linux,phandle = <0x5>; + phandle = <0x5>; + }; + + osc1 { + #clock-cells = <0x0>; + compatible = "fixed-clock"; + clock-frequency = <0x17d7840>; + linux,phandle = <0x3>; + phandle = <0x3>; + }; + + main_pll { + #address-cells = <0x1>; + #size-cells = <0x0>; + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-pll-clock"; + clocks = <0x3 0x4 0x5>; + reg = <0x40>; + linux,phandle = <0x6>; + phandle = <0x6>; + + main_mpu_base_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + div-reg = <0x140 0x0 0xb>; + linux,phandle = <0x9>; + phandle = <0x9>; + }; + + main_noc_base_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + div-reg = <0x144 0x0 0xb>; + linux,phandle = <0xc>; + phandle = <0xc>; + }; + + main_emaca_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x68>; + }; + + main_emacb_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x6c>; + }; + + main_emac_ptp_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x70>; + }; + + main_gpio_db_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x74>; + }; + + main_sdmmc_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x78>; + linux,phandle = <0x10>; + phandle = <0x10>; + }; + + main_s2f_usr0_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x7c>; + }; + + main_s2f_usr1_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x80>; + linux,phandle = <0xe>; + phandle = <0xe>; + }; + + main_hmc_pll_ref_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x84>; + }; + + main_periph_ref_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x6>; + reg = <0x9c>; + linux,phandle = <0x7>; + phandle = <0x7>; + }; + }; + + periph_pll { + #address-cells = <0x1>; + #size-cells = <0x0>; + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-pll-clock"; + clocks = <0x3 0x4 0x5 0x7>; + reg = <0xc0>; + linux,phandle = <0x8>; + phandle = <0x8>; + + peri_mpu_base_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + div-reg = <0x140 0x10 0xb>; + linux,phandle = <0xa>; + phandle = <0xa>; + }; + + peri_noc_base_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + div-reg = <0x144 0x10 0xb>; + linux,phandle = <0xd>; + phandle = <0xd>; + }; + + peri_emaca_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + reg = <0xe8>; + }; + + peri_emacb_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + reg = <0xec>; + }; + + peri_emac_ptp_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + reg = <0xf0>; + }; + + peri_gpio_db_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + reg = <0xf4>; + }; + + peri_sdmmc_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + reg = <0xf8>; + linux,phandle = <0x11>; + phandle = <0x11>; + }; + + peri_s2f_usr0_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + reg = <0xfc>; + }; + + peri_s2f_usr1_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + reg = <0x100>; + linux,phandle = <0xf>; + phandle = <0xf>; + }; + + peri_hmc_pll_ref_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x8>; + reg = <0x104>; + }; + }; + + mpu_free_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x9 0xa 0x3 0xb 0x5>; + reg = <0x60>; + linux,phandle = <0x13>; + phandle = <0x13>; + }; + + noc_free_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0xc 0xd 0x3 0xb 0x5>; + reg = <0x64>; + linux,phandle = <0x12>; + phandle = <0x12>; + }; + + s2f_user1_free_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0xe 0xf 0x3 0xb 0x5>; + reg = <0x104>; + }; + + sdmmc_free_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x10 0x11 0x3 0xb 0x5>; + fixed-divider = <0x4>; + reg = <0xf8>; + linux,phandle = <0x14>; + phandle = <0x14>; + }; + + l4_sys_free_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <0x12>; + fixed-divider = <0x4>; + }; + + l4_main_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x12>; + div-reg = <0xa8 0x0 0x2>; + clk-gate = <0x48 0x1>; + linux,phandle = <0x15>; + phandle = <0x15>; + }; + + l4_mp_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x12>; + div-reg = <0xa8 0x8 0x2>; + clk-gate = <0x48 0x2>; + linux,phandle = <0x16>; + phandle = <0x16>; + }; + + l4_sp_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x12>; + div-reg = <0xa8 0x10 0x2>; + clk-gate = <0x48 0x3>; + }; + + mpu_periph_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x13>; + fixed-divider = <0x4>; + clk-gate = <0x48 0x0>; + }; + + sdmmc_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x14>; + clk-gate = <0xc8 0x5>; + }; + + qspi_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x15>; + clk-gate = <0xc8 0xb>; + }; + + nand_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x16>; + clk-gate = <0xc8 0xa>; + }; + + spi_m_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x15>; + clk-gate = <0xc8 0x9>; + }; + + usb_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0x16>; + clk-gate = <0xc8 0x8>; + }; + + s2f_usr1_clk { + #clock-cells = <0x0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <0xf>; + clk-gate = <0xc8 0x6>; + }; + }; + }; + + l2-cache@fffff000 { + compatible = "arm,pl310-cache"; + reg = <0xfffff000 0x1000>; + interrupts = <0x0 0x12 0x4>; + cache-unified; + cache-level = <0x2>; + linux,phandle = <0x1>; + phandle = <0x1>; + }; + + dwmmc0@ff808000 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "altr,socfpga-dw-mshc"; + reg = <0xff808000 0x1000>; + interrupts = <0x0 0x62 0x4>; + fifo-depth = <0x400>; + status = "okay"; + num-slots = <0x1>; + supports-highspeed; + broken-cd; + altr,dw-mshc-ciu-div = <0x3>; + altr,dw-mshc-sdr-timing = <0x0 0x3>; + clocks = <0x16 0x14>; + clock-names = "biu", "ciu"; + clock-freq-min-max = <0x61a80 0x17d7840>; + pwr-en = <0x0>; + clock-frequency = <0x17d7840>; + + slot@0 { + reg = <0x0>; + bus-width = <0x4>; + }; + }; + + rstmgr@ffd05000 { + #reset-cells = <0x1>; + compatible = "altr,rst-mgr"; + reg = <0xffd05000 0x100>; + }; + + sysmgr@ffd06000 { + compatible = "altr,sys-mgr", "syscon"; + reg = <0xffd06000 0x300>; + cpu1-start-addr = <0xffd06230>; + }; + + timer@ffffc600 { + compatible = "arm,cortex-a9-twd-timer"; + reg = <0xffffc600 0x100>; + interrupts = <0x1 0xd 0xf04>; + clock-frequency = <0x5f5e100>; + }; + + ethernet@ff800000 { + compatible = "altr,socfpga-stmmac", "snps,dwmac-3.72a", "snps,dwmac"; + reg = <0xff800000 0x2000>; + interrupts = <0x0 0x5c 0x4>; + interrupt-names = "macirq"; + mac-address = [00 00 00 00 00 00]; + clocks = <0x16>; + clock-names = "stmmaceth"; + status = "okay"; + phy-mode = "rgmii"; + phy-addr = <0xffffffff>; + snps,max-mtu = <0x0>; + }; + + timer0@ffc02700 { + compatible = "snps,dw-apb-timer-sp"; + interrupts = <0x0 0x73 0x4>; + reg = <0xffc02700 0x100>; + clock-frequency = <0x17d7840>; + }; + + timer1@ffc02800 { + compatible = "snps,dw-apb-timer-sp"; + interrupts = <0x0 0x74 0x4>; + reg = <0xffc02800 0x100>; + clock-frequency = <0x17d7840>; + }; + + timer2@ffd00000 { + compatible = "snps,dw-apb-timer-osc"; + interrupts = <0x0 0x75 0x4>; + reg = <0xffd00000 0x100>; + clock-frequency = <0x17d7840>; + }; + + timer3@ffd00100 { + compatible = "snps,dw-apb-timer-osc"; + interrupts = <0x0 0x76 0x4>; + reg = <0xffd01000 0x100>; + clock-frequency = <0x17d7840>; + }; + + serial0@ffc02000 { + compatible = "snps,dw-apb-uart"; + reg = <0xffc02000 0x100>; + interrupts = <0x0 0x6e 0x4>; + reg-shift = <0x2>; + reg-io-width = <0x4>; + status = "okay"; + clock-frequency = <0x5f5e100>; + }; + + serial1@ffc02100 { + compatible = "snps,dw-apb-uart"; + reg = <0xffc02100 0x100>; + interrupts = <0x0 0x6f 0x4>; + reg-shift = <0x2>; + reg-io-width = <0x4>; + status = "okay"; + clock-frequency = <0x5f5e100>; + }; + }; +}; diff --git a/arch/arm/boot/dts/socfpga_arria5_socdk.dts b/arch/arm/boot/dts/socfpga_arria5_socdk.dts index aac4feea86f3..1bb3a2450773 100644 --- a/arch/arm/boot/dts/socfpga_arria5_socdk.dts +++ b/arch/arm/boot/dts/socfpga_arria5_socdk.dts @@ -107,6 +107,14 @@ i2c-sda-falling-time-ns = <5000>; i2c-scl-falling-time-ns = <5000>; + lcd: lcd@28 { + compatible = "newhaven,nhd-0216k3z-nsw-bbw"; + reg = <0x28>; + height = <2>; + width = <16>; + brightness = <8>; + }; + eeprom@51 { compatible = "atmel,24c32"; reg = <0x51>; diff --git a/arch/arm/boot/dts/socfpga_cyclone5_socdk.dts b/arch/arm/boot/dts/socfpga_cyclone5_socdk.dts index 155829f9eba1..44f0644317dc 100644 --- a/arch/arm/boot/dts/socfpga_cyclone5_socdk.dts +++ b/arch/arm/boot/dts/socfpga_cyclone5_socdk.dts @@ -111,6 +111,14 @@ i2c-sda-falling-time-ns = <5000>; i2c-scl-falling-time-ns = <5000>; + lcd: lcd@28 { + compatible = "newhaven,nhd-0216k3z-nsw-bbw"; + reg = <0x28>; + height = <2>; + width = <16>; + brightness = <8>; + }; + eeprom@51 { compatible = "atmel,24c32"; reg = <0x51>; diff --git a/arch/arm/boot/dts/socfpga_cyclone5_trcom.dts b/arch/arm/boot/dts/socfpga_cyclone5_trcom.dts new file mode 100644 index 000000000000..eec02323ae44 --- /dev/null +++ b/arch/arm/boot/dts/socfpga_cyclone5_trcom.dts @@ -0,0 +1,150 @@ +/* + * Copyright Altera Corporation (C) 2012,2014. All rights reserved. + * + * 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 "socfpga_cyclone5.dtsi" + +/ { + model = "Altera SOCFPGA Cyclone V SoC Development Kit"; + compatible = "altr,socfpga-cyclone5", "altr,socfpga"; + + chosen { + bootargs = "console=ttyS0,115200"; + }; + + memory { + name = "memory"; + device_type = "memory"; + reg = <0x0 0x40000000>; /* 1GB */ + }; + + aliases { + /* this allow the ethaddr uboot environmnet variable contents + * to be added to the gmac1 device tree blob. + */ + ethernet0 = &gmac1; + }; + + regulator_3_3v: 3-3-v-regulator { + compatible = "regulator-fixed"; + regulator-name = "3.3V"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + leds { + compatible = "gpio-leds"; + hps0 { + label = "hps_led0"; + gpios = <&portb 15 1>; + }; + + hps1 { + label = "hps_led1"; + gpios = <&portb 14 1>; + }; + + hps2 { + label = "hps_led2"; + gpios = <&portb 13 1>; + }; + + hps3 { + label = "hps_led3"; + gpios = <&portb 12 1>; + }; + }; +}; + +&can0 { + status = "okay"; +}; + +&gmac1 { + status = "okay"; + phy-mode = "rgmii"; + + rxd0-skew-ps = <0>; + rxd1-skew-ps = <0>; + rxd2-skew-ps = <0>; + rxd3-skew-ps = <0>; + txen-skew-ps = <0>; + txc-skew-ps = <2600>; + rxdv-skew-ps = <0>; + rxc-skew-ps = <2000>; + max-frame-size = <3800>; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&gpio2 { + status = "okay"; +}; + +&i2c0 { + status = "okay"; + clock-frequency = <100000>; + + /* + * adjust the falling times to decrease the i2c frequency to 50Khz + * because the LCD module does not work at the standard 100Khz + */ + i2c-sda-falling-time-ns = <5000>; + i2c-scl-falling-time-ns = <5000>; + + lcd: lcd@28 { + compatible = "newhaven,nhd-0216k3z-nsw-bbw"; + reg = <0x28>; + height = <2>; + width = <16>; + brightness = <8>; + }; + + eeprom@51 { + compatible = "atmel,24c32"; + reg = <0x51>; + pagesize = <32>; + }; + + rtc@68 { + compatible = "dallas,ds1339"; + reg = <0x68>; + }; +}; + +&mmc0 { + cd-gpios = <&portb 18 0>; + vmmc-supply = <®ulator_3_3v>; + vqmmc-supply = <®ulator_3_3v>; +}; + +&uart0 { + status = "okay"; +}; + +&osc1 { + clock-frequency = <19200000>; +}; + +&nand0 { + status = "okay"; +}; + diff --git a/arch/arm/mach-socfpga/Kconfig b/arch/arm/mach-socfpga/Kconfig index 4adb901dd5eb..c1681da181d0 100644 --- a/arch/arm/mach-socfpga/Kconfig +++ b/arch/arm/mach-socfpga/Kconfig @@ -11,6 +11,13 @@ menuconfig ARCH_SOCFPGA select HAVE_ARM_TWD if SMP select MFD_SYSCON select PCI_DOMAINS if PCI + select ARM_ERRATA_754322 + select ARM_ERRATA_764369 if SMP + select ARM_ERRATA_775420 + select PL310_ERRATA_588369 + select PL310_ERRATA_727915 + select PL310_ERRATA_753970 if PL310 + select PL310_ERRATA_769419 if ARCH_SOCFPGA config SOCFPGA_SUSPEND @@ -19,3 +26,8 @@ config SOCFPGA_SUSPEND Select this if you want to enable Suspend-to-RAM on SOCFPGA platforms. endif +config FPGADMA + tristate "FPGA DMA FIFO driver" + depends on DMA_ENGINE + help + Sample FPGA DMA driver, for testing with special FPGA FIFO image diff --git a/arch/arm/mach-socfpga/Makefile b/arch/arm/mach-socfpga/Makefile index 9ec31fad7136..01ad570d7c8e 100644 --- a/arch/arm/mach-socfpga/Makefile +++ b/arch/arm/mach-socfpga/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_SMP) += headsmp.o platsmp.o obj-$(CONFIG_SOCFPGA_SUSPEND) += pm.o self-refresh.o obj-$(CONFIG_EDAC_ALTERA_L2C) += l2_cache.o obj-$(CONFIG_EDAC_ALTERA_OCRAM) += ocram.o +obj-$(CONFIG_FPGADMA) += fpga-dma.o diff --git a/arch/arm/mach-socfpga/core.h b/arch/arm/mach-socfpga/core.h index 65e1817d8afe..45433a467956 100644 --- a/arch/arm/mach-socfpga/core.h +++ b/arch/arm/mach-socfpga/core.h @@ -34,12 +34,15 @@ #define RSTMGR_MPUMODRST_CPU1 0x2 /* CPU1 Reset */ -extern void socfpga_init_clocks(void); -extern void socfpga_sysmgr_init(void); void socfpga_init_l2_ecc(void); void socfpga_init_ocram_ecc(void); void socfpga_init_arria10_l2_ecc(void); void socfpga_init_arria10_ocram_ecc(void); +#define SYSMGR_SILICON_ID1_OFFSET 0x0 +#define SYSMGR_SILICON_ID1_REV_SHIFT 0 +#define SYSMGR_SILICON_ID1_REV_MASK 0x0000FFFF +#define SYSMGR_SILICON_ID1_ID_SHIFT 16 +#define SYSMGR_SILICON_ID1_ID_MASK 0xFFFF0000 extern void __iomem *sys_manager_base_addr; extern void __iomem *rst_manager_base_addr; @@ -54,4 +57,7 @@ extern unsigned long socfpga_cpu1start_addr; #define SOCFPGA_SCU_VIRT_BASE 0xfee00000 +/* Clock manager defines */ +#define SOCFPGA_ENABLE_PLL_REG 0xA0 + #endif diff --git a/arch/arm/mach-socfpga/fpga-dma.c b/arch/arm/mach-socfpga/fpga-dma.c new file mode 100644 index 000000000000..23ed0a0d8e28 --- /dev/null +++ b/arch/arm/mach-socfpga/fpga-dma.c @@ -0,0 +1,689 @@ +/* + * FPGA DMA transfer module + * + * Copyright Altera Corporation (C) 2014. All rights reserved. + * + * 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/cdev.h> +#include <linux/completion.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/pm.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +/****************************************************************************/ + +static unsigned int max_burst_words = 16; +module_param(max_burst_words, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_burst_words, "Size of a burst in words " + "(in this case a word is 64 bits)"); + +static int timeout = 1000; +module_param(timeout, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(timeout, "Transfer Timeout in msec (default: 1000), " + "Pass -1 for infinite timeout"); + +#define ALT_FPGADMA_DATA_WRITE 0x00 +#define ALT_FPGADMA_DATA_READ 0x08 + +#define ALT_FPGADMA_CSR_WR_WTRMK 0x00 +#define ALT_FPGADMA_CSR_RD_WTRMK 0x04 +#define ALT_FPGADMA_CSR_BURST 0x08 +#define ALT_FPGADMA_CSR_FIFO_STATUS 0x0C +#define ALT_FPGADMA_CSR_DATA_WIDTH 0x10 +#define ALT_FPGADMA_CSR_FIFO_DEPTH 0x14 +#define ALT_FPGADMA_CSR_FIFO_CLEAR 0x18 +#define ALT_FPGADMA_CSR_ZERO 0x1C + +#define ALT_FPGADMA_CSR_BURST_TX_SINGLE (1 << 0) +#define ALT_FPGADMA_CSR_BURST_TX_BURST (1 << 1) +#define ALT_FPGADMA_CSR_BURST_RX_SINGLE (1 << 2) +#define ALT_FPGADMA_CSR_BURST_RX_BURST (1 << 3) + +#define ALT_FPGADMA_FIFO_FULL (1 << 25) +#define ALT_FPGADMA_FIFO_EMPTY (1 << 24) +#define ALT_FPGADMA_FIFO_USED_MASK ((1 << 24)-1) + +struct fpga_dma_pdata { + + struct platform_device *pdev; + + struct dentry *root; + + unsigned int data_reg_phy; + void __iomem *data_reg; + void __iomem *csr_reg; + + unsigned int fifo_size_bytes; + unsigned int fifo_depth; + unsigned int data_width; + unsigned int data_width_bytes; + unsigned char *read_buf; + unsigned char *write_buf; + + struct dma_chan *txchan; + struct dma_chan *rxchan; + dma_addr_t tx_dma_addr; + dma_addr_t rx_dma_addr; + dma_cookie_t rx_cookie; + dma_cookie_t tx_cookie; +}; + +static DECLARE_COMPLETION(dma_read_complete); +static DECLARE_COMPLETION(dma_write_complete); + +#define IS_DMA_READ (true) +#define IS_DMA_WRITE (false) + +static int fpga_dma_dma_start_rx(struct platform_device *pdev, + unsigned datalen, unsigned char *databuf, + u32 burst_size); +static int fpga_dma_dma_start_tx(struct platform_device *pdev, + unsigned datalen, unsigned char *databuf, + u32 burst_size); + +/* --------------------------------------------------------------------- */ + +static void dump_csr(struct fpga_dma_pdata *pdata) +{ + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_WR_WTRMK %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_WR_WTRMK)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_RD_WTRMK %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_RD_WTRMK)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_BURST %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_BURST)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_FIFO_STATUS %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_STATUS)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_DATA_WIDTH %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_DATA_WIDTH)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_FIFO_DEPTH %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_DEPTH)); + dev_info(&pdata->pdev->dev, "ALT_FPGADMA_CSR_ZERO %08x\n", + readl(pdata->csr_reg + ALT_FPGADMA_CSR_ZERO)); +} + +/* --------------------------------------------------------------------- */ + +static void recalc_burst_and_words(struct fpga_dma_pdata *pdata, + int *burst_size, int *num_words) +{ + /* adjust size and maxburst so that total bytes transferred + is a multiple of burst length and width */ + if (*num_words < max_burst_words) { + /* we have only a few words left, make it our burst size */ + *burst_size = *num_words; + } else { + /* here we may not transfer all words to FIFO, but next + call will pick them up... */ + *num_words = max_burst_words * (*num_words / max_burst_words); + *burst_size = max_burst_words; + } +} + +static int word_to_bytes(struct fpga_dma_pdata *pdata, int num_bytes) +{ + return (num_bytes + pdata->data_width_bytes - 1) + / pdata->data_width_bytes; +} + +static ssize_t dbgfs_write_dma(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + int ret = 0; + int bytes_to_transfer; + int num_words; + u32 burst_size; + int pad_index; + + *ppos = 0; + + /* get user data into kernel buffer */ + bytes_to_transfer = simple_write_to_buffer(pdata->write_buf, + pdata->fifo_size_bytes, ppos, + user_buf, count); + pad_index = bytes_to_transfer; + + num_words = word_to_bytes(pdata, bytes_to_transfer); + recalc_burst_and_words(pdata, &burst_size, &num_words); + /* we sometimes send more than asked for, padded with zeros */ + bytes_to_transfer = num_words * pdata->data_width_bytes; + for (; pad_index < bytes_to_transfer; pad_index++) + pdata->write_buf[pad_index] = 0; + + ret = fpga_dma_dma_start_tx(pdata->pdev, + bytes_to_transfer, pdata->write_buf, + burst_size); + if (ret) { + dev_err(&pdata->pdev->dev, "Error starting TX DMA %d\n", ret); + return ret; + } + + if (!wait_for_completion_timeout(&dma_write_complete, + msecs_to_jiffies(timeout))) { + dev_err(&pdata->pdev->dev, "Timeout waiting for TX DMA!\n"); + dev_err(&pdata->pdev->dev, + "count %d burst_size %d num_words %d bytes_to_transfer %d\n", + count, burst_size, num_words, bytes_to_transfer); + dmaengine_terminate_all(pdata->txchan); + return -ETIMEDOUT; + } + + return bytes_to_transfer; +} + +static ssize_t dbgfs_read_dma(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + int ret; + int num_words; + int num_bytes; + u32 burst_size; + + num_words = readl(pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_STATUS); + num_words &= ALT_FPGADMA_FIFO_USED_MASK; + + num_bytes = num_words * pdata->data_width_bytes; + if (num_bytes > count) { + dev_dbg(&pdata->pdev->dev, + "dbgfs_read_dma num_bytes %d > count %d\n", + num_bytes, count); + num_bytes = count; + num_words = num_bytes / (pdata->data_width_bytes); + } + if (num_bytes > pdata->fifo_size_bytes) { + dev_dbg(&pdata->pdev->dev, + "dbgfs_read_dma num_bytes %d > pdata->fifo_size_bytes %d\n", + num_bytes, pdata->fifo_size_bytes); + num_bytes = pdata->fifo_size_bytes; + num_words = num_bytes / (pdata->data_width_bytes); + } + + recalc_burst_and_words(pdata, &burst_size, &num_words); + num_bytes = num_words * pdata->data_width_bytes; + + if (num_bytes > 0) { + ret = fpga_dma_dma_start_rx(pdata->pdev, num_bytes, + pdata->read_buf, burst_size); + if (ret) { + dev_err(&pdata->pdev->dev, + "Error starting RX DMA %d\n", ret); + return ret; + } + + if (!wait_for_completion_timeout(&dma_read_complete, + msecs_to_jiffies(timeout))) { + dev_err(&pdata->pdev->dev, + "Timeout waiting for RX DMA!\n"); + dmaengine_terminate_all(pdata->rxchan); + return -ETIMEDOUT; + } + *ppos = 0; + } + return simple_read_from_buffer(user_buf, count, ppos, + pdata->read_buf, num_bytes); +} + +static const struct file_operations dbgfs_dma_fops = { + .write = dbgfs_write_dma, + .read = dbgfs_read_dma, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t dbgfs_read_csr(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + dump_csr(pdata); + return 0; +} + +static const struct file_operations dbgfs_csr_fops = { + .read = dbgfs_read_csr, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t dbgfs_write_clear(struct file *file, + const char __user *user_buf, size_t count, + loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + writel(1, pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_CLEAR); + return count; +} + +static const struct file_operations dbgfs_clear_fops = { + .write = dbgfs_write_clear, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t dbgfs_write_wrwtrmk(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + char buf[32]; + unsigned long val; + int ret; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, user_buf, min(count, (sizeof(buf) - 1)))) + return -EFAULT; + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + writel(val, pdata->csr_reg + ALT_FPGADMA_CSR_WR_WTRMK); + return count; +} + +static const struct file_operations dbgfs_wrwtrmk_fops = { + .write = dbgfs_write_wrwtrmk, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static ssize_t dbgfs_write_rdwtrmk(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_dma_pdata *pdata = file->private_data; + char buf[32]; + int ret; + unsigned long val; + + memset(buf, 0, sizeof(buf)); + + if (copy_from_user(buf, user_buf, min(count, (sizeof(buf) - 1)))) + return -EFAULT; + + ret = kstrtoul(buf, 16, &val); + if (ret) + return ret; + + writel(val, pdata->csr_reg + ALT_FPGADMA_CSR_RD_WTRMK); + return count; +} + +static const struct file_operations dbgfs_rdwtrmk_fops = { + .write = dbgfs_write_rdwtrmk, + .open = simple_open, + .llseek = no_llseek, +}; + +/* --------------------------------------------------------------------- */ + +static int fpga_dma_register_dbgfs(struct fpga_dma_pdata *pdata) +{ + struct dentry *d; + + d = debugfs_create_dir("fpga_dma", NULL); + if (IS_ERR(d)) + return PTR_ERR(d); + if (!d) { + dev_err(&pdata->pdev->dev, "Failed to initialize debugfs\n"); + return -ENOMEM; + } + + pdata->root = d; + + debugfs_create_file("dma", S_IWUSR | S_IRUGO, pdata->root, pdata, + &dbgfs_dma_fops); + + debugfs_create_file("csr", S_IRUGO, pdata->root, pdata, + &dbgfs_csr_fops); + + debugfs_create_file("clear", S_IWUSR, pdata->root, pdata, + &dbgfs_clear_fops); + + debugfs_create_file("wrwtrmk", S_IWUSR, pdata->root, pdata, + &dbgfs_wrwtrmk_fops); + + debugfs_create_file("rdwtrmk", S_IWUSR, pdata->root, pdata, + &dbgfs_rdwtrmk_fops); + + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void fpga_dma_dma_rx_done(void *arg) +{ + complete(&dma_read_complete); +} + +static void fpga_dma_dma_tx_done(void *arg) +{ + complete(&dma_write_complete); +} + +static void fpga_dma_dma_cleanup(struct platform_device *pdev, + unsigned datalen, bool do_read) +{ + struct fpga_dma_pdata *pdata = platform_get_drvdata(pdev); + if (do_read) + dma_unmap_single(&pdev->dev, pdata->rx_dma_addr, + datalen, DMA_FROM_DEVICE); + else + dma_unmap_single(&pdev->dev, pdata->tx_dma_addr, + datalen, DMA_TO_DEVICE); +} + +static int fpga_dma_dma_start_rx(struct platform_device *pdev, + unsigned datalen, unsigned char *databuf, + u32 burst_size) +{ + struct fpga_dma_pdata *pdata = platform_get_drvdata(pdev); + struct dma_chan *dmachan; + struct dma_slave_config dmaconf; + struct dma_async_tx_descriptor *dmadesc = NULL; + + int num_words; + + num_words = word_to_bytes(pdata, datalen); + + dmachan = pdata->rxchan; + memset(&dmaconf, 0, sizeof(dmaconf)); + dmaconf.direction = DMA_DEV_TO_MEM; + dmaconf.src_addr = pdata->data_reg_phy + ALT_FPGADMA_DATA_READ; + dmaconf.src_addr_width = 8; + dmaconf.src_maxburst = burst_size; + + pdata->rx_dma_addr = dma_map_single(&pdev->dev, + databuf, datalen, DMA_FROM_DEVICE); + if (dma_mapping_error(&pdev->dev, pdata->rx_dma_addr)) { + dev_err(&pdev->dev, "dma_map_single for RX failed\n"); + return -EINVAL; + } + + /* set up slave config */ + dmaengine_slave_config(dmachan, &dmaconf); + + /* get dmadesc */ + dmadesc = dmaengine_prep_slave_single(dmachan, + pdata->rx_dma_addr, + datalen, + dmaconf.direction, + DMA_PREP_INTERRUPT); + if (!dmadesc) { + fpga_dma_dma_cleanup(pdev, datalen, IS_DMA_READ); + return -ENOMEM; + } + dmadesc->callback = fpga_dma_dma_rx_done; + dmadesc->callback_param = pdata; + + /* start DMA */ + pdata->rx_cookie = dmaengine_submit(dmadesc); + if (dma_submit_error(pdata->rx_cookie)) + dev_err(&pdev->dev, "rx_cookie error on dmaengine_submit\n"); + dma_async_issue_pending(dmachan); + + return 0; +} + +static int fpga_dma_dma_start_tx(struct platform_device *pdev, + unsigned datalen, unsigned char *databuf, + u32 burst_size) +{ + struct fpga_dma_pdata *pdata = platform_get_drvdata(pdev); + struct dma_chan *dmachan; + struct dma_slave_config dmaconf; + struct dma_async_tx_descriptor *dmadesc = NULL; + + int num_words; + + num_words = word_to_bytes(pdata, datalen); + + dmachan = pdata->txchan; + memset(&dmaconf, 0, sizeof(dmaconf)); + dmaconf.direction = DMA_MEM_TO_DEV; + dmaconf.dst_addr = pdata->data_reg_phy + ALT_FPGADMA_DATA_WRITE; + dmaconf.dst_addr_width = 8; + dmaconf.dst_maxburst = burst_size; + pdata->tx_dma_addr = dma_map_single(&pdev->dev, + databuf, datalen, DMA_TO_DEVICE); + if (dma_mapping_error(&pdev->dev, pdata->tx_dma_addr)) { + dev_err(&pdev->dev, "dma_map_single for TX failed\n"); + return -EINVAL; + } + + /* set up slave config */ + dmaengine_slave_config(dmachan, &dmaconf); + + /* get dmadesc */ + dmadesc = dmaengine_prep_slave_single(dmachan, + pdata->tx_dma_addr, + datalen, + dmaconf.direction, + DMA_PREP_INTERRUPT); + if (!dmadesc) { + fpga_dma_dma_cleanup(pdev, datalen, IS_DMA_WRITE); + return -ENOMEM; + } + dmadesc->callback = fpga_dma_dma_tx_done; + dmadesc->callback_param = pdata; + + /* start DMA */ + pdata->tx_cookie = dmaengine_submit(dmadesc); + if (dma_submit_error(pdata->tx_cookie)) + dev_err(&pdev->dev, "tx_cookie error on dmaengine_submit\n"); + dma_async_issue_pending(dmachan); + + return 0; +} + +static void fpga_dma_dma_shutdown(struct fpga_dma_pdata *pdata) +{ + if (pdata->txchan) { + dmaengine_terminate_all(pdata->txchan); + dma_release_channel(pdata->txchan); + } + if (pdata->rxchan) { + dmaengine_terminate_all(pdata->rxchan); + dma_release_channel(pdata->rxchan); + } + pdata->rxchan = pdata->txchan = NULL; +} + +static int fpga_dma_dma_init(struct fpga_dma_pdata *pdata) +{ + struct platform_device *pdev = pdata->pdev; + + pdata->txchan = dma_request_slave_channel(&pdev->dev, "tx"); + if (pdata->txchan) + dev_dbg(&pdev->dev, "TX channel %s %d selected\n", + dma_chan_name(pdata->txchan), pdata->txchan->chan_id); + else + dev_err(&pdev->dev, "could not get TX dma channel\n"); + + pdata->rxchan = dma_request_slave_channel(&pdev->dev, "rx"); + if (pdata->rxchan) + dev_dbg(&pdev->dev, "RX channel %s %d selected\n", + dma_chan_name(pdata->rxchan), pdata->rxchan->chan_id); + else + dev_err(&pdev->dev, "could not get RX dma channel\n"); + + if (!pdata->rxchan && !pdata->txchan) + /* both channels not there, maybe it's + bcs dma isn't loaded... */ + return -EPROBE_DEFER; + + if (!pdata->rxchan || !pdata->txchan) + return -ENOMEM; + + return 0; +} + +/* --------------------------------------------------------------------- */ + +static void __iomem *request_and_map(struct platform_device *pdev, + const struct resource *res) +{ + void __iomem *ptr; + + if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + pdev->name)) { + dev_err(&pdev->dev, "unable to request %s\n", res->name); + return NULL; + } + + ptr = devm_ioremap_nocache(&pdev->dev, res->start, resource_size(res)); + if (!ptr) + dev_err(&pdev->dev, "ioremap_nocache of %s failed!", res->name); + + return ptr; +} + +static int fpga_dma_remove(struct platform_device *pdev) +{ + struct fpga_dma_pdata *pdata = platform_get_drvdata(pdev); + dev_dbg(&pdev->dev, "fpga_dma_remove\n"); + debugfs_remove_recursive(pdata->root); + fpga_dma_dma_shutdown(pdata); + return 0; +} + +static int fpga_dma_probe(struct platform_device *pdev) +{ + struct resource *csr_reg, *data_reg; + struct fpga_dma_pdata *pdata; + int ret; + + pdata = devm_kzalloc(&pdev->dev, sizeof(struct fpga_dma_pdata), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + csr_reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr"); + data_reg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "data"); + if (!csr_reg || !data_reg) { + dev_err(&pdev->dev, "registers not completely defined\n"); + return -EINVAL; + } + + pdata->csr_reg = request_and_map(pdev, csr_reg); + if (!pdata->csr_reg) + return -ENOMEM; + + pdata->data_reg = request_and_map(pdev, data_reg); + if (!pdata->data_reg) + return -ENOMEM; + pdata->data_reg_phy = data_reg->start; + + /* read HW and calculate fifo size in bytes */ + pdata->fifo_depth = readl(pdata->csr_reg + ALT_FPGADMA_CSR_FIFO_DEPTH); + pdata->data_width = readl(pdata->csr_reg + ALT_FPGADMA_CSR_DATA_WIDTH); + /* 64-bit bus to FIFO */ + pdata->data_width_bytes = pdata->data_width / sizeof(u64); + pdata->fifo_size_bytes = pdata->fifo_depth * pdata->data_width_bytes; + + pdata->read_buf = devm_kzalloc(&pdev->dev, pdata->fifo_size_bytes, + GFP_KERNEL); + if (!pdata->read_buf) + return -ENOMEM; + + pdata->write_buf = devm_kzalloc(&pdev->dev, pdata->fifo_size_bytes, + GFP_KERNEL); + if (!pdata->write_buf) + return -ENOMEM; + + ret = fpga_dma_register_dbgfs(pdata); + if (ret) + return ret; + + pdata->pdev = pdev; + platform_set_drvdata(pdev, pdata); + + ret = fpga_dma_dma_init(pdata); + if (ret) { + fpga_dma_remove(pdev); + return ret; + } + + /* OK almost ready, set up the watermarks */ + /* we may need to tweak this for single/burst, etc */ + writel(pdata->fifo_depth - max_burst_words, + pdata->csr_reg + ALT_FPGADMA_CSR_WR_WTRMK); + /* we use read watermark of 0 so that rx_burst line + is always asserted, i.e. no single-only requests */ + writel(0, pdata->csr_reg + ALT_FPGADMA_CSR_RD_WTRMK); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id fpga_dma_of_match[] = { + {.compatible = "altr,fpga-dma",}, + {}, +}; + +MODULE_DEVICE_TABLE(of, fpga_dma_of_match); +#endif + +static struct platform_driver fpga_dma_driver = { + .probe = fpga_dma_probe, + .remove = fpga_dma_remove, + .driver = { + .name = "fpga_dma", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(fpga_dma_of_match), + }, +}; + +static int __init fpga_dma_init(void) +{ + return platform_driver_probe(&fpga_dma_driver, fpga_dma_probe); +} + +static void __exit fpga_dma_exit(void) +{ + platform_driver_unregister(&fpga_dma_driver); +} + +late_initcall(fpga_dma_init); +module_exit(fpga_dma_exit); + +MODULE_AUTHOR("Graham Moore (Altera)"); +MODULE_DESCRIPTION("Altera FPGA DMA Example Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-socfpga/socfpga.c b/arch/arm/mach-socfpga/socfpga.c index dde14f7bf2c3..5bb877e04a54 100644 --- a/arch/arm/mach-socfpga/socfpga.c +++ b/arch/arm/mach-socfpga/socfpga.c @@ -31,6 +31,7 @@ void __iomem *sys_manager_base_addr; void __iomem *rst_manager_base_addr; void __iomem *sdr_ctl_base_addr; unsigned long socfpga_cpu1start_addr; +void __iomem *clkmgr_base_addr; void __init socfpga_sysmgr_init(void) { @@ -51,6 +52,10 @@ void __init socfpga_sysmgr_init(void) np = of_find_compatible_node(NULL, NULL, "altr,rst-mgr"); rst_manager_base_addr = of_iomap(np, 0); + np = of_find_compatible_node(NULL, NULL, "altr,clk-mgr"); + clkmgr_base_addr = of_iomap(np, 0); + WARN_ON(!clkmgr_base_addr); + np = of_find_compatible_node(NULL, NULL, "altr,sdr-ctl"); sdr_ctl_base_addr = of_iomap(np, 0); } @@ -80,6 +85,9 @@ static void socfpga_cyclone5_restart(enum reboot_mode mode, const char *cmd) { u32 temp; + /* Turn on all periph PLL clocks */ + writel(0xffff, clkmgr_base_addr + SOCFPGA_ENABLE_PLL_REG); + temp = readl(rst_manager_base_addr + SOCFPGA_RSTMGR_CTRL); if (mode == REBOOT_HARD) diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index 21a715ad8222..65404985f84e 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -226,6 +226,11 @@ config ARCH_STRATIX10 help This enables support for Altera's Stratix 10 SoCFPGA Family. +config ARCH_STRATIX10SWVP + bool "Altera Stratix10 Software Virtual Platform" + help + This enables support for Altera Stratix10 Software Virtual Platform + config ARCH_TEGRA bool "NVIDIA Tegra SoC Family" select ARCH_HAS_RESET_CONTROLLER diff --git a/arch/arm64/boot/dts/altera/Makefile b/arch/arm64/boot/dts/altera/Makefile index 68ba0882a8bb..dae9ba404d18 100644 --- a/arch/arm64/boot/dts/altera/Makefile +++ b/arch/arm64/boot/dts/altera/Makefile @@ -1 +1,2 @@ -dtb-$(CONFIG_ARCH_STRATIX10) += socfpga_stratix10_socdk.dtb +dtb-$(CONFIG_ARCH_STRATIX10) += socfpga_stratix10_socdk.dtb socfpga_stratix10_fpga_update.dtb +dtb-$(CONFIG_ARCH_STRATIX10SWVP) += stratix10_swvp.dtb diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi b/arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi index 6c8bd13d64b8..1072baf452e0 100644 --- a/arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi +++ b/arch/arm64/boot/dts/altera/socfpga_stratix10.dtsi @@ -24,6 +24,19 @@ #address-cells = <2>; #size-cells = <2>; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + service_reserved: svcbuffer@0 { + compatible = "shared-dma-pool"; + reg = <0x0 0x0 0x0 0x1000000>; + alignment = <0x1000>; + no-map; + }; + }; + cpus { #address-cells = <1>; #size-cells = <0>; @@ -59,10 +72,10 @@ pmu { compatible = "arm,armv8-pmuv3"; - interrupts = <0 120 8>, - <0 121 8>, - <0 122 8>, - <0 123 8>; + interrupts = <0 170 4>, + <0 171 4>, + <0 172 4>, + <0 173 4>; interrupt-affinity = <&cpu0>, <&cpu1>, <&cpu2>, @@ -93,6 +106,14 @@ interrupt-parent = <&intc>; ranges = <0 0 0 0xffffffff>; + base_fpga_region { + #address-cells = <0x1>; + #size-cells = <0x1>; + + compatible = "fpga-region"; + fpga-mgr = <&fpga_mgr>; + }; + clkmgr: clock-controller@ffd10000 { compatible = "intel,stratix10-clkmgr"; reg = <0xffd10000 0x1000>; @@ -119,16 +140,27 @@ #clock-cells = <0>; compatible = "fixed-clock"; }; + + qspi_clk: qspi-clk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <200000000>; + }; + }; + + fpga_mgr: fpga-mgr@0 { + compatible = "intel,stratix10-soc-fpga-mgr"; }; gmac0: ethernet@ff800000 { compatible = "altr,socfpga-stmmac", "snps,dwmac-3.74a", "snps,dwmac"; + altr,sysmgr-syscon = <&sysmgr 0x44 0>; reg = <0xff800000 0x2000>; interrupts = <0 90 4>; interrupt-names = "macirq"; mac-address = [00 00 00 00 00 00]; - resets = <&rst EMAC0_RESET>; - reset-names = "stmmaceth"; + resets = <&rst EMAC0_RESET>, <&rst EMAC0_OCP_RESET>; + reset-names = "stmmaceth", "stmmaceth-ocp"; clocks = <&clkmgr STRATIX10_EMAC0_CLK>; clock-names = "stmmaceth"; tx-fifo-depth = <16384>; @@ -139,12 +171,13 @@ gmac1: ethernet@ff802000 { compatible = "altr,socfpga-stmmac", "snps,dwmac-3.74a", "snps,dwmac"; + altr,sysmgr-syscon = <&sysmgr 0x48 0>; reg = <0xff802000 0x2000>; interrupts = <0 91 4>; interrupt-names = "macirq"; mac-address = [00 00 00 00 00 00]; - resets = <&rst EMAC1_RESET>; - reset-names = "stmmaceth"; + resets = <&rst EMAC1_RESET>, <&rst EMAC1_OCP_RESET>; + reset-names = "stmmaceth", "stmmaceth-ocp"; clocks = <&clkmgr STRATIX10_EMAC1_CLK>; clock-names = "stmmaceth"; tx-fifo-depth = <16384>; @@ -155,12 +188,13 @@ gmac2: ethernet@ff804000 { compatible = "altr,socfpga-stmmac", "snps,dwmac-3.74a", "snps,dwmac"; + altr,sysmgr-syscon = <&sysmgr 0x4c 0>; reg = <0xff804000 0x2000>; interrupts = <0 92 4>; interrupt-names = "macirq"; mac-address = [00 00 00 00 00 00]; - resets = <&rst EMAC2_RESET>; - reset-names = "stmmaceth"; + resets = <&rst EMAC2_RESET>, <&rst EMAC2_OCP_RESET>; + reset-names = "stmmaceth", "stmmaceth-ocp"; clocks = <&clkmgr STRATIX10_EMAC2_CLK>; clock-names = "stmmaceth"; tx-fifo-depth = <16384>; @@ -337,6 +371,8 @@ sysmgr: sysmgr@ffd12000 { compatible = "altr,sys-mgr", "syscon"; reg = <0xffd12000 0x228>; + interrupts = <0x0 0x10 0x4>; + cpu1-start-addr = <0xffd06230>; }; /* Local timer */ @@ -416,6 +452,7 @@ phy-names = "usb2-phy"; resets = <&rst USB0_RESET>, <&rst USB0_OCP_RESET>; reset-names = "dwc2", "dwc2-ecc"; + clocks = <&clkmgr STRATIX10_USB_CLK>; status = "disabled"; }; @@ -427,6 +464,7 @@ phy-names = "usb2-phy"; resets = <&rst USB1_RESET>, <&rst USB1_OCP_RESET>; reset-names = "dwc2", "dwc2-ecc"; + clocks = <&clkmgr STRATIX10_USB_CLK>; status = "disabled"; }; @@ -435,6 +473,7 @@ reg = <0xffd00200 0x100>; interrupts = <0 117 4>; resets = <&rst WATCHDOG0_RESET>; + clocks = <&clkmgr STRATIX10_L4_SYS_FREE_CLK>; status = "disabled"; }; @@ -443,6 +482,7 @@ reg = <0xffd00300 0x100>; interrupts = <0 118 4>; resets = <&rst WATCHDOG1_RESET>; + clocks = <&clkmgr STRATIX10_L4_SYS_FREE_CLK>; status = "disabled"; }; @@ -451,6 +491,7 @@ reg = <0xffd00400 0x100>; interrupts = <0 125 4>; resets = <&rst WATCHDOG2_RESET>; + clocks = <&clkmgr STRATIX10_L4_SYS_FREE_CLK>; status = "disabled"; }; @@ -459,6 +500,7 @@ reg = <0xffd00500 0x100>; interrupts = <0 126 4>; resets = <&rst WATCHDOG3_RESET>; + clocks = <&clkmgr STRATIX10_L4_SYS_FREE_CLK>; status = "disabled"; }; @@ -473,5 +515,31 @@ interrupts = <16 4>, <48 4>; }; }; + + qspi: spi@ff8d2000 { + compatible = "cdns,qspi-nor"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0xff8d2000 0x100>, + <0xff900000 0x100000>; + interrupts = <0 3 4>; + cdns,fifo-depth = <128>; + cdns,fifo-width = <4>; + cdns,trigger-address = <0x00000000>; + clocks = <&qspi_clk>; + + status = "disabled"; + }; + + firmware { + svc { + compatible = "intel,stratix10-svc"; + method = "smc"; + memory-region = <&service_reserved>; + rsu { + compatible = "intel,stratix10-rsu"; + }; + }; + }; }; }; diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10_fpga_update.dts b/arch/arm64/boot/dts/altera/socfpga_stratix10_fpga_update.dts new file mode 100644 index 000000000000..c7811cc92091 --- /dev/null +++ b/arch/arm64/boot/dts/altera/socfpga_stratix10_fpga_update.dts @@ -0,0 +1,17 @@ +/dts-v1/; +/plugin/; +/ { + fragment@0 { + target-path = "/soc/base_fpga_region"; + #address-cells = <1>; + #size-cells = <1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <1>; + + firmware-name = "soc_s10_fpga_config.rbf"; + config-complete-timeout-us = <2000000>; + }; + }; +}; + diff --git a/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dts b/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dts index fb1b9ddd9f51..444402fae6d3 100644 --- a/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dts +++ b/arch/arm64/boot/dts/altera/socfpga_stratix10_socdk.dts @@ -64,6 +64,22 @@ clock-frequency = <25000000>; }; }; + + gpio_fpga: gpio@f9001080 { + #address-cells = <1>; + #size-cells = <0>; + compatible = "snps,dw-apb-gpio"; + reg = <0xf9001080 0x4>; + status = "disabled"; + + portfpga: gpio-controller@0 { + compatible = "snps,dw-apb-gpio-port"; + gpio-controller; + #gpio-cells = <2>; + snps,nr-gpios = <8>; + reg = <0>; + }; + }; }; }; @@ -77,6 +93,7 @@ phy-handle = <&phy0>; max-frame-size = <9000>; + snps,multicast-filter-bins = <256>; mdio0 { #address-cells = <1>; @@ -124,6 +141,8 @@ &i2c1 { status = "okay"; clock-frequency = <100000>; + i2c-sda-falling-time-ns = <890>; /* hcnt */ + i2c-sdl-falling-time-ns = <890>; /* lcnt */ adc@14 { compatible = "lltc,ltc2497"; @@ -147,3 +166,39 @@ reg = <0x68>; }; }; + +&qspi { + status = "okay"; + flash@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "n25q00a"; + reg = <0>; + spi-max-frequency = <50000000>; + + m25p,fast-read; + cdns,page-size = <256>; + cdns,block-size = <16>; + cdns,read-delay = <1>; + cdns,tshsl-ns = <50>; + cdns,tsd2d-ns = <50>; + cdns,tchsh-ns = <4>; + cdns,tslch-ns = <4>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + qspi_boot: partition@0 { + label = "Boot and fpga data"; + reg = <0x00910000 0x036F0000>; + }; + + qspi_rootfs: partition@4000000 { + label = "Root Filesystem - JFFS2"; + reg = <0x4000000 0x4000000>; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/altera/stratix10_swvp.dts b/arch/arm64/boot/dts/altera/stratix10_swvp.dts new file mode 100644 index 000000000000..69052cf056f7 --- /dev/null +++ b/arch/arm64/boot/dts/altera/stratix10_swvp.dts @@ -0,0 +1,763 @@ +/* + * Copyright (C), 2017 Intel Corporation. All rights reserved. + * Copyright (C), 2015 Altera Corporation. All rights reserved. + * + * 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 <dt-bindings/reset/altr,rst-mgr-s10.h> + +/dts-v1/; + +/ { + #address-cells = <0x1>; + #size-cells = <0x1>; + model = "Altera SOCFPGA Stratix 10 SWVP"; + compatible = "arm,foundation-aarch64", "arm,vexpress"; + interrupt-parent = <&gic>; + + aliases { + serial0 = "/soc/serial0@ffc02000"; + serial1 = "/soc/serial1@ffc02100"; + + timer0 = "/soc/timer0@ffc03000"; + timer1 = "/soc/timer1@ffc03100"; + timer2 = "/soc/timer2@ffd00000"; + timer3 = "/soc/timer3@ffd00100"; + + ethernet0 = "/soc/ethernet@ff800000"; + ethernet1 = "/soc/ethernet@ff802000"; + ethernet2 = "/soc/ethernet@ff804000"; + }; + + memory { + device_type = "memory"; + reg = <0x0 0x80000000>; + }; + + chosen { + bootargs = "rdinit=/sbin/init ip=dhcp mem=2048M"; + stdout-path = "serial1:115200n8"; + linux,initrd-start = <0x10000000>; + linux,initrd-end = <0x125c8324>; + }; + + cpus { + #address-cells = <0x2>; + #size-cells = <0x0>; + + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a53", "arm,armv8"; + reg = <0x0 0x0>; + enable-method = "spin-table"; + cpu-release-addr = <0x0 0x0000fff8>; + }; + + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a53", "arm,armv8"; + reg = <0x0 0x1>; + enable-method = "spin-table"; + cpu-release-addr = <0x0 0x0000fff8>; + }; + + cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a53", "arm,armv8"; + reg = <0x0 0x2>; + enable-method = "spin-table"; + cpu-release-addr = <0x0 0x0000fff8>; + }; + + cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a53", "arm,armv8"; + reg = <0x0 0x3>; + enable-method = "spin-table"; + cpu-release-addr = <0x0 0x0000fff8>; + }; + }; + + gic: interrupt-controller@fffc1000 { + compatible = "arm,gic-400", "arm,cortex-a15-gic"; + #interrupt-cells = <3>; + #address-cells = <0>; + interrupt-controller; + reg = <0xfffc1000 0x1000>, + <0xfffc2000 0x1000>, + <0xfffc4000 0x2000>, + <0xfffc6000 0x2000>; + }; + + soc { + #address-cells = <0x1>; + #size-cells = <0x1>; + compatible = "simple-bus"; + device_type = "soc"; + interrupt-parent = <0x2>; + ranges; + + usbphy0: usbphy@0 { + #phy-cells = <0>; + compatible = "usb-nop-xceiv"; + status = "okay"; + }; + + usb0: usb@ffb00000 { + compatible = "snps,dwc2"; + reg = <0xffb00000 0x40000>; + interrupts = <0x0 0x5d 0x4>; + clocks = <&usb_clk>; + clock-names = "otg"; + resets = <&rst USB0_RESET>; + reset-names = "dwc2"; + dr_mode = "host"; + status = "okay"; + }; + + usb1: usb@ffb40000 { + compatible = "snps,dwc2"; + reg = <0xffb40000 0x40000>; + interrupts = <0x0 0x5e 0x4>; + clocks = <&usb_clk>; + clock-names = "otg"; + resets = <&rst USB0_RESET>; + reset-names = "dwc2"; + dr_mode = "host"; + status = "okay"; + }; + + spi0: spi@ffda4000 { + compatible = "snps,dw-apb-ssi"; + #address-cells = <0x1>; + #size-cells = <0x0>; + reg = <0xffda4000 0x1000>; + interrupts = <0x0 0x65 4>; + num-chipselect = <4>; + bus-num = <0x0>; + status = "disabled"; + }; + + spi1: spi@ffda5000 { + compatible = "snps,dw-apb-ssi"; + #address-cells = <0x1>; + #size-cells = <0x0>; + reg = <0xffda5000 0x1000>; + interrupts = <0x0 0x66 0x4>; + num-chipselect = <0x4>; + bus-num = <0x0>; + status = "disabled"; + }; + + clkmgr@ffd10000 { + compatible = "altr,clk-mgr"; + reg = <0xffd10000 0x1000>; + interrupts = <0 0x77 0x4>; + + clocks { + #address-cells = <1>; + #size-cells = <0>; + + cb_intosc_hs_div2_clk: cb_intosc_hs_div2_clk { + #clock-cells = <0>; + compatible = "fixed-clock"; + }; + + cb_intosc_ls_clk: cb_intosc_ls_clk { + #clock-cells = <0>; + compatible = "fixed-clock"; + }; + + f2s_free_clk: f2s_free_clk { + #clock-cells = <0>; + compatible = "fixed-clock"; + }; + + osc1: osc1 { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <25000000>; + }; + + main_pll: main_pll { + #address-cells = <1>; + #size-cells = <0>; + #clock-cells = <0>; + compatible = "altr,socfpga-a10-pll-clock"; + clocks = <&osc1>, <&cb_intosc_ls_clk>, + <&f2s_free_clk>; + reg = <0x40>; + + main_mpu_base_clk: main_mpu_base_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + div-reg = <0x140 0 11>; + }; + + main_noc_base_clk: main_noc_base_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + div-reg = <0x144 0 11>; + }; + + main_emaca_clk: main_emaca_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x68>; + }; + + main_emacb_clk: main_emacb_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x6c>; + }; + + main_emac_ptp_clk: main_emac_ptp_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x70>; + }; + + main_gpio_db_clk: main_gpio_db_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x74>; + }; + + main_sdmmc_clk: main_sdmmc_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x78>; + }; + + main_s2f_usr0_clk: main_s2f_usr0_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x7c>; + }; + + main_s2f_usr1_clk: main_s2f_usr1_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x80>; + }; + + main_hmc_pll_ref_clk: main_hmc_pll_ref_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x84>; + }; + + main_periph_ref_clk: main_periph_ref_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_pll>; + reg = <0x9c>; + }; + }; + + periph_pll: periph_pll { + #address-cells = <1>; + #size-cells = <0>; + #clock-cells = <0>; + compatible = "altr,socfpga-a10-pll-clock"; + clocks = <&osc1>, <&cb_intosc_ls_clk>, + <&f2s_free_clk>, <&main_periph_ref_clk>; + reg = <0xc0>; + + peri_mpu_base_clk: peri_mpu_base_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + div-reg = <0x140 16 11>; + }; + + peri_noc_base_clk: peri_noc_base_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + div-reg = <0x144 16 11>; + }; + + peri_emaca_clk: peri_emaca_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + reg = <0xe8>; + }; + + peri_emacb_clk: peri_emacb_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + reg = <0xec>; + }; + + peri_emac_ptp_clk: peri_emac_ptp_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + reg = <0xf0>; + }; + + peri_gpio_db_clk: peri_gpio_db_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + reg = <0xf4>; + }; + + peri_sdmmc_clk: peri_sdmmc_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + reg = <0xf8>; + }; + + peri_s2f_usr0_clk: peri_s2f_usr0_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + reg = <0xfc>; + }; + + peri_s2f_usr1_clk: peri_s2f_usr1_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + reg = <0x100>; + }; + + peri_hmc_pll_ref_clk: peri_hmc_pll_ref_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&periph_pll>; + reg = <0x104>; + }; + }; + + mpu_free_clk: mpu_free_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_mpu_base_clk>, <&peri_mpu_base_clk>, + <&osc1>, <&cb_intosc_hs_div2_clk>, + <&f2s_free_clk>; + reg = <0x60>; + }; + + noc_free_clk: noc_free_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_noc_base_clk>, <&peri_noc_base_clk>, + <&osc1>, <&cb_intosc_hs_div2_clk>, + <&f2s_free_clk>; + reg = <0x64>; + }; + + s2f_user1_free_clk: s2f_user1_free_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_s2f_usr1_clk>, <&peri_s2f_usr1_clk>, + <&osc1>, <&cb_intosc_hs_div2_clk>, + <&f2s_free_clk>; + reg = <0x104>; + }; + + sdmmc_free_clk: sdmmc_free_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&main_sdmmc_clk>, <&peri_sdmmc_clk>, + <&osc1>, <&cb_intosc_hs_div2_clk>, + <&f2s_free_clk>; + fixed-divider = <4>; + reg = <0xf8>; + }; + + l4_sys_free_clk: l4_sys_free_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-perip-clk"; + clocks = <&noc_free_clk>; + fixed-divider = <4>; + }; + + l4_main_clk: l4_main_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&noc_free_clk>; + div-reg = <0xa8 0 2>; + clk-gate = <0x48 1>; + }; + + l4_mp_clk: l4_mp_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&noc_free_clk>; + div-reg = <0xa8 8 2>; + clk-gate = <0x48 2>; + }; + + l4_sp_clk: l4_sp_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&noc_free_clk>; + div-reg = <0xa8 16 2>; + clk-gate = <0x48 3>; + }; + + mpu_periph_clk: mpu_periph_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&mpu_free_clk>; + fixed-divider = <4>; + clk-gate = <0x48 0>; + }; + + sdmmc_clk: sdmmc_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&sdmmc_free_clk>; + clk-gate = <0xc8 5>; + }; + + qspi_clk: qspi_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&l4_main_clk>; + clk-gate = <0xc8 11>; + }; + + nand_clk: nand_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&l4_mp_clk>; + clk-gate = <0xc8 10>; + }; + + spi_m_clk: spi_m_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&l4_main_clk>; + clk-gate = <0xc8 9>; + }; + + usb_clk: usb_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&l4_mp_clk>; + clk-gate = <0xc8 8>; + }; + + s2f_usr1_clk: s2f_usr1_clk { + #clock-cells = <0>; + compatible = "altr,socfpga-a10-gate-clk"; + clocks = <&peri_s2f_usr1_clk>; + clk-gate = <0xc8 6>; + }; + + clk24mhz: clk24mhz { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "clk24mhz"; + }; + }; + }; + + mmc: dwmmc0@ff808000 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "altr,socfpga-dw-mshc"; + reg = <0xff808000 0x1000>; + interrupts = <0x0 0x60 0x4>; + fifo-depth = <0x400>; + status = "okay"; + num-slots = <0x1>; + broken-cd; + altr,dw-mshc-ciu-div = <0x3>; + altr,dw-mshc-sdr-timing = <0x0 0x3>; + clocks = <&l4_mp_clk>, <&sdmmc_free_clk>; + clock-names = "biu", "ciu"; + clock-freq-min-max = <400000 25000000>; + pwr-en = <0x0>; + clock-frequency = <25000000>; + + slot@0 { + reg = <0x0>; + bus-width = <0x4>; + }; + }; + + ethernet@ff800000 { + compatible = "altr,socfpga-stmmac", "snps,dwmac-3.72a", "snps,dwmac"; + altr,sysmgr-syscon = <&sysmgr 0x44 0>; + reg = <0xff800000 0x2000>; + interrupts = <0x0 0x5a 0x4>; + interrupt-names = "macirq"; + mac-address = [00 00 00 00 00 00]; + clocks = <&clk24mhz>; + clock-names = "stmmaceth"; + status = "okay"; + phy-mode = "rgmii"; + phy-addr = <0xffffffff>; + snps,max-mtu = <0x0>; + resets = <&rst EMAC0_RESET>; + reset-names = "stmmaceth"; + }; + + ethernet@ff802000 { + compatible = "altr,socfpga-stmmac", "snps,dwmac-3.72a", "snps,dwmac"; + altr,sysmgr-syscon = <&sysmgr 0x48 0>; + reg = <0xff802000 0x2000>; + interrupts = <0x0 0x5b 0x4>; + interrupt-names = "macirq"; + mac-address = [00 00 00 00 00 00]; + clocks = <&clk24mhz>; + clock-names = "stmmaceth"; + status = "okay"; + phy-mode = "rgmii"; + phy-addr = <0xffffffff>; + resets = <&rst EMAC1_RESET>; + reset-names = "stmmaceth"; + }; + + ethernet@ff804000 { + compatible = "altr,socfpga-stmmac", "snps,dwmac-3.72a", "snps,dwmac"; + altr,sysmgr-syscon = <&sysmgr 0x4c 0>; + reg = <0xff804000 0x2000>; + interrupts = <0x0 0x5c 0x4>; + interrupt-names = "macirq"; + mac-address = [00 00 00 00 00 00]; + clocks = <&clk24mhz>; + clock-names = "stmmaceth"; + status = "okay"; + phy-mode = "rgmii"; + phy-addr = <0xffffffff>; + resets = <&rst EMAC2_RESET>; + reset-names = "stmmaceth"; + }; + + gpio@ffc03200 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "snps,dw-apb-gpio"; + reg = <0xffc03200 0x100>; + status = "disabled"; + + gpio-controller@0 { + compatible = "snps,dw-apb-gpio-port"; + gpio-controller; + #gpio-cells = <0x2>; + snps,nr-gpios = <0x1d>; + reg = <0x0>; + interrupt-controller; + #interrupt-cells = <0x2>; + interrupts = <0x0 0x6e 0x4>; + }; + }; + + gpio@ffc03300 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "snps,dw-apb-gpio"; + reg = <0xffc03300 0x100>; + status = "disabled"; + + gpio-controller@0 { + compatible = "snps,dw-apb-gpio-port"; + gpio-controller; + #gpio-cells = <0x2>; + snps,nr-gpios = <0x1d>; + reg = <0x0>; + interrupt-controller; + #interrupt-cells = <0x2>; + interrupts = <0x0 0x6f 0x4>; + }; + }; + + fpgamgr@0xffcfe400 { + compatible = "altr,socfpga-a10-fpga-mgr"; + transport = "mmio"; + reg = <0xffd03000 0x1000 0xffcfe400 0x400>; + }; + + i2c0: i2c@ffc02800 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "snps,designware-i2c"; + reg = <0xffc02800 0x100>; + interrupts = <0 0x67 4>; + status = "disabled"; + }; + + i2c1: i2c@ffc02900 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "snps,designware-i2c"; + reg = <0xffc02900 0x100>; + interrupts = <0 0x68 4>; + status = "disabled"; + }; + + i2c2: i2c@ffc02a00 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "snps,designware-i2c"; + reg = <0xffc02a00 0x100>; + interrupts = <0 0x69 4>; + status = "disabled"; + }; + + i2c3: i2c@ffc02b00 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "snps,designware-i2c"; + reg = <0xffc02b00 0x100>; + interrupts = <0 0x6a 4>; + status = "disabled"; + }; + + i2c4: i2c@ffc02c00 { + #address-cells = <0x1>; + #size-cells = <0x0>; + compatible = "snps,designware-i2c"; + reg = <0xffc02c00 0x100>; + interrupts = <0 0x6b 4>; + status = "disabled"; + }; + + l2-cache@fffff000 { + compatible = "arm,pl310-cache"; + reg = <0xfffff000 0x1000>; + interrupts = <0x0 0x12 0x4>; + cache-unified; + cache-level = <0x2>; + linux,phandle = <0x1>; + phandle = <0x1>; + }; + + sram@ffe00000 { + compatible = "mmio-sram"; + reg = <0xffe00000 0x100000>; + }; + + rst: rstmgr@ffd11000 { + #reset-cells = <0x1>; + compatible = "altr,rst-mgr"; + reg = <0xffd11000 0x1000>; + altr,modrst-offset = <0x20>; + }; + + sysmgr: sysmgr@ffd12000 { + compatible = "altr,sys-mgr", "syscon"; + reg = <0xffd12000 0x1000>; + interrupts = <0x0 0x10 0x4>; + cpu1-start-addr = <0xffd06230>; + }; + + timer { + compatible = "arm,armv8-timer"; + interrupts = <1 13 0xff01>, + <1 14 0xff01>, + <1 11 0xff01>, + <1 10 0xff01>; + clock-frequency = <24000000>; + }; + + timer0@ffc03000 { + compatible = "snps,dw-apb-timer-sp"; + interrupts = <0x0 0x71 0x4>; + reg = <0xffc03000 0x100>; + clock-frequency = <0x17d7840>; + }; + + timer1@ffc03100 { + compatible = "snps,dw-apb-timer-sp"; + interrupts = <0x0 0x72 0x4>; + reg = <0xffc03100 0x100>; + clock-frequency = <0x17d7840>; + }; + + timer2@ffd00000 { + compatible = "snps,dw-apb-timer-osc"; + interrupts = <0x0 0x73 0x4>; + reg = <0xffd00000 0x100>; + clock-frequency = <0x17d7840>; + }; + + timer3@ffd00100 { + compatible = "snps,dw-apb-timer-osc"; + interrupts = <0x0 0x74 0x4>; + reg = <0xffd01000 0x100>; + clock-frequency = <0x17d7840>; + }; + + serial0@ffc02000 { + compatible = "snps,dw-apb-uart"; + reg = <0xffc02000 0x100>; + interrupts = <0x0 0x6c 0x4>; + reg-shift = <0x2>; + reg-io-width = <0x4>; + status = "okay"; + clock-frequency = <0x5f5e100>; + }; + + serial1@ffc02100 { + compatible = "snps,dw-apb-uart"; + reg = <0xffc02100 0x100>; + interrupts = <0x0 0x6d 0x4>; + reg-shift = <0x2>; + reg-io-width = <0x4>; + status = "okay"; + clock-frequency = <0x5f5e100>; + }; + + watchdog@ffd00200 { + compatible = "snps,dw-wdt"; + reg = <0xffd00200 0x100>; + interrupts = <0x0 0x75 0x4>; + status = "disabled"; + }; + + watchdog@ffd00300 { + compatible = "snps,dw-wdt"; + reg = <0xffd00300 0x100>; + interrupts = <0x0 0x76 0x4>; + status = "disabled"; + }; + + watchdog@ffd00400 { + compatible = "snps,dw-wdt"; + reg = <0xffd00400 0x100>; + interrupts = <0x0 0x7d 0x4>; + status = "disabled"; + }; + + watchdog@ffd00500 { + compatible = "snps,dw-wdt"; + reg = <0xffd00500 0x100>; + interrupts = <0x0 0x7e 0x4>; + status = "disabled"; + }; + }; +}; diff --git a/drivers/clk/socfpga/clk-s10.c b/drivers/clk/socfpga/clk-s10.c index 72714633e39c..2f4301883a39 100644 --- a/drivers/clk/socfpga/clk-s10.c +++ b/drivers/clk/socfpga/clk-s10.c @@ -28,7 +28,7 @@ static const char * const emaca_free_mux[] = {"peri_emaca_clk", "boot_clk"}; static const char * const emacb_free_mux[] = {"peri_emacb_clk", "boot_clk"}; static const char * const emac_ptp_free_mux[] = {"peri_emac_ptp_clk", "boot_clk"}; static const char * const gpio_db_free_mux[] = {"peri_gpio_db_clk", "boot_clk"}; -static const char * const sdmmc_free_mux[] = {"peri_sdmmc_clk", "boot_clk"}; +static const char * const sdmmc_free_mux[] = {"main_sdmmc_clk", "boot_clk"}; static const char * const s2f_usr1_free_mux[] = {"peri_s2f_usr1_clk", "boot_clk"}; static const char * const psi_ref_free_mux[] = {"peri_psi_ref_clk", "boot_clk"}; static const char * const mpu_mux[] = { "mpu_free_clk", "boot_clk",}; @@ -37,6 +37,10 @@ static const char * const s2f_usr0_mux[] = {"f2s_free_clk", "boot_clk"}; static const char * const emac_mux[] = {"emaca_free_clk", "emacb_free_clk"}; static const char * const noc_mux[] = {"noc_free_clk", "boot_clk"}; +static const char * const mpu_free_mux[] = {"main_mpu_base_clk", + "peri_mpu_base_clk", + "osc1", "cb_intosc_hs_div2_clk", + "f2s_free_clk"}; /* clocks in AO (always on) controller */ static const struct stratix10_pll_clock s10_pll_clks[] = { { STRATIX10_BOOT_CLK, "boot_clk", boot_mux, ARRAY_SIZE(boot_mux), 0, @@ -57,7 +61,7 @@ static const struct stratix10_perip_c_clock s10_main_perip_c_clks[] = { }; static const struct stratix10_perip_cnt_clock s10_main_perip_cnt_clks[] = { - { STRATIX10_MPU_FREE_CLK, "mpu_free_clk", NULL, cntr_mux, ARRAY_SIZE(cntr_mux), + { STRATIX10_MPU_FREE_CLK, "mpu_free_clk", NULL, mpu_free_mux, ARRAY_SIZE(cntr_mux), 0, 0x48, 0, 0, 0}, { STRATIX10_NOC_FREE_CLK, "noc_free_clk", NULL, noc_free_mux, ARRAY_SIZE(noc_free_mux), 0, 0x4C, 0, 0, 0}, diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c index 89d20eb2b9f5..4d55f5fda60e 100644 --- a/drivers/dma/pl330.c +++ b/drivers/dma/pl330.c @@ -470,6 +470,8 @@ struct pl330_dmac { /* Size of MicroCode buffers for each channel. */ unsigned mcbufsz; + /* True if microcode must reside in cached memory. */ + bool microcode_cached; /* ioremap'ed address of PL330 registers. */ void __iomem *base; /* Populated by the PL330 core driver during pl330_add */ @@ -1894,19 +1896,44 @@ static int dmac_alloc_threads(struct pl330_dmac *pl330) return 0; } +static void *alloc_pl330_microcode_mem(struct pl330_dmac *pl330) +{ + int chans = pl330->pcfg.num_chan; + + if (pl330->microcode_cached) { + pl330->mcode_cpu = kzalloc(chans * pl330->mcbufsz, + GFP_KERNEL); + pl330->mcode_bus = virt_to_phys(pl330->mcode_cpu); + } else + pl330->mcode_cpu = + dma_alloc_coherent(pl330->ddma.dev, + chans * pl330->mcbufsz, + &pl330->mcode_bus, GFP_KERNEL); + + return pl330->mcode_cpu; +} + +static void free_pl330_microcode_mem(struct pl330_dmac *pl330) +{ + int chans = pl330->pcfg.num_chan; + + if (pl330->microcode_cached) + kfree(pl330->mcode_cpu); + else + dma_free_coherent(pl330->ddma.dev, + chans * pl330->mcbufsz, + pl330->mcode_cpu, pl330->mcode_bus); +} + static int dmac_alloc_resources(struct pl330_dmac *pl330) { - int chans = pl330->pcfg.num_chan; int ret; /* * Alloc MicroCode buffer for 'chans' Channel threads. * A channel's buffer offset is (Channel_Id * MCODE_BUFF_PERCHAN) */ - pl330->mcode_cpu = dma_alloc_attrs(pl330->ddma.dev, - chans * pl330->mcbufsz, - &pl330->mcode_bus, GFP_KERNEL, - DMA_ATTR_PRIVILEGED); + pl330->mcode_cpu = alloc_pl330_microcode_mem(pl330); if (!pl330->mcode_cpu) { dev_err(pl330->ddma.dev, "%s:%d Can't allocate memory!\n", __func__, __LINE__); @@ -1917,9 +1944,7 @@ static int dmac_alloc_resources(struct pl330_dmac *pl330) if (ret) { dev_err(pl330->ddma.dev, "%s:%d Can't to create channels for DMAC!\n", __func__, __LINE__); - dma_free_coherent(pl330->ddma.dev, - chans * pl330->mcbufsz, - pl330->mcode_cpu, pl330->mcode_bus); + free_pl330_microcode_mem(pl330); return ret; } @@ -1998,9 +2023,7 @@ static void pl330_del(struct pl330_dmac *pl330) /* Free DMAC resources */ dmac_free_threads(pl330); - dma_free_coherent(pl330->ddma.dev, - pl330->pcfg.num_chan * pl330->mcbufsz, pl330->mcode_cpu, - pl330->mcode_bus); + free_pl330_microcode_mem(pl330); } /* forward declaration */ @@ -2948,7 +2971,12 @@ pl330_probe(struct amba_device *adev, const struct amba_id *id) pl330->mcbufsz = 0; - /* get quirk */ + if (adev->dev.of_node) + pl330->microcode_cached = + of_property_read_bool(adev->dev.of_node, + "microcode-cached"); + + /* get quirk */ for (i = 0; i < ARRAY_SIZE(of_quirks); i++) if (of_property_read_bool(np, of_quirks[i].quirk)) pl330->quirks |= of_quirks[i].id; diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index ee9c5420c47f..f7b5775f748a 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -9,6 +9,13 @@ menuconfig FPGA kernel. The FPGA framework adds a FPGA manager class and FPGA manager drivers. +config FPGA_MGR_DEBUG_FS + bool "FPGA Manager DebugFS" + depends on FPGA && DEBUG_FS + help + Say Y here if you want to expose a DebugFS interface for the + FPGA Manager Framework. + if FPGA config FPGA_MGR_SOCFPGA @@ -56,6 +63,19 @@ config FPGA_MGR_ZYNQ_FPGA help FPGA manager driver support for Xilinx Zynq FPGAs. +config FPGA_MGR_STRATIX10_SOC + tristate "Intel Stratix10 SoC FPGA Manager" + depends on (ARCH_STRATIX10 && INTEL_SERVICE) + help + FPGA manager driver support for the Intel Stratix10 SoC. + +config FPGA_MGR_TS73XX + tristate "Technologic Systems TS-73xx SBC FPGA Manager" + depends on ARCH_EP93XX && MACH_TS72XX + help + FPGA manager driver support for the Altera Cyclone II FPGA + present on the TS-73xx SBC boards + config FPGA_MGR_XILINX_SPI tristate "Xilinx Configuration over Slave Serial (SPI)" depends on SPI diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index f9803dad6919..bed105d45378 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -4,7 +4,8 @@ # # Core FPGA Manager Framework -obj-$(CONFIG_FPGA) += fpga-mgr.o +fpga_mgr-objs := fpga-mgr.o fpga-mgr-debugfs.o +obj-$(CONFIG_FPGA) += fpga_mgr.o # FPGA Manager Drivers obj-$(CONFIG_FPGA_MGR_ALTERA_CVP) += altera-cvp.o @@ -13,6 +14,7 @@ obj-$(CONFIG_FPGA_MGR_ICE40_SPI) += ice40-spi.o obj-$(CONFIG_FPGA_MGR_MACHXO2_SPI) += machxo2-spi.o obj-$(CONFIG_FPGA_MGR_SOCFPGA) += socfpga.o obj-$(CONFIG_FPGA_MGR_SOCFPGA_A10) += socfpga-a10.o +obj-$(CONFIG_FPGA_MGR_STRATIX10_SOC) += stratix10-soc.o obj-$(CONFIG_FPGA_MGR_TS73XX) += ts73xx-fpga.o obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o diff --git a/drivers/fpga/altera-freeze-bridge.c b/drivers/fpga/altera-freeze-bridge.c index ffd586c48ecf..bc986dbeee8d 100644 --- a/drivers/fpga/altera-freeze-bridge.c +++ b/drivers/fpga/altera-freeze-bridge.c @@ -26,8 +26,6 @@ #define FREEZE_CSR_CTRL_RESET_REQ BIT(1) #define FREEZE_CSR_CTRL_UNFREEZE_REQ BIT(2) -#define FREEZE_BRIDGE_NAME "freeze" - struct altera_freeze_br_data { struct device *dev; void __iomem *base_addr; @@ -245,7 +243,7 @@ static int altera_freeze_br_probe(struct platform_device *pdev) priv->base_addr = base_addr; - br = fpga_bridge_create(dev, FREEZE_BRIDGE_NAME, + return fpga_bridge_register(dev, dev_name(dev), &altera_freeze_br_br_ops, priv); if (!br) return -ENOMEM; diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c index 24b8f98b73ec..00047bf6bfd7 100644 --- a/drivers/fpga/fpga-bridge.c +++ b/drivers/fpga/fpga-bridge.c @@ -292,7 +292,7 @@ static ssize_t name_show(struct device *dev, { struct fpga_bridge *bridge = to_fpga_bridge(dev); - return sprintf(buf, "%s\n", bridge->name); + return scnprintf(buf, PAGE_SIZE, "%s\n", bridge->name); } static ssize_t state_show(struct device *dev, @@ -304,7 +304,7 @@ static ssize_t state_show(struct device *dev, if (bridge->br_ops && bridge->br_ops->enable_show) enable = bridge->br_ops->enable_show(bridge); - return sprintf(buf, "%s\n", enable ? "enabled" : "disabled"); + return scnprintf(buf, 10, "%s\n", enable ? "enabled" : "disabled"); } static DEVICE_ATTR_RO(name); diff --git a/drivers/fpga/fpga-mgr-debugfs.c b/drivers/fpga/fpga-mgr-debugfs.c new file mode 100644 index 000000000000..6a47cc6326ca --- /dev/null +++ b/drivers/fpga/fpga-mgr-debugfs.c @@ -0,0 +1,235 @@ +/* + * FPGA Manager DebugFS + * + * Copyright (C) 2016 Intel Corporation. All rights reserved. + * + * 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/debugfs.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#if IS_ENABLED(CONFIG_FPGA_MGR_DEBUG_FS) + +static struct dentry *fpga_mgr_debugfs_root; + +struct fpga_mgr_debugfs { + struct dentry *debugfs_dir; + struct fpga_image_info *info; +}; + +static ssize_t fpga_mgr_firmware_write_file(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_manager *mgr = file->private_data; + struct fpga_mgr_debugfs *debugfs = mgr->debugfs; + char *buf; + int ret; + + ret = fpga_mgr_lock(mgr); + if (ret) { + dev_err(&mgr->dev, "FPGA manager is busy\n"); + return -EBUSY; + } + + buf = devm_kzalloc(&mgr->dev, count, GFP_KERNEL); + if (!buf) { + fpga_mgr_unlock(mgr); + return -ENOMEM; + } + + if (copy_from_user(buf, user_buf, count)) { + fpga_mgr_unlock(mgr); + devm_kfree(&mgr->dev, buf); + return -EFAULT; + } + + buf[count] = 0; + if (buf[count - 1] == '\n') + buf[count - 1] = 0; + + /* Release previous firmware name (if any). Save current one. */ + if (debugfs->info->firmware_name) + devm_kfree(&mgr->dev, debugfs->info->firmware_name); + debugfs->info->firmware_name = buf; + + ret = fpga_mgr_load(mgr, debugfs->info); + if (ret) + dev_err(&mgr->dev, + "fpga_mgr_load returned with value %d\n", ret); + + fpga_mgr_unlock(mgr); + + return count; +} + +static ssize_t fpga_mgr_firmware_read_file(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_manager *mgr = file->private_data; + struct fpga_mgr_debugfs *debugfs = mgr->debugfs; + char *buf; + int ret; + + if (!debugfs->info->firmware_name) + return 0; + + buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = snprintf(buf, PAGE_SIZE, "%s\n", debugfs->info->firmware_name); + if (ret < 0) { + kfree(buf); + return ret; + } + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); + kfree(buf); + + return ret; +} + +static const struct file_operations fpga_mgr_firmware_fops = { + .open = simple_open, + .read = fpga_mgr_firmware_read_file, + .write = fpga_mgr_firmware_write_file, + .llseek = default_llseek, +}; + +static ssize_t fpga_mgr_image_write_file(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct fpga_manager *mgr = file->private_data; + struct fpga_mgr_debugfs *debugfs = mgr->debugfs; + char *buf; + int ret; + + dev_info(&mgr->dev, "writing %zu bytes to %s\n", count, mgr->name); + + ret = fpga_mgr_lock(mgr); + if (ret) { + dev_err(&mgr->dev, "FPGA manager is busy\n"); + return -EBUSY; + } + + buf = kzalloc(count, GFP_KERNEL); + if (!buf) { + fpga_mgr_unlock(mgr); + return -ENOMEM; + } + + if (copy_from_user(buf, user_buf, count)) { + fpga_mgr_unlock(mgr); + kfree(buf); + return -EFAULT; + } + + /* If firmware interface was previously used, forget it. */ + if (debugfs->info->firmware_name) + devm_kfree(&mgr->dev, debugfs->info->firmware_name); + debugfs->info->firmware_name = NULL; + + debugfs->info->buf = buf; + debugfs->info->count = count; + + ret = fpga_mgr_load(mgr, debugfs->info); + if (ret) + dev_err(&mgr->dev, + "fpga_mgr_buf_load returned with value %d\n", ret); + + fpga_mgr_unlock(mgr); + + debugfs->info->buf = NULL; + debugfs->info->count = 0; + + kfree(buf); + + return count; +} + +static const struct file_operations fpga_mgr_image_fops = { + .open = simple_open, + .write = fpga_mgr_image_write_file, + .llseek = default_llseek, +}; + +void fpga_mgr_debugfs_add(struct fpga_manager *mgr) +{ + struct fpga_mgr_debugfs *debugfs; + struct fpga_image_info *info; + + if (!fpga_mgr_debugfs_root) + return; + + debugfs = kzalloc(sizeof(*debugfs), GFP_KERNEL); + if (!debugfs) + return; + + info = fpga_image_info_alloc(&mgr->dev); + if (!info) { + kfree(debugfs); + return; + } + debugfs->info = info; + + debugfs->debugfs_dir = debugfs_create_dir(dev_name(&mgr->dev), + fpga_mgr_debugfs_root); + + debugfs_create_file("firmware_name", 0600, debugfs->debugfs_dir, mgr, + &fpga_mgr_firmware_fops); + + debugfs_create_file("image", 0200, debugfs->debugfs_dir, mgr, + &fpga_mgr_image_fops); + + debugfs_create_u32("flags", 0600, debugfs->debugfs_dir, &info->flags); + + debugfs_create_u32("config_complete_timeout_us", 0600, + debugfs->debugfs_dir, + &info->config_complete_timeout_us); + + mgr->debugfs = debugfs; +} + +void fpga_mgr_debugfs_remove(struct fpga_manager *mgr) +{ + struct fpga_mgr_debugfs *debugfs = mgr->debugfs; + + if (!fpga_mgr_debugfs_root) + return; + + debugfs_remove_recursive(debugfs->debugfs_dir); + + /* this function also frees debugfs->info->firmware_name */ + fpga_image_info_free(debugfs->info); + + kfree(debugfs); +} + +void fpga_mgr_debugfs_init(void) +{ + fpga_mgr_debugfs_root = debugfs_create_dir("fpga_manager", NULL); + if (!fpga_mgr_debugfs_root) + pr_warn("fpga_mgr: Failed to create debugfs root\n"); +} + +void fpga_mgr_debugfs_uninit(void) +{ + debugfs_remove_recursive(fpga_mgr_debugfs_root); +} + +#endif /* CONFIG_FPGA_MGR_DEBUG_FS */ diff --git a/drivers/fpga/fpga-mgr-debugfs.h b/drivers/fpga/fpga-mgr-debugfs.h new file mode 100644 index 000000000000..2546c829d6cd --- /dev/null +++ b/drivers/fpga/fpga-mgr-debugfs.h @@ -0,0 +1,37 @@ +/* + * FPGA Manager DebugFS + * + * Copyright (C) 2016 Intel Corporation. All rights reserved. + * + * 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/>. + */ +#ifndef _LINUX_FPGA_MGR_DEBUGFS_H +#define _LINUX_FPGA_MGR_DEBUGFS_H + +#if IS_ENABLED(CONFIG_FPGA_MGR_DEBUG_FS) + +void fpga_mgr_debugfs_add(struct fpga_manager *mgr); +void fpga_mgr_debugfs_remove(struct fpga_manager *mgr); +void fpga_mgr_debugfs_init(void); +void fpga_mgr_debugfs_uninit(void); + +#else + +void fpga_mgr_debugfs_add(struct fpga_manager *mgr) {} +void fpga_mgr_debugfs_remove(struct fpga_manager *mgr) {} +void fpga_mgr_debugfs_init(void) {} +void fpga_mgr_debugfs_uninit(void) {} + +#endif /* CONFIG_FPGA_MGR_DEBUG_FS */ + +#endif /*_LINUX_FPGA_MGR_DEBUGFS_H */ diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index c1564cf827fe..67c668bb033c 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -17,6 +17,7 @@ #include <linux/slab.h> #include <linux/scatterlist.h> #include <linux/highmem.h> +#include "fpga-mgr-debugfs.h" static DEFINE_IDA(fpga_mgr_ida); static struct class *fpga_mgr_class; @@ -621,6 +622,8 @@ int fpga_mgr_register(struct fpga_manager *mgr) if (ret) goto error_device; + fpga_mgr_debugfs_add(mgr); + dev_info(&mgr->dev, "%s registered\n", mgr->name); return 0; @@ -640,6 +643,8 @@ void fpga_mgr_unregister(struct fpga_manager *mgr) { dev_info(&mgr->dev, "%s %s\n", __func__, mgr->name); + fpga_mgr_debugfs_remove(mgr); + /* * If the low level driver provides a method for putting fpga into * a desired state upon unregister, do it. @@ -669,11 +674,14 @@ static int __init fpga_mgr_class_init(void) fpga_mgr_class->dev_groups = fpga_mgr_groups; fpga_mgr_class->dev_release = fpga_mgr_dev_release; + fpga_mgr_debugfs_init(); + return 0; } static void __exit fpga_mgr_class_exit(void) { + fpga_mgr_debugfs_uninit(); class_destroy(fpga_mgr_class); ida_destroy(&fpga_mgr_ida); } diff --git a/drivers/fpga/stratix10-soc.c b/drivers/fpga/stratix10-soc.c new file mode 100644 index 000000000000..729312ef87ef --- /dev/null +++ b/drivers/fpga/stratix10-soc.c @@ -0,0 +1,536 @@ +/* + * FPGA Manager Driver for Intel Stratix10 SoC + * + * Copyright (C) 2018 Intel Corporation + * + * 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/fpga/fpga-mgr.h> +#include <linux/intel-service-client.h> +#include <linux/module.h> +#include <linux/of.h> + +/* + * FPGA programming requires a higher level of privilege (EL3), per the SoC + * design. + */ +#define NUM_SVC_BUFS 4 +#define SVC_BUF_SIZE SZ_512K + +/* Indicates buffer is in use if set */ +#define SVC_BUF_LOCK 0 + +#define S10_BUFFER_TIMEOUT (msecs_to_jiffies(SVC_RECONFIG_BUFFER_TIMEOUT_MS)) +#define S10_RECONFIG_TIMEOUT (msecs_to_jiffies(SVC_RECONFIG_REQUEST_TIMEOUT_MS)) + +/** + * struct s10_svc_buf + * @buf: virtual address of buf provided by service layer + * @lock: locked if buffer is in use + */ +struct s10_svc_buf { + char *buf; + unsigned long lock; +}; + +struct s10_priv { + struct intel_svc_chan *chan; + struct intel_svc_client client; + struct completion status_return_completion; + struct s10_svc_buf svc_bufs[NUM_SVC_BUFS]; + unsigned long status; +}; + +static int s10_svc_send_msg(struct s10_priv *priv, + enum intel_svc_command_code command, + void *payload, u32 payload_length) +{ + struct intel_svc_chan *chan = priv->chan; + struct intel_svc_client_msg msg; + int ret; + + pr_debug("%s cmd=%d payload=%p legnth=%d\n", + __func__, command, payload, payload_length); + + msg.command = command; + msg.payload = payload; + msg.payload_length = payload_length; + + ret = intel_svc_send(chan, &msg); + pr_debug("intel_svc_send returned status %d\n", ret); + + return ret; +} + +/** + * s10_free_buffers + * Free buffers allocated from the service layer's pool that are not in use. + * @mgr: fpga manager struct + * Free all buffers that are not in use. + * Return true when all buffers are freed. + */ +static bool s10_free_buffers(struct fpga_manager *mgr) +{ + struct s10_priv *priv = mgr->priv; + uint num_free = 0; + uint i; + + for (i = 0; i < NUM_SVC_BUFS; i++) { + if (!priv->svc_bufs[i].buf) { + num_free++; + continue; + } + + if (!test_and_set_bit_lock(SVC_BUF_LOCK, + &priv->svc_bufs[i].lock)) { + intel_svc_free_memory(priv->chan, + priv->svc_bufs[i].buf); + priv->svc_bufs[i].buf = NULL; + num_free++; + } + } + + return num_free == NUM_SVC_BUFS; +} + +/** + * s10_free_buffer_count + * Count how many buffers are not in use. + * @mgr: fpga manager struct + * Return # of buffers that are not in use. + */ +static uint s10_free_buffer_count(struct fpga_manager *mgr) +{ + struct s10_priv *priv = mgr->priv; + uint num_free = 0; + uint i; + + for (i = 0; i < NUM_SVC_BUFS; i++) + if (!priv->svc_bufs[i].buf) + num_free++; + + return num_free; +} + +/** + * s10_unlock_bufs + * Given the returned buffer address, match that address to our buffer struct + * and unlock that buffer. This marks it as available to be refilled and sent + * (or freed). + * @priv: private data + * @kaddr: kernel address of buffer that was returned from service layer + */ +static void s10_unlock_bufs(struct s10_priv *priv, void *kaddr) +{ + uint i; + + if (!kaddr) + return; + + for (i = 0; i < NUM_SVC_BUFS; i++) + if (priv->svc_bufs[i].buf == kaddr) { + clear_bit_unlock(SVC_BUF_LOCK, + &priv->svc_bufs[i].lock); + return; + } + + WARN(1, "Unknown buffer returned from service layer %p\n", kaddr); +} + +/** + * s10_receive_callback + * Callback for service layer to use to provide client (this driver) messages + * received through the mailbox. + * @client: service layer client struct + * @data: message + */ +static void s10_receive_callback(struct intel_svc_client *client, + struct intel_svc_c_data *data) +{ + struct s10_priv *priv = client->priv; + u32 status; + int i; + + WARN_ONCE(!data, "%s: intel_svc_rc_data = NULL", __func__); + + status = data->status; + + /* + * Here we set status bits as we receive them. Elsewhere, we always use + * test_and_clear_bit() to check status in priv->status + */ + for (i = 0; i <= SVC_STATUS_RECONFIG_ERROR; i++) + if (status & (1 << i)) + set_bit(i, &priv->status); + + if (status & BIT(SVC_STATUS_RECONFIG_BUFFER_DONE)) { + s10_unlock_bufs(priv, data->kaddr1); + s10_unlock_bufs(priv, data->kaddr2); + s10_unlock_bufs(priv, data->kaddr3); + } + + complete(&priv->status_return_completion); +} + +/** + * s10_ops_write_init + * Prepare for FPGA reconfiguration by requesting partial reconfig and + * allocating buffers from the service layer. + * @mgr: fpga manager + * @info: fpga image info + * @buf: fpga image buffer + * @count: size of buf in bytes + */ +static int s10_ops_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + struct s10_priv *priv = mgr->priv; + struct device *dev = priv->client.dev; + struct intel_command_reconfig_payload payload; + char *kbuf; + uint i; + int ret; + + payload.flags = 0; + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_info(dev, "Requesting partial reconfiguration.\n"); + payload.flags |= BIT(COMMAND_RECONFIG_FLAG_PARTIAL); + } else { + dev_info(dev, "Requesting full reconfiguration.\n"); + } + + reinit_completion(&priv->status_return_completion); + ret = s10_svc_send_msg(priv, COMMAND_RECONFIG, + &payload, sizeof(payload)); + if (ret < 0) + goto init_done; + + ret = wait_for_completion_interruptible_timeout( + &priv->status_return_completion, S10_RECONFIG_TIMEOUT); + if (!ret) { + dev_err(dev, "timeout waiting for RECONFIG_REQUEST\n"); + ret = -ETIMEDOUT; + goto init_done; + } + if (ret < 0) { + dev_err(dev, "error (%d) waiting for RECONFIG_REQUEST\n", ret); + goto init_done; + } + + ret = 0; + if (!test_and_clear_bit(SVC_STATUS_RECONFIG_REQUEST_OK, + &priv->status)) { + ret = -ETIMEDOUT; + goto init_done; + } + + /* Allocate buffers from the service layer's pool. */ + for (i = 0; i < NUM_SVC_BUFS; i++) { + kbuf = intel_svc_allocate_memory(priv->chan, SVC_BUF_SIZE); + if (!kbuf) { + s10_free_buffers(mgr); + ret = -ENOMEM; + goto init_done; + } + + priv->svc_bufs[i].buf = kbuf; + priv->svc_bufs[i].lock = 0; + } + +init_done: + intel_svc_done(priv->chan); + return ret; +} + +/** + * s10_send_buf + * Send a buffer to the service layer queue. + * @mgr: fpga manager struct + * @buf: fpga image buffer + * @count: size of buf in bytes + * Returns # of bytes transferred or -ENOBUFS if the all the buffers are in use + * or if the service queue is full. Never returns 0. + */ +static int s10_send_buf(struct fpga_manager *mgr, const char *buf, size_t count) + +{ + struct s10_priv *priv = mgr->priv; + struct device *dev = priv->client.dev; + void *svc_buf; + size_t xfer_sz; + int ret; + uint i; + + /* get/lock a buffer that that's not being used */ + for (i = 0; i < NUM_SVC_BUFS; i++) + if (!test_and_set_bit_lock(SVC_BUF_LOCK, + &priv->svc_bufs[i].lock)) + break; + + if (i == NUM_SVC_BUFS) + return -ENOBUFS; + + xfer_sz = count < SVC_BUF_SIZE ? count : SVC_BUF_SIZE; + + svc_buf = priv->svc_bufs[i].buf; + memcpy(svc_buf, buf, xfer_sz); + /* Returns -ENOBUFS If service queue is full. */ + ret = s10_svc_send_msg(priv, COMMAND_RECONFIG_DATA_SUBMIT, + svc_buf, xfer_sz); + if (ret < 0) { + dev_err(dev, + "Error while sending data to service layer (%d)", ret); + return ret; + } + + return xfer_sz; +} + +/** + * s10_ops_write + * + * Send a FPGA image to privileged layers to write to the FPGA. When done + * sending, free all service layer buffers we allocated in write_init. + * + * @mgr: fpga manager + * @buf: fpga image buffer + * @count: size of buf in bytes + * Returns 0 for success or negative errno. + */ +static int s10_ops_write(struct fpga_manager *mgr, const char *buf, + size_t count) +{ + struct s10_priv *priv = mgr->priv; + struct device *dev = priv->client.dev; + long wait_status; + int sent = 0; + int ret = 0; + + /* + * Loop waiting for buffers to be returned. When a buffer is returned, + * reuse it to send more data or free if if all data has been sent. + */ + while (count > 0 || s10_free_buffer_count(mgr) != NUM_SVC_BUFS) { + reinit_completion(&priv->status_return_completion); + + if (count > 0) { + sent = s10_send_buf(mgr, buf, count); + if (sent < 0) + continue; + + count -= sent; + buf += sent; + } else { + if (s10_free_buffers(mgr)) + return 0; + + ret = s10_svc_send_msg( + priv, COMMAND_RECONFIG_DATA_CLAIM, + NULL, 0); + if (ret < 0) + break; + } + + /* + * If callback hasn't already happened, wait for buffers to be + * returned from service layer + */ + wait_status = 1; /* not timed out */ + if (!priv->status) + wait_status = wait_for_completion_interruptible_timeout( + &priv->status_return_completion, + S10_BUFFER_TIMEOUT); + + if (test_and_clear_bit(SVC_STATUS_RECONFIG_BUFFER_DONE, + &priv->status) || + test_and_clear_bit(SVC_STATUS_RECONFIG_BUFFER_SUBMITTED, + &priv->status)) { + ret = 0; + continue; + } + + if (test_and_clear_bit(SVC_STATUS_RECONFIG_ERROR, + &priv->status)) { + dev_err(dev, "ERROR - giving up - SVC_STATUS_RECONFIG_ERROR\n"); + ret = -EFAULT; + break; + } + + if (!wait_status) { + dev_err(dev, "timeout waiting for svc layer buffers\n"); + ret = -ETIMEDOUT; + break; + } + if (wait_status < 0) { + ret = wait_status; + dev_err(dev, + "error (%d) waiting for svc layer buffers\n", + ret); + break; + } + } + + if (!s10_free_buffers(mgr)) + dev_err(dev, "%s not all buffers were freed\n", __func__); + + return ret; +} + +/** + * s10_ops_write_complete + * Wait for FPGA configuration to be done + * @mgr: fpga manager + * @info: fpga image info + * Returns 0 for success negative errno. + */ +static int s10_ops_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + struct s10_priv *priv = mgr->priv; + struct device *dev = priv->client.dev; + unsigned long timeout; + int ret; + + timeout = usecs_to_jiffies(info->config_complete_timeout_us); + + do { + reinit_completion(&priv->status_return_completion); + + ret = s10_svc_send_msg(priv, COMMAND_RECONFIG_STATUS, NULL, 0); + if (ret < 0) + break; + + ret = wait_for_completion_interruptible_timeout( + &priv->status_return_completion, timeout); + if (!ret) { + dev_err(dev, + "timeout waiting for RECONFIG_COMPLETED\n"); + ret = -ETIMEDOUT; + break; + } + if (ret < 0) { + dev_err(dev, + "error (%d) waiting for RECONFIG_COMPLETED\n", + ret); + break; + } + /* Not error or timeout, so ret is # of jiffies until timeout */ + timeout = ret; + ret = 0; + + if (test_and_clear_bit(SVC_STATUS_RECONFIG_COMPLETED, + &priv->status)) + break; + + if (test_and_clear_bit(SVC_STATUS_RECONFIG_ERROR, + &priv->status)) { + dev_err(dev, "ERROR - giving up - SVC_STATUS_RECONFIG_ERROR\n"); + ret = -EFAULT; + break; + } + } while (1); + + intel_svc_done(priv->chan); + + return ret; +} + +static enum fpga_mgr_states s10_ops_state(struct fpga_manager *mgr) +{ + return FPGA_MGR_STATE_UNKNOWN; +} + +static const struct fpga_manager_ops s10_ops = { + .state = s10_ops_state, + .write_init = s10_ops_write_init, + .write = s10_ops_write, + .write_complete = s10_ops_write_complete, +}; + +static int s10_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fpga_manager *mgr; + struct s10_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client.dev = dev; + priv->client.receive_cb = s10_receive_callback; + priv->client.priv = priv; + + priv->chan = request_svc_channel_byname(&priv->client, + SVC_CLIENT_FPGA); + if (IS_ERR(priv->chan)) { + dev_err(dev, "couldn't get service channel (%s)\n", + SVC_CLIENT_FPGA); + return PTR_ERR(priv->chan); + } + + init_completion(&priv->status_return_completion); + + mgr = fpga_mgr_create(dev, "Stratix10 SOC FPGA Manager", + &s10_ops, priv); + if (!mgr) { + free_svc_channel(priv->chan); + return -ENOMEM; + } + + platform_set_drvdata(pdev, mgr); + + ret = fpga_mgr_register(mgr); + if (ret) { + fpga_mgr_free(mgr); + free_svc_channel(priv->chan); + } + + return ret; +} + +static int s10_remove(struct platform_device *pdev) +{ + struct fpga_manager *mgr = platform_get_drvdata(pdev); + struct s10_priv *priv = mgr->priv; + + fpga_mgr_unregister(mgr); + free_svc_channel(priv->chan); + + return 0; +} + +static const struct of_device_id s10_of_match[] = { + { .compatible = "intel,stratix10-soc-fpga-mgr", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, s10_of_match); + +static struct platform_driver s10_driver = { + .probe = s10_probe, + .remove = s10_remove, + .driver = { + .name = "Stratix10 SoC FPGA manager", + .of_match_table = of_match_ptr(s10_of_match), + }, +}; + +module_platform_driver(s10_driver); + +MODULE_AUTHOR("Alan Tull <atull@kernel.org>"); +MODULE_DESCRIPTION("Intel Stratix 10 SOC FPGA Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a3049128a0a8..d8c6fc6572fc 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -245,6 +245,15 @@ config SENSORS_ADT7475 This driver can also be build as a module. If so, the module will be called adt7475. +config SENSORS_ALTERA_A10SR + bool "Altera Arria10 System Status" + depends on MFD_ALTERA_A10SR + help + If you say yes here you get support for the power ready status + for the Arria10's external power supplies on the Arria10 DevKit. + These values are read over the SPI bus from the Arria10 System + Resource chip. + config SENSORS_ASC7621 tristate "Andigilog aSC7621" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e7d52a36e6c4..6aecca24fa29 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o +obj-$(CONFIG_SENSORS_ALTERA_A10SR) += altera-a10sr-hwmon.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o diff --git a/drivers/hwmon/altera-a10sr-hwmon.c b/drivers/hwmon/altera-a10sr-hwmon.c new file mode 100644 index 000000000000..584cc48a9b1b --- /dev/null +++ b/drivers/hwmon/altera-a10sr-hwmon.c @@ -0,0 +1,406 @@ +/* + * Copyright Intel Corporation (C) 2017-2018. All Rights Reserved + * Copyright Altera Corporation (C) 2014-2016. All Rights Reserved + * + * 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/>. + * + * HW Monitor driver for Altera Arria10 MAX5 System Resource Chip + * Adapted from DA9052 + */ + +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/mfd/altera-a10sr.h> +#include <linux/module.h> +#include <linux/of.h> + +#define ALTR_A10SR_1V0_BIT_POS ALTR_A10SR_PG1_1V0_SHIFT +#define ALTR_A10SR_0V95_BIT_POS ALTR_A10SR_PG1_0V95_SHIFT +#define ALTR_A10SR_0V9_BIT_POS ALTR_A10SR_PG1_0V9_SHIFT +#define ALTR_A10SR_10V_BIT_POS ALTR_A10SR_PG1_10V_SHIFT +#define ALTR_A10SR_5V0_BIT_POS ALTR_A10SR_PG1_5V0_SHIFT +#define ALTR_A10SR_3V3_BIT_POS ALTR_A10SR_PG1_3V3_SHIFT +#define ALTR_A10SR_2V5_BIT_POS ALTR_A10SR_PG1_2V5_SHIFT +#define ALTR_A10SR_1V8_BIT_POS ALTR_A10SR_PG1_1V8_SHIFT +#define ALTR_A10SR_OP_FLAG_BIT_POS ALTR_A10SR_PG1_OP_FLAG_SHIFT +/* 2nd register needs an offset of 8 to get to 2nd register */ +#define ALTR_A10SR_FBC2MP_BIT_POS (8 + ALTR_A10SR_PG2_FBC2MP_SHIFT) +#define ALTR_A10SR_FAC2MP_BIT_POS (8 + ALTR_A10SR_PG2_FAC2MP_SHIFT) +#define ALTR_A10SR_FMCBVADJ_BIT_POS (8 + ALTR_A10SR_PG2_FMCBVADJ_SHIFT) +#define ALTR_A10SR_FMCAVADJ_BIT_POS (8 + ALTR_A10SR_PG2_FMCAVADJ_SHIFT) +#define ALTR_A10SR_HL_VDDQ_BIT_POS (8 + ALTR_A10SR_PG2_HL_VDDQ_SHIFT) +#define ALTR_A10SR_HL_VDD_BIT_POS (8 + ALTR_A10SR_PG2_HL_VDD_SHIFT) +#define ALTR_A10SR_HL_HPS_BIT_POS (8 + ALTR_A10SR_PG2_HL_HPS_SHIFT) +#define ALTR_A10SR_HPS_BIT_POS (8 + ALTR_A10SR_PG2_HPS_SHIFT) +/* 3rd register needs an offset of 16 to get to 3rd register */ +#define ALTR_A10SR_PCIE_WAKE_BIT_POS (16 + ALTR_A10SR_PG3_PCIE_WAKE_SHIFT) +#define ALTR_A10SR_PCIE_PR_BIT_POS (16 + ALTR_A10SR_PG3_PCIE_PR_SHIFT) +#define ALTR_A10SR_FMCB_PR_BIT_POS (16 + ALTR_A10SR_PG3_FMCB_PR_SHIFT) +#define ALTR_A10SR_FMCA_PR_BIT_POS (16 + ALTR_A10SR_PG3_FMCA_PR_SHIFT) +#define ALTR_A10SR_FILE_PR_BIT_POS (16 + ALTR_A10SR_PG3_FILE_PR_SHIFT) +#define ALTR_A10SR_BF_PR_BIT_POS (16 + ALTR_A10SR_PG3_BF_PR_SHIFT) +#define ALTR_A10SR_10V_FAIL_BIT_POS (16 + ALTR_A10SR_PG3_10V_FAIL_SHIFT) +#define ALTR_A10SR_FAM2C_BIT_POS (16 + ALTR_A10SR_PG3_FAM2C_SHIFT) +/* FMCA/B & PCIE Enables need an offset of 24 */ +#define ALTR_A10SR_FMCB_AUXEN_POS (24 + ALTR_A10SR_FMCB_AUXEN_SHIFT) +#define ALTR_A10SR_FMCB_EN_POS (24 + ALTR_A10SR_FMCB_EN_SHIFT) +#define ALTR_A10SR_FMCA_AUXEN_POS (24 + ALTR_A10SR_FMCA_AUXEN_SHIFT) +#define ALTR_A10SR_FMCA_EN_POS (24 + ALTR_A10SR_FMCA_EN_SHIFT) +#define ALTR_A10SR_PCIE_AUXEN_POS (24 + ALTR_A10SR_PCIE_AUXEN_SHIFT) +#define ALTR_A10SR_PCIE_EN_POS (24 + ALTR_A10SR_PCIE_EN_SHIFT) +/* HPS Resets need an offset of 32 */ +#define ALTR_A10SR_HPS_RST_UART_POS (32 + ALTR_A10SR_HPS_UARTA_RSTN_SHIFT) +#define ALTR_A10SR_HPS_RST_WARM_POS (32 + ALTR_A10SR_HPS_WARM_RSTN_SHIFT) +#define ALTR_A10SR_HPS_RST_WARM1_POS (32 + ALTR_A10SR_HPS_WARM_RST1N_SHIFT) +#define ALTR_A10SR_HPS_RST_COLD_POS (32 + ALTR_A10SR_HPS_COLD_RSTN_SHIFT) +#define ALTR_A10SR_HPS_RST_NPOR_POS (32 + ALTR_A10SR_HPS_NPOR_SHIFT) +#define ALTR_A10SR_HPS_RST_NRST_POS (32 + ALTR_A10SR_HPS_NRST_SHIFT) +#define ALTR_A10SR_HPS_RST_ENET_POS (32 + ALTR_A10SR_HPS_ENET_RSTN_SHIFT) +#define ALTR_A10SR_HPS_RST_ENETINT_POS (32 + ALTR_A10SR_HPS_ENET_INTN_SHIFT) +/* Peripheral Resets need an offset of 40 */ +#define ALTR_A10SR_PER_RST_USB_POS (40 + ALTR_A10SR_USB_RST_SHIFT) +#define ALTR_A10SR_PER_RST_BQSPI_POS (40 + ALTR_A10SR_BQSPI_RST_N_SHIFT) +#define ALTR_A10SR_PER_RST_FILE_POS (40 + ALTR_A10SR_FILE_RST_N_SHIFT) +#define ALTR_A10SR_PER_RST_PCIE_POS (40 + ALTR_A10SR_PCIE_PERST_N_SHIFT) +/* HWMON - Read Entire Register */ +#define ALTR_A10SR_ENTIRE_REG (88) +#define ALTR_A10SR_ENTIRE_REG_MASK (0xFF) +#define ALTR_A10SR_VERSION (0 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_LED (1 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_PB (2 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_PBF (3 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_PG1 (4 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_PG2 (5 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_PG3 (6 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_FMCAB (7 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_HPS_RST (8 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_PER_RST (9 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_SFPA (10 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_SFPB (11 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_I2C_MASTER (12 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_WARM_RST (13 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_WARM_RST_KEY (14 + ALTR_A10SR_ENTIRE_REG) +#define ALTR_A10SR_PMBUS (15 + ALTR_A10SR_ENTIRE_REG) + +/** + * struct altr_a10sr_hwmon - Altera Max5 HWMON device private data structure + * @device: hwmon class. + * @regmap: the regmap from the parent device. + */ +struct altr_a10sr_hwmon { + struct regmap *regmap; +}; + +static ssize_t altr_a10sr_read_status(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct altr_a10sr_hwmon *hwmon = dev_get_drvdata(dev); + int val, ret, index = to_sensor_dev_attr(devattr)->index; + int mask = ALTR_A10SR_REG_BIT_MASK(index); + unsigned char reg = ALTR_A10SR_PWR_GOOD1_REG + + ALTR_A10SR_REG_OFFSET(index); + + /* Check if this is an entire register read */ + if (index >= ALTR_A10SR_ENTIRE_REG) { + reg = ((index - ALTR_A10SR_ENTIRE_REG) << 1); + mask = ALTR_A10SR_ENTIRE_REG_MASK; + } + + ret = regmap_read(hwmon->regmap, reg, &val); + if (ret < 0) + return ret; + + if (mask == ALTR_A10SR_ENTIRE_REG_MASK) + val = val & mask; + else + val = !!(val & mask); + + return scnprintf(buf, 5, "%d\n", val); +} + +static ssize_t set_enable(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + unsigned long val; + struct altr_a10sr_hwmon *hwmon = dev_get_drvdata(dev); + int ret, index = to_sensor_dev_attr(dev_attr)->index; + int mask = ALTR_A10SR_REG_BIT_MASK(index); + unsigned char reg = (ALTR_A10SR_PWR_GOOD1_REG & WRITE_REG_MASK) + + ALTR_A10SR_REG_OFFSET(index); + int res = kstrtol(buf, 10, &val); + + if (res < 0) + return res; + + /* Check if this is an entire register write */ + if (index >= ALTR_A10SR_ENTIRE_REG) { + reg = ((index - ALTR_A10SR_ENTIRE_REG) << 1); + mask = ALTR_A10SR_ENTIRE_REG_MASK; + } + + ret = regmap_update_bits(hwmon->regmap, reg, mask, val); + if (ret < 0) + return ret; + + return count; +} + +/* First Power Good Register Bits */ +static SENSOR_DEVICE_ATTR(1v0_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_1V0_BIT_POS); +static SENSOR_DEVICE_ATTR(0v95_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_0V95_BIT_POS); +static SENSOR_DEVICE_ATTR(0v9_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_0V9_BIT_POS); +static SENSOR_DEVICE_ATTR(5v0_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_5V0_BIT_POS); +static SENSOR_DEVICE_ATTR(3v3_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_3V3_BIT_POS); +static SENSOR_DEVICE_ATTR(2v5_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_2V5_BIT_POS); +static SENSOR_DEVICE_ATTR(1v8_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_1V8_BIT_POS); +static SENSOR_DEVICE_ATTR(opflag_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_OP_FLAG_BIT_POS); +/* Second Power Good Register Bits */ +static SENSOR_DEVICE_ATTR(fbc2mp_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_FBC2MP_BIT_POS); +static SENSOR_DEVICE_ATTR(fac2mp_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_FAC2MP_BIT_POS); +static SENSOR_DEVICE_ATTR(fmcbvadj_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_FMCBVADJ_BIT_POS); +static SENSOR_DEVICE_ATTR(fmcavadj_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_FMCAVADJ_BIT_POS); +static SENSOR_DEVICE_ATTR(hl_vddq_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_HL_VDDQ_BIT_POS); +static SENSOR_DEVICE_ATTR(hl_vdd_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_HL_VDD_BIT_POS); +static SENSOR_DEVICE_ATTR(hlhps_vdd_alarm, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_HL_HPS_BIT_POS); +static SENSOR_DEVICE_ATTR(hps_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_HPS_BIT_POS); +/* Third Power Good Register Bits */ +static SENSOR_DEVICE_ATTR(pcie_wake_input, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_PCIE_WAKE_BIT_POS); +static SENSOR_DEVICE_ATTR(pcie_pr_input, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_PCIE_PR_BIT_POS); +static SENSOR_DEVICE_ATTR(fmcb_pr_input, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_FMCB_PR_BIT_POS); +static SENSOR_DEVICE_ATTR(fmca_pr_input, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_FMCA_PR_BIT_POS); +static SENSOR_DEVICE_ATTR(file_pr_input, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_FILE_PR_BIT_POS); +static SENSOR_DEVICE_ATTR(bf_pr_input, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_BF_PR_BIT_POS); +static SENSOR_DEVICE_ATTR(10v_alarm, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_10V_FAIL_BIT_POS); +static SENSOR_DEVICE_ATTR(fam2c_alarm, S_IRUGO, altr_a10sr_read_status, NULL, + ALTR_A10SR_FAM2C_BIT_POS); +/* Peripheral Enable bits */ +static SENSOR_DEVICE_ATTR(fmcb_aux_en, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_FMCB_AUXEN_POS); +static SENSOR_DEVICE_ATTR(fmcb_en, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_FMCB_EN_POS); +static SENSOR_DEVICE_ATTR(fmca_aux_en, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_FMCA_AUXEN_POS); +static SENSOR_DEVICE_ATTR(fmca_en, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_FMCA_EN_POS); +static SENSOR_DEVICE_ATTR(pcie_aux_en, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_PCIE_AUXEN_POS); +static SENSOR_DEVICE_ATTR(pcie_en, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_PCIE_EN_POS); +/* HPS Reset bits */ +static SENSOR_DEVICE_ATTR(hps_uart_rst, S_IRUGO, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST_UART_POS); +static SENSOR_DEVICE_ATTR(hps_warm_rst, S_IRUGO, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST_WARM_POS); +static SENSOR_DEVICE_ATTR(hps_warm1_rst, S_IRUGO, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST_WARM1_POS); +static SENSOR_DEVICE_ATTR(hps_cold_rst, S_IRUGO, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST_COLD_POS); +static SENSOR_DEVICE_ATTR(hps_npor, S_IRUGO, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST_NPOR_POS); +static SENSOR_DEVICE_ATTR(hps_nrst, S_IRUGO, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST_NRST_POS); +static SENSOR_DEVICE_ATTR(hps_enet_rst, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST_ENET_POS); +static SENSOR_DEVICE_ATTR(hps_enet_int, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST_ENETINT_POS); +/* Peripheral Reset bits */ +static SENSOR_DEVICE_ATTR(usb_reset, S_IRUGO | S_IWUSR, altr_a10sr_read_status, + set_enable, ALTR_A10SR_PER_RST_USB_POS); +static SENSOR_DEVICE_ATTR(bqspi_resetn, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_PER_RST_BQSPI_POS); +static SENSOR_DEVICE_ATTR(file_resetn, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_PER_RST_FILE_POS); +static SENSOR_DEVICE_ATTR(pcie_perstn, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_PER_RST_PCIE_POS); +/* Entire Byte Read */ +static SENSOR_DEVICE_ATTR(max5_version, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_VERSION); +static SENSOR_DEVICE_ATTR(max5_led, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_LED); +static SENSOR_DEVICE_ATTR(max5_button, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_PB); +static SENSOR_DEVICE_ATTR(max5_button_irq, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, ALTR_A10SR_PBF); +static SENSOR_DEVICE_ATTR(max5_pg1, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_PG1); +static SENSOR_DEVICE_ATTR(max5_pg2, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_PG2); +static SENSOR_DEVICE_ATTR(max5_pg3, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_PG3); +static SENSOR_DEVICE_ATTR(max5_fmcab, S_IRUGO, altr_a10sr_read_status, + NULL, ALTR_A10SR_FMCAB); +static SENSOR_DEVICE_ATTR(max5_hps_resets, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_HPS_RST); +static SENSOR_DEVICE_ATTR(max5_per_resets, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_PER_RST); +static SENSOR_DEVICE_ATTR(max5_sfpa, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, ALTR_A10SR_SFPA); +static SENSOR_DEVICE_ATTR(max5_sfpb, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, ALTR_A10SR_SFPB); +static SENSOR_DEVICE_ATTR(max5_i2c_master, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_I2C_MASTER); +static SENSOR_DEVICE_ATTR(max5_pmbus, S_IRUGO | S_IWUSR, + altr_a10sr_read_status, set_enable, + ALTR_A10SR_PMBUS); + +static struct attribute *altr_a10sr_attrs[] = { + /* First Power Good Register */ + &sensor_dev_attr_opflag_alarm.dev_attr.attr, + &sensor_dev_attr_1v8_alarm.dev_attr.attr, + &sensor_dev_attr_2v5_alarm.dev_attr.attr, + &sensor_dev_attr_1v0_alarm.dev_attr.attr, + &sensor_dev_attr_3v3_alarm.dev_attr.attr, + &sensor_dev_attr_5v0_alarm.dev_attr.attr, + &sensor_dev_attr_0v9_alarm.dev_attr.attr, + &sensor_dev_attr_0v95_alarm.dev_attr.attr, + /* Second Power Good Register */ + &sensor_dev_attr_hps_alarm.dev_attr.attr, + &sensor_dev_attr_hlhps_vdd_alarm.dev_attr.attr, + &sensor_dev_attr_hl_vdd_alarm.dev_attr.attr, + &sensor_dev_attr_hl_vddq_alarm.dev_attr.attr, + &sensor_dev_attr_fmcavadj_alarm.dev_attr.attr, + &sensor_dev_attr_fmcbvadj_alarm.dev_attr.attr, + &sensor_dev_attr_fac2mp_alarm.dev_attr.attr, + &sensor_dev_attr_fbc2mp_alarm.dev_attr.attr, + /* Third Power Good Register */ + &sensor_dev_attr_pcie_wake_input.dev_attr.attr, + &sensor_dev_attr_pcie_pr_input.dev_attr.attr, + &sensor_dev_attr_fmcb_pr_input.dev_attr.attr, + &sensor_dev_attr_fmca_pr_input.dev_attr.attr, + &sensor_dev_attr_file_pr_input.dev_attr.attr, + &sensor_dev_attr_bf_pr_input.dev_attr.attr, + &sensor_dev_attr_10v_alarm.dev_attr.attr, + &sensor_dev_attr_fam2c_alarm.dev_attr.attr, +/* Peripheral Enable Register */ + &sensor_dev_attr_fmcb_aux_en.dev_attr.attr, + &sensor_dev_attr_fmcb_en.dev_attr.attr, + &sensor_dev_attr_fmca_aux_en.dev_attr.attr, + &sensor_dev_attr_fmca_en.dev_attr.attr, + &sensor_dev_attr_pcie_aux_en.dev_attr.attr, + &sensor_dev_attr_pcie_en.dev_attr.attr, + /* HPS Reset bits */ + &sensor_dev_attr_hps_uart_rst.dev_attr.attr, + &sensor_dev_attr_hps_warm_rst.dev_attr.attr, + &sensor_dev_attr_hps_warm1_rst.dev_attr.attr, + &sensor_dev_attr_hps_cold_rst.dev_attr.attr, + &sensor_dev_attr_hps_npor.dev_attr.attr, + &sensor_dev_attr_hps_nrst.dev_attr.attr, + &sensor_dev_attr_hps_enet_rst.dev_attr.attr, + &sensor_dev_attr_hps_enet_int.dev_attr.attr, + /* Peripheral Reset bits */ + &sensor_dev_attr_usb_reset.dev_attr.attr, + &sensor_dev_attr_bqspi_resetn.dev_attr.attr, + &sensor_dev_attr_file_resetn.dev_attr.attr, + &sensor_dev_attr_pcie_perstn.dev_attr.attr, + /* Byte Value Register */ + &sensor_dev_attr_max5_version.dev_attr.attr, + &sensor_dev_attr_max5_led.dev_attr.attr, + &sensor_dev_attr_max5_button.dev_attr.attr, + &sensor_dev_attr_max5_button_irq.dev_attr.attr, + &sensor_dev_attr_max5_pg1.dev_attr.attr, + &sensor_dev_attr_max5_pg2.dev_attr.attr, + &sensor_dev_attr_max5_pg3.dev_attr.attr, + &sensor_dev_attr_max5_fmcab.dev_attr.attr, + &sensor_dev_attr_max5_hps_resets.dev_attr.attr, + &sensor_dev_attr_max5_per_resets.dev_attr.attr, + &sensor_dev_attr_max5_sfpa.dev_attr.attr, + &sensor_dev_attr_max5_sfpb.dev_attr.attr, + &sensor_dev_attr_max5_i2c_master.dev_attr.attr, + &sensor_dev_attr_max5_pmbus.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(altr_a10sr); + +static int altr_a10sr_hwmon_probe(struct platform_device *pdev) +{ + struct altr_a10sr_hwmon *hwmon; + struct device *hwmon_dev; + struct altr_a10sr *a10sr = dev_get_drvdata(pdev->dev.parent); + + hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + hwmon->regmap = a10sr->regmap; + + hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, + "a10sr_hwmon", hwmon, + altr_a10sr_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id altr_a10sr_hwmon_of_match[] = { + { .compatible = "altr,a10sr-hwmon" }, + { }, +}; +MODULE_DEVICE_TABLE(of, altr_a10sr_hwmon_of_match); + +static struct platform_driver altr_a10sr_hwmon_driver = { + .probe = altr_a10sr_hwmon_probe, + .driver = { + .name = "altr_a10sr_hwmon", + .of_match_table = of_match_ptr(altr_a10sr_hwmon_of_match), + }, +}; + +module_platform_driver(altr_a10sr_hwmon_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Thor Thayer <tthayer@opensource.altera.com>"); +MODULE_DESCRIPTION("HW Monitor driver for Altera Arria10 System Resource Chip"); diff --git a/drivers/mfd/altera-a10sr.c b/drivers/mfd/altera-a10sr.c index 96e7d2cb7b89..c57b5493ebcc 100644 --- a/drivers/mfd/altera-a10sr.c +++ b/drivers/mfd/altera-a10sr.c @@ -30,6 +30,10 @@ static const struct mfd_cell altr_a10sr_subdev_info[] = { { + .name = "altr_a10sr_hwmon", + .of_compatible = "altr,a10sr-hwmon", + }, + { .name = "altr_a10sr_gpio", .of_compatible = "altr,a10sr-gpio", }, diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 3726eacdf65d..4c7d95e7e465 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -152,6 +152,36 @@ config INTEL_MID_PTI an Intel Atom (non-netbook) mobile device containing a MIPI P1149.7 standard implementation. +config INTEL_SERVICE + tristate "Intel Service Layer" + depends on ARCH_STRATIX10 + default n + help + Intel service layer runs at privileged exception level, interfaces with + the service providers (FPGA manager is one of them) and manages secure + monitor call to communicate with secure monitor software at secure monitor + exception level. + + Say Y here if you want Intel service layer support. + +config INTEL_RSU + tristate "Intel Remote System Update" + depends on INTEL_SERVICE + help + The Intel Remote System Update (RSU) driver exposes interfaces + accessed through the Intel Service Layer to user space via SysFS + device attribute nodes. The RSU interfaces report/control some of + the optional RSU features of the Stratix 10 SoC FPGA. + + The RSU feature provides a way for customers to update the boot + configuration of a Stratix 10 SoC device with significantly reduced + risk of corrupting the bitstream storage and bricking the system. + + Enable RSU support if you are using an Intel SoC FPGA with the RSU + feature enabled and you want Linux user space control. + + Say Y here if you want Intel RSU support. + config SGI_IOC4 tristate "SGI IOC4 Base IO support" depends on PCI @@ -513,6 +543,17 @@ config MISC_RTSX tristate default MISC_RTSX_PCI || MISC_RTSX_USB +config ALTERA_SYSID + tristate "Altera System ID" + help + This enables Altera System ID soft core driver. + +config ALTERA_ILC + tristate "Altera Interrupt Latency Counter driver" + help + This enables the Interrupt Latency Counter driver for the Altera + SOCFPGA platform. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index af22bbc3d00c..8df0ba395767 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -9,6 +9,8 @@ obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o obj-$(CONFIG_AD525X_DPOT_I2C) += ad525x_dpot-i2c.o obj-$(CONFIG_AD525X_DPOT_SPI) += ad525x_dpot-spi.o obj-$(CONFIG_INTEL_MID_PTI) += pti.o +obj-$(CONFIG_INTEL_SERVICE) += intel-service.o +obj-$(CONFIG_INTEL_RSU) += intel-rsu.o obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o obj-$(CONFIG_DUMMY_IRQ) += dummy-irq.o @@ -42,7 +44,10 @@ obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-y += lis3lv02d/ obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o -obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ +obj-$(CONFIG_ALTERA_STAPL) += altera-stapl/ +obj-$(CONFIG_ALTERA_HWMUTEX) += altera_hwmutex.o +obj-$(CONFIG_ALTERA_ILC) += altera_ilc.o +obj-$(CONFIG_ALTERA_SYSID) += altera_sysid.o obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o diff --git a/drivers/misc/altera_hwmutex.c b/drivers/misc/altera_hwmutex.c new file mode 100644 index 000000000000..5004fe92e94b --- /dev/null +++ b/drivers/misc/altera_hwmutex.c @@ -0,0 +1,320 @@ +/* + * Copyright Altera Corporation (C) 2013. All rights reserved + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/altera_hwmutex.h> + +#define DRV_NAME "altera_hwmutex" + + +static DEFINE_SPINLOCK(list_lock); /* protect mutex_list */ +static LIST_HEAD(mutex_list); + +/* Mutex Registers */ +#define MUTEX_REG 0x0 + +#define MUTEX_REG_VALUE_MASK 0xFFFF +#define MUTEX_REG_OWNER_OFFSET 16 +#define MUTEX_REG_OWNER_MASK 0xFFFF +#define MUTEX_GET_OWNER(reg) \ + ((reg >> MUTEX_REG_OWNER_OFFSET) & MUTEX_REG_OWNER_MASK) + +/** + * altera_mutex_request - Retrieves a pointer to an acquired mutex device + * structure + * @mutex_np: The pointer to mutex device node + * + * Returns a pointer to the mutex device structure associated with the + * supplied device node, or NULL if no corresponding mutex device was + * found. + */ +struct altera_mutex *altera_mutex_request(struct device_node *mutex_np) +{ + struct altera_mutex *mutex; + + spin_lock(&list_lock); + list_for_each_entry(mutex, &mutex_list, list) { + if (mutex_np == mutex->pdev->dev.of_node) { + if (!mutex->requested) { + mutex->requested = true; + spin_unlock(&list_lock); + return mutex; + } else { + pr_info("Mutex device is in use.\n"); + spin_unlock(&list_lock); + return NULL; + } + } + } + spin_unlock(&list_lock); + pr_info("Mutex device not found!\n"); + return NULL; +} +EXPORT_SYMBOL(altera_mutex_request); + +/** + * altera_mutex_free - Free the mutex + * @mutex: the mutex + * + * Return 0 if success. Otherwise, returns non-zero. + */ +int altera_mutex_free(struct altera_mutex *mutex) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + spin_lock(&list_lock); + mutex->requested = false; + spin_unlock(&list_lock); + + return 0; +} +EXPORT_SYMBOL(altera_mutex_free); + +static int __mutex_trylock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + u32 read; + int ret = 0; + u32 data = (owner << MUTEX_REG_OWNER_OFFSET) | value; + + mutex_lock(&mutex->lock); + __raw_writel(data, mutex->regs + MUTEX_REG); + read = __raw_readl(mutex->regs + MUTEX_REG); + if (read != data) + ret = -1; + + mutex_unlock(&mutex->lock); + return ret; +} + +/** + * altera_mutex_lock - Acquires a hardware mutex, wait until it can get it. + * @mutex: the mutex to be acquired + * @owner: owner ID + * @value: the new non-zero value to write to mutex + * + * Returns 0 if mutex was successfully locked. Otherwise, returns non-zero. + * + * The mutex must later on be released by the same owner that acquired it. + * This function is not ISR callable. + */ +int altera_mutex_lock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + while (__mutex_trylock(mutex, owner, value) != 0) + ; + + return 0; +} +EXPORT_SYMBOL(altera_mutex_lock); + +/** + * altera_mutex_trylock - Tries once to lock the hardware mutex and returns + * immediately + * @mutex: the mutex to be acquired + * @owner: owner ID + * @value: the new non-zero value to write to mutex + * + * Returns 0 if mutex was successfully locked. Otherwise, returns non-zero. + * + * The mutex must later on be released by the same owner that acquired it. + * This function is not ISR callable. + */ +int altera_mutex_trylock(struct altera_mutex *mutex, u16 owner, u16 value) +{ + if (!mutex || !mutex->requested) + return -EINVAL; + + return __mutex_trylock(mutex, owner, value); +} +EXPORT_SYMBOL(altera_mutex_trylock); + +/** + * altera_mutex_unlock - Unlock a mutex that has been locked by this owner + * previously that was locked on the + * altera_mutex_lock. Upon release, the value stored + * in the mutex is set to zero. + * @mutex: the mutex to be released + * @owner: Owner ID + * + * Returns 0 if mutex was successfully unlocked. Otherwise, returns + * non-zero. + * + * This function is not ISR callable. + */ +int altera_mutex_unlock(struct altera_mutex *mutex, u16 owner) +{ + u32 reg; + + if (!mutex || !mutex->requested) + return -EINVAL; + + mutex_lock(&mutex->lock); + + __raw_writel(owner << MUTEX_REG_OWNER_OFFSET, + mutex->regs + MUTEX_REG); + + reg = __raw_readl(mutex->regs + MUTEX_REG); + if (reg & MUTEX_REG_VALUE_MASK) { + /* Unlock failed */ + dev_dbg(&mutex->pdev->dev, + "Unlock mutex failed, owner %d and expected owner %d\n", + owner, MUTEX_GET_OWNER(reg)); + mutex_unlock(&mutex->lock); + return -EINVAL; + } + + mutex_unlock(&mutex->lock); + return 0; +} +EXPORT_SYMBOL(altera_mutex_unlock); + +/** + * altera_mutex_owned - Determines if this owner owns the mutex + * @mutex: the mutex to be queried + * @owner: Owner ID + * + * Returns 1 if the owner owns the mutex. Otherwise, returns zero. + */ +int altera_mutex_owned(struct altera_mutex *mutex, u16 owner) +{ + u32 reg; + u16 actual_owner; + int ret = 0; + + if (!mutex || !mutex->requested) + return ret; + + mutex_lock(&mutex->lock); + reg = __raw_readl(mutex->regs + MUTEX_REG); + actual_owner = MUTEX_GET_OWNER(reg); + if (actual_owner == owner) + ret = 1; + + mutex_unlock(&mutex->lock); + return ret; +} +EXPORT_SYMBOL(altera_mutex_owned); + +/** + * altera_mutex_is_locked - Determines if the mutex is locked + * @mutex: the mutex to be queried + * + * Returns 1 if the mutex is locked, 0 if unlocked. + */ +int altera_mutex_is_locked(struct altera_mutex *mutex) +{ + u32 reg; + int ret = 0; + + if (!mutex || !mutex->requested) + return ret; + + mutex_lock(&mutex->lock); + reg = __raw_readl(mutex->regs + MUTEX_REG); + reg &= MUTEX_REG_VALUE_MASK; + if (reg) + ret = 1; + + mutex_unlock(&mutex->lock); + return ret; +} +EXPORT_SYMBOL(altera_mutex_is_locked); + +static int altera_mutex_probe(struct platform_device *pdev) +{ + struct altera_mutex *mutex; + struct resource *regs; + + mutex = devm_kzalloc(&pdev->dev, sizeof(struct altera_mutex), + GFP_KERNEL); + if (!mutex) + return -ENOMEM; + + mutex->pdev = pdev; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + mutex->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(mutex->regs)) + return PTR_ERR(mutex->regs); + + mutex_init(&mutex->lock); + + spin_lock(&list_lock); + list_add_tail(&mutex->list, &mutex_list); + spin_unlock(&list_lock); + + platform_set_drvdata(pdev, mutex); + + return 0; +} + +static int altera_mutex_remove(struct platform_device *pdev) +{ + struct altera_mutex *mutex = platform_get_drvdata(pdev); + + spin_lock(&list_lock); + if (mutex) + list_del(&mutex->list); + spin_unlock(&list_lock); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_mutex_match[] = { + { .compatible = "altr,hwmutex-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_mutex_match); + +static struct platform_driver altera_mutex_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = altera_mutex_match, + }, + .remove = altera_mutex_remove, +}; + +static int __init altera_mutex_init(void) +{ + return platform_driver_probe(&altera_mutex_platform_driver, + altera_mutex_probe); +} + +static void __exit altera_mutex_exit(void) +{ + platform_driver_unregister(&altera_mutex_platform_driver); +} + +module_init(altera_mutex_init); +module_exit(altera_mutex_exit); + +MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera Hardware Mutex driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/altera_ilc.c b/drivers/misc/altera_ilc.c new file mode 100644 index 000000000000..adc78102c7d5 --- /dev/null +++ b/drivers/misc/altera_ilc.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2014 Altera Corporation. All rights reserved + * + * 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/device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kfifo.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> + +#define DRV_NAME "altera_ilc" +#define CTRL_REG 0x80 +#define FREQ_REG 0x84 +#define STP_REG 0x88 +#define VLD_REG 0x8C +#define ILC_MAX_PORTS 32 +#define ILC_FIFO_DEFAULT 32 +#define ILC_ENABLE 0x01 +#define CHAR_SIZE 10 +#define POLL_INTERVAL 1 +#define GET_PORT_COUNT(_val) ((_val & 0x7C) >> 2) +#define GET_VLD_BIT(_val, _offset) (((_val) >> _offset) & 0x1) + +struct altera_ilc { + struct platform_device *pdev; + void __iomem *regs; + unsigned int port_count; + unsigned int irq; + unsigned int channel_offset; + unsigned int interrupt_channels[ILC_MAX_PORTS]; + struct kfifo kfifos[ILC_MAX_PORTS]; + struct device_attribute dev_attr[ILC_MAX_PORTS]; + struct delayed_work ilc_work; + char sysfs[ILC_MAX_PORTS][CHAR_SIZE]; + u32 fifo_depth; +}; + +static int ilc_irq_lookup(struct altera_ilc *ilc, int irq) +{ + int i; + for (i = 0; i < ilc->port_count; i++) { + if (irq == platform_get_irq(ilc->pdev, i)) + return i; + } + return -EPERM; +} + +static ssize_t ilc_show_counter(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret, i, id, fifo_len; + unsigned int fifo_buf[ILC_MAX_PORTS]; + char temp[10]; + struct altera_ilc *ilc = dev_get_drvdata(dev); + + fifo_len = 0; + ret = kstrtouint(attr->attr.name, 0, &id); + + for (i = 0; i < ilc->port_count; i++) { + if (id == (ilc->interrupt_channels[i])) { + /*Check for kfifo length*/ + fifo_len = kfifo_len(&ilc->kfifos[i]) + /sizeof(unsigned int); + if (fifo_len <= 0) { + dev_info(&ilc->pdev->dev, "Fifo for interrupt %s is empty\n", + attr->attr.name); + return 0; + } + /*Read from kfifo*/ + ret = kfifo_out(&ilc->kfifos[i], &fifo_buf, + kfifo_len(&ilc->kfifos[i])); + } + } + + for (i = 0; i < fifo_len; i++) { + sprintf(temp, "%u\n", fifo_buf[i]); + strcat(buf, temp); + } + + strcat(buf, "\0"); + + return strlen(buf); +} + +static struct attribute *altera_ilc_attrs[ILC_MAX_PORTS]; + +struct attribute_group altera_ilc_attr_group = { + .name = "ilc_data", + .attrs = altera_ilc_attrs, +}; + +static void ilc_work(struct work_struct *work) +{ + unsigned int ilc_value, ret, offset, stp_reg; + struct altera_ilc *ilc = + container_of(work, struct altera_ilc, ilc_work.work); + + offset = ilc_irq_lookup(ilc, ilc->irq); + if (offset < 0) { + dev_err(&ilc->pdev->dev, "Unable to lookup irq number\n"); + return; + } + + if (GET_VLD_BIT(readl(ilc->regs + VLD_REG), offset)) { + /*Read counter register*/ + ilc_value = readl(ilc->regs + (offset) * 4); + + /*Putting value into kfifo*/ + ret = kfifo_in((&ilc->kfifos[offset]), + (unsigned int *)&ilc_value, sizeof(ilc_value)); + + /*Clearing stop register*/ + stp_reg = readl(ilc->regs + STP_REG); + writel((!(0x1 << offset))&stp_reg, ilc->regs + STP_REG); + + return; + } + + /*Start workqueue to poll data valid*/ + schedule_delayed_work(&ilc->ilc_work, msecs_to_jiffies(POLL_INTERVAL)); +} + +static irqreturn_t ilc_interrupt_handler(int irq, void *p) +{ + unsigned int offset, stp_reg; + + struct altera_ilc *ilc = (struct altera_ilc *)p; + + /*Update ILC struct*/ + ilc->irq = irq; + + dev_dbg(&ilc->pdev->dev, "Interrupt %u triggered\n", + ilc->irq); + + offset = ilc_irq_lookup(ilc, irq); + if (offset < 0) { + dev_err(&ilc->pdev->dev, "Unable to lookup irq number\n"); + return IRQ_RETVAL(IRQ_NONE); + } + + /*Setting stop register*/ + stp_reg = readl(ilc->regs + STP_REG); + writel((0x1 << offset)|stp_reg, ilc->regs + STP_REG); + + /*Start workqueue to poll data valid*/ + schedule_delayed_work(&ilc->ilc_work, 0); + + return IRQ_RETVAL(IRQ_NONE); +} + +static int altera_ilc_probe(struct platform_device *pdev) +{ + struct altera_ilc *ilc; + struct resource *regs; + struct device_node *np = pdev->dev.of_node; + int ret, i; + + ilc = devm_kzalloc(&pdev->dev, sizeof(struct altera_ilc), + GFP_KERNEL); + if (!ilc) + return -ENOMEM; + + ilc->pdev = pdev; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + ilc->regs = devm_ioremap_resource(&pdev->dev, regs); + if (!ilc->regs) + return -EADDRNOTAVAIL; + + ilc->port_count = GET_PORT_COUNT(readl(ilc->regs + CTRL_REG)); + if (ilc->port_count <= 0) { + dev_warn(&pdev->dev, "No interrupt connected to ILC\n"); + return -EPERM; + } + + /*Check for fifo depth*/ + ret = of_property_read_u32(np, "altr,sw-fifo-depth", + &(ilc->fifo_depth)); + if (ret) { + dev_warn(&pdev->dev, "Fifo depth undefined\n"); + dev_warn(&pdev->dev, "Setting fifo depth to default value (32)\n"); + ilc->fifo_depth = ILC_FIFO_DEFAULT; + } + + /*Initialize Kfifo*/ + for (i = 0; i < ilc->port_count; i++) { + ret = kfifo_alloc(&ilc->kfifos[i], (ilc->fifo_depth * + sizeof(unsigned int)), GFP_KERNEL); + if (ret) { + dev_err(&pdev->dev, "Kfifo failed to initialize\n"); + return ret; + } + } + + /*Register each of the IRQs*/ + for (i = 0; i < ilc->port_count; i++) { + ilc->interrupt_channels[i] = platform_get_irq(pdev, i); + + ret = devm_request_irq(&pdev->dev, (ilc->interrupt_channels[i]), + ilc_interrupt_handler, IRQF_SHARED, "ilc_0", + (void *)(ilc)); + + if (ret < 0) + dev_warn(&pdev->dev, "Failed to register interrupt handler"); + } + + /*Setup sysfs interface*/ + for (i = 0; (i < ilc->port_count); i++) { + sprintf(ilc->sysfs[i], "%d", (ilc->interrupt_channels[i])); + ilc->dev_attr[i].attr.name = ilc->sysfs[i]; + ilc->dev_attr[i].attr.mode = S_IRUGO; + ilc->dev_attr[i].show = ilc_show_counter; + altera_ilc_attrs[i] = &ilc->dev_attr[i].attr; + altera_ilc_attrs[i+1] = NULL; + } + ret = sysfs_create_group(&pdev->dev.kobj, &altera_ilc_attr_group); + + /*Initialize workqueue*/ + INIT_DELAYED_WORK(&ilc->ilc_work, ilc_work); + + /*Global enable ILC softIP*/ + writel(ILC_ENABLE, ilc->regs + CTRL_REG); + + platform_set_drvdata(pdev, ilc); + + dev_info(&pdev->dev, "Driver successfully loaded\n"); + + return 0; +} + +static int altera_ilc_remove(struct platform_device *pdev) +{ + int i; + struct altera_ilc *ilc = platform_get_drvdata(pdev); + + /*Remove sysfs interface*/ + sysfs_remove_group(&pdev->dev.kobj, &altera_ilc_attr_group); + + /*Free up kfifo memory*/ + for (i = 0; i < ilc->port_count; i++) + kfifo_free(&ilc->kfifos[i]); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_ilc_match[] = { + { .compatible = "altr,ilc-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_ilc_match); + +static struct platform_driver altera_ilc_platform_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_ilc_match), + }, + .remove = altera_ilc_remove, +}; + +static int __init altera_ilc_init(void) +{ + return platform_driver_probe(&altera_ilc_platform_driver, + altera_ilc_probe); +} + +static void __exit altera_ilc_exit(void) +{ + platform_driver_unregister(&altera_ilc_platform_driver); +} + +module_init(altera_ilc_init); +module_exit(altera_ilc_exit); + +MODULE_AUTHOR("Chee Nouk Phoon <cnphoon@altera.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera Interrupt Latency Counter Driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/altera_sysid.c b/drivers/misc/altera_sysid.c new file mode 100644 index 000000000000..be35530c504b --- /dev/null +++ b/drivers/misc/altera_sysid.c @@ -0,0 +1,141 @@ +/* + * Copyright Altera Corporation (C) 2013. + * + * 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/>. + * + * Credit: + * Walter Goossens + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> + +#define DRV_NAME "altera_sysid" + +struct altera_sysid { + void __iomem *regs; +}; + +/* System ID Registers*/ +#define SYSID_REG_ID (0x0) +#define SYSID_REG_TIMESTAMP (0x4) + +static ssize_t altera_sysid_show_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct altera_sysid *sysid = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", readl(sysid->regs + SYSID_REG_ID)); +} + +static ssize_t altera_sysid_show_timestamp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int reg; + struct tm timestamp; + struct altera_sysid *sysid = dev_get_drvdata(dev); + + reg = readl(sysid->regs + SYSID_REG_TIMESTAMP); + + time_to_tm(reg, 0, ×tamp); + + return sprintf(buf, "%u (%u-%u-%u %u:%u:%u UTC)\n", reg, + (unsigned int)(timestamp.tm_year + 1900), + timestamp.tm_mon + 1, timestamp.tm_mday, timestamp.tm_hour, + timestamp.tm_min, timestamp.tm_sec); +} + +static DEVICE_ATTR(id, S_IRUGO, altera_sysid_show_id, NULL); +static DEVICE_ATTR(timestamp, S_IRUGO, altera_sysid_show_timestamp, NULL); + +static struct attribute *altera_sysid_attrs[] = { + &dev_attr_id.attr, + &dev_attr_timestamp.attr, + NULL, +}; + +struct attribute_group altera_sysid_attr_group = { + .name = "sysid", + .attrs = altera_sysid_attrs, +}; + +static int altera_sysid_probe(struct platform_device *pdev) +{ + struct altera_sysid *sysid; + struct resource *regs; + + sysid = devm_kzalloc(&pdev->dev, sizeof(struct altera_sysid), + GFP_KERNEL); + if (!sysid) + return -ENOMEM; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) + return -ENXIO; + + sysid->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(sysid->regs)) + return PTR_ERR(sysid->regs); + + platform_set_drvdata(pdev, sysid); + + return sysfs_create_group(&pdev->dev.kobj, &altera_sysid_attr_group); +} + +static int altera_sysid_remove(struct platform_device *pdev) +{ + sysfs_remove_group(&pdev->dev.kobj, &altera_sysid_attr_group); + + platform_set_drvdata(pdev, NULL); + return 0; +} + +static const struct of_device_id altera_sysid_match[] = { + { .compatible = "altr,sysid-1.0" }, + { /* Sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, altera_sysid_match); + +static struct platform_driver altera_sysid_platform_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(altera_sysid_match), + }, + .probe = altera_sysid_probe, + .remove = altera_sysid_remove, +}; + +static int __init altera_sysid_init(void) +{ + return platform_driver_register(&altera_sysid_platform_driver); +} + +static void __exit altera_sysid_exit(void) +{ + platform_driver_unregister(&altera_sysid_platform_driver); +} + +module_init(altera_sysid_init); +module_exit(altera_sysid_exit); + +MODULE_AUTHOR("Ley Foon Tan <lftan@altera.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Altera System ID driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/misc/intel-rsu.c b/drivers/misc/intel-rsu.c new file mode 100644 index 000000000000..94b85416f882 --- /dev/null +++ b/drivers/misc/intel-rsu.c @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Intel Corporation + */ + +/* + * This driver exposes some optional features of the Intel Stratix 10 SoC FPGA. + * The SysFS interfaces exposed here are FPGA Remote System Update (RSU) + * related. They allow user space software to query the configuration system + * status and to request optional reboot behavior specific to Intel FPGAs. + */ + +#include <linux/arm-smccc.h> +#include <linux/completion.h> +#include <linux/intel-service-client.h> +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/sysfs.h> + +#define MAX_U64_STR_LEN 22 + +/* + * Private data structure + */ +struct intel_rsu_priv { + struct intel_svc_chan *chan; + struct intel_svc_client client; + struct completion svc_completion; + struct { + unsigned long current_image; + unsigned long fail_image; + unsigned int version; + unsigned int state; + unsigned int error_details; + unsigned int error_location; + } status; +}; + +/* + * status_svc_callback() - Callback from intel-service layer that returns SMC + * response with RSU status data. Parses up data and + * update driver private data structure. + * client - returned context from intel-service layer + * data - SMC response data + */ +static void status_svc_callback(struct intel_svc_client *client, + struct intel_svc_c_data *data) +{ + struct intel_rsu_priv *priv = client->priv; + struct arm_smccc_res *res = (struct arm_smccc_res *)data->kaddr1; + + if (data->status == BIT(SVC_STATUS_RSU_OK)) { + priv->status.version = + (unsigned int)(res->a2 >> 32) & 0xFFFFFFFF; + priv->status.state = (unsigned int)res->a2 & 0xFFFFFFFF; + priv->status.fail_image = res->a1; + priv->status.current_image = res->a0; + priv->status.error_location = + (unsigned int)res->a3 & 0xFFFFFFFF; + priv->status.error_details = + (unsigned int)(res->a3 >> 32) & 0xFFFFFFFF; + } else { + dev_err(client->dev, "COMMAND_RSU_STATUS returned 0x%lX\n", + res->a0); + priv->status.version = 0; + priv->status.state = 0; + priv->status.fail_image = 0; + priv->status.current_image = 0; + priv->status.error_location = 0; + priv->status.error_details = 0; + } + + complete(&priv->svc_completion); +} + +/* + * get_status() - Start an intel-service layer transaction to perform the SMC + * that is necessary to get RSU status information. Wait for + * completion and timeout if needed. + * priv - driver private data + * + * Returns 0 on success + */ +static int get_status(struct intel_rsu_priv *priv) +{ + struct intel_svc_client_msg msg; + int ret; + unsigned long timeout; + + reinit_completion(&priv->svc_completion); + priv->client.receive_cb = status_svc_callback; + + msg.command = COMMAND_RSU_STATUS; + ret = intel_svc_send(priv->chan, &msg); + if (ret < 0) + goto status_done; + + timeout = msecs_to_jiffies(SVC_RSU_REQUEST_TIMEOUT_MS); + ret = + wait_for_completion_interruptible_timeout(&priv->svc_completion, + timeout); + if (!ret) { + dev_err(priv->client.dev, + "timeout waiting for COMMAND_RSU_STATUS\n"); + ret = -ETIMEDOUT; + goto status_done; + } + if (ret < 0) { + dev_err(priv->client.dev, + "error (%d) waiting for COMMAND_RSU_STATUS\n", ret); + goto status_done; + } + + ret = 0; + +status_done: + intel_svc_done(priv->chan); + return ret; +} + +/* current_image_show() - DEVICE_ATTR callback to show current_image status */ +static ssize_t current_image_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%ld", priv->status.current_image); +} + +/* fail_image_show() - DEVICE_ATTR callback to show fail_image status */ +static ssize_t fail_image_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%ld", priv->status.fail_image); +} + +/* version_show() - DEVICE_ATTR callback to show version status */ +static ssize_t version_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%d", priv->status.version); +} + +/* state_show() - DEVICE_ATTR callback to show state status */ +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%d", priv->status.state); +} + +/* error_location_show() - DEVICE_ATTR callback to show error_location status */ +static ssize_t error_location_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%d", priv->status.error_location); +} + +/* error_details_show() - DEVICE_ATTR callback to show error_details status */ +static ssize_t error_details_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + + if (!priv) + return -ENODEV; + + return scnprintf(buf, PAGE_SIZE, "%d", priv->status.error_details); +} + +/* + * update_svc_callback() - Callback from intel-service layer that returns SMC + * response from RSU update. Checks for success/fail. + * client - returned context from intel-service layer + * data - SMC repsonse data + */ +static void update_svc_callback(struct intel_svc_client *client, + struct intel_svc_c_data *data) +{ + struct intel_rsu_priv *priv = client->priv; + + if (data->status != BIT(SVC_STATUS_RSU_OK)) + dev_err(client->dev, "COMMAND_RSU_UPDATE returned %i\n", + data->status); + + complete(&priv->svc_completion); +} + +/* + * send_update() - Start an intel-service layer transaction to perform the SMC + * that is necessary to send an RSU update request. Wait for + * completion and timeout if needed. + * priv - driver private data + * + * Returns 0 on success + */ +static int send_update(struct intel_rsu_priv *priv, + unsigned long address) +{ + struct intel_svc_client_msg msg; + int ret; + unsigned long timeout; + + reinit_completion(&priv->svc_completion); + priv->client.receive_cb = update_svc_callback; + + msg.command = COMMAND_RSU_UPDATE; + msg.arg[0] = address; + + ret = intel_svc_send(priv->chan, &msg); + if (ret < 0) + goto update_done; + + timeout = msecs_to_jiffies(SVC_RSU_REQUEST_TIMEOUT_MS); + ret = wait_for_completion_interruptible_timeout(&priv->svc_completion, + timeout); + if (!ret) { + dev_err(priv->client.dev, + "timeout waiting for COMMAND_RSU_UPDATE\n"); + ret = -ETIMEDOUT; + goto update_done; + } + if (ret < 0) { + dev_err(priv->client.dev, + "error (%d) waiting for COMMAND_RSU_UPDATE\n", ret); + goto update_done; + } + + ret = 0; + +update_done: + intel_svc_done(priv->chan); + return ret; +} + +/* reboot_image_store() - DEVICE_ATTR callback to store reboot_image request */ +static ssize_t reboot_image_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct intel_rsu_priv *priv = dev_get_drvdata(dev); + unsigned long address; + int ret; + + if (priv == 0) + return -ENODEV; + + /* Ensure the input buffer is null terminated and not too long */ + if (strnlen(buf, MAX_U64_STR_LEN) == MAX_U64_STR_LEN) + return -EINVAL; + + ret = kstrtoul(buf, 10, &address); + if (ret) + return ret; + + send_update(priv, address); + + return count; +} + +/* + * Attribute structures + */ + +static DEVICE_ATTR_RO(current_image); +static DEVICE_ATTR_RO(fail_image); +static DEVICE_ATTR_RO(state); +static DEVICE_ATTR_RO(version); +static DEVICE_ATTR_RO(error_location); +static DEVICE_ATTR_RO(error_details); +static DEVICE_ATTR_WO(reboot_image); + +static struct attribute *attrs[] = { + &dev_attr_current_image.attr, + &dev_attr_fail_image.attr, + &dev_attr_state.attr, + &dev_attr_version.attr, + &dev_attr_error_location.attr, + &dev_attr_error_details.attr, + &dev_attr_reboot_image.attr, + NULL +}; + +static struct attribute_group attr_group = { + .attrs = attrs +}; + +static int intel_rsu_probe(struct platform_device *pdev) +{ + int ret; + struct device *dev = &pdev->dev; + struct intel_rsu_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client.dev = dev; + priv->client.receive_cb = update_svc_callback; + priv->client.priv = priv; + + priv->status.current_image = 0; + priv->status.fail_image = 0; + priv->status.error_location = 0; + priv->status.error_details = 0; + priv->status.version = 0; + priv->status.state = 0; + + priv->chan = request_svc_channel_byname(&priv->client, SVC_CLIENT_RSU); + if (IS_ERR(priv->chan)) { + dev_err(dev, "couldn't get service channel (%s)\n", + SVC_CLIENT_RSU); + return PTR_ERR(priv->chan); + } + + init_completion(&priv->svc_completion); + + platform_set_drvdata(pdev, priv); + + ret = get_status(priv); + if (ret) { + dev_err(dev, "Error getting RSU status (%i)\n", ret); + free_svc_channel(priv->chan); + return ret; + } + + ret = sysfs_create_group(&dev->kobj, &attr_group); + if (ret) + free_svc_channel(priv->chan); + + return ret; +} + +static int intel_rsu_remove(struct platform_device *pdev) +{ + struct intel_rsu_priv *priv = platform_get_drvdata(pdev); + + free_svc_channel(priv->chan); + + return 0; +} + +static const struct of_device_id intel_rsu_of_match[] = { + {.compatible = "intel,stratix10-rsu",}, + {}, +}; +MODULE_DEVICE_TABLE(of, intel_rsu_of_match); + +static struct platform_driver intel_rsu_driver = { + .probe = intel_rsu_probe, + .remove = intel_rsu_remove, + .driver = { + .name = "intel-rsu", + .of_match_table = intel_rsu_of_match, + }, +}; + +module_platform_driver(intel_rsu_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Remote System Update SysFS Driver"); +MODULE_AUTHOR("David Koltak <david.koltak@linux.intel.com>"); diff --git a/drivers/misc/intel-service.c b/drivers/misc/intel-service.c new file mode 100644 index 000000000000..ea32db718ca3 --- /dev/null +++ b/drivers/misc/intel-service.c @@ -0,0 +1,1042 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2018, Intel Corporation + */ + +/* + * Intel Stratix10 SoC is composed of a 64 bit quad-core ARM Cortex A53 hard + * processor system (HPS) and Secure Device Manager (SDM). SDM is the + * hardware which does the FPGA configuration, QSPI, Crypto and warm reset. + * + * When the FPGA is configured from HPS, there needs to be a way for HPS to + * notify SDM the location and size of the configuration data. Then SDM will + * get the configuration data from that location and perform the FPGA + * configuration. + * + * To meet the whole system security needs and support virtual machine + * requesting communication with SDM, only the secure world of software (EL3, + * Exception Level 3) can interface with SDM. All software entities running + * on other exception levels must channel through the EL3 software whenever + * it needs service from SDM. + * + * Intel Stratix10 service layer driver is added to provide the service for + * FPGA configuration. Running at privileged exception level (EL1, Exception + * Level 1), Intel Stratix10 service layer driver interfaces with the service + * client at EL1 (Intel Stratix10 FPGA Manager) and manages secure monitor + * call (SMC) to communicate with secure monitor software at secure monitor + * exception level (EL3). + */ + +#include <linux/arm-smccc.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/genalloc.h> +#include <linux/intel-service-client.h> +#include <linux/io.h> +#include <linux/kfifo.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include "intel-smc.h" + +/* SVC_NUM_DATA_IN_FIFO - number of struct intel_svc_data in the FIFO */ +#define SVC_NUM_DATA_IN_FIFO 32 +/* SVC_NUM_CHANNEL - number of channel supported by service layer driver */ +#define SVC_NUM_CHANNEL 2 +/* + * FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS - claim back the submitted buffer(s) + * from the secure world for FPGA manager to reuse, or to free the buffer(s) + * when all bit-stream data had be send. + */ +#define FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS 200 +/* + * FPGA_CONFIG_STATUS_TIMEOUT_SEC - poll the FPGA configuration status, + * service layer will return error to FPGA manager when timeout occurs, + * timeout is set to 30 seconds (30 * 1000) at Intel Stratix10 SoC. + */ +#define FPGA_CONFIG_STATUS_TIMEOUT_SEC 30 + +typedef void (svc_invoke_fn)(unsigned long, unsigned long, unsigned long, + unsigned long, unsigned long, unsigned long, + unsigned long, unsigned long, + struct arm_smccc_res *); +static int svc_normal_to_secure_thread(void *data); +struct intel_svc_chan; + +/** + * struct intel_svc_sh_memory - service shared memory structure + * @sync_complete: state for a completion + * @addr: physical address of shared memory block + * @size: size of shared memory block + * @invoke_fn: function to issue secure monitor or hypervisor call + * + * This struct is used to save physical address and size of shared memory + * block. The shared memory blocked is allocated by secure monitor software + * at secure world. + * + * Service layer driver uses the physical address and size to create a memory + * pool, then allocates data buffer from that memory pool for service client. + */ +struct intel_svc_sh_memory { + struct completion sync_complete; + unsigned long addr; + unsigned long size; + svc_invoke_fn *invoke_fn; +}; + +/** + * struct intel_svc_data_mem - service memory structure + * @vaddr: virtual address + * @paddr: physical address + * @size: size of memory + * @node: link list head node + * + * This struct is used in a list that keeps track of buffers which have + * been allocated or freed from the memory pool. Service layer driver also + * uses this struct to transfer physical address to virtual address. + */ +struct intel_svc_data_mem { + void *vaddr; + phys_addr_t paddr; + size_t size; + struct list_head node; +}; + +/** + * struct intel_svc_data - service data structure + * @chan: service channel + * @paddr: playload physical address + * @size: playload size + * @command: service command requested by client + * @arg[3]: args to be passed via registers and not physically mapped buffers + * This struct is used in service FIFO for inter-process communication. + */ +struct intel_svc_data { + struct intel_svc_chan *chan; + phys_addr_t paddr; + size_t size; + u32 command; + u64 arg[3]; +}; + +/** + * struct intel_svc_controller - service controller + * @dev: device + * @chans: array of service channels + * $num_chans: number of channels in 'chans' array + * @num_active_client: number of active service client + * @node: list management + * @genpool: memory pool pointing to the memory region + * @task: pointer to the thread task which handles SMC or HVC call + * @svc_fifo: a queue for storing service message data + * @complete_status: state for completion + * @svc_fifo_lock: protect access to service message data queue + * @invoke_fn: function to issue secure monitor call or hypervisor call + * + * This struct is used to create communication channels for service clients, to + * handle secure monitor or hypervisor call. + */ +struct intel_svc_controller { + struct device *dev; + struct intel_svc_chan *chans; + int num_chans; + int num_active_client; + struct list_head node; + struct gen_pool *genpool; + struct task_struct *task; + struct kfifo svc_fifo; + struct completion complete_status; + spinlock_t svc_fifo_lock; + svc_invoke_fn *invoke_fn; +}; + +/** + * struct intel_svc_chan - service communication channel + * @ctrl: pointer to service controller which is the provider of this channel + * @scl: pointer to service client which owns the channel + * @name: service client name associated with the channel + * @lock: protect access to the channel + * + * This struct is used by service client to communicate with service layer, each + * service client has its own channel created by service controller. + */ +struct intel_svc_chan { + struct intel_svc_controller *ctrl; + struct intel_svc_client *scl; + char *name; + spinlock_t lock; +}; + +static LIST_HEAD(svc_ctrl); +static LIST_HEAD(svc_data_mem); + +/** + * request_svc_channel_byname() - request a service channel + * @client: pointer to service client + * @name: service client name + * + * This function is used by service client to request a service channel. + * + * Return: a pointer to channel assigned to the client on success, + * or ERR_PTR() on error. + */ +struct intel_svc_chan *request_svc_channel_byname( + struct intel_svc_client *client, const char *name) +{ + struct device *dev = client->dev; + struct intel_svc_controller *controller; + struct intel_svc_chan *chan = NULL; + unsigned long flag; + int i; + + /* if probe was called after client's, or error on probe */ + if (list_empty(&svc_ctrl)) + return ERR_PTR(-EPROBE_DEFER); + + controller = list_first_entry(&svc_ctrl, + struct intel_svc_controller, node); + for (i = 0; i < SVC_NUM_CHANNEL; i++) { + if (!strcmp(controller->chans[i].name, name)) { + chan = &controller->chans[i]; + break; + } + } + + /* if there was no channel match */ + if (i == SVC_NUM_CHANNEL) { + dev_err(dev, "%s: channel not allocated\n", __func__); + return ERR_PTR(-EINVAL); + } + + if (chan->scl || !try_module_get(controller->dev->driver->owner)) { + dev_dbg(dev, "%s: svc not free\n", __func__); + return ERR_PTR(-EBUSY); + } + + spin_lock_irqsave(&chan->lock, flag); + chan->scl = client; + chan->ctrl->num_active_client++; + spin_unlock_irqrestore(&chan->lock, flag); + + return chan; +} +EXPORT_SYMBOL_GPL(request_svc_channel_byname); + +/** + * free_svc_channel() - free service channel + * @chan: service channel to be freed + * + * This function is used by service client to free a service channel. + */ +void free_svc_channel(struct intel_svc_chan *chan) +{ + unsigned long flag; + + spin_lock_irqsave(&chan->lock, flag); + chan->scl = NULL; + chan->ctrl->num_active_client--; + module_put(chan->ctrl->dev->driver->owner); + spin_unlock_irqrestore(&chan->lock, flag); +} +EXPORT_SYMBOL_GPL(free_svc_channel); + +/** + * intel_svc_send() - send a message data to the remote + * @chan: service channel assigned to the client + * @msg: message data to be sent, in the format of "struct intel_svc_client_msg" + * + * This function is used by service client to send command or data to service + * layer driver. + * + * Return: non-negative value for successful submission to the data queue + * created by service layer driver, or negative value on error. + */ +int intel_svc_send(struct intel_svc_chan *chan, void *msg) +{ + struct intel_svc_client_msg *p_msg = (struct intel_svc_client_msg *)msg; + struct intel_svc_data_mem *p_mem; + struct intel_svc_data *p_data; + int ret = 0; + unsigned int cpu = 0; + + p_data = kmalloc(sizeof(*p_data), GFP_KERNEL); + if (!p_data) + return -ENOMEM; + + /* first client will create kernel thread */ + if (!chan->ctrl->task) { + chan->ctrl->task = + kthread_create_on_node(svc_normal_to_secure_thread, + (void *)chan->ctrl, + cpu_to_node(cpu), + "svc_smc_hvc_thread"); + if (IS_ERR(chan->ctrl->task)) { + dev_err(chan->ctrl->dev, + "fails to create svc_smc_hvc_thread\n"); + kfree(p_data); + return -EINVAL; + } + kthread_bind(chan->ctrl->task, cpu); + wake_up_process(chan->ctrl->task); + } + + pr_debug("%s: sent P-va=%p, P-com=%x, P-size=%u\n", __func__, + p_msg->payload, p_msg->command, + (unsigned int)p_msg->payload_length); + + list_for_each_entry(p_mem, &svc_data_mem, node) + if (p_mem->vaddr == p_msg->payload) { + p_data->paddr = p_mem->paddr; + break; + } + + p_data->command = p_msg->command; + p_data->arg[0] = p_msg->arg[0]; + p_data->arg[1] = p_msg->arg[1]; + p_data->arg[2] = p_msg->arg[2]; + p_data->size = p_msg->payload_length; + p_data->chan = chan; + pr_debug("%s: put to FIFO pa=0x%016x, cmd=%x, size=%u\n", __func__, + (unsigned int)p_data->paddr, p_data->command, + (unsigned int)p_data->size); + ret = kfifo_in_spinlocked(&chan->ctrl->svc_fifo, p_data, + sizeof(*p_data), + &chan->ctrl->svc_fifo_lock); + wake_up_process(chan->ctrl->task); + + kfree(p_data); + + if (!ret) + return -ENOBUFS; + + return ret; +} +EXPORT_SYMBOL_GPL(intel_svc_send); + +/** + * intel_svc_done() - complete service request transactions + * @chan: service channel assigned to the client + * + * This function should be called when client has finished its request + * or there is an error in the request process. It allows the service layer + * to stop the running thread to have maximize savings in kernel resources. + */ +void intel_svc_done(struct intel_svc_chan *chan) +{ + /* stop thread when thread is running AND only one active client */ + if (chan->ctrl->task && (chan->ctrl->num_active_client <= 1)) { + pr_debug("svc_smc_hvc_shm_thread is stopped\n"); + kthread_stop(chan->ctrl->task); + chan->ctrl->task = NULL; + } +} +EXPORT_SYMBOL_GPL(intel_svc_done); + +/** + * intel_svc_allocate_memory() - allocate memory + * @chan: service channel assigned to the client + * @size: memory size requested by a specific service client + * + * Service layer allocates the requested number of bytes buffer from the + * memory pool, service client uses this function to get allocated buffers. + * + * Return: address of allocated memory on success, or ERR_PTR() on error. + */ +void *intel_svc_allocate_memory(struct intel_svc_chan *chan, size_t size) +{ + struct intel_svc_data_mem *pmem; + unsigned long va; + phys_addr_t pa; + struct gen_pool *genpool = chan->ctrl->genpool; + size_t s = roundup(size, 1 << genpool->min_alloc_order); + + pmem = devm_kzalloc(chan->ctrl->dev, sizeof(*pmem), GFP_KERNEL); + if (!pmem) + return ERR_PTR(-ENOMEM); + + va = gen_pool_alloc(genpool, s); + if (!va) + return ERR_PTR(-ENOMEM); + + memset((void *)va, 0, s); + pa = gen_pool_virt_to_phys(genpool, va); + + pmem->vaddr = (void *)va; + pmem->paddr = pa; + pmem->size = s; + list_add_tail(&pmem->node, &svc_data_mem); + pr_debug("%s: va=%p, pa=0x%016x\n", __func__, + pmem->vaddr, (unsigned int)pmem->paddr); + + return (void *)va; +} +EXPORT_SYMBOL_GPL(intel_svc_allocate_memory); + +/** + * intel_svc_free_memory() - free allocated memory + * @chan: service channel assigned to the client + * @kaddr: memory to be freed + * + * This function is used by service client to free allocated buffers. + */ +void intel_svc_free_memory(struct intel_svc_chan *chan, void *kaddr) +{ + struct intel_svc_data_mem *pmem; + size_t size = 0; + + list_for_each_entry(pmem, &svc_data_mem, node) + if (pmem->vaddr == kaddr) { + size = pmem->size; + break; + } + + gen_pool_free(chan->ctrl->genpool, (unsigned long)kaddr, size); + pmem->vaddr = NULL; + list_del(&pmem->node); +} +EXPORT_SYMBOL_GPL(intel_svc_free_memory); + +/** + * svc_pa_to_va() - translate physical address to virtual address + * @addr: to be translated physical address + * + * Return: valid virtual address or NULL if the provided physical + * address doesn't exist. + */ +static void *svc_pa_to_va(unsigned long addr) +{ + struct intel_svc_data_mem *pmem; + + pr_debug("claim back P-addr=0x%016x\n", (unsigned int)addr); + list_for_each_entry(pmem, &svc_data_mem, node) + if (pmem->paddr == addr) + return pmem->vaddr; + + /* physical address is not found */ + return NULL; +} + +/** + * svc_thread_cmd_data_claim() - claim back buffer from the secure world + * @addr: pointer to service layer controller + * @p_data: pointer to service data structure + * @c_data: pointer to callback data structure to service client + * + * Claim back the submitted buffers from the secure world and pass buffer + * back to service client (FPGA manager, etc) for reuse. + */ +static void svc_thread_cmd_data_claim(struct intel_svc_controller *ctrl, + struct intel_svc_data *p_data, + struct intel_svc_c_data *c_data) +{ + struct arm_smccc_res res; + unsigned long timeout; + + reinit_completion(&ctrl->complete_status); + timeout = msecs_to_jiffies(FPGA_CONFIG_DATA_CLAIM_TIMEOUT_MS); + + pr_debug("%s: claim back the submitted buffer\n", __func__); + do { + ctrl->invoke_fn(INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE, + 0, 0, 0, 0, 0, 0, 0, &res); + + if (res.a0 == INTEL_SIP_SMC_STATUS_OK) { + if (!res.a1) { + complete(&ctrl->complete_status); + break; + } + c_data->status = BIT(SVC_STATUS_RECONFIG_BUFFER_DONE); + c_data->kaddr1 = svc_pa_to_va(res.a1); + c_data->kaddr2 = (res.a2) ? svc_pa_to_va(res.a2) : NULL; + c_data->kaddr3 = (res.a3) ? svc_pa_to_va(res.a3) : NULL; + p_data->chan->scl->receive_cb(p_data->chan->scl, + c_data); + } else { + pr_debug("%s: secure world busy, polling again\n", + __func__); + } + } while (res.a0 == INTEL_SIP_SMC_STATUS_OK || + res.a0 == INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY || + wait_for_completion_timeout(&ctrl->complete_status, timeout)); +} + +/** + * svc_thread_cmd_config_status() - check configuration status + * @ctrl: pointer to service layer controller + * @p_data: pointer to service data structure + * @c_data: pointer to callback data structure to service client + * + * Check whether the secure firmware at secure world has finished the FPGA + * configuration, and then inform FPGA manager the configuration status. + */ +static void svc_thread_cmd_config_status(struct intel_svc_controller *ctrl, + struct intel_svc_data *p_data, + struct intel_svc_c_data *c_data) +{ + struct arm_smccc_res res; + int count_in_sec; + + c_data->kaddr1 = NULL; + c_data->kaddr2 = NULL; + c_data->kaddr3 = NULL; + c_data->status = BIT(SVC_STATUS_RECONFIG_ERROR); + + pr_debug("%s: polling config status\n", __func__); + + count_in_sec = FPGA_CONFIG_STATUS_TIMEOUT_SEC; + while (count_in_sec) { + ctrl->invoke_fn(INTEL_SIP_SMC_FPGA_CONFIG_ISDONE, + 0, 0, 0, 0, 0, 0, 0, &res); + if ((res.a0 == INTEL_SIP_SMC_STATUS_OK) || + (res.a0 == INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR)) + break; + + /* + * configuration is still in progress, wait one second then + * poll again + */ + msleep(1000); + count_in_sec--; + }; + + if (res.a0 == INTEL_SIP_SMC_STATUS_OK && count_in_sec) + c_data->status = BIT(SVC_STATUS_RECONFIG_COMPLETED); + + p_data->chan->scl->receive_cb(p_data->chan->scl, c_data); +} + +/** + * svc_thread_recv_status_ok() - handle the successful status + * @p_data: pointer to service data structure + * @c_data: pointer to callback data structure to service client + * @res: result from SMC or HVC call + * + * Send back the correspond status to the service client (FPGA manager etc). + */ +static void svc_thread_recv_status_ok(struct intel_svc_data *p_data, + struct intel_svc_c_data *c_data, + struct arm_smccc_res res) +{ + c_data->kaddr1 = NULL; + c_data->kaddr2 = NULL; + c_data->kaddr3 = NULL; + + switch (p_data->command) { + case COMMAND_RECONFIG: + c_data->status = BIT(SVC_STATUS_RECONFIG_REQUEST_OK); + break; + case COMMAND_RECONFIG_DATA_SUBMIT: + c_data->status = BIT(SVC_STATUS_RECONFIG_BUFFER_SUBMITTED); + break; + case COMMAND_NOOP: + c_data->status = BIT(SVC_STATUS_RECONFIG_BUFFER_SUBMITTED); + c_data->kaddr1 = svc_pa_to_va(res.a1); + break; + case COMMAND_RECONFIG_STATUS: + c_data->status = BIT(SVC_STATUS_RECONFIG_COMPLETED); + break; + default: + break; + } + + pr_debug("%s: call receive_cb\n", __func__); + p_data->chan->scl->receive_cb(p_data->chan->scl, c_data); +} + +/** + * svc_normal_to_secure_thread() - the function to run in the kthread + * @data: data pointer for kthread function + * + * Service layer driver creates intel_svc_smc_hvc_call kthread on CPU + * node 0, its function intel_svc_secure_call_thread is used to handle + * SMC or HVC calls between kernel driver and secure monitor software. + * + * Return: 0 + */ +static int svc_normal_to_secure_thread(void *data) +{ + struct intel_svc_controller *ctrl = (struct intel_svc_controller *)data; + struct intel_svc_data *pdata; + struct intel_svc_c_data *cdata; + struct arm_smccc_res res; + unsigned long a0, a1, a2; + int ret_fifo = 0; + + pdata = kmalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + cdata = kmalloc(sizeof(*cdata), GFP_KERNEL); + if (!cdata) { + kfree(pdata); + return -ENOMEM; + } + + /* default set, to remove build warning */ + a0 = INTEL_SIP_SMC_FPGA_CONFIG_LOOPBACK; + a1 = 0; + a2 = 0; + + pr_debug("smc_hvc_shm_thread is running\n"); + + while (!kthread_should_stop()) { + ret_fifo = kfifo_out_spinlocked(&ctrl->svc_fifo, + pdata, sizeof(*pdata), + &ctrl->svc_fifo_lock); + + if (!ret_fifo) { + schedule_timeout_interruptible(MAX_SCHEDULE_TIMEOUT); + continue; + } + + pr_debug("get from FIFO pa=0x%016x, command=%u, size=%u\n", + (unsigned int)pdata->paddr, pdata->command, + (unsigned int)pdata->size); + + switch (pdata->command) { + case COMMAND_RECONFIG_DATA_CLAIM: + svc_thread_cmd_data_claim(ctrl, pdata, cdata); + continue; + case COMMAND_RECONFIG: + a0 = INTEL_SIP_SMC_FPGA_CONFIG_START; + a1 = 0; + a2 = 0; + break; + case COMMAND_RECONFIG_DATA_SUBMIT: + a0 = INTEL_SIP_SMC_FPGA_CONFIG_WRITE; + a1 = (unsigned long)pdata->paddr; + a2 = (unsigned long)pdata->size; + break; + case COMMAND_RECONFIG_STATUS: + a0 = INTEL_SIP_SMC_FPGA_CONFIG_ISDONE; + a1 = 0; + a2 = 0; + break; + case COMMAND_RSU_STATUS: + a0 = INTEL_SIP_SMC_RSU_STATUS; + a1 = 0; + a2 = 0; + break; + case COMMAND_RSU_UPDATE: + a0 = INTEL_SIP_SMC_RSU_UPDATE; + a1 = pdata->arg[0]; + a2 = 0; + break; + default: + /* it shouldn't happen */ + break; + } + pr_debug("%s: before SMC call -- a0=0x%016x a1=0x%016x", + __func__, (unsigned int)a0, (unsigned int)a1); + pr_debug(" a2=0x%016x\n", (unsigned int)a2); + + ctrl->invoke_fn(a0, a1, a2, 0, 0, 0, 0, 0, &res); + + pr_debug("%s: after SMC call -- res.a0=0x%016x", + __func__, (unsigned int)res.a0); + pr_debug(" res.a1=0x%016x, res.a2=0x%016x", + (unsigned int)res.a1, (unsigned int)res.a2); + pr_debug(" res.a3=0x%016x\n", (unsigned int)res.a3); + + if (pdata->command == COMMAND_RSU_STATUS) { + if (res.a0 == INTEL_SIP_SMC_RSU_ERROR) + cdata->status = 0; + else + cdata->status = BIT(SVC_STATUS_RSU_OK); + + cdata->kaddr1 = &res; + cdata->kaddr2 = NULL; + cdata->kaddr3 = NULL; + pdata->chan->scl->receive_cb(pdata->chan->scl, cdata); + continue; + } + + if (pdata->command == COMMAND_RSU_UPDATE) { + if (res.a0 == INTEL_SIP_SMC_STATUS_OK) + cdata->status = BIT(SVC_STATUS_RSU_OK); + else + cdata->status = 0; + + cdata->kaddr1 = NULL; + cdata->kaddr2 = NULL; + cdata->kaddr3 = NULL; + pdata->chan->scl->receive_cb(pdata->chan->scl, cdata); + continue; + } + + switch (res.a0) { + case INTEL_SIP_SMC_STATUS_OK: + svc_thread_recv_status_ok(pdata, cdata, res); + break; + case INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY: + switch (pdata->command) { + case COMMAND_RECONFIG_DATA_SUBMIT: + svc_thread_cmd_data_claim(ctrl, + pdata, cdata); + break; + case COMMAND_RECONFIG_STATUS: + svc_thread_cmd_config_status(ctrl, + pdata, cdata); + break; + default: + break; + } + break; + case INTEL_SIP_SMC_FPGA_CONFIG_STATUS_REJECTED: + pr_debug("%s: STATUS_REJECTED\n", __func__); + break; + case INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR: + pr_err("%s: STATUS_ERROR\n", __func__); + cdata->status = BIT(SVC_STATUS_RECONFIG_ERROR); + cdata->kaddr1 = NULL; + cdata->kaddr2 = NULL; + cdata->kaddr3 = NULL; + pdata->chan->scl->receive_cb(pdata->chan->scl, cdata); + break; + default: + break; + } + }; + + kfree(cdata); + kfree(pdata); + + return 0; +} + +/** + * svc_normal_to_secure_shm_thread() - the function to run in the kthread + * @data: data pointer for kthread function + * + * Service layer driver creates intel_svc_smc_hvc_shm kthread on CPU + * node 0, its function intel_svc_secure_shm_thread is used to query the + * physical address of memory block reserved by secure monitor software at + * secure world. + * + * Return: 0 + */ +static int svc_normal_to_secure_shm_thread(void *data) +{ + struct intel_svc_sh_memory *sh_mem = (struct intel_svc_sh_memory *)data; + struct arm_smccc_res res; + + /* SMC or HVC call to get shared memory info from secure world */ + sh_mem->invoke_fn(INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM, + 0, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 == INTEL_SIP_SMC_STATUS_OK) { + sh_mem->addr = res.a1; + sh_mem->size = res.a2; + } else { + pr_err("%s: after SMC call -- res.a0=0x%016x", __func__, + (unsigned int)res.a0); + sh_mem->addr = 0; + sh_mem->size = 0; + } + + complete(&sh_mem->sync_complete); + do_exit(0); +} + +/** + * svc_get_sh_memory_param() - get memory block reserved by secure monitor SW + * @pdev: pointer to service layer device + * @param: pointer to service shared memory structure + * + * Return: zero for successfully getting the physical address of memory block + * reserved by secure monitor software, or negative value on error. + */ +static int svc_get_sh_memory_param(struct platform_device *pdev, + struct intel_svc_sh_memory *param) +{ + struct device *dev = &pdev->dev; + struct task_struct *sh_memory_task; + unsigned int cpu = 0; + + init_completion(¶m->sync_complete); + + /* smc/hvc call happens on cpu 0 bound kthread */ + sh_memory_task = kthread_create_on_node(svc_normal_to_secure_shm_thread, + (void *)param, cpu_to_node(cpu), + "svc_smc_hvc_shm_thread"); + if (IS_ERR(sh_memory_task)) + dev_err(dev, "fail to create intel_svc_smc_shm_thread\n"); + kthread_bind(sh_memory_task, cpu); + wake_up_process(sh_memory_task); + + if (!wait_for_completion_timeout(¶m->sync_complete, 10 * HZ)) { + dev_err(dev, + "timeout to get sh-memory paras from secure world\n"); + return -ETIMEDOUT; + } + + if (!param->addr || !param->size) { + dev_err(dev, + "fails to get shared memory info from secure world\n"); + return -ENOMEM; + } + + dev_dbg(dev, "SM software provides paddr: 0x%016x, size: 0x%08x\n", + (unsigned int)param->addr, + (unsigned int)param->size); + + return 0; +} + +/** + * svc_create_memory_pool() - create a memory pool from reserved memory block + * @pdev: pointer to service layer device + * @param: pointer to service shared memory structure + * + * Return: pool allocated from reserved memory block or ERR_PTR() on error. + */ +static struct gen_pool * +svc_create_memory_pool(struct platform_device *pdev, + struct intel_svc_sh_memory *param) +{ + struct device *dev = &pdev->dev; + struct gen_pool *genpool; + unsigned long vaddr; + phys_addr_t paddr; + size_t size; + phys_addr_t begin; + phys_addr_t end; + void *va; + size_t page_mask = PAGE_SIZE - 1; + int min_alloc_order = 3; + int ret; + + begin = roundup(param->addr, PAGE_SIZE); + end = rounddown(param->addr + param->size, PAGE_SIZE); + paddr = begin; + size = end - begin; + va = memremap(paddr, size, MEMREMAP_WC); + if (!va) { + dev_err(dev, "fail to remap shared memory\n"); + return ERR_PTR(-EINVAL); + } + vaddr = (unsigned long)va; + dev_dbg(dev, + "reserved memory vaddr: %p, paddr: 0x%16x size: 0x%8x\n", + va, (unsigned int)paddr, (unsigned int)size); + if ((vaddr & page_mask) || (paddr & page_mask) || + (size & page_mask)) { + dev_err(dev, "page is not aligned\n"); + return ERR_PTR(-EINVAL); + } + genpool = gen_pool_create(min_alloc_order, -1); + if (!genpool) { + dev_err(dev, "fail to create genpool\n"); + return ERR_PTR(-ENOMEM); + } + gen_pool_set_algo(genpool, gen_pool_best_fit, NULL); + ret = gen_pool_add_virt(genpool, vaddr, paddr, size, -1); + if (ret) { + dev_err(dev, "fail to add memory chunk to the pool\n"); + gen_pool_destroy(genpool); + return ERR_PTR(ret); + } + + return genpool; +} + +/** + * svc_smccc_smc() - secure monitor call between normal and secure world + * @a0-a7: arguments passed in registers 0 to 7 + * @res: result values from register 0 to 3 + */ +static void svc_smccc_smc(unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, + unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res); +} + +/** + * svc_smccc_hvc() - hypervisor call between normal and secure world + * @a0-a7: arguments passed in registers 0 to 7 + * @res: result values from register 0 to 3 + */ +static void svc_smccc_hvc(unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, + unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); +} + +/** + * get_invoke_func() - invoke SMC or HVC call + * @dev: pointer to device + * + * Return: function pointer to svc_smccc_smc or svc_smccc_hvc. + */ +static svc_invoke_fn *get_invoke_func(struct device *dev) +{ + const char *method; + + if (of_property_read_string(dev->of_node, "method", &method)) { + dev_warn(dev, "missing \"method\" property\n"); + return ERR_PTR(-ENXIO); + } + + if (!strcmp(method, "smc")) + return svc_smccc_smc; + if (!strcmp(method, "hvc")) + return svc_smccc_hvc; + + dev_warn(dev, "invalid \"method\" property: %s\n", method); + + return ERR_PTR(-EINVAL); +} + +static const struct of_device_id intel_svc_drv_match[] = { + {.compatible = "intel,stratix10-svc"}, + {}, +}; + +static int intel_svc_drv_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_svc_controller *controller; + struct intel_svc_chan *chans; + struct gen_pool *genpool; + struct intel_svc_sh_memory *sh_memory; + svc_invoke_fn *invoke_fn; + size_t fifo_size; + int ret; + + /* get SMC or HVC function */ + invoke_fn = get_invoke_func(dev); + if (IS_ERR(invoke_fn)) + return -EINVAL; + + sh_memory = devm_kzalloc(dev, sizeof(*sh_memory), GFP_KERNEL); + if (!sh_memory) + return -ENOMEM; + + sh_memory->invoke_fn = invoke_fn; + ret = svc_get_sh_memory_param(pdev, sh_memory); + if (ret) + return ret; + + genpool = svc_create_memory_pool(pdev, sh_memory); + if (!genpool) + return -ENOMEM; + + /* allocate service controller and supporting channel */ + controller = devm_kzalloc(dev, sizeof(*controller), GFP_KERNEL); + if (!controller) + return -ENOMEM; + + chans = devm_kmalloc_array(dev, SVC_NUM_CHANNEL, + sizeof(*chans), GFP_KERNEL | __GFP_ZERO); + if (!chans) + return -ENOMEM; + + controller->dev = dev; + controller->num_chans = SVC_NUM_CHANNEL; + controller->num_active_client = 0; + controller->chans = chans; + controller->genpool = genpool; + controller->task = NULL; + controller->invoke_fn = invoke_fn; + init_completion(&controller->complete_status); + + fifo_size = sizeof(struct intel_svc_data) * SVC_NUM_DATA_IN_FIFO; + ret = kfifo_alloc(&controller->svc_fifo, fifo_size, GFP_KERNEL); + if (ret) { + dev_err(dev, "fails to allocate FIFO\n"); + return ret; + } + spin_lock_init(&controller->svc_fifo_lock); + + chans[0].scl = NULL; + chans[0].ctrl = controller; + chans[0].name = "fpga"; + spin_lock_init(&chans[0].lock); + + chans[1].scl = NULL; + chans[1].ctrl = controller; + chans[1].name = "rsu"; + spin_lock_init(&chans[1].lock); + + list_add_tail(&controller->node, &svc_ctrl); + platform_set_drvdata(pdev, controller); + + pr_info("Intel Service Layer Driver Initialized\n"); + + return ret; +} + +static int intel_svc_drv_remove(struct platform_device *pdev) +{ + struct intel_svc_controller *ctrl = platform_get_drvdata(pdev); + + kfifo_free(&ctrl->svc_fifo); + if (ctrl->task) { + kthread_stop(ctrl->task); + ctrl->task = NULL; + } + if (ctrl->genpool) + gen_pool_destroy(ctrl->genpool); + list_del(&ctrl->node); + + return 0; +} + +static struct platform_driver intel_svc_driver = { + .probe = intel_svc_drv_probe, + .remove = intel_svc_drv_remove, + .driver = { + .name = "intel-svc", + .of_match_table = intel_svc_drv_match, + }, +}; + +static int __init intel_svc_init(void) +{ + struct device_node *fw_np; + struct device_node *np; + int ret; + + fw_np = of_find_node_by_name(NULL, "firmware"); + if (!fw_np) + return -ENODEV; + + np = of_find_matching_node(fw_np, intel_svc_drv_match); + if (!np) { + of_node_put(fw_np); + return -ENODEV; + } + + of_node_put(np); + ret = of_platform_populate(fw_np, intel_svc_drv_match, NULL, NULL); + of_node_put(fw_np); + if (ret) + return ret; + + return platform_driver_register(&intel_svc_driver); +} + +static void __exit intel_svc_exit(void) +{ + return platform_driver_unregister(&intel_svc_driver); +} + +subsys_initcall(intel_svc_init); +module_exit(intel_svc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Stratix10 Service Layer Driver"); +MODULE_AUTHOR("Richard Gong <richard.gong@intel.com>"); +MODULE_ALIAS("platform:intel-svc"); diff --git a/drivers/misc/intel-smc.h b/drivers/misc/intel-smc.h new file mode 100644 index 000000000000..1612e5d8dca1 --- /dev/null +++ b/drivers/misc/intel-smc.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2018, Intel Corporation + */ + +#ifndef __INTEL_SMC_H +#define __INTEL_SMC_H + +#include <linux/arm-smccc.h> +#include <linux/bitops.h> + +/* + * This file defines the Secure Monitor Call (SMC) message protocol used for + * service layer driver in normal world (EL1) to communicate with secure + * monitor software in Secure Monitor Exception Level 3 (EL3). + * + * This file is shared with secure firmware (FW) which is out of kernel tree. + * + * An ARM SMC instruction takes a function identifier and up to 6 64-bit + * register values as arguments, and can return up to 4 64-bit register + * value. The operation of the secure monitor is determined by the parameter + * values passed in through registers. + + * EL1 and EL3 communicates pointer as physical address rather than the + * virtual address. + */ + +/* + * Functions specified by ARM SMC Calling convention: + * + * FAST call executes atomic operations, returns when the requested operation + * has completed. + * STD call starts a operation which can be preempted by a non-secure + * interrupt. The call can return before the requested operation has + * completed. + * + * a0..a7 is used as register names in the descriptions below, on arm32 + * that translates to r0..r7 and on arm64 to w0..w7. + */ + +#define INTEL_SIP_SMC_STD_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL, ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_SIP, (func_num)) + +#define INTEL_SIP_SMC_FAST_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_SIP, (func_num)) + +/* + * Return values in INTEL_SIP_SMC_* call + * + * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION: + * Secure monitor software doesn't recognize the request. + * + * INTEL_SIP_SMC_STATUS_OK: + * FPGA configuration completed successfully, + * In case of FPGA configuration write operation, it means secure monitor + * software can accept the next chunk of FPGA configuration data. + * + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY: + * In case of FPGA configuration write operation, it means secure monitor + * software is still processing previous data & can't accept the next chunk + * of data. Service driver needs to issue + * INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE call to query the + * completed block(s). + * + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR: + * There is error during the FPGA configuration process. + * + * INTEL_SIP_SMC_REG_ERROR: + * There is error during a read or write operation of the protected + * registers. + */ +#define INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION 0xFFFFFFFF +#define INTEL_SIP_SMC_STATUS_OK 0x0 +#define INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY 0x1 +#define INTEL_SIP_SMC_FPGA_CONFIG_STATUS_REJECTED 0x2 +#define INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR 0x4 +#define INTEL_SIP_SMC_REG_ERROR 0x5 +#define INTEL_SIP_SMC_RSU_ERROR 0x7 + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_START + * + * Sync call used by service driver at EL1 to request the FPGA in EL3 to + * be prepare to receive a new configuration. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_START. + * a1: flag for full or partial configuration + * 0 full reconfiguration. + * 1 partial reconfiguration. + * a2-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, or INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_START 1 +#define INTEL_SIP_SMC_FPGA_CONFIG_START \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_START) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_WRITE + * + * Async call used by service driver at EL1 to provide FPGA configuration data + * to secure world. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_WRITE. + * a1: 64bit physical address of the configuration data memory block + * a2: Size of configuration data block. + * a3-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY or + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1: 64bit physical address of 1st completed memory block if any completed + * block, otherwise zero value. + * a2: 64bit physical address of 2nd completed memory block if any completed + * block, otherwise zero value. + * a3: 64bit physical address of 3rd completed memory block if any completed + * block, otherwise zero value. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_WRITE 2 +#define INTEL_SIP_SMC_FPGA_CONFIG_WRITE \ + INTEL_SIP_SMC_STD_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_WRITE) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE + * + * Sync call used by service driver at EL1 to track the completed write + * transactions. This request is called after INTEL_SIP_SMC_FPGA_CONFIG_WRITE + * call returns INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE. + * a1-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY or + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1: 64bit physical address of 1st completed memory block. + * a2: 64bit physical address of 2nd completed memory block if + * any completed block, otherwise zero value. + * a3: 64bit physical address of 3rd completed memory block if + * any completed block, otherwise zero value. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE 3 +#define INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE \ +INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_COMPLETED_WRITE) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_ISDONE + * + * Sync call used by service driver at EL1 to inform secure world that all + * data are sent, to check whether or not the secure world had completed + * the FPGA configuration process. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_ISDONE. + * a1-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_FPGA_CONFIG_STATUS_BUSY or + * INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_ISDONE 4 +#define INTEL_SIP_SMC_FPGA_CONFIG_ISDONE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_ISDONE) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM + * + * Sync call used by service driver at EL1 to query the physical address of + * memory block reserved by secure monitor software. + * + * Call register usage: + * a0:INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM. + * a1-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1: start of physical address of reserved memory block. + * a2: size of reserved memory block. + * a3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_GET_MEM 5 +#define INTEL_SIP_SMC_FPGA_CONFIG_GET_MEM \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_GET_MEM) + +/* + * Request INTEL_SIP_SMC_FPGA_CONFIG_LOOPBACK + * + * For SMC loop-back mode only, used for internal integration, debugging + * or troubleshooting. + * + * Call register usage: + * a0: INTEL_SIP_SMC_FPGA_CONFIG_LOOPBACK. + * a1-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_FPGA_CONFIG_STATUS_ERROR. + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_LOOPBACK 6 +#define INTEL_SIP_SMC_FPGA_CONFIG_LOOPBACK \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_FPGA_CONFIG_LOOPBACK) + +/* + * Request INTEL_SIP_SMC_REG_READ + * + * Read a protected register using SMCCC + * + * Call register usage: + * a0: INTEL_SIP_SMC_REG_READ. + * a1: register address. + * a2-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_REG_ERROR. + * a1: Value in the register + * a2-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_REG_READ 7 +#define INTEL_SIP_SMC_REG_READ \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_REG_READ) + +/* + * Request INTEL_SIP_SMC_REG_WRITE + * + * Write a protected register using SMCCC + * + * Call register usage: + * a0: INTEL_SIP_SMC_REG_WRITE. + * a1: register address + * a2: value to program into register. + * a3-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_REG_ERROR. + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_REG_WRITE 8 +#define INTEL_SIP_SMC_REG_WRITE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_REG_WRITE) + +/* + * Request INTEL_SIP_SMC_FUNCID_REG_UPDATE + * + * Update one or more bits in a protected register using a + * read-modify-write operation. + * + * Call register usage: + * a0: INTEL_SIP_SMC_REG_UPDATE. + * a1: register address + * a2: Write Mask. + * a3: Value to write. + * a4-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK or INTEL_SIP_SMC_REG_ERROR. + * a1-3: Not used. + */ +#define INTEL_SIP_SMC_FUNCID_REG_UPDATE 9 +#define INTEL_SIP_SMC_REG_UPDATE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_REG_UPDATE) + +/* + * Request INTEL_SIP_SMC_RSU_STATUS + * + * Sync call used by service driver at EL1 to query the RSU status + * + * Call register usage: + * a0 INTEL_SIP_SMC_RSU_STATUS + * a1-7 not used + * + * Return status + * a0: Current Image + * a1: Last Failing Image + * a2: Version | State + * a3: Error details | Error location + * + * Or + * + * a0: INTEL_SIP_SMC_RSU_ERROR + */ +#define INTEL_SIP_SMC_FUNCID_RSU_STATUS 11 +#define INTEL_SIP_SMC_RSU_STATUS \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_RSU_STATUS) + +/* + * Request INTEL_SIP_SMC_RSU_UPDATE + * + * Sync call used by service driver at EL1 to tell you next reboot is RSU_UPDATE + * + * Call register usage: + * a0 INTEL_SIP_SMC_RSU_UPDATE + * a1 64bit physical address of the configuration data memory in flash + * a2-7 not used + * + * Return status + * a0 INTEL_SIP_SMC_STATUS_OK + */ +#define INTEL_SIP_SMC_FUNCID_RSU_UPDATE 12 +#define INTEL_SIP_SMC_RSU_UPDATE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_RSU_UPDATE) + +#endif diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index d9c368c44194..cf3038e2b839 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -1091,10 +1091,12 @@ static const struct flash_info spi_nor_ids[] = { { "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "mx25u25635f", INFO(0xc22539, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_4B_OPCODES) }, { "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) }, + { "mx25u51245g", INFO(0xc2253a, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, { "mx66u51235f", INFO(0xc2253a, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, { "mx66l1g45g", INFO(0xc2201b, 0, 64 * 1024, 2048, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "mx66l1g55g", INFO(0xc2261b, 0, 64 * 1024, 2048, SPI_NOR_QUAD_READ) }, + { "mx66u2g45g", INFO(0xc2253c, 0, 64 * 1024, 4096, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, /* Micron */ { "n25q016a", INFO(0x20bb15, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_QUAD_READ) }, diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig index edf20361ea5f..a14085b1ad32 100644 --- a/drivers/net/ethernet/stmicro/stmmac/Kconfig +++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig @@ -110,7 +110,7 @@ config DWMAC_ROCKCHIP config DWMAC_SOCFPGA tristate "SOCFPGA dwmac support" - default ARCH_SOCFPGA + default (ARCH_SOCFPGA || ARCH_STRATIX10) depends on OF && (ARCH_SOCFPGA || ARCH_STRATIX10 || COMPILE_TEST) select MFD_SYSCON help diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c index 5b3b06a0a3bf..e6f573535991 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c @@ -15,6 +15,9 @@ * Adopted from dwmac-sti.c */ +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 +#include <linux/arm-smccc.h> +#endif #include <linux/mfd/syscon.h> #include <linux/of.h> #include <linux/of_address.h> @@ -52,6 +55,9 @@ struct socfpga_dwmac { int interface; u32 reg_offset; u32 reg_shift; +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 + u32 sysmgr_reg; +#endif struct device *dev; struct regmap *sys_mgr_base_addr; struct reset_control *stmmac_rst; @@ -61,6 +67,122 @@ struct socfpga_dwmac { struct tse_pcs pcs; }; +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 +/* Functions specified by ARM SMC Calling convention: + * + * FAST call executes atomic operations, returns when the requested operation + * has completed. + * STD call starts a operation which can be preempted by a non-secure + * interrupt. The call can return before the requested operation has completed. + * a0..a7 is used as register names in the descriptions below, on arm32 that + * translates to r0..r7 and on arm64 to w0..w7. + */ + +#define INTEL_SIP_SMC_STD_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL, ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_SIP, (func_num)) + +#define INTEL_SIP_SMC_FAST_CALL_VAL(func_num) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_SIP, (func_num)) + +#define INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION 0xFFFFFFFF +#define INTEL_SIP_SMC_STATUS_OK 0x0 +#define INTEL_SIP_SMC_REG_ERROR 0x5 + +/* Request INTEL_SIP_SMC_REG_READ + * + * Read a protected register using SMCCC + * + * Call register usage: + * a0: INTEL_SIP_SMC_REG_READ. + * a1: register address. + * a2-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_REG_ERROR, or + * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION + * a1: Value in the register + * a2-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_REG_READ 7 +#define INTEL_SIP_SMC_REG_READ \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_REG_READ) + +/* Request INTEL_SIP_SMC_REG_WRITE + * + * Write a protected register using SMCCC + * + * Call register usage: + * a0: INTEL_SIP_SMC_REG_WRITE. + * a1: register address + * a2: value to program into register. + * a3-7: not used. + * + * Return status: + * a0: INTEL_SIP_SMC_STATUS_OK, INTEL_SIP_SMC_REG_ERROR, or + * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION + * a1-3: not used. + */ +#define INTEL_SIP_SMC_FUNCID_REG_WRITE 8 +#define INTEL_SIP_SMC_REG_WRITE \ + INTEL_SIP_SMC_FAST_CALL_VAL(INTEL_SIP_SMC_FUNCID_REG_WRITE) + +/**************** Stratix 10 EMAC Memory Controller Functions ************/ + +/* s10_protected_reg_write + * Write to a protected SMC register. + * @context: Not used + * @reg: Address of register + * @value: Value to write + * Return: INTEL_SIP_SMC_STATUS_OK (0) on success + * INTEL_SIP_SMC_REG_ERROR on error + * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION if not supported + */ +static int s10_protected_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct arm_smccc_res result; + + arm_smccc_smc(INTEL_SIP_SMC_REG_WRITE, reg, val, 0, 0, + 0, 0, 0, &result); + + return (int)result.a0; +} + +/* s10_protected_reg_read + * Read the status of a protected SMC register + * @context: Not used + * @reg: Address of register + * @value: Value read. + * Return: INTEL_SIP_SMC_STATUS_OK (0) on success + * INTEL_SIP_SMC_REG_ERROR on error + * INTEL_SIP_SMC_RETURN_UNKNOWN_FUNCTION if not supported + */ +static int s10_protected_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct arm_smccc_res result; + + arm_smccc_smc(INTEL_SIP_SMC_REG_READ, reg, 0, 0, 0, + 0, 0, 0, &result); + + *val = (unsigned int)result.a1; + + return (int)result.a0; +} + +static const struct regmap_config s10_emac_regmap_cfg = { + .name = "s10_emac", + .reg_bits = 32, + .val_bits = 32, + .max_register = 0xffffffff, + .reg_read = s10_protected_reg_read, + .reg_write = s10_protected_reg_write, + .use_single_rw = true, +}; +#endif + static void socfpga_dwmac_fix_mac_speed(void *priv, unsigned int speed) { struct socfpga_dwmac *dwmac = (struct socfpga_dwmac *)priv; @@ -105,20 +227,43 @@ static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device * struct device_node *np = dev->of_node; struct regmap *sys_mgr_base_addr; u32 reg_offset, reg_shift; +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 + u32 sysmgr_reg = 0; +#endif int ret, index; struct device_node *np_splitter = NULL; struct device_node *np_sgmii_adapter = NULL; +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 + struct device_node *np_sysmgr = NULL; +#endif struct resource res_splitter; struct resource res_tse_pcs; struct resource res_sgmii_adapter; dwmac->interface = of_get_phy_mode(np); +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 + sys_mgr_base_addr = devm_regmap_init(dev, NULL, (void *)dwmac, + &s10_emac_regmap_cfg); + if (IS_ERR(sys_mgr_base_addr)) + return PTR_ERR(sys_mgr_base_addr); + + np_sysmgr = of_parse_phandle(np, "altr,sysmgr-syscon", 0); + if (np_sysmgr) { + ret = of_property_read_u32_index(np_sysmgr, "reg", 0, + &sysmgr_reg); + if (ret) { + dev_info(dev, "Could not read sysmgr register address\n"); + return -EINVAL; + } + } +#else sys_mgr_base_addr = syscon_regmap_lookup_by_phandle(np, "altr,sysmgr-syscon"); if (IS_ERR(sys_mgr_base_addr)) { dev_info(dev, "No sysmgr-syscon node found\n"); return PTR_ERR(sys_mgr_base_addr); } +#endif ret = of_property_read_u32_index(np, "altr,sysmgr-syscon", 1, ®_offset); if (ret) { @@ -222,6 +367,9 @@ static int socfpga_dwmac_parse_data(struct socfpga_dwmac *dwmac, struct device * dwmac->reg_offset = reg_offset; dwmac->reg_shift = reg_shift; dwmac->sys_mgr_base_addr = sys_mgr_base_addr; +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 + dwmac->sysmgr_reg = sysmgr_reg; +#endif dwmac->dev = dev; of_node_put(np_sgmii_adapter); @@ -238,6 +386,9 @@ static int socfpga_dwmac_set_phy_mode(struct socfpga_dwmac *dwmac) int phymode = dwmac->interface; u32 reg_offset = dwmac->reg_offset; u32 reg_shift = dwmac->reg_shift; +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 + u32 sysmgr_reg = dwmac->sysmgr_reg; +#endif u32 ctrl, val, module; switch (phymode) { @@ -266,7 +417,11 @@ static int socfpga_dwmac_set_phy_mode(struct socfpga_dwmac *dwmac) reset_control_assert(dwmac->stmmac_ocp_rst); reset_control_assert(dwmac->stmmac_rst); +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 + regmap_read(sys_mgr_base_addr, sysmgr_reg + reg_offset, &ctrl); +#else regmap_read(sys_mgr_base_addr, reg_offset, &ctrl); +#endif ctrl &= ~(SYSMGR_EMACGRP_CTRL_PHYSEL_MASK << reg_shift); ctrl |= val << reg_shift; @@ -284,7 +439,11 @@ static int socfpga_dwmac_set_phy_mode(struct socfpga_dwmac *dwmac) ctrl &= ~(SYSMGR_EMACGRP_CTRL_PTP_REF_CLK_MASK << (reg_shift / 2)); } +#if defined CONFIG_HAVE_ARM_SMCCC && defined CONFIG_ARCH_STRATIX10 + regmap_write(sys_mgr_base_addr, sysmgr_reg + reg_offset, ctrl); +#else regmap_write(sys_mgr_base_addr, reg_offset, ctrl); +#endif /* Deassert reset for the phy configuration to be sampled by * the enet controller, and operation to start in requested mode diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c index 7516ca210855..acba286fc0b7 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac_lib.c @@ -34,7 +34,7 @@ int dwmac_dma_reset(void __iomem *ioaddr) err = readl_poll_timeout(ioaddr + DMA_BUS_MODE, value, !(value & DMA_BUS_MODE_SFT_RESET), - 10000, 100000); + 0, 10000); if (err) return -EBUSY; diff --git a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c index afed0f0f4027..c0c75c111abb 100644 --- a/drivers/net/ethernet/stmicro/stmmac/ring_mode.c +++ b/drivers/net/ethernet/stmicro/stmmac/ring_mode.c @@ -59,7 +59,7 @@ static int jumbo_frm(void *p, struct sk_buff *skb, int csum) desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); stmmac_prepare_tx_desc(priv, desc, 1, bmax, csum, - STMMAC_RING_MODE, 0, false, skb->len); + STMMAC_RING_MODE, 1, false, skb->len); tx_q->tx_skbuff[entry] = NULL; entry = STMMAC_GET_ENTRY(entry, DMA_TX_SIZE); @@ -91,7 +91,7 @@ static int jumbo_frm(void *p, struct sk_buff *skb, int csum) tx_q->tx_skbuff_dma[entry].is_jumbo = true; desc->des3 = cpu_to_le32(des2 + BUF_SIZE_4KiB); stmmac_prepare_tx_desc(priv, desc, 1, nopaged_len, csum, - STMMAC_RING_MODE, 0, true, skb->len); + STMMAC_RING_MODE, 1, true, skb->len); } tx_q->cur_tx = entry; diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 5dd303212e28..27b4376b0bb2 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -4460,6 +4460,7 @@ int stmmac_suspend(struct device *dev) mutex_lock(&priv->lock); + netif_carrier_off(ndev); netif_device_detach(ndev); stmmac_stop_all_queues(priv); @@ -4477,7 +4478,8 @@ int stmmac_suspend(struct device *dev) pinctrl_pm_select_sleep_state(priv->device); /* Disable clock in case of PWM is off */ clk_disable(priv->plat->pclk); - clk_disable(priv->plat->stmmac_clk); + if (!of_machine_is_compatible("altr,socfpga-stratix10")) + clk_disable(priv->plat->stmmac_clk); } mutex_unlock(&priv->lock); @@ -4528,6 +4530,9 @@ int stmmac_resume(struct device *dev) if (!netif_running(ndev)) return 0; + if (ndev->phydev) + phy_start(ndev->phydev); + /* Power Down bit, into the PM register, is cleared * automatically as soon as a magic packet or a Wake-up frame * is received. Anyway, it's better to manually clear @@ -4567,9 +4572,6 @@ int stmmac_resume(struct device *dev) mutex_unlock(&priv->lock); - if (ndev->phydev) - phy_start(ndev->phydev); - return 0; } EXPORT_SYMBOL_GPL(stmmac_resume); diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig index ad3fcad4d75b..db9b467fd039 100644 --- a/drivers/of/Kconfig +++ b/drivers/of/Kconfig @@ -100,6 +100,14 @@ config OF_OVERLAY While this option is selected automatically when needed, you can enable it manually to improve device tree unit test coverage. +config OF_CONFIGFS + bool "Device Tree Overlay ConfigFS interface" + select CONFIGFS_FS + select OF_FLATTREE + depends on OF_OVERLAY + help + Enable a simple user-space driven DT overlay interface. + config OF_NUMA bool diff --git a/drivers/of/Makefile b/drivers/of/Makefile index 663a4af0cccd..b00a95adf519 100644 --- a/drivers/of/Makefile +++ b/drivers/of/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-y = base.o device.o platform.o property.o obj-$(CONFIG_OF_KOBJ) += kobj.o +obj-$(CONFIG_OF_CONFIGFS) += configfs.o obj-$(CONFIG_OF_DYNAMIC) += dynamic.o obj-$(CONFIG_OF_FLATTREE) += fdt.o obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o diff --git a/drivers/of/configfs.c b/drivers/of/configfs.c new file mode 100644 index 000000000000..7a6cae074381 --- /dev/null +++ b/drivers/of/configfs.c @@ -0,0 +1,284 @@ +/* + * Configfs entries for device-tree + * + * Copyright (C) 2013 - Pantelis Antoniou <panto@antoniou-consulting.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include <linux/ctype.h> +#include <linux/cpu.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_fdt.h> +#include <linux/spinlock.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/configfs.h> +#include <linux/types.h> +#include <linux/stat.h> +#include <linux/limits.h> +#include <linux/file.h> +#include <linux/vmalloc.h> +#include <linux/firmware.h> + +#include "of_private.h" + +struct cfs_overlay_item { + struct config_item item; + + char path[PATH_MAX]; + + const struct firmware *fw; + struct device_node *overlay; + int ov_id; + + void *dtbo; + int dtbo_size; +}; + +static int create_overlay(struct cfs_overlay_item *overlay, const void *blob, + size_t size) +{ + int err; + + err = of_overlay_fdt_apply(blob, size, &overlay->ov_id); + if (err < 0) + pr_err("%s: Failed to create overlay (err=%d)\n", __func__, + err); + + return err; +} + +static inline struct cfs_overlay_item *to_cfs_overlay_item( + struct config_item *item) +{ + return item ? container_of(item, struct cfs_overlay_item, item) : NULL; +} + +static ssize_t cfs_overlay_item_path_show(struct config_item *item, char *page) +{ + return sprintf(page, "%s\n", to_cfs_overlay_item(item)->path); +} + +static ssize_t cfs_overlay_item_path_store(struct config_item *item, + const char *page, size_t count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + const char *p = page; + char *s; + int err; + + /* if it's set do not allow changes */ + if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) + return -EPERM; + + /* copy to path buffer (and make sure it's always zero terminated */ + count = snprintf(overlay->path, sizeof(overlay->path) - 1, "%s", p); + overlay->path[sizeof(overlay->path) - 1] = '\0'; + + /* strip trailing newlines */ + s = overlay->path + strlen(overlay->path); + while (s > overlay->path && *--s == '\n') + *s = '\0'; + + pr_debug("%s: path is '%s'\n", __func__, overlay->path); + + err = request_firmware(&overlay->fw, overlay->path, NULL); + if (err != 0) + goto out_err; + + err = create_overlay(overlay, overlay->fw->data, overlay->fw->size); + if (err < 0) + goto out_err; + + return count; + +out_err: + + release_firmware(overlay->fw); + overlay->fw = NULL; + + overlay->path[0] = '\0'; + return err; +} + +static ssize_t cfs_overlay_item_status_show(struct config_item *item, + char *page) +{ + return sprintf(page, "%s\n", to_cfs_overlay_item(item)->ov_id >= 0 ? + "applied" : "unapplied"); +} + +CONFIGFS_ATTR(cfs_overlay_item_, path); +CONFIGFS_ATTR_RO(cfs_overlay_item_, status); + +static struct configfs_attribute *cfs_overlay_attrs[] = { + &cfs_overlay_item_attr_path, + &cfs_overlay_item_attr_status, + NULL, +}; + +static ssize_t cfs_overlay_item_dtbo_read(struct config_item *item, void *buf, + size_t max_count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + pr_debug("%s: buf=%p max_count=%zu\n", __func__, + buf, max_count); + + if (overlay->dtbo == NULL) + return 0; + + /* copy if buffer provided */ + if (buf != NULL) { + /* the buffer must be large enough */ + if (overlay->dtbo_size > max_count) + return -ENOSPC; + + memcpy(buf, overlay->dtbo, overlay->dtbo_size); + } + + return overlay->dtbo_size; +} + +static ssize_t cfs_overlay_item_dtbo_write(struct config_item *item, + const void *buf, size_t count) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + int err; + + /* if it's set do not allow changes */ + if (overlay->path[0] != '\0' || overlay->dtbo_size > 0) + return -EPERM; + + /* copy the contents */ + overlay->dtbo = kmemdup(buf, count, GFP_KERNEL); + if (overlay->dtbo == NULL) + return -ENOMEM; + + overlay->dtbo_size = count; + + err = create_overlay(overlay, overlay->dtbo, overlay->dtbo_size); + if (err < 0) + goto out_err; + + return count; + +out_err: + kfree(overlay->dtbo); + overlay->dtbo = NULL; + overlay->dtbo_size = 0; + + return err; +} + +CONFIGFS_BIN_ATTR(cfs_overlay_item_, dtbo, NULL, SZ_1M); + +static struct configfs_bin_attribute *cfs_overlay_bin_attrs[] = { + &cfs_overlay_item_attr_dtbo, + NULL, +}; + +static void cfs_overlay_release(struct config_item *item) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + if (overlay->ov_id >= 0) + of_overlay_remove(&overlay->ov_id); + if (overlay->fw) + release_firmware(overlay->fw); + /* kfree with NULL is safe */ + kfree(overlay->dtbo); + kfree(overlay); +} + +static struct configfs_item_operations cfs_overlay_item_ops = { + .release = cfs_overlay_release, +}; + +static struct config_item_type cfs_overlay_type = { + .ct_item_ops = &cfs_overlay_item_ops, + .ct_attrs = cfs_overlay_attrs, + .ct_bin_attrs = cfs_overlay_bin_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *cfs_overlay_group_make_item( + struct config_group *group, const char *name) +{ + struct cfs_overlay_item *overlay; + + overlay = kzalloc(sizeof(*overlay), GFP_KERNEL); + if (!overlay) + return ERR_PTR(-ENOMEM); + overlay->ov_id = -1; + + config_item_init_type_name(&overlay->item, name, &cfs_overlay_type); + return &overlay->item; +} + +static void cfs_overlay_group_drop_item(struct config_group *group, + struct config_item *item) +{ + struct cfs_overlay_item *overlay = to_cfs_overlay_item(item); + + config_item_put(&overlay->item); +} + +static struct configfs_group_operations overlays_ops = { + .make_item = cfs_overlay_group_make_item, + .drop_item = cfs_overlay_group_drop_item, +}; + +static struct config_item_type overlays_type = { + .ct_group_ops = &overlays_ops, + .ct_owner = THIS_MODULE, +}; + +static struct configfs_group_operations of_cfs_ops = { + /* empty - we don't allow anything to be created */ +}; + +static struct config_item_type of_cfs_type = { + .ct_group_ops = &of_cfs_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group of_cfs_overlay_group; + +static struct configfs_subsystem of_cfs_subsys = { + .su_group = { + .cg_item = { + .ci_namebuf = "device-tree", + .ci_type = &of_cfs_type, + }, + }, + .su_mutex = __MUTEX_INITIALIZER(of_cfs_subsys.su_mutex), +}; + +static int __init of_cfs_init(void) +{ + int ret; + + pr_info("%s\n", __func__); + + config_group_init(&of_cfs_subsys.su_group); + config_group_init_type_name(&of_cfs_overlay_group, "overlays", + &overlays_type); + configfs_add_default_group(&of_cfs_overlay_group, + &of_cfs_subsys.su_group); + + ret = configfs_register_subsystem(&of_cfs_subsys); + if (ret != 0) { + pr_err("%s: failed to register subsys\n", __func__); + goto out; + } + pr_info("%s: OK\n", __func__); +out: + return ret; +} +late_initcall(of_cfs_init); diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig index e0a04bfc873e..8db904b73e4c 100644 --- a/drivers/tty/Kconfig +++ b/drivers/tty/Kconfig @@ -151,6 +151,25 @@ config LEGACY_PTY_COUNT When not in use, each legacy PTY occupies 12 bytes on 32-bit architectures and 24 bytes on 64-bit architectures. +config BFIN_JTAG_COMM + tristate "Blackfin JTAG Communication" + depends on BLACKFIN + help + Add support for emulating a TTY device over the Blackfin JTAG. + + To compile this driver as a module, choose M here: the + module will be called bfin_jtag_comm. + +config BFIN_JTAG_COMM_CONSOLE + bool "Console on Blackfin JTAG" + depends on BFIN_JTAG_COMM=y + +config NEWHAVEN_LCD + tristate "NEWHAVEN LCD" + depends on I2C + help + Add support for a TTY device on a Newhaven I2C LCD device. + config SERIAL_NONSTANDARD bool "Non-standard serial port support" depends on HAS_IOMEM diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index c72cafdf32b4..c48a9cac79d1 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -33,5 +33,6 @@ obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o obj-$(CONFIG_GOLDFISH_TTY) += goldfish.o obj-$(CONFIG_MIPS_EJTAG_FDC_TTY) += mips_ejtag_fdc.o obj-$(CONFIG_VCC) += vcc.o +obj-$(CONFIG_NEWHAVEN_LCD) += newhaven_lcd.o obj-y += ipwireless/ diff --git a/drivers/tty/newhaven_lcd.c b/drivers/tty/newhaven_lcd.c new file mode 100644 index 000000000000..9671fda15489 --- /dev/null +++ b/drivers/tty/newhaven_lcd.c @@ -0,0 +1,636 @@ +/* + * TTY on a LCD connected to I2C + * Supports Newhaven NHD-0216K3Z-NSW-BBW + * + * Copyright (C) 2013 Altera Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * 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 DRV_NAME "lcd-comm" +#define DEV_NAME "ttyLCD" + +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/delay.h> + +#define LCD_COMMAND 0xfe +#define LCD_DISPLAY_ON 0x41 +#define LCD_DISPLAY_OFF 0x42 +#define LCD_SET_CURSOR 0x45 +#define LCD_BACKSPACE 0x4e +#define LCD_CLEAR_SCREEN 0x51 +#define LCD_BRIGHTNESS 0x53 +#define LCD_CUSTOM_CHAR 0x54 +#define LCD_BYTES_PER_FONT 8 +#define LCD_BYTES_PER_FONT_CMD (LCD_BYTES_PER_FONT + 3) + +#define LCD_BRIGHTNESS_MIN 1 +#define LCD_BRIGHTNESS_MAX 8 + +#define ASCII_BS 0x08 +#define ASCII_LF 0x0a +#define ASCII_CR 0x0d +#define ASCII_ESC 0x1b +#define ASCII_SPACE 0x20 +#define ASCII_BACKSLASH 0x5c +#define ASCII_TILDE 0x7e + +/* The NewHaven display has 8 custom characters that are user-loadable init + its cg ram. */ +#define CUSTOM_BACKSLASH 0x00 +#define CUSTOM_TILDE 0x01 + +struct custom_font { + const char font_cmd[LCD_BYTES_PER_FONT_CMD]; +}; + +/* Array of commands to send to set up custom fonts. */ +static struct custom_font custom_fonts[] = { + { { LCD_COMMAND, LCD_CUSTOM_CHAR, CUSTOM_BACKSLASH, 0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, }, }, + { { LCD_COMMAND, LCD_CUSTOM_CHAR, CUSTOM_TILDE, 0x00, 0x00, 0x00, 0x08, 0x15, 0x02, 0x00, 0x00, }, }, +}; + +struct lcd { + struct device *dev; + struct i2c_client *client; + struct tty_driver *lcd_tty_driver; + struct tty_port port; + unsigned int width; + unsigned int height; + unsigned int brightness; + char *buffer; + unsigned int top_line; + unsigned int cursor_line; + unsigned int cursor_col; +}; + +#define MAX_LCDS 1 +static struct lcd lcd_data_static[MAX_LCDS]; + +static int lcd_cmd_no_params(struct lcd *lcd_data, u8 cmd) +{ + int count; + u8 buf[2] = {LCD_COMMAND, cmd}; + + count = i2c_master_send(lcd_data->client, buf, sizeof(buf)); + if (count != sizeof(buf)) { + pr_err("%s: i2c_master_send returns %d\n", __func__, count); + return -1; + } + msleep(1); + return 0; +} + +static int lcd_cmd_one_param(struct lcd *lcd_data, u8 cmd, u8 param) +{ + int count; + u8 buf[3] = {LCD_COMMAND, cmd, param}; + + count = i2c_master_send(lcd_data->client, buf, sizeof(buf)); + if (count != sizeof(buf)) { + pr_err("%s: i2c_master_send returns %d\n", __func__, count); + return -1; + } + msleep(1); + return 0; +} + +static int lcd_cmd_backlight_brightness(struct lcd *lcd_data, u8 brightness) +{ + return lcd_cmd_one_param(lcd_data, LCD_BRIGHTNESS, brightness); +} + +static int lcd_cmd_display_on(struct lcd *lcd_data) +{ + return lcd_cmd_no_params(lcd_data, LCD_DISPLAY_ON); +} + +static int lcd_cmd_display_off(struct lcd *lcd_data) +{ + return lcd_cmd_no_params(lcd_data, LCD_DISPLAY_OFF); +} + +static int lcd_cmd_clear_screen(struct lcd *lcd_data) +{ + return lcd_cmd_no_params(lcd_data, LCD_CLEAR_SCREEN); +} + +static int lcd_cmd_backspace(struct lcd *lcd_data) +{ + return lcd_cmd_no_params(lcd_data, LCD_BACKSPACE); +} + +/* Note that this has to happen early on or the LCD module will not + process the command */ +static int lcd_load_custom_fonts(struct lcd *lcd_data) +{ + int count, i; + + for (i = 0; i < ARRAY_SIZE(custom_fonts); i++) { + count = i2c_master_send(lcd_data->client, + (const char *)&custom_fonts[i].font_cmd, + LCD_BYTES_PER_FONT_CMD); + if (count != LCD_BYTES_PER_FONT_CMD) { + pr_err("%s: i2c_master_send returns %d\n", __func__, count); + return -1; + } + } + return 0; +} + +static char lcd_translate_printable_char(char val) +{ + if (val == ASCII_BACKSLASH) + return CUSTOM_BACKSLASH; + else if (val == ASCII_TILDE) + return CUSTOM_TILDE; + + return val; +} + +/* From NHD-0216K3Z-NSW-BBY Display Module datasheet. */ +#define LCD_CURSOR_LINE_MULTIPLIER 0x40 + +static int lcd_cmd_set_cursor(struct lcd *lcd_data, u8 line, u8 col) +{ + u8 cursor; + + BUG_ON((line >= lcd_data->height) || (col >= lcd_data->width)); + + cursor = col + (LCD_CURSOR_LINE_MULTIPLIER * line); + return lcd_cmd_one_param(lcd_data, LCD_SET_CURSOR, cursor); +} + +/* + * Map a line on the lcd display to a line on the buffer. + * Note that the top line on the display (line 0) may not be line 0 on the + * buffer due to scrolling. + */ +static unsigned int lcd_line_to_buf_line(struct lcd *lcd_data, + unsigned int line) +{ + unsigned int buf_line; + + buf_line = line + lcd_data->top_line; + + if (buf_line >= lcd_data->height) + buf_line -= lcd_data->height; + + return buf_line; +} + +/* Returns a pointer to the line, column position in the lcd buffer */ +static char *lcd_buf_pointer(struct lcd *lcd_data, unsigned int line, + unsigned int col) +{ + unsigned int buf_line; + char *buf; + + if ((lcd_data->cursor_line >= lcd_data->height) || + (lcd_data->cursor_col >= lcd_data->width)) + return lcd_data->buffer; + + buf_line = lcd_line_to_buf_line(lcd_data, line); + + buf = lcd_data->buffer + (buf_line * lcd_data->width) + col; + + return buf; +} + +static void lcd_clear_buffer_line(struct lcd *lcd_data, int line) +{ + char *buf; + + BUG_ON(line >= lcd_data->height); + + buf = lcd_buf_pointer(lcd_data, line, 0); + memset(buf, ASCII_SPACE, lcd_data->width); +} + +static void lcd_clear_buffer(struct lcd *lcd_data) +{ + memset(lcd_data->buffer, ASCII_SPACE, + lcd_data->width * lcd_data->height); + lcd_data->cursor_line = 0; + lcd_data->cursor_col = 0; + lcd_data->top_line = 0; +} + +static void lcd_reprint_one_line(struct lcd *lcd_data, u8 line) +{ + char *buf = lcd_buf_pointer(lcd_data, line, 0); + + lcd_cmd_set_cursor(lcd_data, line, 0); + i2c_master_send(lcd_data->client, buf, lcd_data->width); +} + +static void lcd_print_top_n_lines(struct lcd *lcd_data, u8 lines) +{ + unsigned int disp_line = 0; + + while (disp_line < lines) + lcd_reprint_one_line(lcd_data, disp_line++); +} + +static void lcd_add_char_at_cursor(struct lcd *lcd_data, char val) +{ + char *buf; + + buf = lcd_buf_pointer(lcd_data, lcd_data->cursor_line, + lcd_data->cursor_col); + + *buf = val; + + if (lcd_data->cursor_col < (lcd_data->width - 1)) + lcd_data->cursor_col++; +} + +static void lcd_crlf(struct lcd *lcd_data) +{ + if (lcd_data->cursor_line < (lcd_data->height - 1)) { + /* Next line is blank, carriage return to beginning of line. */ + lcd_data->cursor_line++; + if (lcd_data->cursor_line >= lcd_data->height) + lcd_data->cursor_line = 0; + + } else { + /* Display is full. Scroll up one line. */ + lcd_data->top_line++; + if (lcd_data->top_line >= lcd_data->height) + lcd_data->top_line = 0; + + lcd_cmd_clear_screen(lcd_data); + lcd_clear_buffer_line(lcd_data, lcd_data->cursor_line); + lcd_print_top_n_lines(lcd_data, lcd_data->height); + } + + lcd_cmd_set_cursor(lcd_data, lcd_data->height - 1, 0); + lcd_data->cursor_col = 0; +} + +static void lcd_backspace(struct lcd *lcd_data) +{ + if (lcd_data->cursor_col > 0) { + lcd_cmd_backspace(lcd_data); + lcd_data->cursor_col--; + } +} + +static int lcd_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + struct lcd *lcd_data = tty->driver_data; + int buf_i = 0, left; + char val; + +#ifdef DEBUG + char *dbgbuf = kzalloc(count + 1, GFP_KERNEL); + strncpy(dbgbuf, buf, count); + pr_debug("\n%s: count=%d buf[0]=%02x --->%s<---\n", __func__, count, + buf[0], dbgbuf); +#endif /* DEBUG */ + + if (count == 0) { +#ifdef DEBUG + kfree(dbgbuf); +#endif /* DEBUG */ + return 0; + } + + while (buf_i < count) { + left = count - buf_i; + + /* process displayable chars */ + if ((0x20 <= buf[buf_i]) && (buf[buf_i] <= 0x7f)) { + while ((buf_i < count) && + ((0x20 <= buf[buf_i]) && (buf[buf_i] <= 0x7f))) { + val = lcd_translate_printable_char(buf[buf_i]); + lcd_add_char_at_cursor(lcd_data, val); + buf_i++; + } + + /* flush the line out to the display when we get to eol */ + lcd_reprint_one_line(lcd_data, lcd_data->cursor_line); + + /* + * ECMA-48 CSI sequences (from console_codes man page) + * + * ESC [ 2 J : erase whole display. + * ESC [ 2 K : erase whole line. + */ + } else if (buf[buf_i] == ASCII_ESC) { + if ((left >= 4) && + (buf[buf_i + 1] == '[') && + (buf[buf_i + 2] == '2') && + (buf[buf_i + 3] == 'J')) { + pr_debug("ESC [2J = clear screan\n"); + lcd_clear_buffer(lcd_data); + lcd_cmd_clear_screen(lcd_data); + buf_i += 4; + + } else if ((left >= 4) && + (buf[buf_i + 1] == '[') && + (buf[buf_i + 2] == '2') && + (buf[buf_i + 3] == 'K')) { + pr_debug("ESC [2K = clear line\n"); + lcd_clear_buffer_line(lcd_data, lcd_data->cursor_line); + lcd_reprint_one_line(lcd_data, lcd_data->cursor_line); + lcd_cmd_set_cursor(lcd_data, lcd_data->cursor_line, 0); + lcd_data->cursor_col = 0; + buf_i += 4; + + } else { + pr_debug("Unsupported escape sequence\n"); + buf_i++; + } + + } else if ((left >= 2) && + (buf[buf_i] == ASCII_CR) && (buf[buf_i + 1] == ASCII_LF)) { + pr_debug("ASCII_CR/LF\n"); + lcd_crlf(lcd_data); + buf_i += 2; + + } else if ((left >= 1) && (buf[buf_i] == ASCII_CR)) { + pr_debug("ASCII_CR\n"); + lcd_crlf(lcd_data); + buf_i++; + + } else if ((left >= 1) && (buf[buf_i] == ASCII_LF)) { + pr_debug("ASCII_LF\n"); + lcd_crlf(lcd_data); + buf_i++; + + } else if ((left >= 1) && (buf[buf_i] == ASCII_BS)) { + pr_debug("ASCII_BS\n"); + lcd_backspace(lcd_data); + buf_i++; + + } else { + pr_debug("%s - Unsupported command 0x%02x\n", __func__, buf[buf_i]); + buf_i++; + } + } + +#ifdef DEBUG + kfree(dbgbuf); +#endif /* DEBUG */ + return count; +} + +static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lcd *lcd_data = dev_get_drvdata(dev); + + return scnprintf(buf, 2, "%d\n", lcd_data->brightness); +} + +static ssize_t brightness_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lcd *lcd_data = dev_get_drvdata(dev); + int ret, brightness; + + ret = sscanf(buf, "%d", &brightness); + if (ret != 1) + return -EINVAL; + + if ((brightness < LCD_BRIGHTNESS_MIN) || + (brightness > LCD_BRIGHTNESS_MAX)) { + dev_err(lcd_data->dev, "out of range (%d to %d)\n", + LCD_BRIGHTNESS_MIN, LCD_BRIGHTNESS_MAX); + return -EINVAL; + } + + lcd_data->brightness = brightness; + lcd_cmd_backlight_brightness(lcd_data, brightness); + + return count; +} +static DEVICE_ATTR(brightness, S_IRUGO | S_IWUSR, brightness_show, brightness_store); + +static struct attribute *lcd_attrs[] = { + &dev_attr_brightness.attr, + NULL, +}; + +static struct attribute_group lcd_attr_group = { + .attrs = lcd_attrs, +}; + +static int lcd_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct lcd *lcd_data; + + lcd_data = &lcd_data_static[tty->index]; + if (lcd_data == NULL) + return -ENODEV; + + tty->driver_data = lcd_data; + + return tty_port_install(&lcd_data->port, driver, tty); +} + +static int lcd_open(struct tty_struct *tty, struct file *filp) +{ + struct lcd *lcd_data = tty->driver_data; + unsigned long flags; + + tty->driver_data = lcd_data; + spin_lock_irqsave(&lcd_data->port.lock, flags); + lcd_data->port.count++; + spin_unlock_irqrestore(&lcd_data->port.lock, flags); + tty_port_tty_set(&lcd_data->port, tty); + + return 0; +} + +static void lcd_close(struct tty_struct *tty, struct file *filp) +{ + struct lcd *lcd_data = tty->driver_data; + unsigned long flags; + bool last; + + spin_lock_irqsave(&lcd_data->port.lock, flags); + --lcd_data->port.count; + last = (lcd_data->port.count == 0); + spin_unlock_irqrestore(&lcd_data->port.lock, flags); + if (last) + tty_port_tty_set(&lcd_data->port, NULL); +} + +static int lcd_write_room(struct tty_struct *tty) +{ + struct lcd *lcd_data = tty->driver_data; + + return lcd_data->height * lcd_data->width; +} + +static const struct tty_operations lcd_ops = { + .install = lcd_install, + .open = lcd_open, + .close = lcd_close, + .write = lcd_write, + .write_room = lcd_write_room, +}; + +static int lcd_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + struct device_node *np = client->dev.of_node; + struct lcd *lcd_data; + struct tty_driver *lcd_tty_driver; + unsigned int width = 0, height = 0, i, brightness = 0; + char *buffer; + int ret = -ENOMEM; + + of_property_read_u32(np, "height", &height); + of_property_read_u32(np, "width", &width); + if ((width == 0) || (height == 0)) { + dev_err(&client->dev, + "Need to specify lcd width/height in device tree\n"); + ret = -EINVAL; + goto err_devtree; + } + + of_property_read_u32(np, "brightness", &brightness); + if ((brightness < LCD_BRIGHTNESS_MIN) || + (brightness > LCD_BRIGHTNESS_MAX)) { + dev_info(&client->dev, + "lcd brighness not set or out of range, defaulting to maximum\n"); + brightness = LCD_BRIGHTNESS_MAX; + } + + for (i = 0 ; i < MAX_LCDS ; i++) + if (lcd_data_static[i].client == NULL) + break; + if (i >= MAX_LCDS) { + ret = -ENODEV; + dev_warn(&client->dev, + "More than %d I2C LCD displays found. Giving up.\n", + MAX_LCDS); + goto err_devtree; + } + lcd_data = &lcd_data_static[i]; + + buffer = kzalloc(height * width, GFP_KERNEL); + if (!buffer) + goto err_devtree; + + i2c_set_clientdata(client, lcd_data); + + lcd_data->client = client; + lcd_data->dev = &client->dev; + lcd_data->height = height; + lcd_data->width = width; + lcd_data->buffer = buffer; + lcd_data->brightness = brightness; + + dev_set_drvdata(&client->dev, lcd_data); + tty_port_init(&lcd_data->port); + lcd_tty_driver = alloc_tty_driver(MAX_LCDS); + if (!lcd_tty_driver) + goto err_driver; + + lcd_tty_driver->driver_name = DRV_NAME; + lcd_tty_driver->name = DEV_NAME; + lcd_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + lcd_tty_driver->subtype = SERIAL_TYPE_NORMAL; + lcd_tty_driver->init_termios = tty_std_termios; + tty_set_operations(lcd_tty_driver, &lcd_ops); + + ret = tty_register_driver(lcd_tty_driver); + if (ret) + goto err_register; + + lcd_data->lcd_tty_driver = lcd_tty_driver; + + lcd_clear_buffer(lcd_data); + lcd_load_custom_fonts(lcd_data); + lcd_cmd_display_on(lcd_data); + lcd_cmd_backlight_brightness(lcd_data, brightness); + lcd_cmd_clear_screen(lcd_data); + + ret = sysfs_create_group(&lcd_data->dev->kobj, &lcd_attr_group); + if (ret) { + dev_err(lcd_data->dev, "Can't create sysfs attrs for lcd\n"); + return ret; + } + + dev_info(&client->dev, "LCD driver initialized\n"); + + return 0; + +err_register: + put_tty_driver(lcd_data->lcd_tty_driver); +err_driver: + kfree(buffer); +err_devtree: + return ret; +} + +static int __exit lcd_remove(struct i2c_client *client) +{ + struct lcd *lcd_data = i2c_get_clientdata(client); + + lcd_cmd_display_off(lcd_data); + + sysfs_remove_group(&lcd_data->dev->kobj, &lcd_attr_group); + tty_unregister_driver(lcd_data->lcd_tty_driver); + put_tty_driver(lcd_data->lcd_tty_driver); + kfree(lcd_data->buffer); + + return 0; +} + +static const struct of_device_id lcd_of_match[] = { + { .compatible = "newhaven,nhd-0216k3z-nsw-bbw", }, + {}, +}; + +static const struct i2c_device_id lcd_id[] = { + { DRV_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lcd_id); + +static struct i2c_driver lcd_i2c_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = lcd_of_match, + }, + .probe = lcd_probe, + .remove = lcd_remove, + .id_table = lcd_id, +}; + +static int __init lcd_init(void) +{ + return i2c_add_driver(&lcd_i2c_driver); +} +subsys_initcall(lcd_init); + +static void __exit lcd_exit(void) +{ + i2c_del_driver(&lcd_i2c_driver); +} +module_exit(lcd_exit); + +MODULE_DESCRIPTION("LCD 2x16"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 83d3d271ca15..5bd01054efe6 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -35,6 +35,15 @@ config HDMI bool endif # HAS_IOMEM +config FB_ALTERA_VIP + tristate "Altera VIP Frame Reader framebuffer support" + depends on FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This driver supports the Altera Video and Image Processing(VIP) + Frame Reader if VT source "drivers/video/console/Kconfig" diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig index 591a13a59787..d480b7aed00f 100644 --- a/drivers/video/fbdev/Kconfig +++ b/drivers/video/fbdev/Kconfig @@ -236,6 +236,16 @@ config FB_TILEBLITTING comment "Frame buffer hardware drivers" depends on FB +config FB_ALTERA_VIP + tristate "Altera VIP Frame Reader framebuffer support" + depends on FB + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This driver supports the Altera Video and Image Processing(VIP) + Frame Reader + config FB_GRVGA tristate "Aeroflex Gaisler framebuffer support" depends on FB && SPARC diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile index 13c900320c2c..49ca14827e2c 100644 --- a/drivers/video/fbdev/Makefile +++ b/drivers/video/fbdev/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_FB_MACMODES) += macmodes.o obj-$(CONFIG_FB_WMT_GE_ROPS) += wmt_ge_rops.o # Hardware specific drivers go first +obj-$(CONFIG_FB_ALTERA_VIP) += altvipfb.o obj-$(CONFIG_FB_AMIGA) += amifb.o c2p_planar.o obj-$(CONFIG_FB_ARC) += arcfb.o obj-$(CONFIG_FB_CLPS711X) += clps711x-fb.o diff --git a/drivers/video/fbdev/altvipfb.c b/drivers/video/fbdev/altvipfb.c new file mode 100644 index 000000000000..b247858ba43a --- /dev/null +++ b/drivers/video/fbdev/altvipfb.c @@ -0,0 +1,303 @@ +/* + * altvipfb.c -- Altera Video and Image Processing(VIP) Frame Reader driver + * + * This is based on a driver made by Thomas Chou <thomas@wytron.com.tw> and + * Walter Goossens <waltergoossens@home.nl> This driver supports the Altera VIP + * Frame Reader component. More info on the hardware can be found in + * the Altera Video and Image Processing Suite User Guide at this address + * http://www.altera.com/literature/ug/ug_vip.pdf. + * + * 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. + * + */ + +#include <linux/dma-mapping.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#define PALETTE_SIZE 256 +#define DRIVER_NAME "altvipfb" + +/* control registers */ +#define ALTVIPFB_CONTROL 0 +#define ALTVIPFB_FRAME_SELECT 12 +#define ALTVIPFB_FRAME0_BASE_ADDRESS 16 +#define ALTVIPFB_FRAME0_NUM_WORDS 20 +#define ALTVIPFB_FRAME0_SAMPLES 24 +#define ALTVIPFB_FRAME0_WIDTH 32 +#define ALTVIPFB_FRAME0_HEIGHT 36 +#define ALTVIPFB_FRAME0_INTERLACED 40 + +struct altvipfb_type; + +struct altvipfb_dev { + struct platform_device *pdev; + struct fb_info info; + struct resource *reg_res; + void __iomem *base; + int mem_word_width; + u32 pseudo_palette[PALETTE_SIZE]; +}; + +static int altvipfb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, struct fb_info *info) +{ + /* + * Set a single color register. The values supplied have a 32 bit + * magnitude. + * Return != 0 for invalid regno. + */ + + if (regno > 255) + return 1; + + red >>= 8; + green >>= 8; + blue >>= 8; + + if (regno < 255) { + ((u32 *)info->pseudo_palette)[regno] = + ((red & 255) << 16) | ((green & 255) << 8) | (blue & 255); + } + + return 0; +} + +static struct fb_ops altvipfb_ops = { + .owner = THIS_MODULE, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_setcolreg = altvipfb_setcolreg, +}; + +static int altvipfb_of_setup(struct altvipfb_dev *fbdev) +{ + struct device_node *np = fbdev->pdev->dev.of_node; + int ret; + u32 bits_per_color; + + ret = of_property_read_u32(np, "max-width", &fbdev->info.var.xres); + if (ret) { + dev_err(&fbdev->pdev->dev, + "Missing required parameter 'max-width'"); + return ret; + } + fbdev->info.var.xres_virtual = fbdev->info.var.xres, + + ret = of_property_read_u32(np, "max-height", &fbdev->info.var.yres); + if (ret) { + dev_err(&fbdev->pdev->dev, + "Missing required parameter 'max-height'"); + return ret; + } + fbdev->info.var.yres_virtual = fbdev->info.var.yres; + + ret = of_property_read_u32(np, "bits-per-color", &bits_per_color); + if (ret) { + dev_err(&fbdev->pdev->dev, + "Missing required parameter 'bits-per-color'"); + return ret; + } + if (bits_per_color != 8) { + dev_err(&fbdev->pdev->dev, + "bits-per-color is set to %i. Curently only 8 is supported.", + bits_per_color); + return -ENODEV; + } + fbdev->info.var.bits_per_pixel = 32; + + ret = of_property_read_u32(np, "mem-word-width", + &fbdev->mem_word_width); + if (ret) { + dev_err(&fbdev->pdev->dev, + "Missing required parameter 'mem-word-width'"); + return ret; + } + if (!(fbdev->mem_word_width >= 32 && fbdev->mem_word_width % 32 == 0)) { + dev_err(&fbdev->pdev->dev, + "mem-word-width is set to %i. must be >= 32 and multiple of 32.", + fbdev->mem_word_width); + return -ENODEV; + } + + return 0; +} + +static void altvipfb_start_hw(struct altvipfb_dev *fbdev) +{ + writel(fbdev->info.fix.smem_start, fbdev->base + + ALTVIPFB_FRAME0_BASE_ADDRESS); + writel(fbdev->info.var.xres * fbdev->info.var.yres / + (fbdev->mem_word_width/32), + fbdev->base + ALTVIPFB_FRAME0_NUM_WORDS); + writel(fbdev->info.var.xres * fbdev->info.var.yres, + fbdev->base + ALTVIPFB_FRAME0_SAMPLES); + writel(fbdev->info.var.xres, fbdev->base + ALTVIPFB_FRAME0_WIDTH); + writel(fbdev->info.var.yres, fbdev->base + ALTVIPFB_FRAME0_HEIGHT); + writel(3, fbdev->base + ALTVIPFB_FRAME0_INTERLACED); + writel(0, fbdev->base + ALTVIPFB_FRAME_SELECT); + + /* Finally set the control register to 1 to start streaming */ + writel(1, fbdev->base + ALTVIPFB_CONTROL); +} + +static void altvipfb_disable_hw(struct altvipfb_dev *fbdev) +{ + /* set the control register to 0 to stop streaming */ + writel(0, fbdev->base + ALTVIPFB_CONTROL); +} + + +static int altvipfb_setup_fb_info(struct altvipfb_dev *fbdev) +{ + struct fb_info *info = &fbdev->info; + int ret; + + strcpy(info->fix.id, DRIVER_NAME); + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.accel = FB_ACCEL_NONE; + + info->fbops = &altvipfb_ops; + info->var.activate = FB_ACTIVATE_NOW; + info->var.height = -1; + info->var.width = -1; + info->var.vmode = FB_VMODE_NONINTERLACED; + + ret = altvipfb_of_setup(fbdev); + if (ret) + return ret; + + /* settings for 32bit pixels */ + info->var.red.offset = 16; + info->var.red.length = 8; + info->var.red.msb_right = 0; + info->var.green.offset = 8; + info->var.green.length = 8; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->var.blue.msb_right = 0; + + info->fix.line_length = (info->var.xres * + (info->var.bits_per_pixel >> 3)); + info->fix.smem_len = info->fix.line_length * info->var.yres; + + info->pseudo_palette = fbdev->pseudo_palette; + info->flags = FBINFO_FLAG_DEFAULT; + + return 0; +} + +static int altvipfb_probe(struct platform_device *pdev) +{ + int retval; + void *fbmem_virt; + struct altvipfb_dev *fbdev; + + fbdev = devm_kzalloc(&pdev->dev, sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) + return -ENOMEM; + + fbdev->pdev = pdev; + fbdev->reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!fbdev->reg_res) + return -ENODEV; + + retval = altvipfb_setup_fb_info(fbdev); + + fbmem_virt = dma_alloc_coherent(NULL, + fbdev->info.fix.smem_len, + (void *)&(fbdev->info.fix.smem_start), + GFP_KERNEL); + if (!fbmem_virt) { + dev_err(&pdev->dev, + "altvipfb: unable to allocate %d Bytes fb memory\n", + fbdev->info.fix.smem_len); + return retval; + } + + fbdev->info.screen_base = fbmem_virt; + + retval = fb_alloc_cmap(&fbdev->info.cmap, PALETTE_SIZE, 0); + if (retval < 0) + goto err_dma_free; + + platform_set_drvdata(pdev, fbdev); + + fbdev->base = devm_ioremap_resource(&pdev->dev, fbdev->reg_res); + if (IS_ERR(fbdev->base)) { + dev_err(&pdev->dev, "devm_ioremap_resource failed\n"); + retval = PTR_ERR(fbdev->base); + goto err_dealloc_cmap; + } + + altvipfb_start_hw(fbdev); + + retval = register_framebuffer(&fbdev->info); + if (retval < 0) + goto err_dealloc_cmap; + + dev_info(&pdev->dev, "fb%d: %s frame buffer device at 0x%x+0x%x\n", + fbdev->info.node, fbdev->info.fix.id, + (unsigned)fbdev->info.fix.smem_start, + fbdev->info.fix.smem_len); + + return 0; + +err_dealloc_cmap: + fb_dealloc_cmap(&fbdev->info.cmap); +err_dma_free: + dma_free_coherent(NULL, fbdev->info.fix.smem_len, fbmem_virt, + fbdev->info.fix.smem_start); + return retval; +} + +static int altvipfb_remove(struct platform_device *dev) +{ + struct altvipfb_dev *fbdev = platform_get_drvdata(dev); + + if (fbdev) { + unregister_framebuffer(&fbdev->info); + fb_dealloc_cmap(&fbdev->info.cmap); + dma_free_coherent(NULL, fbdev->info.fix.smem_len, + fbdev->info.screen_base, + fbdev->info.fix.smem_start); + altvipfb_disable_hw(fbdev); + } + return 0; +} + + +static struct of_device_id altvipfb_match[] = { + { .compatible = "altr,vip-frame-reader-1.0" }, + { .compatible = "altr,vip-frame-reader-9.1" }, + {}, +}; +MODULE_DEVICE_TABLE(of, altvipfb_match); + +static struct platform_driver altvipfb_driver = { + .probe = altvipfb_probe, + .remove = altvipfb_remove, + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .of_match_table = altvipfb_match, + }, +}; +module_platform_driver(altvipfb_driver); + +MODULE_DESCRIPTION("Altera VIP Frame Reader framebuffer driver"); +MODULE_AUTHOR("Chris Rauer <crauer@altera.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/altera_hwmutex.h b/include/linux/altera_hwmutex.h new file mode 100644 index 000000000000..166502b379f6 --- /dev/null +++ b/include/linux/altera_hwmutex.h @@ -0,0 +1,41 @@ +/* + * Copyright Altera Corporation (C) 2013. All rights reserved + * + * 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/>. + */ +#ifndef _ALTERA_MUTEX_H +#define _ALTERA_MUTEX_H + +#include <linux/device.h> +#include <linux/platform_device.h> + +struct altera_mutex { + struct list_head list; + struct platform_device *pdev; + struct mutex lock; + void __iomem *regs; + bool requested; +}; + +extern struct altera_mutex *altera_mutex_request(struct device_node *mutex_np); +extern int altera_mutex_free(struct altera_mutex *mutex); + +extern int altera_mutex_lock(struct altera_mutex *mutex, u16 owner, u16 value); + +extern int altera_mutex_trylock(struct altera_mutex *mutex, u16 owner, + u16 value); +extern int altera_mutex_unlock(struct altera_mutex *mutex, u16 owner); +extern int altera_mutex_owned(struct altera_mutex *mutex, u16 owner); +extern int altera_mutex_is_locked(struct altera_mutex *mutex); + +#endif /* _ALTERA_MUTEX_H */ diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h index eec7c2478b0d..244f5f5301d0 100644 --- a/include/linux/fpga/fpga-mgr.h +++ b/include/linux/fpga/fpga-mgr.h @@ -140,6 +140,9 @@ struct fpga_manager { enum fpga_mgr_states state; const struct fpga_manager_ops *mops; void *priv; +#if IS_ENABLED(CONFIG_FPGA_MGR_DEBUG_FS) + void *debugfs; +#endif }; #define to_fpga_manager(d) container_of(d, struct fpga_manager, dev) diff --git a/include/linux/intel-service-client.h b/include/linux/intel-service-client.h new file mode 100644 index 000000000000..88f0d9f298a6 --- /dev/null +++ b/include/linux/intel-service-client.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2018, Intel Corporation + */ + +#ifndef __INTEL_SERVICE_CLIENT_H +#define __INTEL_SERVICE_CLIENT_H + +/* + * Service layer driver supports client names + * @fpga: for FPGA configuration + * @rsu: for remote status update + */ +#define SVC_CLIENT_FPGA "fpga" +#define SVC_CLIENT_RSU "rsu" + +/* + * Status of the sent command, in bit number + * @SVC_COMMAND_STATUS_RECONFIG_REQUEST_OK: + * Secure firmware accepts the request of FPGA reconfiguration. + * @SVC_STATUS_RECONFIG_BUFFER_SUBMITTED: + * Service client successfully submits FPGA configuration + * data buffer to secure firmware. + * @SVC_COMMAND_STATUS_RECONFIG_BUFFER_DONE: + * Secure firmware completes data process, ready to accept the + * next WRITE transaction. + * @SVC_COMMAND_STATUS_RECONFIG_COMPLETED: + * Secure firmware completes FPGA configuration successfully, FPGA should + * be in user mode. + * @SVC_COMMAND_STATUS_RECONFIG_BUSY: + * FPGA configuration is still in process. + * @SVC_COMMAND_STATUS_RECONFIG_ERROR: + * Error encountered during FPGA configuration. + */ +#define SVC_STATUS_RECONFIG_REQUEST_OK 0 +#define SVC_STATUS_RECONFIG_BUFFER_SUBMITTED 1 +#define SVC_STATUS_RECONFIG_BUFFER_DONE 2 +#define SVC_STATUS_RECONFIG_COMPLETED 3 +#define SVC_STATUS_RECONFIG_BUSY 4 +#define SVC_STATUS_RECONFIG_ERROR 5 +#define SVC_STATUS_RSU_OK 6 + +/* + * Flag bit for COMMAND_RECONFIG + * @COMMAND_RECONFIG_FLAG_PARTIAL + * Set to FPGA configuration type (full or partial), the default + * is full reconfig. + */ +#define COMMAND_RECONFIG_FLAG_PARTIAL 0 + +/* Timeout settings for FPGA manager driver */ +#define SVC_RECONFIG_REQUEST_TIMEOUT_MS 100 +#define SVC_RECONFIG_BUFFER_TIMEOUT_MS 240 + +/* Timeout settings for RSU driver */ +#define SVC_RSU_REQUEST_TIMEOUT_MS 300 + +struct intel_svc_chan; + +/** + * enum intel_svc_command_code - supporting service commands + * @COMMAND_NOOP: do 'dummy' request for integration/debug/trouble-shootings + * @COMMAND_RECONFIG: ask for FPGA configuration preparation, return status + * is SVC_STATUS_RECONFIG_REQUEST_OK + * @COMMAND_RECONFIG_DATA_SUBMIT: submit buffer(s) of bit-stream data for the + * FPGA configuration, return status is SVC_STATUS_RECONFIG_BUFFER_SUBMITTED, + * or SVC_STATUS_RECONFIG_ERROR + * @COMMAND_RECONFIG_DATA_CLAIM: check the status of the configuration, return + * status is SVC_STATUS_RECONFIG_COMPLETED, or SVC_STATUS_RECONFIG_BUSY, or + * SVC_STATUS_RECONFIG_ERROR + * @COMMAND_RECONFIG_STATUS: check the status of the configuration, return + * status is SVC_STATUS_RECONFIG_COMPLETED, or SVC_STATUS_RECONFIG_BUSY, or + * SVC_STATUS_RECONFIG_ERROR + * @COMMAND_RSU_STATUS: request remote system update boot log + * status is SVC_STATUS_RSU_ERROR or log data + * @COMMAND_RSU_UPDATE: set the offset of the bitstream to boot after reboot + * status is SVC_STATUS_RSU_OK or SVC_STATUS_RSU_ERROR + */ +enum intel_svc_command_code { + COMMAND_NOOP = 0, + COMMAND_RECONFIG, + COMMAND_RECONFIG_DATA_SUBMIT, + COMMAND_RECONFIG_DATA_CLAIM, + COMMAND_RECONFIG_STATUS, + COMMAND_RSU_STATUS, + COMMAND_RSU_UPDATE +}; + +/** + * struct intel_svc_client_msg - message sent by client to service + * @command: service command + * @payload: starting address of data need be processed + * @payload_length: data size in bytes + * @arg: args to be passed via registers and not physically mapped buffers + */ +struct intel_svc_client_msg { + void *payload; + size_t payload_length; + enum intel_svc_command_code command; + u64 arg[3]; +}; + +/** + * struct intel_command_reconfig_payload - reconfig payload + * @flags: flag bit for the type of FPGA configuration + */ +struct intel_command_reconfig_payload { + u32 flags; +}; + +/** + * struct intel_svc_c_data - callback data structure from service layer + * @status: the status of sent command + * @kaddr1-3: used when status is SVC_COMMAND_STATUS_RECONFIG_BUFFER_DONE + * + * kaddr1 - address of 1st completed data block. + * kaddr2 - address of 2nd completed data block. + * kaddr3 - address of 3rd completed data block. + */ +struct intel_svc_c_data { + u32 status; + void *kaddr1; + void *kaddr2; + void *kaddr3; +}; + +/** + * struct intel_svc_client - service client structure + * @dev: the client device + * @receive_callback: callback to provide service client the received data + * @priv: client private data + */ +struct intel_svc_client { + struct device *dev; + void (*receive_cb)(struct intel_svc_client *client, + struct intel_svc_c_data *data); + void *priv; +}; + +/** + * request_svc_channel_byname() - request service channel + * @client: identity of the client requesting the channel + * @name: supporting client name defined above + * + * Return: a pointer to channel assigned to the client on success, + * or ERR_PTR() on error. + */ +struct intel_svc_chan +*request_svc_channel_byname(struct intel_svc_client *client, + const char *name); + +/** + * free_svc_channel() - free service channel. + * @chan: service channel to be freed + */ +void free_svc_channel(struct intel_svc_chan *chan); + +/** + * intel_svc_allocate_memory() - allocate the momory + * @chan: service channel assigned to the client + * @size: number of bytes client requests + * + * Service layer allocates the requested number of bytes from the memory + * pool for the client. + * + * Return: the starting address of allocated memory on success, or + * ERR_PTR() on error. + */ +void *intel_svc_allocate_memory(struct intel_svc_chan *chan, size_t size); + +/** + * intel_svc_free_memory() - free allocated memory + * @chan: service channel assigned to the client + * @kaddr: starting address of memory to be free back to pool + */ +void intel_svc_free_memory(struct intel_svc_chan *chan, void *kaddr); + +/** + * intel_svc_send() - send a message to the remote + * @chan: service channel assigned to the client + * @msg: message data to be sent, in the format of struct intel_svc_client_msg + * + * Return: positive value for successful submission to the data queue created + * by service layer driver, or -ENOBUFS if the data queue FIFO is full. + */ +int intel_svc_send(struct intel_svc_chan *chan, void *msg); + +/** + * intel_svc_done() - complete service request + * @chan: service channel assigned to the client + * + * This function is used by service client to inform service layer that + * client's service requests are completed, or there is an error in the + * request process. + */ +void intel_svc_done(struct intel_svc_chan *chan); +#endif + diff --git a/include/linux/mfd/altera-a10sr.h b/include/linux/mfd/altera-a10sr.h index 45a5e6e7db54..d177744be95f 100644 --- a/include/linux/mfd/altera-a10sr.h +++ b/include/linux/mfd/altera-a10sr.h @@ -60,13 +60,71 @@ #define ALTR_A10SR_IN_VALID_RANGE_HI 15 #define ALTR_A10SR_PWR_GOOD1_REG 0x08 /* Power Good1 Read */ +/* Power Good #1 Register Bit Definitions */ +#define ALTR_A10SR_PG1_OP_FLAG_SHIFT 7 /* Power On Complete */ +#define ALTR_A10SR_PG1_1V8_SHIFT 6 /* 1.8V Power Good */ +#define ALTR_A10SR_PG1_2V5_SHIFT 5 /* 2.5V Power Good */ +#define ALTR_A10SR_PG1_3V3_SHIFT 4 /* 3.3V Power Good */ +#define ALTR_A10SR_PG1_5V0_SHIFT 3 /* 5.0V Power Good */ +#define ALTR_A10SR_PG1_0V9_SHIFT 2 /* 0.9V Power Good */ +#define ALTR_A10SR_PG1_0V95_SHIFT 1 /* 0.95V Power Good */ +#define ALTR_A10SR_PG1_1V0_SHIFT 0 /* 1.0V Power Good */ + #define ALTR_A10SR_PWR_GOOD2_REG 0x0A /* Power Good2 Read */ +/* Power Good #2 Register Bit Definitions */ +#define ALTR_A10SR_PG2_HPS_SHIFT 7 /* HPS Power Good */ +#define ALTR_A10SR_PG2_HL_HPS_SHIFT 6 /* HILOHPS_VDD Power Good */ +#define ALTR_A10SR_PG2_HL_VDD_SHIFT 5 /* HILO VDD Power Good */ +#define ALTR_A10SR_PG2_HL_VDDQ_SHIFT 4 /* HILO VDDQ Power Good */ +#define ALTR_A10SR_PG2_FMCAVADJ_SHIFT 3 /* FMCA VADJ Power Good */ +#define ALTR_A10SR_PG2_FMCBVADJ_SHIFT 2 /* FMCB VADJ Power Good */ +#define ALTR_A10SR_PG2_FAC2MP_SHIFT 1 /* FAC2MP Power Good */ +#define ALTR_A10SR_PG2_FBC2MP_SHIFT 0 /* FBC2MP Power Good */ + #define ALTR_A10SR_PWR_GOOD3_REG 0x0C /* Power Good3 Read */ +/* Power Good #3 Register Bit Definitions */ +#define ALTR_A10SR_PG3_FAM2C_SHIFT 7 /* FAM2C Power Good */ +#define ALTR_A10SR_PG3_10V_FAIL_SHIFT 6 /* 10V Fail n */ +#define ALTR_A10SR_PG3_BF_PR_SHIFT 5 /* BF Present n */ +#define ALTR_A10SR_PG3_FILE_PR_SHIFT 4 /* File Present n */ +#define ALTR_A10SR_PG3_FMCA_PR_SHIFT 3 /* FMCA Present n */ +#define ALTR_A10SR_PG3_FMCB_PR_SHIFT 2 /* FMCB Present n */ +#define ALTR_A10SR_PG3_PCIE_PR_SHIFT 1 /* PCIE Present n */ +#define ALTR_A10SR_PG3_PCIE_WAKE_SHIFT 0 /* PCIe Wake N */ + #define ALTR_A10SR_FMCAB_REG 0x0E /* FMCA/B & PCIe Pwr Enable */ +/* FMCA/B & PCIe Power Bit Definitions */ +#define ALTR_A10SR_PCIE_EN_SHIFT 7 /* PCIe Pwr Enable */ +#define ALTR_A10SR_PCIE_AUXEN_SHIFT 6 /* PCIe Aux Pwr Enable */ +#define ALTR_A10SR_FMCA_EN_SHIFT 5 /* FMCA Pwr Enable */ +#define ALTR_A10SR_FMCA_AUXEN_SHIFT 4 /* FMCA Aux Pwr Enable */ +#define ALTR_A10SR_FMCB_EN_SHIFT 3 /* FMCB Pwr Enable */ +#define ALTR_A10SR_FMCB_AUXEN_SHIFT 2 /* FMCB Aux Pwr Enable */ + #define ALTR_A10SR_HPS_RST_REG 0x10 /* HPS Reset */ +#define ALTR_A10SR_HPS_UARTA_RSTN_SHIFT 7 /* UARTA Reset n */ +#define ALTR_A10SR_HPS_WARM_RSTN_SHIFT 6 /* WARM Reset n */ +#define ALTR_A10SR_HPS_WARM_RST1N_SHIFT 5 /* WARM Reset1 n */ +#define ALTR_A10SR_HPS_COLD_RSTN_SHIFT 4 /* COLD Reset n */ +#define ALTR_A10SR_HPS_NPOR_SHIFT 3 /* N Power On Reset */ +#define ALTR_A10SR_HPS_NRST_SHIFT 2 /* N Reset */ +#define ALTR_A10SR_HPS_ENET_RSTN_SHIFT 1 /* Ethernet Reset n */ +#define ALTR_A10SR_HPS_ENET_INTN_SHIFT 0 /* Ethernet IRQ n */ + #define ALTR_A10SR_USB_QSPI_REG 0x12 /* USB, BQSPI, FILE Reset */ +#define ALTR_A10SR_USB_RST_SHIFT 7 /* USB Reset */ +#define ALTR_A10SR_BQSPI_RST_N_SHIFT 6 /* BQSPI Reset n */ +#define ALTR_A10SR_FILE_RST_N_SHIFT 5 /* FILE Reset n */ +#define ALTR_A10SR_PCIE_PERST_N_SHIFT 4 /* PCIe PE Reset n */ + #define ALTR_A10SR_SFPA_REG 0x14 /* SFPA Control Reg */ #define ALTR_A10SR_SFPB_REG 0x16 /* SFPB Control Reg */ +/* SFPA Bit Definitions */ +#define ALTR_A10SR_SFP_TXDIS_SHIFT 7 /* SFPA TX Disable */ +#define ALTR_A10SR_SFP_RATESEL10 0x60 /* SFPA_Rate Select [1:0] */ +#define ALTR_A10SR_SFP_LOS_SHIFT 4 /* SFPA LOS */ +#define ALTR_A10SR_SFP_FAULT_SHIFT 3 /* SFPA Fault */ + #define ALTR_A10SR_I2C_M_REG 0x18 /* I2C Master Select */ #define ALTR_A10SR_WARM_RST_REG 0x1A /* HPS Warm Reset */ #define ALTR_A10SR_WR_KEY_REG 0x1C /* HPS Warm Reset Key */ |