Merge tag 'v4.17.18' into dev-4.17 This is the 4.17.18 stable release Signed-off-by: Joel Stanley <joel@jms.id.au>
diff --git a/Documentation/devicetree/bindings/clock/nuvoton,npcm750-clk.txt b/Documentation/devicetree/bindings/clock/nuvoton,npcm750-clk.txt new file mode 100644 index 0000000..f820645 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/nuvoton,npcm750-clk.txt
@@ -0,0 +1,100 @@ +* Nuvoton NPCM7XX Clock Controller + +Nuvoton Poleg BMC NPCM7XX contains an integrated clock controller, which +generates and supplies clocks to all modules within the BMC. + +External clocks: + +There are six fixed clocks that are generated outside the BMC. All clocks are of +a known fixed value that cannot be changed. clk_refclk, clk_mcbypck and +clk_sysbypck are inputs to the clock controller. +clk_rg1refck, clk_rg2refck and clk_xin are external clocks suppling the +network. They are set on the device tree, but not used by the clock module. The +network devices use them directly. +Example can be found below. + +All available clocks are defined as preprocessor macros in: +dt-bindings/clock/nuvoton,npcm7xx-clock.h +and can be reused as DT sources. + +Required Properties of clock controller: + + - compatible: "nuvoton,npcm750-clk" : for clock controller of Nuvoton + Poleg BMC NPCM750 + + - reg: physical base address of the clock controller and length of + memory mapped region. + + - #clock-cells: should be 1. + +Example: Clock controller node: + + clk: clock-controller@f0801000 { + compatible = "nuvoton,npcm750-clk"; + #clock-cells = <1>; + reg = <0xf0801000 0x1000>; + clock-names = "refclk", "sysbypck", "mcbypck"; + clocks = <&clk_refclk>, <&clk_sysbypck>, <&clk_mcbypck>; + }; + +Example: Required external clocks for network: + + /* external reference clock */ + clk_refclk: clk-refclk { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + clock-output-names = "refclk"; + }; + + /* external reference clock for cpu. float in normal operation */ + clk_sysbypck: clk-sysbypck { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <800000000>; + clock-output-names = "sysbypck"; + }; + + /* external reference clock for MC. float in normal operation */ + clk_mcbypck: clk-mcbypck { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <800000000>; + clock-output-names = "mcbypck"; + }; + + /* external clock signal rg1refck, supplied by the phy */ + clk_rg1refck: clk-rg1refck { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <125000000>; + clock-output-names = "clk_rg1refck"; + }; + + /* external clock signal rg2refck, supplied by the phy */ + clk_rg2refck: clk-rg2refck { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <125000000>; + clock-output-names = "clk_rg2refck"; + }; + + clk_xin: clk-xin { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <50000000>; + clock-output-names = "clk_xin"; + }; + + +Example: GMAC controller node that consumes two clocks: a generated clk by the +clock controller and a fixed clock from DT (clk_rg1refck). + + ethernet0: ethernet@f0802000 { + compatible = "snps,dwmac"; + reg = <0xf0802000 0x2000>; + interrupts = <0 14 4>; + interrupt-names = "macirq"; + clocks = <&clk_rg1refck>, <&clk NPCM7XX_CLK_AHB>; + clock-names = "stmmaceth", "clk_gmac"; + };
diff --git a/Documentation/devicetree/bindings/fsi/fsi-master-ast-cf.txt b/Documentation/devicetree/bindings/fsi/fsi-master-ast-cf.txt new file mode 100644 index 0000000..431bf8a --- /dev/null +++ b/Documentation/devicetree/bindings/fsi/fsi-master-ast-cf.txt
@@ -0,0 +1,36 @@ +Device-tree bindings for ColdFire offloaded gpio-based FSI master driver +------------------------------------------------------------------------ + +Required properties: + - compatible = + "aspeed,ast2400-cf-fsi-master" for an AST2400 based system + or + "aspeed,ast2500-cf-fsi-master" for an AST2500 based system + + - clock-gpios = <gpio-descriptor>; : GPIO for FSI clock + - data-gpios = <gpio-descriptor>; : GPIO for FSI data signal + - enable-gpios = <gpio-descriptor>; : GPIO for enable signal + - trans-gpios = <gpio-descriptor>; : GPIO for voltage translator enable + - mux-gpios = <gpio-descriptor>; : GPIO for pin multiplexing with other + functions (eg, external FSI masters) + - memory-region = <phandle>; : Reference to the reserved memory for + the ColdFire. Must be 2M aligned on + AST2400 and 1M aligned on AST2500 + - aspeed,sram = <phandle>; : Reference to the SRAM node. + - aspeed,cvic = <phandle>; : Reference to the CVIC node. + +Examples: + + fsi-master { + compatible = "aspeed,ast2500-cf-fsi-master", "fsi-master"; + + clock-gpios = <&gpio 0>; + data-gpios = <&gpio 1>; + enable-gpios = <&gpio 2>; + trans-gpios = <&gpio 3>; + mux-gpios = <&gpio 4>; + + memory-region = <&coldfire_memory>; + sram = <&sram>; + cvic = <&cvic>; + }
diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/max31785.txt b/Documentation/devicetree/bindings/hwmon/pmbus/max31785.txt new file mode 100644 index 0000000..af9578e --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/pmbus/max31785.txt
@@ -0,0 +1,158 @@ +Bindings for the Maxim MAX31785 Intelligent Fan Controller +========================================================== + +Reference: + +https://datasheets.maximintegrated.com/en/ds/MAX31785.pdf + +Required properties: +- compatible : One of "maxim,max31785" or "maxim,max31785a" +- reg : I2C address, one of 0x52, 0x53, 0x54, 0x55. +- #address-cells : Must be 1 +- #size-cells : Must be 0 +- #thermal-sensor-cells : Should be 1. The device supports: + - One internal sensor + - Four external I2C digital sensors + - Six external thermal diodes + +Optional properties: +- use-stored-presence : Do not treat the devicetree description as canon for + fan presence (the 'installed' bit of FAN_CONFIG_*). + Instead, rely on the on the default value store of + the device to populate it. + +Capabilities are configured through subnodes of the controller's node. + +Fans +---- + +Only fans with subnodes present will be considered as installed. If +use-stored-presence is present in the parent node, then only fans that are both +defined in the devicetree and have their installed bit set are considered +installed. + +Required subnode properties: +- compatible : Must be "pmbus-fan" +- reg : The PMBus page the properties apply to. +- #cooling-cells : Should be 2. See the thermal bindings at [1]. +- maxim,fan-rotor-input : The type of rotor measurement provided to the + controller. Must be either "tach" for tachometer + pulses or "lock" for a locked-rotor signal. +- maxim,fan-lock-polarity: Required iff maxim,fan-rotor-input is "lock". Valid + values are "low" for active low, "high" for active + high. + +Optional subnode properties: +- fan-mode : "rpm" or "pwm". Default value is "pwm". +- tach-pulses : Tachometer pulses per revolution. Valid values are + 1, 2, 3 or 4. The default is 1. +- cooling-min-level : Smallest cooling state accepted. See [1]. +- cooling-max-level : Largest cooling state accepted. See [1]. +- maxim,fan-no-fault-ramp: Do not ramp the fan to 100% PWM duty on detecting a + fan fault +- maxim,fan-startup : The number of rotations required before taking + emergency action for an unresponsive fan and driving + it with 100% or 0% PWM duty, depending on the state + of maxim,fan-no-fault-ramp. Valid values are 0 + (automatic spin-up disabled), 2, 4, or 8. Default + value is 0. +- maxim,fan-health : Enable automated fan health check +- maxim,fan-ramp : Configures how fast the device ramps the PWM duty + cycle from one value to another. Valid values are 0 + to 7 inclusive, with values 0 - 2 configuring a + 1000ms update rate and 1 - 3% duty respective duty + increase, and 3 - 7 a 200ms update rate with a 1 - + 5% respective duty increase. Default value is 0. +- maxim,fan-no-watchdog : Do not ramp fan to 100% PWM duty on failure to + update desired fan rate inside 10s. This implies + maxim,tmp-no-fault-ramp +- maxim,tmp-no-fault-ramp: Do not ramp fan to 100% PWM duty on temperature + sensor fault detection. This implies + maxim,fan-no-watchdog +- maxim,tmp-hysteresis : The temperature hysteresis used to determine + transitions to lower fan speed bands in the + temperature/fan rate lookup table. Valid values are + 2, 4, 6 or 8 (degrees celcius). Default value is 2. +- maxim,fan-dual-tach : Enable dual tachometer functionality +- maxim,fan-pwm-freq : The PWM frequency. Valid values are 30, 50, 100, 150 + and 25000 (Hz). Default value is 30Hz. +- maxim,fan-lookup-table : A 16-element cell array of alternating temperature + and rate values representing the look up table. The + rate units are set through the fan-mode property. +- maxim,fan-fault-pin-mon: Ramp fans to 100% PWM duty when the FAULT pin is + asserted + +Temperature +----------- + +Required subnode properties: +- compatible : Must be "pmbus-temperature" +- reg : The PMBus page the properties apply to. + +Optional subnode properties: +- maxim,tmp-offset : Valid values are 0 - 30 (degrees celcius) inclusive. + Default value is 0. +- maxim,tmp-fans : An array of phandles to fans controlled by the + current temperature sensor. + +[1] Documentation/devicetree/bindings/thermal/thermal.txt + +Example: + fan-max31785: max31785@52 { + reg = <0x52>; + compatible = "maxim,max31785"; + #address-cells = <1>; + #size-cells = <0>; + #thermal-sensor-cells = <1>; + + fan@0 { + compatible = "pmbus-fan"; + reg = <0>; + mode = "rpm"; + tach-pulses = <1>; + + #cooling-cells = <2>; + cooling-min-level = <0>; + cooling-max-level = <9>; + + maxim,fan-rotor-input = "tach"; + maxim,fan-dual-tach; + }; + + /* + * Hardware controlled fan: Fan speed is controlled by a + * temperature sensor feeding values into the lookup table. The + * fan association is done in the temperature sensor node. One + * sensor can drive multiple fans. + */ + cpu_fan: fan@1 { + compatible = "pmbus-fan"; + reg = <1>; + mode = "rpm"; + tach-pulses = <1>; + + #cooling-cells = <2>; + + maxim,fan-rotor-input = "tach"; + maxim,tmp-hysteresis = <2>; + maxim,fan-lookup-table = < + /* Temperature RPM */ + 0 1000 + 10 2000 + 20 3000 + 30 4000 + 40 5000 + 50 6000 + 60 7000 + 70 8000 + >; + }; + + cpu_temp: sensor@6 { + compatible = "pmbus-temperature"; + reg = <6>; + + maxim,tmp-offset = <0>; + maxim,tmp-fans = <&cpu_fan>; + }; + };
diff --git a/Documentation/devicetree/bindings/i2c/i2c-fsi.txt b/Documentation/devicetree/bindings/i2c/i2c-fsi.txt new file mode 100644 index 0000000..b1be2ce --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-fsi.txt
@@ -0,0 +1,40 @@ +Device-tree bindings for FSI-attached I2C master and busses +----------------------------------------------------------- + +Required properties: + - compatible = "ibm,i2c-fsi"; + - reg = < address size >; : The FSI CFAM address and address + space size. + - #address-cells = <1>; : Number of address cells in child + nodes. + - #size-cells = <0>; : Number of size cells in child nodes. + - child nodes : Nodes to describe busses off the I2C + master. + +Child node required properties: + - reg = < port number > : The port number on the I2C master. + +Child node optional properties: + - child nodes : Nodes to describe devices on the I2C + bus. + +Examples: + + i2c@1800 { + compatible = "ibm,i2c-fsi"; + reg = < 0x1800 0x400 >; + #address-cells = <1>; + #size-cells = <0>; + + i2c-bus@0 { + reg = <0>; + }; + + i2c-bus@1 { + reg = <1>; + + eeprom@50 { + compatible = "vendor,dev-name"; + }; + }; + };
diff --git a/Documentation/devicetree/bindings/ipmi/npcm7xx-kcs-bmc.txt b/Documentation/devicetree/bindings/ipmi/npcm7xx-kcs-bmc.txt new file mode 100644 index 0000000..3538a21 --- /dev/null +++ b/Documentation/devicetree/bindings/ipmi/npcm7xx-kcs-bmc.txt
@@ -0,0 +1,39 @@ +* Nuvoton NPCM7xx KCS (Keyboard Controller Style) IPMI interface + +The Nuvoton SOCs (NPCM7xx) are commonly used as BMCs +(Baseboard Management Controllers) and the KCS interface can be +used to perform in-band IPMI communication with their host. + +Required properties: +- compatible : should be one of + "nuvoton,npcm750-kcs-bmc" +- interrupts : interrupt generated by the controller +- kcs_chan : The KCS channel number in the controller + +Example: + + lpc_kcs: lpc_kcs@f0007000 { + compatible = "nuvoton,npcm750-lpc-kcs", "simple-mfd", "syscon"; + reg = <0xf0007000 0x40>; + reg-io-width = <1>; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xf0007000 0x40>; + + kcs1: kcs1@0 { + compatible = "nuvoton,npcm750-kcs-bmc"; + reg = <0x0 0x40>; + interrupts = <0 9 4>; + kcs_chan = <1>; + status = "disabled"; + }; + + kcs2: kcs2@0 { + compatible = "nuvoton,npcm750-kcs-bmc"; + reg = <0x0 0x40>; + interrupts = <0 9 4>; + kcs_chan = <2>; + status = "disabled"; + }; + }; \ No newline at end of file
diff --git a/Documentation/devicetree/bindings/misc/aspeed,cvic.txt b/Documentation/devicetree/bindings/misc/aspeed,cvic.txt new file mode 100644 index 0000000..d62c783 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/aspeed,cvic.txt
@@ -0,0 +1,35 @@ +* ASPEED AST2400 and AST2500 coprocessor interrupt controller + +This file describes the bindings for the interrupt controller present +in the AST2400 and AST2500 BMC SoCs which provides interrupt to the +ColdFire coprocessor. + +It is not a normal interrupt controller and it would be rather +inconvenient to create an interrupt tree for it as it somewhat shares +some of the same sources as the main ARM interrupt controller but with +different numbers. + +The AST2500 supports a SW generated interrupt + +Required properties: +- reg: address and length of the register for the device. +- compatible: "aspeed,cvic" and one of: + "aspeed,ast2400-cvic" + or + "aspeed,ast2500-cvic" + +- valid-sources: One cell, bitmap of supported sources for the implementation + +Optional properties; +- copro-sw-interrupts: List of interrupt numbers that can be used as + SW interrupts from the ARM to the coprocessor. + (AST2500 only) + +Example: + + cvic: copro-interrupt-controller@1e6c2000 { + compatible = "aspeed,ast2500-cvic"; + valid-sources = <0xffffffff>; + copro-sw-interrupts = <1>; + reg = <0x1e6c2000 0x80>; + };
diff --git a/Documentation/devicetree/bindings/pinctrl/nuvoton,npcm7xx-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/nuvoton,npcm7xx-pinctrl.txt new file mode 100644 index 0000000..a2bba4d --- /dev/null +++ b/Documentation/devicetree/bindings/pinctrl/nuvoton,npcm7xx-pinctrl.txt
@@ -0,0 +1,70 @@ +Nuvoton NPCM7XX Pin Controllers + +The NPCM7XX Pin Controller multi-function routed through +the multiplexing block, Each pin supports GPIO functionality (GPIOx) +and multiple functions that directly connect the pin to different +hardware blocks. + +Required properties: +- compatible : "nuvoton,npcm750-pinctrl" for Poleg NPCM750. + +Contents of function subnode node +--------------------------------- +Required subnode-properties: +- groups : An array of strings. Each string contains the name of a group. +- function: A string containing the name of the function to mux to the + group. + + Valid values for group and function names can be found from looking at the + group and function arrays in driver files: + drivers/pinctrl/nuvoton/pinctrl-npcm7xx.c + +For example, pinctrl might have subnodes like the following: + r1err_pins: r1err_pins { + groups = "r1err"; + function = "r1err"; + }; + r1md_pins: r1md_pins { + groups = "r1md"; + function = "r1md"; + }; + r1_pins: r1_pins { + groups = "r1"; + function = "r1"; + }; + +For a specific board, if it wants to use EMC (10/100 network), +it can add the following to its board-specific .dts file. +emc0: eth@f0825000 { + pinctrl-names = "default"; + pinctrl-0 = <&r1_pins + &r1err_pins + &r1md_pins>; + phy-mode = "rmii"; + +if EMC hardware is not used the EMC pin can used for GPIO56 + pinctrl-names = "default"; + pinctrl-0 = <&gpio56_pins> + +Examples +======== + +pinctrl: pinctrl@0 { + compatible = "nuvoton,npcm7xx-pinctrl"; + status = "okay"; + iox1_pins: iox1_pins { + groups = "iox1"; + function = "iox1"; + }; + iox2_pins: iox2_pins { + groups = "iox2"; + function = "iox2"; + }; + + .... + + clkreq_pins: clkreq_pins { + groups = "clkreq"; + function = "clkreq"; + }; +};
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index a38d8bf..a9469b7 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -279,6 +279,7 @@ pixcir PIXCIR MICROELECTRONICS Co., Ltd plathome Plat'Home Co., Ltd. plda PLDA +portwell Portwell Inc. poslab Poslab Technology Co., Ltd. powervr PowerVR (deprecated, use img) probox2 PROBOX2 (by W2COMP Co., Ltd.)
diff --git a/MAINTAINERS b/MAINTAINERS index 9c125f70..dcf4809 100644 --- a/MAINTAINERS +++ b/MAINTAINERS
@@ -9632,6 +9632,11 @@ S: Maintained F: drivers/scsi/NCR_D700.* +NCSI LIBRARY: +M: Samuel Mendoza-Jonas <sam@mendozajonas.com> +S: Maintained +F: net/ncsi/ + NCT6775 HARDWARE MONITOR DRIVER M: Guenter Roeck <linux@roeck-us.net> L: linux-hwmon@vger.kernel.org
diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 7e24249..6d81e3e 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile
@@ -1158,8 +1158,12 @@ dtb-$(CONFIG_ARCH_ASPEED) += \ aspeed-ast2500-evb.dtb \ aspeed-bmc-arm-centriq2400-rep.dtb \ + aspeed-bmc-intel-s2600wf.dtb \ + aspeed-bmc-opp-lanyang.dtb \ aspeed-bmc-opp-palmetto.dtb \ aspeed-bmc-opp-romulus.dtb \ aspeed-bmc-opp-witherspoon.dtb \ aspeed-bmc-opp-zaius.dtb \ + aspeed-bmc-portwell-neptune.dtb \ aspeed-bmc-quanta-q71l.dtb +
diff --git a/arch/arm/boot/dts/aspeed-ast2500-evb.dts b/arch/arm/boot/dts/aspeed-ast2500-evb.dts index 91a36c1..5dbb33c1 100644 --- a/arch/arm/boot/dts/aspeed-ast2500-evb.dts +++ b/arch/arm/boot/dts/aspeed-ast2500-evb.dts
@@ -13,12 +13,26 @@ chosen { stdout-path = &uart5; - bootargs = "console=ttyS4,115200 earlyprintk"; + bootargs = "console=tty0 console=ttyS4,115200 earlyprintk"; }; memory@80000000 { + device_type = "memory"; reg = <0x80000000 0x20000000>; }; + + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + gfx_memory: framebuffer { + size = <0x01000000>; + alignment = <0x01000000>; + compatible = "shared-dma-pool"; + reusable; + }; + }; }; &fmc { @@ -27,6 +41,8 @@ status = "okay"; m25p,fast-read; label = "bmc"; + spi-max-frequency = <50000000>; +#include "openbmc-flash-layout.dtsi" }; }; @@ -36,6 +52,7 @@ status = "okay"; m25p,fast-read; label = "pnor"; + spi-max-frequency = <100000000>; }; }; @@ -79,3 +96,26 @@ reg = <0x4d>; }; }; + +/* + * Enable port A as device (via the virtual hub) and port B as + * host by default on the eval board. This can be easily changed + * by replacing the override below with &ehci0 { ... } to enable + * host on both ports. + */ +&vhub { + status = "okay"; +}; + +&ehci1 { + status = "okay"; +}; + +&uhci { + status = "okay"; +}; + +&gfx { + status = "okay"; + memory-region = <&gfx_memory>; +};
diff --git a/arch/arm/boot/dts/aspeed-bmc-intel-s2600wf.dts b/arch/arm/boot/dts/aspeed-bmc-intel-s2600wf.dts new file mode 100644 index 0000000..7a291de --- /dev/null +++ b/arch/arm/boot/dts/aspeed-bmc-intel-s2600wf.dts
@@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017 Intel Corporation +/dts-v1/; + +#include "aspeed-g5.dtsi" + +/ { + model = "S2600WF BMC"; + compatible = "intel,s2600wf-bmc", "aspeed,ast2500"; + + chosen { + stdout-path = &uart5; + bootargs = "earlyprintk"; + }; + + memory { + reg = <0x80000000 0x20000000>; + }; + + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + vga_memory: framebuffer@7f000000 { + no-map; + reg = <0x7f000000 0x01000000>; + }; + }; + + iio-hwmon { + compatible = "iio-hwmon"; + io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 3>, + <&adc 4>, <&adc 5>, <&adc 6>, <&adc 7>, + <&adc 8>, <&adc 9>, <&adc 10>, <&adc 11>, + <&adc 12>, <&adc 13>, <&adc 14>, <&adc 15>; + }; + +}; + +&fmc { + status = "okay"; + flash@0 { + status = "okay"; + m25p,fast-read; + label = "bmc"; +#include "openbmc-flash-layout.dtsi" + }; +}; + +&spi1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_spi1_default>; + + flash@0 { + status = "okay"; + m25p,fast-read; + label = "pnor"; + }; +}; + +&uart5 { + status = "okay"; +}; + +&mac0 { + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_rmii1_default>; + use-ncsi; +}; + +&mac1 { + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_rgmii2_default &pinctrl_mdio2_default>; +}; + +&i2c1 { + status = "okay"; +}; + +&i2c2 { + status = "okay"; +}; + +&i2c3 { + status = "okay"; +}; + +&i2c4 { + status = "okay"; +}; + +&i2c5 { + status = "okay"; +}; + +&i2c6 { + status = "okay"; +}; + +&i2c7 { + status = "okay"; +}; + +&i2c13 { + status = "okay"; +}; + +&gfx { + status = "okay"; +}; + +&pinctrl { + aspeed,external-nodes = <&gfx &lhc>; +}; + +&pwm_tacho { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default + &pinctrl_pwm2_default &pinctrl_pwm3_default + &pinctrl_pwm4_default &pinctrl_pwm5_default + &pinctrl_pwm6_default &pinctrl_pwm7_default>; +};
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-lanyang.dts b/arch/arm/boot/dts/aspeed-bmc-opp-lanyang.dts new file mode 100644 index 0000000..53bf2ae --- /dev/null +++ b/arch/arm/boot/dts/aspeed-bmc-opp-lanyang.dts
@@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright (c) 2018 Inventec Corporation +/dts-v1/; + +#include "aspeed-g5.dtsi" +#include <dt-bindings/gpio/aspeed-gpio.h> + +/ { + model = "Lanyang BMC"; + compatible = "inventec,lanyang-bmc", "aspeed,ast2500"; + + chosen { + stdout-path = &uart5; + bootargs = "console=ttyS4,115200 earlyprintk"; + }; + + memory { + reg = <0x80000000 0x40000000>; + }; + + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; + ranges; + + flash_memory: region@98000000 { + no-map; + reg = <0x98000000 0x04000000>; /* 64M */ + }; + }; + + leds { + compatible = "gpio-leds"; + + sys_boot_status { + label = "System_boot_status"; + gpios = <&gpio ASPEED_GPIO(B, 6) GPIO_ACTIVE_LOW>; + }; + + attention { + label = "Attention_locator"; + gpios = <&gpio ASPEED_GPIO(B, 7) GPIO_ACTIVE_HIGH>; + }; + + plt_fault { + label = "Platform_fault"; + gpios = <&gpio ASPEED_GPIO(B, 1) GPIO_ACTIVE_HIGH>; + }; + + hdd_fault { + label = "Onboard_drive_fault"; + gpios = <&gpio ASPEED_GPIO(B, 3) GPIO_ACTIVE_HIGH>; + }; + bmc_err { + lable = "BMC_fault"; + gpios = <&gpio ASPEED_GPIO(H, 6) GPIO_ACTIVE_HIGH>; + }; + + sys_err { + lable = "Sys_fault"; + gpios = <&gpio ASPEED_GPIO(H, 7) GPIO_ACTIVE_HIGH>; + }; + }; + + fsi: gpio-fsi { + compatible = "fsi-master-gpio", "fsi-master"; + #address-cells = <2>; + #size-cells = <0>; + + clock-gpios = <&gpio ASPEED_GPIO(J, 0) GPIO_ACTIVE_HIGH>; + data-gpios = <&gpio ASPEED_GPIO(J, 1) GPIO_ACTIVE_HIGH>; + trans-gpios = <&gpio ASPEED_GPIO(D, 5) GPIO_ACTIVE_HIGH>; + enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>; + mux-gpios = <&gpio ASPEED_GPIO(H, 2) GPIO_ACTIVE_HIGH>; + }; + + iio-hwmon { + compatible = "iio-hwmon"; + io-channels = <&adc 0>, <&adc 1>, <&adc 2>, <&adc 3>, + <&adc 4>, <&adc 5>, <&adc 6>, <&adc 7>, + <&adc 8>, <&adc 9>, <&adc 10>, <&adc 11>, + <&adc 13>, <&adc 14>, <&adc 15>; + }; + + iio-hwmon-battery { + compatible = "iio-hwmon"; + io-channels = <&adc 12>; + }; +}; + +#include "ibm-power9-dual.dtsi" + +&pwm_tacho { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default + &pinctrl_pwm2_default &pinctrl_pwm3_default>; + + fan@0 { + reg = <0x00>; + aspeed,fan-tach-ch = /bits/ 8 <0x00>; + }; + + fan@1 { + reg = <0x01>; + aspeed,fan-tach-ch = /bits/ 8 <0x01>; + }; + + fan@2 { + reg = <0x02>; + aspeed,fan-tach-ch = /bits/ 8 <0x02>; + }; + + fan@3 { + reg = <0x03>; + aspeed,fan-tach-ch = /bits/ 8 <0x03>; + }; +}; + +&fmc { + status = "okay"; + flash@0 { + status = "okay"; + m25p,fast-read; + label = "bmc"; +#include "openbmc-flash-layout.dtsi" + }; +}; + +&spi1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_spi1_default>; + + flash@0 { + status = "okay"; + label = "pnor"; + m25p,fast-read; + }; +}; + +&spi2 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_spi2ck_default + &pinctrl_spi2cs0_default + &pinctrl_spi2cs1_default + &pinctrl_spi2miso_default + &pinctrl_spi2mosi_default>; + + flash@0 { + status = "okay"; + }; +}; + +&uart1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_txd1_default + &pinctrl_rxd1_default>; +}; + +&lpc_ctrl { + status = "okay"; + memory-region = <&flash_memory>; + flash = <&spi1>; +}; + +&lpc_snoop { + status = "okay"; + snoop-ports = <0x80>; +}; + +&mbox { + status = "okay"; +}; + +&uart5 { + status = "okay"; +}; + +&mac0 { + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_rmii1_default>; + use-ncsi; +}; + +&mac1 { + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_rgmii2_default &pinctrl_mdio2_default>; +}; + +&i2c0 { + status = "okay"; + + eeprom@55 { + compatible = "atmel,24c64"; + reg = <0x55>; + pagesize = <32>; + }; + + rtc@68 { + compatible = "nxp,pcf8523"; + reg = <0x68>; + }; + + tmp75@48 { + compatible = "ti,tmp75"; + reg = <0x48>; + }; +}; + +&i2c1 { + status = "okay"; +}; + +&i2c2 { + status = "okay"; +}; + +&i2c3 { + status = "okay"; +}; + +&i2c4 { + status = "okay"; +}; + +&i2c5 { + status = "okay"; +}; + +&i2c6 { + status = "okay"; +}; + +&i2c7 { + status = "okay"; +}; + +&i2c8 { + status = "okay"; +}; + +&i2c9 { + status = "okay"; +}; + +&i2c10 { + status = "okay"; +}; + +&i2c11 { + status = "okay"; +}; + +&vuart { + status = "okay"; +}; + +&gfx { + status = "okay"; +}; + +&pinctrl { + aspeed,external-nodes = <&gfx &lhc>; +}; + +&gpio { + pin_gpio_b0 { + gpio-hog; + gpios = <ASPEED_GPIO(B, 0) GPIO_ACTIVE_HIGH>; + output-high; + line-name = "BMC_HDD1_PWR_EN"; + }; + + pin_gpio_b5 { + gpio-hog; + gpios = <ASPEED_GPIO(B, 5) GPIO_ACTIVE_HIGH>; + input; + line-name = "BMC_USB1_OCI2"; + }; + + pin_gpio_h5 { + gpio-hog; + gpios = <ASPEED_GPIO(H, 5) GPIO_ACTIVE_HIGH>; + output-high; + line-name = "BMC_CP0_PERST_ENABLE_R"; + }; + + pin_gpio_z2 { + gpio-hog; + gpios = <ASPEED_GPIO(Z, 2) GPIO_ACTIVE_HIGH>; + output-high; + line-name = "RST_PCA9546_U177_N"; + }; + + pin_gpio_aa6 { + gpio-hog; + gpios = <ASPEED_GPIO(AA, 6) GPIO_ACTIVE_HIGH>; + output-high; + line-name = "BMC_CP0_RESET_N"; + }; + + pin_gpio_aa7 { + gpio-hog; + gpios = <ASPEED_GPIO(AA, 7) GPIO_ACTIVE_HIGH>; + output-high; + line-name = "BMC_TPM_RESET_N"; + }; + + pin_gpio_ab0 { + gpio-hog; + gpios = <ASPEED_GPIO(AB, 0) GPIO_ACTIVE_LOW>; + output-high; + line-name = "BMC_USB_PWRON_N"; + }; +}; + +&ibt { + status = "okay"; +}; + +&adc { + status = "okay"; +}; +
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts index c7084a8..3b30ee2 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
@@ -26,6 +26,16 @@ no-map; reg = <0x5f000000 0x01000000>; /* 16M */ }; + + flash_memory: region@98000000 { + no-map; + reg = <0x98000000 0x01000000>; /* 16MB */ + }; + + coldfire_memory: codefire_memory@5ee00000 { + reg = <0x5ee00000 0x00200000>; + no-map; + }; }; leds { @@ -44,6 +54,22 @@ }; }; + fsi: gpio-fsi { + compatible = "aspeed,ast2400-cf-fsi-master", "fsi-master"; + #address-cells = <2>; + #size-cells = <0>; + + memory-region = <&coldfire_memory>; + aspeed,sram = <&sram>; + aspeed,cvic = <&cvic>; + + clock-gpios = <&gpio ASPEED_GPIO(A, 4) GPIO_ACTIVE_HIGH>; + data-gpios = <&gpio ASPEED_GPIO(A, 5) GPIO_ACTIVE_HIGH>; + mux-gpios = <&gpio ASPEED_GPIO(A, 6) GPIO_ACTIVE_HIGH>; + enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>; + trans-gpios = <&gpio ASPEED_GPIO(H, 6) GPIO_ACTIVE_HIGH>; + }; + gpio-keys { compatible = "gpio-keys"; @@ -61,6 +87,7 @@ status = "okay"; m25p,fast-read; label = "bmc"; + spi-max-frequency = <50000000>; #include "openbmc-flash-layout.dtsi" }; }; @@ -73,6 +100,7 @@ flash@0 { status = "okay"; m25p,fast-read; + spi-max-frequency = <50000000>; label = "pnor"; }; }; @@ -143,6 +171,11 @@ &i2c3 { status = "okay"; + + occ-hwmon@50 { + compatible = "ibm,p8-occ-hwmon"; + reg = <0x50>; + }; }; &i2c4 { @@ -169,6 +202,16 @@ status = "okay"; }; +&lpc_ctrl { + status = "okay"; + memory-region = <&flash_memory>; + flash = <&spi>; +}; + +&mbox { + status = "okay"; +}; + &gpio { pin_func_mode0 { gpio-hog; @@ -303,13 +346,6 @@ line-name = "SYS_PWROK_BMC"; }; - pin_gpio_h6 { - gpio-hog; - gpios = <ASPEED_GPIO(H, 6) GPIO_ACTIVE_HIGH>; - output-high; - line-name = "SCM1_FSI0_DATA_EN"; - }; - pin_gpio_h7 { gpio-hog; gpios = <ASPEED_GPIO(H, 7) GPIO_ACTIVE_HIGH>; @@ -317,3 +353,13 @@ line-name = "BMC_TPM_INT_N"; }; }; + +/* Instantiate chip 0 */ +#define CFAM_CHIP_ID 0 +&fsi { + cfam@0,0 { + reg = <0 0>; + #include "ibm-power8-cfam.dtsi" + }; +}; +#undef CFAM_CHIP_ID
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts b/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts index 51bc6a2..9784a59a 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-romulus.dts
@@ -21,15 +21,27 @@ #size-cells = <1>; ranges; - vga_memory: framebuffer@bf000000 { + vga_memory: framebuffer@9f000000 { no-map; - reg = <0xbf000000 0x01000000>; /* 16M */ + reg = <0x9f000000 0x01000000>; /* 16M */ }; flash_memory: region@98000000 { no-map; reg = <0x98000000 0x04000000>; /* 64M */ }; + + gfx_memory: framebuffer { + size = <0x01000000>; + alignment = <0x01000000>; + compatible = "shared-dma-pool"; + reusable; + }; + + coldfire_memory: codefire_memory@9ef00000 { + reg = <0x9ef00000 0x00100000>; + no-map; + }; }; leds { @@ -49,10 +61,14 @@ }; fsi: gpio-fsi { - compatible = "fsi-master-gpio", "fsi-master"; + compatible = "aspeed,ast2500-cf-fsi-master", "fsi-master"; #address-cells = <2>; #size-cells = <0>; + memory-region = <&coldfire_memory>; + aspeed,sram = <&sram>; + aspeed,cvic = <&cvic>; + clock-gpios = <&gpio ASPEED_GPIO(AA, 0) GPIO_ACTIVE_HIGH>; data-gpios = <&gpio ASPEED_GPIO(AA, 2) GPIO_ACTIVE_HIGH>; mux-gpios = <&gpio ASPEED_GPIO(A, 6) GPIO_ACTIVE_HIGH>; @@ -68,15 +84,24 @@ gpios = <&gpio ASPEED_GPIO(J, 2) GPIO_ACTIVE_LOW>; linux,code = <ASPEED_GPIO(J, 2)>; }; + + id-button { + label = "id-button"; + gpios = <&gpio ASPEED_GPIO(Q, 7) GPIO_ACTIVE_LOW>; + linux,code = <ASPEED_GPIO(Q, 7)>; + }; }; }; +#include "ibm-power9-dual.dtsi" + &fmc { status = "okay"; flash@0 { status = "okay"; m25p,fast-read; label = "bmc"; + spi-max-frequency = <50000000>; #include "openbmc-flash-layout.dtsi" }; }; @@ -90,6 +115,7 @@ status = "okay"; m25p,fast-read; label = "pnor"; + spi-max-frequency = <100000000>; }; }; @@ -99,6 +125,10 @@ flash = <&spi1>; }; +&mbox { + status = "okay"; +}; + &uart1 { /* Rear RS-232 connector */ status = "okay"; @@ -188,6 +218,11 @@ compatible = "nuvoton,w83773g"; reg = <0x4c>; }; + + w83773g@4c { + compatible = "nuvoton,w83773g"; + reg = <0x4c>; + }; }; &gpio { @@ -267,3 +302,12 @@ &ibt { status = "okay"; }; + +&gfx { + status = "okay"; + memory-region = <&gfx_memory>; +}; + +&vhub { + status = "okay"; +};
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts b/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts index 7056231..92f4966 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-witherspoon.dts
@@ -26,6 +26,41 @@ no-map; reg = <0x98000000 0x04000000>; /* 64M */ }; + + gfx_memory: framebuffer { + size = <0x01000000>; + alignment = <0x01000000>; + compatible = "shared-dma-pool"; + reusable; + }; + }; + + gpio-keys { + compatible = "gpio-keys"; + + air-water { + label = "air-water"; + gpios = <&gpio ASPEED_GPIO(B, 5) GPIO_ACTIVE_LOW>; + linux,code = <ASPEED_GPIO(B, 5)>; + }; + + checkstop { + label = "checkstop"; + gpios = <&gpio ASPEED_GPIO(J, 2) GPIO_ACTIVE_LOW>; + linux,code = <ASPEED_GPIO(J, 2)>; + }; + + ps0-presence { + label = "ps0-presence"; + gpios = <&gpio ASPEED_GPIO(P, 7) GPIO_ACTIVE_LOW>; + linux,code = <ASPEED_GPIO(P, 7)>; + }; + + ps1-presence { + label = "ps1-presence"; + gpios = <&gpio ASPEED_GPIO(N, 0) GPIO_ACTIVE_LOW>; + linux,code = <ASPEED_GPIO(N, 0)>; + }; }; gpio-keys-polled { @@ -125,6 +160,7 @@ compatible = "fsi-master-gpio", "fsi-master"; #address-cells = <2>; #size-cells = <0>; + no-gpio-delays; clock-gpios = <&gpio ASPEED_GPIO(AA, 0) GPIO_ACTIVE_HIGH>; data-gpios = <&gpio ASPEED_GPIO(E, 0) GPIO_ACTIVE_HIGH>; @@ -145,6 +181,8 @@ }; +#include "ibm-power9-dual.dtsi" + &fmc { status = "okay"; @@ -152,6 +190,7 @@ status = "okay"; label = "bmc"; m25p,fast-read; + spi-max-frequency = <50000000>; #include "openbmc-flash-layout.dtsi" }; @@ -159,6 +198,7 @@ status = "okay"; label = "alt"; m25p,fast-read; + spi-max-frequency = <50000000>; }; }; @@ -171,6 +211,7 @@ status = "okay"; label = "pnor"; m25p,fast-read; + spi-max-frequency = <100000000>; }; }; @@ -205,6 +246,10 @@ flash = <&spi1>; }; +&mbox { + status = "okay"; +}; + &mac0 { status = "okay"; pinctrl-names = "default"; @@ -235,6 +280,58 @@ reg = <0x52>; #address-cells = <1>; #size-cells = <0>; + + fan@0 { + compatible = "pmbus-fan"; + reg = <0>; + tach-pulses = <2>; + maxim,fan-rotor-input = "tach"; + maxim,fan-pwm-freq = <25000>; + maxim,fan-dual-tach; + maxim,fan-no-watchdog; + maxim,fan-no-fault-ramp; + maxim,fan-ramp = <2>; + maxim,fan-fault-pin-mon; + }; + + fan@1 { + compatible = "pmbus-fan"; + reg = <1>; + tach-pulses = <2>; + maxim,fan-rotor-input = "tach"; + maxim,fan-pwm-freq = <25000>; + maxim,fan-dual-tach; + maxim,fan-no-watchdog; + maxim,fan-no-fault-ramp; + maxim,fan-ramp = <2>; + maxim,fan-fault-pin-mon; + }; + + fan@2 { + compatible = "pmbus-fan"; + reg = <2>; + tach-pulses = <2>; + maxim,fan-rotor-input = "tach"; + maxim,fan-pwm-freq = <25000>; + maxim,fan-dual-tach; + maxim,fan-no-watchdog; + maxim,fan-no-fault-ramp; + maxim,fan-ramp = <2>; + maxim,fan-fault-pin-mon; + }; + + fan@3 { + compatible = "pmbus-fan"; + reg = <3>; + tach-pulses = <2>; + maxim,fan-rotor-input = "tach"; + maxim,fan-pwm-freq = <25000>; + maxim,fan-dual-tach; + maxim,fan-no-watchdog; + maxim,fan-no-fault-ramp; + maxim,fan-ramp = <2>; + maxim,fan-fault-pin-mon; + }; }; dps: dps310@76 { @@ -531,6 +628,7 @@ &gfx { status = "okay"; + memory-region = <&gfx_memory>; }; &pinctrl { @@ -547,6 +645,10 @@ pinctrl-0 = <&pinctrl_wdtrst1_default>; }; +&wdt2 { + aspeed,alt-boot; +}; + &ibt { status = "okay"; };
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts b/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts index ebe726a..3f574ab 100644 --- a/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts +++ b/arch/arm/boot/dts/aspeed-bmc-opp-zaius.dts
@@ -55,6 +55,12 @@ gpios = <&gpio ASPEED_GPIO(F, 7) GPIO_ACTIVE_LOW>; linux,code = <ASPEED_GPIO(F, 7)>; }; + + pcie-e2b-present{ + label = "pcie-e2b-present"; + gpios = <&gpio ASPEED_GPIO(E, 7) GPIO_ACTIVE_LOW>; + linux,code = <ASPEED_GPIO(E, 7)>; + }; }; leds { @@ -85,6 +91,7 @@ compatible = "fsi-master-gpio", "fsi-master"; #address-cells = <2>; #size-cells = <0>; + no-gpio-delays; trans-gpios = <&gpio ASPEED_GPIO(O, 6) GPIO_ACTIVE_HIGH>; enable-gpios = <&gpio ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>; @@ -108,6 +115,8 @@ }; +#include "ibm-power9-dual.dtsi" + &fmc { status = "okay"; @@ -115,6 +124,7 @@ status = "okay"; label = "bmc"; m25p,fast-read; + spi-max-frequency = <50000000>; #include "openbmc-flash-layout.dtsi" }; }; @@ -128,6 +138,7 @@ status = "okay"; label = "pnor"; m25p,fast-read; + spi-max-frequency = <100000000>; }; }; @@ -163,6 +174,9 @@ snoop-ports = <0x80>; }; +&mbox { + status = "okay"; +}; &uart5 { status = "okay";
diff --git a/arch/arm/boot/dts/aspeed-bmc-portwell-neptune.dts b/arch/arm/boot/dts/aspeed-bmc-portwell-neptune.dts new file mode 100644 index 0000000..43ed139 --- /dev/null +++ b/arch/arm/boot/dts/aspeed-bmc-portwell-neptune.dts
@@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017 Facebook Inc. +/dts-v1/; + +#include "aspeed-g5.dtsi" +#include <dt-bindings/gpio/aspeed-gpio.h> + +/ { + model = "Portwell Neptune BMC"; + compatible = "portwell,neptune-bmc", "aspeed,ast2500"; + aliases { + serial0 = &uart1; + serial4 = &uart5; + }; + chosen { + stdout-path = &uart5; + bootargs = "console=ttyS4,115200 earlyprintk"; + }; + + memory { + reg = <0x80000000 0x20000000>; + }; + + leds { + compatible = "gpio-leds"; + postcode0 { + label="BMC_UP"; + gpios = <&gpio ASPEED_GPIO(H, 0) GPIO_ACTIVE_HIGH>; + default-state = "on"; + }; + postcode1 { + label="BMC_HB"; + gpios = <&gpio ASPEED_GPIO(H, 1) GPIO_ACTIVE_HIGH>; + linux,default-trigger = "heartbeat"; + }; + postcode2 { + label="FAULT"; + gpios = <&gpio ASPEED_GPIO(H, 2) GPIO_ACTIVE_HIGH>; + }; + // postcode3-7 are GPIOH3-H7 + }; +}; + +&fmc { + status = "okay"; + flash@0 { + status = "okay"; + m25p,fast-read; +#include "openbmc-flash-layout.dtsi" + }; +}; + +&spi1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_spi1_default>; + flash@0 { + status = "okay"; + m25p,fast-read; + label = "pnor"; + }; +}; + +&uart1 { + // Host Console + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_txd1_default + &pinctrl_rxd1_default>; +}; + +&uart5 { + // BMC Console + status = "okay"; +}; + +&mac0 { + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_rmii1_default + &pinctrl_mdio1_default>; +}; + +&mac1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_rmii2_default>; + use-ncsi; +}; + +&i2c1 { + status = "okay"; + // To PCIe slot SMBUS +}; + +&i2c2 { + status = "okay"; + // To LAN I210 +}; + +&i2c3 { + status = "okay"; + // SMBus to COMe AB +}; + +&i2c4 { + status = "okay"; + // I2C to COMe AB +}; + +&i2c5 { + status = "okay"; +// USB Debug card + pca9555@27 { + compatible = "nxp,pca9555"; + reg = <0x27>; + }; +}; + +&i2c6 { + status = "okay"; + tpm@20 { + compatible = "infineon,slb9645tt"; + reg = <0x20>; + }; + tmp421@4e { + compatible = "ti,tmp421"; + reg = <0x4e>; + }; + tmp421@4f { + compatible = "ti,tmp421"; + reg = <0x4f>; + }; +}; + +&i2c8 { + status = "okay"; + eeprom@51 { + compatible = "atmel,24c128"; + reg = <0x51>; + pagesize = <32>; + }; +}; + +&pwm_tacho { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_pwm0_default &pinctrl_pwm1_default>; + fan@0 { + reg = <0x00>; + aspeed,fan-tach-ch = /bits/ 8 <0x00>; + }; + + fan@1 { + reg = <0x00>; + aspeed,fan-tach-ch = /bits/ 8 <0x01>; + }; +};
diff --git a/arch/arm/boot/dts/aspeed-g4.dtsi b/arch/arm/boot/dts/aspeed-g4.dtsi index 518d2bc..6af1287 100644 --- a/arch/arm/boot/dts/aspeed-g4.dtsi +++ b/arch/arm/boot/dts/aspeed-g4.dtsi
@@ -65,6 +65,7 @@ flash@0 { reg = < 0 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; }; @@ -80,6 +81,7 @@ flash@0 { reg = < 0 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; }; @@ -92,6 +94,12 @@ reg = <0x1e6c0080 0x80>; }; + cvic: copro-interrupt-controller@1e6c2000 { + compatible = "aspeed,ast2400-cvic", "aspeed-cvic"; + valid-sources = <0x7fffffff>; + reg = <0x1e6c2000 0x80>; + }; + mac0: ethernet@1e660000 { compatible = "aspeed,ast2400-mac", "faraday,ftgmac100"; reg = <0x1e660000 0x180>; @@ -108,6 +116,39 @@ status = "disabled"; }; + ehci0: usb@1e6a1000 { + compatible = "aspeed,ast2400-ehci", "generic-ehci"; + reg = <0x1e6a1000 0x100>; + interrupts = <5>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2h_default>; + status = "disabled"; + }; + + uhci: usb@1e6b0000 { + compatible = "aspeed,ast2400-uhci", "generic-uhci"; + reg = <0x1e6b0000 0x100>; + interrupts = <14>; + #ports = <3>; + clocks = <&syscon ASPEED_CLK_GATE_USBUHCICLK>; + status = "disabled"; + /* + * No default pinmux, it will follow EHCI, use an explicit pinmux + * override if you don't enable EHCI + */ + }; + + vhub: usb-vhub@1e6a0000 { + compatible = "aspeed,ast2400-usb-vhub"; + reg = <0x1e6a0000 0x300>; + interrupts = <5>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2d_default>; + status = "disabled"; + }; + apb { compatible = "simple-bus"; #address-cells = <1>; @@ -125,6 +166,14 @@ pinctrl: pinctrl { compatible = "aspeed,g4-pinctrl"; }; + + }; + + rng: hwrng@1e6e2078 { + compatible = "timeriomem_rng"; + reg = <0x1e6e2078 0x4>; + period = <1>; + quality = <100>; }; adc: adc@1e6e9000 { @@ -136,7 +185,7 @@ status = "disabled"; }; - sram@1e720000 { + sram: sram@1e720000 { compatible = "mmio-sram"; reg = <0x1e720000 0x8000>; // 32K }; @@ -199,7 +248,7 @@ #address-cells = <1>; #size-cells = <0>; reg = <0x1e786000 0x1000>; - clocks = <&syscon ASPEED_CLK_APB>; + clocks = <&syscon ASPEED_CLK_24M>; resets = <&syscon ASPEED_RESET_PWM>; status = "disabled"; }; @@ -265,6 +314,17 @@ compatible = "aspeed,ast2400-ibt-bmc"; reg = <0xc0 0x18>; interrupts = <8>; + }; + + sio_regs: regs { + compatible = "aspeed,bmc-misc"; + }; + + mbox: mbox@180 { + compatible = "aspeed,ast2400-mbox"; + reg = <0x180 0x5c>; + interrupts = <46>; + #mbox-cells = <1>; status = "disabled"; }; }; @@ -1250,6 +1310,16 @@ groups = "USBCKI"; }; + pinctrl_usb2h_default: usb2h_default { + function = "USB2H1"; + groups = "USB2H1"; + }; + + pinctrl_usb2d_default: usb2d_default { + function = "USB2D1"; + groups = "USB2D1"; + }; + pinctrl_vgabios_rom_default: vgabios_rom_default { function = "VGABIOS_ROM"; groups = "VGABIOS_ROM"; @@ -1300,3 +1370,86 @@ groups = "WDTRST2"; }; }; + +&sio_regs { + sio_2b { + offset = <0xf0>; + bit-mask = <0xff>; + bit-shift = <24>; + }; + sio_2a { + offset = <0xf0>; + bit-mask = <0xff>; + bit-shift = <16>; + }; + sio_29 { + offset = <0xf0>; + bit-mask = <0xff>; + bit-shift = <8>; + }; + sio_28 { + offset = <0xf0>; + bit-mask = <0xff>; + bit-shift = <0>; + }; + sio_2f { + offset = <0xf4>; + bit-mask = <0xff>; + bit-shift = <24>; + }; + sio_2e { + offset = <0xf4>; + bit-mask = <0xff>; + bit-shift = <16>; + }; + sio_2d { + offset = <0xf4>; + bit-mask = <0xff>; + bit-shift = <8>; + }; + sio_2c { + offset = <0xf4>; + bit-mask = <0xff>; + bit-shift = <0>; + }; + sio_23 { + offset = <0xf8>; + bit-mask = <0xff>; + bit-shift = <24>; + }; + sio_22 { + offset = <0xf8>; + bit-mask = <0xff>; + bit-shift = <16>; + }; + sio_21 { + offset = <0xf8>; + bit-mask = <0xff>; + bit-shift = <8>; + }; + sio_20 { + offset = <0xf8>; + bit-mask = <0xff>; + bit-shift = <0>; + }; + sio_27 { + offset = <0xfc>; + bit-mask = <0xff>; + bit-shift = <24>; + }; + sio_26 { + offset = <0xfc>; + bit-mask = <0xff>; + bit-shift = <16>; + }; + sio_25 { + offset = <0xfc>; + bit-mask = <0xff>; + bit-shift = <8>; + }; + sio_24 { + offset = <0xfc>; + bit-mask = <0xff>; + bit-shift = <0>; + }; +};
diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi index f991771..d92f047 100644 --- a/arch/arm/boot/dts/aspeed-g5.dtsi +++ b/arch/arm/boot/dts/aspeed-g5.dtsi
@@ -65,16 +65,19 @@ flash@0 { reg = < 0 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; flash@1 { reg = < 1 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; flash@2 { reg = < 2 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; }; @@ -90,11 +93,13 @@ flash@0 { reg = < 0 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; flash@1 { reg = < 1 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; }; @@ -110,11 +115,13 @@ flash@0 { reg = < 0 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; flash@1 { reg = < 1 >; compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; status = "disabled"; }; }; @@ -127,6 +134,13 @@ reg = <0x1e6c0080 0x80>; }; + cvic: copro-interrupt-controller@1e6c2000 { + compatible = "aspeed,ast2500-cvic", "aspeed-cvic"; + valid-sources = <0xffffffff>; + copro-sw-interrupts = <1>; + reg = <0x1e6c2000 0x80>; + }; + mac0: ethernet@1e660000 { compatible = "aspeed,ast2500-mac", "faraday,ftgmac100"; reg = <0x1e660000 0x180>; @@ -143,6 +157,49 @@ status = "disabled"; }; + ehci0: usb@1e6a1000 { + compatible = "aspeed,ast2500-ehci", "generic-ehci"; + reg = <0x1e6a1000 0x100>; + interrupts = <5>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2ah_default>; + status = "disabled"; + }; + + ehci1: usb@1e6a3000 { + compatible = "aspeed,ast2500-ehci", "generic-ehci"; + reg = <0x1e6a3000 0x100>; + interrupts = <13>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT2CLK>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2bh_default>; + status = "disabled"; + }; + + uhci: usb@1e6b0000 { + compatible = "aspeed,ast2500-uhci", "generic-uhci"; + reg = <0x1e6b0000 0x100>; + interrupts = <14>; + #ports = <2>; + clocks = <&syscon ASPEED_CLK_GATE_USBUHCICLK>; + status = "disabled"; + /* + * No default pinmux, it will follow EHCI, use an explicit pinmux + * override if you don't enable EHCI + */ + }; + + vhub: usb-vhub@1e6a0000 { + compatible = "aspeed,ast2500-usb-vhub"; + reg = <0x1e6a0000 0x300>; + interrupts = <5>; + clocks = <&syscon ASPEED_CLK_GATE_USBPORT1CLK>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_usb2ad_default>; + status = "disabled"; + }; + apb { compatible = "simple-bus"; #address-cells = <1>; @@ -162,12 +219,27 @@ aspeed,external-nodes = <&gfx &lhc>; }; + + vga_scratch: scratch { + compatible = "aspeed,bmc-misc"; + }; + }; + + rng: hwrng@1e6e2078 { + compatible = "timeriomem_rng"; + reg = <0x1e6e2078 0x4>; + period = <1>; + quality = <100>; }; gfx: display@1e6e6000 { compatible = "aspeed,ast2500-gfx", "syscon"; reg = <0x1e6e6000 0x1000>; reg-io-width = <4>; + clocks = <&syscon ASPEED_CLK_GATE_D1CLK>; + resets = <&syscon ASPEED_RESET_CRT1>; + status = "disabled"; + interrupts = <0x19>; }; adc: adc@1e6e9000 { @@ -179,7 +251,7 @@ status = "disabled"; }; - sram@1e720000 { + sram: sram@1e720000 { compatible = "mmio-sram"; reg = <0x1e720000 0x9000>; // 36K }; @@ -249,7 +321,7 @@ #address-cells = <1>; #size-cells = <0>; reg = <0x1e786000 0x1000>; - clocks = <&syscon ASPEED_CLK_APB>; + clocks = <&syscon ASPEED_CLK_24M>; resets = <&syscon ASPEED_RESET_PWM>; status = "disabled"; }; @@ -315,6 +387,17 @@ compatible = "aspeed,ast2500-ibt-bmc"; reg = <0xc0 0x18>; interrupts = <8>; + }; + + sio_regs: regs { + compatible = "aspeed,bmc-misc"; + }; + + mbox: mbox@180 { + compatible = "aspeed,ast2500-mbox"; + reg = <0x180 0x5c>; + interrupts = <46>; + #mbox-cells = <1>; status = "disabled"; }; }; @@ -1380,6 +1463,26 @@ groups = "USBCKI"; }; + pinctrl_usb2ah_default: usb2ah_default { + function = "USB2AH"; + groups = "USB2AH"; + }; + + pinctrl_usb2ad_default: usb2ad_default { + function = "USB2AD"; + groups = "USB2AD"; + }; + + pinctrl_usb11bhid_default: usb11bhid_default { + function = "USB11BHID"; + groups = "USB11BHID"; + }; + + pinctrl_usb2bh_default: usb2bh_default { + function = "USB2BH"; + groups = "USB2BH"; + }; + pinctrl_vgabiosrom_default: vgabiosrom_default { function = "VGABIOSROM"; groups = "VGABIOSROM"; @@ -1415,3 +1518,134 @@ groups = "WDTRST2"; }; }; + +&vga_scratch { + dac_mux { + offset = <0x2c>; + bit-mask = <0x3>; + bit-shift = <16>; + }; + vga0 { + offset = <0x50>; + bit-mask = <0xffffffff>; + bit-shift = <0>; + }; + vga1 { + offset = <0x54>; + bit-mask = <0xffffffff>; + bit-shift = <0>; + }; + vga2 { + offset = <0x58>; + bit-mask = <0xffffffff>; + bit-shift = <0>; + }; + vga3 { + offset = <0x5c>; + bit-mask = <0xffffffff>; + bit-shift = <0>; + }; + vga4 { + offset = <0x60>; + bit-mask = <0xffffffff>; + bit-shift = <0>; + }; + vga5 { + offset = <0x64>; + bit-mask = <0xffffffff>; + bit-shift = <0>; + }; + vga6 { + offset = <0x68>; + bit-mask = <0xffffffff>; + bit-shift = <0>; + }; + vga7 { + offset = <0x6c>; + bit-mask = <0xffffffff>; + bit-shift = <0>; + }; +}; + +&sio_regs { + sio_2b { + offset = <0xf0>; + bit-mask = <0xff>; + bit-shift = <24>; + }; + sio_2a { + offset = <0xf0>; + bit-mask = <0xff>; + bit-shift = <16>; + }; + sio_29 { + offset = <0xf0>; + bit-mask = <0xff>; + bit-shift = <8>; + }; + sio_28 { + offset = <0xf0>; + bit-mask = <0xff>; + bit-shift = <0>; + }; + sio_2f { + offset = <0xf4>; + bit-mask = <0xff>; + bit-shift = <24>; + }; + sio_2e { + offset = <0xf4>; + bit-mask = <0xff>; + bit-shift = <16>; + }; + sio_2d { + offset = <0xf4>; + bit-mask = <0xff>; + bit-shift = <8>; + }; + sio_2c { + offset = <0xf4>; + bit-mask = <0xff>; + bit-shift = <0>; + }; + sio_23 { + offset = <0xf8>; + bit-mask = <0xff>; + bit-shift = <24>; + }; + sio_22 { + offset = <0xf8>; + bit-mask = <0xff>; + bit-shift = <16>; + }; + sio_21 { + offset = <0xf8>; + bit-mask = <0xff>; + bit-shift = <8>; + }; + sio_20 { + offset = <0xf8>; + bit-mask = <0xff>; + bit-shift = <0>; + }; + sio_27 { + offset = <0xfc>; + bit-mask = <0xff>; + bit-shift = <24>; + }; + sio_26 { + offset = <0xfc>; + bit-mask = <0xff>; + bit-shift = <16>; + }; + sio_25 { + offset = <0xfc>; + bit-mask = <0xff>; + bit-shift = <8>; + }; + sio_24 { + offset = <0xfc>; + bit-mask = <0xff>; + bit-shift = <0>; + }; +};
diff --git a/arch/arm/boot/dts/ibm-power8-cfam.dtsi b/arch/arm/boot/dts/ibm-power8-cfam.dtsi new file mode 100644 index 0000000..a07e950 --- /dev/null +++ b/arch/arm/boot/dts/ibm-power8-cfam.dtsi
@@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corp + +#define __MAKE_LABEL(p,i) p##i +#define _MAKE_LABEL(p,i) __MAKE_LABEL(p,i) +#define HUB_LABEL _MAKE_LABEL(fsi_hub,CFAM_CHIP_ID) +#define OCC_LABEL _MAKE_LABEL(fsi_occ,CFAM_CHIP_ID) +#define I2C_LABEL(n) _MAKE_LABEL(_MAKE_LABEL(cfam,CFAM_CHIP_ID),_i2c##n) + +#address-cells = <1>; +#size-cells = <1>; +chip-id = <CFAM_CHIP_ID>; + +scom@1000 { + compatible = "ibm,fsi2pib"; + reg = <0x1000 0x400>; +}; + +HUB_LABEL: hub@3400 { + compatible = "ibm,fsi-master-hub"; + reg = <0x3400 0x400>; + #address-cells = <2>; + #size-cells = <0>; + no-scan-on-init; +}; + +#undef __MAKE_LABEL +#undef _MAKE_LABEL +#undef HUB_LABEL +#undef OCC_LABEL +#undef I2C_LABEL
diff --git a/arch/arm/boot/dts/ibm-power9-cfam.dtsi b/arch/arm/boot/dts/ibm-power9-cfam.dtsi new file mode 100644 index 0000000..14c7023 --- /dev/null +++ b/arch/arm/boot/dts/ibm-power9-cfam.dtsi
@@ -0,0 +1,107 @@ +#define __MAKE_LABEL(p,i) p##i +#define _MAKE_LABEL(p,i) __MAKE_LABEL(p,i) +#define HUB_LABEL _MAKE_LABEL(fsi_hub,CFAM_CHIP_ID) +#define OCC_LABEL _MAKE_LABEL(fsi_occ,CFAM_CHIP_ID) +#define I2C_LABEL(n) _MAKE_LABEL(_MAKE_LABEL(cfam,CFAM_CHIP_ID),_i2c##n) + +#address-cells = <1>; +#size-cells = <1>; +chip-id = <CFAM_CHIP_ID>; + +scom@1000 { + compatible = "ibm,fsi2pib"; + reg = <0x1000 0x400>; +}; + +i2c@1800 { + compatible = "ibm,fsi-i2c-master"; + reg = <0x1800 0x400>; + #address-cells = <1>; + #size-cells = <0>; + + I2C_LABEL(0): i2c-bus@0 { + reg = <0>; + }; + + I2C_LABEL(1): i2c-bus@1 { + reg = <1>; + }; + + I2C_LABEL(2): i2c-bus@2 { + reg = <2>; + }; + + I2C_LABEL(3): i2c-bus@3 { + reg = <3>; + }; + + I2C_LABEL(4): i2c-bus@4 { + reg = <4>; + }; + + I2C_LABEL(5): i2c-bus@5 { + reg = <5>; + }; + + I2C_LABEL(6): i2c-bus@6 { + reg = <6>; + }; + + I2C_LABEL(7): i2c-bus@7 { + reg = <7>; + }; + + I2C_LABEL(8): i2c-bus@8 { + reg = <8>; + }; + + I2C_LABEL(9): i2c-bus@9 { + reg = <9>; + }; + + I2C_LABEL(10): i2c-bus@10 { + reg = <10>; + }; + + I2C_LABEL(11): i2c-bus@11 { + reg = <11>; + }; + + I2C_LABEL(12): i2c-bus@12 { + reg = <12>; + }; + + I2C_LABEL(13): i2c-bus@13 { + reg = <13>; + }; + + I2C_LABEL(14): i2c-bus@14 { + reg = <14>; + }; +}; + +sbefifo@2400 { + compatible = "ibm,p9-sbefifo"; + reg = <0x2400 0x400>; + #address-cells = <1>; + #size-cells = <0>; + + OCC_LABEL: occ { + compatible = "ibm,p9-occ"; + }; +}; + +HUB_LABEL: hub@3400 { + compatible = "fsi-master-hub"; + reg = <0x3400 0x400>; + #address-cells = <2>; + #size-cells = <0>; + + no-scan-on-init; +}; + +#undef __MAKE_LABEL +#undef _MAKE_LABEL +#undef HUB_LABEL +#undef OCC_LABEL +#undef I2C_LABEL
diff --git a/arch/arm/boot/dts/ibm-power9-dual.dtsi b/arch/arm/boot/dts/ibm-power9-dual.dtsi new file mode 100644 index 0000000..050fdc6 --- /dev/null +++ b/arch/arm/boot/dts/ibm-power9-dual.dtsi
@@ -0,0 +1,64 @@ +/* Instanciate chip 0 */ +#define CFAM_CHIP_ID 0 +&fsi { + cfam@0,0 { + reg = <0 0>; + #include "ibm-power9-cfam.dtsi" + }; +}; +#undef CFAM_CHIP_ID + +/* Instanciate chip 1 */ +#define CFAM_CHIP_ID 1 +&fsi_hub0 { + cfam@1,0 { + reg = <1 0>; + #include "ibm-power9-cfam.dtsi" + }; +}; +#undef CFAM_CHIP_ID + +/* Legacy OCC numbering (to get rid of when userspace is fixed) */ +&fsi_occ0 { + reg = <1>; +}; + +&fsi_occ1 { + reg = <2>; +}; + +/ { + aliases { + i2c100 = &cfam0_i2c0; + i2c101 = &cfam0_i2c1; + i2c102 = &cfam0_i2c2; + i2c103 = &cfam0_i2c3; + i2c104 = &cfam0_i2c4; + i2c105 = &cfam0_i2c5; + i2c106 = &cfam0_i2c6; + i2c107 = &cfam0_i2c7; + i2c108 = &cfam0_i2c8; + i2c109 = &cfam0_i2c9; + i2c110 = &cfam0_i2c10; + i2c111 = &cfam0_i2c11; + i2c112 = &cfam0_i2c12; + i2c113 = &cfam0_i2c13; + i2c114 = &cfam0_i2c14; + i2c200 = &cfam1_i2c0; + i2c201 = &cfam1_i2c1; + i2c202 = &cfam1_i2c2; + i2c203 = &cfam1_i2c3; + i2c204 = &cfam1_i2c4; + i2c205 = &cfam1_i2c5; + i2c206 = &cfam1_i2c6; + i2c207 = &cfam1_i2c7; + i2c208 = &cfam1_i2c8; + i2c209 = &cfam1_i2c9; + i2c210 = &cfam1_i2c10; + i2c211 = &cfam1_i2c11; + i2c212 = &cfam1_i2c12; + i2c213 = &cfam1_i2c13; + i2c214 = &cfam1_i2c14; + }; +}; +
diff --git a/arch/arm/configs/aspeed_g4_defconfig b/arch/arm/configs/aspeed_g4_defconfig index 95946de..5f65009 100644 --- a/arch/arm/configs/aspeed_g4_defconfig +++ b/arch/arm/configs/aspeed_g4_defconfig
@@ -3,36 +3,44 @@ CONFIG_SYSVIPC=y CONFIG_NO_HZ_IDLE=y CONFIG_HIGH_RES_TIMERS=y -CONFIG_LOG_BUF_SHIFT=14 +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=16 CONFIG_CGROUPS=y CONFIG_BLK_DEV_INITRD=y # CONFIG_RD_BZIP2 is not set # CONFIG_RD_LZO is not set # CONFIG_RD_LZ4 is not set -CONFIG_KALLSYMS_ALL=y -CONFIG_BPF_SYSCALL=y +# CONFIG_UID16 is not set +# CONFIG_SYSFS_SYSCALL is not set # CONFIG_AIO is not set +CONFIG_BPF_SYSCALL=y CONFIG_EMBEDDED=y +CONFIG_PERF_EVENTS=y # CONFIG_COMPAT_BRK is not set CONFIG_SLAB=y +CONFIG_SLAB_FREELIST_RANDOM=y CONFIG_JUMP_LABEL=y CONFIG_GCC_PLUGINS=y -CONFIG_MODULES=y -CONFIG_MODULE_UNLOAD=y +CONFIG_CC_STACKPROTECTOR_STRONG=y +CONFIG_STRICT_KERNEL_RWX=y # CONFIG_LBDAF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEBUG_FS is not set +# CONFIG_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set # CONFIG_ARCH_MULTI_V7 is not set CONFIG_ARCH_ASPEED=y CONFIG_MACH_ASPEED_G4=y CONFIG_VMSPLIT_2G=y CONFIG_AEABI=y -# CONFIG_CPU_SW_DOMAIN_PAN is not set # CONFIG_COMPACTION is not set +CONFIG_UACCESS_WITH_MEMCPY=y CONFIG_SECCOMP=y # CONFIG_ATAGS is not set CONFIG_ZBOOT_ROM_TEXT=0x0 CONFIG_ZBOOT_ROM_BSS=0x0 -CONFIG_ARM_APPENDED_DTB=y -CONFIG_ARM_ATAG_DTB_COMPAT=y CONFIG_KEXEC=y # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set CONFIG_NET=y @@ -47,8 +55,14 @@ # CONFIG_INET_XFRM_MODE_TUNNEL is not set # CONFIG_INET_XFRM_MODE_BEET is not set # CONFIG_INET_DIAG is not set -# CONFIG_IPV6 is not set +# CONFIG_INET6_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET6_XFRM_MODE_TUNNEL is not set +# CONFIG_INET6_XFRM_MODE_BEET is not set +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_ADVANCED is not set +CONFIG_VLAN_8021Q=y CONFIG_NET_NCSI=y +CONFIG_BPF_STREAM_PARSER=y # CONFIG_WIRELESS is not set CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" CONFIG_DEVTMPFS=y @@ -58,30 +72,37 @@ CONFIG_MTD_BLOCK=y CONFIG_MTD_PARTITIONED_MASTER=y CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set CONFIG_SPI_ASPEED_SMC=y CONFIG_MTD_UBI=y CONFIG_MTD_UBI_FASTMAP=y CONFIG_MTD_UBI_BLOCK=y -CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_LOOP=y CONFIG_ASPEED_LPC_CTRL=y CONFIG_ASPEED_LPC_SNOOP=y +CONFIG_ASPEED_LPC_MBOX=y CONFIG_EEPROM_AT24=y CONFIG_NETDEVICES=y CONFIG_NETCONSOLE=y # CONFIG_NET_VENDOR_ALACRITECH is not set # CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set # CONFIG_NET_VENDOR_ARC is not set # CONFIG_NET_CADENCE is not set # CONFIG_NET_VENDOR_BROADCOM is not set # CONFIG_NET_VENDOR_CIRRUS is not set +# CONFIG_NET_VENDOR_CORTINA is not set # CONFIG_NET_VENDOR_EZCHIP is not set CONFIG_FTGMAC100=y # CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set # CONFIG_NET_VENDOR_INTEL is not set # CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set # CONFIG_NET_VENDOR_MICREL is not set # CONFIG_NET_VENDOR_NATSEMI is not set # CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NI is not set # CONFIG_NET_VENDOR_QUALCOMM is not set # CONFIG_NET_VENDOR_RENESAS is not set # CONFIG_NET_VENDOR_ROCKER is not set @@ -89,13 +110,20 @@ # CONFIG_NET_VENDOR_SEEQ is not set # CONFIG_NET_VENDOR_SOLARFLARE is not set # CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set # CONFIG_NET_VENDOR_STMICRO is not set # CONFIG_NET_VENDOR_VIA is not set # CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set CONFIG_BROADCOM_PHY=y CONFIG_REALTEK_PHY=y +# CONFIG_USB_NET_DRIVERS is not set # CONFIG_WLAN is not set -# CONFIG_INPUT is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_GPIO_POLLED=y +# CONFIG_INPUT_MOUSE is not set # CONFIG_SERIO is not set # CONFIG_VT is not set # CONFIG_LEGACY_PTYS is not set @@ -108,15 +136,16 @@ CONFIG_SERIAL_8250_ASPEED_VUART=y CONFIG_SERIAL_8250_SHARE_IRQ=y CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_ASPEED_KCS_IPMI_BMC=y CONFIG_ASPEED_BT_IPMI_BMC=y -# CONFIG_HW_RANDOM is not set -CONFIG_I2C=y +CONFIG_HW_RANDOM_TIMERIOMEM=y # CONFIG_I2C_COMPAT is not set CONFIG_I2C_CHARDEV=y CONFIG_I2C_MUX=y CONFIG_I2C_MUX_PCA9541=y CONFIG_I2C_MUX_PCA954x=y CONFIG_I2C_ASPEED=y +CONFIG_I2C_FSI=y CONFIG_GPIOLIB=y CONFIG_GPIO_SYSFS=y CONFIG_GPIO_ASPEED=y @@ -127,21 +156,51 @@ CONFIG_SENSORS_IIO_HWMON=y CONFIG_SENSORS_LM75=y CONFIG_SENSORS_NCT7904=y +CONFIG_SENSORS_OCC=y +CONFIG_SENSORS_OCC_P8_I2C=y +CONFIG_SENSORS_OCC_P9_SBE=y CONFIG_PMBUS=y CONFIG_SENSORS_ADM1275=y +CONFIG_SENSORS_IBM_CFFPS=y +CONFIG_SENSORS_IR35221=y CONFIG_SENSORS_LM25066=y +CONFIG_SENSORS_MAX31785=y CONFIG_SENSORS_UCD9000=y +CONFIG_SENSORS_UCD9200=y CONFIG_SENSORS_TMP421=y +CONFIG_SENSORS_W83773G=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_DRM=y +CONFIG_DRM_ASPEED_GFX=y CONFIG_USB=y CONFIG_USB_ANNOUNCE_NEW_DEVICES=y CONFIG_USB_DYNAMIC_MINORS=y CONFIG_USB_EHCI_HCD=y CONFIG_USB_EHCI_ROOT_HUB_TT=y CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_GADGET=y +CONFIG_U_SERIAL_CONSOLE=y +CONFIG_USB_ASPEED_VHUB=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_LB_SS=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_PRINTER=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_LEDS_CLASS_FLASH=y CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PCA955X=y +CONFIG_LEDS_PCA955X_GPIO=y CONFIG_LEDS_TRIGGERS=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_LEDS_TRIGGER_HEARTBEAT=y @@ -150,33 +209,56 @@ CONFIG_RTC_DRV_DS1307=y CONFIG_RTC_DRV_PCF8523=y CONFIG_RTC_DRV_RV8803=y -CONFIG_MAILBOX=y +# CONFIG_VIRTIO_MENU is not set # CONFIG_IOMMU_SUPPORT is not set CONFIG_IIO=y CONFIG_ASPEED_ADC=y +CONFIG_MAX1363=y CONFIG_BMP280=y +CONFIG_FSI=y +CONFIG_FSI_MASTER_GPIO=y +CONFIG_FSI_MASTER_HUB=y +CONFIG_FSI_SCOM=y +CONFIG_FSI_SBEFIFO=y +CONFIG_FSI_OCC=y CONFIG_FIRMWARE_MEMMAP=y CONFIG_FANOTIFY=y CONFIG_OVERLAY_FS=y CONFIG_TMPFS=y CONFIG_JFFS2_FS=y +# CONFIG_JFFS2_FS_WRITEBUFFER is not set CONFIG_JFFS2_SUMMARY=y CONFIG_JFFS2_FS_XATTR=y CONFIG_UBIFS_FS=y CONFIG_SQUASHFS=y CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +# CONFIG_NETWORK_FILESYSTEMS is not set CONFIG_PRINTK_TIME=y CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_REDUCED=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_GDB_SCRIPTS=y CONFIG_STRIP_ASM_SYMS=y -CONFIG_DEBUG_FS=y +CONFIG_SOFTLOCKUP_DETECTOR=y +# CONFIG_DETECT_HUNG_TASK is not set CONFIG_WQ_WATCHDOG=y +CONFIG_PANIC_ON_OOPS=y CONFIG_PANIC_TIMEOUT=-1 # CONFIG_SCHED_DEBUG is not set CONFIG_SCHED_STACK_END_CHECK=y -CONFIG_STACKTRACE=y -# CONFIG_FTRACE is not set +CONFIG_FUNCTION_TRACER=y +# CONFIG_TRACING_EVENTS_GPIO is not set +# CONFIG_RUNTIME_TESTING_MENU is not set +CONFIG_DEBUG_WX=y CONFIG_DEBUG_USER=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_FORTIFY_SOURCE=y # CONFIG_CRYPTO_ECHAINIV is not set +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_USER_API_HASH=y # CONFIG_CRYPTO_HW is not set # CONFIG_XZ_DEC_X86 is not set # CONFIG_XZ_DEC_POWERPC is not set
diff --git a/arch/arm/configs/aspeed_g5_defconfig b/arch/arm/configs/aspeed_g5_defconfig index 8c7ea03..b7f8fa1 100644 --- a/arch/arm/configs/aspeed_g5_defconfig +++ b/arch/arm/configs/aspeed_g5_defconfig
@@ -3,40 +3,48 @@ CONFIG_SYSVIPC=y CONFIG_NO_HZ_IDLE=y CONFIG_HIGH_RES_TIMERS=y -CONFIG_LOG_BUF_SHIFT=14 +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_LOG_BUF_SHIFT=16 CONFIG_CGROUPS=y CONFIG_BLK_DEV_INITRD=y # CONFIG_RD_BZIP2 is not set # CONFIG_RD_LZO is not set # CONFIG_RD_LZ4 is not set -CONFIG_KALLSYMS_ALL=y -CONFIG_BPF_SYSCALL=y +# CONFIG_UID16 is not set +# CONFIG_SYSFS_SYSCALL is not set # CONFIG_AIO is not set +CONFIG_BPF_SYSCALL=y CONFIG_EMBEDDED=y +CONFIG_PERF_EVENTS=y # CONFIG_COMPAT_BRK is not set CONFIG_SLAB=y +CONFIG_SLAB_FREELIST_RANDOM=y CONFIG_JUMP_LABEL=y CONFIG_GCC_PLUGINS=y -CONFIG_MODULES=y -CONFIG_MODULE_UNLOAD=y +CONFIG_CC_STACKPROTECTOR_STRONG=y +CONFIG_STRICT_KERNEL_RWX=y # CONFIG_LBDAF is not set +# CONFIG_BLK_DEV_BSG is not set +# CONFIG_BLK_DEBUG_FS is not set +# CONFIG_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_DEADLINE is not set +# CONFIG_MQ_IOSCHED_KYBER is not set CONFIG_ARCH_MULTI_V6=y # CONFIG_ARCH_MULTI_V7 is not set CONFIG_ARCH_ASPEED=y CONFIG_MACH_ASPEED_G5=y # CONFIG_CACHE_L2X0 is not set CONFIG_VMSPLIT_2G=y -CONFIG_AEABI=y -# CONFIG_CPU_SW_DOMAIN_PAN is not set # CONFIG_COMPACTION is not set +CONFIG_UACCESS_WITH_MEMCPY=y CONFIG_SECCOMP=y # CONFIG_ATAGS is not set CONFIG_ZBOOT_ROM_TEXT=0x0 CONFIG_ZBOOT_ROM_BSS=0x0 -CONFIG_ARM_APPENDED_DTB=y -CONFIG_ARM_ATAG_DTB_COMPAT=y CONFIG_KEXEC=y # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +# CONFIG_SUSPEND is not set CONFIG_NET=y CONFIG_PACKET=y CONFIG_PACKET_DIAG=y @@ -49,8 +57,14 @@ # CONFIG_INET_XFRM_MODE_TUNNEL is not set # CONFIG_INET_XFRM_MODE_BEET is not set # CONFIG_INET_DIAG is not set -# CONFIG_IPV6 is not set +# CONFIG_INET6_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET6_XFRM_MODE_TUNNEL is not set +# CONFIG_INET6_XFRM_MODE_BEET is not set +CONFIG_NETFILTER=y +# CONFIG_NETFILTER_ADVANCED is not set +CONFIG_VLAN_8021Q=y CONFIG_NET_NCSI=y +CONFIG_BPF_STREAM_PARSER=y # CONFIG_WIRELESS is not set CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" CONFIG_DEVTMPFS=y @@ -60,30 +74,37 @@ CONFIG_MTD_BLOCK=y CONFIG_MTD_PARTITIONED_MASTER=y CONFIG_MTD_SPI_NOR=y +# CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is not set CONFIG_SPI_ASPEED_SMC=y CONFIG_MTD_UBI=y CONFIG_MTD_UBI_FASTMAP=y CONFIG_MTD_UBI_BLOCK=y -CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_LOOP=y CONFIG_ASPEED_LPC_CTRL=y CONFIG_ASPEED_LPC_SNOOP=y +CONFIG_ASPEED_LPC_MBOX=y CONFIG_EEPROM_AT24=y CONFIG_NETDEVICES=y CONFIG_NETCONSOLE=y # CONFIG_NET_VENDOR_ALACRITECH is not set # CONFIG_NET_VENDOR_AMAZON is not set +# CONFIG_NET_VENDOR_AQUANTIA is not set # CONFIG_NET_VENDOR_ARC is not set # CONFIG_NET_CADENCE is not set # CONFIG_NET_VENDOR_BROADCOM is not set # CONFIG_NET_VENDOR_CIRRUS is not set +# CONFIG_NET_VENDOR_CORTINA is not set # CONFIG_NET_VENDOR_EZCHIP is not set CONFIG_FTGMAC100=y # CONFIG_NET_VENDOR_HISILICON is not set +# CONFIG_NET_VENDOR_HUAWEI is not set # CONFIG_NET_VENDOR_INTEL is not set # CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set # CONFIG_NET_VENDOR_MICREL is not set # CONFIG_NET_VENDOR_NATSEMI is not set # CONFIG_NET_VENDOR_NETRONOME is not set +# CONFIG_NET_VENDOR_NI is not set # CONFIG_NET_VENDOR_QUALCOMM is not set # CONFIG_NET_VENDOR_RENESAS is not set # CONFIG_NET_VENDOR_ROCKER is not set @@ -91,13 +112,20 @@ # CONFIG_NET_VENDOR_SEEQ is not set # CONFIG_NET_VENDOR_SOLARFLARE is not set # CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_SOCIONEXT is not set # CONFIG_NET_VENDOR_STMICRO is not set # CONFIG_NET_VENDOR_VIA is not set # CONFIG_NET_VENDOR_WIZNET is not set +# CONFIG_NET_VENDOR_SYNOPSYS is not set CONFIG_BROADCOM_PHY=y CONFIG_REALTEK_PHY=y +# CONFIG_USB_NET_DRIVERS is not set # CONFIG_WLAN is not set -# CONFIG_INPUT is not set +CONFIG_INPUT_EVDEV=y +# CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_GPIO=y +CONFIG_KEYBOARD_GPIO_POLLED=y +# CONFIG_INPUT_MOUSE is not set # CONFIG_SERIO is not set # CONFIG_VT is not set # CONFIG_LEGACY_PTYS is not set @@ -110,15 +138,16 @@ CONFIG_SERIAL_8250_ASPEED_VUART=y CONFIG_SERIAL_8250_SHARE_IRQ=y CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_ASPEED_KCS_IPMI_BMC=y CONFIG_ASPEED_BT_IPMI_BMC=y -# CONFIG_HW_RANDOM is not set -CONFIG_I2C=y +CONFIG_HW_RANDOM_TIMERIOMEM=y # CONFIG_I2C_COMPAT is not set CONFIG_I2C_CHARDEV=y CONFIG_I2C_MUX=y CONFIG_I2C_MUX_PCA9541=y CONFIG_I2C_MUX_PCA954x=y CONFIG_I2C_ASPEED=y +CONFIG_I2C_FSI=y CONFIG_GPIOLIB=y CONFIG_GPIO_SYSFS=y CONFIG_GPIO_ASPEED=y @@ -129,21 +158,51 @@ CONFIG_SENSORS_IIO_HWMON=y CONFIG_SENSORS_LM75=y CONFIG_SENSORS_NCT7904=y +CONFIG_SENSORS_OCC=y +CONFIG_SENSORS_OCC_P8_I2C=y +CONFIG_SENSORS_OCC_P9_SBE=y CONFIG_PMBUS=y CONFIG_SENSORS_ADM1275=y +CONFIG_SENSORS_IBM_CFFPS=y +CONFIG_SENSORS_IR35221=y CONFIG_SENSORS_LM25066=y +CONFIG_SENSORS_MAX31785=y CONFIG_SENSORS_UCD9000=y +CONFIG_SENSORS_UCD9200=y CONFIG_SENSORS_TMP421=y +CONFIG_SENSORS_W83773G=y +CONFIG_WATCHDOG_SYSFS=y +CONFIG_DRM=y +CONFIG_DRM_ASPEED_GFX=y CONFIG_USB=y CONFIG_USB_ANNOUNCE_NEW_DEVICES=y CONFIG_USB_DYNAMIC_MINORS=y CONFIG_USB_EHCI_HCD=y CONFIG_USB_EHCI_ROOT_HUB_TT=y CONFIG_USB_EHCI_HCD_PLATFORM=y +CONFIG_USB_GADGET=y +CONFIG_U_SERIAL_CONSOLE=y +CONFIG_USB_ASPEED_VHUB=y +CONFIG_USB_CONFIGFS=y +CONFIG_USB_CONFIGFS_SERIAL=y +CONFIG_USB_CONFIGFS_ACM=y +CONFIG_USB_CONFIGFS_OBEX=y +CONFIG_USB_CONFIGFS_NCM=y +CONFIG_USB_CONFIGFS_ECM=y +CONFIG_USB_CONFIGFS_ECM_SUBSET=y +CONFIG_USB_CONFIGFS_RNDIS=y +CONFIG_USB_CONFIGFS_EEM=y +CONFIG_USB_CONFIGFS_MASS_STORAGE=y +CONFIG_USB_CONFIGFS_F_LB_SS=y +CONFIG_USB_CONFIGFS_F_FS=y +CONFIG_USB_CONFIGFS_F_HID=y +CONFIG_USB_CONFIGFS_F_PRINTER=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_LEDS_CLASS_FLASH=y CONFIG_LEDS_GPIO=y +CONFIG_LEDS_PCA955X=y +CONFIG_LEDS_PCA955X_GPIO=y CONFIG_LEDS_TRIGGERS=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_LEDS_TRIGGER_HEARTBEAT=y @@ -152,33 +211,57 @@ CONFIG_RTC_DRV_DS1307=y CONFIG_RTC_DRV_PCF8523=y CONFIG_RTC_DRV_RV8803=y -CONFIG_MAILBOX=y +# CONFIG_VIRTIO_MENU is not set # CONFIG_IOMMU_SUPPORT is not set CONFIG_IIO=y CONFIG_ASPEED_ADC=y +CONFIG_MAX1363=y CONFIG_BMP280=y +CONFIG_DPS310=y +CONFIG_FSI=y +CONFIG_FSI_MASTER_GPIO=y +CONFIG_FSI_MASTER_HUB=y +CONFIG_FSI_SCOM=y +CONFIG_FSI_SBEFIFO=y +CONFIG_FSI_OCC=y CONFIG_FIRMWARE_MEMMAP=y CONFIG_FANOTIFY=y CONFIG_OVERLAY_FS=y CONFIG_TMPFS=y CONFIG_JFFS2_FS=y +# CONFIG_JFFS2_FS_WRITEBUFFER is not set CONFIG_JFFS2_SUMMARY=y CONFIG_JFFS2_FS_XATTR=y CONFIG_UBIFS_FS=y CONFIG_SQUASHFS=y CONFIG_SQUASHFS_XZ=y +CONFIG_SQUASHFS_ZSTD=y +# CONFIG_NETWORK_FILESYSTEMS is not set CONFIG_PRINTK_TIME=y CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_INFO=y +CONFIG_DEBUG_INFO_REDUCED=y +CONFIG_DEBUG_INFO_DWARF4=y +CONFIG_GDB_SCRIPTS=y CONFIG_STRIP_ASM_SYMS=y -CONFIG_DEBUG_FS=y +CONFIG_SOFTLOCKUP_DETECTOR=y +# CONFIG_DETECT_HUNG_TASK is not set CONFIG_WQ_WATCHDOG=y +CONFIG_PANIC_ON_OOPS=y CONFIG_PANIC_TIMEOUT=-1 # CONFIG_SCHED_DEBUG is not set CONFIG_SCHED_STACK_END_CHECK=y -CONFIG_STACKTRACE=y -# CONFIG_FTRACE is not set +CONFIG_FUNCTION_TRACER=y +# CONFIG_TRACING_EVENTS_GPIO is not set +# CONFIG_RUNTIME_TESTING_MENU is not set +CONFIG_DEBUG_WX=y CONFIG_DEBUG_USER=y +CONFIG_HARDENED_USERCOPY=y +CONFIG_FORTIFY_SOURCE=y # CONFIG_CRYPTO_ECHAINIV is not set +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_USER_API_HASH=y # CONFIG_CRYPTO_HW is not set # CONFIG_XZ_DEC_X86 is not set # CONFIG_XZ_DEC_POWERPC is not set
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig index 3bda116..470f976 100644 --- a/drivers/char/ipmi/Kconfig +++ b/drivers/char/ipmi/Kconfig
@@ -111,6 +111,21 @@ The driver implements the BMC side of the KCS contorller, it provides the access of KCS IO space for BMC side. +config NPCM7XX_KCS_IPMI_BMC + depends on ARCH_NPCM7XX || COMPILE_TEST + select IPMI_KCS_BMC + select REGMAP_MMIO + tristate "NPCM7xx KCS IPMI BMC driver" + help + Provides a driver for the KCS (Keyboard Controller Style) IPMI + interface found on Nuvoton NPCM7xx SOCs. + + The driver implements the BMC side of the KCS contorller, it + provides the access of KCS IO space for BMC side. + + This support is also available as a module. If so, the module + will be called kcs_bmc_npcm7xx. + config ASPEED_BT_IPMI_BMC depends on ARCH_ASPEED || COMPILE_TEST depends on REGMAP && REGMAP_MMIO && MFD_SYSCON
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile index 21e9e87..7a3baf3 100644 --- a/drivers/char/ipmi/Makefile +++ b/drivers/char/ipmi/Makefile
@@ -24,3 +24,4 @@ obj-$(CONFIG_IPMI_KCS_BMC) += kcs_bmc.o obj-$(CONFIG_ASPEED_BT_IPMI_BMC) += bt-bmc.o obj-$(CONFIG_ASPEED_KCS_IPMI_BMC) += kcs_bmc_aspeed.o +obj-$(CONFIG_NPCM7XX_KCS_IPMI_BMC) += kcs_bmc_npcm7xx.o
diff --git a/drivers/char/ipmi/kcs_bmc_npcm7xx.c b/drivers/char/ipmi/kcs_bmc_npcm7xx.c new file mode 100644 index 0000000..722f739 --- /dev/null +++ b/drivers/char/ipmi/kcs_bmc_npcm7xx.c
@@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, Nuvoton Corporation. + * Copyright (c) 2018, Intel Corporation. + */ + +#define pr_fmt(fmt) "nuvoton-kcs-bmc: " fmt + +#include <linux/atomic.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#include "kcs_bmc.h" + +#define DEVICE_NAME "npcm-kcs-bmc" +#define KCS_CHANNEL_MAX 3 + +#define KCS1ST 0x0C +#define KCS2ST 0x1E +#define KCS3ST 0x30 + +#define KCS1DO 0x0E +#define KCS2DO 0x20 +#define KCS3DO 0x32 + +#define KCS1DI 0x10 +#define KCS2DI 0x22 +#define KCS3DI 0x34 + +#define KCS1CTL 0x18 +#define KCS2CTL 0x2A +#define KCS3CTL 0x3C +#define KCS_CTL_IBFIE BIT(0) + +#define KCS1IE 0x1C +#define KCS2IE 0x2E +#define KCS3IE 0x40 +#define KCS_IE_IRQE BIT(0) +#define KCS_IE_HIRQE BIT(3) + +/* + * 7.2.4 Core KCS Registers + * Registers in this module are 8 bits. An 8-bit register must be accessed + * by an 8-bit read or write. + * + * sts: KCS Channel n Status Register (KCSnST). + * dob: KCS Channel n Data Out Buffer Register (KCSnDO). + * dib: KCS Channel n Data In Buffer Register (KCSnDI). + * ctl: KCS Channel n Control Register (KCSnCTL). + * ie : KCS Channel n Interrupt Enable Register (KCSnIE). + */ +struct npcm7xx_kcs_reg { + u32 sts; + u32 dob; + u32 dib; + u32 ctl; + u32 ie; +}; + +struct npcm7xx_kcs_bmc { + struct regmap *map; + + const struct npcm7xx_kcs_reg *reg; +}; + +static const struct npcm7xx_kcs_reg npcm7xx_kcs_reg_tbl[KCS_CHANNEL_MAX] = { + { .sts = KCS1ST, .dob = KCS1DO, .dib = KCS1DI, .ctl = KCS1CTL, .ie = KCS1IE }, + { .sts = KCS2ST, .dob = KCS2DO, .dib = KCS2DI, .ctl = KCS2CTL, .ie = KCS2IE }, + { .sts = KCS3ST, .dob = KCS3DO, .dib = KCS3DI, .ctl = KCS3CTL, .ie = KCS3IE }, +}; + +static u8 npcm7xx_kcs_inb(struct kcs_bmc *kcs_bmc, u32 reg) +{ + struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc); + u32 val = 0; + int rc; + + rc = regmap_read(priv->map, reg, &val); + WARN(rc != 0, "regmap_read() failed: %d\n", rc); + + return rc == 0 ? (u8)val : 0; +} + +static void npcm7xx_kcs_outb(struct kcs_bmc *kcs_bmc, u32 reg, u8 data) +{ + struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc); + int rc; + + rc = regmap_write(priv->map, reg, data); + WARN(rc != 0, "regmap_write() failed: %d\n", rc); +} + +static void npcm7xx_kcs_enable_channel(struct kcs_bmc *kcs_bmc, bool enable) +{ + struct npcm7xx_kcs_bmc *priv = kcs_bmc_priv(kcs_bmc); + + regmap_update_bits(priv->map, priv->reg->ctl, KCS_CTL_IBFIE, + enable ? KCS_CTL_IBFIE : 0); + + regmap_update_bits(priv->map, priv->reg->ie, KCS_IE_IRQE | KCS_IE_HIRQE, + enable ? KCS_IE_IRQE | KCS_IE_HIRQE : 0); +} + +static irqreturn_t npcm7xx_kcs_irq(int irq, void *arg) +{ + struct kcs_bmc *kcs_bmc = arg; + + if (!kcs_bmc_handle_event(kcs_bmc)) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static int npcm7xx_kcs_config_irq(struct kcs_bmc *kcs_bmc, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + return devm_request_irq(dev, irq, npcm7xx_kcs_irq, IRQF_SHARED, + dev_name(dev), kcs_bmc); +} + +static int npcm7xx_kcs_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct npcm7xx_kcs_bmc *priv; + struct kcs_bmc *kcs_bmc; + u32 chan; + int rc; + + rc = of_property_read_u32(dev->of_node, "kcs_chan", &chan); + if (rc != 0 || chan == 0 || chan > KCS_CHANNEL_MAX) { + dev_err(dev, "no valid 'kcs_chan' configured\n"); + return -ENODEV; + } + + kcs_bmc = kcs_bmc_alloc(dev, sizeof(*priv), chan); + if (!kcs_bmc) + return -ENOMEM; + + priv = kcs_bmc_priv(kcs_bmc); + priv->map = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(priv->map)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + priv->reg = &npcm7xx_kcs_reg_tbl[chan - 1]; + + kcs_bmc->ioreg.idr = priv->reg->dib; + kcs_bmc->ioreg.odr = priv->reg->dob; + kcs_bmc->ioreg.str = priv->reg->sts; + kcs_bmc->io_inputb = npcm7xx_kcs_inb; + kcs_bmc->io_outputb = npcm7xx_kcs_outb; + + dev_set_drvdata(dev, kcs_bmc); + + npcm7xx_kcs_enable_channel(kcs_bmc, true); + rc = npcm7xx_kcs_config_irq(kcs_bmc, pdev); + if (rc) + return rc; + + rc = misc_register(&kcs_bmc->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + return rc; + } + + pr_info("channel=%u idr=0x%x odr=0x%x str=0x%x\n", + chan, + kcs_bmc->ioreg.idr, kcs_bmc->ioreg.odr, kcs_bmc->ioreg.str); + + return 0; +} + +static int npcm7xx_kcs_remove(struct platform_device *pdev) +{ + struct kcs_bmc *kcs_bmc = dev_get_drvdata(&pdev->dev); + + misc_deregister(&kcs_bmc->miscdev); + + return 0; +} + +static const struct of_device_id npcm_kcs_bmc_match[] = { + { .compatible = "nuvoton,npcm750-kcs-bmc" }, + { } +}; +MODULE_DEVICE_TABLE(of, npcm_kcs_bmc_match); + +static struct platform_driver npcm_kcs_bmc_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = npcm_kcs_bmc_match, + }, + .probe = npcm7xx_kcs_probe, + .remove = npcm7xx_kcs_remove, +}; +module_platform_driver(npcm_kcs_bmc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Avi Fishman <avifishman70@gmail.com>"); +MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>"); +MODULE_DESCRIPTION("NPCM7xx device interface to the KCS BMC device");
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index de6d06a..0ae4aa5 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o obj-$(CONFIG_ARCH_MOXART) += clk-moxart.o obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o +obj-$(CONFIG_ARCH_NPCM7XX) += clk-npcm7xx.o obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o obj-$(CONFIG_COMMON_CLK_OXNAS) += clk-oxnas.o obj-$(CONFIG_COMMON_CLK_PALMAS) += clk-palmas.o
diff --git a/drivers/clk/clk-aspeed.c b/drivers/clk/clk-aspeed.c index 43e0c33..8796b8a 100644 --- a/drivers/clk/clk-aspeed.c +++ b/drivers/clk/clk-aspeed.c
@@ -14,7 +14,9 @@ #include <dt-bindings/clock/aspeed-clock.h> -#define ASPEED_NUM_CLKS 35 +#define ASPEED_NUM_CLKS 36 + +#define ASPEED_RESET2_OFFSET 32 #define ASPEED_RESET_CTRL 0x04 #define ASPEED_CLK_SELECTION 0x08 @@ -30,6 +32,7 @@ #define CLKIN_25MHZ_EN BIT(23) #define AST2400_CLK_SOURCE_SEL BIT(18) #define ASPEED_CLK_SELECTION_2 0xd8 +#define ASPEED_RESET_CTRL2 0xd4 /* Globally visible clocks */ static DEFINE_SPINLOCK(aspeed_clk_lock); @@ -209,9 +212,22 @@ static int aspeed_clk_is_enabled(struct clk_hw *hw) { struct aspeed_clk_gate *gate = to_aspeed_clk_gate(hw); u32 clk = BIT(gate->clock_idx); + u32 rst = BIT(gate->reset_idx); u32 enval = (gate->flags & CLK_GATE_SET_TO_DISABLE) ? 0 : clk; u32 reg; + /* + * If the IP is in reset, treat the clock as not enabled, + * this happens with some clocks such as the USB one when + * coming from cold reset. Without this, aspeed_clk_enable() + * will fail to lift the reset. + */ + if (gate->reset_idx >= 0) { + regmap_read(gate->map, ASPEED_RESET_CTRL, ®); + if (reg & rst) + return 0; + } + regmap_read(gate->map, ASPEED_CLK_STOP_CTRL, ®); return ((reg & clk) == enval) ? 1 : 0; @@ -291,6 +307,7 @@ struct aspeed_reset { #define to_aspeed_reset(p) container_of((p), struct aspeed_reset, rcdev) static const u8 aspeed_resets[] = { + /* SCU04 resets */ [ASPEED_RESET_XDMA] = 25, [ASPEED_RESET_MCTP] = 24, [ASPEED_RESET_ADC] = 23, @@ -300,38 +317,62 @@ static const u8 aspeed_resets[] = { [ASPEED_RESET_PECI] = 10, [ASPEED_RESET_I2C] = 2, [ASPEED_RESET_AHB] = 1, + + /* + * SCUD4 resets start at an offset to separate them from + * the SCU04 resets. + */ + [ASPEED_RESET_CRT1] = ASPEED_RESET2_OFFSET + 5, }; static int aspeed_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) { struct aspeed_reset *ar = to_aspeed_reset(rcdev); - u32 rst = BIT(aspeed_resets[id]); + u32 reg = ASPEED_RESET_CTRL; + u32 bit = aspeed_resets[id]; - return regmap_update_bits(ar->map, ASPEED_RESET_CTRL, rst, 0); + if (bit >= ASPEED_RESET2_OFFSET) { + bit -= ASPEED_RESET2_OFFSET; + reg = ASPEED_RESET_CTRL2; + } + + return regmap_update_bits(ar->map, reg, BIT(bit), 0); } static int aspeed_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) { struct aspeed_reset *ar = to_aspeed_reset(rcdev); - u32 rst = BIT(aspeed_resets[id]); + u32 reg = ASPEED_RESET_CTRL; + u32 bit = aspeed_resets[id]; - return regmap_update_bits(ar->map, ASPEED_RESET_CTRL, rst, rst); + if (bit >= ASPEED_RESET2_OFFSET) { + bit -= ASPEED_RESET2_OFFSET; + reg = ASPEED_RESET_CTRL2; + } + + return regmap_update_bits(ar->map, reg, BIT(bit), BIT(bit)); } static int aspeed_reset_status(struct reset_controller_dev *rcdev, unsigned long id) { struct aspeed_reset *ar = to_aspeed_reset(rcdev); - u32 val, rst = BIT(aspeed_resets[id]); - int ret; + u32 reg = ASPEED_RESET_CTRL; + u32 bit = aspeed_resets[id]; + int ret, val; - ret = regmap_read(ar->map, ASPEED_RESET_CTRL, &val); + if (bit >= ASPEED_RESET2_OFFSET) { + bit -= ASPEED_RESET2_OFFSET; + reg = ASPEED_RESET_CTRL2; + } + + ret = regmap_read(ar->map, reg, &val); if (ret) return ret; - return !!(val & rst); + return !!(val & BIT(bit)); } static const struct reset_control_ops aspeed_reset_ops = { @@ -474,6 +515,13 @@ static int aspeed_clk_probe(struct platform_device *pdev) return PTR_ERR(hw); aspeed_clk_data->hws[ASPEED_CLK_BCLK] = hw; + /* Fixed 24MHz clock */ + hw = clk_hw_register_fixed_rate(NULL, "fixed-24m", "clkin", + 0, 24000000); + if (IS_ERR(hw)) + return PTR_ERR(hw); + aspeed_clk_data->hws[ASPEED_CLK_24M] = hw; + /* * TODO: There are a number of clocks that not included in this driver * as more information is required:
diff --git a/drivers/clk/clk-npcm7xx.c b/drivers/clk/clk-npcm7xx.c new file mode 100644 index 0000000..740af90 --- /dev/null +++ b/drivers/clk/clk-npcm7xx.c
@@ -0,0 +1,656 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Nuvoton NPCM7xx Clock Generator + * All the clocks are initialized by the bootloader, so this driver allow only + * reading of current settings directly from the hardware. + * + * Copyright (C) 2018 Nuvoton Technologies tali.perry@nuvoton.com + */ + +#include <linux/module.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/bitfield.h> + +#include <dt-bindings/clock/nuvoton,npcm7xx-clock.h> + +struct npcm7xx_clk_pll { + struct clk_hw hw; + void __iomem *pllcon; + u8 flags; +}; + +#define to_npcm7xx_clk_pll(_hw) container_of(_hw, struct npcm7xx_clk_pll, hw) + +#define PLLCON_LOKI BIT(31) +#define PLLCON_LOKS BIT(30) +#define PLLCON_FBDV GENMASK(27, 16) +#define PLLCON_OTDV2 GENMASK(15, 13) +#define PLLCON_PWDEN BIT(12) +#define PLLCON_OTDV1 GENMASK(10, 8) +#define PLLCON_INDV GENMASK(5, 0) + +static unsigned long npcm7xx_clk_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct npcm7xx_clk_pll *pll = to_npcm7xx_clk_pll(hw); + unsigned long fbdv, indv, otdv1, otdv2; + unsigned int val; + u64 ret; + + if (parent_rate == 0) { + pr_err("%s: parent rate is zero", __func__); + return 0; + } + + val = readl_relaxed(pll->pllcon); + + indv = FIELD_GET(PLLCON_INDV, val); + fbdv = FIELD_GET(PLLCON_FBDV, val); + otdv1 = FIELD_GET(PLLCON_OTDV1, val); + otdv2 = FIELD_GET(PLLCON_OTDV2, val); + + ret = (u64)parent_rate * fbdv; + do_div(ret, indv * otdv1 * otdv2); + + return ret; +} + +static const struct clk_ops npcm7xx_clk_pll_ops = { + .recalc_rate = npcm7xx_clk_pll_recalc_rate, +}; + +static struct clk_hw * +npcm7xx_clk_register_pll(void __iomem *pllcon, const char *name, + const char *parent_name, unsigned long flags) +{ + struct npcm7xx_clk_pll *pll; + struct clk_init_data init; + struct clk_hw *hw; + int ret; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + pr_debug("%s reg, name=%s, p=%s\n", __func__, name, parent_name); + + init.name = name; + init.ops = &npcm7xx_clk_pll_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = flags; + + pll->pllcon = pllcon; + pll->hw.init = &init; + + hw = &pll->hw; + + ret = clk_hw_register(NULL, hw); + if (ret) { + kfree(pll); + hw = ERR_PTR(ret); + } + + return hw; +} + +#define NPCM7XX_CLKEN1 (0x00) +#define NPCM7XX_CLKEN2 (0x28) +#define NPCM7XX_CLKEN3 (0x30) +#define NPCM7XX_CLKSEL (0x04) +#define NPCM7XX_CLKDIV1 (0x08) +#define NPCM7XX_CLKDIV2 (0x2C) +#define NPCM7XX_CLKDIV3 (0x58) +#define NPCM7XX_PLLCON0 (0x0C) +#define NPCM7XX_PLLCON1 (0x10) +#define NPCM7XX_PLLCON2 (0x54) +#define NPCM7XX_SWRSTR (0x14) +#define NPCM7XX_IRQWAKECON (0x18) +#define NPCM7XX_IRQWAKEFLAG (0x1C) +#define NPCM7XX_IPSRST1 (0x20) +#define NPCM7XX_IPSRST2 (0x24) +#define NPCM7XX_IPSRST3 (0x34) +#define NPCM7XX_WD0RCR (0x38) +#define NPCM7XX_WD1RCR (0x3C) +#define NPCM7XX_WD2RCR (0x40) +#define NPCM7XX_SWRSTC1 (0x44) +#define NPCM7XX_SWRSTC2 (0x48) +#define NPCM7XX_SWRSTC3 (0x4C) +#define NPCM7XX_SWRSTC4 (0x50) +#define NPCM7XX_CORSTC (0x5C) +#define NPCM7XX_PLLCONG (0x60) +#define NPCM7XX_AHBCKFI (0x64) +#define NPCM7XX_SECCNT (0x68) +#define NPCM7XX_CNTR25M (0x6C) + +struct npcm7xx_clk_gate_data { + u32 reg; + u8 bit_idx; + const char *name; + const char *parent_name; + unsigned long flags; + /* + * If this clock is exported via DT, set onecell_idx to constant + * defined in include/dt-bindings/clock/nuvoton, NPCM7XX-clock.h for + * this specific clock. Otherwise, set to -1. + */ + int onecell_idx; +}; + +struct npcm7xx_clk_mux_data { + u8 shift; + u8 mask; + u32 *table; + const char *name; + const char * const *parent_names; + u8 num_parents; + unsigned long flags; + /* + * If this clock is exported via DT, set onecell_idx to constant + * defined in include/dt-bindings/clock/nuvoton, NPCM7XX-clock.h for + * this specific clock. Otherwise, set to -1. + */ + int onecell_idx; + +}; + +struct npcm7xx_clk_div_fixed_data { + u8 mult; + u8 div; + const char *name; + const char *parent_name; + u8 clk_divider_flags; + /* + * If this clock is exported via DT, set onecell_idx to constant + * defined in include/dt-bindings/clock/nuvoton, NPCM7XX-clock.h for + * this specific clock. Otherwise, set to -1. + */ + int onecell_idx; +}; + + +struct npcm7xx_clk_div_data { + u32 reg; + u8 shift; + u8 width; + const char *name; + const char *parent_name; + u8 clk_divider_flags; + unsigned long flags; + /* + * If this clock is exported via DT, set onecell_idx to constant + * defined in include/dt-bindings/clock/nuvoton, NPCM7XX-clock.h for + * this specific clock. Otherwise, set to -1. + */ + int onecell_idx; +}; + +struct npcm7xx_clk_pll_data { + u32 reg; + const char *name; + const char *parent_name; + unsigned long flags; + /* + * If this clock is exported via DT, set onecell_idx to constant + * defined in include/dt-bindings/clock/nuvoton, NPCM7XX-clock.h for + * this specific clock. Otherwise, set to -1. + */ + int onecell_idx; +}; + +/* + * Single copy of strings used to refer to clocks within this driver indexed by + * above enum. + */ +#define NPCM7XX_CLK_S_REFCLK "refclk" +#define NPCM7XX_CLK_S_SYSBYPCK "sysbypck" +#define NPCM7XX_CLK_S_MCBYPCK "mcbypck" +#define NPCM7XX_CLK_S_GFXBYPCK "gfxbypck" +#define NPCM7XX_CLK_S_PLL0 "pll0" +#define NPCM7XX_CLK_S_PLL1 "pll1" +#define NPCM7XX_CLK_S_PLL1_DIV2 "pll1_div2" +#define NPCM7XX_CLK_S_PLL2 "pll2" +#define NPCM7XX_CLK_S_PLL_GFX "pll_gfx" +#define NPCM7XX_CLK_S_PLL2_DIV2 "pll2_div2" +#define NPCM7XX_CLK_S_PIX_MUX "gfx_pixel" +#define NPCM7XX_CLK_S_GPRFSEL_MUX "gprfsel_mux" +#define NPCM7XX_CLK_S_MC_MUX "mc_phy" +#define NPCM7XX_CLK_S_CPU_MUX "cpu" /*AKA system clock.*/ +#define NPCM7XX_CLK_S_MC "mc" +#define NPCM7XX_CLK_S_AXI "axi" /*AKA CLK2*/ +#define NPCM7XX_CLK_S_AHB "ahb" /*AKA CLK4*/ +#define NPCM7XX_CLK_S_CLKOUT_MUX "clkout_mux" +#define NPCM7XX_CLK_S_UART_MUX "uart_mux" +#define NPCM7XX_CLK_S_TIM_MUX "timer_mux" +#define NPCM7XX_CLK_S_SD_MUX "sd_mux" +#define NPCM7XX_CLK_S_GFXM_MUX "gfxm_mux" +#define NPCM7XX_CLK_S_SU_MUX "serial_usb_mux" +#define NPCM7XX_CLK_S_DVC_MUX "dvc_mux" +#define NPCM7XX_CLK_S_GFX_MUX "gfx_mux" +#define NPCM7XX_CLK_S_GFX_PIXEL "gfx_pixel" +#define NPCM7XX_CLK_S_SPI0 "spi0" +#define NPCM7XX_CLK_S_SPI3 "spi3" +#define NPCM7XX_CLK_S_SPIX "spix" +#define NPCM7XX_CLK_S_APB1 "apb1" +#define NPCM7XX_CLK_S_APB2 "apb2" +#define NPCM7XX_CLK_S_APB3 "apb3" +#define NPCM7XX_CLK_S_APB4 "apb4" +#define NPCM7XX_CLK_S_APB5 "apb5" +#define NPCM7XX_CLK_S_TOCK "tock" +#define NPCM7XX_CLK_S_CLKOUT "clkout" +#define NPCM7XX_CLK_S_UART "uart" +#define NPCM7XX_CLK_S_TIMER "timer" +#define NPCM7XX_CLK_S_MMC "mmc" +#define NPCM7XX_CLK_S_SDHC "sdhc" +#define NPCM7XX_CLK_S_ADC "adc" +#define NPCM7XX_CLK_S_GFX "gfx0_gfx1_mem" +#define NPCM7XX_CLK_S_USBIF "serial_usbif" +#define NPCM7XX_CLK_S_USB_HOST "usb_host" +#define NPCM7XX_CLK_S_USB_BRIDGE "usb_bridge" +#define NPCM7XX_CLK_S_PCI "pci" + +static u32 pll_mux_table[] = {0, 1, 2, 3}; +static const char * const pll_mux_parents[] __initconst = { + NPCM7XX_CLK_S_PLL0, + NPCM7XX_CLK_S_PLL1_DIV2, + NPCM7XX_CLK_S_REFCLK, + NPCM7XX_CLK_S_PLL2_DIV2, +}; + +static u32 cpuck_mux_table[] = {0, 1, 2, 3}; +static const char * const cpuck_mux_parents[] __initconst = { + NPCM7XX_CLK_S_PLL0, + NPCM7XX_CLK_S_PLL1_DIV2, + NPCM7XX_CLK_S_REFCLK, + NPCM7XX_CLK_S_SYSBYPCK, +}; + +static u32 pixcksel_mux_table[] = {0, 2}; +static const char * const pixcksel_mux_parents[] __initconst = { + NPCM7XX_CLK_S_PLL_GFX, + NPCM7XX_CLK_S_REFCLK, +}; + +static u32 sucksel_mux_table[] = {2, 3}; +static const char * const sucksel_mux_parents[] __initconst = { + NPCM7XX_CLK_S_REFCLK, + NPCM7XX_CLK_S_PLL2_DIV2, +}; + +static u32 mccksel_mux_table[] = {0, 2, 3}; +static const char * const mccksel_mux_parents[] __initconst = { + NPCM7XX_CLK_S_PLL1_DIV2, + NPCM7XX_CLK_S_REFCLK, + NPCM7XX_CLK_S_MCBYPCK, +}; + +static u32 clkoutsel_mux_table[] = {0, 1, 2, 3, 4}; +static const char * const clkoutsel_mux_parents[] __initconst = { + NPCM7XX_CLK_S_PLL0, + NPCM7XX_CLK_S_PLL1_DIV2, + NPCM7XX_CLK_S_REFCLK, + NPCM7XX_CLK_S_PLL_GFX, // divided by 2 + NPCM7XX_CLK_S_PLL2_DIV2, +}; + +static u32 gfxmsel_mux_table[] = {2, 3}; +static const char * const gfxmsel_mux_parents[] __initconst = { + NPCM7XX_CLK_S_REFCLK, + NPCM7XX_CLK_S_PLL2_DIV2, +}; + +static u32 dvcssel_mux_table[] = {2, 3}; +static const char * const dvcssel_mux_parents[] __initconst = { + NPCM7XX_CLK_S_REFCLK, + NPCM7XX_CLK_S_PLL2, +}; + +static const struct npcm7xx_clk_pll_data npcm7xx_plls[] __initconst = { + {NPCM7XX_PLLCON0, NPCM7XX_CLK_S_PLL0, NPCM7XX_CLK_S_REFCLK, 0, -1}, + + {NPCM7XX_PLLCON1, NPCM7XX_CLK_S_PLL1, + NPCM7XX_CLK_S_REFCLK, 0, -1}, + + {NPCM7XX_PLLCON2, NPCM7XX_CLK_S_PLL2, + NPCM7XX_CLK_S_REFCLK, 0, -1}, + + {NPCM7XX_PLLCONG, NPCM7XX_CLK_S_PLL_GFX, + NPCM7XX_CLK_S_REFCLK, 0, -1}, +}; + +static const struct npcm7xx_clk_mux_data npcm7xx_muxes[] __initconst = { + {0, GENMASK(1, 0), cpuck_mux_table, NPCM7XX_CLK_S_CPU_MUX, + cpuck_mux_parents, ARRAY_SIZE(cpuck_mux_parents), CLK_IS_CRITICAL, + NPCM7XX_CLK_CPU}, + + {4, GENMASK(1, 0), pixcksel_mux_table, NPCM7XX_CLK_S_PIX_MUX, + pixcksel_mux_parents, ARRAY_SIZE(pixcksel_mux_parents), 0, + NPCM7XX_CLK_GFX_PIXEL}, + + {6, GENMASK(1, 0), pll_mux_table, NPCM7XX_CLK_S_SD_MUX, + pll_mux_parents, ARRAY_SIZE(pll_mux_parents), 0, -1}, + + {8, GENMASK(1, 0), pll_mux_table, NPCM7XX_CLK_S_UART_MUX, + pll_mux_parents, ARRAY_SIZE(pll_mux_parents), 0, -1}, + + {10, GENMASK(1, 0), sucksel_mux_table, NPCM7XX_CLK_S_SU_MUX, + sucksel_mux_parents, ARRAY_SIZE(sucksel_mux_parents), 0, -1}, + + {12, GENMASK(1, 0), mccksel_mux_table, NPCM7XX_CLK_S_MC_MUX, + mccksel_mux_parents, ARRAY_SIZE(mccksel_mux_parents), 0, -1}, + + {14, GENMASK(1, 0), pll_mux_table, NPCM7XX_CLK_S_TIM_MUX, + pll_mux_parents, ARRAY_SIZE(pll_mux_parents), 0, -1}, + + {16, GENMASK(1, 0), pll_mux_table, NPCM7XX_CLK_S_GFX_MUX, + pll_mux_parents, ARRAY_SIZE(pll_mux_parents), 0, -1}, + + {18, GENMASK(2, 0), clkoutsel_mux_table, NPCM7XX_CLK_S_CLKOUT_MUX, + clkoutsel_mux_parents, ARRAY_SIZE(clkoutsel_mux_parents), 0, -1}, + + {21, GENMASK(1, 0), gfxmsel_mux_table, NPCM7XX_CLK_S_GFXM_MUX, + gfxmsel_mux_parents, ARRAY_SIZE(gfxmsel_mux_parents), 0, -1}, + + {23, GENMASK(1, 0), dvcssel_mux_table, NPCM7XX_CLK_S_DVC_MUX, + dvcssel_mux_parents, ARRAY_SIZE(dvcssel_mux_parents), 0, -1}, +}; + +/* fixed ratio dividers (no register): */ +static const struct npcm7xx_clk_div_fixed_data npcm7xx_divs_fx[] __initconst = { + { 1, 2, NPCM7XX_CLK_S_MC, NPCM7XX_CLK_S_MC_MUX, 0, NPCM7XX_CLK_MC}, + { 1, 2, NPCM7XX_CLK_S_PLL1_DIV2, NPCM7XX_CLK_S_PLL1, 0, -1}, + { 1, 2, NPCM7XX_CLK_S_PLL2_DIV2, NPCM7XX_CLK_S_PLL2, 0, -1}, +}; + +/* configurable dividers: */ +static const struct npcm7xx_clk_div_data npcm7xx_divs[] __initconst = { + {NPCM7XX_CLKDIV1, 28, 3, NPCM7XX_CLK_S_ADC, + NPCM7XX_CLK_S_TIMER, CLK_DIVIDER_POWER_OF_TWO, 0, NPCM7XX_CLK_ADC}, + /*30-28 ADCCKDIV*/ + {NPCM7XX_CLKDIV1, 26, 2, NPCM7XX_CLK_S_AHB, + NPCM7XX_CLK_S_AXI, 0, CLK_IS_CRITICAL, NPCM7XX_CLK_AHB}, + /*27-26 CLK4DIV*/ + {NPCM7XX_CLKDIV1, 21, 5, NPCM7XX_CLK_S_TIMER, + NPCM7XX_CLK_S_TIM_MUX, 0, 0, NPCM7XX_CLK_TIMER}, + /*25-21 TIMCKDIV*/ + {NPCM7XX_CLKDIV1, 16, 5, NPCM7XX_CLK_S_UART, + NPCM7XX_CLK_S_UART_MUX, 0, 0, NPCM7XX_CLK_UART}, + /*20-16 UARTDIV*/ + {NPCM7XX_CLKDIV1, 11, 5, NPCM7XX_CLK_S_MMC, + NPCM7XX_CLK_S_SD_MUX, 0, 0, NPCM7XX_CLK_MMC}, + /*15-11 MMCCKDIV*/ + {NPCM7XX_CLKDIV1, 6, 5, NPCM7XX_CLK_S_SPI3, + NPCM7XX_CLK_S_AHB, 0, 0, NPCM7XX_CLK_SPI3}, + /*10-6 AHB3CKDIV*/ + {NPCM7XX_CLKDIV1, 2, 4, NPCM7XX_CLK_S_PCI, + NPCM7XX_CLK_S_GFX_MUX, 0, 0, NPCM7XX_CLK_PCI}, + /*5-2 PCICKDIV*/ + {NPCM7XX_CLKDIV1, 0, 1, NPCM7XX_CLK_S_AXI, + NPCM7XX_CLK_S_CPU_MUX, CLK_DIVIDER_POWER_OF_TWO, CLK_IS_CRITICAL, + NPCM7XX_CLK_AXI},/*0 CLK2DIV*/ + + {NPCM7XX_CLKDIV2, 30, 2, NPCM7XX_CLK_S_APB4, + NPCM7XX_CLK_S_AHB, CLK_DIVIDER_POWER_OF_TWO, 0, NPCM7XX_CLK_APB4}, + /*31-30 APB4CKDIV*/ + {NPCM7XX_CLKDIV2, 28, 2, NPCM7XX_CLK_S_APB3, + NPCM7XX_CLK_S_AHB, CLK_DIVIDER_POWER_OF_TWO, 0, NPCM7XX_CLK_APB3}, + /*29-28 APB3CKDIV*/ + {NPCM7XX_CLKDIV2, 26, 2, NPCM7XX_CLK_S_APB2, + NPCM7XX_CLK_S_AHB, CLK_DIVIDER_POWER_OF_TWO, 0, NPCM7XX_CLK_APB2}, + /*27-26 APB2CKDIV*/ + {NPCM7XX_CLKDIV2, 24, 2, NPCM7XX_CLK_S_APB1, + NPCM7XX_CLK_S_AHB, CLK_DIVIDER_POWER_OF_TWO, 0, NPCM7XX_CLK_APB1}, + /*25-24 APB1CKDIV*/ + {NPCM7XX_CLKDIV2, 22, 2, NPCM7XX_CLK_S_APB5, + NPCM7XX_CLK_S_AHB, CLK_DIVIDER_POWER_OF_TWO, 0, NPCM7XX_CLK_APB5}, + /*23-22 APB5CKDIV*/ + {NPCM7XX_CLKDIV2, 16, 5, NPCM7XX_CLK_S_CLKOUT, + NPCM7XX_CLK_S_CLKOUT_MUX, 0, 0, NPCM7XX_CLK_CLKOUT}, + /*20-16 CLKOUTDIV*/ + {NPCM7XX_CLKDIV2, 13, 3, NPCM7XX_CLK_S_GFX, + NPCM7XX_CLK_S_GFX_MUX, 0, 0, NPCM7XX_CLK_GFX}, + /*15-13 GFXCKDIV*/ + {NPCM7XX_CLKDIV2, 8, 5, NPCM7XX_CLK_S_USB_BRIDGE, + NPCM7XX_CLK_S_SU_MUX, 0, 0, NPCM7XX_CLK_SU}, + /*12-8 SUCKDIV*/ + {NPCM7XX_CLKDIV2, 4, 4, NPCM7XX_CLK_S_USB_HOST, + NPCM7XX_CLK_S_SU_MUX, 0, 0, NPCM7XX_CLK_SU48}, + /*7-4 SU48CKDIV*/ + {NPCM7XX_CLKDIV2, 0, 4, NPCM7XX_CLK_S_SDHC, + NPCM7XX_CLK_S_SD_MUX, 0, 0, NPCM7XX_CLK_SDHC} + ,/*3-0 SD1CKDIV*/ + + {NPCM7XX_CLKDIV3, 6, 5, NPCM7XX_CLK_S_SPI0, + NPCM7XX_CLK_S_AHB, 0, 0, NPCM7XX_CLK_SPI0}, + /*10-6 SPI0CKDV*/ + {NPCM7XX_CLKDIV3, 1, 5, NPCM7XX_CLK_S_SPIX, + NPCM7XX_CLK_S_AHB, 0, 0, NPCM7XX_CLK_SPIX}, + /*5-1 SPIXCKDV*/ + +}; + +static const struct npcm7xx_clk_gate_data npcm7xx_gates[] __initconst = { + {NPCM7XX_CLKEN1, 31, "smb1-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN1, 30, "smb0-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN1, 29, "smb7-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN1, 28, "smb6-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN1, 27, "adc-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN1, 26, "wdt-gate", NPCM7XX_CLK_S_TIMER, 0}, + {NPCM7XX_CLKEN1, 25, "usbdev3-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 24, "usbdev6-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 23, "usbdev5-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 22, "usbdev4-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 21, "emc2-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 20, "timer5_9-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN1, 19, "timer0_4-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN1, 18, "pwmm0-gate", NPCM7XX_CLK_S_APB3, 0}, + {NPCM7XX_CLKEN1, 17, "huart-gate", NPCM7XX_CLK_S_UART, 0}, + {NPCM7XX_CLKEN1, 16, "smb5-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN1, 15, "smb4-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN1, 14, "smb3-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN1, 13, "smb2-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN1, 12, "mc-gate", NPCM7XX_CLK_S_MC, 0}, + {NPCM7XX_CLKEN1, 11, "uart01-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN1, 10, "aes-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 9, "peci-gate", NPCM7XX_CLK_S_APB3, 0}, + {NPCM7XX_CLKEN1, 8, "usbdev2-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 7, "uart23-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN1, 6, "emc1-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 5, "usbdev1-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 4, "shm-gate", NPCM7XX_CLK_S_AHB, 0}, + /* bit 3 is reserved */ + {NPCM7XX_CLKEN1, 2, "kcs-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN1, 1, "spi3-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN1, 0, "spi0-gate", NPCM7XX_CLK_S_AHB, 0}, + + {NPCM7XX_CLKEN2, 31, "cp-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 30, "tock-gate", NPCM7XX_CLK_S_TOCK, 0}, + /* bit 29 is reserved */ + {NPCM7XX_CLKEN2, 28, "gmac1-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 27, "usbif-gate", NPCM7XX_CLK_S_USBIF, 0}, + {NPCM7XX_CLKEN2, 26, "usbhost-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 25, "gmac2-gate", NPCM7XX_CLK_S_AHB, 0}, + /* bit 24 is reserved */ + {NPCM7XX_CLKEN2, 23, "pspi2-gate", NPCM7XX_CLK_S_APB5, 0}, + {NPCM7XX_CLKEN2, 22, "pspi1-gate", NPCM7XX_CLK_S_APB5, 0}, + {NPCM7XX_CLKEN2, 21, "3des-gate", NPCM7XX_CLK_S_AHB, 0}, + /* bit 20 is reserved */ + {NPCM7XX_CLKEN2, 19, "siox2-gate", NPCM7XX_CLK_S_APB3, 0}, + {NPCM7XX_CLKEN2, 18, "siox1-gate", NPCM7XX_CLK_S_APB3, 0}, + /* bit 17 is reserved */ + {NPCM7XX_CLKEN2, 16, "fuse-gate", NPCM7XX_CLK_S_APB4, 0}, + /* bit 15 is reserved */ + {NPCM7XX_CLKEN2, 14, "vcd-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 13, "ece-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 12, "vdma-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 11, "ahbpcibrg-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 10, "gfxsys-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN2, 9, "sdhc-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 8, "mmc-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN2, 7, "mft7-gate", NPCM7XX_CLK_S_APB4, 0}, + {NPCM7XX_CLKEN2, 6, "mft6-gate", NPCM7XX_CLK_S_APB4, 0}, + {NPCM7XX_CLKEN2, 5, "mft5-gate", NPCM7XX_CLK_S_APB4, 0}, + {NPCM7XX_CLKEN2, 4, "mft4-gate", NPCM7XX_CLK_S_APB4, 0}, + {NPCM7XX_CLKEN2, 3, "mft3-gate", NPCM7XX_CLK_S_APB4, 0}, + {NPCM7XX_CLKEN2, 2, "mft2-gate", NPCM7XX_CLK_S_APB4, 0}, + {NPCM7XX_CLKEN2, 1, "mft1-gate", NPCM7XX_CLK_S_APB4, 0}, + {NPCM7XX_CLKEN2, 0, "mft0-gate", NPCM7XX_CLK_S_APB4, 0}, + + {NPCM7XX_CLKEN3, 31, "gpiom7-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 30, "gpiom6-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 29, "gpiom5-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 28, "gpiom4-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 27, "gpiom3-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 26, "gpiom2-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 25, "gpiom1-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 24, "gpiom0-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 23, "espi-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN3, 22, "smb11-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN3, 21, "smb10-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN3, 20, "smb9-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN3, 19, "smb8-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN3, 18, "smb15-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN3, 17, "rng-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 16, "timer10_14-gate", NPCM7XX_CLK_S_APB1, 0}, + {NPCM7XX_CLKEN3, 15, "pcirc-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN3, 14, "sececc-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN3, 13, "sha-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN3, 12, "smb14-gate", NPCM7XX_CLK_S_APB2, 0}, + /* bit 11 is reserved */ + /* bit 10 is reserved */ + {NPCM7XX_CLKEN3, 9, "pcimbx-gate", NPCM7XX_CLK_S_AHB, 0}, + /* bit 8 is reserved */ + {NPCM7XX_CLKEN3, 7, "usbdev9-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN3, 6, "usbdev8-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN3, 5, "usbdev7-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN3, 4, "usbdev0-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN3, 3, "smb13-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN3, 2, "spix-gate", NPCM7XX_CLK_S_AHB, 0}, + {NPCM7XX_CLKEN3, 1, "smb12-gate", NPCM7XX_CLK_S_APB2, 0}, + {NPCM7XX_CLKEN3, 0, "pwmm1-gate", NPCM7XX_CLK_S_APB3, 0}, +}; + +static DEFINE_SPINLOCK(npcm7xx_clk_lock); + +static void __init npcm7xx_clk_init(struct device_node *clk_np) +{ + struct clk_hw_onecell_data *npcm7xx_clk_data; + void __iomem *clk_base; + struct resource res; + struct clk_hw *hw; + int ret; + int i; + + ret = of_address_to_resource(clk_np, 0, &res); + if (ret) { + pr_err("%s: failed to get resource, ret %d\n", clk_np->name, + ret); + return; + } + + clk_base = ioremap(res.start, resource_size(&res)); + if (!clk_base) + goto npcm7xx_init_error; + + npcm7xx_clk_data = kzalloc(sizeof(*npcm7xx_clk_data->hws) * + NPCM7XX_NUM_CLOCKS + sizeof(npcm7xx_clk_data), GFP_KERNEL); + if (!npcm7xx_clk_data) + goto npcm7xx_init_np_err; + + npcm7xx_clk_data->num = NPCM7XX_NUM_CLOCKS; + + for (i = 0; i < NPCM7XX_NUM_CLOCKS; i++) + npcm7xx_clk_data->hws[i] = ERR_PTR(-EPROBE_DEFER); + + /* Register plls */ + for (i = 0; i < ARRAY_SIZE(npcm7xx_plls); i++) { + const struct npcm7xx_clk_pll_data *pll_data = &npcm7xx_plls[i]; + + hw = npcm7xx_clk_register_pll(clk_base + pll_data->reg, + pll_data->name, pll_data->parent_name, pll_data->flags); + if (IS_ERR(hw)) { + pr_err("npcm7xx_clk: Can't register pll\n"); + goto npcm7xx_init_fail; + } + + if (pll_data->onecell_idx >= 0) + npcm7xx_clk_data->hws[pll_data->onecell_idx] = hw; + } + + /* Register fixed dividers */ + hw = clk_hw_register_fixed_factor(NULL, NPCM7XX_CLK_S_PLL1_DIV2, + NPCM7XX_CLK_S_PLL1, 0, 1, 2); + if (IS_ERR(hw)) { + pr_err("npcm7xx_clk: Can't register fixed div\n"); + goto npcm7xx_init_fail; + } + + hw = clk_hw_register_fixed_factor(NULL, NPCM7XX_CLK_S_PLL2_DIV2, + NPCM7XX_CLK_S_PLL2, 0, 1, 2); + if (IS_ERR(hw)) { + pr_err("npcm7xx_clk: Can't register div2\n"); + goto npcm7xx_init_fail; + } + + /* Register muxes */ + for (i = 0; i < ARRAY_SIZE(npcm7xx_muxes); i++) { + const struct npcm7xx_clk_mux_data *mux_data = &npcm7xx_muxes[i]; + + hw = clk_hw_register_mux_table(NULL, + mux_data->name, + mux_data->parent_names, mux_data->num_parents, + mux_data->flags, clk_base + NPCM7XX_CLKSEL, + mux_data->shift, mux_data->mask, 0, + mux_data->table, &npcm7xx_clk_lock); + + if (IS_ERR(hw)) { + pr_err("npcm7xx_clk: Can't register mux\n"); + goto npcm7xx_init_fail; + } + + if (mux_data->onecell_idx >= 0) + npcm7xx_clk_data->hws[mux_data->onecell_idx] = hw; + } + + /* Register clock dividers specified in npcm7xx_divs */ + for (i = 0; i < ARRAY_SIZE(npcm7xx_divs); i++) { + const struct npcm7xx_clk_div_data *div_data = &npcm7xx_divs[i]; + + hw = clk_hw_register_divider(NULL, div_data->name, + div_data->parent_name, + div_data->flags, + clk_base + div_data->reg, + div_data->shift, div_data->width, + div_data->clk_divider_flags, &npcm7xx_clk_lock); + if (IS_ERR(hw)) { + pr_err("npcm7xx_clk: Can't register div table\n"); + goto npcm7xx_init_fail; + } + + if (div_data->onecell_idx >= 0) + npcm7xx_clk_data->hws[div_data->onecell_idx] = hw; + } + + ret = of_clk_add_hw_provider(clk_np, of_clk_hw_onecell_get, + npcm7xx_clk_data); + if (ret) + pr_err("failed to add DT provider: %d\n", ret); + + of_node_put(clk_np); + + return; + +npcm7xx_init_fail: + kfree(npcm7xx_clk_data->hws); +npcm7xx_init_np_err: + iounmap(clk_base); +npcm7xx_init_error: + of_node_put(clk_np); +} +CLK_OF_DECLARE(npcm7xx_clk_init, "nuvoton,npcm750-clk", npcm7xx_clk_init);
diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index a326ed6..ea2f4a1 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig
@@ -12,6 +12,21 @@ if FSI +config FSI_NEW_DEV_NODE + bool "Create '/dev/fsi' directory for char devices" + default n + ---help--- + This option causes char devices created for FSI devices to be + located under a common /dev/fsi/ directory. Set to N unless your + userspace has been updated to handle the new location. + + Additionally, it also causes the char device names to be offset + by one so that chip 0 will have /dev/scom1 and chip1 /dev/scom2 + to match old userspace expectations. + + New userspace will use udev rules to generate predictable access + symlinks in /dev/fsi/by-path when this option is enabled. + config FSI_MASTER_GPIO tristate "GPIO-based FSI master" depends on GPIOLIB @@ -27,9 +42,36 @@ allow chaining of FSI links to an arbitrary depth. This allows for a high target device fanout. +config FSI_MASTER_AST_CF + tristate "FSI master based on Aspeed ColdFire coprocessor" + depends on GPIOLIB + depends on GPIO_ASPEED + ---help--- + This option enables a FSI master using the AST2400 and AST2500 GPIO + lines driven by the internal ColdFire coprocessor. This requires + the corresponding machine specific ColdFire firmware to be available. + config FSI_SCOM tristate "SCOM FSI client device driver" ---help--- This option enables an FSI based SCOM device driver. +config FSI_SBEFIFO + tristate "SBEFIFO FSI client device driver" + depends on OF_ADDRESS + ---help--- + This option enables an FSI based SBEFIFO device driver. The SBEFIFO is + a pipe-like FSI device for communicating with the self boot engine + (SBE) on POWER processors. + +config FSI_OCC + tristate "OCC SBEFIFO client device driver" + depends on FSI_SBEFIFO + ---help--- + This option enables an SBEFIFO based On-Chip Controller (OCC) device + driver. The OCC is a device embedded on a POWER processor that collects + and aggregates sensor data from the processor and system. The OCC can + provide the raw sensor data as well as perform thermal and power + management on the system. + endif
diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index 65eb99d..62687ec 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile
@@ -2,4 +2,7 @@ obj-$(CONFIG_FSI) += fsi-core.o obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o +obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o obj-$(CONFIG_FSI_SCOM) += fsi-scom.o +obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o +obj-$(CONFIG_FSI_OCC) += fsi-occ.o
diff --git a/drivers/fsi/cf-fsi-fw.h b/drivers/fsi/cf-fsi-fw.h new file mode 100644 index 0000000..712df04 --- /dev/null +++ b/drivers/fsi/cf-fsi-fw.h
@@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0+ +#ifndef __CF_FSI_FW_H +#define __CF_FSI_FW_H + +/* + * uCode file layout + * + * 0000...03ff : m68k exception vectors + * 0400...04ff : Header info & boot config block + * 0500....... : Code & stack + */ + +/* + * Header info & boot config area + * + * The Header info is built into the ucode and provide version and + * platform information. + * + * the Boot config needs to be adjusted by the ARM prior to starting + * the ucode if the Command/Status area isn't at 0x320000 in CF space + * (ie. beginning of SRAM). + */ + +#define HDR_OFFSET 0x400 + +/* Info: Signature & version */ +#define HDR_SYS_SIG 0x00 /* 2 bytes system signature */ +#define SYS_SIG_SHARED 0x5348 +#define SYS_SIG_SPLIT 0x5350 +#define HDR_FW_VERS 0x02 /* 2 bytes Major.Minor */ +#define HDR_API_VERS 0x04 /* 2 bytes Major.Minor */ +#define API_VERSION_MAJ 2 /* Current version */ +#define API_VERSION_MIN 1 +#define HDR_FW_OPTIONS 0x08 /* 4 bytes option flags */ +#define FW_OPTION_TRACE_EN 0x00000001 /* FW tracing enabled */ +#define FW_OPTION_CONT_CLOCK 0x00000002 /* Continuous clocking supported */ +#define HDR_FW_SIZE 0x10 /* 4 bytes size for combo image */ + +/* Boot Config: Address of Command/Status area */ +#define HDR_CMD_STAT_AREA 0x80 /* 4 bytes CF address */ +#define HDR_FW_CONTROL 0x84 /* 4 bytes control flags */ +#define FW_CONTROL_CONT_CLOCK 0x00000002 /* Continuous clocking enabled */ +#define FW_CONTROL_DUMMY_RD 0x00000004 /* Extra dummy read (AST2400) */ +#define FW_CONTROL_USE_STOP 0x00000008 /* Use STOP instructions */ +#define HDR_CLOCK_GPIO_VADDR 0x90 /* 2 bytes offset from GPIO base */ +#define HDR_CLOCK_GPIO_DADDR 0x92 /* 2 bytes offset from GPIO base */ +#define HDR_DATA_GPIO_VADDR 0x94 /* 2 bytes offset from GPIO base */ +#define HDR_DATA_GPIO_DADDR 0x96 /* 2 bytes offset from GPIO base */ +#define HDR_TRANS_GPIO_VADDR 0x98 /* 2 bytes offset from GPIO base */ +#define HDR_TRANS_GPIO_DADDR 0x9a /* 2 bytes offset from GPIO base */ +#define HDR_CLOCK_GPIO_BIT 0x9c /* 1 byte bit number */ +#define HDR_DATA_GPIO_BIT 0x9d /* 1 byte bit number */ +#define HDR_TRANS_GPIO_BIT 0x9e /* 1 byte bit number */ + +/* + * Command/Status area layout: Main part + */ + +/* Command/Status register: + * + * +---------------------------+ + * | STAT | RLEN | CLEN | CMD | + * | 8 | 8 | 8 | 8 | + * +---------------------------+ + * | | | | + * status | | | + * Response len | | + * (in bits) | | + * | | + * Command len | + * (in bits) | + * | + * Command code + * + * Due to the big endian layout, that means that a byte read will + * return the status byte + */ +#define CMD_STAT_REG 0x00 +#define CMD_REG_CMD_MASK 0x000000ff +#define CMD_REG_CMD_SHIFT 0 +#define CMD_NONE 0x00 +#define CMD_COMMAND 0x01 +#define CMD_BREAK 0x02 +#define CMD_IDLE_CLOCKS 0x03 /* clen = #clocks */ +#define CMD_INVALID 0xff +#define CMD_REG_CLEN_MASK 0x0000ff00 +#define CMD_REG_CLEN_SHIFT 8 +#define CMD_REG_RLEN_MASK 0x00ff0000 +#define CMD_REG_RLEN_SHIFT 16 +#define CMD_REG_STAT_MASK 0xff000000 +#define CMD_REG_STAT_SHIFT 24 +#define STAT_WORKING 0x00 +#define STAT_COMPLETE 0x01 +#define STAT_ERR_INVAL_CMD 0x80 +#define STAT_ERR_INVAL_IRQ 0x81 +#define STAT_ERR_MTOE 0x82 + +/* Response tag & CRC */ +#define STAT_RTAG 0x04 + +/* Response CRC */ +#define STAT_RCRC 0x05 + +/* Echo and Send delay */ +#define ECHO_DLY_REG 0x08 +#define SEND_DLY_REG 0x09 + +/* Command data area + * + * Last byte of message must be left aligned + */ +#define CMD_DATA 0x10 /* 64 bit of data */ + +/* Response data area, right aligned, unused top bits are 1 */ +#define RSP_DATA 0x20 /* 32 bit of data */ + +/* Misc */ +#define INT_CNT 0x30 /* 32-bit interrupt count */ +#define BAD_INT_VEC 0x34 /* 32-bit bad interrupt vector # */ +#define CF_STARTED 0x38 /* byte, set to -1 when copro started */ +#define CLK_CNT 0x3c /* 32-bit, clock count (debug only) */ + +/* + * SRAM layout: GPIO arbitration part + */ +#define ARB_REG 0x40 +#define ARB_ARM_REQ 0x01 +#define ARB_ARM_ACK 0x02 + +/* Misc2 */ +#define CF_RESET_D0 0x50 +#define CF_RESET_D1 0x54 +#define BAD_INT_S0 0x58 +#define BAD_INT_S1 0x5c +#define STOP_CNT 0x60 + +/* Internal */ + +/* + * SRAM layout: Trace buffer (debug builds only) + */ +#define TRACEBUF 0x100 +#define TR_CLKOBIT0 0xc0 +#define TR_CLKOBIT1 0xc1 +#define TR_CLKOSTART 0x82 +#define TR_OLEN 0x83 /* + len */ +#define TR_CLKZ 0x84 /* + count */ +#define TR_CLKWSTART 0x85 +#define TR_CLKTAG 0x86 /* + tag */ +#define TR_CLKDATA 0x87 /* + len */ +#define TR_CLKCRC 0x88 /* + raw crc */ +#define TR_CLKIBIT0 0x90 +#define TR_CLKIBIT1 0x91 +#define TR_END 0xff + +#endif /* __CF_FSI_FW_H */ +
diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index 4c03d69..2c31563 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c
@@ -11,6 +11,11 @@ * 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. + * + * TODO: + * - Rework topology + * - s/chip_id/chip_loc + * - s/cfam/chip (cfam_id -> chip_id etc...) */ #include <linux/crc4.h> @@ -21,6 +26,9 @@ #include <linux/of.h> #include <linux/slab.h> #include <linux/bitops.h> +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> #include "fsi-master.h" @@ -78,9 +86,15 @@ static DEFINE_IDA(master_ida); struct fsi_slave { struct device dev; struct fsi_master *master; - int id; - int link; + struct cdev cdev; + int cdev_idx; + int id; /* FSI address */ + int link; /* FSI link# */ + u32 cfam_id; + int chip_id; uint32_t size; /* size of slave address space */ + u8 t_send_delay; + u8 t_echo_delay; }; #define to_fsi_master(d) container_of(d, struct fsi_master, dev) @@ -89,6 +103,13 @@ struct fsi_slave { static const int slave_retries = 2; static int discard_errors; +static dev_t fsi_base_dev; +static DEFINE_IDA(fsi_minor_ida); +#define FSI_CHAR_MAX_DEVICES 0x1000 + +/* Legacy /dev numbering: 4 devices per chip, 16 chips */ +#define FSI_CHAR_LEGACY_TOP 64 + static int fsi_master_read(struct fsi_master *master, int link, uint8_t slave_id, uint32_t addr, void *val, size_t size); static int fsi_master_write(struct fsi_master *master, int link, @@ -190,7 +211,7 @@ static int fsi_slave_calc_addr(struct fsi_slave *slave, uint32_t *addrp, static int fsi_slave_report_and_clear_errors(struct fsi_slave *slave) { struct fsi_master *master = slave->master; - uint32_t irq, stat; + __be32 irq, stat; int rc, link; uint8_t id; @@ -215,7 +236,53 @@ static int fsi_slave_report_and_clear_errors(struct fsi_slave *slave) &irq, sizeof(irq)); } -static int fsi_slave_set_smode(struct fsi_master *master, int link, int id); +/* Encode slave local bus echo delay */ +static inline uint32_t fsi_smode_echodly(int x) +{ + return (x & FSI_SMODE_ED_MASK) << FSI_SMODE_ED_SHIFT; +} + +/* Encode slave local bus send delay */ +static inline uint32_t fsi_smode_senddly(int x) +{ + return (x & FSI_SMODE_SD_MASK) << FSI_SMODE_SD_SHIFT; +} + +/* Encode slave local bus clock rate ratio */ +static inline uint32_t fsi_smode_lbcrr(int x) +{ + return (x & FSI_SMODE_LBCRR_MASK) << FSI_SMODE_LBCRR_SHIFT; +} + +/* Encode slave ID */ +static inline uint32_t fsi_smode_sid(int x) +{ + return (x & FSI_SMODE_SID_MASK) << FSI_SMODE_SID_SHIFT; +} + +static uint32_t fsi_slave_smode(int id, u8 t_senddly, u8 t_echodly) +{ + return FSI_SMODE_WSC | FSI_SMODE_ECRC + | fsi_smode_sid(id) + | fsi_smode_echodly(t_echodly - 1) | fsi_smode_senddly(t_senddly - 1) + | fsi_smode_lbcrr(0x8); +} + +static int fsi_slave_set_smode(struct fsi_slave *slave) +{ + uint32_t smode; + __be32 data; + + /* set our smode register with the slave ID field to 0; this enables + * extended slave addressing + */ + smode = fsi_slave_smode(slave->id, slave->t_send_delay, slave->t_echo_delay); + data = cpu_to_be32(smode); + + return fsi_master_write(slave->master, slave->link, slave->id, + FSI_SLAVE_BASE + FSI_SMODE, + &data, sizeof(data)); +} static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, uint32_t addr, size_t size) @@ -223,7 +290,7 @@ static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, struct fsi_master *master = slave->master; int rc, link; uint32_t reg; - uint8_t id; + uint8_t id, send_delay, echo_delay; if (discard_errors) return -1; @@ -254,15 +321,26 @@ static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, } } + send_delay = slave->t_send_delay; + echo_delay = slave->t_echo_delay; + /* getting serious, reset the slave via BREAK */ rc = fsi_master_break(master, link); if (rc) return rc; - rc = fsi_slave_set_smode(master, link, id); + slave->t_send_delay = send_delay; + slave->t_echo_delay = echo_delay; + + rc = fsi_slave_set_smode(slave); if (rc) return rc; + if (master->link_config) + master->link_config(master, link, + slave->t_send_delay, + slave->t_echo_delay); + return fsi_slave_report_and_clear_errors(slave); } @@ -390,7 +468,6 @@ static struct device_node *fsi_device_find_of_node(struct fsi_device *dev) static int fsi_slave_scan(struct fsi_slave *slave) { uint32_t engine_addr; - uint32_t conf; int rc, i; /* @@ -404,15 +481,17 @@ static int fsi_slave_scan(struct fsi_slave *slave) for (i = 2; i < engine_page_size / sizeof(uint32_t); i++) { uint8_t slots, version, type, crc; struct fsi_device *dev; + uint32_t conf; + __be32 data; - rc = fsi_slave_read(slave, (i + 1) * sizeof(conf), - &conf, sizeof(conf)); + rc = fsi_slave_read(slave, (i + 1) * sizeof(data), + &data, sizeof(data)); if (rc) { dev_warn(&slave->dev, "error reading slave registers\n"); return -1; } - conf = be32_to_cpu(conf); + conf = be32_to_cpu(data); crc = crc4(0, conf, 32); if (crc) { @@ -539,79 +618,11 @@ static const struct bin_attribute fsi_slave_raw_attr = { .write = fsi_slave_sysfs_raw_write, }; -static ssize_t fsi_slave_sysfs_term_write(struct file *file, - struct kobject *kobj, struct bin_attribute *attr, - char *buf, loff_t off, size_t count) -{ - struct fsi_slave *slave = to_fsi_slave(kobj_to_dev(kobj)); - struct fsi_master *master = slave->master; - - if (!master->term) - return -ENODEV; - - master->term(master, slave->link, slave->id); - return count; -} - -static const struct bin_attribute fsi_slave_term_attr = { - .attr = { - .name = "term", - .mode = 0200, - }, - .size = 0, - .write = fsi_slave_sysfs_term_write, -}; - -/* Encode slave local bus echo delay */ -static inline uint32_t fsi_smode_echodly(int x) -{ - return (x & FSI_SMODE_ED_MASK) << FSI_SMODE_ED_SHIFT; -} - -/* Encode slave local bus send delay */ -static inline uint32_t fsi_smode_senddly(int x) -{ - return (x & FSI_SMODE_SD_MASK) << FSI_SMODE_SD_SHIFT; -} - -/* Encode slave local bus clock rate ratio */ -static inline uint32_t fsi_smode_lbcrr(int x) -{ - return (x & FSI_SMODE_LBCRR_MASK) << FSI_SMODE_LBCRR_SHIFT; -} - -/* Encode slave ID */ -static inline uint32_t fsi_smode_sid(int x) -{ - return (x & FSI_SMODE_SID_MASK) << FSI_SMODE_SID_SHIFT; -} - -static uint32_t fsi_slave_smode(int id) -{ - return FSI_SMODE_WSC | FSI_SMODE_ECRC - | fsi_smode_sid(id) - | fsi_smode_echodly(0xf) | fsi_smode_senddly(0xf) - | fsi_smode_lbcrr(0x8); -} - -static int fsi_slave_set_smode(struct fsi_master *master, int link, int id) -{ - uint32_t smode; - - /* set our smode register with the slave ID field to 0; this enables - * extended slave addressing - */ - smode = fsi_slave_smode(id); - smode = cpu_to_be32(smode); - - return fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SMODE, - &smode, sizeof(smode)); -} - static void fsi_slave_release(struct device *dev) { struct fsi_slave *slave = to_fsi_slave(dev); + fsi_free_minor(slave->dev.devt); of_node_put(dev->of_node); kfree(slave); } @@ -659,11 +670,303 @@ static struct device_node *fsi_slave_find_of_node(struct fsi_master *master, return NULL; } +static ssize_t cfam_read(struct file *filep, char __user *buf, size_t count, + loff_t *offset) +{ + struct fsi_slave *slave = filep->private_data; + size_t total_len, read_len; + loff_t off = *offset; + ssize_t rc; + + if (off < 0) + return -EINVAL; + + if (off > 0xffffffff || count > 0xffffffff || off + count > 0xffffffff) + return -EINVAL; + + for (total_len = 0; total_len < count; total_len += read_len) { + __be32 data; + + read_len = min_t(size_t, count, 4); + read_len -= off & 0x3; + + rc = fsi_slave_read(slave, off, &data, read_len); + if (rc) + goto fail; + rc = copy_to_user(buf + total_len, &data, read_len); + if (rc) { + rc = -EFAULT; + goto fail; + } + off += read_len; + } + rc = count; + fail: + *offset = off; + return count; +} + +static ssize_t cfam_write(struct file *filep, const char __user *buf, + size_t count, loff_t *offset) +{ + struct fsi_slave *slave = filep->private_data; + size_t total_len, write_len; + loff_t off = *offset; + ssize_t rc; + + + if (off < 0) + return -EINVAL; + + if (off > 0xffffffff || count > 0xffffffff || off + count > 0xffffffff) + return -EINVAL; + + for (total_len = 0; total_len < count; total_len += write_len) { + __be32 data; + + write_len = min_t(size_t, count, 4); + write_len -= off & 0x3; + + rc = copy_from_user(&data, buf + total_len, write_len); + if (rc) { + rc = -EFAULT; + goto fail; + } + rc = fsi_slave_write(slave, off, &data, write_len); + if (rc) + goto fail; + off += write_len; + } + rc = count; + fail: + *offset = off; + return count; +} + +static loff_t cfam_llseek(struct file *file, loff_t offset, int whence) +{ + switch (whence) { + case SEEK_CUR: + break; + case SEEK_SET: + file->f_pos = offset; + break; + default: + return -EINVAL; + } + + return offset; +} + +static int cfam_open(struct inode *inode, struct file *file) +{ + struct fsi_slave *slave = container_of(inode->i_cdev, struct fsi_slave, cdev); + + file->private_data = slave; + + return 0; +} + +static const struct file_operations cfam_fops = { + .owner = THIS_MODULE, + .open = cfam_open, + .llseek = cfam_llseek, + .read = cfam_read, + .write = cfam_write, +}; + +static ssize_t send_term_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + struct fsi_master *master = slave->master; + + if (!master->term) + return -ENODEV; + + master->term(master, slave->link, slave->id); + return count; +} + +static DEVICE_ATTR_WO(send_term); + +static ssize_t slave_send_echo_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "%u\n", slave->t_send_delay); +} + +static ssize_t slave_send_echo_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + struct fsi_master *master = slave->master; + unsigned long val; + int rc; + + if (kstrtoul(buf, 0, &val) < 0) + return -EINVAL; + + if (val < 1 || val > 16) + return -EINVAL; + + if (!master->link_config) + return -ENXIO; + + /* Current HW mandates that send and echo delay are identical */ + slave->t_send_delay = val; + slave->t_echo_delay = val; + + rc = fsi_slave_set_smode(slave); + if (rc < 0) + return rc; + if (master->link_config) + master->link_config(master, slave->link, + slave->t_send_delay, + slave->t_echo_delay); + + return count; +} + +static DEVICE_ATTR(send_echo_delays, 0600, + slave_send_echo_show, slave_send_echo_store); + +static ssize_t chip_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "%d\n", slave->chip_id); +} + +static DEVICE_ATTR_RO(chip_id); + +static ssize_t cfam_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "0x%x\n", slave->cfam_id); +} + +static DEVICE_ATTR_RO(cfam_id); + +static struct attribute *cfam_attr[] = { + &dev_attr_send_echo_delays.attr, + &dev_attr_chip_id.attr, + &dev_attr_cfam_id.attr, + &dev_attr_send_term.attr, + NULL, +}; + +static const struct attribute_group cfam_attr_group = { + .attrs = cfam_attr, +}; + +static const struct attribute_group *cfam_attr_groups[] = { + &cfam_attr_group, + NULL, +}; + +static char *cfam_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + +#ifdef CONFIG_FSI_NEW_DEV_NODE + return kasprintf(GFP_KERNEL, "fsi/cfam%d", slave->cdev_idx); +#else + return kasprintf(GFP_KERNEL, "cfam%d", slave->cdev_idx); +#endif +} + +static const struct device_type cfam_type = { + .name = "cfam", + .devnode = cfam_devnode, + .groups = cfam_attr_groups +}; + +static char *fsi_cdev_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ +#ifdef CONFIG_FSI_NEW_DEV_NODE + return kasprintf(GFP_KERNEL, "fsi/%s", dev_name(dev)); +#else + return kasprintf(GFP_KERNEL, "%s", dev_name(dev)); +#endif +} + +const struct device_type fsi_cdev_type = { + .name = "fsi-cdev", + .devnode = fsi_cdev_devnode, +}; +EXPORT_SYMBOL_GPL(fsi_cdev_type); + +/* Backward compatible /dev/ numbering in "old style" mode */ +static int fsi_adjust_index(int index) +{ +#ifdef CONFIG_FSI_NEW_DEV_NODE + return index; +#else + return index + 1; +#endif +} + +static int __fsi_get_new_minor(struct fsi_slave *slave, enum fsi_dev_type type, + dev_t *out_dev, int *out_index) +{ + int cid = slave->chip_id; + int id; + + /* Check if we qualify for legacy numbering */ + if (cid >= 0 && cid < 16 && type < 4) { + /* Try reserving the legacy number */ + id = (cid << 4) | type; + id = ida_simple_get(&fsi_minor_ida, id, id + 1, GFP_KERNEL); + if (id >= 0) { + *out_index = fsi_adjust_index(cid); + *out_dev = fsi_base_dev + id; + return 0; + } + /* Other failure */ + if (id != -ENOSPC) + return id; + /* Fallback to non-legacy allocation */ + } + id = ida_simple_get(&fsi_minor_ida, FSI_CHAR_LEGACY_TOP, + FSI_CHAR_MAX_DEVICES, GFP_KERNEL); + if (id < 0) + return id; + *out_index = fsi_adjust_index(id); + *out_dev = fsi_base_dev + id; + return 0; +} + +int fsi_get_new_minor(struct fsi_device *fdev, enum fsi_dev_type type, + dev_t *out_dev, int *out_index) +{ + return __fsi_get_new_minor(fdev->slave, type, out_dev, out_index); +} +EXPORT_SYMBOL_GPL(fsi_get_new_minor); + +void fsi_free_minor(dev_t dev) +{ + ida_simple_remove(&fsi_minor_ida, MINOR(dev)); +} +EXPORT_SYMBOL_GPL(fsi_free_minor); + static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) { - uint32_t chip_id, llmode; + uint32_t cfam_id; struct fsi_slave *slave; uint8_t crc; + __be32 data, llmode; int rc; /* Currently, we only support single slaves on a link, and use the @@ -672,31 +975,23 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) if (id != 0) return -EINVAL; - rc = fsi_master_read(master, link, id, 0, &chip_id, sizeof(chip_id)); + rc = fsi_master_read(master, link, id, 0, &data, sizeof(data)); if (rc) { dev_dbg(&master->dev, "can't read slave %02x:%02x %d\n", link, id, rc); return -ENODEV; } - chip_id = be32_to_cpu(chip_id); + cfam_id = be32_to_cpu(data); - crc = crc4(0, chip_id, 32); + crc = crc4(0, cfam_id, 32); if (crc) { - dev_warn(&master->dev, "slave %02x:%02x invalid chip id CRC!\n", + dev_warn(&master->dev, "slave %02x:%02x invalid cfam id CRC!\n", link, id); return -EIO; } dev_dbg(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n", - chip_id, master->idx, link, id); - - rc = fsi_slave_set_smode(master, link, id); - if (rc) { - dev_warn(&master->dev, - "can't set smode on slave:%02x:%02x %d\n", - link, id, rc); - return -ENODEV; - } + cfam_id, master->idx, link, id); /* If we're behind a master that doesn't provide a self-running bus * clock, put the slave into async mode @@ -719,30 +1014,61 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) if (!slave) return -ENOMEM; - slave->master = master; + dev_set_name(&slave->dev, "slave@%02x:%02x", link, id); + slave->dev.type = &cfam_type; slave->dev.parent = &master->dev; slave->dev.of_node = fsi_slave_find_of_node(master, link, id); slave->dev.release = fsi_slave_release; + device_initialize(&slave->dev); + slave->cfam_id = cfam_id; + slave->master = master; slave->link = link; slave->id = id; slave->size = FSI_SLAVE_SIZE_23b; + slave->t_send_delay = 16; + slave->t_echo_delay = 16; - dev_set_name(&slave->dev, "slave@%02x:%02x", link, id); - rc = device_register(&slave->dev); - if (rc < 0) { - dev_warn(&master->dev, "failed to create slave device: %d\n", - rc); - put_device(&slave->dev); - return rc; + /* Get chip ID if any */ + slave->chip_id = -1; + if (slave->dev.of_node) { + uint32_t prop; + if (!of_property_read_u32(slave->dev.of_node, "chip-id", &prop)) + slave->chip_id = prop; + } + /* Allocate a minor in the FSI space */ + rc = __fsi_get_new_minor(slave, fsi_dev_cfam, &slave->dev.devt, + &slave->cdev_idx); + if (rc) + goto err_free; + + /* Create chardev for userspace access */ + cdev_init(&slave->cdev, &cfam_fops); + rc = cdev_device_add(&slave->cdev, &slave->dev); + if (rc) { + dev_err(&slave->dev, "Error %d creating slave device\n", rc); + goto err_free; + } + + rc = fsi_slave_set_smode(slave); + if (rc) { + dev_warn(&master->dev, + "can't set smode on slave:%02x:%02x %d\n", + link, id, rc); + kfree(slave); + return -ENODEV; + } + if (master->link_config) + master->link_config(master, link, + slave->t_send_delay, + slave->t_echo_delay); + + /* Legacy raw file -> to be removed */ rc = device_create_bin_file(&slave->dev, &fsi_slave_raw_attr); if (rc) dev_warn(&slave->dev, "failed to create raw attr: %d\n", rc); - rc = device_create_bin_file(&slave->dev, &fsi_slave_term_attr); - if (rc) - dev_warn(&slave->dev, "failed to create term attr: %d\n", rc); rc = fsi_slave_scan(slave); if (rc) @@ -750,6 +1076,10 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) rc); return rc; + + err_free: + put_device(&slave->dev); + return rc; } /* FSI master support */ @@ -814,12 +1144,16 @@ static int fsi_master_link_enable(struct fsi_master *master, int link) */ static int fsi_master_break(struct fsi_master *master, int link) { + int rc = 0; + trace_fsi_master_break(master, link); if (master->send_break) - return master->send_break(master, link); + rc = master->send_break(master, link); + if (master->link_config) + master->link_config(master, link, 16, 16); - return 0; + return rc; } static int fsi_master_scan(struct fsi_master *master) @@ -854,8 +1188,11 @@ static int fsi_slave_remove_device(struct device *dev, void *arg) static int fsi_master_remove_slave(struct device *dev, void *arg) { + struct fsi_slave *slave = to_fsi_slave(dev); + device_for_each_child(dev, NULL, fsi_slave_remove_device); - device_unregister(dev); + cdev_device_del(&slave->cdev, &slave->dev); + put_device(dev); return 0; } @@ -866,8 +1203,14 @@ static void fsi_master_unscan(struct fsi_master *master) int fsi_master_rescan(struct fsi_master *master) { + int rc; + + mutex_lock(&master->scan_lock); fsi_master_unscan(master); - return fsi_master_scan(master); + rc = fsi_master_scan(master); + mutex_unlock(&master->scan_lock); + + return rc; } EXPORT_SYMBOL_GPL(fsi_master_rescan); @@ -903,9 +1246,7 @@ int fsi_master_register(struct fsi_master *master) int rc; struct device_node *np; - if (!master) - return -EINVAL; - + mutex_init(&master->scan_lock); master->idx = ida_simple_get(&master_ida, 0, INT_MAX, GFP_KERNEL); dev_set_name(&master->dev, "fsi%d", master->idx); @@ -917,21 +1258,24 @@ int fsi_master_register(struct fsi_master *master) rc = device_create_file(&master->dev, &dev_attr_rescan); if (rc) { - device_unregister(&master->dev); + device_del(&master->dev); ida_simple_remove(&master_ida, master->idx); return rc; } rc = device_create_file(&master->dev, &dev_attr_break); if (rc) { - device_unregister(&master->dev); + device_del(&master->dev); ida_simple_remove(&master_ida, master->idx); return rc; } np = dev_of_node(&master->dev); - if (!of_property_read_bool(np, "no-scan-on-init")) + if (!of_property_read_bool(np, "no-scan-on-init")) { + mutex_lock(&master->scan_lock); fsi_master_scan(master); + mutex_unlock(&master->scan_lock); + } return 0; } @@ -944,7 +1288,9 @@ void fsi_master_unregister(struct fsi_master *master) master->idx = -1; } + mutex_lock(&master->scan_lock); fsi_master_unscan(master); + mutex_unlock(&master->scan_lock); device_unregister(&master->dev); } EXPORT_SYMBOL_GPL(fsi_master_unregister); @@ -996,13 +1342,27 @@ EXPORT_SYMBOL_GPL(fsi_bus_type); static int __init fsi_init(void) { - return bus_register(&fsi_bus_type); + int rc; + + rc = alloc_chrdev_region(&fsi_base_dev, 0, FSI_CHAR_MAX_DEVICES, "fsi"); + if (rc) + return rc; + rc = bus_register(&fsi_bus_type); + if (rc) + goto fail_bus; + return 0; + + fail_bus: + unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES); + return rc; } postcore_initcall(fsi_init); static void fsi_exit(void) { bus_unregister(&fsi_bus_type); + unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES); + ida_destroy(&fsi_minor_ida); } module_exit(fsi_exit); module_param(discard_errors, int, 0664);
diff --git a/drivers/fsi/fsi-master-ast-cf.c b/drivers/fsi/fsi-master-ast-cf.c new file mode 100644 index 0000000..04d10ea --- /dev/null +++ b/drivers/fsi/fsi-master-ast-cf.c
@@ -0,0 +1,1440 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corp +/* + * A FSI master controller, using a simple GPIO bit-banging interface + */ + +#include <linux/crc4.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fsi.h> +#include <linux/gpio/consumer.h> +#include <linux/io.h> +#include <linux/irqflags.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/firmware.h> +#include <linux/gpio/aspeed.h> +#include <linux/mfd/syscon.h> +#include <linux/of_address.h> +#include <linux/genalloc.h> + +#include "fsi-master.h" +#include "cf-fsi-fw.h" + +#define FW_FILE_NAME "cf-fsi-fw.bin" + +/* Common SCU based coprocessor control registers */ +#define SCU_COPRO_CTRL 0x100 +#define SCU_COPRO_RESET 0x00000002 +#define SCU_COPRO_CLK_EN 0x00000001 + +/* AST2500 specific ones */ +#define SCU_2500_COPRO_SEG0 0x104 +#define SCU_2500_COPRO_SEG1 0x108 +#define SCU_2500_COPRO_SEG2 0x10c +#define SCU_2500_COPRO_SEG3 0x110 +#define SCU_2500_COPRO_SEG4 0x114 +#define SCU_2500_COPRO_SEG5 0x118 +#define SCU_2500_COPRO_SEG6 0x11c +#define SCU_2500_COPRO_SEG7 0x120 +#define SCU_2500_COPRO_SEG8 0x124 +#define SCU_2500_COPRO_SEG_SWAP 0x00000001 +#define SCU_2500_COPRO_CACHE_CTL 0x128 +#define SCU_2500_COPRO_CACHE_EN 0x00000001 +#define SCU_2500_COPRO_SEG0_CACHE_EN 0x00000002 +#define SCU_2500_COPRO_SEG1_CACHE_EN 0x00000004 +#define SCU_2500_COPRO_SEG2_CACHE_EN 0x00000008 +#define SCU_2500_COPRO_SEG3_CACHE_EN 0x00000010 +#define SCU_2500_COPRO_SEG4_CACHE_EN 0x00000020 +#define SCU_2500_COPRO_SEG5_CACHE_EN 0x00000040 +#define SCU_2500_COPRO_SEG6_CACHE_EN 0x00000080 +#define SCU_2500_COPRO_SEG7_CACHE_EN 0x00000100 +#define SCU_2500_COPRO_SEG8_CACHE_EN 0x00000200 + +#define SCU_2400_COPRO_SEG0 0x104 +#define SCU_2400_COPRO_SEG2 0x108 +#define SCU_2400_COPRO_SEG4 0x10c +#define SCU_2400_COPRO_SEG6 0x110 +#define SCU_2400_COPRO_SEG8 0x114 +#define SCU_2400_COPRO_SEG_SWAP 0x80000000 +#define SCU_2400_COPRO_CACHE_CTL 0x118 +#define SCU_2400_COPRO_CACHE_EN 0x00000001 +#define SCU_2400_COPRO_SEG0_CACHE_EN 0x00000002 +#define SCU_2400_COPRO_SEG2_CACHE_EN 0x00000004 +#define SCU_2400_COPRO_SEG4_CACHE_EN 0x00000008 +#define SCU_2400_COPRO_SEG6_CACHE_EN 0x00000010 +#define SCU_2400_COPRO_SEG8_CACHE_EN 0x00000020 + +/* CVIC registers */ +#define CVIC_EN_REG 0x10 +#define CVIC_TRIG_REG 0x18 + +/* + * System register base address (needed for configuring the + * coldfire maps) + */ +#define SYSREG_BASE 0x1e600000 + +/* Amount of SRAM required */ +#define SRAM_SIZE 0x1000 + +#define LAST_ADDR_INVALID 0x1 + +struct fsi_master_acf { + struct fsi_master master; + struct device *dev; + struct regmap *scu; + struct mutex lock; /* mutex for command ordering */ + struct gpio_desc *gpio_clk; + struct gpio_desc *gpio_data; + struct gpio_desc *gpio_trans; /* Voltage translator */ + struct gpio_desc *gpio_enable; /* FSI enable */ + struct gpio_desc *gpio_mux; /* Mux control */ + uint16_t gpio_clk_vreg; + uint16_t gpio_clk_dreg; + uint16_t gpio_dat_vreg; + uint16_t gpio_dat_dreg; + uint16_t gpio_tra_vreg; + uint16_t gpio_tra_dreg; + uint8_t gpio_clk_bit; + uint8_t gpio_dat_bit; + uint8_t gpio_tra_bit; + uint32_t cf_mem_addr; + size_t cf_mem_size; + void __iomem *cf_mem; + void __iomem *cvic; + struct gen_pool *sram_pool; + void __iomem *sram; + bool is_ast2500; + bool external_mode; + bool trace_enabled; + uint32_t last_addr; + uint8_t t_send_delay; + uint8_t t_echo_delay; + uint32_t cvic_sw_irq; +}; +#define to_fsi_master_acf(m) container_of(m, struct fsi_master_acf, master) + +struct fsi_msg { + uint64_t msg; + uint8_t bits; +}; + +#define CREATE_TRACE_POINTS +#include <trace/events/fsi_master_ast_cf.h> + +static void msg_push_bits(struct fsi_msg *msg, uint64_t data, int bits) +{ + msg->msg <<= bits; + msg->msg |= data & ((1ull << bits) - 1); + msg->bits += bits; +} + +static void msg_push_crc(struct fsi_msg *msg) +{ + uint8_t crc; + int top; + + top = msg->bits & 0x3; + + /* start bit, and any non-aligned top bits */ + crc = crc4(0, 1 << top | msg->msg >> (msg->bits - top), top + 1); + + /* aligned bits */ + crc = crc4(crc, msg->msg, msg->bits - top); + + msg_push_bits(msg, crc, 4); +} + +static void msg_finish_cmd(struct fsi_msg *cmd) +{ + /* Left align message */ + cmd->msg <<= (64 - cmd->bits); +} + +static bool check_same_address(struct fsi_master_acf *master, int id, + uint32_t addr) +{ + /* this will also handle LAST_ADDR_INVALID */ + return master->last_addr == (((id & 0x3) << 21) | (addr & ~0x3)); +} + +static bool check_relative_address(struct fsi_master_acf *master, int id, + uint32_t addr, uint32_t *rel_addrp) +{ + uint32_t last_addr = master->last_addr; + int32_t rel_addr; + + if (last_addr == LAST_ADDR_INVALID) + return false; + + /* We may be in 23-bit addressing mode, which uses the id as the + * top two address bits. So, if we're referencing a different ID, + * use absolute addresses. + */ + if (((last_addr >> 21) & 0x3) != id) + return false; + + /* remove the top two bits from any 23-bit addressing */ + last_addr &= (1 << 21) - 1; + + /* We know that the addresses are limited to 21 bits, so this won't + * overflow the signed rel_addr */ + rel_addr = addr - last_addr; + if (rel_addr > 255 || rel_addr < -256) + return false; + + *rel_addrp = (uint32_t)rel_addr; + + return true; +} + +static void last_address_update(struct fsi_master_acf *master, + int id, bool valid, uint32_t addr) +{ + if (!valid) + master->last_addr = LAST_ADDR_INVALID; + else + master->last_addr = ((id & 0x3) << 21) | (addr & ~0x3); +} + +/* + * Encode an Absolute/Relative/Same Address command + */ +static void build_ar_command(struct fsi_master_acf *master, + struct fsi_msg *cmd, uint8_t id, + uint32_t addr, size_t size, + const void *data) +{ + int i, addr_bits, opcode_bits; + bool write = !!data; + uint8_t ds, opcode; + uint32_t rel_addr; + + cmd->bits = 0; + cmd->msg = 0; + + /* we have 21 bits of address max */ + addr &= ((1 << 21) - 1); + + /* cmd opcodes are variable length - SAME_AR is only two bits */ + opcode_bits = 3; + + if (check_same_address(master, id, addr)) { + /* we still address the byte offset within the word */ + addr_bits = 2; + opcode_bits = 2; + opcode = FSI_CMD_SAME_AR; + trace_fsi_master_acf_cmd_same_addr(master); + + } else if (check_relative_address(master, id, addr, &rel_addr)) { + /* 8 bits plus sign */ + addr_bits = 9; + addr = rel_addr; + opcode = FSI_CMD_REL_AR; + trace_fsi_master_acf_cmd_rel_addr(master, rel_addr); + + } else { + addr_bits = 21; + opcode = FSI_CMD_ABS_AR; + trace_fsi_master_acf_cmd_abs_addr(master, addr); + } + + /* + * The read/write size is encoded in the lower bits of the address + * (as it must be naturally-aligned), and the following ds bit. + * + * size addr:1 addr:0 ds + * 1 x x 0 + * 2 x 0 1 + * 4 0 1 1 + * + */ + ds = size > 1 ? 1 : 0; + addr &= ~(size - 1); + if (size == 4) + addr |= 1; + + msg_push_bits(cmd, id, 2); + msg_push_bits(cmd, opcode, opcode_bits); + msg_push_bits(cmd, write ? 0 : 1, 1); + msg_push_bits(cmd, addr, addr_bits); + msg_push_bits(cmd, ds, 1); + for (i = 0; write && i < size; i++) + msg_push_bits(cmd, ((uint8_t *)data)[i], 8); + + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_dpoll_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_DPOLL, 3); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_epoll_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_EPOLL, 3); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_term_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_TERM, 6); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static int do_copro_command(struct fsi_master_acf *master, uint32_t op) +{ + uint32_t timeout = 10000000; + uint8_t stat; + + trace_fsi_master_acf_copro_command(master, op); + + /* Send command */ + iowrite32be(op, master->sram + CMD_STAT_REG); + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + /* Wait for status to indicate completion (or error) */ + do { + if (timeout-- == 0) { + dev_warn(master->dev, + "Timeout waiting for coprocessor completion\n"); + return -ETIMEDOUT; + } + stat = ioread8(master->sram + CMD_STAT_REG); + } while(stat < STAT_COMPLETE || stat == 0xff); + + if (stat == STAT_COMPLETE) + return 0; + switch(stat) { + case STAT_ERR_INVAL_CMD: + return -EINVAL; + case STAT_ERR_INVAL_IRQ: + return -EIO; + case STAT_ERR_MTOE: + return -ESHUTDOWN; + } + return -ENXIO; +} + +static int clock_zeros(struct fsi_master_acf *master, int count) +{ + while (count) { + int rc, lcnt = min(count, 255); + + rc = do_copro_command(master, + CMD_IDLE_CLOCKS | (lcnt << CMD_REG_CLEN_SHIFT)); + if (rc) + return rc; + count -= lcnt; + } + return 0; +} + +static int send_request(struct fsi_master_acf *master, struct fsi_msg *cmd, + unsigned int resp_bits) +{ + uint32_t op; + + trace_fsi_master_acf_send_request(master, cmd, resp_bits); + + /* Store message into SRAM */ + iowrite32be((cmd->msg >> 32), master->sram + CMD_DATA); + iowrite32be((cmd->msg & 0xffffffff), master->sram + CMD_DATA + 4); + + op = CMD_COMMAND; + op |= cmd->bits << CMD_REG_CLEN_SHIFT; + if (resp_bits) + op |= resp_bits << CMD_REG_RLEN_SHIFT; + + return do_copro_command(master, op); +} + +static int read_copro_response(struct fsi_master_acf *master, uint8_t size, + uint32_t *response, u8 *tag) +{ + uint8_t rtag = ioread8(master->sram + STAT_RTAG) & 0xf; + uint8_t rcrc = ioread8(master->sram + STAT_RCRC) & 0xf; + uint32_t rdata = 0; + uint32_t crc; + uint8_t ack; + + *tag = ack = rtag & 3; + + /* we have a whole message now; check CRC */ + crc = crc4(0, 1, 1); + crc = crc4(crc, rtag, 4); + if (ack == FSI_RESP_ACK && size) { + rdata = ioread32be(master->sram + RSP_DATA); + crc = crc4(crc, rdata, size); + if (response) + *response = rdata; + } + crc = crc4(crc, rcrc, 4); + + trace_fsi_master_acf_copro_response(master, rtag, rcrc, rdata, crc == 0); + + if (crc) { + /* + * Check if it's all 1's or all 0's, that probably means + * the host is off + */ + if ((rtag == 0xf && rcrc == 0xf) || (rtag == 0 && rcrc == 0)) + return -ENODEV; + dev_dbg(master->dev, "Bad response CRC !\n"); + return -EAGAIN; + } + return 0; +} + +static int send_term(struct fsi_master_acf *master, uint8_t slave) +{ + struct fsi_msg cmd; + uint8_t tag; + int rc; + + build_term_command(&cmd, slave); + + rc = send_request(master, &cmd, 0); + if (rc) { + dev_warn(master->dev, "Error %d sending term\n", rc); + return rc; + } + + rc = read_copro_response(master, 0, NULL, &tag); + if (rc < 0) { + dev_err(master->dev, + "TERM failed; lost communication with slave\n"); + return -EIO; + } else if (tag != FSI_RESP_ACK) { + dev_err(master->dev, "TERM failed; response %d\n", tag); + return -EIO; + } + return 0; +} + +static void dump_ucode_trace(struct fsi_master_acf *master) +{ + char trbuf[52]; + char *p; + int i; + + dev_dbg(master->dev, + "CMDSTAT:%08x RTAG=%02x RCRC=%02x RDATA=%02x #INT=%08x\n", + ioread32be(master->sram + CMD_STAT_REG), + ioread8(master->sram + STAT_RTAG), + ioread8(master->sram + STAT_RCRC), + ioread32be(master->sram + RSP_DATA), + ioread32be(master->sram + INT_CNT)); + + for (i = 0; i < 512; i++) { + uint8_t v; + if ((i % 16) == 0) + p = trbuf; + v = ioread8(master->sram + TRACEBUF + i); + p += sprintf(p, "%02x ", v); + if (((i % 16) == 15) || v == TR_END) + dev_dbg(master->dev, "%s\n", trbuf); + if (v == TR_END) + break; + } +} + +static int handle_response(struct fsi_master_acf *master, + uint8_t slave, uint8_t size, void *data) +{ + int busy_count = 0, rc; + int crc_err_retries = 0; + struct fsi_msg cmd; + uint32_t response; + uint8_t tag; +retry: + rc = read_copro_response(master, size, &response, &tag); + + /* Handle retries on CRC errors */ + if (rc == -EAGAIN) { + /* Too many retries ? */ + if (crc_err_retries++ > FSI_CRC_ERR_RETRIES) { + /* + * Pass it up as a -EIO otherwise upper level will retry + * the whole command which isn't what we want here. + */ + rc = -EIO; + goto bail; + } + trace_fsi_master_acf_crc_rsp_error(master, crc_err_retries); + if (master->trace_enabled) + dump_ucode_trace(master); + rc = clock_zeros(master, FSI_MASTER_EPOLL_CLOCKS); + if (rc) { + dev_warn(master->dev, + "Error %d clocking zeros for E_POLL\n", rc); + return rc; + } + build_epoll_command(&cmd, slave); + rc = send_request(master, &cmd, size); + if (rc) { + dev_warn(master->dev, "Error %d sending E_POLL\n", rc); + return -EIO; + } + goto retry; + } + if (rc) + return rc; + + switch (tag) { + case FSI_RESP_ACK: + if (size && data) { + if (size == 32) + *(__be32 *)data = cpu_to_be32(response); + else if (size == 16) + *(__be16 *)data = cpu_to_be16(response); + else + *(u8 *)data = response; + } + break; + case FSI_RESP_BUSY: + /* + * Its necessary to clock slave before issuing + * d-poll, not indicated in the hardware protocol + * spec. < 20 clocks causes slave to hang, 21 ok. + */ + dev_dbg(master->dev, "Busy, retrying...\n"); + if (master->trace_enabled) + dump_ucode_trace(master); + rc = clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); + if (rc) { + dev_warn(master->dev, + "Error %d clocking zeros for D_POLL\n", rc); + break; + } + if (busy_count++ < FSI_MASTER_MAX_BUSY) { + build_dpoll_command(&cmd, slave); + rc = send_request(master, &cmd, size); + if (rc) { + dev_warn(master->dev, "Error %d sending D_POLL\n", rc); + break; + } + goto retry; + } + dev_dbg(master->dev, + "ERR slave is stuck in busy state, issuing TERM\n"); + send_term(master, slave); + rc = -EIO; + break; + + case FSI_RESP_ERRA: + dev_dbg(master->dev, "ERRA received\n"); + if (master->trace_enabled) + dump_ucode_trace(master); + rc = -EIO; + break; + case FSI_RESP_ERRC: + dev_dbg(master->dev, "ERRC received\n"); + if (master->trace_enabled) + dump_ucode_trace(master); + rc = -EAGAIN; + break; + } + bail: + if (busy_count > 0) { + trace_fsi_master_acf_poll_response_busy(master, busy_count); + } + + return rc; +} + +static int fsi_master_acf_xfer(struct fsi_master_acf *master, uint8_t slave, + struct fsi_msg *cmd, size_t resp_len, void *resp) +{ + int rc = -EAGAIN, retries = 0; + + resp_len <<= 3; + while ((retries++) < FSI_CRC_ERR_RETRIES) { + rc = send_request(master, cmd, resp_len); + if (rc) { + if (rc != -ESHUTDOWN) + dev_warn(master->dev, "Error %d sending command\n", rc); + break; + } + rc = handle_response(master, slave, resp_len, resp); + if (rc != -EAGAIN) + break; + rc = -EIO; + dev_dbg(master->dev, "ECRC retry %d\n", retries); + + /* Pace it a bit before retry */ + msleep(1); + } + + return rc; +} + +static int fsi_master_acf_read(struct fsi_master *_master, int link, + uint8_t id, uint32_t addr, void *val, + size_t size) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + dev_dbg(master->dev, "read id %d addr %x size %zd\n", id, addr, size); + build_ar_command(master, &cmd, id, addr, size, NULL); + rc = fsi_master_acf_xfer(master, id, &cmd, size, val); + last_address_update(master, id, rc == 0, addr); + if (rc) + dev_dbg(master->dev, "read id %d addr 0x%08x err: %d\n", + id, addr, rc); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_write(struct fsi_master *_master, int link, + uint8_t id, uint32_t addr, const void *val, + size_t size) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + build_ar_command(master, &cmd, id, addr, size, val); + dev_dbg(master->dev, "write id %d addr %x size %zd raw_data: %08x\n", + id, addr, size, *(uint32_t *)val); + rc = fsi_master_acf_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, rc == 0, addr); + if (rc) + dev_dbg(master->dev, "write id %d addr 0x%08x err: %d\n", + id, addr, rc); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_term(struct fsi_master *_master, + int link, uint8_t id) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + build_term_command(&cmd, id); + dev_dbg(master->dev, "term id %d\n", id); + rc = fsi_master_acf_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, false, 0); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_break(struct fsi_master *_master, int link) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + if (master->external_mode) { + mutex_unlock(&master->lock); + return -EBUSY; + } + dev_dbg(master->dev, "sending BREAK\n"); + rc = do_copro_command(master, CMD_BREAK); + last_address_update(master, 0, false, 0); + mutex_unlock(&master->lock); + + /* Wait for logic reset to take effect */ + udelay(200); + + return rc; +} + +static void reset_cf(struct fsi_master_acf *master) +{ + regmap_write(master->scu, SCU_COPRO_CTRL, SCU_COPRO_RESET); + usleep_range(20,20); + regmap_write(master->scu, SCU_COPRO_CTRL, 0); + usleep_range(20,20); +} + +static void start_cf(struct fsi_master_acf *master) +{ + regmap_write(master->scu, SCU_COPRO_CTRL, SCU_COPRO_CLK_EN); +} + +static void setup_ast2500_cf_maps(struct fsi_master_acf *master) +{ + /* + * Note about byteswap setting: the bus is wired backwards, + * so setting the byteswap bit actually makes the ColdFire + * work "normally" for a BE processor, ie, put the MSB in + * the lowest address byte. + * + * We thus need to set the bit for our main memory which + * contains our program code. We create two mappings for + * the register, one with each setting. + * + * Segments 2 and 3 has a "swapped" mapping (BE) + * and 6 and 7 have a non-swapped mapping (LE) which allows + * us to avoid byteswapping register accesses since the + * registers are all LE. + */ + + /* Setup segment 0 to our memory region */ + regmap_write(master->scu, SCU_2500_COPRO_SEG0, master->cf_mem_addr | + SCU_2500_COPRO_SEG_SWAP); + + /* Segments 2 and 3 to sysregs with byteswap (for SRAM) */ + regmap_write(master->scu, SCU_2500_COPRO_SEG2, SYSREG_BASE | + SCU_2500_COPRO_SEG_SWAP); + regmap_write(master->scu, SCU_2500_COPRO_SEG3, SYSREG_BASE | 0x100000 | + SCU_2500_COPRO_SEG_SWAP); + + /* And segment 6 and 7 to sysregs no byteswap */ + regmap_write(master->scu, SCU_2500_COPRO_SEG6, SYSREG_BASE); + regmap_write(master->scu, SCU_2500_COPRO_SEG7, SYSREG_BASE | 0x100000); + + /* Memory cachable, regs and SRAM not cachable */ + regmap_write(master->scu, SCU_2500_COPRO_CACHE_CTL, + SCU_2500_COPRO_SEG0_CACHE_EN | SCU_2500_COPRO_CACHE_EN); +} + +static void setup_ast2400_cf_maps(struct fsi_master_acf *master) +{ + /* Setup segment 0 to our memory region */ + regmap_write(master->scu, SCU_2400_COPRO_SEG0, master->cf_mem_addr | + SCU_2400_COPRO_SEG_SWAP); + + /* Segments 2 to sysregs with byteswap (for SRAM) */ + regmap_write(master->scu, SCU_2400_COPRO_SEG2, SYSREG_BASE | + SCU_2400_COPRO_SEG_SWAP); + + /* And segment 6 to sysregs no byteswap */ + regmap_write(master->scu, SCU_2400_COPRO_SEG6, SYSREG_BASE); + + /* Memory cachable, regs and SRAM not cachable */ + regmap_write(master->scu, SCU_2400_COPRO_CACHE_CTL, + SCU_2400_COPRO_SEG0_CACHE_EN | SCU_2400_COPRO_CACHE_EN); +} + +static void setup_common_fw_config(struct fsi_master_acf *master, + void __iomem *base) +{ + iowrite16be(master->gpio_clk_vreg, base + HDR_CLOCK_GPIO_VADDR); + iowrite16be(master->gpio_clk_dreg, base + HDR_CLOCK_GPIO_DADDR); + iowrite16be(master->gpio_dat_vreg, base + HDR_DATA_GPIO_VADDR); + iowrite16be(master->gpio_dat_dreg, base + HDR_DATA_GPIO_DADDR); + iowrite16be(master->gpio_tra_vreg, base + HDR_TRANS_GPIO_VADDR); + iowrite16be(master->gpio_tra_dreg, base + HDR_TRANS_GPIO_DADDR); + iowrite8(master->gpio_clk_bit, base + HDR_CLOCK_GPIO_BIT); + iowrite8(master->gpio_dat_bit, base + HDR_DATA_GPIO_BIT); + iowrite8(master->gpio_tra_bit, base + HDR_TRANS_GPIO_BIT); +} + +static void setup_ast2500_fw_config(struct fsi_master_acf *master) +{ + void __iomem *base = master->cf_mem + HDR_OFFSET; + + setup_common_fw_config(master, base); + iowrite32be(FW_CONTROL_USE_STOP, base + HDR_FW_CONTROL); +} + +static void setup_ast2400_fw_config(struct fsi_master_acf *master) +{ + void __iomem *base = master->cf_mem + HDR_OFFSET; + + setup_common_fw_config(master, base); + iowrite32be(FW_CONTROL_CONT_CLOCK|FW_CONTROL_DUMMY_RD, base + HDR_FW_CONTROL); +} + +static int setup_gpios_for_copro(struct fsi_master_acf *master) +{ + + int rc; + + /* This aren't under ColdFire control, just set them up appropriately */ + gpiod_direction_output(master->gpio_mux, 1); + gpiod_direction_output(master->gpio_enable, 1); + + /* Those are under ColdFire control, let it configure them */ + rc = aspeed_gpio_copro_grab_gpio(master->gpio_clk, &master->gpio_clk_vreg, + &master->gpio_clk_dreg, &master->gpio_clk_bit); + if (rc) { + dev_err(master->dev, "failed to assign clock gpio to coprocessor\n"); + return rc; + } + rc = aspeed_gpio_copro_grab_gpio(master->gpio_data, &master->gpio_dat_vreg, + &master->gpio_dat_dreg, &master->gpio_dat_bit); + if (rc) { + dev_err(master->dev, "failed to assign data gpio to coprocessor\n"); + aspeed_gpio_copro_release_gpio(master->gpio_clk); + return rc; + } + rc = aspeed_gpio_copro_grab_gpio(master->gpio_trans, &master->gpio_tra_vreg, + &master->gpio_tra_dreg, &master->gpio_tra_bit); + if (rc) { + dev_err(master->dev, "failed to assign trans gpio to coprocessor\n"); + aspeed_gpio_copro_release_gpio(master->gpio_clk); + aspeed_gpio_copro_release_gpio(master->gpio_data); + return rc; + } + return 0; +} + +static void release_copro_gpios(struct fsi_master_acf *master) +{ + aspeed_gpio_copro_release_gpio(master->gpio_clk); + aspeed_gpio_copro_release_gpio(master->gpio_data); + aspeed_gpio_copro_release_gpio(master->gpio_trans); +} + +static int load_copro_firmware(struct fsi_master_acf *master) +{ + const struct firmware *fw; + uint16_t sig = 0, wanted_sig; + const u8 *data; + size_t size = 0; + int rc; + + /* Get the binary */ + rc = request_firmware(&fw, FW_FILE_NAME, master->dev); + if (rc) { + dev_err( + master->dev, "Error %d to load firwmare '%s' !\n", + rc, FW_FILE_NAME); + return rc; + } + + /* Which image do we want ? (shared vs. split clock/data GPIOs) */ + if (master->gpio_clk_vreg == master->gpio_dat_vreg) + wanted_sig = SYS_SIG_SHARED; + else + wanted_sig = SYS_SIG_SPLIT; + dev_dbg(master->dev, "Looking for image sig %04x\n", wanted_sig); + + /* Try to find it */ + for (data = fw->data; data < (fw->data + fw->size);) { + sig = be16_to_cpup((__be16 *)(data + HDR_OFFSET + HDR_SYS_SIG)); + size = be32_to_cpup((__be32 *)(data + HDR_OFFSET + HDR_FW_SIZE)); + if (sig == wanted_sig) + break; + data += size; + } + if (sig != wanted_sig) { + dev_err(master->dev, "Failed to locate image sig %04x in FW blob\n", + wanted_sig); + rc = -ENODEV; + goto release_fw; + } + if (size > master->cf_mem_size) { + dev_err(master->dev, "FW size (%zd) bigger than memory reserve (%zd)\n", + fw->size, master->cf_mem_size); + rc = -ENOMEM; + } else { + memcpy_toio(master->cf_mem, data, size); + } + +release_fw: + release_firmware(fw); + return rc; +} + +static int check_firmware_image(struct fsi_master_acf *master) +{ + uint32_t fw_vers, fw_api, fw_options; + + fw_vers = ioread16be(master->cf_mem + HDR_OFFSET + HDR_FW_VERS); + fw_api = ioread16be(master->cf_mem + HDR_OFFSET + HDR_API_VERS); + fw_options = ioread32be(master->cf_mem + HDR_OFFSET + HDR_FW_OPTIONS); + master->trace_enabled = !!(fw_options & FW_OPTION_TRACE_EN); + + /* Check version and signature */ + dev_info(master->dev, "ColdFire initialized, firmware v%d API v%d.%d (trace %s)\n", + fw_vers, fw_api >> 8, fw_api & 0xff, + master->trace_enabled ? "enabled" : "disabled"); + + if ((fw_api >> 8) != API_VERSION_MAJ) { + dev_err(master->dev, "Unsupported coprocessor API version !\n"); + return -ENODEV; + } + + return 0; +} + +static int copro_enable_sw_irq(struct fsi_master_acf *master) +{ + int timeout; + uint32_t val; + + /* + * Enable coprocessor interrupt input. I've had problems getting the + * value to stick, so try in a loop + */ + for (timeout = 0; timeout < 10; timeout++) { + iowrite32(0x2, master->cvic + CVIC_EN_REG); + val = ioread32(master->cvic + CVIC_EN_REG); + if (val & 2) + break; + msleep(1); + } + if (!(val & 2)) { + dev_err(master->dev, "Failed to enable coprocessor interrupt !\n"); + return -ENODEV; + } + return 0; +} + +static int fsi_master_acf_setup(struct fsi_master_acf *master) +{ + int timeout, rc; + uint32_t val; + + /* Make sure the ColdFire is stopped */ + reset_cf(master); + + /* + * Clear SRAM. This needs to happen before we setup the GPIOs + * as we might start trying to arbitrate as soon as that happens. + */ + memset_io(master->sram, 0, SRAM_SIZE); + + /* Configure GPIOs */ + rc = setup_gpios_for_copro(master); + if (rc) + return rc; + + /* Load the firmware into the reserved memory */ + rc = load_copro_firmware(master); + if (rc) + return rc; + + /* Read signature and check versions */ + rc = check_firmware_image(master); + if (rc) + return rc; + + /* Setup coldfire memory map */ + if (master->is_ast2500) { + setup_ast2500_cf_maps(master); + setup_ast2500_fw_config(master); + } else { + setup_ast2400_cf_maps(master); + setup_ast2400_fw_config(master); + } + + /* Start the ColdFire */ + start_cf(master); + + /* Wait for status register to indicate command completion + * which signals the initialization is complete + */ + for (timeout = 0; timeout < 10; timeout++) { + val = ioread8(master->sram + CF_STARTED); + if (val) + break; + msleep(1); + } + if (!val) { + dev_err(master->dev, "Coprocessor startup timeout !\n"); + rc = -ENODEV; + goto err; + } + + /* Configure echo & send delay */ + iowrite8(master->t_send_delay, master->sram + SEND_DLY_REG); + iowrite8(master->t_echo_delay, master->sram + ECHO_DLY_REG); + + /* Enable SW interrupt to copro if any */ + if (master->cvic) { + rc = copro_enable_sw_irq(master); + if (rc) + goto err; + } + return 0; + err: + /* An error occurred, don't leave the coprocessor running */ + reset_cf(master); + + /* Release the GPIOs */ + release_copro_gpios(master); + + return rc; +} + + +static void fsi_master_acf_terminate(struct fsi_master_acf *master) +{ + unsigned long flags; + + /* + * A GPIO arbitration requestion could come in while this is + * happening. To avoid problems, we disable interrupts so it + * cannot preempt us on this CPU + */ + + local_irq_save(flags); + + /* Stop the coprocessor */ + reset_cf(master); + + /* We mark the copro not-started */ + iowrite32(0, master->sram + CF_STARTED); + + /* We mark the ARB register as having given up arbitration to + * deal with a potential race with the arbitration request + */ + iowrite8(ARB_ARM_ACK, master->sram + ARB_REG); + + local_irq_restore(flags); + + /* Return the GPIOs to the ARM */ + release_copro_gpios(master); +} + +static void fsi_master_acf_setup_external(struct fsi_master_acf *master) +{ + /* Setup GPIOs for external FSI master (FSP box) */ + gpiod_direction_output(master->gpio_mux, 0); + gpiod_direction_output(master->gpio_trans, 0); + gpiod_direction_output(master->gpio_enable, 1); + gpiod_direction_input(master->gpio_clk); + gpiod_direction_input(master->gpio_data); +} + +static int fsi_master_acf_link_enable(struct fsi_master *_master, int link) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + int rc = -EBUSY; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + if (!master->external_mode) { + gpiod_set_value(master->gpio_enable, 1); + rc = 0; + } + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_link_config(struct fsi_master *_master, int link, + u8 t_send_delay, u8 t_echo_delay) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + master->t_send_delay = t_send_delay; + master->t_echo_delay = t_echo_delay; + dev_dbg(master->dev, "Changing delays: send=%d echo=%d\n", + t_send_delay, t_echo_delay); + iowrite8(master->t_send_delay, master->sram + SEND_DLY_REG); + iowrite8(master->t_echo_delay, master->sram + ECHO_DLY_REG); + mutex_unlock(&master->lock); + + return 0; +} + +static ssize_t external_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsi_master_acf *master = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", + master->external_mode ? 1 : 0); +} + +static ssize_t external_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fsi_master_acf *master = dev_get_drvdata(dev); + unsigned long val; + bool external_mode; + int err; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + external_mode = !!val; + + mutex_lock(&master->lock); + + if (external_mode == master->external_mode) { + mutex_unlock(&master->lock); + return count; + } + + master->external_mode = external_mode; + if (master->external_mode) { + fsi_master_acf_terminate(master); + fsi_master_acf_setup_external(master); + } else + fsi_master_acf_setup(master); + + mutex_unlock(&master->lock); + + fsi_master_rescan(&master->master); + + return count; +} + +static DEVICE_ATTR(external_mode, 0664, + external_mode_show, external_mode_store); + +static int fsi_master_acf_gpio_request(void *data) +{ + struct fsi_master_acf *master = data; + int timeout; + u8 val; + + /* Note: This doesn't require holding out mutex */ + + /* Write reqest */ + iowrite8(ARB_ARM_REQ, master->sram + ARB_REG); + + /* + * There is a race (which does happen at boot time) when we get an + * arbitration request as we are either about to or just starting + * the coprocessor. + * + * To handle it, we first check if we are running. If not yet we + * check whether the copro is started in the SCU. + * + * If it's not started, we can basically just assume we have arbitration + * and return. Otherwise, we wait normally expecting for the arbitration + * to eventually complete. + */ + if (ioread32(master->sram + CF_STARTED) == 0) { + unsigned int reg = 0; + + regmap_read(master->scu, SCU_COPRO_CTRL, ®); + if (!(reg & SCU_COPRO_CLK_EN)) + return 0; + } + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + for (timeout = 0; timeout < 10000; timeout++) { + val = ioread8(master->sram + ARB_REG); + if (val != ARB_ARM_REQ) + break; + udelay(1); + } + + /* If it failed, override anyway */ + if (val != ARB_ARM_ACK) + dev_warn(master->dev, "GPIO request arbitration timeout\n"); + + return 0; +} + +static int fsi_master_acf_gpio_release(void *data) +{ + struct fsi_master_acf *master = data; + + /* Write release */ + iowrite8(0, master->sram + ARB_REG); + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + return 0; +} + +static void fsi_master_acf_release(struct device *dev) +{ + struct fsi_master_acf *master = to_fsi_master_acf(dev_to_fsi_master(dev)); + + /* Cleanup, stop coprocessor */ + mutex_lock(&master->lock); + fsi_master_acf_terminate(master); + aspeed_gpio_copro_set_ops(NULL, NULL); + mutex_unlock(&master->lock); + + /* Free resources */ + gen_pool_free(master->sram_pool, (unsigned long)master->sram, SRAM_SIZE); + of_node_put(dev_of_node(master->dev)); + + kfree(master); +} + +static const struct aspeed_gpio_copro_ops fsi_master_acf_gpio_ops = { + .request_access = fsi_master_acf_gpio_request, + .release_access = fsi_master_acf_gpio_release, +}; + +static int fsi_master_acf_probe(struct platform_device *pdev) +{ + struct device_node *np, *mnode = dev_of_node(&pdev->dev); + struct genpool_data_fixed gpdf; + struct fsi_master_acf *master; + struct gpio_desc *gpio; + struct resource res; + uint32_t cf_mem_align; + int rc; + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; + + master->dev = &pdev->dev; + master->master.dev.parent = master->dev; + master->last_addr = LAST_ADDR_INVALID; + + /* AST2400 vs. AST2500 */ + master->is_ast2500 = of_device_is_compatible(mnode, "aspeed,ast2500-cf-fsi-master"); + + /* Grab the SCU, we'll need to access it to configure the coprocessor */ + if (master->is_ast2500) + master->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2500-scu"); + else + master->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2400-scu"); + if (IS_ERR(master->scu)) { + dev_err(&pdev->dev, "failed to find SCU regmap\n"); + rc = PTR_ERR(master->scu); + goto err_free; + } + + /* Grab all the GPIOs we need */ + gpio = devm_gpiod_get(&pdev->dev, "clock", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get clock gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_clk = gpio; + + gpio = devm_gpiod_get(&pdev->dev, "data", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get data gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_data = gpio; + + /* Optional GPIOs */ + gpio = devm_gpiod_get_optional(&pdev->dev, "trans", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get trans gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_trans = gpio; + + gpio = devm_gpiod_get_optional(&pdev->dev, "enable", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get enable gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_enable = gpio; + + gpio = devm_gpiod_get_optional(&pdev->dev, "mux", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get mux gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_mux = gpio; + + /* Grab the reserved memory region (use DMA API instead ?) */ + np = of_parse_phandle(mnode, "memory-region", 0); + if (!np) { + dev_err(&pdev->dev, "Didn't find reserved memory\n"); + rc = -EINVAL; + goto err_free; + } + rc = of_address_to_resource(np, 0, &res); + of_node_put(np); + if (rc) { + dev_err(&pdev->dev, "Couldn't address to resource for reserved memory\n"); + rc = -ENOMEM; + goto err_free; + } + master->cf_mem_size = resource_size(&res); + master->cf_mem_addr = (uint32_t)res.start; + cf_mem_align = master->is_ast2500 ? 0x00100000 : 0x00200000; + if (master->cf_mem_addr & (cf_mem_align - 1)) { + dev_err(&pdev->dev, "Reserved memory has insufficient alignment\n"); + rc = -ENOMEM; + goto err_free; + } + master->cf_mem = devm_ioremap_resource(&pdev->dev, &res); + if (IS_ERR(master->cf_mem)) { + rc = PTR_ERR(master->cf_mem); + dev_err(&pdev->dev, "Error %d mapping coldfire memory\n", rc); + goto err_free; + } + dev_dbg(&pdev->dev, "DRAM allocation @%x\n", master->cf_mem_addr); + + /* AST2500 has a SW interrupt to the coprocessor */ + if (master->is_ast2500) { + /* Grab the CVIC (ColdFire interrupts controller) */ + np = of_parse_phandle(mnode, "aspeed,cvic", 0); + if (!np) { + dev_err(&pdev->dev, "Didn't find CVIC\n"); + rc = -EINVAL; + goto err_free; + } + master->cvic = devm_of_iomap(&pdev->dev, np, 0, NULL); + if (IS_ERR(master->cvic)) { + rc = PTR_ERR(master->cvic); + dev_err(&pdev->dev, "Error %d mapping CVIC\n", rc); + goto err_free; + } + rc = of_property_read_u32(np, "copro-sw-interrupts", + &master->cvic_sw_irq); + if (rc) { + dev_err(&pdev->dev, "Can't find coprocessor SW interrupt\n"); + goto err_free; + } + } + + /* Grab the SRAM */ + master->sram_pool = of_gen_pool_get(dev_of_node(&pdev->dev), "aspeed,sram", 0); + if (!master->sram_pool) { + rc = -ENODEV; + dev_err(&pdev->dev, "Can't find sram pool\n"); + goto err_free; + } + + /* Current microcode only deals with fixed location in SRAM */ + gpdf.offset = 0; + master->sram = (void __iomem *)gen_pool_alloc_algo(master->sram_pool, SRAM_SIZE, + gen_pool_fixed_alloc, &gpdf); + if (!master->sram) { + rc = -ENOMEM; + dev_err(&pdev->dev, "Failed to allocate sram from pool\n"); + goto err_free; + } + dev_dbg(&pdev->dev, "SRAM allocation @%lx\n", + (unsigned long)gen_pool_virt_to_phys(master->sram_pool, + (unsigned long)master->sram)); + + /* + * Hookup with the GPIO driver for arbitration of GPIO banks + * ownership. + */ + aspeed_gpio_copro_set_ops(&fsi_master_acf_gpio_ops, master); + + /* Default FSI command delays */ + master->t_send_delay = FSI_SEND_DELAY_CLOCKS; + master->t_echo_delay = FSI_ECHO_DELAY_CLOCKS; + master->master.n_links = 1; + if (master->is_ast2500) + master->master.flags = FSI_MASTER_FLAG_SWCLOCK; + master->master.read = fsi_master_acf_read; + master->master.write = fsi_master_acf_write; + master->master.term = fsi_master_acf_term; + master->master.send_break = fsi_master_acf_break; + master->master.link_enable = fsi_master_acf_link_enable; + master->master.link_config = fsi_master_acf_link_config; + master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); + master->master.dev.release = fsi_master_acf_release; + platform_set_drvdata(pdev, master); + mutex_init(&master->lock); + + mutex_lock(&master->lock); + rc = fsi_master_acf_setup(master); + mutex_unlock(&master->lock); + if (rc) + goto release_of_dev; + + rc = device_create_file(&pdev->dev, &dev_attr_external_mode); + if (rc) + goto stop_copro; + + rc = fsi_master_register(&master->master); + if (!rc) + return 0; + + device_remove_file(master->dev, &dev_attr_external_mode); + put_device(&master->master.dev); + return rc; + + stop_copro: + fsi_master_acf_terminate(master); + release_of_dev: + aspeed_gpio_copro_set_ops(NULL, NULL); + gen_pool_free(master->sram_pool, (unsigned long)master->sram, SRAM_SIZE); + of_node_put(dev_of_node(master->dev)); + err_free: + kfree(master); + return rc; +} + + +static int fsi_master_acf_remove(struct platform_device *pdev) +{ + struct fsi_master_acf *master = platform_get_drvdata(pdev); + + device_remove_file(master->dev, &dev_attr_external_mode); + + fsi_master_unregister(&master->master); + + return 0; +} + +static const struct of_device_id fsi_master_acf_match[] = { + { .compatible = "aspeed,ast2400-cf-fsi-master" }, + { .compatible = "aspeed,ast2500-cf-fsi-master" }, + { }, +}; + +static struct platform_driver fsi_master_acf = { + .driver = { + .name = "fsi-master-acf", + .of_match_table = fsi_master_acf_match, + }, + .probe = fsi_master_acf_probe, + .remove = fsi_master_acf_remove, +}; + +module_platform_driver(fsi_master_acf); +MODULE_LICENSE("GPL");
diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 3f487449..4eb3a76 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c
@@ -8,59 +8,31 @@ #include <linux/fsi.h> #include <linux/gpio/consumer.h> #include <linux/io.h> +#include <linux/irqflags.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/slab.h> -#include <linux/spinlock.h> #include "fsi-master.h" #define FSI_GPIO_STD_DLY 1 /* Standard pin delay in nS */ -#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */ -#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */ -#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ -#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ -#define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ -#define FSI_GPIO_STD_DELAY 10 /* Standard GPIO delay in nS */ - /* todo: adjust down as low as */ - /* possible or eliminate */ -#define FSI_GPIO_CMD_DPOLL 0x2 -#define FSI_GPIO_CMD_TERM 0x3f -#define FSI_GPIO_CMD_ABS_AR 0x4 - -#define FSI_GPIO_DPOLL_CLOCKS 100 /* < 21 will cause slave to hang */ - -/* Bus errors */ -#define FSI_GPIO_ERR_BUSY 1 /* Slave stuck in busy state */ -#define FSI_GPIO_RESP_ERRA 2 /* Any (misc) Error */ -#define FSI_GPIO_RESP_ERRC 3 /* Slave reports master CRC error */ -#define FSI_GPIO_MTOE 4 /* Master time out error */ -#define FSI_GPIO_CRC_INVAL 5 /* Master reports slave CRC error */ - -/* Normal slave responses */ -#define FSI_GPIO_RESP_BUSY 1 -#define FSI_GPIO_RESP_ACK 0 -#define FSI_GPIO_RESP_ACKD 4 - -#define FSI_GPIO_MAX_BUSY 100 -#define FSI_GPIO_MTOE_COUNT 1000 -#define FSI_GPIO_DRAIN_BITS 20 -#define FSI_GPIO_CRC_SIZE 4 -#define FSI_GPIO_MSG_ID_SIZE 2 -#define FSI_GPIO_MSG_RESPID_SIZE 2 -#define FSI_GPIO_PRIME_SLAVE_CLOCKS 100 +#define LAST_ADDR_INVALID 0x1 struct fsi_master_gpio { struct fsi_master master; struct device *dev; - spinlock_t cmd_lock; /* Lock for commands */ + struct mutex cmd_lock; /* mutex for command ordering */ struct gpio_desc *gpio_clk; struct gpio_desc *gpio_data; struct gpio_desc *gpio_trans; /* Voltage translator */ struct gpio_desc *gpio_enable; /* FSI enable */ struct gpio_desc *gpio_mux; /* Mux control */ bool external_mode; + bool no_delays; + uint32_t last_addr; + uint8_t t_send_delay; + uint8_t t_echo_delay; }; #define CREATE_TRACE_POINTS @@ -78,19 +50,31 @@ static void clock_toggle(struct fsi_master_gpio *master, int count) int i; for (i = 0; i < count; i++) { - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 0); - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 1); } } -static int sda_in(struct fsi_master_gpio *master) +static int sda_clock_in(struct fsi_master_gpio *master) { int in; - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); + gpiod_set_value(master->gpio_clk, 0); + + /* Dummy read to feed the synchronizers */ + gpiod_get_value(master->gpio_data); + + /* Actual data read */ in = gpiod_get_value(master->gpio_data); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); + gpiod_set_value(master->gpio_clk, 1); return in ? 1 : 0; } @@ -113,10 +97,17 @@ static void set_sda_output(struct fsi_master_gpio *master, int value) static void clock_zeros(struct fsi_master_gpio *master, int count) { + trace_fsi_master_gpio_clock_zeros(master, count); set_sda_output(master, 1); clock_toggle(master, count); } +static void echo_delay(struct fsi_master_gpio *master) +{ + clock_zeros(master, master->t_echo_delay); +} + + static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg *msg, uint8_t num_bits) { @@ -125,8 +116,7 @@ static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg *msg, set_sda_input(master); for (bit = 0; bit < num_bits; bit++) { - clock_toggle(master, 1); - in_bit = sda_in(master); + in_bit = sda_clock_in(master); msg->msg <<= 1; msg->msg |= ~in_bit & 0x1; /* Data is active low */ } @@ -191,22 +181,92 @@ static void msg_push_crc(struct fsi_gpio_msg *msg) msg_push_bits(msg, crc, 4); } -/* - * Encode an Absolute Address command - */ -static void build_abs_ar_command(struct fsi_gpio_msg *cmd, - uint8_t id, uint32_t addr, size_t size, const void *data) +static bool check_same_address(struct fsi_master_gpio *master, int id, + uint32_t addr) { + /* this will also handle LAST_ADDR_INVALID */ + return master->last_addr == (((id & 0x3) << 21) | (addr & ~0x3)); +} + +static bool check_relative_address(struct fsi_master_gpio *master, int id, + uint32_t addr, uint32_t *rel_addrp) +{ + uint32_t last_addr = master->last_addr; + int32_t rel_addr; + + if (last_addr == LAST_ADDR_INVALID) + return false; + + /* We may be in 23-bit addressing mode, which uses the id as the + * top two address bits. So, if we're referencing a different ID, + * use absolute addresses. + */ + if (((last_addr >> 21) & 0x3) != id) + return false; + + /* remove the top two bits from any 23-bit addressing */ + last_addr &= (1 << 21) - 1; + + /* We know that the addresses are limited to 21 bits, so this won't + * overflow the signed rel_addr */ + rel_addr = addr - last_addr; + if (rel_addr > 255 || rel_addr < -256) + return false; + + *rel_addrp = (uint32_t)rel_addr; + + return true; +} + +static void last_address_update(struct fsi_master_gpio *master, + int id, bool valid, uint32_t addr) +{ + if (!valid) + master->last_addr = LAST_ADDR_INVALID; + else + master->last_addr = ((id & 0x3) << 21) | (addr & ~0x3); +} + +/* + * Encode an Absolute/Relative/Same Address command + */ +static void build_ar_command(struct fsi_master_gpio *master, + struct fsi_gpio_msg *cmd, uint8_t id, + uint32_t addr, size_t size, const void *data) +{ + int i, addr_bits, opcode_bits; bool write = !!data; - uint8_t ds; - int i; + uint8_t ds, opcode; + uint32_t rel_addr; cmd->bits = 0; cmd->msg = 0; - msg_push_bits(cmd, id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_ABS_AR, 3); - msg_push_bits(cmd, write ? 0 : 1, 1); + /* we have 21 bits of address max */ + addr &= ((1 << 21) - 1); + + /* cmd opcodes are variable length - SAME_AR is only two bits */ + opcode_bits = 3; + + if (check_same_address(master, id, addr)) { + /* we still address the byte offset within the word */ + addr_bits = 2; + opcode_bits = 2; + opcode = FSI_CMD_SAME_AR; + trace_fsi_master_gpio_cmd_same_addr(master); + + } else if (check_relative_address(master, id, addr, &rel_addr)) { + /* 8 bits plus sign */ + addr_bits = 9; + addr = rel_addr; + opcode = FSI_CMD_REL_AR; + trace_fsi_master_gpio_cmd_rel_addr(master, rel_addr); + + } else { + addr_bits = 21; + opcode = FSI_CMD_ABS_AR; + trace_fsi_master_gpio_cmd_abs_addr(master, addr); + } /* * The read/write size is encoded in the lower bits of the address @@ -223,7 +283,10 @@ static void build_abs_ar_command(struct fsi_gpio_msg *cmd, if (size == 4) addr |= 1; - msg_push_bits(cmd, addr & ((1 << 21) - 1), 21); + msg_push_bits(cmd, id, 2); + msg_push_bits(cmd, opcode, opcode_bits); + msg_push_bits(cmd, write ? 0 : 1, 1); + msg_push_bits(cmd, addr, addr_bits); msg_push_bits(cmd, ds, 1); for (i = 0; write && i < size; i++) msg_push_bits(cmd, ((uint8_t *)data)[i], 8); @@ -237,14 +300,18 @@ static void build_dpoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) cmd->msg = 0; msg_push_bits(cmd, slave_id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_DPOLL, 3); + msg_push_bits(cmd, FSI_CMD_DPOLL, 3); msg_push_crc(cmd); } -static void echo_delay(struct fsi_master_gpio *master) +static void build_epoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) { - set_sda_output(master, 1); - clock_toggle(master, FSI_ECHO_DELAY_CLOCKS); + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_EPOLL, 3); + msg_push_crc(cmd); } static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) @@ -253,40 +320,40 @@ static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) cmd->msg = 0; msg_push_bits(cmd, slave_id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_TERM, 6); + msg_push_bits(cmd, FSI_CMD_TERM, 6); msg_push_crc(cmd); } /* - * Store information on master errors so handler can detect and clean - * up the bus + * Note: callers rely specifically on this returning -EAGAIN for + * a CRC error detected in the response. Use other error code + * for other situations. It will be converted to something else + * higher up the stack before it reaches userspace. */ -static void fsi_master_gpio_error(struct fsi_master_gpio *master, int error) -{ - -} - static int read_one_response(struct fsi_master_gpio *master, uint8_t data_size, struct fsi_gpio_msg *msgp, uint8_t *tagp) { struct fsi_gpio_msg msg; - uint8_t id, tag; + unsigned long flags; uint32_t crc; + uint8_t tag; int i; + local_irq_save(flags); + /* wait for the start bit */ - for (i = 0; i < FSI_GPIO_MTOE_COUNT; i++) { + for (i = 0; i < FSI_MASTER_MTOE_COUNT; i++) { msg.bits = 0; msg.msg = 0; serial_in(master, &msg, 1); if (msg.msg) break; } - if (i == FSI_GPIO_MTOE_COUNT) { + if (i == FSI_MASTER_MTOE_COUNT) { dev_dbg(master->dev, "Master time out waiting for response\n"); - fsi_master_gpio_error(master, FSI_GPIO_MTOE); - return -EIO; + local_irq_restore(flags); + return -ETIMEDOUT; } msg.bits = 0; @@ -295,23 +362,27 @@ static int read_one_response(struct fsi_master_gpio *master, /* Read slave ID & response tag */ serial_in(master, &msg, 4); - id = (msg.msg >> FSI_GPIO_MSG_RESPID_SIZE) & 0x3; tag = msg.msg & 0x3; /* If we have an ACK and we're expecting data, clock the data in too */ - if (tag == FSI_GPIO_RESP_ACK && data_size) + if (tag == FSI_RESP_ACK && data_size) serial_in(master, &msg, data_size * 8); /* read CRC */ - serial_in(master, &msg, FSI_GPIO_CRC_SIZE); + serial_in(master, &msg, FSI_CRC_SIZE); + + local_irq_restore(flags); /* we have a whole message now; check CRC */ crc = crc4(0, 1, 1); crc = crc4(crc, msg.msg, msg.bits); if (crc) { - dev_dbg(master->dev, "ERR response CRC\n"); - fsi_master_gpio_error(master, FSI_GPIO_CRC_INVAL); - return -EIO; + /* Check if it's all 1's, that probably means the host is off */ + if (((~msg.msg) & ((1ull << msg.bits) - 1)) == 0) + return -ENODEV; + dev_dbg(master->dev, "ERR response CRC msg: 0x%016llx (%d bits)\n", + msg.msg, msg.bits); + return -EAGAIN; } if (msgp) @@ -325,19 +396,23 @@ static int read_one_response(struct fsi_master_gpio *master, static int issue_term(struct fsi_master_gpio *master, uint8_t slave) { struct fsi_gpio_msg cmd; + unsigned long flags; uint8_t tag; int rc; build_term_command(&cmd, slave); + + local_irq_save(flags); serial_out(master, &cmd); echo_delay(master); + local_irq_restore(flags); rc = read_one_response(master, 0, NULL, &tag); if (rc < 0) { dev_err(master->dev, "TERM failed; lost communication with slave\n"); return -EIO; - } else if (tag != FSI_GPIO_RESP_ACK) { + } else if (tag != FSI_RESP_ACK) { dev_err(master->dev, "TERM failed; response %d\n", tag); return -EIO; } @@ -350,16 +425,39 @@ static int poll_for_response(struct fsi_master_gpio *master, { struct fsi_gpio_msg response, cmd; int busy_count = 0, rc, i; + unsigned long flags; uint8_t tag; uint8_t *data_byte = data; - + int crc_err_retries = 0; retry: rc = read_one_response(master, size, &response, &tag); - if (rc) - return rc; + + /* Handle retries on CRC errors */ + if (rc == -EAGAIN) { + /* Too many retries ? */ + if (crc_err_retries++ > FSI_CRC_ERR_RETRIES) { + /* + * Pass it up as a -EIO otherwise upper level will retry + * the whole command which isn't what we want here. + */ + rc = -EIO; + goto fail; + } + dev_dbg(master->dev, + "CRC error retry %d\n", crc_err_retries); + trace_fsi_master_gpio_crc_rsp_error(master); + build_epoll_command(&cmd, slave); + local_irq_save(flags); + clock_zeros(master, FSI_MASTER_EPOLL_CLOCKS); + serial_out(master, &cmd); + echo_delay(master); + local_irq_restore(flags); + goto retry; + } else if (rc) + goto fail; switch (tag) { - case FSI_GPIO_RESP_ACK: + case FSI_RESP_ACK: if (size && data) { uint64_t val = response.msg; /* clear crc & mask */ @@ -372,58 +470,90 @@ static int poll_for_response(struct fsi_master_gpio *master, } } break; - case FSI_GPIO_RESP_BUSY: + case FSI_RESP_BUSY: /* * Its necessary to clock slave before issuing * d-poll, not indicated in the hardware protocol * spec. < 20 clocks causes slave to hang, 21 ok. */ - clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); - if (busy_count++ < FSI_GPIO_MAX_BUSY) { + if (busy_count++ < FSI_MASTER_MAX_BUSY) { build_dpoll_command(&cmd, slave); + local_irq_save(flags); + clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); serial_out(master, &cmd); echo_delay(master); + local_irq_restore(flags); goto retry; } dev_warn(master->dev, "ERR slave is stuck in busy state, issuing TERM\n"); + local_irq_save(flags); + clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); + local_irq_restore(flags); issue_term(master, slave); rc = -EIO; break; - case FSI_GPIO_RESP_ERRA: - case FSI_GPIO_RESP_ERRC: - dev_dbg(master->dev, "ERR%c received: 0x%x\n", - tag == FSI_GPIO_RESP_ERRA ? 'A' : 'C', - (int)response.msg); - fsi_master_gpio_error(master, response.msg); + case FSI_RESP_ERRA: + dev_dbg(master->dev, "ERRA received: 0x%x\n", (int)response.msg); rc = -EIO; break; + case FSI_RESP_ERRC: + dev_dbg(master->dev, "ERRC received: 0x%x\n", (int)response.msg); + trace_fsi_master_gpio_crc_cmd_error(master); + rc = -EAGAIN; + break; } - /* Clock the slave enough to be ready for next operation */ - clock_zeros(master, FSI_GPIO_PRIME_SLAVE_CLOCKS); + if (busy_count > 0) + trace_fsi_master_gpio_poll_response_busy(master, busy_count); + fail: + /* + * tSendDelay clocks, avoids signal reflections when switching + * from receive of response back to send of data. + */ + local_irq_save(flags); + clock_zeros(master, master->t_send_delay); + local_irq_restore(flags); + return rc; } +static int send_request(struct fsi_master_gpio *master, + struct fsi_gpio_msg *cmd) +{ + unsigned long flags; + + if (master->external_mode) + return -EBUSY; + + local_irq_save(flags); + serial_out(master, cmd); + echo_delay(master); + local_irq_restore(flags); + + return 0; +} + static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave, struct fsi_gpio_msg *cmd, size_t resp_len, void *resp) { - unsigned long flags; - int rc; + int rc = -EAGAIN, retries = 0; - spin_lock_irqsave(&master->cmd_lock, flags); + while ((retries++) < FSI_CRC_ERR_RETRIES) { + rc = send_request(master, cmd); + if (rc) + break; + rc = poll_for_response(master, slave, resp_len, resp); + if (rc != -EAGAIN) + break; + rc = -EIO; + dev_warn(master->dev, "ECRC retry %d\n", retries); - if (master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); - return -EBUSY; + /* Pace it a bit before retry */ + msleep(1); } - serial_out(master, cmd); - echo_delay(master); - rc = poll_for_response(master, slave, resp_len, resp); - spin_unlock_irqrestore(&master->cmd_lock, flags); - return rc; } @@ -432,12 +562,18 @@ static int fsi_master_gpio_read(struct fsi_master *_master, int link, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; - build_abs_ar_command(&cmd, id, addr, size, NULL); - return fsi_master_gpio_xfer(master, id, &cmd, size, val); + mutex_lock(&master->cmd_lock); + build_ar_command(master, &cmd, id, addr, size, NULL); + rc = fsi_master_gpio_xfer(master, id, &cmd, size, val); + last_address_update(master, id, rc == 0, addr); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_write(struct fsi_master *_master, int link, @@ -445,12 +581,18 @@ static int fsi_master_gpio_write(struct fsi_master *_master, int link, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; - build_abs_ar_command(&cmd, id, addr, size, val); - return fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + mutex_lock(&master->cmd_lock); + build_ar_command(master, &cmd, id, addr, size, val); + rc = fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, rc == 0, addr); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_term(struct fsi_master *_master, @@ -458,12 +600,18 @@ static int fsi_master_gpio_term(struct fsi_master *_master, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; + mutex_lock(&master->cmd_lock); build_term_command(&cmd, id); - return fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + rc = fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, false, 0); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_break(struct fsi_master *_master, int link) @@ -476,11 +624,14 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) trace_fsi_master_gpio_break(master); - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return -EBUSY; } + + local_irq_save(flags); + set_sda_output(master, 1); sda_out(master, 1); clock_toggle(master, FSI_PRE_BREAK_CLOCKS); @@ -489,7 +640,11 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) echo_delay(master); sda_out(master, 1); clock_toggle(master, FSI_POST_BREAK_CLOCKS); - spin_unlock_irqrestore(&master->cmd_lock, flags); + + local_irq_restore(flags); + + last_address_update(master, 0, false, 0); + mutex_unlock(&master->cmd_lock); /* Wait for logic reset to take effect */ udelay(200); @@ -499,6 +654,8 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) static void fsi_master_gpio_init(struct fsi_master_gpio *master) { + unsigned long flags; + gpiod_direction_output(master->gpio_mux, 1); gpiod_direction_output(master->gpio_trans, 1); gpiod_direction_output(master->gpio_enable, 1); @@ -506,7 +663,9 @@ static void fsi_master_gpio_init(struct fsi_master_gpio *master) gpiod_direction_output(master->gpio_data, 1); /* todo: evaluate if clocks can be reduced */ + local_irq_save(flags); clock_zeros(master, FSI_INIT_CLOCKS); + local_irq_restore(flags); } static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) @@ -521,22 +680,37 @@ static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link) { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); - unsigned long flags; int rc = -EBUSY; if (link != 0) return -ENODEV; - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (!master->external_mode) { gpiod_set_value(master->gpio_enable, 1); rc = 0; } - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return rc; } +static int fsi_master_gpio_link_config(struct fsi_master *_master, int link, + u8 t_send_delay, u8 t_echo_delay) +{ + struct fsi_master_gpio *master = to_fsi_master_gpio(_master); + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->cmd_lock); + master->t_send_delay = t_send_delay; + master->t_echo_delay = t_echo_delay; + mutex_unlock(&master->cmd_lock); + + return 0; +} + static ssize_t external_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -550,7 +724,7 @@ static ssize_t external_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct fsi_master_gpio *master = dev_get_drvdata(dev); - unsigned long flags, val; + unsigned long val; bool external_mode; int err; @@ -560,10 +734,10 @@ static ssize_t external_mode_store(struct device *dev, external_mode = !!val; - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (external_mode == master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return count; } @@ -572,7 +746,8 @@ static ssize_t external_mode_store(struct device *dev, fsi_master_gpio_init_external(master); else fsi_master_gpio_init(master); - spin_unlock_irqrestore(&master->cmd_lock, flags); + + mutex_unlock(&master->cmd_lock); fsi_master_rescan(&master->master); @@ -582,31 +757,44 @@ static ssize_t external_mode_store(struct device *dev, static DEVICE_ATTR(external_mode, 0664, external_mode_show, external_mode_store); +static void fsi_master_gpio_release(struct device *dev) +{ + struct fsi_master_gpio *master = to_fsi_master_gpio(dev_to_fsi_master(dev)); + + of_node_put(dev_of_node(master->dev)); + + kfree(master); +} + static int fsi_master_gpio_probe(struct platform_device *pdev) { struct fsi_master_gpio *master; struct gpio_desc *gpio; int rc; - master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + master = kzalloc(sizeof(*master), GFP_KERNEL); if (!master) return -ENOMEM; master->dev = &pdev->dev; master->master.dev.parent = master->dev; master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); + master->master.dev.release = fsi_master_gpio_release; + master->last_addr = LAST_ADDR_INVALID; gpio = devm_gpiod_get(&pdev->dev, "clock", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get clock gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_clk = gpio; gpio = devm_gpiod_get(&pdev->dev, "data", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get data gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_data = gpio; @@ -614,24 +802,38 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) gpio = devm_gpiod_get_optional(&pdev->dev, "trans", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get trans gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_trans = gpio; gpio = devm_gpiod_get_optional(&pdev->dev, "enable", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get enable gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_enable = gpio; gpio = devm_gpiod_get_optional(&pdev->dev, "mux", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get mux gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_mux = gpio; + /* + * Check if GPIO block is slow enought that no extra delays + * are necessary. This improves performance on ast2500 by + * an order of magnitude. + */ + master->no_delays = device_property_present(&pdev->dev, "no-gpio-delays"); + + /* Default FSI command delays */ + master->t_send_delay = FSI_SEND_DELAY_CLOCKS; + master->t_echo_delay = FSI_ECHO_DELAY_CLOCKS; + master->master.n_links = 1; master->master.flags = FSI_MASTER_FLAG_SWCLOCK; master->master.read = fsi_master_gpio_read; @@ -639,34 +841,37 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) master->master.term = fsi_master_gpio_term; master->master.send_break = fsi_master_gpio_break; master->master.link_enable = fsi_master_gpio_link_enable; + master->master.link_config = fsi_master_gpio_link_config; platform_set_drvdata(pdev, master); - spin_lock_init(&master->cmd_lock); + mutex_init(&master->cmd_lock); fsi_master_gpio_init(master); rc = device_create_file(&pdev->dev, &dev_attr_external_mode); if (rc) - return rc; + goto err_free; - return fsi_master_register(&master->master); + rc = fsi_master_register(&master->master); + if (rc) { + device_remove_file(&pdev->dev, &dev_attr_external_mode); + put_device(&master->master.dev); + return rc; + } + return 0; + err_free: + kfree(master); + return rc; } + static int fsi_master_gpio_remove(struct platform_device *pdev) { struct fsi_master_gpio *master = platform_get_drvdata(pdev); - devm_gpiod_put(&pdev->dev, master->gpio_clk); - devm_gpiod_put(&pdev->dev, master->gpio_data); - if (master->gpio_trans) - devm_gpiod_put(&pdev->dev, master->gpio_trans); - if (master->gpio_enable) - devm_gpiod_put(&pdev->dev, master->gpio_enable); - if (master->gpio_mux) - devm_gpiod_put(&pdev->dev, master->gpio_mux); - fsi_master_unregister(&master->master); + device_remove_file(&pdev->dev, &dev_attr_external_mode); - of_node_put(master->master.dev.of_node); + fsi_master_unregister(&master->master); return 0; }
diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c index 5885fc4..b3c1e9d 100644 --- a/drivers/fsi/fsi-master-hub.c +++ b/drivers/fsi/fsi-master-hub.c
@@ -122,7 +122,8 @@ static int hub_master_write(struct fsi_master *master, int link, static int hub_master_break(struct fsi_master *master, int link) { - uint32_t addr, cmd; + uint32_t addr; + __be32 cmd; addr = 0x4; cmd = cpu_to_be32(0xc0de0000); @@ -205,7 +206,7 @@ static int hub_master_init(struct fsi_master_hub *hub) if (rc) return rc; - reg = ~0; + reg = cpu_to_be32(~0); rc = fsi_device_write(dev, FSI_MSENP0, ®, sizeof(reg)); if (rc) return rc;
diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h index ee0b460..040a7d4 100644 --- a/drivers/fsi/fsi-master.h +++ b/drivers/fsi/fsi-master.h
@@ -18,7 +18,41 @@ #define DRIVERS_FSI_MASTER_H #include <linux/device.h> +#include <linux/mutex.h> +/* Various protocol delays */ +#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */ +#define FSI_SEND_DELAY_CLOCKS 16 /* Number clocks for send delay */ +#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */ +#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ +#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ +#define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ +#define FSI_MASTER_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ +#define FSI_MASTER_EPOLL_CLOCKS 50 /* Number of clocks for E_POLL retry */ + +/* Various retry maximums */ +#define FSI_CRC_ERR_RETRIES 10 +#define FSI_MASTER_MAX_BUSY 200 +#define FSI_MASTER_MTOE_COUNT 1000 + +/* Command encodings */ +#define FSI_CMD_DPOLL 0x2 +#define FSI_CMD_EPOLL 0x3 +#define FSI_CMD_TERM 0x3f +#define FSI_CMD_ABS_AR 0x4 +#define FSI_CMD_REL_AR 0x5 +#define FSI_CMD_SAME_AR 0x3 /* but only a 2-bit opcode... */ + +/* Slave responses */ +#define FSI_RESP_ACK 0 /* Success */ +#define FSI_RESP_BUSY 1 /* Slave busy */ +#define FSI_RESP_ERRA 2 /* Any (misc) Error */ +#define FSI_RESP_ERRC 3 /* Slave reports master CRC error */ + +/* Misc */ +#define FSI_CRC_SIZE 4 + +/* fsi-master definition and flags */ #define FSI_MASTER_FLAG_SWCLOCK 0x1 struct fsi_master { @@ -26,6 +60,7 @@ struct fsi_master { int idx; int n_links; int flags; + struct mutex scan_lock; int (*read)(struct fsi_master *, int link, uint8_t id, uint32_t addr, void *val, size_t size); int (*write)(struct fsi_master *, int link, uint8_t id, @@ -33,6 +68,8 @@ struct fsi_master { int (*term)(struct fsi_master *, int link, uint8_t id); int (*send_break)(struct fsi_master *, int link); int (*link_enable)(struct fsi_master *, int link); + int (*link_config)(struct fsi_master *, int link, + u8 t_send_delay, u8 t_echo_delay); }; #define dev_to_fsi_master(d) container_of(d, struct fsi_master, dev)
diff --git a/drivers/fsi/fsi-occ.c b/drivers/fsi/fsi-occ.c new file mode 100644 index 0000000..7c28ca3 --- /dev/null +++ b/drivers/fsi/fsi-occ.c
@@ -0,0 +1,609 @@ +/* + * Copyright 2017 IBM Corp. + * + * 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 <asm/unaligned.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/cdev.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/fsi.h> +#include <linux/fsi-sbefifo.h> +#include <linux/fsi-occ.h> + +#define OCC_SRAM_BYTES 4096 +#define OCC_CMD_DATA_BYTES 4090 +#define OCC_RESP_DATA_BYTES 4089 + +#define OCC_SRAM_CMD_ADDR 0xFFFBE000 +#define OCC_SRAM_RSP_ADDR 0xFFFBF000 + +/* + * Assume we don't have much FFDC, if we do we'll overflow and + * fail the command. This needs to be big enough for simple + * commands as well. + */ +#define OCC_SBE_STATUS_WORDS 32 + +#define OCC_TIMEOUT_MS 1000 +#define OCC_CMD_IN_PRG_WAIT_MS 50 + +struct occ { + struct platform_device *pdev; + struct device *sbefifo; + struct device dev; + struct cdev cdev; + struct mutex lock; +}; + +#define to_occ(x) container_of((x), struct occ, mdev) + +struct occ_response { + u8 seq_no; + u8 cmd_type; + u8 return_status; + __be16 data_length; + u8 data[OCC_RESP_DATA_BYTES + 2]; /* two bytes checksum */ +} __packed; + +struct occ_client { + struct occ *occ; + struct mutex lock; + size_t data_size; + size_t read_offset; + u8 *buffer; +}; + +#define to_client(x) container_of((x), struct occ_client, xfr) + +static int occ_open(struct inode *inode, struct file *file) +{ + struct occ_client *client = kzalloc(sizeof(*client), GFP_KERNEL); + struct occ *occ = container_of(inode->i_cdev, struct occ, cdev); + + if (!client) + return -ENOMEM; + client->buffer = (u8 *)__get_free_page(GFP_KERNEL); + if (!client->buffer) { + kfree(client); + return -ENOMEM; + } + client->occ = occ; + mutex_init(&client->lock); + file->private_data = client; + + /* We allocate a 1-page buffer, make sure it all fits */ + BUILD_BUG_ON((OCC_CMD_DATA_BYTES + 3) > PAGE_SIZE); + BUILD_BUG_ON((OCC_RESP_DATA_BYTES + 7) > PAGE_SIZE); + + return 0; +} + +static ssize_t occ_read(struct file *file, char __user *buf, size_t len, + loff_t *offset) +{ + struct occ_client *client = file->private_data; + ssize_t rc = 0; + + if (!client) + return -ENODEV; + if (len > OCC_SRAM_BYTES) + return -EINVAL; + + mutex_lock(&client->lock); + + /* This should not be possible ... */ + if (WARN_ON_ONCE(client->read_offset > client->data_size)) { + rc = -EIO; + goto done; + } + + /* Grab how much data we have to read */ + rc = min(len, client->data_size - client->read_offset); + + if (copy_to_user(buf, client->buffer + client->read_offset, rc)) + rc = -EFAULT; + else + client->read_offset += rc; + done: + mutex_unlock(&client->lock); + + return rc; +} + +static ssize_t occ_write(struct file *file, const char __user *buf, + size_t len, loff_t *offset) +{ + struct occ_client *client = file->private_data; + size_t rlen, data_length; + u16 checksum = 0; + ssize_t rc, i; + u8 *cmd; + + if (!client) + return -ENODEV; + + if (len > (OCC_CMD_DATA_BYTES + 3) || len < 3) + return -EINVAL; + + mutex_lock(&client->lock); + + /* Construct the command */ + cmd = client->buffer; + + /* Sequence number (we could increment it and compare with the response) */ + cmd[0] = 1; + + /* + * Copy the user command (assume user data follows the occ command format) + * byte 0 : command type + * bytes 1-2: data length (msb first) + * bytes 3-n: data + */ + if (copy_from_user(&cmd[1], buf, len)) { + rc = -EFAULT; + goto done; + } + + /* Extract data length */ + data_length = (cmd[2] << 8) + cmd[3]; + if (data_length > OCC_CMD_DATA_BYTES) { + rc = -EINVAL; + goto done; + } + + /* Calculate checksum */ + for (i = 0; i < data_length + 4; ++i) + checksum += cmd[i]; + cmd[data_length + 4] = checksum >> 8; + cmd[data_length + 5] = checksum & 0xFF; + + /* Submit command */ + rlen = PAGE_SIZE; + rc = fsi_occ_submit(&client->occ->pdev->dev, cmd, data_length + 6, cmd, &rlen); + if (rc) + goto done; + + /* Set read tracking data */ + client->data_size = rlen; + client->read_offset = 0; + + /* Done */ + rc = len; + done: + mutex_unlock(&client->lock); + + return rc; +} + +static int occ_release(struct inode *inode, struct file *file) +{ + struct occ_client *client = file->private_data; + + free_page((unsigned long)client->buffer); + kfree(client); + + return 0; +} + +static const struct file_operations occ_fops = { + .owner = THIS_MODULE, + .open = occ_open, + .read = occ_read, + .write = occ_write, + .release = occ_release, +}; + +static int occ_verify_checksum(struct occ_response *resp, u16 data_length) +{ + u16 checksum; + /* Fetch the two bytes after the data for the checksum. */ + u16 checksum_resp = get_unaligned_be16(&resp->data[data_length]); + u16 i; + + checksum = resp->seq_no; + checksum += resp->cmd_type; + checksum += resp->return_status; + checksum += (data_length >> 8) + (data_length & 0xFF); + + for (i = 0; i < data_length; ++i) + checksum += resp->data[i]; + + if (checksum != checksum_resp) + return -EBADMSG; + + return 0; +} + +static int occ_getsram(struct device *sbefifo, u32 address, void *data, + ssize_t len) +{ + u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */ + size_t resp_len, resp_data_len; + __be32 *resp, cmd[5]; + int rc; + + /* + * Magic sequence to do SBE getsram command. SBE will fetch data from + * specified SRAM address. + */ + cmd[0] = cpu_to_be32(0x5); + cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_OCC_SRAM); + cmd[2] = cpu_to_be32(1); + cmd[3] = cpu_to_be32(address); + cmd[4] = cpu_to_be32(data_len); + + resp_len = (data_len >> 2) + OCC_SBE_STATUS_WORDS; + resp = kzalloc(resp_len << 2 , GFP_KERNEL); + if (!resp) + return -ENOMEM; + + rc = sbefifo_submit(sbefifo, cmd, 5, resp, &resp_len); + if (rc) + goto free; + rc = sbefifo_parse_status(sbefifo, SBEFIFO_CMD_GET_OCC_SRAM, + resp, resp_len, &resp_len); + if (rc) + goto free; + + resp_data_len = be32_to_cpu(resp[resp_len - 1]); + if (resp_data_len != data_len) { + pr_err("occ: SRAM read expected %d bytes got %zd\n", + data_len, resp_data_len); + rc = -EBADMSG; + } else { + memcpy(data, resp, len); + } + +free: + /* Convert positive SBEI status */ + if (rc > 0) { + pr_err("occ: SRAM read returned failure status: %08x\n", rc); + rc = -EBADMSG; + } + kfree(resp); + return rc; +} + +static int occ_putsram(struct device *sbefifo, u32 address, const void *data, + ssize_t len) +{ + size_t cmd_len, buf_len, resp_len, resp_data_len; + u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */ + __be32 *buf; + int rc; + + /* + * We use the same buffer for command and response, make + * sure it's big enough + */ + resp_len = OCC_SBE_STATUS_WORDS; + cmd_len = (data_len >> 2) + 5; + buf_len = max(cmd_len, resp_len); + buf = kzalloc(buf_len << 2, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* + * Magic sequence to do SBE putsram command. SBE will transfer + * data to specified SRAM address. + */ + buf[0] = cpu_to_be32(cmd_len); + buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM); + buf[2] = cpu_to_be32(1); + buf[3] = cpu_to_be32(address); + buf[4] = cpu_to_be32(data_len); + + memcpy(&buf[5], data, len); + + rc = sbefifo_submit(sbefifo, buf, cmd_len, buf, &resp_len); + if (rc) + goto free; + rc = sbefifo_parse_status(sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM, + buf, resp_len, &resp_len); + if (rc) + goto free; + + if (resp_len != 1) { + pr_err("occ: SRAM write response lenght invalid: %zd\n", + resp_len); + rc = -EBADMSG; + } else { + resp_data_len = be32_to_cpu(buf[0]); + if (resp_data_len != data_len) { + pr_err("occ: SRAM write expected %d bytes got %zd\n", + data_len, resp_data_len); + rc = -EBADMSG; + } + } +free: + /* Convert positive SBEI status */ + if (rc > 0) { + pr_err("occ: SRAM write returned failure status: %08x\n", rc); + rc = -EBADMSG; + } + kfree(buf); + return rc; +} + +static int occ_trigger_attn(struct device *sbefifo) +{ + __be32 buf[OCC_SBE_STATUS_WORDS]; + size_t resp_len, resp_data_len; + int rc; + + BUILD_BUG_ON(OCC_SBE_STATUS_WORDS < 7); + resp_len = OCC_SBE_STATUS_WORDS; + + buf[0] = cpu_to_be32(0x5 + 0x2); /* Chip-op length in words */ + buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM); + buf[2] = cpu_to_be32(0x3); /* Mode: Circular */ + buf[3] = cpu_to_be32(0x0); /* Address: ignored in mode 3 */ + buf[4] = cpu_to_be32(0x8); /* Data length in bytes */ + buf[5] = cpu_to_be32(0x20010000); /* Trigger OCC attention */ + buf[6] = 0; + + rc = sbefifo_submit(sbefifo, buf, 7, buf, &resp_len); + if (rc) + goto error; + rc = sbefifo_parse_status(sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM, + buf, resp_len, &resp_len); + if (rc) + goto error; + + if (resp_len != 1) { + pr_err("occ: SRAM attn response lenght invalid: %zd\n", + resp_len); + rc = -EBADMSG; + } else { + resp_data_len = be32_to_cpu(buf[0]); + if (resp_data_len != 8) { + pr_err("occ: SRAM attn expected 8 bytes got %zd\n", + resp_data_len); + rc = -EBADMSG; + } + } + error: + /* Convert positive SBEI status */ + if (rc > 0) { + pr_err("occ: SRAM attn returned failure status: %08x\n", rc); + rc = -EBADMSG; + } + + + return rc; +} + +int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, + void *response, size_t *resp_len) +{ + const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS); + const unsigned long wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS); + struct occ *occ = dev_get_drvdata(dev); + struct occ_response *resp = response; + struct device *sbefifo = occ->sbefifo; + u16 resp_data_length; + unsigned long start; + int rc; + + if (!occ) + return -ENODEV; + if (*resp_len < 7) { + dev_dbg(dev, "Bad resplen %zd\n", *resp_len); + return -EINVAL; + } + + mutex_lock(&occ->lock); + if (!sbefifo) + rc = -ENODEV; + else + rc = occ_putsram(sbefifo, OCC_SRAM_CMD_ADDR, request, req_len); + if (rc) + goto done; + + rc = occ_trigger_attn(sbefifo); + if (rc) + goto done; + + /* Read occ response header */ + start = jiffies; + do { + rc = occ_getsram(sbefifo, OCC_SRAM_RSP_ADDR, resp, 8); + if (rc) + goto done; + + if (resp->return_status == OCC_RESP_CMD_IN_PRG) { + rc = -ETIMEDOUT; + + if (time_after(jiffies, start + timeout)) + break; + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(wait_time); + } + } while (rc); + + /* Extract size of response data */ + resp_data_length = get_unaligned_be16(&resp->data_length); + + /* Message size is data length + 5 bytes header + 2 bytes checksum */ + if ((resp_data_length + 7) > *resp_len) { + rc = -EMSGSIZE; + goto done; + } + + dev_dbg(dev, "resp_status=%02x resp_data_len=%d\n", + resp->return_status, resp_data_length); + + /* Grab the rest */ + if (resp_data_length > 1) { + /* already got 3 bytes resp, also need 2 bytes checksum */ + rc = occ_getsram(sbefifo, OCC_SRAM_RSP_ADDR + 8, + &resp->data[3], resp_data_length - 1); + if (rc) + goto done; + } + + *resp_len = resp_data_length + 7; + + rc = occ_verify_checksum(resp, resp_data_length); + done: + mutex_unlock(&occ->lock); + + return rc; +} +EXPORT_SYMBOL_GPL(fsi_occ_submit); + +static int occ_unregister_child(struct device *dev, void *data) +{ + struct platform_device *hwmon_dev = to_platform_device(dev); + + platform_device_unregister(hwmon_dev); + + return 0; +} + +static void occ_free(struct device *dev) +{ + struct occ *occ = container_of(dev, struct occ, dev); + + put_device(&occ->pdev->dev); + kfree(occ); +} + +static int occ_probe(struct platform_device *pdev) +{ + int rc, didx; + struct occ *occ; + struct platform_device *hwmon_dev; + struct platform_device_info hwmon_dev_info = { + .parent = &pdev->dev, + .name = "occ-hwmon", + }; + + occ = kzalloc(sizeof(*occ), GFP_KERNEL); + if (!occ) + return -ENOMEM; + + /* Grab a reference to the device (parent of our cdev), we'll drop it later */ + if (!get_device(&pdev->dev)) { + kfree(occ); + return -ENODEV; + } + + occ->pdev = pdev; + occ->sbefifo = pdev->dev.parent; + platform_set_drvdata(pdev, occ); + mutex_init(&occ->lock); + + /* Create chardev for userspace access */ + occ->dev.type = &fsi_cdev_type; + occ->dev.parent = &pdev->dev; + occ->dev.release = occ_free; + device_initialize(&occ->dev); + + /* Allocate a minor in the FSI space */ + rc = fsi_get_new_minor(sbefifo_get_fsidev(occ->sbefifo), + fsi_dev_occ, &occ->dev.devt, &didx); + if (rc) + goto err; + + /* + * If we have a device node, try to use the "reg" property as our + * device index, otherwise use didx which is our chip-id on simple + * platforms. + */ + if (dev_of_node(&pdev->dev)) { + u32 reg; + rc = of_property_read_u32(dev_of_node(&pdev->dev), "reg", ®); + if (!rc) + didx = reg; + } + + dev_set_name(&occ->dev, "occ%d", didx); + cdev_init(&occ->cdev, &occ_fops); + rc = cdev_device_add(&occ->cdev, &occ->dev); + if (rc) { + dev_err(&pdev->dev, "Error %d creating char device %s\n", + rc, dev_name(&occ->dev)); + goto err_free_minor; + } + + hwmon_dev_info.id = didx; + hwmon_dev = platform_device_register_full(&hwmon_dev_info); + if (!hwmon_dev) + dev_warn(&pdev->dev, "failed to create hwmon device\n"); + + return 0; + err_free_minor: + fsi_free_minor(occ->dev.devt); + err: + put_device(&occ->dev); + return rc; +} + +static int occ_remove(struct platform_device *pdev) +{ + struct occ *occ = platform_get_drvdata(pdev); + + /* The parent is potentially going away, so we must not reference it anymore */ + mutex_lock(&occ->lock); + occ->sbefifo = NULL; + mutex_unlock(&occ->lock); + + cdev_device_del(&occ->cdev, &occ->dev); + fsi_free_minor(occ->dev.devt); + + device_for_each_child(&pdev->dev, NULL, occ_unregister_child); + + put_device(&occ->dev); + + return 0; +} + +static const struct of_device_id occ_match[] = { + { .compatible = "ibm,p9-occ" }, + { }, +}; + +static struct platform_driver occ_driver = { + .driver = { + .name = "occ", + .of_match_table = occ_match, + }, + .probe = occ_probe, + .remove = occ_remove, +}; + +static int occ_init(void) +{ + return platform_driver_register(&occ_driver); +} + +static void occ_exit(void) +{ + platform_driver_unregister(&occ_driver); +} + +module_init(occ_init); +module_exit(occ_exit); + +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>"); +MODULE_DESCRIPTION("BMC P9 OCC driver"); +MODULE_LICENSE("GPL");
diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c new file mode 100644 index 0000000..c4b1eb5 --- /dev/null +++ b/drivers/fsi/fsi-sbefifo.c
@@ -0,0 +1,1074 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) IBM Corporation 2017 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/fsi.h> +#include <linux/fsi-sbefifo.h> +#include <linux/kernel.h> +#include <linux/cdev.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/uio.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> + +/* + * The SBEFIFO is a pipe-like FSI device for communicating with + * the self boot engine on POWER processors. + */ + +#define DEVICE_NAME "sbefifo" +#define FSI_ENGID_SBE 0x22 + +/* + * Register layout + */ + +/* Register banks */ +#define SBEFIFO_UP 0x00 /* FSI -> Host */ +#define SBEFIFO_DOWN 0x40 /* Host -> FSI */ + +/* Per-bank registers */ +#define SBEFIFO_FIFO 0x00 /* The FIFO itself */ +#define SBEFIFO_STS 0x04 /* Status register */ +#define SBEFIFO_STS_PARITY_ERR 0x20000000 +#define SBEFIFO_STS_RESET_REQ 0x02000000 +#define SBEFIFO_STS_GOT_EOT 0x00800000 +#define SBEFIFO_STS_MAX_XFER_LIMIT 0x00400000 +#define SBEFIFO_STS_FULL 0x00200000 +#define SBEFIFO_STS_EMPTY 0x00100000 +#define SBEFIFO_STS_ECNT_MASK 0x000f0000 +#define SBEFIFO_STS_ECNT_SHIFT 16 +#define SBEFIFO_STS_VALID_MASK 0x0000ff00 +#define SBEFIFO_STS_VALID_SHIFT 8 +#define SBEFIFO_STS_EOT_MASK 0x000000ff +#define SBEFIFO_STS_EOT_SHIFT 0 +#define SBEFIFO_EOT_RAISE 0x08 /* (Up only) Set End Of Transfer */ +#define SBEFIFO_REQ_RESET 0x0C /* (Up only) Reset Request */ +#define SBEFIFO_PERFORM_RESET 0x10 /* (Down only) Perform Reset */ +#define SBEFIFO_EOT_ACK 0x14 /* (Down only) Acknowledge EOT */ +#define SBEFIFO_DOWN_MAX 0x18 /* (Down only) Max transfer */ + +/* CFAM GP Mailbox SelfBoot Message register */ +#define CFAM_GP_MBOX_SBM_ADDR 0x2824 /* Converted 0x2809 */ + +#define CFAM_SBM_SBE_BOOTED 0x80000000 +#define CFAM_SBM_SBE_ASYNC_FFDC 0x40000000 +#define CFAM_SBM_SBE_STATE_MASK 0x00f00000 +#define CFAM_SBM_SBE_STATE_SHIFT 20 + +enum sbe_state +{ + SBE_STATE_UNKNOWN = 0x0, // Unkown, initial state + SBE_STATE_IPLING = 0x1, // IPL'ing - autonomous mode (transient) + SBE_STATE_ISTEP = 0x2, // ISTEP - Running IPL by steps (transient) + SBE_STATE_MPIPL = 0x3, // MPIPL + SBE_STATE_RUNTIME = 0x4, // SBE Runtime + SBE_STATE_DMT = 0x5, // Dead Man Timer State (transient) + SBE_STATE_DUMP = 0x6, // Dumping + SBE_STATE_FAILURE = 0x7, // Internal SBE failure + SBE_STATE_QUIESCE = 0x8, // Final state - needs SBE reset to get out +}; + +/* FIFO depth */ +#define SBEFIFO_FIFO_DEPTH 8 + +/* Helpers */ +#define sbefifo_empty(sts) ((sts) & SBEFIFO_STS_EMPTY) +#define sbefifo_full(sts) ((sts) & SBEFIFO_STS_FULL) +#define sbefifo_parity_err(sts) ((sts) & SBEFIFO_STS_PARITY_ERR) +#define sbefifo_populated(sts) (((sts) & SBEFIFO_STS_ECNT_MASK) >> SBEFIFO_STS_ECNT_SHIFT) +#define sbefifo_vacant(sts) (SBEFIFO_FIFO_DEPTH - sbefifo_populated(sts)) +#define sbefifo_eot_set(sts) (((sts) & SBEFIFO_STS_EOT_MASK) >> SBEFIFO_STS_EOT_SHIFT) + +/* Reset request timeout in ms */ +#define SBEFIFO_RESET_TIMEOUT 10000 + +/* Timeouts for commands in ms */ +#define SBEFIFO_TIMEOUT_START_CMD 10000 +#define SBEFIFO_TIMEOUT_IN_CMD 1000 +#define SBEFIFO_TIMEOUT_START_RSP 10000 +#define SBEFIFO_TIMEOUT_IN_RSP 1000 + +/* Other constants */ +#define SBEFIFO_MAX_USER_CMD_LEN (0x100000 + PAGE_SIZE) +#define SBEFIFO_RESET_MAGIC 0x52534554 /* "RSET" */ + +struct sbefifo { + uint32_t magic; +#define SBEFIFO_MAGIC 0x53424546 /* "SBEF" */ + struct fsi_device *fsi_dev; + struct device dev; + struct cdev cdev; + struct mutex lock; + bool broken; + bool dead; + bool async_ffdc; +}; + +struct sbefifo_user { + struct sbefifo *sbefifo; + struct mutex file_lock; + void *cmd_page; + void *pending_cmd; + size_t pending_len; +}; + +static DEFINE_MUTEX(sbefifo_ffdc_mutex); + + +static void __sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, + size_t ffdc_sz, bool internal) +{ + int pack = 0; +#define FFDC_LSIZE 60 + static char ffdc_line[FFDC_LSIZE]; + char *p = ffdc_line; + + while (ffdc_sz) { + u32 w0, w1, w2, i; + if (ffdc_sz < 3) { + dev_err(dev, "SBE invalid FFDC package size %zd\n", ffdc_sz); + return; + } + w0 = be32_to_cpu(*(ffdc++)); + w1 = be32_to_cpu(*(ffdc++)); + w2 = be32_to_cpu(*(ffdc++)); + ffdc_sz -= 3; + if ((w0 >> 16) != 0xFFDC) { + dev_err(dev, "SBE invalid FFDC package signature %08x %08x %08x\n", + w0, w1, w2); + break; + } + w0 &= 0xffff; + if (w0 > ffdc_sz) { + dev_err(dev, "SBE FFDC package len %d words but only %zd remaining\n", + w0, ffdc_sz); + w0 = ffdc_sz; + break; + } + if (internal) { + dev_warn(dev, "+---- SBE FFDC package %d for async err -----+\n", + pack++); + } else { + dev_warn(dev, "+---- SBE FFDC package %d for cmd %02x:%02x -----+\n", + pack++, (w1 >> 8) & 0xff, w1 & 0xff); + } + dev_warn(dev, "| Response code: %08x |\n", w2); + dev_warn(dev, "|-------------------------------------------|\n"); + for (i = 0; i < w0; i++) { + if ((i & 3) == 0) { + p = ffdc_line; + p += sprintf(p, "| %04x:", i << 4); + } + p += sprintf(p, " %08x", be32_to_cpu(*(ffdc++))); + ffdc_sz--; + if ((i & 3) == 3 || i == (w0 - 1)) { + while ((i & 3) < 3) { + p += sprintf(p, " "); + i++; + } + dev_warn(dev, "%s |\n", ffdc_line); + } + } + dev_warn(dev, "+-------------------------------------------+\n"); + } +} + +static void sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, + size_t ffdc_sz, bool internal) +{ + mutex_lock(&sbefifo_ffdc_mutex); + __sbefifo_dump_ffdc(dev, ffdc, ffdc_sz, internal); + mutex_unlock(&sbefifo_ffdc_mutex); +} + +int sbefifo_parse_status(struct device *dev, u16 cmd, __be32 *response, + size_t resp_len, size_t *data_len) +{ + u32 dh, s0, s1; + size_t ffdc_sz; + + if (resp_len < 3) { + pr_debug("sbefifo: cmd %04x, response too small: %zd\n", + cmd, resp_len); + return -ENXIO; + } + dh = be32_to_cpu(response[resp_len - 1]); + if (dh > resp_len || dh < 3) { + dev_err(dev, "SBE cmd %02x:%02x status offset out of range: %d/%zd\n", + cmd >> 8, cmd & 0xff, dh, resp_len); + return -ENXIO; + } + s0 = be32_to_cpu(response[resp_len - dh]); + s1 = be32_to_cpu(response[resp_len - dh + 1]); + if (((s0 >> 16) != 0xC0DE) || ((s0 & 0xffff) != cmd)) { + dev_err(dev, "SBE cmd %02x:%02x, status signature invalid: 0x%08x 0x%08x\n", + cmd >> 8, cmd & 0xff, s0, s1); + return -ENXIO; + } + if (s1 != 0) { + ffdc_sz = dh - 3; + dev_warn(dev, "SBE error cmd %02x:%02x status=%04x:%04x\n", + cmd >> 8, cmd & 0xff, s1 >> 16, s1 & 0xffff); + if (ffdc_sz) + sbefifo_dump_ffdc(dev, &response[resp_len - dh + 2], + ffdc_sz, false); + } + if (data_len) + *data_len = resp_len - dh; + + /* + * Primary status don't have the top bit set, so can't be confused with + * Linux negative error codes, so return the status word whole. + */ + return s1; +} +EXPORT_SYMBOL_GPL(sbefifo_parse_status); + +static int sbefifo_regr(struct sbefifo *sbefifo, int reg, u32 *word) +{ + __be32 raw_word; + int rc; + + rc = fsi_device_read(sbefifo->fsi_dev, reg, &raw_word, + sizeof(raw_word)); + if (rc) + return rc; + + *word = be32_to_cpu(raw_word); + + return 0; +} + +static int sbefifo_regw(struct sbefifo *sbefifo, int reg, u32 word) +{ + __be32 raw_word = cpu_to_be32(word); + + return fsi_device_write(sbefifo->fsi_dev, reg, &raw_word, + sizeof(raw_word)); +} + +static int sbefifo_check_sbe_state(struct sbefifo *sbefifo) +{ + __be32 raw_word; + u32 sbm; + int rc; + + rc = fsi_slave_read(sbefifo->fsi_dev->slave, CFAM_GP_MBOX_SBM_ADDR, + &raw_word, sizeof(raw_word)); + if (rc) + return rc; + sbm = be32_to_cpu(raw_word); + + /* SBE booted at all ? */ + if (!(sbm & CFAM_SBM_SBE_BOOTED)) + return -ESHUTDOWN; + + /* Check its state */ + switch ((sbm & CFAM_SBM_SBE_STATE_MASK) >> CFAM_SBM_SBE_STATE_SHIFT) { + case SBE_STATE_UNKNOWN: + return -ESHUTDOWN; + case SBE_STATE_IPLING: + case SBE_STATE_ISTEP: + case SBE_STATE_MPIPL: + case SBE_STATE_DMT: + return -EBUSY; + case SBE_STATE_RUNTIME: + case SBE_STATE_DUMP: /* Not sure about that one */ + break; + case SBE_STATE_FAILURE: + case SBE_STATE_QUIESCE: + return -ESHUTDOWN; + } + + /* Is there async FFDC available ? Remember it */ + if (sbm & CFAM_SBM_SBE_ASYNC_FFDC) + sbefifo->async_ffdc = true; + + return 0; +} + +/* Don't flip endianness of data to/from FIFO, just pass through. */ +static int sbefifo_down_read(struct sbefifo *sbefifo, __be32 *word) +{ + return fsi_device_read(sbefifo->fsi_dev, SBEFIFO_DOWN, word, + sizeof(*word)); +} + +static int sbefifo_up_write(struct sbefifo *sbefifo, __be32 word) +{ + return fsi_device_write(sbefifo->fsi_dev, SBEFIFO_UP, &word, + sizeof(word)); +} + +static int sbefifo_request_reset(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 status, timeout; + int rc; + + dev_dbg(dev, "Requesting FIFO reset\n"); + + /* Mark broken first, will be cleared if reset succeeds */ + sbefifo->broken = true; + + /* Send reset request */ + rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_REQ_RESET, 1); + if (rc) { + dev_err(dev, "Sending reset request failed, rc=%d\n", rc); + return rc; + } + + /* Wait for it to complete */ + for (timeout = 0; timeout < SBEFIFO_RESET_TIMEOUT; timeout++) { + rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &status); + if (rc) { + dev_err(dev, "Failed to read UP fifo status during reset" + " , rc=%d\n", rc); + return rc; + } + + if (!(status & SBEFIFO_STS_RESET_REQ)) { + dev_dbg(dev, "FIFO reset done\n"); + sbefifo->broken = false; + return 0; + } + + msleep(1); + } + dev_err(dev, "FIFO reset timed out\n"); + + return -ETIMEDOUT; +} + +static int sbefifo_cleanup_hw(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 up_status, down_status; + bool need_reset = false; + int rc; + + rc = sbefifo_check_sbe_state(sbefifo); + if (rc) { + dev_dbg(dev, "SBE state=%d\n", rc); + return rc; + } + + /* If broken, we don't need to look at status, go straight to reset */ + if (sbefifo->broken) + goto do_reset; + + rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &up_status); + if (rc) { + dev_err(dev, "Cleanup: Reading UP status failed, rc=%d\n", rc); + + /* Will try reset again on next attempt at using it */ + sbefifo->broken = true; + return rc; + } + + rc = sbefifo_regr(sbefifo, SBEFIFO_DOWN | SBEFIFO_STS, &down_status); + if (rc) { + dev_err(dev, "Cleanup: Reading DOWN status failed, rc=%d\n", rc); + + /* Will try reset again on next attempt at using it */ + sbefifo->broken = true; + return rc; + } + + /* The FIFO already contains a reset request from the SBE ? */ + if (down_status & SBEFIFO_STS_RESET_REQ) { + dev_info(dev, "Cleanup: FIFO reset request set, resetting\n"); + rc = sbefifo_regw(sbefifo, SBEFIFO_UP, SBEFIFO_PERFORM_RESET); + if (rc) { + sbefifo->broken = true; + dev_err(dev, "Cleanup: Reset reg write failed, rc=%d\n", rc); + return rc; + } + sbefifo->broken = false; + return 0; + } + + /* Parity error on either FIFO ? */ + if ((up_status | down_status) & SBEFIFO_STS_PARITY_ERR) + need_reset = true; + + /* Either FIFO not empty ? */ + if (!((up_status & down_status) & SBEFIFO_STS_EMPTY)) + need_reset = true; + + if (!need_reset) + return 0; + + dev_info(dev, "Cleanup: FIFO not clean (up=0x%08x down=0x%08x)\n", + up_status, down_status); + + do_reset: + + /* Mark broken, will be cleared if/when reset succeeds */ + return sbefifo_request_reset(sbefifo); +} + +static int sbefifo_wait(struct sbefifo *sbefifo, bool up, + u32 *status, unsigned long timeout) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + unsigned long end_time; + bool ready = false; + u32 addr, sts = 0; + int rc; + + dev_vdbg(dev, "Wait on %s fifo...\n", up ? "up" : "down"); + + addr = (up ? SBEFIFO_UP : SBEFIFO_DOWN) | SBEFIFO_STS; + + end_time = jiffies + timeout; + while (!time_after(jiffies, end_time)) { + cond_resched(); + rc = sbefifo_regr(sbefifo, addr, &sts); + if (rc < 0) { + dev_err(dev, "FSI error %d reading status register\n", rc); + return rc; + } + if (!up && sbefifo_parity_err(sts)) { + dev_err(dev, "Parity error in DOWN FIFO\n"); + return -ENXIO; + } + ready = !(up ? sbefifo_full(sts) : sbefifo_empty(sts)); + if (ready) + break; + } + if (!ready) { + dev_err(dev, "%s FIFO Timeout ! status=%08x\n", up ? "UP" : "DOWN", sts); + return -ETIMEDOUT; + } + dev_vdbg(dev, "End of wait status: %08x\n", sts); + + *status = sts; + + return 0; +} + +static int sbefifo_send_command(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + size_t len, chunk, vacant = 0, remaining = cmd_len; + unsigned long timeout; + u32 status; + int rc; + + dev_vdbg(dev, "sending command (%zd words, cmd=%04x)\n", + cmd_len, be32_to_cpu(command[1])); + + /* As long as there's something to send */ + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_CMD); + while (remaining) { + /* Wait for room in the FIFO */ + rc = sbefifo_wait(sbefifo, true, &status, timeout); + if (rc < 0) + return rc; + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_CMD); + + vacant = sbefifo_vacant(status); + len = chunk = min(vacant, remaining); + + dev_vdbg(dev, " status=%08x vacant=%zd chunk=%zd\n", + status, vacant, chunk); + + /* Write as much as we can */ + while (len--) { + rc = sbefifo_up_write(sbefifo, *(command++)); + if (rc) { + dev_err(dev, "FSI error %d writing UP FIFO\n", rc); + return rc; + } + } + remaining -= chunk; + vacant -= chunk; + } + + /* If there's no room left, wait for some to write EOT */ + if (!vacant) { + rc = sbefifo_wait(sbefifo, true, &status, timeout); + if (rc) + return rc; + } + + /* Send an EOT */ + rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_EOT_RAISE, 0); + if (rc) + dev_err(dev, "FSI error %d writing EOT\n", rc); + return rc; +} + +static int sbefifo_read_response(struct sbefifo *sbefifo, struct iov_iter *response) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 status, eot_set; + unsigned long timeout; + bool overflow = false; + __be32 data; + size_t len; + int rc; + + dev_vdbg(dev, "reading response, buflen = %zd\n", iov_iter_count(response)); + + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_RSP); + for (;;) { + /* Grab FIFO status (this will handle parity errors) */ + rc = sbefifo_wait(sbefifo, false, &status, timeout); + if (rc < 0) + return rc; + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_RSP); + + /* Decode status */ + len = sbefifo_populated(status); + eot_set = sbefifo_eot_set(status); + + dev_vdbg(dev, " chunk size %zd eot_set=0x%x\n", len, eot_set); + + /* Go through the chunk */ + while(len--) { + /* Read the data */ + rc = sbefifo_down_read(sbefifo, &data); + if (rc < 0) + return rc; + + /* Was it an EOT ? */ + if (eot_set & 0x80) { + /* + * There should be nothing else in the FIFO, + * if there is, mark broken, this will force + * a reset on next use, but don't fail the + * command. + */ + if (len) { + dev_warn(dev, "FIFO read hit" + " EOT with still %zd data\n", + len); + sbefifo->broken = true; + } + + /* We are done */ + rc = sbefifo_regw(sbefifo, + SBEFIFO_DOWN | SBEFIFO_EOT_ACK, 0); + + /* + * If that write fail, still complete the request but mark + * the fifo as broken for subsequent reset (not much else + * we can do here). + */ + if (rc) { + dev_err(dev, "FSI error %d ack'ing EOT\n", rc); + sbefifo->broken = true; + } + + /* Tell whether we overflowed */ + return overflow ? -EOVERFLOW : 0; + } + + /* Store it if there is room */ + if (iov_iter_count(response) >= sizeof(__be32)) { + if (copy_to_iter(&data, sizeof(__be32), response) < sizeof(__be32)) + return -EFAULT; + } else { + dev_vdbg(dev, "Response overflowed !\n"); + + overflow = true; + } + + /* Next EOT bit */ + eot_set <<= 1; + } + } + /* Shouldn't happen */ + return -EIO; +} + +static int sbefifo_do_command(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len, + struct iov_iter *response) +{ + /* Try sending the command */ + int rc = sbefifo_send_command(sbefifo, command, cmd_len); + if (rc) + return rc; + + /* Now, get the response */ + return sbefifo_read_response(sbefifo, response); +} + +static void sbefifo_collect_async_ffdc(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + struct iov_iter ffdc_iter; + struct kvec ffdc_iov; + __be32 *ffdc; + size_t ffdc_sz; + __be32 cmd[2]; + int rc; + + sbefifo->async_ffdc = false; + ffdc = vmalloc(SBEFIFO_MAX_FFDC_SIZE); + if (!ffdc) { + dev_err(dev, "Failed to allocate SBE FFDC buffer\n"); + return; + } + ffdc_iov.iov_base = ffdc; + ffdc_iov.iov_len = SBEFIFO_MAX_FFDC_SIZE; + iov_iter_kvec(&ffdc_iter, WRITE | ITER_KVEC, &ffdc_iov, 1, SBEFIFO_MAX_FFDC_SIZE); + cmd[0] = cpu_to_be32(2); + cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_SBE_FFDC); + rc = sbefifo_do_command(sbefifo, cmd, 2, &ffdc_iter); + if (rc != 0) { + dev_err(dev, "Error %d retrieving SBE FFDC\n", rc); + goto bail; + } + ffdc_sz = SBEFIFO_MAX_FFDC_SIZE - iov_iter_count(&ffdc_iter); + ffdc_sz /= sizeof(__be32); + rc = sbefifo_parse_status(dev, SBEFIFO_CMD_GET_SBE_FFDC, ffdc, + ffdc_sz, &ffdc_sz); + if (rc != 0) { + dev_err(dev, "Error %d decoding SBE FFDC\n", rc); + goto bail; + } + if (ffdc_sz > 0) + sbefifo_dump_ffdc(dev, ffdc, ffdc_sz, true); + bail: + vfree(ffdc); + +} + +static int __sbefifo_submit(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len, + struct iov_iter *response) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + int rc; + + if (sbefifo->dead) + return -ENODEV; + + if (cmd_len < 2 || be32_to_cpu(command[0]) != cmd_len) { + dev_vdbg(dev, "Invalid command len %zd (header: %d)\n", + cmd_len, be32_to_cpu(command[0])); + return -EINVAL; + } + + /* First ensure the HW is in a clean state */ + rc = sbefifo_cleanup_hw(sbefifo); + if (rc) + return rc; + + /* Look for async FFDC first if any */ + if (sbefifo->async_ffdc) + sbefifo_collect_async_ffdc(sbefifo); + + rc = sbefifo_do_command(sbefifo, command, cmd_len, response); + if (rc != 0 && rc != -EOVERFLOW) + goto fail; + return rc; + fail: + /* + * On failure, attempt a reset. Ignore the result, it will mark + * the fifo broken if the reset fails + */ + sbefifo_request_reset(sbefifo); + + /* Return original error */ + return rc; +} + +/** + * sbefifo_submit() - Submit and SBE fifo command and receive response + * @dev: The sbefifo device + * @command: The raw command data + * @cmd_len: The command size (in 32-bit words) + * @response: The output response buffer + * @resp_len: In: Response buffer size, Out: Response size + * + * This will perform the entire operation. If the reponse buffer + * overflows, returns -EOVERFLOW + */ +int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len, + __be32 *response, size_t *resp_len) +{ + struct sbefifo *sbefifo; + struct iov_iter resp_iter; + struct kvec resp_iov; + size_t rbytes; + int rc; + + if (!dev) + return -ENODEV; + sbefifo = dev_get_drvdata(dev); + if (!sbefifo) + return -ENODEV; + if (WARN_ON_ONCE(sbefifo->magic != SBEFIFO_MAGIC)) + return -ENODEV; + if (!resp_len || !command || !response) + return -EINVAL; + + /* Prepare iov iterator */ + rbytes = (*resp_len) * sizeof(__be32); + resp_iov.iov_base = response; + resp_iov.iov_len = rbytes; + iov_iter_kvec(&resp_iter, WRITE | ITER_KVEC, &resp_iov, 1, rbytes); + + /* Perform the command */ + mutex_lock(&sbefifo->lock); + rc = __sbefifo_submit(sbefifo, command, cmd_len, &resp_iter); + mutex_unlock(&sbefifo->lock); + + /* Extract the response length */ + rbytes -= iov_iter_count(&resp_iter); + *resp_len = rbytes / sizeof(__be32); + + return rc; +} +EXPORT_SYMBOL_GPL(sbefifo_submit); + +/* + * Char device interface + */ + +static void sbefifo_release_command(struct sbefifo_user *user) +{ + if (is_vmalloc_addr(user->pending_cmd)) + vfree(user->pending_cmd); + user->pending_cmd = NULL; + user->pending_len = 0; +} + +static int sbefifo_user_open(struct inode *inode, struct file *file) +{ + struct sbefifo *sbefifo = container_of(inode->i_cdev, struct sbefifo, cdev); + struct sbefifo_user *user; + + user = kzalloc(sizeof(struct sbefifo_user), GFP_KERNEL); + if (!user) + return -ENOMEM; + + file->private_data = user; + user->sbefifo = sbefifo; + user->cmd_page = (void *)__get_free_page(GFP_KERNEL); + if (!user->cmd_page) { + kfree(user); + return -ENOMEM; + } + mutex_init(&user->file_lock); + + return 0; +} + +static ssize_t sbefifo_user_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + struct sbefifo_user *user = file->private_data; + struct sbefifo *sbefifo; + struct iov_iter resp_iter; + struct iovec resp_iov; + size_t cmd_len; + int rc; + + if (!user) + return -EINVAL; + sbefifo = user->sbefifo; + if (len & 3) + return -EINVAL; + + mutex_lock(&user->file_lock); + + /* Cronus relies on -EAGAIN after a short read */ + if (user->pending_len == 0) { + rc = -EAGAIN; + goto bail; + } + if (user->pending_len < 8) { + rc = -EINVAL; + goto bail; + } + cmd_len = user->pending_len >> 2; + + /* Prepare iov iterator */ + resp_iov.iov_base = buf; + resp_iov.iov_len = len; + iov_iter_init(&resp_iter, WRITE, &resp_iov, 1, len); + + /* Perform the command */ + mutex_lock(&sbefifo->lock); + rc = __sbefifo_submit(sbefifo, user->pending_cmd, cmd_len, &resp_iter); + mutex_unlock(&sbefifo->lock); + if (rc < 0) + goto bail; + + /* Extract the response length */ + rc = len - iov_iter_count(&resp_iter); + bail: + sbefifo_release_command(user); + mutex_unlock(&user->file_lock); + return rc; +} + +static ssize_t sbefifo_user_write(struct file *file, const char __user *buf, + size_t len, loff_t *offset) +{ + struct sbefifo_user *user = file->private_data; + struct sbefifo *sbefifo; + int rc = len; + + if (!user) + return -EINVAL; + sbefifo = user->sbefifo; + if (len > SBEFIFO_MAX_USER_CMD_LEN) + return -EINVAL; + if (len & 3) + return -EINVAL; + + mutex_lock(&user->file_lock); + + /* Can we use the pre-allocate buffer ? If not, allocate */ + if (len <= PAGE_SIZE) + user->pending_cmd = user->cmd_page; + else + user->pending_cmd = vmalloc(len); + if (!user->pending_cmd) { + rc = -ENOMEM; + goto bail; + } + + /* Copy the command into the staging buffer */ + if (copy_from_user(user->pending_cmd, buf, len)) { + rc = -EFAULT; + goto bail; + } + + /* Check for the magic reset command */ + if (len == 4 && be32_to_cpu(*(__be32 *)user->pending_cmd) == + SBEFIFO_RESET_MAGIC) { + + /* Clear out any pending command */ + user->pending_len = 0; + + /* Trigger reset request */ + mutex_lock(&sbefifo->lock); + rc = sbefifo_request_reset(user->sbefifo); + mutex_unlock(&sbefifo->lock); + if (rc == 0) + rc = 4; + goto bail; + } + + /* Update the staging buffer size */ + user->pending_len = len; + bail: + if (!user->pending_len) + sbefifo_release_command(user); + + mutex_unlock(&user->file_lock); + + /* And that's it, we'll issue the command on a read */ + return rc; +} + +static int sbefifo_user_release(struct inode *inode, struct file *file) +{ + struct sbefifo_user *user = file->private_data; + + if (!user) + return -EINVAL; + + sbefifo_release_command(user); + free_page((unsigned long)user->cmd_page); + kfree(user); + + return 0; +} + +static const struct file_operations sbefifo_fops = { + .owner = THIS_MODULE, + .open = sbefifo_user_open, + .read = sbefifo_user_read, + .write = sbefifo_user_write, + .release = sbefifo_user_release, +}; + +struct fsi_device *sbefifo_get_fsidev(struct device *dev) +{ + struct sbefifo *sbefifo = dev_get_drvdata(dev); + + return sbefifo->fsi_dev; +} +EXPORT_SYMBOL_GPL(sbefifo_get_fsidev); + +static void sbefifo_free(struct device *dev) +{ + struct sbefifo *sbefifo = container_of(dev, struct sbefifo, dev); + + put_device(&sbefifo->fsi_dev->dev); + kfree(sbefifo); +} + +/* + * Probe/remove + */ + +static int sbefifo_probe(struct device *dev) +{ + struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct sbefifo *sbefifo; + struct device_node *np; + struct platform_device *child; + char child_name[32]; + int rc, didx, child_idx = 0; + + dev_dbg(dev, "Found sbefifo device\n"); + + sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL); + if (!sbefifo) + return -ENOMEM; + + /* Grab a reference to the device (parent of our cdev), we'll drop it later */ + if (!get_device(dev)) { + kfree(sbefifo); + return -ENODEV; + } + + sbefifo->magic = SBEFIFO_MAGIC; + sbefifo->fsi_dev = fsi_dev; + dev_set_drvdata(dev, sbefifo); + mutex_init(&sbefifo->lock); + + /* + * Try cleaning up the FIFO. If this fails, we still register the + * driver and will try cleaning things up again on the next access. + */ + rc = sbefifo_cleanup_hw(sbefifo); + if (rc && rc != -ESHUTDOWN) + dev_err(dev, "Initial HW cleanup failed, will retry later\n"); + + /* Create chardev for userspace access */ + sbefifo->dev.type = &fsi_cdev_type; + sbefifo->dev.parent = dev; + sbefifo->dev.release = sbefifo_free; + device_initialize(&sbefifo->dev); + + /* Allocate a minor in the FSI space */ + rc = fsi_get_new_minor(fsi_dev, fsi_dev_sbefifo, &sbefifo->dev.devt, &didx); + if (rc) + goto err; + + dev_set_name(&sbefifo->dev, "sbefifo%d", didx); + cdev_init(&sbefifo->cdev, &sbefifo_fops); + rc = cdev_device_add(&sbefifo->cdev, &sbefifo->dev); + if (rc) { + dev_err(dev, "Error %d creating char device %s\n", + rc, dev_name(&sbefifo->dev)); + goto err_free_minor; + } + + /* Create platform devs for dts child nodes (occ, etc) */ + for_each_available_child_of_node(dev->of_node, np) { + snprintf(child_name, sizeof(child_name), "%s-dev%d", + dev_name(&sbefifo->dev), child_idx++); + child = of_platform_device_create(np, child_name, dev); + if (!child) + dev_warn(dev, "failed to create child %s dev\n", + child_name); + } + + return 0; + err_free_minor: + fsi_free_minor(sbefifo->dev.devt); + err: + put_device(&sbefifo->dev); + return rc; +} + +static int sbefifo_unregister_child(struct device *dev, void *data) +{ + struct platform_device *child = to_platform_device(dev); + + of_device_unregister(child); + if (dev->of_node) + of_node_clear_flag(dev->of_node, OF_POPULATED); + + return 0; +} + +static int sbefifo_remove(struct device *dev) +{ + struct sbefifo *sbefifo = dev_get_drvdata(dev); + + dev_dbg(dev, "Removing sbefifo device...\n"); + + mutex_lock(&sbefifo->lock); + sbefifo->dead = true; + mutex_unlock(&sbefifo->lock); + + cdev_device_del(&sbefifo->cdev, &sbefifo->dev); + fsi_free_minor(sbefifo->dev.devt); + device_for_each_child(dev, NULL, sbefifo_unregister_child); + put_device(&sbefifo->dev); + + return 0; +} + +static struct fsi_device_id sbefifo_ids[] = { + { + .engine_type = FSI_ENGID_SBE, + .version = FSI_VERSION_ANY, + }, + { 0 } +}; + +static struct fsi_driver sbefifo_drv = { + .id_table = sbefifo_ids, + .drv = { + .name = DEVICE_NAME, + .bus = &fsi_bus_type, + .probe = sbefifo_probe, + .remove = sbefifo_remove, + } +}; + +static int sbefifo_init(void) +{ + return fsi_driver_register(&sbefifo_drv); +} + +static void sbefifo_exit(void) +{ + fsi_driver_unregister(&sbefifo_drv); +} + +module_init(sbefifo_init); +module_exit(sbefifo_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Brad Bishop <bradleyb@fuzziesquirrel.com>"); +MODULE_AUTHOR("Eddie James <eajames@linux.vnet.ibm.com>"); +MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Linux device interface to the POWER Self Boot Engine");
diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index e13353a..df94021 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c
@@ -20,42 +20,73 @@ #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/slab.h> -#include <linux/miscdevice.h> +#include <linux/cdev.h> #include <linux/list.h> -#include <linux/idr.h> + +#include <uapi/linux/fsi.h> #define FSI_ENGID_SCOM 0x5 -#define SCOM_FSI2PIB_DELAY 50 - /* SCOM engine register set */ #define SCOM_DATA0_REG 0x00 #define SCOM_DATA1_REG 0x04 #define SCOM_CMD_REG 0x08 -#define SCOM_RESET_REG 0x1C +#define SCOM_FSI2PIB_RESET_REG 0x18 +#define SCOM_STATUS_REG 0x1C /* Read */ +#define SCOM_PIB_RESET_REG 0x1C /* Write */ -#define SCOM_RESET_CMD 0x80000000 +/* Command register */ #define SCOM_WRITE_CMD 0x80000000 +#define SCOM_READ_CMD 0x00000000 + +/* Status register bits */ +#define SCOM_STATUS_ERR_SUMMARY 0x80000000 +#define SCOM_STATUS_PROTECTION 0x01000000 +#define SCOM_STATUS_PARITY 0x04000000 +#define SCOM_STATUS_PIB_ABORT 0x00100000 +#define SCOM_STATUS_PIB_RESP_MASK 0x00007000 +#define SCOM_STATUS_PIB_RESP_SHIFT 12 + +#define SCOM_STATUS_ANY_ERR (SCOM_STATUS_ERR_SUMMARY | \ + SCOM_STATUS_PROTECTION | \ + SCOM_STATUS_PARITY | \ + SCOM_STATUS_PIB_ABORT | \ + SCOM_STATUS_PIB_RESP_MASK) +/* SCOM address encodings */ +#define XSCOM_ADDR_IND_FLAG BIT_ULL(63) +#define XSCOM_ADDR_INF_FORM1 BIT_ULL(60) + +/* SCOM indirect stuff */ +#define XSCOM_ADDR_DIRECT_PART 0x7fffffffull +#define XSCOM_ADDR_INDIRECT_PART 0x000fffff00000000ull +#define XSCOM_DATA_IND_READ BIT_ULL(63) +#define XSCOM_DATA_IND_COMPLETE BIT_ULL(31) +#define XSCOM_DATA_IND_ERR_MASK 0x70000000ull +#define XSCOM_DATA_IND_ERR_SHIFT 28 +#define XSCOM_DATA_IND_DATA 0x0000ffffull +#define XSCOM_DATA_IND_FORM1_DATA 0x000fffffffffffffull +#define XSCOM_ADDR_FORM1_LOW 0x000ffffffffull +#define XSCOM_ADDR_FORM1_HI 0xfff00000000ull +#define XSCOM_ADDR_FORM1_HI_SHIFT 20 + +/* Retries */ +#define SCOM_MAX_RETRIES 100 /* Retries on busy */ +#define SCOM_MAX_IND_RETRIES 10 /* Retries indirect not ready */ struct scom_device { struct list_head link; struct fsi_device *fsi_dev; - struct miscdevice mdev; - char name[32]; - int idx; + struct device dev; + struct cdev cdev; + struct mutex lock; + bool dead; }; -#define to_scom_dev(x) container_of((x), struct scom_device, mdev) - -static struct list_head scom_devices; - -static DEFINE_IDA(scom_ida); - -static int put_scom(struct scom_device *scom_dev, uint64_t value, - uint32_t addr) +static int __put_scom(struct scom_device *scom_dev, uint64_t value, + uint32_t addr, uint32_t *status) { + __be32 data, raw_status; int rc; - uint32_t data; data = cpu_to_be32((value >> 32) & 0xffffffff); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, @@ -70,53 +101,286 @@ static int put_scom(struct scom_device *scom_dev, uint64_t value, return rc; data = cpu_to_be32(SCOM_WRITE_CMD | addr); - return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, - sizeof(uint32_t)); -} - -static int get_scom(struct scom_device *scom_dev, uint64_t *value, - uint32_t addr) -{ - uint32_t result, data; - int rc; - - *value = 0ULL; - data = cpu_to_be32(addr); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, sizeof(uint32_t)); if (rc) return rc; - - rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, - sizeof(uint32_t)); + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status, + sizeof(uint32_t)); if (rc) return rc; - - *value |= (uint64_t)cpu_to_be32(result) << 32; - rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, - sizeof(uint32_t)); - if (rc) - return rc; - - *value |= cpu_to_be32(result); + *status = be32_to_cpu(raw_status); return 0; } -static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, - loff_t *offset) +static int __get_scom(struct scom_device *scom_dev, uint64_t *value, + uint32_t addr, uint32_t *status) { + __be32 data, raw_status; int rc; - struct miscdevice *mdev = - (struct miscdevice *)filep->private_data; - struct scom_device *scom = to_scom_dev(mdev); + + + *value = 0ULL; + data = cpu_to_be32(SCOM_READ_CMD | addr); + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, + sizeof(uint32_t)); + if (rc) + return rc; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status, + sizeof(uint32_t)); + if (rc) + return rc; + + /* + * Read the data registers even on error, so we don't have + * to interpret the status register here. + */ + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, + sizeof(uint32_t)); + if (rc) + return rc; + *value |= (uint64_t)be32_to_cpu(data) << 32; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, + sizeof(uint32_t)); + if (rc) + return rc; + *value |= be32_to_cpu(data); + *status = be32_to_cpu(raw_status); + + return rc; +} + +static int put_indirect_scom_form0(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + int rc, retries, err = 0; + + if (value & ~XSCOM_DATA_IND_DATA) + return -EINVAL; + + ind_addr = addr & XSCOM_ADDR_DIRECT_PART; + ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | value; + rc = __put_scom(scom, ind_data, ind_addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + for (retries = 0; retries < SCOM_MAX_IND_RETRIES; retries++) { + rc = __get_scom(scom, &ind_data, addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT; + *status = err << SCOM_STATUS_PIB_RESP_SHIFT; + if ((ind_data & XSCOM_DATA_IND_COMPLETE) || (err != SCOM_PIB_BLOCKED)) + return 0; + + msleep(1); + } + return rc; +} + +static int put_indirect_scom_form1(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + + if (value & ~XSCOM_DATA_IND_FORM1_DATA) + return -EINVAL; + + ind_addr = addr & XSCOM_ADDR_FORM1_LOW; + ind_data = value | (addr & XSCOM_ADDR_FORM1_HI) << XSCOM_ADDR_FORM1_HI_SHIFT; + return __put_scom(scom, ind_data, ind_addr, status); +} + +static int get_indirect_scom_form0(struct scom_device *scom, uint64_t *value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + int rc, retries, err = 0; + + ind_addr = addr & XSCOM_ADDR_DIRECT_PART; + ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | XSCOM_DATA_IND_READ; + rc = __put_scom(scom, ind_data, ind_addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + for (retries = 0; retries < SCOM_MAX_IND_RETRIES; retries++) { + rc = __get_scom(scom, &ind_data, addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT; + *status = err << SCOM_STATUS_PIB_RESP_SHIFT; + *value = ind_data & XSCOM_DATA_IND_DATA; + + if ((ind_data & XSCOM_DATA_IND_COMPLETE) || (err != SCOM_PIB_BLOCKED)) + return 0; + + msleep(1); + } + return rc; +} + +static int raw_put_scom(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + if (addr & XSCOM_ADDR_IND_FLAG) { + if (addr & XSCOM_ADDR_INF_FORM1) + return put_indirect_scom_form1(scom, value, addr, status); + else + return put_indirect_scom_form0(scom, value, addr, status); + } else + return __put_scom(scom, value, addr, status); +} + +static int raw_get_scom(struct scom_device *scom, uint64_t *value, + uint64_t addr, uint32_t *status) +{ + if (addr & XSCOM_ADDR_IND_FLAG) { + if (addr & XSCOM_ADDR_INF_FORM1) + return -ENXIO; + return get_indirect_scom_form0(scom, value, addr, status); + } else + return __get_scom(scom, value, addr, status); +} + +static int handle_fsi2pib_status(struct scom_device *scom, uint32_t status) +{ + uint32_t dummy = -1; + + if (status & SCOM_STATUS_PROTECTION) + return -EPERM; + if (status & SCOM_STATUS_PARITY) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return -EIO; + } + /* Return -EBUSY on PIB abort to force a retry */ + if (status & SCOM_STATUS_PIB_ABORT) + return -EBUSY; + if (status & SCOM_STATUS_ERR_SUMMARY) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return -EIO; + } + return 0; +} + +static int handle_pib_status(struct scom_device *scom, uint8_t status) +{ + uint32_t dummy = -1; + + if (status == SCOM_PIB_SUCCESS) + return 0; + if (status == SCOM_PIB_BLOCKED) + return -EBUSY; + + /* Reset the bridge */ + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + + switch(status) { + case SCOM_PIB_OFFLINE: + return -ENODEV; + case SCOM_PIB_BAD_ADDR: + return -ENXIO; + case SCOM_PIB_TIMEOUT: + return -ETIMEDOUT; + case SCOM_PIB_PARTIAL: + case SCOM_PIB_CLK_ERR: + case SCOM_PIB_PARITY_ERR: + default: + return -EIO; + } +} + +static int put_scom(struct scom_device *scom, uint64_t value, + uint64_t addr) +{ + uint32_t status, dummy = -1; + int rc, retries; + + for (retries = 0; retries < SCOM_MAX_RETRIES; retries++) { + rc = raw_put_scom(scom, value, addr, &status); + if (rc) { + /* Try resetting the bridge if FSI fails */ + if (rc != -ENODEV && retries == 0) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, + &dummy, sizeof(uint32_t)); + rc = -EBUSY; + } else + return rc; + } else + rc = handle_fsi2pib_status(scom, status); + if (rc && rc != -EBUSY) + break; + if (rc == 0) { + rc = handle_pib_status(scom, + (status & SCOM_STATUS_PIB_RESP_MASK) + >> SCOM_STATUS_PIB_RESP_SHIFT); + if (rc && rc != -EBUSY) + break; + } + if (rc == 0) + break; + msleep(1); + } + return rc; +} + +static int get_scom(struct scom_device *scom, uint64_t *value, + uint64_t addr) +{ + uint32_t status, dummy = -1; + int rc, retries; + + for (retries = 0; retries < SCOM_MAX_RETRIES; retries++) { + rc = raw_get_scom(scom, value, addr, &status); + if (rc) { + /* Try resetting the bridge if FSI fails */ + if (rc != -ENODEV && retries == 0) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, + &dummy, sizeof(uint32_t)); + rc = -EBUSY; + } else + return rc; + } else + rc = handle_fsi2pib_status(scom, status); + if (rc && rc != -EBUSY) + break; + if (rc == 0) { + rc = handle_pib_status(scom, + (status & SCOM_STATUS_PIB_RESP_MASK) + >> SCOM_STATUS_PIB_RESP_SHIFT); + if (rc && rc != -EBUSY) + break; + } + if (rc == 0) + break; + msleep(1); + } + return rc; +} + +static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, + loff_t *offset) +{ + struct scom_device *scom = filep->private_data; struct device *dev = &scom->fsi_dev->dev; uint64_t val; + int rc; if (len != sizeof(uint64_t)) return -EINVAL; - rc = get_scom(scom, &val, *offset); + mutex_lock(&scom->lock); + if (scom->dead) + rc = -ENODEV; + else + rc = get_scom(scom, &val, *offset); + mutex_unlock(&scom->lock); if (rc) { dev_dbg(dev, "get_scom fail:%d\n", rc); return rc; @@ -130,11 +394,10 @@ static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, } static ssize_t scom_write(struct file *filep, const char __user *buf, - size_t len, loff_t *offset) + size_t len, loff_t *offset) { int rc; - struct miscdevice *mdev = filep->private_data; - struct scom_device *scom = to_scom_dev(mdev); + struct scom_device *scom = filep->private_data; struct device *dev = &scom->fsi_dev->dev; uint64_t val; @@ -147,7 +410,12 @@ static ssize_t scom_write(struct file *filep, const char __user *buf, return -EINVAL; } - rc = put_scom(scom, val, *offset); + mutex_lock(&scom->lock); + if (scom->dead) + rc = -ENODEV; + else + rc = put_scom(scom, val, *offset); + mutex_unlock(&scom->lock); if (rc) { dev_dbg(dev, "put_scom failed with:%d\n", rc); return rc; @@ -171,50 +439,205 @@ static loff_t scom_llseek(struct file *file, loff_t offset, int whence) return offset; } +static void raw_convert_status(struct scom_access *acc, uint32_t status) +{ + acc->pib_status = (status & SCOM_STATUS_PIB_RESP_MASK) >> + SCOM_STATUS_PIB_RESP_SHIFT; + acc->intf_errors = 0; + + if (status & SCOM_STATUS_PROTECTION) + acc->intf_errors |= SCOM_INTF_ERR_PROTECTION; + else if (status & SCOM_STATUS_PARITY) + acc->intf_errors |= SCOM_INTF_ERR_PARITY; + else if (status & SCOM_STATUS_PIB_ABORT) + acc->intf_errors |= SCOM_INTF_ERR_ABORT; + else if (status & SCOM_STATUS_ERR_SUMMARY) + acc->intf_errors |= SCOM_INTF_ERR_UNKNOWN; +} + +static int scom_raw_read(struct scom_device *scom, void __user *argp) +{ + struct scom_access acc; + uint32_t status; + int rc; + + if (copy_from_user(&acc, argp, sizeof(struct scom_access))) + return -EFAULT; + + rc = raw_get_scom(scom, &acc.data, acc.addr, &status); + if (rc) + return rc; + raw_convert_status(&acc, status); + if (copy_to_user(argp, &acc, sizeof(struct scom_access))) + return -EFAULT; + return 0; +} + +static int scom_raw_write(struct scom_device *scom, void __user *argp) +{ + u64 prev_data, mask, data; + struct scom_access acc; + uint32_t status; + int rc; + + if (copy_from_user(&acc, argp, sizeof(struct scom_access))) + return -EFAULT; + + if (acc.mask) { + rc = raw_get_scom(scom, &prev_data, acc.addr, &status); + if (rc) + return rc; + if (status & SCOM_STATUS_ANY_ERR) + goto fail; + mask = acc.mask; + } else { + prev_data = mask = -1ull; + } + data = (prev_data & ~mask) | (acc.data & mask); + rc = raw_put_scom(scom, data, acc.addr, &status); + if (rc) + return rc; + fail: + raw_convert_status(&acc, status); + if (copy_to_user(argp, &acc, sizeof(struct scom_access))) + return -EFAULT; + return 0; +} + +static int scom_reset(struct scom_device *scom, void __user *argp) +{ + uint32_t flags, dummy = -1; + int rc = 0; + + if (get_user(flags, (__u32 __user *)argp)) + return -EFAULT; + if (flags & SCOM_RESET_PIB) + rc = fsi_device_write(scom->fsi_dev, SCOM_PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + if (!rc && (flags & (SCOM_RESET_PIB | SCOM_RESET_INTF))) + rc = fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return rc; +} + +static int scom_check(struct scom_device *scom, void __user *argp) +{ + /* Still need to find out how to get "protected" */ + return put_user(SCOM_CHECK_SUPPORTED, (__u32 __user *)argp); +} + +static long scom_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct scom_device *scom = file->private_data; + void __user *argp = (void __user *)arg; + int rc = -ENOTTY; + + mutex_lock(&scom->lock); + if (scom->dead) { + mutex_unlock(&scom->lock); + return -ENODEV; + } + switch(cmd) { + case FSI_SCOM_CHECK: + rc = scom_check(scom, argp); + break; + case FSI_SCOM_READ: + rc = scom_raw_read(scom, argp); + break; + case FSI_SCOM_WRITE: + rc = scom_raw_write(scom, argp); + break; + case FSI_SCOM_RESET: + rc = scom_reset(scom, argp); + break; + } + mutex_unlock(&scom->lock); + return rc; +} + +static int scom_open(struct inode *inode, struct file *file) +{ + struct scom_device *scom = container_of(inode->i_cdev, struct scom_device, cdev); + + file->private_data = scom; + + return 0; +} + static const struct file_operations scom_fops = { - .owner = THIS_MODULE, - .llseek = scom_llseek, - .read = scom_read, - .write = scom_write, + .owner = THIS_MODULE, + .open = scom_open, + .llseek = scom_llseek, + .read = scom_read, + .write = scom_write, + .unlocked_ioctl = scom_ioctl, }; +static void scom_free(struct device *dev) +{ + struct scom_device *scom = container_of(dev, struct scom_device, dev); + + put_device(&scom->fsi_dev->dev); + kfree(scom); +} + static int scom_probe(struct device *dev) { - uint32_t data; struct fsi_device *fsi_dev = to_fsi_dev(dev); struct scom_device *scom; + int rc, didx; - scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); + scom = kzalloc(sizeof(*scom), GFP_KERNEL); if (!scom) return -ENOMEM; + dev_set_drvdata(dev, scom); + mutex_init(&scom->lock); - scom->idx = ida_simple_get(&scom_ida, 1, INT_MAX, GFP_KERNEL); - snprintf(scom->name, sizeof(scom->name), "scom%d", scom->idx); + /* Grab a reference to the device (parent of our cdev), we'll drop it later */ + if (!get_device(dev)) { + kfree(scom); + return -ENODEV; + } scom->fsi_dev = fsi_dev; - scom->mdev.minor = MISC_DYNAMIC_MINOR; - scom->mdev.fops = &scom_fops; - scom->mdev.name = scom->name; - scom->mdev.parent = dev; - list_add(&scom->link, &scom_devices); - data = cpu_to_be32(SCOM_RESET_CMD); - fsi_device_write(fsi_dev, SCOM_RESET_REG, &data, sizeof(uint32_t)); + /* Create chardev for userspace access */ + scom->dev.type = &fsi_cdev_type; + scom->dev.parent = dev; + scom->dev.release = scom_free; + device_initialize(&scom->dev); - return misc_register(&scom->mdev); + /* Allocate a minor in the FSI space */ + rc = fsi_get_new_minor(fsi_dev, fsi_dev_scom, &scom->dev.devt, &didx); + if (rc) + goto err; + + dev_set_name(&scom->dev, "scom%d", didx); + cdev_init(&scom->cdev, &scom_fops); + rc = cdev_device_add(&scom->cdev, &scom->dev); + if (rc) { + dev_err(dev, "Error %d creating char device %s\n", + rc, dev_name(&scom->dev)); + goto err_free_minor; + } + + return 0; + err_free_minor: + fsi_free_minor(scom->dev.devt); + err: + put_device(&scom->dev); + return rc; } static int scom_remove(struct device *dev) { - struct scom_device *scom, *scom_tmp; - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct scom_device *scom = dev_get_drvdata(dev); - list_for_each_entry_safe(scom, scom_tmp, &scom_devices, link) { - if (scom->fsi_dev == fsi_dev) { - list_del(&scom->link); - ida_simple_remove(&scom_ida, scom->idx); - misc_deregister(&scom->mdev); - } - } + mutex_lock(&scom->lock); + scom->dead = true; + mutex_unlock(&scom->lock); + cdev_device_del(&scom->cdev, &scom->dev); + fsi_free_minor(scom->dev.devt); + put_device(&scom->dev); return 0; } @@ -239,20 +662,11 @@ static struct fsi_driver scom_drv = { static int scom_init(void) { - INIT_LIST_HEAD(&scom_devices); return fsi_driver_register(&scom_drv); } static void scom_exit(void) { - struct list_head *pos; - struct scom_device *scom; - - list_for_each(pos, &scom_devices) { - scom = list_entry(pos, struct scom_device, link); - misc_deregister(&scom->mdev); - devm_kfree(&scom->fsi_dev->dev, scom); - } fsi_driver_unregister(&scom_drv); }
diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c index 6f693b7..1e00f40 100644 --- a/drivers/gpio/gpio-aspeed.c +++ b/drivers/gpio/gpio-aspeed.c
@@ -12,6 +12,7 @@ #include <asm/div64.h> #include <linux/clk.h> #include <linux/gpio/driver.h> +#include <linux/gpio/aspeed.h> #include <linux/hashtable.h> #include <linux/init.h> #include <linux/io.h> @@ -22,6 +23,15 @@ #include <linux/spinlock.h> #include <linux/string.h> +/* + * These two headers aren't meant to be used by GPIO drivers. We need + * them in order to access gpio_chip_hwgpio() which we need to implement + * the aspeed specific API which allows the coprocessor to request + * access to some GPIOs and to arbitrate between coprocessor and ARM. + */ +#include <linux/gpio/consumer.h> +#include "gpiolib.h" + struct aspeed_bank_props { unsigned int bank; u32 input; @@ -54,83 +64,132 @@ struct aspeed_gpio { u8 *offset_timer; unsigned int timer_users[4]; struct clk *clk; + + u32 *dcache; + u8 *cf_copro_bankmap; }; struct aspeed_gpio_bank { - uint16_t val_regs; + uint16_t val_regs; /* +0: Rd: read input value, Wr: set write latch + * +4: Rd/Wr: Direction (0=in, 1=out) + */ + uint16_t rdata_reg; /* Rd: read write latch, Wr: <none> */ uint16_t irq_regs; uint16_t debounce_regs; uint16_t tolerance_regs; + uint16_t cmdsrc_regs; const char names[4][3]; }; +/* + * Note: The "value" register returns the input value sampled on the + * line even when the GPIO is configured as an output. Since + * that input goes through synchronizers, writing, then reading + * back may not return the written value right away. + * + * The "rdata" register returns the content of the write latch + * and thus can be used to read back what was last written + * reliably. + */ + static const int debounce_timers[4] = { 0x00, 0x50, 0x54, 0x58 }; +static const struct aspeed_gpio_copro_ops *copro_ops; +static void *copro_data; + static const struct aspeed_gpio_bank aspeed_gpio_banks[] = { { .val_regs = 0x0000, + .rdata_reg = 0x00c0, .irq_regs = 0x0008, .debounce_regs = 0x0040, .tolerance_regs = 0x001c, + .cmdsrc_regs = 0x0060, .names = { "A", "B", "C", "D" }, }, { .val_regs = 0x0020, + .rdata_reg = 0x00c4, .irq_regs = 0x0028, .debounce_regs = 0x0048, .tolerance_regs = 0x003c, + .cmdsrc_regs = 0x0068, .names = { "E", "F", "G", "H" }, }, { .val_regs = 0x0070, + .rdata_reg = 0x00c8, .irq_regs = 0x0098, .debounce_regs = 0x00b0, .tolerance_regs = 0x00ac, + .cmdsrc_regs = 0x0090, .names = { "I", "J", "K", "L" }, }, { .val_regs = 0x0078, + .rdata_reg = 0x00cc, .irq_regs = 0x00e8, .debounce_regs = 0x0100, .tolerance_regs = 0x00fc, + .cmdsrc_regs = 0x00e0, .names = { "M", "N", "O", "P" }, }, { .val_regs = 0x0080, + .rdata_reg = 0x00d0, .irq_regs = 0x0118, .debounce_regs = 0x0130, .tolerance_regs = 0x012c, + .cmdsrc_regs = 0x0110, .names = { "Q", "R", "S", "T" }, }, { .val_regs = 0x0088, + .rdata_reg = 0x00d4, .irq_regs = 0x0148, .debounce_regs = 0x0160, .tolerance_regs = 0x015c, + .cmdsrc_regs = 0x0140, .names = { "U", "V", "W", "X" }, }, { .val_regs = 0x01E0, + .rdata_reg = 0x00d8, .irq_regs = 0x0178, .debounce_regs = 0x0190, .tolerance_regs = 0x018c, + .cmdsrc_regs = 0x0170, .names = { "Y", "Z", "AA", "AB" }, }, { .val_regs = 0x01e8, + .rdata_reg = 0x00dc, .irq_regs = 0x01a8, .debounce_regs = 0x01c0, .tolerance_regs = 0x01bc, + .cmdsrc_regs = 0x01a0, .names = { "AC", "", "", "" }, }, }; -#define GPIO_BANK(x) ((x) >> 5) -#define GPIO_OFFSET(x) ((x) & 0x1f) -#define GPIO_BIT(x) BIT(GPIO_OFFSET(x)) +enum aspeed_gpio_reg { + reg_val, + reg_rdata, + reg_dir, + reg_irq_enable, + reg_irq_type0, + reg_irq_type1, + reg_irq_type2, + reg_irq_status, + reg_debounce_sel1, + reg_debounce_sel2, + reg_tolerance, + reg_cmdsrc0, + reg_cmdsrc1, +}; -#define GPIO_DATA 0x00 -#define GPIO_DIR 0x04 +#define GPIO_VAL_VALUE 0x00 +#define GPIO_VAL_DIR 0x04 #define GPIO_IRQ_ENABLE 0x00 #define GPIO_IRQ_TYPE0 0x04 @@ -141,6 +200,53 @@ static const struct aspeed_gpio_bank aspeed_gpio_banks[] = { #define GPIO_DEBOUNCE_SEL1 0x00 #define GPIO_DEBOUNCE_SEL2 0x04 +#define GPIO_CMDSRC_0 0x00 +#define GPIO_CMDSRC_1 0x04 +#define GPIO_CMDSRC_ARM 0 +#define GPIO_CMDSRC_LPC 1 +#define GPIO_CMDSRC_COLDFIRE 2 +#define GPIO_CMDSRC_RESERVED 3 + +/* This will be resolved at compile time */ +static inline void __iomem *bank_reg(struct aspeed_gpio *gpio, + const struct aspeed_gpio_bank *bank, + const enum aspeed_gpio_reg reg) +{ + switch (reg) { + case reg_val: + return gpio->base + bank->val_regs + GPIO_VAL_VALUE; + case reg_rdata: + return gpio->base + bank->rdata_reg; + case reg_dir: + return gpio->base + bank->val_regs + GPIO_VAL_DIR; + case reg_irq_enable: + return gpio->base + bank->irq_regs + GPIO_IRQ_ENABLE; + case reg_irq_type0: + return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE0; + case reg_irq_type1: + return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE1; + case reg_irq_type2: + return gpio->base + bank->irq_regs + GPIO_IRQ_TYPE2; + case reg_irq_status: + return gpio->base + bank->irq_regs + GPIO_IRQ_STATUS; + case reg_debounce_sel1: + return gpio->base + bank->debounce_regs + GPIO_DEBOUNCE_SEL1; + case reg_debounce_sel2: + return gpio->base + bank->debounce_regs + GPIO_DEBOUNCE_SEL2; + case reg_tolerance: + return gpio->base + bank->tolerance_regs; + case reg_cmdsrc0: + return gpio->base + bank->cmdsrc_regs + GPIO_CMDSRC_0; + case reg_cmdsrc1: + return gpio->base + bank->cmdsrc_regs + GPIO_CMDSRC_1; + } + BUG_ON(1); +} + +#define GPIO_BANK(x) ((x) >> 5) +#define GPIO_OFFSET(x) ((x) & 0x1f) +#define GPIO_BIT(x) BIT(GPIO_OFFSET(x)) + #define _GPIO_SET_DEBOUNCE(t, o, i) ((!!((t) & BIT(i))) << GPIO_OFFSET(o)) #define GPIO_SET_DEBOUNCE1(t, o) _GPIO_SET_DEBOUNCE(t, o, 1) #define GPIO_SET_DEBOUNCE2(t, o) _GPIO_SET_DEBOUNCE(t, o, 0) @@ -199,18 +305,80 @@ static inline bool have_output(struct aspeed_gpio *gpio, unsigned int offset) return !props || (props->output & GPIO_BIT(offset)); } -static void __iomem *bank_val_reg(struct aspeed_gpio *gpio, - const struct aspeed_gpio_bank *bank, - unsigned int reg) +static void aspeed_gpio_change_cmd_source(struct aspeed_gpio *gpio, + const struct aspeed_gpio_bank *bank, + int bindex, int cmdsrc) { - return gpio->base + bank->val_regs + reg; + void __iomem *c0 = bank_reg(gpio, bank, reg_cmdsrc0); + void __iomem *c1 = bank_reg(gpio, bank, reg_cmdsrc1); + u32 bit, reg; + + /* + * Each register controls 4 banks, so take the bottom 2 + * bits of the bank index, and use them to select the + * right control bit (0, 8, 16 or 24). + */ + bit = BIT((bindex & 3) << 3); + + /* Source 1 first to avoid illegal 11 combination */ + reg = ioread32(c1); + if (cmdsrc & 2) + reg |= bit; + else + reg &= ~bit; + iowrite32(reg, c1); + + /* Then Source 0 */ + reg = ioread32(c0); + if (cmdsrc & 1) + reg |= bit; + else + reg &= ~bit; + iowrite32(reg, c0); } -static void __iomem *bank_irq_reg(struct aspeed_gpio *gpio, - const struct aspeed_gpio_bank *bank, - unsigned int reg) +static bool aspeed_gpio_copro_request(struct aspeed_gpio *gpio, + unsigned int offset) { - return gpio->base + bank->irq_regs + reg; + const struct aspeed_gpio_bank *bank = to_bank(offset); + + if (!copro_ops || !gpio->cf_copro_bankmap) + return false; + if (!gpio->cf_copro_bankmap[offset >> 3]) + return false; + if (!copro_ops->request_access) + return false; + + /* Pause the coprocessor */ + copro_ops->request_access(copro_data); + + /* Change command source back to ARM */ + aspeed_gpio_change_cmd_source(gpio, bank, offset >> 3, GPIO_CMDSRC_ARM); + + /* Update cache */ + gpio->dcache[GPIO_BANK(offset)] = ioread32(bank_reg(gpio, bank, reg_rdata)); + + return true; +} + +static void aspeed_gpio_copro_release(struct aspeed_gpio *gpio, + unsigned int offset) +{ + const struct aspeed_gpio_bank *bank = to_bank(offset); + + if (!copro_ops || !gpio->cf_copro_bankmap) + return; + if (!gpio->cf_copro_bankmap[offset >> 3]) + return; + if (!copro_ops->release_access) + return; + + /* Change command source back to ColdFire */ + aspeed_gpio_change_cmd_source(gpio, bank, offset >> 3, + GPIO_CMDSRC_COLDFIRE); + + /* Restart the coprocessor */ + copro_ops->release_access(copro_data); } static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset) @@ -218,8 +386,7 @@ static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset) struct aspeed_gpio *gpio = gpiochip_get_data(gc); const struct aspeed_gpio_bank *bank = to_bank(offset); - return !!(ioread32(bank_val_reg(gpio, bank, GPIO_DATA)) - & GPIO_BIT(offset)); + return !!(ioread32(bank_reg(gpio, bank, reg_val)) & GPIO_BIT(offset)); } static void __aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, @@ -230,13 +397,14 @@ static void __aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, void __iomem *addr; u32 reg; - addr = bank_val_reg(gpio, bank, GPIO_DATA); - reg = ioread32(addr); + addr = bank_reg(gpio, bank, reg_val); + reg = gpio->dcache[GPIO_BANK(offset)]; if (val) reg |= GPIO_BIT(offset); else reg &= ~GPIO_BIT(offset); + gpio->dcache[GPIO_BANK(offset)] = reg; iowrite32(reg, addr); } @@ -246,11 +414,15 @@ static void aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, { struct aspeed_gpio *gpio = gpiochip_get_data(gc); unsigned long flags; + bool copro; spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); __aspeed_gpio_set(gc, offset, val); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); } @@ -258,7 +430,9 @@ static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) { struct aspeed_gpio *gpio = gpiochip_get_data(gc); const struct aspeed_gpio_bank *bank = to_bank(offset); + void __iomem *addr = bank_reg(gpio, bank, reg_dir); unsigned long flags; + bool copro; u32 reg; if (!have_input(gpio, offset)) @@ -266,8 +440,13 @@ static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset) spin_lock_irqsave(&gpio->lock, flags); - reg = ioread32(bank_val_reg(gpio, bank, GPIO_DIR)); - iowrite32(reg & ~GPIO_BIT(offset), bank_val_reg(gpio, bank, GPIO_DIR)); + reg = ioread32(addr); + reg &= ~GPIO_BIT(offset); + + copro = aspeed_gpio_copro_request(gpio, offset); + iowrite32(reg, addr); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); @@ -279,7 +458,9 @@ static int aspeed_gpio_dir_out(struct gpio_chip *gc, { struct aspeed_gpio *gpio = gpiochip_get_data(gc); const struct aspeed_gpio_bank *bank = to_bank(offset); + void __iomem *addr = bank_reg(gpio, bank, reg_dir); unsigned long flags; + bool copro; u32 reg; if (!have_output(gpio, offset)) @@ -287,11 +468,15 @@ static int aspeed_gpio_dir_out(struct gpio_chip *gc, spin_lock_irqsave(&gpio->lock, flags); - reg = ioread32(bank_val_reg(gpio, bank, GPIO_DIR)); - iowrite32(reg | GPIO_BIT(offset), bank_val_reg(gpio, bank, GPIO_DIR)); + reg = ioread32(addr); + reg |= GPIO_BIT(offset); + copro = aspeed_gpio_copro_request(gpio, offset); __aspeed_gpio_set(gc, offset, val); + iowrite32(reg, addr); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); return 0; @@ -312,7 +497,7 @@ static int aspeed_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) spin_lock_irqsave(&gpio->lock, flags); - val = ioread32(bank_val_reg(gpio, bank, GPIO_DIR)) & GPIO_BIT(offset); + val = ioread32(bank_reg(gpio, bank, reg_dir)) & GPIO_BIT(offset); spin_unlock_irqrestore(&gpio->lock, flags); @@ -321,24 +506,23 @@ static int aspeed_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) } static inline int irqd_to_aspeed_gpio_data(struct irq_data *d, - struct aspeed_gpio **gpio, - const struct aspeed_gpio_bank **bank, - u32 *bit) + struct aspeed_gpio **gpio, + const struct aspeed_gpio_bank **bank, + u32 *bit, int *offset) { - int offset; struct aspeed_gpio *internal; - offset = irqd_to_hwirq(d); + *offset = irqd_to_hwirq(d); internal = irq_data_get_irq_chip_data(d); /* This might be a bit of a questionable place to check */ - if (!have_irq(internal, offset)) + if (!have_irq(internal, *offset)) return -ENOTSUPP; *gpio = internal; - *bank = to_bank(offset); - *bit = GPIO_BIT(offset); + *bank = to_bank(*offset); + *bit = GPIO_BIT(*offset); return 0; } @@ -349,17 +533,23 @@ static void aspeed_gpio_irq_ack(struct irq_data *d) struct aspeed_gpio *gpio; unsigned long flags; void __iomem *status_addr; + int rc, offset; + bool copro; u32 bit; - int rc; - rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); if (rc) return; - status_addr = bank_irq_reg(gpio, bank, GPIO_IRQ_STATUS); + status_addr = bank_reg(gpio, bank, reg_irq_status); spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); + iowrite32(bit, status_addr); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); } @@ -370,15 +560,17 @@ static void aspeed_gpio_irq_set_mask(struct irq_data *d, bool set) unsigned long flags; u32 reg, bit; void __iomem *addr; - int rc; + int rc, offset; + bool copro; - rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); if (rc) return; - addr = bank_irq_reg(gpio, bank, GPIO_IRQ_ENABLE); + addr = bank_reg(gpio, bank, reg_irq_enable); spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); reg = ioread32(addr); if (set) @@ -387,6 +579,8 @@ static void aspeed_gpio_irq_set_mask(struct irq_data *d, bool set) reg &= ~bit; iowrite32(reg, addr); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); } @@ -411,9 +605,10 @@ static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type) struct aspeed_gpio *gpio; unsigned long flags; void __iomem *addr; - int rc; + int rc, offset; + bool copro; - rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit); + rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit, &offset); if (rc) return -EINVAL; @@ -439,22 +634,25 @@ static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type) } spin_lock_irqsave(&gpio->lock, flags); + copro = aspeed_gpio_copro_request(gpio, offset); - addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE0); + addr = bank_reg(gpio, bank, reg_irq_type0); reg = ioread32(addr); reg = (reg & ~bit) | type0; iowrite32(reg, addr); - addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE1); + addr = bank_reg(gpio, bank, reg_irq_type1); reg = ioread32(addr); reg = (reg & ~bit) | type1; iowrite32(reg, addr); - addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE2); + addr = bank_reg(gpio, bank, reg_irq_type2); reg = ioread32(addr); reg = (reg & ~bit) | type2; iowrite32(reg, addr); + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); irq_set_handler_locked(d, handler); @@ -475,7 +673,7 @@ static void aspeed_gpio_irq_handler(struct irq_desc *desc) for (i = 0; i < ARRAY_SIZE(aspeed_gpio_banks); i++) { const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i]; - reg = ioread32(bank_irq_reg(data, bank, GPIO_IRQ_STATUS)); + reg = ioread32(bank_reg(data, bank, reg_irq_status)); for_each_set_bit(p, ®, 32) { girq = irq_find_mapping(gc->irq.domain, i * 32 + p); @@ -547,21 +745,27 @@ static int aspeed_gpio_reset_tolerance(struct gpio_chip *chip, unsigned int offset, bool enable) { struct aspeed_gpio *gpio = gpiochip_get_data(chip); - const struct aspeed_gpio_bank *bank; unsigned long flags; + void __iomem *treg; + bool copro; u32 val; - bank = to_bank(offset); + treg = bank_reg(gpio, to_bank(offset), reg_tolerance); spin_lock_irqsave(&gpio->lock, flags); - val = readl(gpio->base + bank->tolerance_regs); + copro = aspeed_gpio_copro_request(gpio, offset); + + val = readl(treg); if (enable) val |= GPIO_BIT(offset); else val &= ~GPIO_BIT(offset); - writel(val, gpio->base + bank->tolerance_regs); + writel(val, treg); + + if (copro) + aspeed_gpio_copro_release(gpio, offset); spin_unlock_irqrestore(&gpio->lock, flags); return 0; @@ -580,13 +784,6 @@ static void aspeed_gpio_free(struct gpio_chip *chip, unsigned int offset) pinctrl_gpio_free(chip->base + offset); } -static inline void __iomem *bank_debounce_reg(struct aspeed_gpio *gpio, - const struct aspeed_gpio_bank *bank, - unsigned int reg) -{ - return gpio->base + bank->debounce_regs + reg; -} - static int usecs_to_cycles(struct aspeed_gpio *gpio, unsigned long usecs, u32 *cycles) { @@ -664,11 +861,14 @@ static void configure_timer(struct aspeed_gpio *gpio, unsigned int offset, void __iomem *addr; u32 val; - addr = bank_debounce_reg(gpio, bank, GPIO_DEBOUNCE_SEL1); + /* Note: Debounce timer isn't under control of the command + * source registers, so no need to sync with the coprocessor + */ + addr = bank_reg(gpio, bank, reg_debounce_sel1); val = ioread32(addr); iowrite32((val & ~mask) | GPIO_SET_DEBOUNCE1(timer, offset), addr); - addr = bank_debounce_reg(gpio, bank, GPIO_DEBOUNCE_SEL2); + addr = bank_reg(gpio, bank, reg_debounce_sel2); val = ioread32(addr); iowrite32((val & ~mask) | GPIO_SET_DEBOUNCE2(timer, offset), addr); } @@ -810,6 +1010,111 @@ static int aspeed_gpio_set_config(struct gpio_chip *chip, unsigned int offset, return -ENOTSUPP; } +/** + * aspeed_gpio_copro_set_ops - Sets the callbacks used for handhsaking with + * the coprocessor for shared GPIO banks + * @ops: The callbacks + * @data: Pointer passed back to the callbacks + */ +int aspeed_gpio_copro_set_ops(const struct aspeed_gpio_copro_ops *ops, void *data) +{ + copro_data = data; + copro_ops = ops; + + return 0; +} +EXPORT_SYMBOL_GPL(aspeed_gpio_copro_set_ops); + +/** + * aspeed_gpio_copro_grab_gpio - Mark a GPIO used by the coprocessor. The entire + * bank gets marked and any access from the ARM will + * result in handshaking via callbacks. + * @desc: The GPIO to be marked + * @vreg_offset: If non-NULL, returns the value register offset in the GPIO space + * @dreg_offset: If non-NULL, returns the data latch register offset in the GPIO space + * @bit: If non-NULL, returns the bit number of the GPIO in the registers + */ +int aspeed_gpio_copro_grab_gpio(struct gpio_desc *desc, + u16 *vreg_offset, u16 *dreg_offset, u8 *bit) +{ + struct gpio_chip *chip = gpiod_to_chip(desc); + struct aspeed_gpio *gpio = gpiochip_get_data(chip); + int rc = 0, bindex, offset = gpio_chip_hwgpio(desc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + + if (!gpio->cf_copro_bankmap) + gpio->cf_copro_bankmap = kzalloc(gpio->config->nr_gpios >> 3, GFP_KERNEL); + if (!gpio->cf_copro_bankmap) + return -ENOMEM; + if (offset < 0 || offset > gpio->config->nr_gpios) + return -EINVAL; + bindex = offset >> 3; + + spin_lock_irqsave(&gpio->lock, flags); + + /* Sanity check, this shouldn't happen */ + if (gpio->cf_copro_bankmap[bindex] == 0xff) { + rc = -EIO; + goto bail; + } + gpio->cf_copro_bankmap[bindex]++; + + /* Switch command source */ + if (gpio->cf_copro_bankmap[bindex] == 1) + aspeed_gpio_change_cmd_source(gpio, bank, bindex, + GPIO_CMDSRC_COLDFIRE); + + if (vreg_offset) + *vreg_offset = bank->val_regs; + if (dreg_offset) + *dreg_offset = bank->rdata_reg; + if (bit) + *bit = GPIO_OFFSET(offset); + bail: + spin_unlock_irqrestore(&gpio->lock, flags); + return rc; +} +EXPORT_SYMBOL_GPL(aspeed_gpio_copro_grab_gpio); + +/** + * aspeed_gpio_copro_release_gpio - Unmark a GPIO used by the coprocessor. + * @desc: The GPIO to be marked + */ +int aspeed_gpio_copro_release_gpio(struct gpio_desc *desc) +{ + struct gpio_chip *chip = gpiod_to_chip(desc); + struct aspeed_gpio *gpio = gpiochip_get_data(chip); + int rc = 0, bindex, offset = gpio_chip_hwgpio(desc); + const struct aspeed_gpio_bank *bank = to_bank(offset); + unsigned long flags; + + if (!gpio->cf_copro_bankmap) + return -ENXIO; + + if (offset < 0 || offset > gpio->config->nr_gpios) + return -EINVAL; + bindex = offset >> 3; + + spin_lock_irqsave(&gpio->lock, flags); + + /* Sanity check, this shouldn't happen */ + if (gpio->cf_copro_bankmap[bindex] == 0) { + rc = -EIO; + goto bail; + } + gpio->cf_copro_bankmap[bindex]--; + + /* Switch command source */ + if (gpio->cf_copro_bankmap[bindex] == 0) + aspeed_gpio_change_cmd_source(gpio, bank, bindex, + GPIO_CMDSRC_ARM); + bail: + spin_unlock_irqrestore(&gpio->lock, flags); + return rc; +} +EXPORT_SYMBOL_GPL(aspeed_gpio_copro_release_gpio); + /* * Any banks not specified in a struct aspeed_bank_props array are assumed to * have the properties: @@ -852,7 +1157,7 @@ static int __init aspeed_gpio_probe(struct platform_device *pdev) const struct of_device_id *gpio_id; struct aspeed_gpio *gpio; struct resource *res; - int rc; + int rc, i, banks; gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL); if (!gpio) @@ -893,6 +1198,27 @@ static int __init aspeed_gpio_probe(struct platform_device *pdev) gpio->chip.base = -1; gpio->chip.irq.need_valid_mask = true; + /* Allocate a cache of the output registers */ + banks = gpio->config->nr_gpios >> 5; + gpio->dcache = devm_kcalloc(&pdev->dev, + banks, sizeof(u32), GFP_KERNEL); + if (!gpio->dcache) + return -ENOMEM; + + /* + * Populate it with initial values read from the HW and switch + * all command sources to the ARM by default + */ + for (i = 0; i < banks; i++) { + const struct aspeed_gpio_bank *bank = &aspeed_gpio_banks[i]; + void __iomem *addr = bank_reg(gpio, bank, reg_rdata); + gpio->dcache[i] = ioread32(addr); + aspeed_gpio_change_cmd_source(gpio, bank, 0, GPIO_CMDSRC_ARM); + aspeed_gpio_change_cmd_source(gpio, bank, 1, GPIO_CMDSRC_ARM); + aspeed_gpio_change_cmd_source(gpio, bank, 2, GPIO_CMDSRC_ARM); + aspeed_gpio_change_cmd_source(gpio, bank, 3, GPIO_CMDSRC_ARM); + } + rc = devm_gpiochip_add_data(&pdev->dev, &gpio->chip, gpio); if (rc < 0) return rc;
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index deeefa7..ee6c17f 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig
@@ -289,6 +289,8 @@ source "drivers/gpu/drm/tve200/Kconfig" +source "drivers/gpu/drm/aspeed/Kconfig" + # Keep legacy drivers last menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 50093ff..4ec875d 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile
@@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/ obj-$(CONFIG_DRM_PL111) += pl111/ obj-$(CONFIG_DRM_TVE200) += tve200/ +obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
diff --git a/drivers/gpu/drm/aspeed/Kconfig b/drivers/gpu/drm/aspeed/Kconfig new file mode 100644 index 0000000..6f1e64c --- /dev/null +++ b/drivers/gpu/drm/aspeed/Kconfig
@@ -0,0 +1,15 @@ +config DRM_ASPEED_GFX + tristate "ASPEED BMC Display Controller" + depends on DRM && OF + select DRM_KMS_HELPER + select DRM_KMS_FB_HELPER + select DRM_KMS_CMA_HELPER + select DRM_PANEL + select DMA_CMA + select CMA + select MFD_SYSCON + help + Chose this option if you have an ASPEED AST2400/AST2500 + SOC Display Controller (aka GFX). + + If M is selected this module will be called aspeed_gfx.
diff --git a/drivers/gpu/drm/aspeed/Makefile b/drivers/gpu/drm/aspeed/Makefile new file mode 100644 index 0000000..b01dd58 --- /dev/null +++ b/drivers/gpu/drm/aspeed/Makefile
@@ -0,0 +1,4 @@ +aspeed_gfx-y := aspeed_gfx_drv.o aspeed_gfx_crtc.o aspeed_gfx_out.o +aspeed_gfx-$(CONFIG_DEBUG_FS) += aspeed_gfx_debugfs.o + +obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed_gfx.o
diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx.h b/drivers/gpu/drm/aspeed/aspeed_gfx.h new file mode 100644 index 0000000..c46afb3 --- /dev/null +++ b/drivers/gpu/drm/aspeed/aspeed_gfx.h
@@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corporation + +#include <drm/drmP.h> +#include <drm/drm_simple_kms_helper.h> + +struct aspeed_gfx { + void __iomem *base; + struct clk *clk; + struct reset_control *rst; + struct regmap *scu; + + struct drm_simple_display_pipe pipe; + struct drm_connector connector; + struct drm_fbdev_cma *fbdev; +}; + +int aspeed_gfx_create_pipe(struct drm_device *drm); +int aspeed_gfx_create_output(struct drm_device *drm); +int aspeed_gfx_debugfs_init(struct drm_minor *minor); + +#define CRT_CTRL1 0x60 /* CRT Control I */ +#define CRT_CTRL2 0x64 /* CRT Control II */ +#define CRT_STATUS 0x68 /* CRT Status */ +#define CRT_MISC 0x6c /* CRT Misc Setting */ +#define CRT_HORIZ0 0x70 /* CRT Horizontal Total & Display Enable End */ +#define CRT_HORIZ1 0x74 /* CRT Horizontal Retrace Start & End */ +#define CRT_VERT0 0x78 /* CRT Vertical Total & Display Enable End */ +#define CRT_VERT1 0x7C /* CRT Vertical Retrace Start & End */ +#define CRT_ADDR 0x80 /* CRT Display Starting Address */ +#define CRT_OFFSET 0x84 /* CRT Display Offset & Terminal Count */ +#define CRT_THROD 0x88 /* CRT Threshold */ +#define CRT_XSCALE 0x8C /* CRT Scaling-Up Factor */ +#define CRT_CURSOR0 0x90 /* CRT Hardware Cursor X & Y Offset */ +#define CRT_CURSOR1 0x94 /* CRT Hardware Cursor X & Y Position */ +#define CRT_CURSOR2 0x98 /* CRT Hardware Cursor Pattern Address */ +#define CRT_9C 0x9C +#define CRT_OSD_H 0xA0 /* CRT OSD Horizontal Start/End */ +#define CRT_OSD_V 0xA4 /* CRT OSD Vertical Start/End */ +#define CRT_OSD_ADDR 0xA8 /* CRT OSD Pattern Address */ +#define CRT_OSD_DISP 0xAC /* CRT OSD Offset */ +#define CRT_OSD_THRESH 0xB0 /* CRT OSD Threshold & Alpha */ +#define CRT_B4 0xB4 +#define CRT_STS_V 0xB8 /* CRT Status V */ +#define CRT_SCRATCH 0xBC /* Scratchpad */ +#define CRT_BB0_ADDR 0xD0 /* CRT Display BB0 Starting Address */ +#define CRT_BB1_ADDR 0xD4 /* CRT Display BB1 Starting Address */ +#define CRT_BB_COUNT 0xD8 /* CRT Display BB Terminal Count */ +#define OSD_COLOR1 0xE0 /* OSD Color Palette Index 1 & 0 */ +#define OSD_COLOR2 0xE4 /* OSD Color Palette Index 3 & 2 */ +#define OSD_COLOR3 0xE8 /* OSD Color Palette Index 5 & 4 */ +#define OSD_COLOR4 0xEC /* OSD Color Palette Index 7 & 6 */ +#define OSD_COLOR5 0xF0 /* OSD Color Palette Index 9 & 8 */ +#define OSD_COLOR6 0xF4 /* OSD Color Palette Index 11 & 10 */ +#define OSD_COLOR7 0xF8 /* OSD Color Palette Index 13 & 12 */ +#define OSD_COLOR8 0xFC /* OSD Color Palette Index 15 & 14 */ + +/* CTRL1 */ +#define CRT_CTRL_EN BIT(0) +#define CRT_CTRL_HW_CURSOR_EN BIT(1) +#define CRT_CTRL_OSD_EN BIT(2) +#define CRT_CTRL_INTERLACED BIT(3) +#define CRT_CTRL_COLOR_RGB565 (0 << 7) +#define CRT_CTRL_COLOR_YUV444 (1 << 7) +#define CRT_CTRL_COLOR_XRGB8888 (2 << 7) +#define CRT_CTRL_COLOR_RGB888 (3 << 7) +#define CRT_CTRL_COLOR_YUV444_2RGB (5 << 7) +#define CRT_CTRL_COLOR_YUV422 (7 << 7) +#define CRT_CTRL_COLOR_MASK GENMASK(9, 7) +#define CRT_CTRL_HSYNC_NEGATIVE BIT(16) +#define CRT_CTRL_VSYNC_NEGATIVE BIT(17) +#define CRT_CTRL_VERTICAL_INTR_EN BIT(30) +#define CRT_CTRL_VERTICAL_INTR_STS BIT(31) + +/* CTRL2 */ +#define CRT_CTRL_DAC_EN BIT(0) +#define CRT_CTRL_VBLANK_LINE(x) (((x) << 20) & CRT_CTRL_VBLANK_LINE_MASK) +#define CRT_CTRL_VBLANK_LINE_MASK GENMASK(20, 31) + +/* CRT_HORIZ0 */ +#define CRT_H_TOTAL(x) (x) +#define CRT_H_DE(x) ((x) << 16) + +/* CRT_HORIZ1 */ +#define CRT_H_RS_START(x) (x) +#define CRT_H_RS_END(x) ((x) << 16) + +/* CRT_VIRT0 */ +#define CRT_V_TOTAL(x) (x) +#define CRT_V_DE(x) ((x) << 16) + +/* CRT_VIRT1 */ +#define CRT_V_RS_START(x) (x) +#define CRT_V_RS_END(x) ((x) << 16) + +/* CRT_OFFSET */ +#define CRT_DISP_OFFSET(x) (x) +#define CRT_TERM_COUNT(x) ((x) << 16) + +/* CRT_THROD */ +#define CRT_THROD_LOW(x) (x) +#define CRT_THROD_HIGH(x) ((x) << 8) + +/* Default Threshold Seting */ +#define G5_CRT_THROD_VAL (CRT_THROD_LOW(0x24) | CRT_THROD_HIGH(0x3C))
diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c b/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c new file mode 100644 index 0000000..a40c2c0 --- /dev/null +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_crtc.c
@@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corporation + +#include <linux/clk.h> +#include <linux/reset.h> +#include <linux/regmap.h> + +#include <drm/drm_crtc_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_panel.h> + +#include "aspeed_gfx.h" + +static struct aspeed_gfx * +drm_pipe_to_aspeed_gfx(struct drm_simple_display_pipe *pipe) +{ + return container_of(pipe, struct aspeed_gfx, pipe); +} + +static int aspeed_gfx_set_pixel_fmt(struct aspeed_gfx *priv, u32 *bpp) +{ + struct drm_crtc *crtc = &priv->pipe.crtc; + struct drm_device *drm = crtc->dev; + const u32 format = crtc->primary->state->fb->format->format; + u32 ctrl1; + + ctrl1 = readl(priv->base + CRT_CTRL1); + ctrl1 &= ~CRT_CTRL_COLOR_MASK; + + switch (format) { + case DRM_FORMAT_RGB565: + dev_dbg(drm->dev, "Setting up RGB565 mode\n"); + ctrl1 |= CRT_CTRL_COLOR_RGB565; + *bpp = 16; + break; + case DRM_FORMAT_XRGB8888: + dev_dbg(drm->dev, "Setting up XRGB8888 mode\n"); + ctrl1 |= CRT_CTRL_COLOR_XRGB8888; + *bpp = 32; + break; + default: + dev_err(drm->dev, "Unhandled pixel format %08x\n", format); + return -EINVAL; + } + + writel(ctrl1, priv->base + CRT_CTRL1); + + return 0; +} + +static void aspeed_gfx_enable_controller(struct aspeed_gfx *priv) +{ + u32 ctrl1 = readl(priv->base + CRT_CTRL1); + u32 ctrl2 = readl(priv->base + CRT_CTRL2); + + /* SCU2C: set DAC source for display output to Graphics CRT (GFX) */ + regmap_update_bits(priv->scu, 0x2c, BIT(16), BIT(16)); + + writel(ctrl1 | CRT_CTRL_EN, priv->base + CRT_CTRL1); + writel(ctrl2 | CRT_CTRL_DAC_EN, priv->base + CRT_CTRL2); +} + +static void aspeed_gfx_disable_controller(struct aspeed_gfx *priv) +{ + u32 ctrl1 = readl(priv->base + CRT_CTRL1); + u32 ctrl2 = readl(priv->base + CRT_CTRL2); + + writel(ctrl1 & ~CRT_CTRL_EN, priv->base + CRT_CTRL1); + writel(ctrl2 & ~CRT_CTRL_DAC_EN, priv->base + CRT_CTRL2); + + regmap_update_bits(priv->scu, 0x2c, BIT(16), 0); +} + +static void aspeed_gfx_crtc_mode_set_nofb(struct aspeed_gfx *priv) +{ + struct drm_display_mode *m = &priv->pipe.crtc.state->adjusted_mode; + u32 ctrl1, d_offset, t_count, bpp; + int err; + + err = aspeed_gfx_set_pixel_fmt(priv, &bpp); + if (err) + return; + +#if 0 + /* TODO */ + clk_set_rate(priv->pixel_clk, m->crtc_clock * 1000); +#endif + + ctrl1 = readl(priv->base + CRT_CTRL1); + ctrl1 &= ~(CRT_CTRL_INTERLACED | + CRT_CTRL_HSYNC_NEGATIVE | + CRT_CTRL_VSYNC_NEGATIVE); + + if (m->flags & DRM_MODE_FLAG_INTERLACE) + ctrl1 |= CRT_CTRL_INTERLACED; + + if (!(m->flags & DRM_MODE_FLAG_PHSYNC)) + ctrl1 |= CRT_CTRL_HSYNC_NEGATIVE; + + if (!(m->flags & DRM_MODE_FLAG_PVSYNC)) + ctrl1 |= CRT_CTRL_VSYNC_NEGATIVE; + + writel(ctrl1, priv->base + CRT_CTRL1); + + /* Horizontal timing */ + writel(CRT_H_TOTAL(m->htotal - 1) | CRT_H_DE(m->hdisplay - 1), + priv->base + CRT_HORIZ0); + writel(CRT_H_RS_START(m->hsync_start - 1) | CRT_H_RS_END(m->hsync_end), + priv->base + CRT_HORIZ1); + + + /* Vertical timing */ + writel(CRT_V_TOTAL(m->vtotal - 1) | CRT_V_DE(m->vdisplay - 1), + priv->base + CRT_VERT0); + writel(CRT_V_RS_START(m->vsync_start) | CRT_V_RS_END(m->vsync_end), + priv->base + CRT_VERT1); + + /* + * Display Offset: address difference between consecutive scan lines + * Terminal Count: memory size of one scan line + */ + d_offset = m->hdisplay * bpp / 8; + t_count = (m->hdisplay * bpp + 127) / 128; + writel(CRT_DISP_OFFSET(d_offset) | CRT_TERM_COUNT(t_count), + priv->base + CRT_OFFSET); + + /* + * Threshold: FIFO thresholds of refill and stop (16 byte chunks + * per line, rounded up) + */ + writel(G5_CRT_THROD_VAL, priv->base + CRT_THROD); +} + +static void aspeed_gfx_pipe_enable(struct drm_simple_display_pipe *pipe, + struct drm_crtc_state *crtc_state) +{ + struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); + struct drm_crtc *crtc = &pipe->crtc; + + aspeed_gfx_crtc_mode_set_nofb(priv); + aspeed_gfx_enable_controller(priv); + drm_crtc_vblank_on(crtc); +} + +static void aspeed_gfx_pipe_disable(struct drm_simple_display_pipe *pipe) +{ + struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); + struct drm_crtc *crtc = &pipe->crtc; + + drm_crtc_vblank_off(crtc); + aspeed_gfx_disable_controller(priv); +} + +static void aspeed_gfx_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *plane_state) +{ + struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); + struct drm_crtc *crtc = &pipe->crtc; + struct drm_framebuffer *fb = pipe->plane.state->fb; + struct drm_pending_vblank_event *event; + struct drm_gem_cma_object *gem; + + if (!crtc) + return; + + spin_lock_irq(&crtc->dev->event_lock); + event = crtc->state->event; + if (event) { + crtc->state->event = NULL; + + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, event); + else + drm_crtc_send_vblank_event(crtc, event); + } + spin_unlock_irq(&crtc->dev->event_lock); + + if (!fb) + return; + + gem = drm_fb_cma_get_gem_obj(fb, 0); + if (!gem) + return; + writel(gem->paddr, priv->base + CRT_ADDR); +} + +static int aspeed_gfx_pipe_prepare_fb(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *plane_state) +{ + return drm_gem_fb_prepare_fb(&pipe->plane, plane_state); +} + +static int aspeed_gfx_enable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); + u32 reg = readl(priv->base + CRT_CTRL1); + + /* Clear pending VBLANK IRQ */ + writel(reg | CRT_CTRL_VERTICAL_INTR_STS, priv->base + CRT_CTRL1); + + reg |= CRT_CTRL_VERTICAL_INTR_EN; + writel(reg, priv->base + CRT_CTRL1); + + reg = readl(priv->base + CRT_CTRL1); + + return 0; +} + +static void aspeed_gfx_disable_vblank(struct drm_simple_display_pipe *pipe) +{ + struct aspeed_gfx *priv = drm_pipe_to_aspeed_gfx(pipe); + u32 reg = readl(priv->base + CRT_CTRL1); + + reg &= ~CRT_CTRL_VERTICAL_INTR_EN; + writel(reg, priv->base + CRT_CTRL1); + + /* Clear pending VBLANK IRQ */ + writel(reg | CRT_CTRL_VERTICAL_INTR_STS, priv->base + CRT_CTRL1); + + reg = readl(priv->base + CRT_CTRL1); +} + +static struct drm_simple_display_pipe_funcs aspeed_gfx_funcs = { + .enable = aspeed_gfx_pipe_enable, + .disable = aspeed_gfx_pipe_disable, + .update = aspeed_gfx_pipe_update, + .prepare_fb = aspeed_gfx_pipe_prepare_fb, + .enable_vblank = aspeed_gfx_enable_vblank, + .disable_vblank = aspeed_gfx_disable_vblank, +}; + +static const uint32_t aspeed_gfx_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB565, +}; + +int aspeed_gfx_create_pipe(struct drm_device *drm) +{ + struct aspeed_gfx *priv = drm->dev_private; + + return drm_simple_display_pipe_init(drm, &priv->pipe, &aspeed_gfx_funcs, + aspeed_gfx_formats, + ARRAY_SIZE(aspeed_gfx_formats), + NULL, + &priv->connector); +}
diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_debugfs.c b/drivers/gpu/drm/aspeed/aspeed_gfx_debugfs.c new file mode 100644 index 0000000..ac47c27d --- /dev/null +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_debugfs.c
@@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright © 2017 Broadcom +// Copyright 2018 IBM Corp + +#include <linux/seq_file.h> +#include <drm/drm_debugfs.h> + +#include "aspeed_gfx.h" + +#define REGDEF(reg) { reg, #reg } +static const struct { + u32 reg; + const char *name; +} aspeed_gfx_reg_defs[] = { + REGDEF(CRT_CTRL1), + REGDEF(CRT_CTRL2), + REGDEF(CRT_STATUS), + REGDEF(CRT_MISC), + REGDEF(CRT_HORIZ0), + REGDEF(CRT_HORIZ1), + REGDEF(CRT_VERT0), + REGDEF(CRT_VERT1), + REGDEF(CRT_ADDR), + REGDEF(CRT_OFFSET), + REGDEF(CRT_THROD), + REGDEF(CRT_XSCALE), + REGDEF(CRT_CURSOR0), + REGDEF(CRT_CURSOR1), + REGDEF(CRT_CURSOR2), + REGDEF(CRT_9C), + REGDEF(CRT_OSD_H), + REGDEF(CRT_OSD_V), + REGDEF(CRT_OSD_ADDR), + REGDEF(CRT_OSD_DISP), + REGDEF(CRT_OSD_THRESH), + REGDEF(CRT_B4), + REGDEF(CRT_STS_V), + REGDEF(CRT_SCRATCH), + REGDEF(CRT_BB0_ADDR), + REGDEF(CRT_BB1_ADDR), + REGDEF(CRT_BB_COUNT), + REGDEF(OSD_COLOR1), + REGDEF(OSD_COLOR2), + REGDEF(OSD_COLOR3), + REGDEF(OSD_COLOR4), + REGDEF(OSD_COLOR5), + REGDEF(OSD_COLOR6), + REGDEF(OSD_COLOR7), + REGDEF(OSD_COLOR8), +}; + +int aspeed_gfx_debugfs_regs(struct seq_file *m, void *unused) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *dev = node->minor->dev; + struct aspeed_gfx *priv = dev->dev_private; + int i; + + for (i = 0; i < ARRAY_SIZE(aspeed_gfx_reg_defs); i++) { + seq_printf(m, "%15s (0x%02x): 0x%08x\n", + aspeed_gfx_reg_defs[i].name, aspeed_gfx_reg_defs[i].reg, + readl(priv->base + aspeed_gfx_reg_defs[i].reg)); + } + + return 0; +} + +static const struct drm_info_list aspeed_gfx_debugfs_list[] = { + {"regs", aspeed_gfx_debugfs_regs, 0}, +}; + +int aspeed_gfx_debugfs_init(struct drm_minor *minor) +{ + return drm_debugfs_create_files(aspeed_gfx_debugfs_list, + ARRAY_SIZE(aspeed_gfx_debugfs_list), + minor->debugfs_root, minor); +}
diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c new file mode 100644 index 0000000..e2db7dd --- /dev/null +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_drv.c
@@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corporation + +#include <linux/module.h> +#include <linux/irq.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_reserved_mem.h> +#include <linux/reset.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#include <drm/drm_crtc_helper.h> +#include <drm/drm_device.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_simple_kms_helper.h> +#include <drm/drm_atomic_helper.h> + +#include "aspeed_gfx.h" + +static const struct drm_mode_config_funcs aspeed_gfx_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void aspeed_gfx_setup_mode_config(struct drm_device *drm) +{ + drm_mode_config_init(drm); + + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = 800; + drm->mode_config.max_height = 600; + drm->mode_config.funcs = &aspeed_gfx_mode_config_funcs; +} + +static int aspeed_gfx_load(struct drm_device *drm) +{ + struct platform_device *pdev = to_platform_device(drm->dev); + struct aspeed_gfx *priv; + struct resource *res; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + drm->dev_private = priv; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(drm->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2500-scu"); + if (IS_ERR(priv->scu)) { + dev_err(&pdev->dev, "failed to find SCU regmap\n"); + return PTR_ERR(priv->scu); + } + + ret = of_reserved_mem_device_init(drm->dev); + if (ret) { + dev_err(&pdev->dev, + "failed to initialize reserved mem: %d\n", ret); + return ret; + } + + ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "failed to set DMA mask: %d\n", ret); + return ret; + } + + priv->rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(priv->rst)) { + dev_err(&pdev->dev, + "missing or invalid reset controller device tree entry"); + return PTR_ERR(priv->rst); + } + reset_control_deassert(priv->rst); + + priv->clk = devm_clk_get(drm->dev, NULL); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, + "missing or invalid clk device tree entry"); + return PTR_ERR(priv->clk); + } + clk_prepare_enable(priv->clk); + + /* Sanitize control registers */ + writel(0, priv->base + CRT_CTRL1); + writel(0, priv->base + CRT_CTRL2); + + aspeed_gfx_setup_mode_config(drm); + + ret = drm_vblank_init(drm, 1); + if (ret < 0) { + dev_err(drm->dev, "Failed to initialise vblank\n"); + return ret; + } + + ret = aspeed_gfx_create_output(drm); + if (ret < 0) { + dev_err(drm->dev, "Failed to create outputs\n"); + return ret; + } + + ret = aspeed_gfx_create_pipe(drm); + if (ret < 0) { + dev_err(drm->dev, "Cannot setup simple display pipe\n"); + return ret; + } + + ret = drm_irq_install(drm, platform_get_irq(pdev, 0)); + if (ret < 0) { + dev_err(drm->dev, "Failed to install IRQ handler\n"); + return ret; + } + + drm_mode_config_reset(drm); + + priv->fbdev = drm_fbdev_cma_init(drm, 32, 1); + if (IS_ERR(priv->fbdev)) { + ret = PTR_ERR(priv->fbdev); + dev_err(drm->dev, "Failed to init FB CMA area\n"); + goto err_cma; + } + + return 0; + +err_cma: + drm_irq_uninstall(drm); + return ret; +} + +static void aspeed_gfx_unload(struct drm_device *drm) +{ + struct aspeed_gfx *priv = drm->dev_private; + + if (priv->fbdev) + drm_fbdev_cma_fini(priv->fbdev); + + drm_kms_helper_poll_fini(drm); + drm_mode_config_cleanup(drm); + + drm_irq_uninstall(drm); + + drm->dev_private = NULL; +} + +static void aspeed_gfx_lastclose(struct drm_device *drm) +{ + struct aspeed_gfx *priv = drm->dev_private; + + drm_fbdev_cma_restore_mode(priv->fbdev); +} + +static irqreturn_t aspeed_gfx_irq_handler(int irq, void *data) +{ + struct drm_device *drm = data; + struct aspeed_gfx *priv = drm->dev_private; + u32 reg; + + reg = readl(priv->base + CRT_CTRL1); + + if (reg & CRT_CTRL_VERTICAL_INTR_STS) { + drm_crtc_handle_vblank(&priv->pipe.crtc); + writel(reg, priv->base + CRT_CTRL1); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +DEFINE_DRM_GEM_CMA_FOPS(fops); + +static struct drm_driver aspeed_gfx_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | + DRIVER_PRIME | DRIVER_ATOMIC | + DRIVER_HAVE_IRQ, + .lastclose = aspeed_gfx_lastclose, + .irq_handler = aspeed_gfx_irq_handler, + .gem_free_object_unlocked = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + .fops = &fops, + .name = "aspeed-gfx-drm", + .desc = "ASPEED GFX DRM", + .date = "20180319", + .major = 1, + .minor = 0, + +#if defined(CONFIG_DEBUG_FS) + .debugfs_init = aspeed_gfx_debugfs_init, +#endif +}; + +static const struct of_device_id aspeed_gfx_match[] = { + { .compatible = "aspeed,ast2400-gfx" }, + { .compatible = "aspeed,ast2500-gfx" }, + { } +}; + +static int aspeed_gfx_probe(struct platform_device *pdev) +{ + struct drm_device *drm; + int ret; + + drm = drm_dev_alloc(&aspeed_gfx_driver, &pdev->dev); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + ret = aspeed_gfx_load(drm); + if (ret) + goto err_free; + + ret = drm_dev_register(drm, 0); + if (ret) + goto err_unload; + + return 0; + +err_unload: + aspeed_gfx_unload(drm); +err_free: + drm_dev_put(drm); + + return ret; +} + +static int aspeed_gfx_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + drm_dev_unregister(drm); + aspeed_gfx_unload(drm); + drm_dev_put(drm); + + return 0; +} + +static struct platform_driver aspeed_gfx_platform_driver = { + .probe = aspeed_gfx_probe, + .remove = aspeed_gfx_remove, + .driver = { + .name = "aspeed_gfx", + .of_match_table = aspeed_gfx_match, + }, +}; + +module_platform_driver(aspeed_gfx_platform_driver); + +MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>"); +MODULE_DESCRIPTION("ASPEED BMC DRM/KMS driver"); +MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/aspeed/aspeed_gfx_out.c b/drivers/gpu/drm/aspeed/aspeed_gfx_out.c new file mode 100644 index 0000000..aee30ff --- /dev/null +++ b/drivers/gpu/drm/aspeed/aspeed_gfx_out.c
@@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corporation + +#include <drm/drmP.h> +#include <drm/drm_connector.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> + +#include "aspeed_gfx.h" + +static int aspeed_gfx_get_modes(struct drm_connector *connector) +{ + return drm_add_modes_noedid(connector, 800, 600); +} + +static const struct +drm_connector_helper_funcs aspeed_gfx_connector_helper_funcs = { + .get_modes = aspeed_gfx_get_modes, +}; + +static void aspeed_gfx_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs aspeed_gfx_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = aspeed_gfx_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +int aspeed_gfx_create_output(struct drm_device *drm) +{ + struct aspeed_gfx *priv = drm->dev_private; + int ret; + + priv->connector.dpms = DRM_MODE_DPMS_OFF; + priv->connector.polled = 0; + drm_connector_helper_add(&priv->connector, + &aspeed_gfx_connector_helper_funcs); + ret = drm_connector_init(drm, &priv->connector, + &aspeed_gfx_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + return ret; +}
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 6ec307c..eac159d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig
@@ -1274,6 +1274,8 @@ This driver can also be built as a module. If so, the module will be called nsa320-hwmon. +source drivers/hwmon/occ/Kconfig + config SENSORS_PCF8591 tristate "Philips PCF8591 ADC/DAC" depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e7d52a3..3e8cd93 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile
@@ -175,6 +175,7 @@ obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o +obj-$(CONFIG_SENSORS_OCC) += occ/ obj-$(CONFIG_PMBUS) += pmbus/ ccflags-$(CONFIG_HWMON_DEBUG_CHIP) := -DDEBUG
diff --git a/drivers/hwmon/occ/Kconfig b/drivers/hwmon/occ/Kconfig new file mode 100644 index 0000000..aa39de9 --- /dev/null +++ b/drivers/hwmon/occ/Kconfig
@@ -0,0 +1,28 @@ +# +# On-Chip Controller configuration +# + +config SENSORS_OCC + tristate "POWER On-Chip Controller" + ---help--- + This option enables support for monitoring a variety of system sensors + provided by the On-Chip Controller (OCC) on a POWER processor. + + This driver can also be built as a module. If so, the module will be + called occ-hwmon. + +config SENSORS_OCC_P8_I2C + bool "POWER8 OCC through I2C" + depends on I2C && SENSORS_OCC + ---help--- + This option enables support for monitoring sensors provided by the OCC + on a POWER8 processor. Communications with the OCC are established + through I2C bus. + +config SENSORS_OCC_P9_SBE + bool "POWER9 OCC through SBE" + depends on FSI_OCC && SENSORS_OCC + ---help--- + This option enables support for monitoring sensors provided by the OCC + on a POWER9 processor. Communications with the OCC are established + through SBE engine on an FSI bus.
diff --git a/drivers/hwmon/occ/Makefile b/drivers/hwmon/occ/Makefile new file mode 100644 index 0000000..ab5c3e9 --- /dev/null +++ b/drivers/hwmon/occ/Makefile
@@ -0,0 +1,11 @@ +occ-hwmon-objs := common.o + +ifeq ($(CONFIG_SENSORS_OCC_P9_SBE), y) +occ-hwmon-objs += p9_sbe.o +endif + +ifeq ($(CONFIG_SENSORS_OCC_P8_I2C), y) +occ-hwmon-objs += p8_i2c.o +endif + +obj-$(CONFIG_SENSORS_OCC) += occ-hwmon.o
diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c new file mode 100644 index 0000000..c5115a0 --- /dev/null +++ b/drivers/hwmon/occ/common.c
@@ -0,0 +1,1402 @@ +/* + * Copyright 2017 IBM Corp. + * + * 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/device.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> +#include <asm/unaligned.h> + +#include "common.h" + +#define OCC_ERROR_COUNT_THRESHOLD 2 /* OCC HW defined */ + +#define OCC_STATE_SAFE 4 +#define OCC_SAFE_TIMEOUT msecs_to_jiffies(60000) /* 1 min */ + +#define OCC_UPDATE_FREQUENCY msecs_to_jiffies(1000) + +#define OCC_TEMP_SENSOR_FAULT 0xFF + +#define OCC_FRU_TYPE_VRM 0x3 + +/* OCC status bits */ +#define OCC_STAT_MASTER 0x80 +#define OCC_STAT_ACTIVE 0x01 +#define OCC_EXT_STAT_DVFS_OT 0x80 +#define OCC_EXT_STAT_DVFS_POWER 0x40 +#define OCC_EXT_STAT_MEM_THROTTLE 0x20 +#define OCC_EXT_STAT_QUICK_DROP 0x10 + +/* OCC sensor type and version definitions */ + +struct temp_sensor_1 { + u16 sensor_id; + u16 value; +} __packed; + +struct temp_sensor_2 { + u32 sensor_id; + u8 fru_type; + u8 value; +} __packed; + +struct freq_sensor_1 { + u16 sensor_id; + u16 value; +} __packed; + +struct freq_sensor_2 { + u32 sensor_id; + u16 value; +} __packed; + +struct power_sensor_1 { + u16 sensor_id; + u32 update_tag; + u32 accumulator; + u16 value; +} __packed; + +struct power_sensor_2 { + u32 sensor_id; + u8 function_id; + u8 apss_channel; + u16 reserved; + u32 update_tag; + u64 accumulator; + u16 value; +} __packed; + +struct power_sensor_data { + u16 value; + u32 update_tag; + u64 accumulator; +} __packed; + +struct power_sensor_data_and_time { + u16 update_time; + u16 value; + u32 update_tag; + u64 accumulator; +} __packed; + +struct power_sensor_a0 { + u32 sensor_id; + struct power_sensor_data_and_time system; + u32 reserved; + struct power_sensor_data_and_time proc; + struct power_sensor_data vdd; + struct power_sensor_data vdn; +} __packed; + +struct caps_sensor_1 { + u16 curr_powercap; + u16 curr_powerreading; + u16 norm_powercap; + u16 max_powercap; + u16 min_powercap; + u16 user_powerlimit; +} __packed; + +struct caps_sensor_2 { + u16 curr_powercap; + u16 curr_powerreading; + u16 norm_powercap; + u16 max_powercap; + u16 min_powercap; + u16 user_powerlimit; + u8 user_powerlimit_source; +} __packed; + +struct caps_sensor_3 { + u16 curr_powercap; + u16 curr_powerreading; + u16 norm_powercap; + u16 max_powercap; + u16 hard_min_powercap; + u16 soft_min_powercap; + u16 user_powerlimit; + u8 user_powerlimit_source; +} __packed; + +struct extended_sensor { + u8 name[4]; + u8 flags; + u8 reserved; + u8 data[6]; +} __packed; + +static ssize_t occ_show_error(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct occ *occ = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE - 1, "%d\n", occ->error); +} + +static DEVICE_ATTR(occ_error, 0444, occ_show_error, NULL); + +static void occ_sysfs_notify(struct occ *occ); + +static int occ_poll(struct occ *occ) +{ + struct occ_poll_response_header *header; + u16 checksum = occ->poll_cmd_data + 1; + u8 cmd[8]; + int rc; + + /* big endian */ + cmd[0] = 0; /* sequence number */ + cmd[1] = 0; /* cmd type */ + cmd[2] = 0; /* data length msb */ + cmd[3] = 1; /* data length lsb */ + cmd[4] = occ->poll_cmd_data; /* data */ + cmd[5] = checksum >> 8; /* checksum msb */ + cmd[6] = checksum & 0xFF; /* checksum lsb */ + cmd[7] = 0; + + /* mutex should already be locked if necessary */ + rc = occ->send_cmd(occ, cmd); + if (rc) { + if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD) + occ->error = rc; + + goto done; + } + + /* clear error since communication was successful */ + occ->error_count = 0; + occ->error = 0; + + /* check for safe state */ + header = (struct occ_poll_response_header *)occ->resp.data; + if (header->occ_state == OCC_STATE_SAFE) { + if (occ->last_safe) { + if (time_after(jiffies, + occ->last_safe + OCC_SAFE_TIMEOUT)) + occ->error = -EHOSTDOWN; + } else { + occ->last_safe = jiffies; + } + } else { + occ->last_safe = 0; + } + +done: + occ_sysfs_notify(occ); + return rc; +} + +static int occ_set_user_power_cap(struct occ *occ, u16 user_power_cap) +{ + int rc; + u8 cmd[8]; + u16 checksum = 0x24; + __be16 user_power_cap_be = cpu_to_be16(user_power_cap); + + cmd[0] = 0; + cmd[1] = 0x22; + cmd[2] = 0; + cmd[3] = 2; + + memcpy(&cmd[4], &user_power_cap_be, 2); + + checksum += cmd[4] + cmd[5]; + cmd[6] = checksum >> 8; + cmd[7] = checksum & 0xFF; + + rc = mutex_lock_interruptible(&occ->lock); + if (rc) + return rc; + + rc = occ->send_cmd(occ, cmd); + + mutex_unlock(&occ->lock); + + if (rc) { + if (occ->error_count++ > OCC_ERROR_COUNT_THRESHOLD) + occ->error = rc; + } else { + /* successful communication so clear the error */ + occ->error_count = 0; + occ->error = 0; + } + + return rc; +} + +static int occ_update_response(struct occ *occ) +{ + int rc = mutex_lock_interruptible(&occ->lock); + + if (rc) + return rc; + + /* limit the maximum rate of polling the OCC */ + if (time_after(jiffies, occ->last_update + OCC_UPDATE_FREQUENCY)) { + rc = occ_poll(occ); + occ->last_update = jiffies; + } + + mutex_unlock(&occ->lock); + return rc; +} + +static ssize_t occ_show_temp_1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u32 val = 0; + struct temp_sensor_1 *temp; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + temp = ((struct temp_sensor_1 *)sensors->temp.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be16(&temp->sensor_id); + break; + case 1: + /* millidegrees */ + val = get_unaligned_be16(&temp->value) * 1000; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + +static ssize_t occ_show_temp_2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u32 val = 0; + struct temp_sensor_2 *temp; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + temp = ((struct temp_sensor_2 *)sensors->temp.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be32(&temp->sensor_id); + break; + case 1: + val = temp->value; + if (val == OCC_TEMP_SENSOR_FAULT) + return -EREMOTEIO; + + if (temp->fru_type != OCC_FRU_TYPE_VRM) { + /* sensor not ready */ + if (val == 0) + return -EAGAIN; + + val *= 1000; /* millidegrees */ + } + break; + case 2: + val = temp->fru_type; + break; + case 3: + val = temp->value == OCC_TEMP_SENSOR_FAULT; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + +static ssize_t occ_show_freq_1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u16 val = 0; + struct freq_sensor_1 *freq; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + freq = ((struct freq_sensor_1 *)sensors->freq.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be16(&freq->sensor_id); + break; + case 1: + val = get_unaligned_be16(&freq->value); + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + +static ssize_t occ_show_freq_2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u32 val = 0; + struct freq_sensor_2 *freq; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + freq = ((struct freq_sensor_2 *)sensors->freq.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be32(&freq->sensor_id); + break; + case 1: + val = get_unaligned_be16(&freq->value); + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + +static ssize_t occ_show_power_1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u64 val = 0; + struct power_sensor_1 *power; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + power = ((struct power_sensor_1 *)sensors->power.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be16(&power->sensor_id); + break; + case 1: + val = get_unaligned_be32(&power->update_tag); + break; + case 2: + val = get_unaligned_be32(&power->accumulator); + break; + case 3: + /* microwatts */ + val = get_unaligned_be16(&power->value) * 1000000ULL; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val); +} + +static ssize_t occ_show_power_2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u64 val = 0; + struct power_sensor_2 *power; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + power = ((struct power_sensor_2 *)sensors->power.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be32(&power->sensor_id); + break; + case 1: + val = get_unaligned_be32(&power->update_tag); + break; + case 2: + val = get_unaligned_be64(&power->accumulator); + break; + case 3: + /* microwatts */ + val = get_unaligned_be16(&power->value) * 1000000ULL; + break; + case 4: + val = power->function_id; + break; + case 5: + val = power->apss_channel; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val); +} + +static ssize_t occ_show_power_a0(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u64 val = 0; + struct power_sensor_a0 *power; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + power = ((struct power_sensor_a0 *)sensors->power.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be32(&power->sensor_id); + break; + case 1: + return snprintf(buf, PAGE_SIZE - 1, "system\n"); + case 2: + val = get_unaligned_be16(&power->system.update_time); + break; + case 3: + /* microwatts */ + val = get_unaligned_be16(&power->system.value) * 1000000ULL; + break; + case 4: + val = get_unaligned_be32(&power->system.update_tag); + break; + case 5: + val = get_unaligned_be64(&power->system.accumulator); + break; + case 6: + return snprintf(buf, PAGE_SIZE - 1, "proc\n"); + case 7: + val = get_unaligned_be16(&power->proc.update_time); + break; + case 8: + /* microwatts */ + val = get_unaligned_be16(&power->proc.value) * 1000000ULL; + break; + case 9: + val = get_unaligned_be32(&power->proc.update_tag); + break; + case 10: + val = get_unaligned_be64(&power->proc.accumulator); + break; + case 11: + return snprintf(buf, PAGE_SIZE - 1, "vdd\n"); + case 12: + /* microwatts */ + val = get_unaligned_be16(&power->vdd.value) * 1000000ULL; + break; + case 13: + val = get_unaligned_be32(&power->vdd.update_tag); + break; + case 14: + val = get_unaligned_be64(&power->vdd.accumulator); + break; + case 15: + return snprintf(buf, PAGE_SIZE - 1, "vdn\n"); + case 16: + /* microwatts */ + val = get_unaligned_be16(&power->vdn.value) * 1000000ULL; + break; + case 17: + val = get_unaligned_be32(&power->vdn.update_tag); + break; + case 18: + val = get_unaligned_be64(&power->vdn.accumulator); + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%llu\n", val); +} + +static ssize_t occ_show_caps_1(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u16 val = 0; + struct caps_sensor_1 *caps; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + caps = ((struct caps_sensor_1 *)sensors->caps.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be16(&caps->curr_powercap); + break; + case 1: + val = get_unaligned_be16(&caps->curr_powerreading); + break; + case 2: + val = get_unaligned_be16(&caps->norm_powercap); + break; + case 3: + val = get_unaligned_be16(&caps->max_powercap); + break; + case 4: + val = get_unaligned_be16(&caps->min_powercap); + break; + case 5: + val = get_unaligned_be16(&caps->user_powerlimit); + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + +static ssize_t occ_show_caps_2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u16 val = 0; + struct caps_sensor_2 *caps; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + caps = ((struct caps_sensor_2 *)sensors->caps.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be16(&caps->curr_powercap); + break; + case 1: + val = get_unaligned_be16(&caps->curr_powerreading); + break; + case 2: + val = get_unaligned_be16(&caps->norm_powercap); + break; + case 3: + val = get_unaligned_be16(&caps->max_powercap); + break; + case 4: + val = get_unaligned_be16(&caps->min_powercap); + break; + case 5: + val = get_unaligned_be16(&caps->user_powerlimit); + break; + case 6: + val = caps->user_powerlimit_source; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + +static ssize_t occ_show_caps_3(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + u16 val = 0; + struct caps_sensor_3 *caps; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + caps = ((struct caps_sensor_3 *)sensors->caps.data) + sattr->index; + + switch (sattr->nr) { + case 0: + val = get_unaligned_be16(&caps->curr_powercap); + break; + case 1: + val = get_unaligned_be16(&caps->curr_powerreading); + break; + case 2: + val = get_unaligned_be16(&caps->norm_powercap); + break; + case 3: + val = get_unaligned_be16(&caps->max_powercap); + break; + case 4: + val = get_unaligned_be16(&caps->hard_min_powercap); + break; + case 5: + val = get_unaligned_be16(&caps->user_powerlimit); + break; + case 6: + val = caps->user_powerlimit_source; + break; + case 7: + val = get_unaligned_be16(&caps->soft_min_powercap); + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", val); +} + +static ssize_t occ_store_caps_user(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int rc; + u16 user_power_cap; + struct occ *occ = dev_get_drvdata(dev); + + rc = kstrtou16(buf, 0, &user_power_cap); + if (rc) + return rc; + + rc = occ_set_user_power_cap(occ, user_power_cap); + if (rc) + return rc; + + return count; +} + +static ssize_t occ_show_extended(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + struct extended_sensor *extn; + struct occ *occ = dev_get_drvdata(dev); + struct occ_sensors *sensors = &occ->sensors; + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + extn = ((struct extended_sensor *)sensors->extended.data) + + sattr->index; + + switch (sattr->nr) { + case 0: + rc = snprintf(buf, PAGE_SIZE - 1, "%02x%02x%02x%02x\n", + extn->name[0], extn->name[1], extn->name[2], + extn->name[3]); + break; + case 1: + rc = snprintf(buf, PAGE_SIZE - 1, "%02x\n", extn->flags); + break; + case 2: + rc = snprintf(buf, PAGE_SIZE - 1, "%02x%02x%02x%02x%02x%02x\n", + extn->data[0], extn->data[1], extn->data[2], + extn->data[3], extn->data[4], extn->data[5]); + break; + default: + return -EINVAL; + } + + return rc; +} + +/* + * Some helper macros to make it easier to define an occ_attribute. Since these + * are dynamically allocated, we shouldn't use the existing kernel macros which + * stringify the name argument. + */ +#define ATTR_OCC(_name, _mode, _show, _store) { \ + .attr = { \ + .name = _name, \ + .mode = VERIFY_OCTAL_PERMISSIONS(_mode), \ + }, \ + .show = _show, \ + .store = _store, \ +} + +#define SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index) { \ + .dev_attr = ATTR_OCC(_name, _mode, _show, _store), \ + .index = _index, \ + .nr = _nr, \ +} + +#define OCC_INIT_ATTR(_name, _mode, _show, _store, _nr, _index) \ + ((struct sensor_device_attribute_2) \ + SENSOR_ATTR_OCC(_name, _mode, _show, _store, _nr, _index)) + +/* + * Allocate and instatiate sensor_device_attribute_2s. It's most efficient to + * use our own instead of the built-in hwmon attribute types. + */ +static int occ_setup_sensor_attrs(struct occ *occ) +{ + unsigned int i, s, num_attrs = 0; + struct device *dev = occ->bus_dev; + struct occ_sensors *sensors = &occ->sensors; + struct occ_attribute *attr; + struct temp_sensor_2 *temp; + ssize_t (*show_temp)(struct device *, struct device_attribute *, + char *) = occ_show_temp_1; + ssize_t (*show_freq)(struct device *, struct device_attribute *, + char *) = occ_show_freq_1; + ssize_t (*show_power)(struct device *, struct device_attribute *, + char *) = occ_show_power_1; + ssize_t (*show_caps)(struct device *, struct device_attribute *, + char *) = occ_show_caps_1; + + switch (sensors->temp.version) { + case 1: + num_attrs += (sensors->temp.num_sensors * 2); + break; + case 2: + num_attrs += (sensors->temp.num_sensors * 4); + show_temp = occ_show_temp_2; + break; + default: + sensors->temp.num_sensors = 0; + } + + switch (sensors->freq.version) { + case 2: + show_freq = occ_show_freq_2; + /* fall through */ + case 1: + num_attrs += (sensors->freq.num_sensors * 2); + break; + default: + sensors->freq.num_sensors = 0; + } + + switch (sensors->power.version) { + case 1: + num_attrs += (sensors->power.num_sensors * 4); + break; + case 2: + num_attrs += (sensors->power.num_sensors * 6); + show_power = occ_show_power_2; + break; + case 0xA0: + num_attrs += (sensors->power.num_sensors * 19); + show_power = occ_show_power_a0; + break; + default: + sensors->power.num_sensors = 0; + } + + switch (sensors->caps.version) { + case 1: + num_attrs += (sensors->caps.num_sensors * 6); + break; + case 2: + num_attrs += (sensors->caps.num_sensors * 7); + show_caps = occ_show_caps_2; + break; + case 3: + num_attrs += (sensors->caps.num_sensors * 8); + show_caps = occ_show_caps_3; + break; + default: + sensors->caps.num_sensors = 0; + } + + switch (sensors->extended.version) { + case 1: + num_attrs += (sensors->extended.num_sensors * 3); + break; + default: + sensors->extended.num_sensors = 0; + } + + occ->attrs = devm_kzalloc(dev, sizeof(*occ->attrs) * num_attrs, + GFP_KERNEL); + if (!occ->attrs) + return -ENOMEM; + + /* null-terminated list */ + occ->group.attrs = devm_kzalloc(dev, sizeof(*occ->group.attrs) * + num_attrs + 1, GFP_KERNEL); + if (!occ->group.attrs) + return -ENOMEM; + + attr = occ->attrs; + + for (i = 0; i < sensors->temp.num_sensors; ++i) { + s = i + 1; + temp = ((struct temp_sensor_2 *)sensors->temp.data) + i; + + snprintf(attr->name, sizeof(attr->name), "temp%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL, + 0, i); + attr++; + + if (sensors->temp.version > 1 && + temp->fru_type == OCC_FRU_TYPE_VRM) { + snprintf(attr->name, sizeof(attr->name), + "temp%d_alarm", s); + } else { + snprintf(attr->name, sizeof(attr->name), + "temp%d_input", s); + } + + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_temp, NULL, + 1, i); + attr++; + + if (sensors->temp.version > 1) { + snprintf(attr->name, sizeof(attr->name), + "temp%d_fru_type", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_temp, NULL, 2, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "temp%d_fault", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_temp, NULL, 3, i); + attr++; + } + } + + for (i = 0; i < sensors->freq.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), "freq%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL, + 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "freq%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_freq, NULL, + 1, i); + attr++; + } + + if (sensors->power.version == 0xA0) { + /* Special case for many-attribute power sensor. Split it into + * a sensor number per power type, emulating several sensors. + */ + for (i = 0; i < sensors->power.num_sensors; ++i) { + s = (i * 4) + 1; + + snprintf(attr->name, sizeof(attr->name), + "power%d_id", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 0, i); + attr++; + + /* system power attributes */ + snprintf(attr->name, sizeof(attr->name), + "power%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 1, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_update_time", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 2, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 3, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 4, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 5, i); + attr++; + + s++; + + /* processor power attributes */ + snprintf(attr->name, sizeof(attr->name), + "power%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 6, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_update_time", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 7, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 8, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 9, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 10, i); + attr++; + + s++; + + /* vdd power attributes */ + snprintf(attr->name, sizeof(attr->name), + "power%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 11, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 12, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 13, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 14, i); + attr++; + + s++; + + /* vdn power attributes */ + snprintf(attr->name, sizeof(attr->name), + "power%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 15, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 16, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 17, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 18, i); + attr++; + } + } else { + for (i = 0; i < sensors->power.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), + "power%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_update_tag", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 1, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_accumulator", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 2, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, 3, i); + attr++; + + if (sensors->power.version > 1) { + snprintf(attr->name, sizeof(attr->name), + "power%d_function_id", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, + 4, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "power%d_apss_channel", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_power, NULL, + 5, i); + attr++; + } + } + } + + for (i = 0; i < sensors->caps.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), "caps%d_current", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, + 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "caps%d_reading", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, + 1, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "caps%d_norm", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, + 2, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "caps%d_max", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, show_caps, NULL, + 3, i); + attr++; + + if (sensors->caps.version > 2) { + snprintf(attr->name, sizeof(attr->name), + "caps%d_min_hard", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, 4, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), + "caps%d_min_soft", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, 7, i); + attr++; + } else { + snprintf(attr->name, sizeof(attr->name), "caps%d_min", + s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, 4, i); + attr++; + } + + snprintf(attr->name, sizeof(attr->name), "caps%d_user", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0644, show_caps, + occ_store_caps_user, 5, i); + attr++; + + if (sensors->caps.version > 1) { + snprintf(attr->name, sizeof(attr->name), + "caps%d_user_source", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + show_caps, NULL, 6, i); + attr++; + } + } + + for (i = 0; i < sensors->extended.num_sensors; ++i) { + s = i + 1; + + snprintf(attr->name, sizeof(attr->name), "extn%d_label", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + occ_show_extended, NULL, 0, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "extn%d_flags", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + occ_show_extended, NULL, 1, i); + attr++; + + snprintf(attr->name, sizeof(attr->name), "extn%d_input", s); + attr->sensor = OCC_INIT_ATTR(attr->name, 0444, + occ_show_extended, NULL, 2, i); + attr++; + } + + /* put the sensors in the group */ + for (i = 0; i < num_attrs; ++i) { + sysfs_attr_init(&occ->attrs[i].sensor.dev_attr.attr); + occ->group.attrs[i] = &occ->attrs[i].sensor.dev_attr.attr; + } + + return 0; +} + +static ssize_t occ_show_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int rc; + int val = 0; + struct occ *occ = dev_get_drvdata(dev); + struct occ_poll_response_header *header; + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + rc = occ_update_response(occ); + if (rc) + return rc; + + header = (struct occ_poll_response_header *)occ->resp.data; + + switch (sattr->index) { + case 0: + val = (header->status & OCC_STAT_MASTER) ? 1 : 0; + break; + case 1: + val = (header->status & OCC_STAT_ACTIVE) ? 1 : 0; + break; + case 2: + val = (header->ext_status & OCC_EXT_STAT_DVFS_OT) ? 1 : 0; + break; + case 3: + val = (header->ext_status & OCC_EXT_STAT_DVFS_POWER) ? 1 : 0; + break; + case 4: + val = (header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) ? 1 : 0; + break; + case 5: + val = (header->ext_status & OCC_EXT_STAT_QUICK_DROP) ? 1 : 0; + break; + case 6: + val = header->occ_state; + break; + case 7: + if (header->status & OCC_STAT_MASTER) + val = hweight8(header->occs_present); + else + val = 1; + break; + default: + return -EINVAL; + } + + return snprintf(buf, PAGE_SIZE - 1, "%d\n", val); +} + +static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_show_status, NULL, 0); +static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_show_status, NULL, 1); +static SENSOR_DEVICE_ATTR(occ_dvfs_ot, 0444, occ_show_status, NULL, 2); +static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_show_status, NULL, 3); +static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_show_status, NULL, 4); +static SENSOR_DEVICE_ATTR(occ_quick_drop, 0444, occ_show_status, NULL, 5); +static SENSOR_DEVICE_ATTR(occ_status, 0444, occ_show_status, NULL, 6); +static SENSOR_DEVICE_ATTR(occs_present, 0444, occ_show_status, NULL, 7); + +static struct attribute *occ_attributes[] = { + &sensor_dev_attr_occ_master.dev_attr.attr, + &sensor_dev_attr_occ_active.dev_attr.attr, + &sensor_dev_attr_occ_dvfs_ot.dev_attr.attr, + &sensor_dev_attr_occ_dvfs_power.dev_attr.attr, + &sensor_dev_attr_occ_mem_throttle.dev_attr.attr, + &sensor_dev_attr_occ_quick_drop.dev_attr.attr, + &sensor_dev_attr_occ_status.dev_attr.attr, + &sensor_dev_attr_occs_present.dev_attr.attr, + &dev_attr_occ_error.attr, + NULL +}; + +static const struct attribute_group occ_attr_group = { + .attrs = occ_attributes, +}; + +static void occ_sysfs_notify(struct occ *occ) +{ + const char *name; + struct occ_poll_response_header *header = + (struct occ_poll_response_header *)occ->resp.data; + + /* sysfs attributes aren't loaded yet; don't proceed */ + if (!occ->hwmon) + goto done; + + if (header->occs_present != occ->previous_occs_present && + (header->status & OCC_STAT_MASTER)) { + name = sensor_dev_attr_occs_present.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + + if ((header->ext_status & OCC_EXT_STAT_DVFS_OT) != + (occ->previous_ext_status & OCC_EXT_STAT_DVFS_OT)) { + name = sensor_dev_attr_occ_dvfs_ot.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + + if ((header->ext_status & OCC_EXT_STAT_DVFS_POWER) != + (occ->previous_ext_status & OCC_EXT_STAT_DVFS_POWER)) { + name = sensor_dev_attr_occ_dvfs_power.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + + if ((header->ext_status & OCC_EXT_STAT_MEM_THROTTLE) != + (occ->previous_ext_status & OCC_EXT_STAT_MEM_THROTTLE)) { + name = sensor_dev_attr_occ_mem_throttle.dev_attr.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + + if (occ->error && occ->error != occ->previous_error) { + name = dev_attr_occ_error.attr.name; + sysfs_notify(&occ->bus_dev->kobj, NULL, name); + } + +done: + occ->previous_error = occ->error; + occ->previous_ext_status = header->ext_status; + occ->previous_occs_present = header->occs_present; +} + +/* only need to do this once at startup, as OCC won't change sensors on us */ +static void occ_parse_poll_response(struct occ *occ) +{ + unsigned int i, offset = 0, size = 0, old_offset; + struct occ_sensor *sensor; + struct occ_sensors *sensors = &occ->sensors; + struct occ_response *resp = &occ->resp; + struct occ_poll_response *poll = + (struct occ_poll_response *)&resp->data[0]; + struct occ_poll_response_header *header = &poll->header; + struct occ_sensor_data_block *block = &poll->block; + + dev_info(occ->bus_dev, "OCC found, code level: %.16s\n", + header->occ_code_level); + + for (i = 0; i < header->num_sensor_data_blocks; ++i) { + block = (struct occ_sensor_data_block *)((u8 *)block + offset); + old_offset = offset; + offset = (block->header.num_sensors * + block->header.sensor_length) + sizeof(block->header); + size += offset; + + /* validate all the length/size fields */ + if ((size + sizeof(*header)) >= OCC_RESP_DATA_BYTES) { + dev_warn(occ->bus_dev, "exceeded response buffer\n"); + return; + } + + dev_dbg(occ->bus_dev, " %04x..%04x: %.4s (%d sensors)\n", + old_offset, offset - 1, block->header.eye_catcher, + block->header.num_sensors); + + /* match sensor block type */ + if (strncmp(block->header.eye_catcher, "TEMP", 4) == 0) + sensor = &sensors->temp; + else if (strncmp(block->header.eye_catcher, "FREQ", 4) == 0) + sensor = &sensors->freq; + else if (strncmp(block->header.eye_catcher, "POWR", 4) == 0) + sensor = &sensors->power; + else if (strncmp(block->header.eye_catcher, "CAPS", 4) == 0) + sensor = &sensors->caps; + else if (strncmp(block->header.eye_catcher, "EXTN", 4) == 0) + sensor = &sensors->extended; + else { + dev_warn(occ->bus_dev, "sensor not supported %.4s\n", + block->header.eye_catcher); + continue; + } + + sensor->num_sensors = block->header.num_sensors; + sensor->version = block->header.sensor_format; + sensor->data = &block->data; + } + dev_dbg(occ->bus_dev, "Max resp size: %u+%zd=%zd\n", + size, sizeof(*header), size + sizeof(*header)); +} + +int occ_setup(struct occ *occ, const char *name) +{ + int rc; + + mutex_init(&occ->lock); + occ->groups[0] = &occ->group; + + /* no need to lock */ + rc = occ_poll(occ); + if (rc == -ESHUTDOWN) { + dev_info(occ->bus_dev, "host is not ready\n"); + return rc; + } else if (rc < 0) { + dev_err(occ->bus_dev, "failed to get OCC poll response: %d\n", + rc); + return rc; + } + + occ_parse_poll_response(occ); + + rc = occ_setup_sensor_attrs(occ); + if (rc) { + dev_err(occ->bus_dev, "failed to setup sensor attrs: %d\n", + rc); + return rc; + } + + occ->hwmon = devm_hwmon_device_register_with_groups(occ->bus_dev, name, + occ, occ->groups); + if (IS_ERR(occ->hwmon)) { + rc = PTR_ERR(occ->hwmon); + dev_err(occ->bus_dev, "failed to register hwmon device: %d\n", + rc); + return rc; + } + + rc = sysfs_create_group(&occ->bus_dev->kobj, &occ_attr_group); + if (rc) + dev_warn(occ->bus_dev, "failed to create status attrs: %d\n", + rc); + + return 0; +} + +void occ_shutdown(struct occ *occ) +{ + sysfs_remove_group(&occ->bus_dev->kobj, &occ_attr_group); +}
diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h new file mode 100644 index 0000000..ffc809f --- /dev/null +++ b/drivers/hwmon/occ/common.h
@@ -0,0 +1,124 @@ +/* + * Copyright 2017 IBM Corp. + * + * 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. + */ + +#ifndef OCC_COMMON_H +#define OCC_COMMON_H + +#include <linux/hwmon-sysfs.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> + +struct device; + +#define OCC_RESP_DATA_BYTES 4089 + +/* Same response format for all OCC versions. + * Allocate the largest possible response. + */ +struct occ_response { + u8 seq_no; + u8 cmd_type; + u8 return_status; + __be16 data_length; + u8 data[OCC_RESP_DATA_BYTES]; + __be16 checksum; +} __packed; + +struct occ_sensor_data_block_header { + u8 eye_catcher[4]; + u8 reserved; + u8 sensor_format; + u8 sensor_length; + u8 num_sensors; +} __packed; + +struct occ_sensor_data_block { + struct occ_sensor_data_block_header header; + u32 data; +} __packed; + +struct occ_poll_response_header { + u8 status; + u8 ext_status; + u8 occs_present; + u8 config_data; + u8 occ_state; + u8 mode; + u8 ips_status; + u8 error_log_id; + __be32 error_log_start_address; + __be16 error_log_length; + u16 reserved; + u8 occ_code_level[16]; + u8 eye_catcher[6]; + u8 num_sensor_data_blocks; + u8 sensor_data_block_header_version; +} __packed; + +struct occ_poll_response { + struct occ_poll_response_header header; + struct occ_sensor_data_block block; +} __packed; + +struct occ_sensor { + u8 num_sensors; + u8 version; + void *data; /* pointer to sensor data start within response */ +}; + +/* OCC only provides one sensor data block of each type, but any number of + * sensors within that block. + */ +struct occ_sensors { + struct occ_sensor temp; + struct occ_sensor freq; + struct occ_sensor power; + struct occ_sensor caps; + struct occ_sensor extended; +}; + +/* Use our own attribute struct so we can dynamically allocate space for the + * name. + */ +struct occ_attribute { + char name[32]; + struct sensor_device_attribute_2 sensor; +}; + +struct occ { + struct device *bus_dev; + + struct occ_response resp; + struct occ_sensors sensors; + + u8 poll_cmd_data; /* to perform OCC poll command */ + int (*send_cmd)(struct occ *occ, u8 *cmd); + + unsigned long last_update; + struct mutex lock; /* lock OCC access */ + + struct device *hwmon; + struct occ_attribute *attrs; + struct attribute_group group; + const struct attribute_group *groups[2]; + + int error; + unsigned int error_count; /* number of errors observed */ + unsigned long last_safe; /* time OCC entered safe state */ + + /* store previous poll state to compare; notify sysfs on change */ + int previous_error; + u8 previous_ext_status; + u8 previous_occs_present; +}; + +int occ_setup(struct occ *occ, const char *name); +void occ_shutdown(struct occ *occ); + +#endif /* OCC_COMMON_H */
diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c new file mode 100644 index 0000000..d719632 --- /dev/null +++ b/drivers/hwmon/occ/p8_i2c.c
@@ -0,0 +1,262 @@ +/* + * Copyright 2017 IBM Corp. + * + * 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/device.h> +#include <linux/errno.h> +#include <linux/fsi-occ.h> +#include <linux/i2c.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <asm/unaligned.h> + +#include "common.h" + +#define OCC_TIMEOUT_MS 1000 +#define OCC_CMD_IN_PRG_WAIT_MS 50 + +/* OCB (on-chip control bridge - interface to OCC) registers */ +#define OCB_DATA1 0x6B035 +#define OCB_ADDR 0x6B070 +#define OCB_DATA3 0x6B075 + +/* OCC SRAM address space */ +#define OCC_SRAM_ADDR_CMD 0xFFFF6000 +#define OCC_SRAM_ADDR_RESP 0xFFFF7000 + +#define OCC_DATA_ATTN 0x20010000 + +struct p8_i2c_occ { + struct occ occ; + struct i2c_client *client; +}; + +#define to_p8_i2c_occ(x) container_of((x), struct p8_i2c_occ, occ) + +static int p8_i2c_occ_getscom(struct i2c_client *client, u32 address, u8 *data) +{ + ssize_t rc; + __be64 buf; + struct i2c_msg msgs[2]; + + /* p8 i2c slave requires shift */ + address <<= 1; + + msgs[0].addr = client->addr; + msgs[0].flags = client->flags & I2C_M_TEN; + msgs[0].len = sizeof(u32); + /* address is a scom address; bus-endian */ + msgs[0].buf = (char *)&address; + + /* data from OCC is big-endian */ + msgs[1].addr = client->addr; + msgs[1].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[1].len = sizeof(u64); + msgs[1].buf = (char *)&buf; + + rc = i2c_transfer(client->adapter, msgs, 2); + if (rc < 0) + return rc; + + *(u64 *)data = be64_to_cpu(buf); + + return 0; +} + +static int p8_i2c_occ_putscom(struct i2c_client *client, u32 address, u8 *data) +{ + u32 buf[3]; + ssize_t rc; + + /* p8 i2c slave requires shift */ + address <<= 1; + + /* address is bus-endian; data passed through from user as-is */ + buf[0] = address; + memcpy(&buf[1], &data[4], sizeof(u32)); + memcpy(&buf[2], data, sizeof(u32)); + + rc = i2c_master_send(client, (const char *)buf, sizeof(buf)); + if (rc < 0) + return rc; + else if (rc != sizeof(buf)) + return -EIO; + + return 0; +} + +static int p8_i2c_occ_putscom_u32(struct i2c_client *client, u32 address, + u32 data0, u32 data1) +{ + u8 buf[8]; + + memcpy(buf, &data0, 4); + memcpy(buf + 4, &data1, 4); + + return p8_i2c_occ_putscom(client, address, buf); +} + +static int p8_i2c_occ_putscom_be(struct i2c_client *client, u32 address, + u8 *data) +{ + __be32 data0, data1; + + memcpy(&data0, data, 4); + memcpy(&data1, data + 4, 4); + + return p8_i2c_occ_putscom_u32(client, address, be32_to_cpu(data0), + be32_to_cpu(data1)); +} + +static int p8_i2c_occ_send_cmd(struct occ *occ, u8 *cmd) +{ + int i, rc; + unsigned long start; + u16 data_length; + const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS); + const long int wait_time = msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS); + struct p8_i2c_occ *p8_i2c_occ = to_p8_i2c_occ(occ); + struct i2c_client *client = p8_i2c_occ->client; + struct occ_response *resp = &occ->resp; + + start = jiffies; + + /* set sram address for command */ + rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, OCC_SRAM_ADDR_CMD, 0); + if (rc) + return rc; + + /* write command (expected to already be BE), we need bus-endian... */ + rc = p8_i2c_occ_putscom_be(client, OCB_DATA3, cmd); + if (rc) + return rc; + + /* trigger OCC attention */ + rc = p8_i2c_occ_putscom_u32(client, OCB_DATA1, OCC_DATA_ATTN, 0); + if (rc) + return rc; + + do { + /* set sram address for response */ + rc = p8_i2c_occ_putscom_u32(client, OCB_ADDR, + OCC_SRAM_ADDR_RESP, 0); + if (rc) + return rc; + + rc = p8_i2c_occ_getscom(client, OCB_DATA3, (u8 *)resp); + if (rc) + return rc; + + /* wait for OCC */ + if (resp->return_status == OCC_RESP_CMD_IN_PRG) { + rc = -EALREADY; + + if (time_after(jiffies, start + timeout)) + break; + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(wait_time); + } + } while (rc); + + /* check the OCC response */ + switch (resp->return_status) { + case OCC_RESP_CMD_IN_PRG: + rc = -ETIMEDOUT; + break; + case OCC_RESP_SUCCESS: + rc = 0; + break; + case OCC_RESP_CMD_INVAL: + case OCC_RESP_CMD_LEN_INVAL: + case OCC_RESP_DATA_INVAL: + case OCC_RESP_CHKSUM_ERR: + rc = -EINVAL; + break; + case OCC_RESP_INT_ERR: + case OCC_RESP_BAD_STATE: + case OCC_RESP_CRIT_EXCEPT: + case OCC_RESP_CRIT_INIT: + case OCC_RESP_CRIT_WATCHDOG: + case OCC_RESP_CRIT_OCB: + case OCC_RESP_CRIT_HW: + rc = -EREMOTEIO; + break; + default: + rc = -EPROTO; + } + + if (rc < 0) + return rc; + + data_length = get_unaligned_be16(&resp->data_length); + if (data_length > OCC_RESP_DATA_BYTES) + return -EMSGSIZE; + + /* fetch the rest of the response data */ + for (i = 8; i < data_length + 7; i += 8) { + rc = p8_i2c_occ_getscom(client, OCB_DATA3, ((u8 *)resp) + i); + if (rc) + return rc; + } + + return 0; +} + +static int p8_i2c_occ_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct occ *occ; + struct p8_i2c_occ *p8_i2c_occ = devm_kzalloc(&client->dev, + sizeof(*p8_i2c_occ), + GFP_KERNEL); + if (!p8_i2c_occ) + return -ENOMEM; + + p8_i2c_occ->client = client; + occ = &p8_i2c_occ->occ; + occ->bus_dev = &client->dev; + dev_set_drvdata(&client->dev, occ); + + occ->poll_cmd_data = 0x10; /* P8 OCC poll data */ + occ->send_cmd = p8_i2c_occ_send_cmd; + + return occ_setup(occ, "p8_occ"); +} + +static int p8_i2c_occ_remove(struct i2c_client *client) +{ + struct occ *occ = dev_get_drvdata(&client->dev); + + occ_shutdown(occ); + + return 0; +} + +static const struct of_device_id p8_i2c_occ_of_match[] = { + { .compatible = "ibm,p8-occ-hwmon" }, + {} +}; +MODULE_DEVICE_TABLE(of, p8_i2c_occ_of_match); + +static struct i2c_driver p8_i2c_occ_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "occ-hwmon", + .of_match_table = p8_i2c_occ_of_match, + }, + .probe = p8_i2c_occ_probe, + .remove = p8_i2c_occ_remove, +}; + +module_i2c_driver(p8_i2c_occ_driver); + +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>"); +MODULE_DESCRIPTION("BMC P8 OCC hwmon driver"); +MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c new file mode 100644 index 0000000..34fe4d3 --- /dev/null +++ b/drivers/hwmon/occ/p9_sbe.c
@@ -0,0 +1,115 @@ +/* + * Copyright 2017 IBM Corp. + * + * 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/device.h> +#include <linux/errno.h> +#include <linux/fsi-occ.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "common.h" + +struct p9_sbe_occ { + struct occ occ; + struct device *sbe; +}; + +#define to_p9_sbe_occ(x) container_of((x), struct p9_sbe_occ, occ) + +static int p9_sbe_occ_send_cmd(struct occ *occ, u8 *cmd) +{ + struct occ_response *resp = &occ->resp; + struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ); + size_t resp_len = sizeof(*resp); + int rc; + + rc = fsi_occ_submit(ctx->sbe, cmd, 8, resp, &resp_len); + if (rc < 0) + return rc; + + switch (resp->return_status) { + case OCC_RESP_CMD_IN_PRG: + rc = -ETIMEDOUT; + break; + case OCC_RESP_SUCCESS: + rc = 0; + break; + case OCC_RESP_CMD_INVAL: + case OCC_RESP_CMD_LEN_INVAL: + case OCC_RESP_DATA_INVAL: + case OCC_RESP_CHKSUM_ERR: + rc = -EINVAL; + break; + case OCC_RESP_INT_ERR: + case OCC_RESP_BAD_STATE: + case OCC_RESP_CRIT_EXCEPT: + case OCC_RESP_CRIT_INIT: + case OCC_RESP_CRIT_WATCHDOG: + case OCC_RESP_CRIT_OCB: + case OCC_RESP_CRIT_HW: + rc = -EREMOTEIO; + break; + default: + rc = -EPROTO; + } + + return rc; +} + +static int p9_sbe_occ_probe(struct platform_device *pdev) +{ + struct occ *occ; + int rc; + struct p9_sbe_occ *ctx = devm_kzalloc(&pdev->dev, + sizeof(*ctx), + GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->sbe = pdev->dev.parent; + occ = &ctx->occ; + occ->bus_dev = &pdev->dev; + platform_set_drvdata(pdev, occ); + + occ->poll_cmd_data = 0x20; /* P9 OCC poll data */ + occ->send_cmd = p9_sbe_occ_send_cmd; + + rc = occ_setup(occ, "p9_occ"); + + /* Host is shutdown, don't spew errors */ + if (rc == -ESHUTDOWN) + rc = -ENODEV; + return rc; +} + +static int p9_sbe_occ_remove(struct platform_device *pdev) +{ + struct occ *occ = platform_get_drvdata(pdev); + struct p9_sbe_occ *ctx = to_p9_sbe_occ(occ); + + ctx->sbe = NULL; + + occ_shutdown(occ); + + return 0; +} + +static struct platform_driver p9_sbe_occ_driver = { + .driver = { + .name = "occ-hwmon", + }, + .probe = p9_sbe_occ_probe, + .remove = p9_sbe_occ_remove, +}; + +module_platform_driver(p9_sbe_occ_driver); + +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>"); +MODULE_DESCRIPTION("BMC P9 OCC hwmon driver"); +MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/pmbus/max31785.c b/drivers/hwmon/pmbus/max31785.c index c9dc879..393641d0 100644 --- a/drivers/hwmon/pmbus/max31785.c +++ b/drivers/hwmon/pmbus/max31785.c
@@ -16,40 +16,126 @@ enum max31785_regs { MFR_REVISION = 0x9b, + MFR_FAULT_RESPONSE = 0xd9, + MFR_TEMP_SENSOR_CONFIG = 0xf0, MFR_FAN_CONFIG = 0xf1, + MFR_FAN_FAULT_LIMIT = 0xf5, }; #define MAX31785 0x3030 #define MAX31785A 0x3040 #define MFR_FAN_CONFIG_DUAL_TACH BIT(12) +#define MFR_FAN_CONFIG_TSFO BIT(9) +#define MFR_FAN_CONFIG_TACHO BIT(8) +#define MFR_FAN_CONFIG_HEALTH BIT(4) +#define MFR_FAN_CONFIG_ROTOR_HI_LO BIT(3) +#define MFR_FAN_CONFIG_ROTOR BIT(2) + +#define MFR_FAULT_RESPONSE_MONITOR BIT(0) #define MAX31785_NR_PAGES 23 #define MAX31785_NR_FAN_PAGES 6 +/* + * MAX31785 dragons ahead + * + * We see weird issues where some transfers fail. There doesn't appear to be + * any pattern to the problem, so below we wrap all the read/write calls with a + * retry. The device provides no indication of this besides NACK'ing master + * Txs; no bits are set in STATUS_BYTE to suggest anything has gone wrong. + */ + +#define max31785_retry(_func, ...) ({ \ + /* All relevant functions return int, sue me */ \ + int _ret = _func(__VA_ARGS__); \ + if (_ret == -EIO) \ + _ret = _func(__VA_ARGS__); \ + _ret; \ +}) + +static int max31785_i2c_smbus_read_byte_data(struct i2c_client *client, + int command) +{ + return max31785_retry(i2c_smbus_read_byte_data, client, command); +} + + +static int max31785_i2c_smbus_write_byte_data(struct i2c_client *client, + int command, u16 data) +{ + return max31785_retry(i2c_smbus_write_byte_data, client, command, data); +} + +static int max31785_i2c_smbus_read_word_data(struct i2c_client *client, + int command) +{ + return max31785_retry(i2c_smbus_read_word_data, client, command); +} + +static int max31785_i2c_smbus_write_word_data(struct i2c_client *client, + int command, u16 data) +{ + return max31785_retry(i2c_smbus_write_word_data, client, command, data); +} + +static int max31785_pmbus_write_byte(struct i2c_client *client, int page, + u8 value) +{ + return max31785_retry(pmbus_write_byte, client, page, value); +} + +static int max31785_pmbus_read_byte_data(struct i2c_client *client, int page, + int command) +{ + return max31785_retry(pmbus_read_byte_data, client, page, command); +} + +static int max31785_pmbus_write_byte_data(struct i2c_client *client, int page, + int command, u16 data) +{ + return max31785_retry(pmbus_write_byte_data, client, page, command, + data); +} + +static int max31785_pmbus_read_word_data(struct i2c_client *client, int page, + int command) +{ + return max31785_retry(pmbus_read_word_data, client, page, command); +} + +static int max31785_pmbus_write_word_data(struct i2c_client *client, int page, + int command, u16 data) +{ + return max31785_retry(pmbus_write_word_data, client, page, command, + data); +} + static int max31785_read_byte_data(struct i2c_client *client, int page, int reg) { - if (page < MAX31785_NR_PAGES) - return -ENODATA; - switch (reg) { case PMBUS_VOUT_MODE: - return -ENOTSUPP; + if (page >= MAX31785_NR_PAGES) + return -ENOTSUPP; + break; case PMBUS_FAN_CONFIG_12: - return pmbus_read_byte_data(client, page - MAX31785_NR_PAGES, - reg); + if (page >= MAX31785_NR_PAGES) + return max31785_pmbus_read_byte_data(client, + page - MAX31785_NR_PAGES, + reg); + break; } - return -ENODATA; + return max31785_pmbus_read_byte_data(client, page, reg); } static int max31785_write_byte(struct i2c_client *client, int page, u8 value) { - if (page < MAX31785_NR_PAGES) - return -ENODATA; + if (page >= MAX31785_NR_PAGES) + return -ENOTSUPP; - return -ENOTSUPP; + return max31785_pmbus_write_byte(client, page, value); } static int max31785_read_long_data(struct i2c_client *client, int page, @@ -110,11 +196,13 @@ static int max31785_get_pwm_mode(struct i2c_client *client, int page) int config; int command; - config = pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); + config = max31785_pmbus_read_byte_data(client, page, + PMBUS_FAN_CONFIG_12); if (config < 0) return config; - command = pmbus_read_word_data(client, page, PMBUS_FAN_COMMAND_1); + command = max31785_pmbus_read_word_data(client, page, + PMBUS_FAN_COMMAND_1); if (command < 0) return command; @@ -138,15 +226,14 @@ static int max31785_read_word_data(struct i2c_client *client, int page, switch (reg) { case PMBUS_READ_FAN_SPEED_1: if (page < MAX31785_NR_PAGES) - return -ENODATA; + return max31785_pmbus_read_word_data(client, page, reg); rv = max31785_read_long_data(client, page - MAX31785_NR_PAGES, reg, &val); if (rv < 0) return rv; - rv = (val >> 16) & 0xffff; - break; + return (val >> 16) & 0xffff; case PMBUS_FAN_COMMAND_1: /* * PMBUS_FAN_COMMAND_x is probed to judge whether or not to @@ -154,20 +241,28 @@ static int max31785_read_word_data(struct i2c_client *client, int page, * * Don't expose fan_target attribute for virtual pages. */ - rv = (page >= MAX31785_NR_PAGES) ? -ENOTSUPP : -ENODATA; + if (page >= MAX31785_NR_PAGES) + return -ENOTSUPP; break; + case PMBUS_VIRT_FAN_TARGET_1: + if (page >= MAX31785_NR_PAGES) + return -ENOTSUPP; + + return -ENODATA; case PMBUS_VIRT_PWM_1: - rv = max31785_get_pwm(client, page); - break; + return max31785_get_pwm(client, page); case PMBUS_VIRT_PWM_ENABLE_1: - rv = max31785_get_pwm_mode(client, page); - break; + return max31785_get_pwm_mode(client, page); default: - rv = -ENODATA; + if (page >= MAX31785_NR_PAGES) + return -ENXIO; break; } - return rv; + if (reg >= PMBUS_VIRT_BASE) + return -ENXIO; + + return max31785_pmbus_read_word_data(client, page, reg); } static inline u32 max31785_scale_pwm(u32 sensor_val) @@ -191,6 +286,31 @@ static inline u32 max31785_scale_pwm(u32 sensor_val) return (sensor_val * 100) / 255; } +static int max31785_update_fan(struct i2c_client *client, int page, + u8 config, u8 mask, u16 command) +{ + int from, rv; + u8 to; + + from = max31785_pmbus_read_byte_data(client, page, PMBUS_FAN_CONFIG_12); + if (from < 0) + return from; + + to = (from & ~mask) | (config & mask); + + if (to != from) { + rv = max31785_pmbus_write_byte_data(client, page, + PMBUS_FAN_CONFIG_12, to); + if (rv < 0) + return rv; + } + + rv = max31785_pmbus_write_word_data(client, page, PMBUS_FAN_COMMAND_1, + command); + + return rv; +} + static int max31785_pwm_enable(struct i2c_client *client, int page, u16 word) { @@ -220,15 +340,18 @@ static int max31785_pwm_enable(struct i2c_client *client, int page, return -EINVAL; } - return pmbus_update_fan(client, page, 0, config, PB_FAN_1_RPM, rate); + return max31785_update_fan(client, page, config, PB_FAN_1_RPM, rate); } static int max31785_write_word_data(struct i2c_client *client, int page, int reg, u16 word) { switch (reg) { + case PMBUS_VIRT_FAN_TARGET_1: + return max31785_update_fan(client, page, PB_FAN_1_RPM, + PB_FAN_1_RPM, word); case PMBUS_VIRT_PWM_1: - return pmbus_update_fan(client, page, 0, 0, PB_FAN_1_RPM, + return max31785_update_fan(client, page, 0, PB_FAN_1_RPM, max31785_scale_pwm(word)); case PMBUS_VIRT_PWM_ENABLE_1: return max31785_pwm_enable(client, page, word); @@ -236,7 +359,279 @@ static int max31785_write_word_data(struct i2c_client *client, int page, break; } - return -ENODATA; + if (reg < PMBUS_VIRT_BASE) + return max31785_pmbus_write_word_data(client, page, reg, word); + + return -ENXIO; +} + +/* + * Returns negative error codes if an unrecoverable problem is detected, 0 if a + * recoverable problem is detected, or a positive value on success. + */ +static int max31785_of_fan_config(struct i2c_client *client, + struct pmbus_driver_info *info, + struct device_node *child) +{ + int mfr_cfg = 0, mfr_fault_resp = 0, pb_cfg; + struct device *dev = &client->dev; + char *lock_polarity = NULL; + const char *sval; + u32 page; + u32 uval; + int ret; + + if (!of_device_is_compatible(child, "pmbus-fan")) + return 0; + + ret = of_property_read_u32(child, "reg", &page); + if (ret < 0) { + dev_err(&client->dev, "Missing valid reg property\n"); + return ret; + } + + if (!(info->func[page] & PMBUS_HAVE_FAN12)) { + dev_err(dev, "Page %d does not have fan capabilities\n", page); + return -ENXIO; + } + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + pb_cfg = max31785_i2c_smbus_read_byte_data(client, PMBUS_FAN_CONFIG_12); + if (pb_cfg < 0) + return pb_cfg; + + if (of_property_read_bool(child->parent, "use-stored-presence")) { + if (!(pb_cfg & PB_FAN_1_INSTALLED)) + dev_info(dev, "Fan %d is configured but not installed\n", + page); + } else { + pb_cfg |= PB_FAN_1_INSTALLED; + } + + ret = of_property_read_string(child, "maxim,fan-rotor-input", &sval); + if (ret < 0) { + dev_err(dev, "Missing valid maxim,fan-rotor-input property for fan %d\n", + page); + return ret; + } + + if (strcmp("tach", sval) && strcmp("lock", sval)) { + dev_err(dev, "maxim,fan-rotor-input has invalid value for fan %d: %s\n", + page, sval); + return -EINVAL; + } else if (!strcmp("lock", sval)) { + mfr_cfg |= MFR_FAN_CONFIG_ROTOR; + + ret = max31785_i2c_smbus_write_word_data(client, + MFR_FAN_FAULT_LIMIT, + 1); + if (ret < 0) + return ret; + + ret = of_property_read_string(child, "maxim,fan-lock-polarity", + &sval); + if (ret < 0) { + dev_err(dev, "Missing valid maxim,fan-lock-polarity property for fan %d\n", + page); + return ret; + } + + if (strcmp("low", sval) && strcmp("high", sval)) { + dev_err(dev, "maxim,fan-lock-polarity has invalid value for fan %d: %s\n", + page, lock_polarity); + return -EINVAL; + } else if (!strcmp("high", sval)) + mfr_cfg |= MFR_FAN_CONFIG_ROTOR_HI_LO; + } + + if (!of_property_read_string(child, "fan-mode", &sval)) { + if (!strcmp("rpm", sval)) + pb_cfg |= PB_FAN_1_RPM; + else if (!strcmp("pwm", sval)) + pb_cfg &= ~PB_FAN_1_RPM; + else { + dev_err(dev, "fan-mode has invalid value for fan %d: %s\n", + page, sval); + return -EINVAL; + } + } + + ret = of_property_read_u32(child, "tach-pulses", &uval); + if (ret < 0) { + pb_cfg &= ~PB_FAN_1_PULSE_MASK; + } else if (uval && (uval - 1) < 4) { + pb_cfg = ((pb_cfg & ~PB_FAN_1_PULSE_MASK) | ((uval - 1) << 4)); + } else { + dev_err(dev, "tach-pulses has invalid value for fan %d: %u\n", + page, uval); + return -EINVAL; + } + + if (of_property_read_bool(child, "maxim,fan-health")) + mfr_cfg |= MFR_FAN_CONFIG_HEALTH; + + if (of_property_read_bool(child, "maxim,fan-no-watchdog") || + of_property_read_bool(child, "maxim,tmp-no-fault-ramp")) + mfr_cfg |= MFR_FAN_CONFIG_TSFO; + + if (of_property_read_bool(child, "maxim,fan-dual-tach")) + mfr_cfg |= MFR_FAN_CONFIG_DUAL_TACH; + + if (of_property_read_bool(child, "maxim,fan-no-fault-ramp")) + mfr_cfg |= MFR_FAN_CONFIG_TACHO; + + if (!of_property_read_u32(child, "maxim,fan-startup", &uval)) { + uval /= 2; + if (uval < 5) { + mfr_cfg |= uval; + } else { + dev_err(dev, "maxim,fan-startup has invalid value for fan %d: %u\n", + page, uval); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "maxim,fan-ramp", &uval)) { + if (uval < 8) { + mfr_cfg |= uval << 5; + } else { + dev_err(dev, "maxim,fan-ramp has invalid value for fan %d: %u\n", + page, uval); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "maxim,tmp-hysteresis", &uval)) { + uval /= 2; + uval -= 1; + if (uval < 4) { + mfr_cfg |= uval << 10; + } else { + dev_err(dev, "maxim,tmp-hysteresis has invalid value for fan %d, %u\n", + page, uval); + return -EINVAL; + } + } + + if (!of_property_read_u32(child, "maxim,fan-pwm-freq", &uval)) { + u16 val; + + if (uval == 30) { + val = 0; + } else if (uval == 50) { + val = 1; + } else if (uval == 100) { + val = 2; + } else if (uval == 150) { + val = 3; + } else if (uval == 25000) { + val = 7; + } else { + dev_err(dev, "maxim,fan-pwm-freq has invalid value for fan %d: %u\n", + page, uval); + return -EINVAL; + } + + mfr_cfg |= val << 13; + } + + if (of_property_read_bool(child, "maxim,fan-fault-pin-mon")) + mfr_fault_resp |= MFR_FAULT_RESPONSE_MONITOR; + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_FAN_CONFIG_12, + pb_cfg & ~PB_FAN_1_INSTALLED); + if (ret < 0) + return ret; + + ret = max31785_i2c_smbus_write_word_data(client, MFR_FAN_CONFIG, + mfr_cfg); + if (ret < 0) + return ret; + + ret = max31785_i2c_smbus_write_byte_data(client, MFR_FAULT_RESPONSE, + mfr_fault_resp); + if (ret < 0) + return ret; + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_FAN_CONFIG_12, + pb_cfg); + if (ret < 0) + return ret; + + /* + * Fans are on pages 0 - 5. If the page property of a fan node is + * greater than 5 we will have errored in checks above out above. + * Therefore we don't need to cope with values up to 31, and the int + * return type is enough. + * + * The bit mask return value is used to populate a bitfield of fans + * who are both configured in the devicetree _and_ reported as + * installed by the hardware. Any fans that are not configured in the + * devicetree but are reported as installed by the hardware will have + * their hardware configuration updated to unset the installed bit. + */ + return BIT(page); +} + +static int max31785_of_tmp_config(struct i2c_client *client, + struct pmbus_driver_info *info, + struct device_node *child) +{ + struct device *dev = &client->dev; + struct device_node *np; + u16 mfr_tmp_cfg = 0; + u32 page; + u32 uval; + int ret; + int i; + + if (!of_device_is_compatible(child, "pmbus-temperature")) + return 0; + + ret = of_property_read_u32(child, "reg", &page); + if (ret < 0) { + dev_err(&client->dev, "Missing valid reg property\n"); + return ret; + } + + if (!(info->func[page] & PMBUS_HAVE_TEMP)) { + dev_err(dev, "Page %d does not have temp capabilities\n", page); + return -ENXIO; + } + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + if (!of_property_read_u32(child, "maxim,tmp-offset", &uval)) { + if (uval < 32) + mfr_tmp_cfg |= uval << 10; + } + + i = 0; + while ((np = of_parse_phandle(child, "maxim,tmp-fans", i))) { + if (of_property_read_u32(np, "reg", &uval)) { + dev_err(&client->dev, "Failed to read fan reg property for phandle index %d\n", + i); + } else { + if (uval < 6) + mfr_tmp_cfg |= BIT(uval); + else + dev_warn(&client->dev, "Invalid fan page: %d\n", + uval); + } + i++; + } + + ret = max31785_i2c_smbus_write_word_data(client, MFR_TEMP_SENSOR_CONFIG, + mfr_tmp_cfg); + if (ret < 0) + return ret; + + return 0; } #define MAX31785_FAN_FUNCS \ @@ -310,11 +705,11 @@ static int max31785_configure_dual_tach(struct i2c_client *client, int i; for (i = 0; i < MAX31785_NR_FAN_PAGES; i++) { - ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); if (ret < 0) return ret; - ret = i2c_smbus_read_word_data(client, MFR_FAN_CONFIG); + ret = max31785_i2c_smbus_read_word_data(client, MFR_FAN_CONFIG); if (ret < 0) return ret; @@ -334,9 +729,12 @@ static int max31785_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; + struct device_node *child; struct pmbus_driver_info *info; bool dual_tach = false; + u32 fans; s64 ret; + int i; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | @@ -349,7 +747,7 @@ static int max31785_probe(struct i2c_client *client, *info = max31785_info; - ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255); + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, 255); if (ret < 0) return ret; @@ -366,6 +764,49 @@ static int max31785_probe(struct i2c_client *client, return -ENODEV; } + fans = 0; + for_each_child_of_node(dev->of_node, child) { + ret = max31785_of_fan_config(client, info, child); + if (ret < 0) { + of_node_put(child); + return ret; + } + + if (ret) + fans |= ret; + + ret = max31785_of_tmp_config(client, info, child); + if (ret < 0) { + of_node_put(child); + return ret; + } + } + + for (i = 0; i < MAX31785_NR_PAGES; i++) { + bool have_fan = !!(info->func[i] & PMBUS_HAVE_FAN12); + bool fan_configured = !!(fans & BIT(i)); + + if (!have_fan || fan_configured) + continue; + + ret = max31785_i2c_smbus_write_byte_data(client, PMBUS_PAGE, + i); + if (ret < 0) + return ret; + + ret = max31785_i2c_smbus_read_byte_data(client, + PMBUS_FAN_CONFIG_12); + if (ret < 0) + return ret; + + ret &= ~PB_FAN_1_INSTALLED; + ret = max31785_i2c_smbus_write_word_data(client, + PMBUS_FAN_CONFIG_12, + ret); + if (ret < 0) + return ret; + } + if (dual_tach) { ret = max31785_configure_dual_tach(client, info); if (ret < 0)
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index f7c47d7..caec1c5 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -168,9 +168,17 @@ int pmbus_set_page(struct i2c_client *client, int page) return 0; if (!(data->info->func[page] & PMBUS_PAGE_VIRTUAL)) { + dev_dbg(&client->dev, "Want page %u, %u cached\n", page, + data->currpage); + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); - if (rv < 0) - return rv; + if (rv) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, + page); + dev_dbg(&client->dev, + "Failed to set page %u, performed one-shot retry %s: %d\n", + page, rv ? "and failed" : "with success", rv); + } rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE); if (rv < 0) @@ -446,15 +454,15 @@ static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id, return s->data; } - config = pmbus_read_byte_data(client, page, - pmbus_fan_config_registers[id]); + config = _pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[id]); if (config < 0) return config; have_rpm = !!(config & pmbus_fan_rpm_mask[id]); if (want_rpm == have_rpm) - return pmbus_read_word_data(client, page, - pmbus_fan_command_registers[id]); + return _pmbus_read_word_data(client, page, + pmbus_fan_command_registers[id]); /* Can't sensibly map between RPM and PWM, just return zero */ return 0;
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 8d21b98..7313759 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig
@@ -1330,4 +1330,15 @@ This driver can also be built as a module. If so, the module will be called i2c-zx2967. +config I2C_FSI + tristate "FSI I2C driver" + depends on FSI + help + Driver for FSI bus attached I2C masters. These are I2C masters that + are connected to the system over an FSI bus, instead of the more + common PCI or MMIO interface. + + This driver can also be built as a module. If so, the module will be + called as i2c-fsi. + endmenu
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 189e34b..14c3991 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile
@@ -136,5 +136,6 @@ obj-$(CONFIG_I2C_SIBYTE) += i2c-sibyte.o obj-$(CONFIG_I2C_XGENE_SLIMPRO) += i2c-xgene-slimpro.o obj-$(CONFIG_SCx200_ACB) += scx200_acb.o +obj-$(CONFIG_I2C_FSI) += i2c-fsi.o ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c new file mode 100644 index 0000000..1e2be22 --- /dev/null +++ b/drivers/i2c/busses/i2c-fsi.c
@@ -0,0 +1,752 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * FSI-attached I2C master algorithm + * + * Copyright 2018 IBM Corporation + * + * 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/bitfield.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/fsi.h> +#include <linux/i2c.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/slab.h> + +#define FSI_ENGID_I2C 0x7 + +#define I2C_DEFAULT_CLK_DIV 6 + +/* i2c registers */ +#define I2C_FSI_FIFO 0x00 +#define I2C_FSI_CMD 0x04 +#define I2C_FSI_MODE 0x08 +#define I2C_FSI_WATER_MARK 0x0C +#define I2C_FSI_INT_MASK 0x10 +#define I2C_FSI_INT_COND 0x14 +#define I2C_FSI_OR_INT_MASK 0x14 +#define I2C_FSI_INTS 0x18 +#define I2C_FSI_AND_INT_MASK 0x18 +#define I2C_FSI_STAT 0x1C +#define I2C_FSI_RESET_I2C 0x1C +#define I2C_FSI_ESTAT 0x20 +#define I2C_FSI_RESET_ERR 0x20 +#define I2C_FSI_RESID_LEN 0x24 +#define I2C_FSI_SET_SCL 0x24 +#define I2C_FSI_PORT_BUSY 0x28 +#define I2C_FSI_RESET_SCL 0x2C +#define I2C_FSI_SET_SDA 0x30 +#define I2C_FSI_RESET_SDA 0x34 + +/* cmd register */ +#define I2C_CMD_WITH_START BIT(31) +#define I2C_CMD_WITH_ADDR BIT(30) +#define I2C_CMD_RD_CONT BIT(29) +#define I2C_CMD_WITH_STOP BIT(28) +#define I2C_CMD_FORCELAUNCH BIT(27) +#define I2C_CMD_ADDR GENMASK(23, 17) +#define I2C_CMD_READ BIT(16) +#define I2C_CMD_LEN GENMASK(15, 0) + +/* mode register */ +#define I2C_MODE_CLKDIV GENMASK(31, 16) +#define I2C_MODE_PORT GENMASK(15, 10) +#define I2C_MODE_ENHANCED BIT(3) +#define I2C_MODE_DIAG BIT(2) +#define I2C_MODE_PACE_ALLOW BIT(1) +#define I2C_MODE_WRAP BIT(0) + +/* watermark register */ +#define I2C_WATERMARK_HI GENMASK(15, 12) +#define I2C_WATERMARK_LO GENMASK(7, 4) + +#define I2C_FIFO_HI_LVL 4 +#define I2C_FIFO_LO_LVL 4 + +/* interrupt register */ +#define I2C_INT_INV_CMD BIT(15) +#define I2C_INT_PARITY BIT(14) +#define I2C_INT_BE_OVERRUN BIT(13) +#define I2C_INT_BE_ACCESS BIT(12) +#define I2C_INT_LOST_ARB BIT(11) +#define I2C_INT_NACK BIT(10) +#define I2C_INT_DAT_REQ BIT(9) +#define I2C_INT_CMD_COMP BIT(8) +#define I2C_INT_STOP_ERR BIT(7) +#define I2C_INT_BUSY BIT(6) +#define I2C_INT_IDLE BIT(5) + +/* status register */ +#define I2C_STAT_INV_CMD BIT(31) +#define I2C_STAT_PARITY BIT(30) +#define I2C_STAT_BE_OVERRUN BIT(29) +#define I2C_STAT_BE_ACCESS BIT(28) +#define I2C_STAT_LOST_ARB BIT(27) +#define I2C_STAT_NACK BIT(26) +#define I2C_STAT_DAT_REQ BIT(25) +#define I2C_STAT_CMD_COMP BIT(24) +#define I2C_STAT_STOP_ERR BIT(23) +#define I2C_STAT_MAX_PORT GENMASK(19, 16) +#define I2C_STAT_ANY_INT BIT(15) +#define I2C_STAT_SCL_IN BIT(11) +#define I2C_STAT_SDA_IN BIT(10) +#define I2C_STAT_PORT_BUSY BIT(9) +#define I2C_STAT_SELF_BUSY BIT(8) +#define I2C_STAT_FIFO_COUNT GENMASK(7, 0) + +#define I2C_STAT_ERR (I2C_STAT_INV_CMD | \ + I2C_STAT_PARITY | \ + I2C_STAT_BE_OVERRUN | \ + I2C_STAT_BE_ACCESS | \ + I2C_STAT_LOST_ARB | \ + I2C_STAT_NACK | \ + I2C_STAT_STOP_ERR) +#define I2C_STAT_ANY_RESP (I2C_STAT_ERR | \ + I2C_STAT_DAT_REQ | \ + I2C_STAT_CMD_COMP) + +/* extended status register */ +#define I2C_ESTAT_FIFO_SZ GENMASK(31, 24) +#define I2C_ESTAT_SCL_IN_SY BIT(15) +#define I2C_ESTAT_SDA_IN_SY BIT(14) +#define I2C_ESTAT_S_SCL BIT(13) +#define I2C_ESTAT_S_SDA BIT(12) +#define I2C_ESTAT_M_SCL BIT(11) +#define I2C_ESTAT_M_SDA BIT(10) +#define I2C_ESTAT_HI_WATER BIT(9) +#define I2C_ESTAT_LO_WATER BIT(8) +#define I2C_ESTAT_PORT_BUSY BIT(7) +#define I2C_ESTAT_SELF_BUSY BIT(6) +#define I2C_ESTAT_VERSION GENMASK(4, 0) + +/* port busy register */ +#define I2C_PORT_BUSY_RESET BIT(31) + +/* wait for command complete or data request */ +#define I2C_CMD_SLEEP_MAX_US 500 +#define I2C_CMD_SLEEP_MIN_US 50 + +/* wait after reset; choose time from legacy driver */ +#define I2C_RESET_SLEEP_MAX_US 2000 +#define I2C_RESET_SLEEP_MIN_US 1000 + +/* choose timeout length from legacy driver; it's well tested */ +#define I2C_ABORT_TIMEOUT msecs_to_jiffies(100) + +struct fsi_i2c_master { + struct fsi_device *fsi; + u8 fifo_size; + struct list_head ports; + struct mutex lock; +}; + +struct fsi_i2c_port { + struct list_head list; + struct i2c_adapter adapter; + struct fsi_i2c_master *master; + u16 port; + u16 xfrd; +}; + +static int fsi_i2c_read_reg(struct fsi_device *fsi, unsigned int reg, + u32 *data) +{ + int rc; + __be32 data_be; + + rc = fsi_device_read(fsi, reg, &data_be, sizeof(data_be)); + if (rc) + return rc; + + *data = be32_to_cpu(data_be); + + return 0; +} + +static int fsi_i2c_write_reg(struct fsi_device *fsi, unsigned int reg, + u32 *data) +{ + __be32 data_be = cpu_to_be32p(data); + + return fsi_device_write(fsi, reg, &data_be, sizeof(data_be)); +} + +static int fsi_i2c_dev_init(struct fsi_i2c_master *i2c) +{ + int rc; + u32 mode = I2C_MODE_ENHANCED, extended_status, watermark; + u32 interrupt = 0; + + /* since we use polling, disable interrupts */ + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_INT_MASK, &interrupt); + if (rc) + return rc; + + mode |= FIELD_PREP(I2C_MODE_CLKDIV, I2C_DEFAULT_CLK_DIV); + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_ESTAT, &extended_status); + if (rc) + return rc; + + i2c->fifo_size = FIELD_GET(I2C_ESTAT_FIFO_SZ, extended_status); + watermark = FIELD_PREP(I2C_WATERMARK_HI, + i2c->fifo_size - I2C_FIFO_HI_LVL); + watermark |= FIELD_PREP(I2C_WATERMARK_LO, I2C_FIFO_LO_LVL); + + return fsi_i2c_write_reg(i2c->fsi, I2C_FSI_WATER_MARK, &watermark); +} + +static int fsi_i2c_set_port(struct fsi_i2c_port *port) +{ + int rc; + struct fsi_device *fsi = port->master->fsi; + u32 mode, dummy = 0; + + rc = fsi_i2c_read_reg(fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + + if (FIELD_GET(I2C_MODE_PORT, mode) == port->port) + return 0; + + mode = (mode & ~I2C_MODE_PORT) | FIELD_PREP(I2C_MODE_PORT, port->port); + rc = fsi_i2c_write_reg(fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + + /* reset engine when port is changed */ + return fsi_i2c_write_reg(fsi, I2C_FSI_RESET_ERR, &dummy); +} + +static int fsi_i2c_start(struct fsi_i2c_port *port, struct i2c_msg *msg, + bool stop) +{ + struct fsi_i2c_master *i2c = port->master; + u32 cmd = I2C_CMD_WITH_START | I2C_CMD_WITH_ADDR; + + port->xfrd = 0; + + if (msg->flags & I2C_M_RD) + cmd |= I2C_CMD_READ; + + if (stop || msg->flags & I2C_M_STOP) + cmd |= I2C_CMD_WITH_STOP; + + cmd |= FIELD_PREP(I2C_CMD_ADDR, msg->addr); + cmd |= FIELD_PREP(I2C_CMD_LEN, msg->len); + + return fsi_i2c_write_reg(i2c->fsi, I2C_FSI_CMD, &cmd); +} + +static int fsi_i2c_get_op_bytes(int op_bytes) +{ + /* fsi is limited to max 4 byte aligned ops */ + if (op_bytes > 4) + return 4; + else if (op_bytes == 3) + return 2; + return op_bytes; +} + +static int fsi_i2c_write_fifo(struct fsi_i2c_port *port, struct i2c_msg *msg, + u8 fifo_count) +{ + int write; + int rc; + struct fsi_i2c_master *i2c = port->master; + int bytes_to_write = i2c->fifo_size - fifo_count; + int bytes_remaining = msg->len - port->xfrd; + + bytes_to_write = min(bytes_to_write, bytes_remaining); + + while (bytes_to_write) { + write = fsi_i2c_get_op_bytes(bytes_to_write); + + rc = fsi_device_write(i2c->fsi, I2C_FSI_FIFO, + &msg->buf[port->xfrd], write); + if (rc) + return rc; + + port->xfrd += write; + bytes_to_write -= write; + } + + return 0; +} + +static int fsi_i2c_read_fifo(struct fsi_i2c_port *port, struct i2c_msg *msg, + u8 fifo_count) +{ + int read; + int rc; + struct fsi_i2c_master *i2c = port->master; + int bytes_to_read; + int xfr_remaining = msg->len - port->xfrd; + u32 dummy; + + bytes_to_read = min_t(int, fifo_count, xfr_remaining); + + while (bytes_to_read) { + read = fsi_i2c_get_op_bytes(bytes_to_read); + + if (xfr_remaining) { + rc = fsi_device_read(i2c->fsi, I2C_FSI_FIFO, + &msg->buf[port->xfrd], read); + if (rc) + return rc; + + port->xfrd += read; + xfr_remaining -= read; + } else { + /* no more buffer but data in fifo, need to clear it */ + rc = fsi_device_read(i2c->fsi, I2C_FSI_FIFO, &dummy, + read); + if (rc) + return rc; + } + + bytes_to_read -= read; + } + + return 0; +} + +static int fsi_i2c_get_scl(struct i2c_adapter *adap) +{ + u32 stat = 0; + struct fsi_i2c_port *port = adap->algo_data; + struct fsi_i2c_master *i2c = port->master; + + fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); + + return !!(stat & I2C_STAT_SCL_IN); +} + +static void fsi_i2c_set_scl(struct i2c_adapter *adap, int val) +{ + u32 dummy = 0; + struct fsi_i2c_port *port = adap->algo_data; + struct fsi_i2c_master *i2c = port->master; + + if (val) + fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); + else + fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); +} + +static int fsi_i2c_get_sda(struct i2c_adapter *adap) +{ + u32 stat = 0; + struct fsi_i2c_port *port = adap->algo_data; + struct fsi_i2c_master *i2c = port->master; + + fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); + + return !!(stat & I2C_STAT_SDA_IN); +} + +static void fsi_i2c_set_sda(struct i2c_adapter *adap, int val) +{ + u32 dummy = 0; + struct fsi_i2c_port *port = adap->algo_data; + struct fsi_i2c_master *i2c = port->master; + + if (val) + fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); + else + fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); +} + +static void fsi_i2c_prepare_recovery(struct i2c_adapter *adap) +{ + int rc; + u32 mode; + struct fsi_i2c_port *port = adap->algo_data; + struct fsi_i2c_master *i2c = port->master; + + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return; + + mode |= I2C_MODE_DIAG; + fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); +} + +static void fsi_i2c_unprepare_recovery(struct i2c_adapter *adap) +{ + int rc; + u32 mode; + struct fsi_i2c_port *port = adap->algo_data; + struct fsi_i2c_master *i2c = port->master; + + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return; + + mode &= ~I2C_MODE_DIAG; + fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); +} + +static int fsi_i2c_reset_bus(struct fsi_i2c_master *i2c, + struct fsi_i2c_port *port) +{ + int rc; + u32 stat, dummy = 0; + + /* force bus reset, ignore errors */ + i2c_recover_bus(&port->adapter); + + /* reset errors */ + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); + if (rc) + return rc; + + /* wait for command complete */ + usleep_range(I2C_RESET_SLEEP_MIN_US, I2C_RESET_SLEEP_MAX_US); + + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); + if (rc) + return rc; + + if (stat & I2C_STAT_CMD_COMP) + return 0; + + /* failed to get command complete; reset engine again */ + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); + if (rc) + return rc; + + /* re-init engine again */ + return fsi_i2c_dev_init(i2c); +} + +static int fsi_i2c_reset_engine(struct fsi_i2c_master *i2c, u16 port) +{ + int rc; + u32 mode, dummy = 0; + + /* reset engine */ + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); + if (rc) + return rc; + + /* re-init engine */ + rc = fsi_i2c_dev_init(i2c); + if (rc) + return rc; + + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + + /* set port; default after reset is 0 */ + if (port) { + mode &= ~I2C_MODE_PORT; + mode |= FIELD_PREP(I2C_MODE_PORT, port); + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + } + + /* reset busy register; hw workaround */ + dummy = I2C_PORT_BUSY_RESET; + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_PORT_BUSY, &dummy); + if (rc) + return rc; + + return 0; +} + +static int fsi_i2c_abort(struct fsi_i2c_port *port, u32 status) +{ + int rc; + unsigned long start; + u32 cmd = I2C_CMD_WITH_STOP; + u32 stat; + struct fsi_i2c_master *i2c = port->master; + struct fsi_device *fsi = i2c->fsi; + + rc = fsi_i2c_reset_engine(i2c, port->port); + if (rc) + return rc; + + rc = fsi_i2c_read_reg(fsi, I2C_FSI_STAT, &stat); + if (rc) + return rc; + + /* if sda is low, peform full bus reset */ + if (!(stat & I2C_STAT_SDA_IN)) { + rc = fsi_i2c_reset_bus(i2c, port); + if (rc) + return rc; + } + + /* skip final stop command for these errors */ + if (status & (I2C_STAT_PARITY | I2C_STAT_LOST_ARB | I2C_STAT_STOP_ERR)) + return 0; + + /* write stop command */ + rc = fsi_i2c_write_reg(fsi, I2C_FSI_CMD, &cmd); + if (rc) + return rc; + + /* wait until we see command complete in the master */ + start = jiffies; + + do { + rc = fsi_i2c_read_reg(fsi, I2C_FSI_STAT, &status); + if (rc) + return rc; + + if (status & I2C_STAT_CMD_COMP) + return 0; + + usleep_range(I2C_CMD_SLEEP_MIN_US, I2C_CMD_SLEEP_MAX_US); + } while (time_after(start + I2C_ABORT_TIMEOUT, jiffies)); + + return -ETIMEDOUT; +} + +static int fsi_i2c_handle_status(struct fsi_i2c_port *port, + struct i2c_msg *msg, u32 status) +{ + int rc; + u8 fifo_count; + + if (status & I2C_STAT_ERR) { + rc = fsi_i2c_abort(port, status); + if (rc) + return rc; + + if (status & I2C_STAT_INV_CMD) + return -EINVAL; + + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | + I2C_STAT_BE_ACCESS)) + return -EPROTO; + + if (status & I2C_STAT_NACK) + return -ENXIO; + + if (status & I2C_STAT_LOST_ARB) + return -EAGAIN; + + if (status & I2C_STAT_STOP_ERR) + return -EBADMSG; + + return -EIO; + } + + if (status & I2C_STAT_DAT_REQ) { + fifo_count = FIELD_GET(I2C_STAT_FIFO_COUNT, status); + + if (msg->flags & I2C_M_RD) + return fsi_i2c_read_fifo(port, msg, fifo_count); + + return fsi_i2c_write_fifo(port, msg, fifo_count); + } + + if (status & I2C_STAT_CMD_COMP) { + if (port->xfrd < msg->len) + return -ENODATA; + + return msg->len; + } + + return 0; +} + +static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, + unsigned long timeout) +{ + u32 status = 0; + int rc; + unsigned long start = jiffies; + + do { + rc = fsi_i2c_read_reg(port->master->fsi, I2C_FSI_STAT, + &status); + if (rc) + return rc; + + if (status & I2C_STAT_ANY_RESP) { + rc = fsi_i2c_handle_status(port, msg, status); + if (rc < 0) + return rc; + + /* cmd complete and all data xfrd */ + if (rc == msg->len) + return 0; + + /* need to xfr more data, but maybe don't need wait */ + continue; + } + + usleep_range(I2C_CMD_SLEEP_MIN_US, I2C_CMD_SLEEP_MAX_US); + } while (time_after(start + timeout, jiffies)); + + return -ETIMEDOUT; +} + +static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + int i, rc; + unsigned long start_time; + struct fsi_i2c_port *port = adap->algo_data; + struct fsi_i2c_master *master = port->master; + struct i2c_msg *msg; + + mutex_lock(&master->lock); + + rc = fsi_i2c_set_port(port); + if (rc) + goto unlock; + + for (i = 0; i < num; i++) { + msg = msgs + i; + start_time = jiffies; + + rc = fsi_i2c_start(port, msg, i == num - 1); + if (rc) + goto unlock; + + rc = fsi_i2c_wait(port, msg, + adap->timeout - (jiffies - start_time)); + if (rc) + goto unlock; + } + +unlock: + mutex_unlock(&master->lock); + return rc ? : num; +} + +static u32 fsi_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_PROTOCOL_MANGLING | + I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static struct i2c_bus_recovery_info fsi_i2c_bus_recovery_info = { + .recover_bus = i2c_generic_scl_recovery, + .get_scl = fsi_i2c_get_scl, + .set_scl = fsi_i2c_set_scl, + .get_sda = fsi_i2c_get_sda, + .set_sda = fsi_i2c_set_sda, + .prepare_recovery = fsi_i2c_prepare_recovery, + .unprepare_recovery = fsi_i2c_unprepare_recovery, +}; + +static const struct i2c_algorithm fsi_i2c_algorithm = { + .master_xfer = fsi_i2c_xfer, + .functionality = fsi_i2c_functionality, +}; + +static int fsi_i2c_probe(struct device *dev) +{ + struct fsi_i2c_master *i2c; + struct fsi_i2c_port *port; + struct device_node *np; + int rc; + u32 port_no; + + i2c = devm_kzalloc(dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + mutex_init(&i2c->lock); + i2c->fsi = to_fsi_dev(dev); + INIT_LIST_HEAD(&i2c->ports); + + rc = fsi_i2c_dev_init(i2c); + if (rc) + return rc; + + /* Add adapter for each i2c port of the master. */ + for_each_available_child_of_node(dev->of_node, np) { + rc = of_property_read_u32(np, "reg", &port_no); + if (rc || port_no > USHRT_MAX) + continue; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + break; + + port->master = i2c; + port->port = port_no; + + port->adapter.owner = THIS_MODULE; + port->adapter.dev.of_node = np; + port->adapter.dev.parent = dev; + port->adapter.algo = &fsi_i2c_algorithm; + port->adapter.bus_recovery_info = &fsi_i2c_bus_recovery_info; + port->adapter.algo_data = port; + + snprintf(port->adapter.name, sizeof(port->adapter.name), + "i2c_bus-%u", port_no); + + rc = i2c_add_adapter(&port->adapter); + if (rc < 0) { + dev_err(dev, "Failed to register adapter: %d\n", rc); + kfree(port); + continue; + } + + list_add(&port->list, &i2c->ports); + } + + dev_set_drvdata(dev, i2c); + + return 0; +} + +static int fsi_i2c_remove(struct device *dev) +{ + struct fsi_i2c_master *i2c = dev_get_drvdata(dev); + struct fsi_i2c_port *port, *tmp; + + list_for_each_entry_safe(port, tmp, &i2c->ports, list) { + list_del(&port->list); + i2c_del_adapter(&port->adapter); + kfree(port); + } + + return 0; +} + +static const struct fsi_device_id fsi_i2c_ids[] = { + { FSI_ENGID_I2C, FSI_VERSION_ANY }, + { } +}; + +static struct fsi_driver fsi_i2c_driver = { + .id_table = fsi_i2c_ids, + .drv = { + .name = "i2c-fsi", + .bus = &fsi_bus_type, + .probe = fsi_i2c_probe, + .remove = fsi_i2c_remove, + }, +}; + +module_fsi_driver(fsi_i2c_driver); + +MODULE_AUTHOR("Eddie James <eajames@us.ibm.com>"); +MODULE_DESCRIPTION("FSI attached I2C master"); +MODULE_LICENSE("GPL");
diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index eaa7cfc..e879a01 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig
@@ -52,6 +52,17 @@ To compile this driver as a module, choose M here: the module will be called cros_ec_baro. +config DPS310 + tristate "Infineon DPS310 pressure and temperature sensor" + depends on I2C + select REGMAP_I2C + help + Support for the Infineon DPS310 digital barometric pressure sensor. + This driver measures temperature only. + + This driver can also be built as a module. If so, the module will be + called dps310. + config HID_SENSOR_PRESS depends on HID_SENSOR_HUB select IIO_BUFFER
diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index c2058d7..d8f5ace 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile
@@ -9,6 +9,7 @@ bmp280-objs := bmp280-core.o bmp280-regmap.o obj-$(CONFIG_BMP280_I2C) += bmp280-i2c.o obj-$(CONFIG_BMP280_SPI) += bmp280-spi.o +obj-$(CONFIG_DPS310) += dps310.o obj-$(CONFIG_IIO_CROS_EC_BARO) += cros_ec_baro.o obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o obj-$(CONFIG_HP03) += hp03.o
diff --git a/drivers/iio/pressure/dps310.c b/drivers/iio/pressure/dps310.c new file mode 100644 index 0000000..beb8b11 --- /dev/null +++ b/drivers/iio/pressure/dps310.c
@@ -0,0 +1,470 @@ +/* + * Copyright 2017 IBM Corporation + * + * Joel Stanley <joel@jms.id.au> + * + * 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. + * + * The DPS310 is a barometric pressure and temperature sensor. + * Currently only reading a single temperature is supported by + * this driver. + * + * https://www.infineon.com/dgdl/?fileId=5546d462576f34750157750826c42242 + * + * Temperature calculation: + * c0 * 0.5 + c1 * T_raw / kT °C + * + * TODO: + * - Pressure sensor readings + * - Optionally support the FIFO + */ + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include <linux/iio/iio.h> +#include <linux/iio/sysfs.h> + +#define DPS310_PRS_B0 0x00 +#define DPS310_PRS_B1 0x01 +#define DPS310_PRS_B2 0x02 +#define DPS310_TMP_B0 0x03 +#define DPS310_TMP_B1 0x04 +#define DPS310_TMP_B2 0x05 +#define DPS310_PRS_CFG 0x06 +#define DPS310_TMP_CFG 0x07 +#define DPS310_TMP_RATE_BITS GENMASK(6, 4) +#define DPS310_TMP_PRC_BITS GENMASK(3, 0) +#define DPS310_TMP_EXT BIT(7) +#define DPS310_MEAS_CFG 0x08 +#define DPS310_MEAS_CTRL_BITS GENMASK(2, 0) +#define DPS310_PRESSURE_EN BIT(0) +#define DPS310_TEMP_EN BIT(1) +#define DPS310_BACKGROUND BIT(2) +#define DPS310_PRS_RDY BIT(4) +#define DPS310_TMP_RDY BIT(5) +#define DPS310_SENSOR_RDY BIT(6) +#define DPS310_COEF_RDY BIT(7) +#define DPS310_CFG_REG 0x09 +#define DPS310_INT_HL BIT(7) +#define DPS310_TMP_SHIFT_EN BIT(3) +#define DPS310_PRS_SHIFT_EN BIT(4) +#define DPS310_FIFO_EN BIT(5) +#define DPS310_SPI_EN BIT(6) +#define DPS310_RESET 0x0c +#define DPS310_RESET_MAGIC (BIT(0) | BIT(3)) +#define DPS310_COEF_BASE 0x10 + +#define DPS310_PRS_BASE DPS310_PRS_B0 +#define DPS310_TMP_BASE DPS310_TMP_B0 + +#define DPS310_TMP_RATE(_n) ilog2(_n) +#define DPS310_TMP_PRC(_n) ilog2(_n) + +#define MCELSIUS_PER_CELSIUS 1000 + +const int scale_factor[] = { + 524288, + 1572864, + 3670016, + 7864320, + 253952, + 516096, + 1040384, + 2088960, +}; + +struct dps310_data { + struct i2c_client *client; + struct regmap *regmap; + + s32 c0, c1; + s32 temp_raw; +}; + +static const struct iio_chan_spec dps310_channels[] = { + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_RAW), + }, +}; + +/* To be called after checking the TMP_RDY bit in MEAS_CFG */ +static int dps310_get_temp_coef(struct dps310_data *data) +{ + struct regmap *regmap = data->regmap; + uint8_t coef[3] = {0}; + int r; + u32 c0, c1; + + /* + * Read temperature calibration coefficients c0 and c1 from the + * COEF register. The numbers are 12-bit 2's compliment numbers + */ + r = regmap_bulk_read(regmap, DPS310_COEF_BASE, coef, 3); + if (r < 0) + return r; + + c0 = (coef[0] << 4) | (coef[1] >> 4); + data->c0 = sign_extend32(c0, 11); + + c1 = ((coef[1] & GENMASK(3, 0)) << 8) | coef[2]; + data->c1 = sign_extend32(c1, 11); + + return 0; +} + +static int dps310_get_temp_precision(struct dps310_data *data) +{ + int val, r; + + r = regmap_read(data->regmap, DPS310_TMP_CFG, &val); + if (r < 0) + return r; + + /* + * Scale factor is bottom 4 bits of the register, but 1111 is + * reserved so just grab bottom three + */ + return BIT(val & GENMASK(2, 0)); +} + +static int dps310_set_temp_precision(struct dps310_data *data, int val) +{ + int ret; + u8 shift_en; + + if (val < 0 || val > 128) + return -EINVAL; + + shift_en = val >= 16 ? DPS310_TMP_SHIFT_EN : 0; + ret = regmap_write_bits(data->regmap, DPS310_CFG_REG, + DPS310_TMP_SHIFT_EN, + shift_en); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, DPS310_TMP_CFG, + DPS310_TMP_PRC_BITS, DPS310_TMP_PRC(val)); +} + +static int dps310_set_temp_samp_freq(struct dps310_data *data, int freq) +{ + uint8_t val; + + if (freq < 0 || freq > 128) + return -EINVAL; + + val = DPS310_TMP_RATE(freq) << 4; + + return regmap_update_bits(data->regmap, DPS310_TMP_CFG, + DPS310_TMP_RATE_BITS, val); +} + +static int dps310_get_temp_samp_freq(struct dps310_data *data) +{ + int val, r; + + r = regmap_read(data->regmap, DPS310_TMP_CFG, &val); + if (r < 0) + return r; + + return BIT((val & DPS310_TMP_RATE_BITS) >> 4); +} + +static int dps310_get_temp_k(struct dps310_data *data) +{ + return scale_factor[DPS310_TMP_PRC(dps310_get_temp_precision(data))]; +} + +static int dps310_read_temp(struct dps310_data *data) +{ + struct device *dev = &data->client->dev; + struct regmap *regmap = data->regmap; + uint8_t val[3] = {0}; + int r, ready; + int T_raw; + + r = regmap_read(regmap, DPS310_MEAS_CFG, &ready); + if (r < 0) + return r; + if (!(ready & DPS310_TMP_RDY)) { + dev_dbg(dev, "temperature not ready\n"); + return -EAGAIN; + } + + r = regmap_bulk_read(regmap, DPS310_TMP_BASE, val, 3); + if (r < 0) + return r; + + T_raw = (val[0] << 16) | (val[1] << 8) | val[2]; + data->temp_raw = sign_extend32(T_raw, 23); + + return 0; +} + +static bool dps310_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DPS310_PRS_CFG: + case DPS310_TMP_CFG: + case DPS310_MEAS_CFG: + case DPS310_CFG_REG: + case DPS310_RESET: + case 0x0e: + case 0x0f: + case 0x62: + return true; + default: + return false; + } +} + +static bool dps310_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case DPS310_PRS_B0: + case DPS310_PRS_B1: + case DPS310_PRS_B2: + case DPS310_TMP_B0: + case DPS310_TMP_B1: + case DPS310_TMP_B2: + case DPS310_MEAS_CFG: + case 0x32: + return true; + default: + return false; + } +} + +static int dps310_write_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct dps310_data *data = iio_priv(iio); + + if (chan->type != IIO_TEMP) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return dps310_set_temp_samp_freq(data, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return dps310_set_temp_precision(data, val); + default: + return -EINVAL; + } + + return -EINVAL; +} + +static int dps310_read_raw(struct iio_dev *iio, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dps310_data *data = iio_priv(iio); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *val = dps310_get_temp_samp_freq(data); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_RAW: + ret = dps310_read_temp(data); + if (ret) + return ret; + + *val = data->temp_raw * data->c1; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_OFFSET: + *val = (data->c0 >> 1) * dps310_get_temp_k(data); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 1000; /* milliCelsius per Celsius */ + *val2 = dps310_get_temp_k(data); + return IIO_VAL_FRACTIONAL; + + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *val = dps310_get_temp_precision(data); + return IIO_VAL_INT; + + default: + return -EINVAL; + } + + return -EINVAL; +} + +static const struct regmap_config dps310_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = dps310_is_writeable_reg, + .volatile_reg = dps310_is_volatile_reg, + .cache_type = REGCACHE_RBTREE, + .max_register = 0x62, +}; + +static const struct iio_info dps310_info = { + .read_raw = dps310_read_raw, + .write_raw = dps310_write_raw, +}; + +/* + * Some verions of chip will read temperatures in the ~60C range when + * its acutally ~20C. This is the manufacturer recommended workaround + * to correct the issue. + */ +static int dps310_temp_workaround(struct dps310_data *data) +{ + int r, reg; + + r = regmap_read(data->regmap, 0x32, ®); + if (r < 0) + return r; + + /* If bit 1 is set then the device is okay, and the workaround does not + * need to be applied */ + if (reg & BIT(1)) + return 0; + + r = regmap_write(data->regmap, 0x0e, 0xA5); + if (r < 0) + return r; + + r = regmap_write(data->regmap, 0x0f, 0x96); + if (r < 0) + return r; + + r = regmap_write(data->regmap, 0x62, 0x02); + if (r < 0) + return r; + + r = regmap_write(data->regmap, 0x0e, 0x00); + if (r < 0) + return r; + + r = regmap_write(data->regmap, 0x0f, 0x00); + + return r; +} + +static int dps310_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct dps310_data *data; + struct iio_dev *iio; + int r, ready; + + iio = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (!iio) + return -ENOMEM; + + data = iio_priv(iio); + data->client = client; + + iio->dev.parent = &client->dev; + iio->name = id->name; + iio->channels = dps310_channels; + iio->num_channels = ARRAY_SIZE(dps310_channels); + iio->info = &dps310_info; + iio->modes = INDIO_DIRECT_MODE; + + data->regmap = devm_regmap_init_i2c(client, &dps310_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + /* + * Set up external (MEMS) temperature sensor in single sample, one + * measurement per second mode + */ + r = regmap_write(data->regmap, DPS310_TMP_CFG, + DPS310_TMP_EXT | DPS310_TMP_RATE(1) | DPS310_TMP_PRC(1)); + if (r < 0) + return r; + + /* Temp shift is disabled when PRC <= 8 */ + r = regmap_write_bits(data->regmap, DPS310_CFG_REG, + DPS310_TMP_SHIFT_EN, 0); + if (r < 0) + return r; + + /* Turn on temperature measurement in the background */ + r = regmap_write_bits(data->regmap, DPS310_MEAS_CFG, + DPS310_MEAS_CTRL_BITS, + DPS310_TEMP_EN | DPS310_BACKGROUND); + if (r < 0) + return r; + + /* + * Calibration coefficients required for reporting temperature. + * They are available 40ms after the device has started + */ + r = regmap_read_poll_timeout(data->regmap, DPS310_MEAS_CFG, ready, + ready & DPS310_COEF_RDY, + 10 * 1000, + 40 * 1000); + if (r < 0) + return r; + + r = dps310_get_temp_coef(data); + if (r < 0) + return r; + + r = dps310_temp_workaround(data); + if (r < 0) + return r; + + r = devm_iio_device_register(&client->dev, iio); + if (r) + return r; + + i2c_set_clientdata(client, iio); + + dev_info(&client->dev, "%s: sensor '%s'\n", dev_name(&iio->dev), + client->name); + + return 0; +} + +static int dps310_remove(struct i2c_client *client) +{ + struct dps310_data *data = i2c_get_clientdata(client); + + return regmap_write(data->regmap, DPS310_RESET, DPS310_RESET_MAGIC); +} + +static const struct i2c_device_id dps310_id[] = { + { "dps310", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, dps310_id); + +static const unsigned short normal_i2c[] = { + 0x77, 0x76, I2C_CLIENT_END +}; + +static struct i2c_driver dps310_driver = { + .driver = { + .name = "dps310", + }, + .probe = dps310_probe, + .remove = dps310_remove, + .address_list = normal_i2c, + .id_table = dps310_id, +}; +module_i2c_driver(dps310_driver); + +MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>"); +MODULE_DESCRIPTION("Infineon DPS310 pressure and temperature sensor"); +MODULE_LICENSE("GPL");
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 5d71300..c1c9452 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig
@@ -487,6 +487,13 @@ allows the BMC to listen on and save the data written by the host to an arbitrary LPC I/O port. +config ASPEED_LPC_MBOX + tristate "Aspeed LPC Mailbox Controller" + depends on (ARCH_ASPEED || COMPILE_TEST) && REGMAP && MFD_SYSCON + ---help--- + Expose the ASPEED LPC MBOX registers found on Aspeed SOCs (AST2400 + and AST2500) to userspace. + config PCI_ENDPOINT_TEST depends on PCI select CRC32
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 20be70c..623e155 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_ASPEED_LPC_CTRL) += aspeed-lpc-ctrl.o obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o +obj-$(CONFIG_ASPEED_LPC_MBOX) += aspeed-lpc-mbox.o obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o obj-$(CONFIG_OCXL) += ocxl/ obj-$(CONFIG_MISC_RTSX) += cardreader/
diff --git a/drivers/misc/aspeed-lpc-mbox.c b/drivers/misc/aspeed-lpc-mbox.c new file mode 100644 index 0000000..0f0c711 --- /dev/null +++ b/drivers/misc/aspeed-lpc-mbox.c
@@ -0,0 +1,334 @@ +/* + * Copyright 2017 IBM Corporation + * + * 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/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define DEVICE_NAME "aspeed-mbox" + +#define ASPEED_MBOX_NUM_REGS 16 + +#define ASPEED_MBOX_DATA_0 0x00 +#define ASPEED_MBOX_STATUS_0 0x40 +#define ASPEED_MBOX_STATUS_1 0x44 +#define ASPEED_MBOX_BMC_CTRL 0x48 +#define ASPEED_MBOX_CTRL_RECV BIT(7) +#define ASPEED_MBOX_CTRL_MASK BIT(1) +#define ASPEED_MBOX_CTRL_SEND BIT(0) +#define ASPEED_MBOX_HOST_CTRL 0x4c +#define ASPEED_MBOX_INTERRUPT_0 0x50 +#define ASPEED_MBOX_INTERRUPT_1 0x54 + +struct aspeed_mbox { + struct miscdevice miscdev; + struct regmap *regmap; + unsigned int base; + wait_queue_head_t queue; + struct mutex mutex; +}; + +static atomic_t aspeed_mbox_open_count = ATOMIC_INIT(0); + +static u8 aspeed_mbox_inb(struct aspeed_mbox *mbox, int reg) +{ + /* + * The mbox registers are actually only one byte but are addressed + * four bytes apart. The other three bytes are marked 'reserved', + * they *should* be zero but lets not rely on it. + * I am going to rely on the fact we can casually read/write to them... + */ + unsigned int val = 0xff; /* If regmap throws an error return 0xff */ + int rc = regmap_read(mbox->regmap, mbox->base + reg, &val); + + if (rc) + dev_err(mbox->miscdev.parent, "regmap_read() failed with " + "%d (reg: 0x%08x)\n", rc, reg); + + return val & 0xff; +} + +static void aspeed_mbox_outb(struct aspeed_mbox *mbox, u8 data, int reg) +{ + int rc = regmap_write(mbox->regmap, mbox->base + reg, data); + + if (rc) + dev_err(mbox->miscdev.parent, "regmap_write() failed with " + "%d (data: %u reg: 0x%08x)\n", rc, data, reg); +} + +static struct aspeed_mbox *file_mbox(struct file *file) +{ + return container_of(file->private_data, struct aspeed_mbox, miscdev); +} + +static int aspeed_mbox_open(struct inode *inode, struct file *file) +{ + struct aspeed_mbox *mbox = file_mbox(file); + + if (atomic_inc_return(&aspeed_mbox_open_count) == 1) { + /* + * Clear the interrupt status bit if it was left on and unmask + * interrupts. + * ASPEED_MBOX_CTRL_RECV bit is W1C, this also unmasks in 1 step + */ + aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_RECV, ASPEED_MBOX_BMC_CTRL); + return 0; + } + + atomic_dec(&aspeed_mbox_open_count); + return -EBUSY; +} + +static ssize_t aspeed_mbox_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct aspeed_mbox *mbox = file_mbox(file); + char __user *p = buf; + ssize_t ret; + int i; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + if (count + *ppos > ASPEED_MBOX_NUM_REGS) + return -EINVAL; + + if (file->f_flags & O_NONBLOCK) { + if (!(aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL) & + ASPEED_MBOX_CTRL_RECV)) + return -EAGAIN; + } else if (wait_event_interruptible(mbox->queue, + aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL) & + ASPEED_MBOX_CTRL_RECV)) { + return -ERESTARTSYS; + } + + mutex_lock(&mbox->mutex); + + for (i = *ppos; count > 0 && i < ASPEED_MBOX_NUM_REGS; i++) { + uint8_t reg = aspeed_mbox_inb(mbox, ASPEED_MBOX_DATA_0 + (i * 4)); + + ret = __put_user(reg, p); + if (ret) + goto out_unlock; + + p++; + count--; + } + + /* ASPEED_MBOX_CTRL_RECV bit is write to clear, this also unmasks in 1 step */ + aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_RECV, ASPEED_MBOX_BMC_CTRL); + ret = p - buf; + +out_unlock: + mutex_unlock(&mbox->mutex); + return ret; +} + +static ssize_t aspeed_mbox_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct aspeed_mbox *mbox = file_mbox(file); + const char __user *p = buf; + ssize_t ret; + char c; + int i; + + if (!access_ok(VERIFY_READ, buf, count)) + return -EFAULT; + + if (count + *ppos > ASPEED_MBOX_NUM_REGS) + return -EINVAL; + + mutex_lock(&mbox->mutex); + + for (i = *ppos; count > 0 && i < ASPEED_MBOX_NUM_REGS; i++) { + ret = __get_user(c, p); + if (ret) + goto out_unlock; + + aspeed_mbox_outb(mbox, c, ASPEED_MBOX_DATA_0 + (i * 4)); + p++; + count--; + } + + aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_SEND, ASPEED_MBOX_BMC_CTRL); + ret = p - buf; + +out_unlock: + mutex_unlock(&mbox->mutex); + return ret; +} + +static unsigned int aspeed_mbox_poll(struct file *file, poll_table *wait) +{ + struct aspeed_mbox *mbox = file_mbox(file); + unsigned int mask = 0; + + poll_wait(file, &mbox->queue, wait); + + if (aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL) & ASPEED_MBOX_CTRL_RECV) + mask |= POLLIN; + + return mask; +} + +static int aspeed_mbox_release(struct inode *inode, struct file *file) +{ + atomic_dec(&aspeed_mbox_open_count); + return 0; +} + +static const struct file_operations aspeed_mbox_fops = { + .owner = THIS_MODULE, + .llseek = no_seek_end_llseek, + .read = aspeed_mbox_read, + .write = aspeed_mbox_write, + .open = aspeed_mbox_open, + .release = aspeed_mbox_release, + .poll = aspeed_mbox_poll, +}; + +static irqreturn_t aspeed_mbox_irq(int irq, void *arg) +{ + struct aspeed_mbox *mbox = arg; + + if (!(aspeed_mbox_inb(mbox, ASPEED_MBOX_BMC_CTRL) & ASPEED_MBOX_CTRL_RECV)) + return IRQ_NONE; + + /* + * Leave the status bit set so that we know the data is for us, + * clear it once it has been read. + */ + + /* Mask it off, we'll clear it when we the data gets read */ + aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_MASK, ASPEED_MBOX_BMC_CTRL); + + wake_up(&mbox->queue); + return IRQ_HANDLED; +} + +static int aspeed_mbox_config_irq(struct aspeed_mbox *mbox, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int rc, irq; + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (!irq) + return -ENODEV; + + rc = devm_request_irq(dev, irq, aspeed_mbox_irq, + IRQF_SHARED, DEVICE_NAME, mbox); + if (rc < 0) { + dev_err(dev, "Unable to request IRQ %d\n", irq); + return rc; + } + + /* + * Disable all register based interrupts. + */ + aspeed_mbox_outb(mbox, 0x00, ASPEED_MBOX_INTERRUPT_0); /* regs 0 - 7 */ + aspeed_mbox_outb(mbox, 0x00, ASPEED_MBOX_INTERRUPT_1); /* regs 8 - 15 */ + + /* These registers are write one to clear. Clear them. */ + aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STATUS_0); + aspeed_mbox_outb(mbox, 0xff, ASPEED_MBOX_STATUS_1); + + aspeed_mbox_outb(mbox, ASPEED_MBOX_CTRL_RECV, ASPEED_MBOX_BMC_CTRL); + return 0; +} + +static int aspeed_mbox_probe(struct platform_device *pdev) +{ + struct aspeed_mbox *mbox; + struct device *dev; + int rc; + + dev = &pdev->dev; + + mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, mbox); + + rc = of_property_read_u32(dev->of_node, "reg", &mbox->base); + if (rc) { + dev_err(dev, "Couldn't read reg device-tree property\n"); + return rc; + } + + mbox->regmap = syscon_node_to_regmap( + pdev->dev.parent->of_node); + if (IS_ERR(mbox->regmap)) { + dev_err(dev, "Couldn't get regmap\n"); + return -ENODEV; + } + + mutex_init(&mbox->mutex); + init_waitqueue_head(&mbox->queue); + + mbox->miscdev.minor = MISC_DYNAMIC_MINOR; + mbox->miscdev.name = DEVICE_NAME; + mbox->miscdev.fops = &aspeed_mbox_fops; + mbox->miscdev.parent = dev; + rc = misc_register(&mbox->miscdev); + if (rc) { + dev_err(dev, "Unable to register device\n"); + return rc; + } + + rc = aspeed_mbox_config_irq(mbox, pdev); + if (rc) { + dev_err(dev, "Failed to configure IRQ\n"); + misc_deregister(&mbox->miscdev); + return rc; + } + + return 0; +} + +static int aspeed_mbox_remove(struct platform_device *pdev) +{ + struct aspeed_mbox *mbox = dev_get_drvdata(&pdev->dev); + + misc_deregister(&mbox->miscdev); + + return 0; +} + +static const struct of_device_id aspeed_mbox_match[] = { + { .compatible = "aspeed,ast2400-mbox" }, + { .compatible = "aspeed,ast2500-mbox" }, + { }, +}; + +static struct platform_driver aspeed_mbox_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_mbox_match, + }, + .probe = aspeed_mbox_probe, + .remove = aspeed_mbox_remove, +}; + +module_platform_driver(aspeed_mbox_driver); + +MODULE_DEVICE_TABLE(of, aspeed_mbox_match); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cyril Bur <cyrilbur@gmail.com>"); +MODULE_DESCRIPTION("Aspeed mailbox device driver");
diff --git a/drivers/mtd/spi-nor/aspeed-smc.c b/drivers/mtd/spi-nor/aspeed-smc.c index 8d3cbe2..9ddf24b 100644 --- a/drivers/mtd/spi-nor/aspeed-smc.c +++ b/drivers/mtd/spi-nor/aspeed-smc.c
@@ -10,6 +10,7 @@ */ #include <linux/bug.h> +#include <linux/clk.h> #include <linux/device.h> #include <linux/io.h> #include <linux/module.h> @@ -20,6 +21,7 @@ #include <linux/of.h> #include <linux/of_platform.h> #include <linux/sizes.h> +#include <linux/slab.h> #include <linux/sysfs.h> #define DEVICE_NAME "aspeed-smc" @@ -41,12 +43,16 @@ struct aspeed_smc_info { bool hastype; /* flash type field exists in config reg */ u8 we0; /* shift for write enable bit for CE0 */ u8 ctl0; /* offset in regs of ctl for CE0 */ + u8 timing; /* offset in regs of timing */ void (*set_4b)(struct aspeed_smc_chip *chip); + int (*optimize_read)(struct aspeed_smc_chip *chip, u32 max_freq); }; static void aspeed_smc_chip_set_4b_spi_2400(struct aspeed_smc_chip *chip); static void aspeed_smc_chip_set_4b(struct aspeed_smc_chip *chip); +static int aspeed_smc_optimize_read(struct aspeed_smc_chip *chip, + u32 max_freq); static const struct aspeed_smc_info fmc_2400_info = { .maxsize = 64 * 1024 * 1024, @@ -54,7 +60,9 @@ static const struct aspeed_smc_info fmc_2400_info = { .hastype = true, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, }; static const struct aspeed_smc_info spi_2400_info = { @@ -63,7 +71,9 @@ static const struct aspeed_smc_info spi_2400_info = { .hastype = false, .we0 = 0, .ctl0 = 0x04, + .timing = 0x14, .set_4b = aspeed_smc_chip_set_4b_spi_2400, + .optimize_read = aspeed_smc_optimize_read, }; static const struct aspeed_smc_info fmc_2500_info = { @@ -72,7 +82,9 @@ static const struct aspeed_smc_info fmc_2500_info = { .hastype = true, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, }; static const struct aspeed_smc_info spi_2500_info = { @@ -81,7 +93,9 @@ static const struct aspeed_smc_info spi_2500_info = { .hastype = false, .we0 = 16, .ctl0 = 0x10, + .timing = 0x94, .set_4b = aspeed_smc_chip_set_4b, + .optimize_read = aspeed_smc_optimize_read, }; enum aspeed_smc_ctl_reg_value { @@ -102,6 +116,7 @@ struct aspeed_smc_chip { u32 ctl_val[smc_max]; /* control settings */ enum aspeed_smc_flash_type type; /* what type of flash */ struct spi_nor nor; + u32 clk_rate; }; struct aspeed_smc_controller { @@ -113,9 +128,13 @@ struct aspeed_smc_controller { void __iomem *ahb_base; /* per-chip windows resource */ u32 ahb_window_size; /* full mapping window size */ + unsigned long clk_frequency; + struct aspeed_smc_chip *chips[0]; /* pointers to attached chips */ }; +#define ASPEED_SPI_DEFAULT_FREQ 50000000 + /* * SPI Flash Configuration Register (AST2500 SPI) * or @@ -202,6 +221,12 @@ struct aspeed_smc_controller { ((controller)->regs + SEGMENT_ADDR_REG0 + (cs) * 4) /* + * Switch to turn off read optimisation if needed + */ +static bool optimize_read = true; +module_param(optimize_read, bool, 0644); + +/* * In user mode all data bytes read or written to the chip decode address * range are transferred to or from the SPI bus. The range is treated as a * fifo of arbitratry 1, 2, or 4 byte width but each write has to be aligned @@ -373,18 +398,49 @@ static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr) } } +static int aspeed_smc_get_io_mode(struct aspeed_smc_chip *chip) +{ + switch (chip->nor.read_proto) { + case SNOR_PROTO_1_1_1: + return 0; + case SNOR_PROTO_1_1_2: + return CONTROL_IO_DUAL_DATA; + case SNOR_PROTO_1_2_2: + return CONTROL_IO_DUAL_ADDR_DATA; + default: + dev_err(chip->nor.dev, "unsupported SPI read mode\n"); + return -EINVAL; + } +} + +static void aspeed_smc_set_io_mode(struct aspeed_smc_chip *chip, u32 io_mode) +{ + u32 ctl; + + if (io_mode > 0) { + ctl = readl(chip->ctl) & ~CONTROL_IO_MODE_MASK; + ctl |= io_mode; + writel(ctl, chip->ctl); + } +} + static ssize_t aspeed_smc_read_user(struct spi_nor *nor, loff_t from, size_t len, u_char *read_buf) { struct aspeed_smc_chip *chip = nor->priv; int i; u8 dummy = 0xFF; + int io_mode = aspeed_smc_get_io_mode(chip); aspeed_smc_start_user(nor); aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from); for (i = 0; i < chip->nor.read_dummy / 8; i++) aspeed_smc_write_to_ahb(chip->ahb_base, &dummy, sizeof(dummy)); + /* Set IO mode only for data */ + if (io_mode == CONTROL_IO_DUAL_DATA) + aspeed_smc_set_io_mode(chip, io_mode); + aspeed_smc_read_from_ahb(read_buf, chip->ahb_base, len); aspeed_smc_stop_user(nor); return len; @@ -402,6 +458,31 @@ static ssize_t aspeed_smc_write_user(struct spi_nor *nor, loff_t to, return len; } +static ssize_t aspeed_smc_read(struct spi_nor *nor, loff_t from, size_t len, + u_char *read_buf) +{ + struct aspeed_smc_chip *chip = nor->priv; + + /* + * The AHB window configured for the chip is too small for the + * read offset. Use the "User mode" of the controller to + * perform the read. + */ + if (from >= chip->ahb_window_size) { + aspeed_smc_read_user(nor, from, len, read_buf); + goto out; + } + + /* + * Use the "Command mode" to do a direct read from the AHB + * window configured for the chip. This should be the default. + */ + memcpy_fromio(read_buf, chip->ahb_base + from, len); + +out: + return len; +} + static int aspeed_smc_unregister(struct aspeed_smc_controller *controller) { struct aspeed_smc_chip *chip; @@ -706,10 +787,179 @@ static int aspeed_smc_chip_setup_init(struct aspeed_smc_chip *chip, return 0; } + +#define CALIBRATE_BUF_SIZE 16384 + +static bool aspeed_smc_check_reads(struct aspeed_smc_chip *chip, + const u8 *golden_buf, u8 *test_buf) +{ + int i; + + for (i = 0; i < 10; i++) { + aspeed_smc_read_from_ahb(test_buf, chip->ahb_base, + CALIBRATE_BUF_SIZE); + if (memcmp(test_buf, golden_buf, CALIBRATE_BUF_SIZE) != 0) + return false; + } + return true; +} + +static int aspeed_smc_calibrate_reads(struct aspeed_smc_chip *chip, u32 hdiv, + const u8 *golden_buf, u8 *test_buf) +{ + struct aspeed_smc_controller *controller = chip->controller; + const struct aspeed_smc_info *info = controller->info; + int i; + int good_pass = -1, pass_count = 0; + u32 shift = (hdiv - 1) << 2; + u32 mask = ~(0xfu << shift); + u32 fread_timing_val = 0; + +#define FREAD_TPASS(i) (((i) / 2) | (((i) & 1) ? 0 : 8)) + + /* Try HCLK delay 0..5, each one with/without delay and look for a + * good pair. + */ + for (i = 0; i < 12; i++) { + bool pass; + + fread_timing_val &= mask; + fread_timing_val |= FREAD_TPASS(i) << shift; + + writel(fread_timing_val, controller->regs + info->timing); + pass = aspeed_smc_check_reads(chip, golden_buf, test_buf); + dev_dbg(chip->nor.dev, + " * [%08x] %d HCLK delay, %dns DI delay : %s", + fread_timing_val, i/2, (i & 1) ? 0 : 4, + pass ? "PASS" : "FAIL"); + if (pass) { + pass_count++; + if (pass_count == 3) { + good_pass = i - 1; + break; + } + } else + pass_count = 0; + } + + /* No good setting for this frequency */ + if (good_pass < 0) + return -1; + + /* We have at least one pass of margin, let's use first pass */ + fread_timing_val &= mask; + fread_timing_val |= FREAD_TPASS(good_pass) << shift; + writel(fread_timing_val, controller->regs + info->timing); + dev_dbg(chip->nor.dev, " * -> good is pass %d [0x%08x]", + good_pass, fread_timing_val); + return 0; +} + +static bool aspeed_smc_check_calib_data(const u8 *test_buf, u32 size) +{ + const u32 *tb32 = (const u32 *) test_buf; + u32 i, cnt = 0; + + /* We check if we have enough words that are neither all 0 + * nor all 1's so the calibration can be considered valid. + * + * I use an arbitrary threshold for now of 64 + */ + size >>= 2; + for (i = 0; i < size; i++) { + if (tb32[i] != 0 && tb32[i] != 0xffffffff) + cnt++; + } + return cnt >= 64; +} + +static const uint32_t aspeed_smc_hclk_divs[] = { + 0xf, /* HCLK */ + 0x7, /* HCLK/2 */ + 0xe, /* HCLK/3 */ + 0x6, /* HCLK/4 */ + 0xd, /* HCLK/5 */ +}; +#define ASPEED_SMC_HCLK_DIV(i) (aspeed_smc_hclk_divs[(i) - 1] << 8) + +static int aspeed_smc_optimize_read(struct aspeed_smc_chip *chip, + u32 max_freq) +{ + u8 *golden_buf, *test_buf; + int i, rc, best_div = -1; + u32 save_read_val = chip->ctl_val[smc_read]; + u32 ahb_freq = chip->controller->clk_frequency; + + dev_dbg(chip->nor.dev, "AHB frequency: %d MHz", ahb_freq / 1000000); + + test_buf = kmalloc(CALIBRATE_BUF_SIZE * 2, GFP_KERNEL); + golden_buf = test_buf + CALIBRATE_BUF_SIZE; + + /* We start with the dumbest setting (keep 4Byte bit) and read + * some data + */ + chip->ctl_val[smc_read] = (chip->ctl_val[smc_read] & 0x2000) | + (0x00 << 28) | /* Single bit */ + (0x00 << 24) | /* CE# max */ + (0x03 << 16) | /* use normal reads */ + (0x00 << 8) | /* HCLK/16 */ + (0x00 << 6) | /* no dummy cycle */ + (0x00); /* normal read */ + + writel(chip->ctl_val[smc_read], chip->ctl); + + aspeed_smc_read_from_ahb(golden_buf, chip->ahb_base, + CALIBRATE_BUF_SIZE); + + /* Establish our read mode with freq field set to 0 (HCLK/16) */ + chip->ctl_val[smc_read] = save_read_val & 0xfffff0ff; + + /* Check if calibration data is suitable */ + if (!aspeed_smc_check_calib_data(golden_buf, CALIBRATE_BUF_SIZE)) { + dev_info(chip->nor.dev, + "Calibration area too uniform, using low speed"); + writel(chip->ctl_val[smc_read], chip->ctl); + kfree(test_buf); + return 0; + } + + /* Now we iterate the HCLK dividers until we find our breaking point */ + for (i = ARRAY_SIZE(aspeed_smc_hclk_divs); i > 0; i--) { + u32 tv, freq; + + /* Compare timing to max */ + freq = ahb_freq / i; + if (freq >= max_freq) + continue; + + /* Set the timing */ + tv = chip->ctl_val[smc_read] | ASPEED_SMC_HCLK_DIV(i); + writel(tv, chip->ctl); + dev_dbg(chip->nor.dev, "Trying HCLK/%d...", i); + rc = aspeed_smc_calibrate_reads(chip, i, golden_buf, test_buf); + if (rc == 0) + best_div = i; + } + kfree(test_buf); + + /* Nothing found ? */ + if (best_div < 0) + dev_warn(chip->nor.dev, "No good frequency, using dumb slow"); + else { + dev_dbg(chip->nor.dev, "Found good read timings at HCLK/%d", + best_div); + chip->ctl_val[smc_read] |= ASPEED_SMC_HCLK_DIV(best_div); + } + + writel(chip->ctl_val[smc_read], chip->ctl); + return 0; +} + static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) { struct aspeed_smc_controller *controller = chip->controller; const struct aspeed_smc_info *info = controller->info; + int io_mode; u32 cmd; if (chip->nor.addr_width == 4 && info->set_4b) @@ -732,21 +982,24 @@ static int aspeed_smc_chip_setup_finish(struct aspeed_smc_chip *chip) * TODO: Adjust clocks if fast read is supported and interpret * SPI-NOR flags to adjust controller settings. */ - if (chip->nor.read_proto == SNOR_PROTO_1_1_1) { - if (chip->nor.read_dummy == 0) - cmd = CONTROL_COMMAND_MODE_NORMAL; - else - cmd = CONTROL_COMMAND_MODE_FREAD; - } else { - dev_err(chip->nor.dev, "unsupported SPI read mode\n"); - return -EINVAL; - } + io_mode = aspeed_smc_get_io_mode(chip); + if (io_mode < 0) + return io_mode; - chip->ctl_val[smc_read] |= cmd | + if (chip->nor.read_dummy == 0) + cmd = CONTROL_COMMAND_MODE_NORMAL; + else + cmd = CONTROL_COMMAND_MODE_FREAD; + + chip->ctl_val[smc_read] |= cmd | io_mode | + chip->nor.read_opcode << CONTROL_COMMAND_SHIFT | CONTROL_IO_DUMMY_SET(chip->nor.read_dummy / 8); - dev_dbg(controller->dev, "base control register: %08x\n", + dev_info(controller->dev, "read control register: %08x\n", chip->ctl_val[smc_read]); + + if (optimize_read && info->optimize_read) + info->optimize_read(chip, chip->clk_rate); return 0; } @@ -756,6 +1009,7 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller, const struct spi_nor_hwcaps hwcaps = { .mask = SNOR_HWCAPS_READ | SNOR_HWCAPS_READ_FAST | + SNOR_HWCAPS_READ_1_1_2 | SNOR_HWCAPS_PP, }; const struct aspeed_smc_info *info = controller->info; @@ -799,6 +1053,13 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller, break; } + if (of_property_read_u32(child, "spi-max-frequency", + &chip->clk_rate)) { + chip->clk_rate = ASPEED_SPI_DEFAULT_FREQ; + } + dev_info(dev, "Using %d MHz SPI frequency\n", + chip->clk_rate / 1000000); + chip->controller = controller; chip->ctl = controller->regs + info->ctl0 + cs * 4; chip->cs = cs; @@ -809,7 +1070,7 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller, nor->dev = dev; nor->priv = chip; spi_nor_set_flash_node(nor, child); - nor->read = aspeed_smc_read_user; + nor->read = aspeed_smc_read; nor->write = aspeed_smc_write_user; nor->read_reg = aspeed_smc_read_reg; nor->write_reg = aspeed_smc_write_reg; @@ -853,6 +1114,7 @@ static int aspeed_smc_probe(struct platform_device *pdev) struct aspeed_smc_controller *controller; const struct of_device_id *match; const struct aspeed_smc_info *info; + struct clk *clk; struct resource *res; int ret; @@ -883,6 +1145,12 @@ static int aspeed_smc_probe(struct platform_device *pdev) controller->ahb_window_size = resource_size(res); + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + controller->clk_frequency = clk_get_rate(clk); + devm_clk_put(&pdev->dev, clk); + ret = aspeed_smc_setup_flash(controller, np, res); if (ret) dev_err(dev, "Aspeed SMC probe failed %d\n", ret);
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c index 5bfa36e..e9f4447 100644 --- a/drivers/mtd/spi-nor/spi-nor.c +++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -1068,7 +1068,7 @@ 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) }, - { "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) }, + { "mx66l51235f", INFO(0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) }, { "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) },
diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c index 78db8e6..ed6c76d 100644 --- a/drivers/net/ethernet/faraday/ftgmac100.c +++ b/drivers/net/ethernet/faraday/ftgmac100.c
@@ -1735,8 +1735,8 @@ static void ftgmac100_ncsi_handler(struct ncsi_dev *nd) if (unlikely(nd->state != ncsi_dev_state_functional)) return; - netdev_info(nd->dev, "NCSI interface %s\n", - nd->link_up ? "up" : "down"); + netdev_dbg(nd->dev, "NCSI interface %s\n", + nd->link_up ? "up" : "down"); } static void ftgmac100_setup_clk(struct ftgmac100 *priv)
diff --git a/drivers/net/team/team.c b/drivers/net/team/team.c index e141563..592d24e 100644 --- a/drivers/net/team/team.c +++ b/drivers/net/team/team.c
@@ -2425,7 +2425,6 @@ static int team_nl_send_options_get(struct team *team, u32 portid, u32 seq, nla_put_failure: err = -EMSGSIZE; errout: - genlmsg_cancel(skb, hdr); nlmsg_free(skb); return err; } @@ -2719,7 +2718,6 @@ static int team_nl_send_port_list_get(struct team *team, u32 portid, u32 seq, nla_put_failure: err = -EMSGSIZE; errout: - genlmsg_cancel(skb, hdr); nlmsg_free(skb); return err; }
diff --git a/drivers/net/wireless/mac80211_hwsim.c b/drivers/net/wireless/mac80211_hwsim.c index 920c23e..08395d1 100644 --- a/drivers/net/wireless/mac80211_hwsim.c +++ b/drivers/net/wireless/mac80211_hwsim.c
@@ -2514,7 +2514,6 @@ static void hwsim_mcast_new_radio(int id, struct genl_info *info, return; out_err: - genlmsg_cancel(mcast_skb, data); nlmsg_free(mcast_skb); }
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig index 01fe8e0..5e94cc2 100644 --- a/drivers/pinctrl/Kconfig +++ b/drivers/pinctrl/Kconfig
@@ -359,6 +359,7 @@ source "drivers/pinctrl/mediatek/Kconfig" source "drivers/pinctrl/zte/Kconfig" source "drivers/pinctrl/meson/Kconfig" +source "drivers/pinctrl/nuvoton/Kconfig" config PINCTRL_XWAY bool
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 657332b..289e8e8 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile
@@ -63,3 +63,5 @@ obj-$(CONFIG_ARCH_VT8500) += vt8500/ obj-y += mediatek/ obj-$(CONFIG_PINCTRL_ZX) += zte/ +obj-$(CONFIG_ARCH_NPCM7XX) += nuvoton/ +
diff --git a/drivers/pinctrl/nuvoton/Kconfig b/drivers/pinctrl/nuvoton/Kconfig new file mode 100644 index 0000000..d849074 --- /dev/null +++ b/drivers/pinctrl/nuvoton/Kconfig
@@ -0,0 +1,12 @@ +config PINCTRL_NPCM7XX + bool "Pinctrl driver for Nuvoton NPCM7XX" + depends on (ARCH_NPCM7XX || COMPILE_TEST) && OF + select MFD_SYSCON + select GPIOLIB + select GPIOLIB_IRQCHIP + select PINMUX + select PINCONF + select GENERIC_PINCONF + help + Say Y here to enable pin controller and GPIO support + for Nuvoton NPCM7xx SoCs.
diff --git a/drivers/pinctrl/nuvoton/Makefile b/drivers/pinctrl/nuvoton/Makefile new file mode 100644 index 0000000..244dcd9 --- /dev/null +++ b/drivers/pinctrl/nuvoton/Makefile
@@ -0,0 +1 @@ +obj-$(CONFIG_PINCTRL_NPCM7XX) += pinctrl-npcm7xx.o
diff --git a/drivers/pinctrl/nuvoton/pinctrl-npcm7xx.c b/drivers/pinctrl/nuvoton/pinctrl-npcm7xx.c new file mode 100644 index 0000000..1c644f2 --- /dev/null +++ b/drivers/pinctrl/nuvoton/pinctrl-npcm7xx.c
@@ -0,0 +1,2133 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2016-2018 Nuvoton Technology corporation. +// Copyright (c) 2016, Dell Inc + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/pinctrl/pinmux.h> +#include <linux/pinctrl/pinconf.h> +#include <linux/pinctrl/pinconf-generic.h> +#include <linux/pinctrl/machine.h> +#include <linux/interrupt.h> +#include <linux/gpio/driver.h> +#include <linux/sysfs.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#define DRV_DATE "2018-05-14" +#define DRV_VERSION "2.0.0" + +#define GPIO_BANK_NUM 8 + +/* GCR registers */ +#define NPCM7XX_GCR_PDID 0x00 +#define NPCM7XX_GCR_MFSEL1 0x0C +#define NPCM7XX_GCR_MFSEL2 0x10 +#define NPCM7XX_GCR_MFSEL3 0x64 +#define NPCM7XX_GCR_MFSEL4 0xb0 +#define NPCM7XX_GCR_CPCTL 0xD0 +#define NPCM7XX_GCR_CP2BST 0xD4 +#define NPCM7XX_GCR_B2CPNT 0xD8 +#define NPCM7XX_GCR_I2CSEGSEL 0xE0 +#define NPCM7XX_GCR_I2CSEGCTL 0xE4 + +#define SMBXX_BITS 2 +#define SMB0SS_SHIFT 0 +#define SMB1SS_SHIFT 2 +#define SMB2SS_SHIFT 4 +#define SMB3SS_SHIFT 6 +#define SMB4SS_SHIFT 8 +#define SMB5SS_SHIFT 10 +#define WEN0_SS BIT(12) +#define WEN1_SS BIT(13) +#define WEN2_SS BIT(14) +#define WEN3_SS BIT(15) +#define WEN4_SS BIT(16) +#define WEN5_SS BIT(17) + +#define NPCM7XX_GCR_SRCNT 0x68 +#define SRCNT_ESPI BIT(3) +/* SPI0D = 1:1 + * SPI0C = 2:1 + * ESPI = 3:1 + * TDO = 4:1 + */ +#define NPCM7XX_GCR_FLOCKR1 0x74 +#define NPCM7XX_GCR_DSCNT 0x78 +/* SPI0D = 1:1 (8,12) + * SPI0C = 2:1 (8,12) + * SYNC1 = 3:1 (4,8) + * ESPI = 6:2 (8,12,16,24) + * SPLD = 9:1 (2,4) + */ + +#define NPCM7XX_GCR_NONE 0 + +/* GPIO module */ +#define GPIO_PER_BANK 32 + +#define NPCM_GP_N_TLOCK1 0x00 +#define NPCM_GP_N_DIN 0x04 /* Data IN */ +#define NPCM_GP_N_POL 0x08 /* Polarity */ +#define NPCM_GP_N_DOUT 0x0c /* Data OUT */ +#define NPCM_GP_N_OE 0x10 /* Output Enable */ +#define NPCM_GP_N_OTYP 0x14 +#define NPCM_GP_N_MP 0x18 +#define NPCM_GP_N_PU 0x1c /* Pull-up */ +#define NPCM_GP_N_PD 0x20 /* Pull-down */ +#define NPCM_GP_N_DBNC 0x24 /* Debounce */ +#define NPCM_GP_N_EVTYP 0x28 /* Event Type */ +#define NPCM_GP_N_EVBE 0x2c /* Event Both Edge */ +#define NPCM_GP_N_OBL0 0x30 +#define NPCM_GP_N_OBL1 0x34 +#define NPCM_GP_N_OBL2 0x38 +#define NPCM_GP_N_OBL3 0x3c +#define NPCM_GP_N_EVEN 0x40 /* Event Enable */ +#define NPCM_GP_N_EVENS 0x44 /* Event Set (enable) */ +#define NPCM_GP_N_EVENC 0x48 /* Event Clear (disable) */ +#define NPCM_GP_N_EVST 0x4c /* Event Status */ +#define NPCM_GP_N_SPLCK 0x50 +#define NPCM_GP_N_MPLCK 0x54 +#define NPCM_GP_N_IEM 0x58 /* Input Enable */ +#define NPCM_GP_N_OSRC 0x5c +#define NPCM_GP_N_ODSC 0x60 +#define NPCM_GP_N_DOS 0x68 /* Data OUT Set */ +#define NPCM_GP_N_DOC 0x6c /* Data OUT Clear */ +#define NPCM_GP_N_OES 0x70 /* Output Enable Set */ +#define NPCM_GP_N_OEC 0x74 /* Output Enable Clear */ +#define NPCM_GP_N_TLOCK2 0x7c + +/* Structure for register banks */ +struct NPCM_GPIO { + void __iomem *base; + struct gpio_chip gc; + int irqbase; + int irq; + spinlock_t lock; + void *priv; + struct irq_chip irq_chip; + u32 pinctrl_id; +}; + +struct NPCM7xx_pinctrl { + struct pinctrl_dev *pctldev; + struct device *dev; + struct NPCM_GPIO gpio_bank[GPIO_BANK_NUM]; + struct irq_domain *domain; + struct regmap *gcr_regmap; + void __iomem *regs; + u32 bank_num; +}; + +enum operand{ + opSET, + opGETBIT, + opSETBIT, + opCLRBIT, +}; + +/* Perform locked bit operations on GPIO registers */ +static int gpio_bitop(struct NPCM_GPIO *bank, int op, unsigned int offset, + int reg) +{ + unsigned long flags; + u32 mask, val; + + mask = (1L << offset); + spin_lock_irqsave(&bank->lock, flags); + switch (op) { + case opSET: + iowrite32(mask, bank->base + reg); + break; + case opGETBIT: + mask &= ioread32(bank->base + reg); + break; + case opSETBIT: + val = ioread32(bank->base + reg); + iowrite32(val|mask, bank->base + reg); + break; + case opCLRBIT: + val = ioread32(bank->base + reg); + iowrite32(val&(~mask), bank->base + reg); + break; + } + spin_unlock_irqrestore(&bank->lock, flags); + return !!mask; +} + +/* + * GPIO code + */ + +/* Dump GPIO and GCR registers */ +static void npcmgpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) +{ + struct NPCM_GPIO *bank = gpiochip_get_data(chip); + u8 *base; + + base = bank->base; + seq_printf(s, "-- module %d [gpio%d - %d]\n", + bank->gc.base / bank->gc.ngpio, + bank->gc.base, + bank->gc.base + bank->gc.ngpio); + seq_printf(s, "DIN :%.8x DOUT:%.8x IE :%.8x OE :%.8x\n", + ioread32(base + NPCM_GP_N_DIN), + ioread32(base + NPCM_GP_N_DOUT), + ioread32(base + NPCM_GP_N_IEM), + ioread32(base + NPCM_GP_N_OE)); + seq_printf(s, "PU :%.8x PD :%.8x DB :%.8x POL :%.8x\n", + ioread32(base + NPCM_GP_N_PU), + ioread32(base + NPCM_GP_N_PD), + ioread32(base + NPCM_GP_N_DBNC), + ioread32(base + NPCM_GP_N_POL)); + seq_printf(s, "ETYP:%.8x EVBE:%.8x EVEN:%.8x EVST:%.8x\n", + ioread32(base + NPCM_GP_N_EVTYP), + ioread32(base + NPCM_GP_N_EVBE), + ioread32(base + NPCM_GP_N_EVEN), + ioread32(base + NPCM_GP_N_EVST)); + seq_printf(s, "OTYP:%.8x OSRC:%.8x ODSC:%.8x\n", + ioread32(base + NPCM_GP_N_OTYP), + ioread32(base + NPCM_GP_N_OSRC), + ioread32(base + NPCM_GP_N_ODSC)); + seq_printf(s, "OBL0:%.8x OBL1:%.8x OBL2:%.8x OBL3:%.8x\n", + ioread32(base + NPCM_GP_N_OBL0), + ioread32(base + NPCM_GP_N_OBL1), + ioread32(base + NPCM_GP_N_OBL2), + ioread32(base + NPCM_GP_N_OBL3)); + seq_printf(s, "SLCK:%.8x MLCK:%.8x\n", + ioread32(base + NPCM_GP_N_SPLCK), + ioread32(base + NPCM_GP_N_MPLCK)); +} + +/* Get direction of GPIO pin */ +static int npcmgpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + struct NPCM_GPIO *bank = gpiochip_get_data(chip); + u32 oe, ie; + + /* Get Input & Output state */ + ie = gpio_bitop(bank, opGETBIT, offset, NPCM_GP_N_IEM); + oe = gpio_bitop(bank, opGETBIT, offset, NPCM_GP_N_OE); + if (ie && !oe) + return 1; + else if (oe && !ie) + return 0; + return -EINVAL; +} + +/* Set GPIO to Input */ +static int npcmgpio_direction_input(struct gpio_chip *chip, unsigned int offset) +{ + dev_dbg(chip->parent, "%s: %d\n", __func__, offset); + return pinctrl_gpio_direction_input(offset + chip->base); +} + +/* Set GPIO to Output with initial value */ +static int npcmgpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct NPCM_GPIO *bank = gpiochip_get_data(chip); + + dev_dbg(chip->parent, "gpio_direction_output: offset%d = %x\n", offset, + value); + /* Check if we're enabled as an interrupt.. */ + if (gpio_bitop(bank, opGETBIT, offset, NPCM_GP_N_EVEN) && + gpio_bitop(bank, opGETBIT, offset, NPCM_GP_N_IEM)) { + dev_dbg(chip->parent, + "gpio_direction_output: IRQ enabled on offset%d\n", + offset); + return -EINVAL; + } + + gpio_bitop(bank, opSETBIT, offset, value ? NPCM_GP_N_DOS : + NPCM_GP_N_DOC); + return pinctrl_gpio_direction_output(offset + chip->base); +} + +/* Retrieve value of GPIO */ +static int npcmgpio_get_value(struct gpio_chip *chip, unsigned int offset) +{ + struct NPCM_GPIO *bank = gpiochip_get_data(chip); + int dir; + + dev_dbg(chip->parent, "gpio_get: gpio%d\n", offset); + dir = npcmgpio_get_direction(chip, offset); + return gpio_bitop(bank, opGETBIT, offset, dir == 0 ? + NPCM_GP_N_DOUT : NPCM_GP_N_DIN); +} + +/* Set value of Output GPIO */ +static void npcmgpio_set_value(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct NPCM_GPIO *bank = gpiochip_get_data(chip); + + dev_dbg(chip->parent, "gpio_set: gpio%d = %x\n", offset, value); + if (npcmgpio_get_direction(chip, offset) == 0) + gpio_bitop(bank, opSETBIT, offset, value ? NPCM_GP_N_DOS : + NPCM_GP_N_DOC); +} + +/* Request GPIO */ +static int npcmgpio_gpio_request(struct gpio_chip *chip, unsigned int offset) +{ + dev_dbg(chip->parent, "gpio_request: offset%d\n", offset); + return pinctrl_gpio_request(offset+chip->base); +} + +/* Release GPIO */ +static void npcmgpio_gpio_free(struct gpio_chip *chip, unsigned int offset) +{ + dev_dbg(chip->parent, "gpio_free: offset%d\n", offset); + pinctrl_gpio_free(offset+chip->base); +} + +/* + * IRQ code + */ +static void npcmgpio_irq_handler(struct irq_desc *desc) +{ + struct gpio_chip *gc; + struct irq_chip *chip; + struct NPCM_GPIO *bank; + u32 sts, en, bit; + + gc = irq_desc_get_handler_data(desc); + bank = gpiochip_get_data(gc); + chip = irq_desc_get_chip(desc); + + chained_irq_enter(chip, desc); + sts = ioread32(bank->base + NPCM_GP_N_EVST); + en = ioread32(bank->base + NPCM_GP_N_EVEN); + dev_dbg(chip->parent_device, "==> got irq sts %.8x %.8x\n", sts, + en); + + sts &= en; + for_each_set_bit(bit, (const void *)&sts, GPIO_PER_BANK) + generic_handle_irq(irq_linear_revmap(gc->irq.domain, bit)); + chained_irq_exit(chip, desc); +} + +/* Set trigger type of GPIO interrupt */ +static int npcmgpio_set_irq_type(struct irq_data *d, unsigned int type) +{ + struct NPCM_GPIO *bank = + gpiochip_get_data(irq_data_get_irq_chip_data(d)); + unsigned int gpio = d->hwirq; + + dev_dbg(d->chip->parent_device, "setirqtype: %u.%u = %u\n", gpio, + d->irq, type); + switch (type) { + case IRQ_TYPE_EDGE_RISING: + /* EVTYP=1, POL=0, EVBE=0 */ + dev_dbg(d->chip->parent_device, "edge.rising\n"); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_EVBE); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_POL); + break; + case IRQ_TYPE_EDGE_FALLING: + /* EVTYP=1, POL=1, EVBE=1 */ + dev_dbg(d->chip->parent_device, "edge.falling\n"); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_EVBE); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_POL); + break; + case IRQ_TYPE_EDGE_BOTH: + /* EVTYP=1, POL=0, EVBE=1 */ + dev_dbg(d->chip->parent_device, "edge.both\n"); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_EVBE); + break; + case IRQ_TYPE_LEVEL_LOW: + /* EVTYP=0, POL=1 */ + dev_dbg(d->chip->parent_device, "level.low\n"); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_POL); + break; + case IRQ_TYPE_LEVEL_HIGH: + /* EVTYP=0, POL=0 */ + dev_dbg(d->chip->parent_device, "level.high\n"); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_POL); + break; + default: + dev_dbg(d->chip->parent_device, "invalid irq type\n"); + return -EINVAL; + } + if (type & (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW)) { + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_EVTYP); + irq_set_handler_locked(d, handle_level_irq); + } else if (type & (IRQ_TYPE_EDGE_BOTH | IRQ_TYPE_EDGE_RISING + | IRQ_TYPE_EDGE_FALLING)) { + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_EVTYP); + irq_set_handler_locked(d, handle_edge_irq); + } + return 0; +} + +/* ACK GPIO interrupt */ +static void npcmgpio_irq_ack(struct irq_data *d) +{ + struct NPCM_GPIO *bank = + gpiochip_get_data(irq_data_get_irq_chip_data(d)); + unsigned int gpio = d->hwirq; + + dev_dbg(d->chip->parent_device, "irq_ack: %u.%u\n", gpio, d->irq); + gpio_bitop(bank, opSET, gpio, NPCM_GP_N_EVST); +} + +/* Disable GPIO interrupt */ +static void npcmgpio_irq_mask(struct irq_data *d) +{ + struct NPCM_GPIO *bank = + gpiochip_get_data(irq_data_get_irq_chip_data(d)); + unsigned int gpio = d->hwirq; + + /* Clear events */ + dev_dbg(d->chip->parent_device, "irq_mask: %u.%u\n", gpio, d->irq); + gpio_bitop(bank, opSET, gpio, NPCM_GP_N_EVENC); +} + +/* Enable GPIO interrupt */ +static void npcmgpio_irq_unmask(struct irq_data *d) +{ + struct NPCM_GPIO *bank = + gpiochip_get_data(irq_data_get_irq_chip_data(d)); + unsigned int gpio = d->hwirq; + + /* Enable events */ + dev_dbg(d->chip->parent_device, "irq_unmask: %u.%u\n", gpio, d->irq); + gpio_bitop(bank, opSET, gpio, NPCM_GP_N_EVENS); +} + +/* Initialize GPIO interrupt */ +static unsigned int npcmgpio_irq_startup(struct irq_data *d) +{ + struct gpio_chip *gc = irq_data_get_irq_chip_data(d); + unsigned int gpio = d->hwirq; + + /* active-high, input, clear interrupt, enable interrupt */ + dev_dbg(d->chip->parent_device, "startup: %u.%u\n", gpio, d->irq); + npcmgpio_direction_output(gc, gpio, 1); + npcmgpio_direction_input(gc, gpio); + npcmgpio_irq_ack(d); + npcmgpio_irq_unmask(d); + return 0; +} + +static struct irq_chip npcmgpio_irqchip = { + .name = "npcm", + .irq_ack = npcmgpio_irq_ack, + .irq_unmask = npcmgpio_irq_unmask, + .irq_mask = npcmgpio_irq_mask, + .irq_set_type = npcmgpio_set_irq_type, + .irq_startup = npcmgpio_irq_startup, +}; + + +/* + * PINCTRL code + */ +static const int smb0_pins[] = { 115, 114 }; +static const int smb0b_pins[] = { 195, 194 }; +static const int smb0c_pins[] = { 202, 196 }; +static const int smb0d_pins[] = { 198, 199 }; +static const int smb0den_pins[] = { 197 }; + +static const int smb1_pins[] = { 117, 116 }; +static const int smb1b_pins[] = { 126, 127 }; +static const int smb1c_pins[] = { 124, 125 }; +static const int smb1d_pins[] = { 4, 5 }; + +static const int smb2_pins[] = { 119, 118 }; +static const int smb2b_pins[] = { 122, 123 }; +static const int smb2c_pins[] = { 120, 121 }; +static const int smb2d_pins[] = { 6, 7 }; + +static const int smb3_pins[] = { 30, 31 }; +static const int smb3b_pins[] = { 39, 40 }; +static const int smb3c_pins[] = { 37, 38 }; +static const int smb3d_pins[] = { 59, 60 }; + +static const int smb4_pins[] = { 28, 29 }; +static const int smb4b_pins[] = { 18, 19 }; +static const int smb4c_pins[] = { 20, 21 }; +static const int smb4d_pins[] = { 22, 23 }; +static const int smb4den_pins[] = { 17 }; + +static const int smb5_pins[] = { 26, 27 }; +static const int smb5b_pins[] = { 13, 12 }; +static const int smb5c_pins[] = { 15, 14 }; +static const int smb5d_pins[] = { 94, 93 }; +static const int ga20kbc_pins[] = { 94, 93 }; + +static const int smb6_pins[] = { 172, 171 }; +static const int smb7_pins[] = { 174, 173 }; +static const int smb8_pins[] = { 129, 128 }; +static const int smb9_pins[] = { 131, 130 }; +static const int smb10_pins[] = { 133, 132 }; +static const int smb11_pins[] = { 135, 134 }; +static const int smb12_pins[] = { 221, 220 }; +static const int smb13_pins[] = { 223, 222 }; +static const int smb14_pins[] = { 22, 23 }; +static const int smb15_pins[] = { 20, 21 }; + +static const int fanin0_pins[] = { 64 }; +static const int fanin1_pins[] = { 65 }; +static const int fanin2_pins[] = { 66 }; +static const int fanin3_pins[] = { 67 }; +static const int fanin4_pins[] = { 68 }; +static const int fanin5_pins[] = { 69 }; +static const int fanin6_pins[] = { 70 }; +static const int fanin7_pins[] = { 71 }; +static const int fanin8_pins[] = { 72 }; +static const int fanin9_pins[] = { 73 }; +static const int fanin10_pins[] = { 74 }; +static const int fanin11_pins[] = { 75 }; +static const int fanin12_pins[] = { 76 }; +static const int fanin13_pins[] = { 77 }; +static const int fanin14_pins[] = { 78 }; +static const int fanin15_pins[] = { 79 }; +static const int faninx_pins[] = { 175, 176, 177, 203 }; + +static const int pwm0_pins[] = { 80 }; +static const int pwm1_pins[] = { 81 }; +static const int pwm2_pins[] = { 82 }; +static const int pwm3_pins[] = { 83 }; +static const int pwm4_pins[] = { 144 }; +static const int pwm5_pins[] = { 145 }; +static const int pwm6_pins[] = { 146 }; +static const int pwm7_pins[] = { 147 }; + +static const int uart1_pins[] = { 43, 44, 45, 46, 47, 61, 62, 63 }; +static const int uart2_pins[] = { 48, 49, 50, 51, 52, 53, 54, 55 }; + +static const int rg1_pins[] = { 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107 }; +static const int rg1mdio_pins[] = { 108, 109 }; + +static const int rg2_pins[] = { 110, 111, 112, 113, 208, 209, 210, 211, 212, + 213, 214, 215 }; +static const int rg2mdio_pins[] = { 216, 217 }; +static const int ddr_pins[] = { 110, 111, 112, 113, 208, 209, 210, 211, 212, + 213, 214, 215, 216, 217 }; + +static const int iox1_pins[] = { 0, 1, 2, 3 }; +static const int iox2_pins[] = { 4, 5, 6, 7 }; +static const int ioxh_pins[] = { 10, 11, 24, 25 }; + +static const int mmc_pins[] = { 152, 154, 156, 157, 158, 159 }; +static const int mmcwp_pins[] = { 153 }; +static const int mmccd_pins[] = { 155 }; +static const int mmcrst_pins[] = { 155 }; +static const int mmc8_pins[] = { 148, 149, 150, 151 }; + +static const int r1_pins[] = { 178, 179, 180, 181, 182, 193, 201 }; +static const int r1err_pins[] = { 56 }; +static const int r1md_pins[] = { 57, 58 }; + +static const int r2_pins[] = { 84, 85, 86, 87, 88, 89, 200 }; +static const int r2err_pins[] = { 90 }; +static const int r2md_pins[] = { 91, 92 }; + +static const int sd1_pins[] = { 136, 137, 138, 139, 140, 141, 142, 143 }; +static const int sd1pwr_pins[] = { 143 }; + +static const int wdog1_pins[] = { 218 }; +static const int wdog2_pins[] = { 219 }; + +static const int bmcuart0a_pins[] = { 41, 42 }; +static const int bmcuart0b_pins[] = { 48, 49 }; + +static const int bmcuart1_pins[] = { 43, 44, 62, 63 }; + +static const int scipme_pins[] = { 169 }; +static const int sci_pins[] = { 170 }; +static const int serirq_pins[] = { 162 }; + +static const int clkout_pins[] = { 160 }; +static const int clkreq_pins[] = { 231 }; + +static const int jtag2_pins[] = { 43, 44, 45, 46, 47 }; + +static const int gspi_pins[] = { 12, 13, 14, 15 }; + +static const int spix_pins[] = { 224, 225, 226, 227, 229, 230 }; +static const int spixcs1_pins[] = { 228 }; + +static const int pspi1_pins[] = { 175, 176, 177 }; +static const int pspi2_pins[] = { 17, 18, 19 }; + +static const int spi0cs1_pins[] = { 32 }; + +static const int spi3_pins[] = { 183, 184, 185, 186 }; +static const int spi3cs1_pins[] = { 187 }; +static const int spi3quad_pins[] = { 188, 189 }; +static const int spi3cs2_pins[] = { 188 }; +static const int spi3cs3_pins[] = { 189 }; + +static const int ddc_pins[] = { 204, 205, 206, 207 }; + +static const int lpc_pins[] = { 95, 161, 163, 164, 165, 166, 167 }; +static const int lpcclk_pins[] = { 168 }; +static const int espi_pins[] = { 95, 161, 163, 164, 165, 166, 167, 168 }; + +static const int lkgpo0_pins[] = { 16 }; +static const int lkgpo1_pins[] = { 8 }; +static const int lkgpo2_pins[] = { 9 }; + +static const int nprd_smi_pins[] = { 190 }; + +/* + * pin: name, number + * group: name, npins, pins + * function: name, ngroups, groups + */ +struct npcm_group { + const char *name; + const unsigned int *pins; + int npins; +}; + +#define NPCM_GRPS \ + GRP(smb0), \ + GRP(smb0b), \ + GRP(smb0c), \ + GRP(smb0d), \ + GRP(smb0den), \ + GRP(smb1), \ + GRP(smb1b), \ + GRP(smb1c), \ + GRP(smb1d), \ + GRP(smb2), \ + GRP(smb2b), \ + GRP(smb2c), \ + GRP(smb2d), \ + GRP(smb3), \ + GRP(smb3b), \ + GRP(smb3c), \ + GRP(smb3d), \ + GRP(smb4), \ + GRP(smb4b), \ + GRP(smb4c), \ + GRP(smb4d), \ + GRP(smb4den), \ + GRP(smb5), \ + GRP(smb5b), \ + GRP(smb5c), \ + GRP(smb5d), \ + GRP(ga20kbc), \ + GRP(smb6), \ + GRP(smb7), \ + GRP(smb8), \ + GRP(smb9), \ + GRP(smb10), \ + GRP(smb11), \ + GRP(smb12), \ + GRP(smb13), \ + GRP(smb14), \ + GRP(smb15), \ + GRP(fanin0), \ + GRP(fanin1), \ + GRP(fanin2), \ + GRP(fanin3), \ + GRP(fanin4), \ + GRP(fanin5), \ + GRP(fanin6), \ + GRP(fanin7), \ + GRP(fanin8), \ + GRP(fanin9), \ + GRP(fanin10), \ + GRP(fanin11), \ + GRP(fanin12), \ + GRP(fanin13), \ + GRP(fanin14), \ + GRP(fanin15), \ + GRP(faninx), \ + GRP(pwm0), \ + GRP(pwm1), \ + GRP(pwm2), \ + GRP(pwm3), \ + GRP(pwm4), \ + GRP(pwm5), \ + GRP(pwm6), \ + GRP(pwm7), \ + GRP(rg1), \ + GRP(rg1mdio), \ + GRP(rg2), \ + GRP(rg2mdio), \ + GRP(ddr), \ + GRP(uart1), \ + GRP(uart2), \ + GRP(bmcuart0a), \ + GRP(bmcuart0b), \ + GRP(bmcuart1), \ + GRP(iox1), \ + GRP(iox2), \ + GRP(ioxh), \ + GRP(gspi), \ + GRP(mmc), \ + GRP(mmcwp), \ + GRP(mmccd), \ + GRP(mmcrst), \ + GRP(mmc8), \ + GRP(r1), \ + GRP(r1err), \ + GRP(r1md), \ + GRP(r2), \ + GRP(r2err), \ + GRP(r2md), \ + GRP(sd1), \ + GRP(sd1pwr), \ + GRP(wdog1), \ + GRP(wdog2), \ + GRP(scipme), \ + GRP(sci), \ + GRP(serirq), \ + GRP(jtag2), \ + GRP(spix), \ + GRP(spixcs1), \ + GRP(pspi1), \ + GRP(pspi2), \ + GRP(ddc), \ + GRP(clkreq), \ + GRP(clkout), \ + GRP(spi3), \ + GRP(spi3cs1), \ + GRP(spi3quad), \ + GRP(spi3cs2), \ + GRP(spi3cs3), \ + GRP(spi0cs1), \ + GRP(lpc), \ + GRP(lpcclk), \ + GRP(espi), \ + GRP(lkgpo0), \ + GRP(lkgpo1), \ + GRP(lkgpo2), \ + GRP(nprd_smi), \ + \ + +/* Group enums */ +enum { +#define GRP(x) fn_ ## x + NPCM_GRPS + /* add placeholder for none/gpio */ + GRP(none), + GRP(gpio), +#undef GRP +}; + +/* Group names/pins */ +static struct npcm_group npcm_groups[] = { +#define GRP(x) { .name = #x, .pins = x ## _pins, .npins = ARRAY_SIZE(x ## _pins) } + NPCM_GRPS +#undef GRP +}; + +#define NPCM_SFUNC(a) NPCM_FUNC(a, #a) +#define NPCM_FUNC(a, b...) static const char *a ## _grp[] = { b } +#define NPCM_MKFUNC(nm) { .name = #nm, .ngroups = ARRAY_SIZE(nm ## _grp), .groups = nm ## _grp } +struct npcm_func { + const char *name; + const unsigned int ngroups; + const char *const *groups; +}; + +NPCM_SFUNC(smb0); +NPCM_SFUNC(smb0b); +NPCM_SFUNC(smb0c); +NPCM_SFUNC(smb0d); +NPCM_SFUNC(smb0den); +NPCM_SFUNC(smb1); +NPCM_SFUNC(smb1b); +NPCM_SFUNC(smb1c); +NPCM_SFUNC(smb1d); +NPCM_SFUNC(smb2); +NPCM_SFUNC(smb2b); +NPCM_SFUNC(smb2c); +NPCM_SFUNC(smb2d); +NPCM_SFUNC(smb3); +NPCM_SFUNC(smb3b); +NPCM_SFUNC(smb3c); +NPCM_SFUNC(smb3d); +NPCM_SFUNC(smb4); +NPCM_SFUNC(smb4b); +NPCM_SFUNC(smb4c); +NPCM_SFUNC(smb4d); +NPCM_SFUNC(smb4den); +NPCM_SFUNC(smb5); +NPCM_SFUNC(smb5b); +NPCM_SFUNC(smb5c); +NPCM_SFUNC(smb5d); +NPCM_SFUNC(ga20kbc); +NPCM_SFUNC(smb6); +NPCM_SFUNC(smb7); +NPCM_SFUNC(smb8); +NPCM_SFUNC(smb9); +NPCM_SFUNC(smb10); +NPCM_SFUNC(smb11); +NPCM_SFUNC(smb12); +NPCM_SFUNC(smb13); +NPCM_SFUNC(smb14); +NPCM_SFUNC(smb15); +NPCM_SFUNC(fanin0); +NPCM_SFUNC(fanin1); +NPCM_SFUNC(fanin2); +NPCM_SFUNC(fanin3); +NPCM_SFUNC(fanin4); +NPCM_SFUNC(fanin5); +NPCM_SFUNC(fanin6); +NPCM_SFUNC(fanin7); +NPCM_SFUNC(fanin8); +NPCM_SFUNC(fanin9); +NPCM_SFUNC(fanin10); +NPCM_SFUNC(fanin11); +NPCM_SFUNC(fanin12); +NPCM_SFUNC(fanin13); +NPCM_SFUNC(fanin14); +NPCM_SFUNC(fanin15); +NPCM_SFUNC(faninx); +NPCM_SFUNC(pwm0); +NPCM_SFUNC(pwm1); +NPCM_SFUNC(pwm2); +NPCM_SFUNC(pwm3); +NPCM_SFUNC(pwm4); +NPCM_SFUNC(pwm5); +NPCM_SFUNC(pwm6); +NPCM_SFUNC(pwm7); +NPCM_SFUNC(rg1); +NPCM_SFUNC(rg1mdio); +NPCM_SFUNC(rg2); +NPCM_SFUNC(rg2mdio); +NPCM_SFUNC(ddr); +NPCM_SFUNC(uart1); +NPCM_SFUNC(uart2); +NPCM_SFUNC(bmcuart0a); +NPCM_SFUNC(bmcuart0b); +NPCM_SFUNC(bmcuart1); +NPCM_SFUNC(iox1); +NPCM_SFUNC(iox2); +NPCM_SFUNC(ioxh); +NPCM_SFUNC(gspi); +NPCM_SFUNC(mmc); +NPCM_SFUNC(mmcwp); +NPCM_SFUNC(mmccd); +NPCM_SFUNC(mmcrst); +NPCM_SFUNC(mmc8); +NPCM_SFUNC(r1); +NPCM_SFUNC(r1err); +NPCM_SFUNC(r1md); +NPCM_SFUNC(r2); +NPCM_SFUNC(r2err); +NPCM_SFUNC(r2md); +NPCM_SFUNC(sd1); +NPCM_SFUNC(sd1pwr); +NPCM_SFUNC(wdog1); +NPCM_SFUNC(wdog2); +NPCM_SFUNC(scipme); +NPCM_SFUNC(sci); +NPCM_SFUNC(serirq); +NPCM_SFUNC(jtag2); +NPCM_SFUNC(spix); +NPCM_SFUNC(spixcs1); +NPCM_SFUNC(pspi1); +NPCM_SFUNC(pspi2); +NPCM_SFUNC(ddc); +NPCM_SFUNC(clkreq); +NPCM_SFUNC(clkout); +NPCM_SFUNC(spi3); +NPCM_SFUNC(spi3cs1); +NPCM_SFUNC(spi3quad); +NPCM_SFUNC(spi3cs2); +NPCM_SFUNC(spi3cs3); +NPCM_SFUNC(spi0cs1); +NPCM_SFUNC(lpc); +NPCM_SFUNC(lpcclk); +NPCM_SFUNC(espi); +NPCM_SFUNC(lkgpo0); +NPCM_SFUNC(lkgpo1); +NPCM_SFUNC(lkgpo2); +NPCM_SFUNC(nprd_smi); + +/* Function names */ +static struct npcm_func npcm_funcs[] = { + NPCM_MKFUNC(smb0), + NPCM_MKFUNC(smb0b), + NPCM_MKFUNC(smb0c), + NPCM_MKFUNC(smb0d), + NPCM_MKFUNC(smb0den), + NPCM_MKFUNC(smb1), + NPCM_MKFUNC(smb1b), + NPCM_MKFUNC(smb1c), + NPCM_MKFUNC(smb1d), + NPCM_MKFUNC(smb2), + NPCM_MKFUNC(smb2b), + NPCM_MKFUNC(smb2c), + NPCM_MKFUNC(smb2d), + NPCM_MKFUNC(smb3), + NPCM_MKFUNC(smb3b), + NPCM_MKFUNC(smb3c), + NPCM_MKFUNC(smb3d), + NPCM_MKFUNC(smb4), + NPCM_MKFUNC(smb4b), + NPCM_MKFUNC(smb4c), + NPCM_MKFUNC(smb4d), + NPCM_MKFUNC(smb4den), + NPCM_MKFUNC(smb5), + NPCM_MKFUNC(smb5b), + NPCM_MKFUNC(smb5c), + NPCM_MKFUNC(smb5d), + NPCM_MKFUNC(ga20kbc), + NPCM_MKFUNC(smb6), + NPCM_MKFUNC(smb7), + NPCM_MKFUNC(smb8), + NPCM_MKFUNC(smb9), + NPCM_MKFUNC(smb10), + NPCM_MKFUNC(smb11), + NPCM_MKFUNC(smb12), + NPCM_MKFUNC(smb13), + NPCM_MKFUNC(smb14), + NPCM_MKFUNC(smb15), + NPCM_MKFUNC(fanin0), + NPCM_MKFUNC(fanin1), + NPCM_MKFUNC(fanin2), + NPCM_MKFUNC(fanin3), + NPCM_MKFUNC(fanin4), + NPCM_MKFUNC(fanin5), + NPCM_MKFUNC(fanin6), + NPCM_MKFUNC(fanin7), + NPCM_MKFUNC(fanin8), + NPCM_MKFUNC(fanin9), + NPCM_MKFUNC(fanin10), + NPCM_MKFUNC(fanin11), + NPCM_MKFUNC(fanin12), + NPCM_MKFUNC(fanin13), + NPCM_MKFUNC(fanin14), + NPCM_MKFUNC(fanin15), + NPCM_MKFUNC(faninx), + NPCM_MKFUNC(pwm0), + NPCM_MKFUNC(pwm1), + NPCM_MKFUNC(pwm2), + NPCM_MKFUNC(pwm3), + NPCM_MKFUNC(pwm4), + NPCM_MKFUNC(pwm5), + NPCM_MKFUNC(pwm6), + NPCM_MKFUNC(pwm7), + NPCM_MKFUNC(rg1), + NPCM_MKFUNC(rg1mdio), + NPCM_MKFUNC(rg2), + NPCM_MKFUNC(rg2mdio), + NPCM_MKFUNC(ddr), + NPCM_MKFUNC(uart1), + NPCM_MKFUNC(uart2), + NPCM_MKFUNC(bmcuart0a), + NPCM_MKFUNC(bmcuart0b), + NPCM_MKFUNC(bmcuart1), + NPCM_MKFUNC(iox1), + NPCM_MKFUNC(iox2), + NPCM_MKFUNC(ioxh), + NPCM_MKFUNC(gspi), + NPCM_MKFUNC(mmc), + NPCM_MKFUNC(mmcwp), + NPCM_MKFUNC(mmccd), + NPCM_MKFUNC(mmcrst), + NPCM_MKFUNC(mmc8), + NPCM_MKFUNC(r1), + NPCM_MKFUNC(r1err), + NPCM_MKFUNC(r1md), + NPCM_MKFUNC(r2), + NPCM_MKFUNC(r2err), + NPCM_MKFUNC(r2md), + NPCM_MKFUNC(sd1), + NPCM_MKFUNC(sd1pwr), + NPCM_MKFUNC(wdog1), + NPCM_MKFUNC(wdog2), + NPCM_MKFUNC(scipme), + NPCM_MKFUNC(sci), + NPCM_MKFUNC(serirq), + NPCM_MKFUNC(jtag2), + NPCM_MKFUNC(spix), + NPCM_MKFUNC(spixcs1), + NPCM_MKFUNC(pspi1), + NPCM_MKFUNC(pspi2), + NPCM_MKFUNC(ddc), + NPCM_MKFUNC(clkreq), + NPCM_MKFUNC(clkout), + NPCM_MKFUNC(spi3), + NPCM_MKFUNC(spi3cs1), + NPCM_MKFUNC(spi3quad), + NPCM_MKFUNC(spi3cs2), + NPCM_MKFUNC(spi3cs3), + NPCM_MKFUNC(spi0cs1), + NPCM_MKFUNC(lpc), + NPCM_MKFUNC(lpcclk), + NPCM_MKFUNC(espi), + NPCM_MKFUNC(lkgpo0), + NPCM_MKFUNC(lkgpo1), + NPCM_MKFUNC(lkgpo2), + NPCM_MKFUNC(nprd_smi), +}; + +#define PINCFG(a, b, c, d, e, f, g, h, i, j, k) \ + [a] { .fn0 = fn_ ## b, .reg0 = NPCM7XX_GCR_ ## c, .bit0 = d, \ + .fn1 = fn_ ## e, .reg1 = NPCM7XX_GCR_ ## f, .bit1 = g, \ + .fn2 = fn_ ## h, .reg2 = NPCM7XX_GCR_ ## i, .bit2 = j, \ + .flag = k } + +/* Drive strength controlled by NPCM_GP_N_ODSC */ +#define DRIVE_STRENGTH_LO_SHIFT 8 +#define DRIVE_STRENGTH_HI_SHIFT 12 +#define DRIVE_STRENGTH_MASK 0x0000FF00 + +#define DS(lo, hi) (((lo) << DRIVE_STRENGTH_LO_SHIFT) | \ + ((hi) << DRIVE_STRENGTH_HI_SHIFT)) +#define DSLO(x) (((x) >> DRIVE_STRENGTH_LO_SHIFT) & 0xF) +#define DSHI(x) (((x) >> DRIVE_STRENGTH_HI_SHIFT) & 0xF) + +#define GPI 0x1 /* Not GPO */ +#define GPO 0x2 /* Not GPI */ +#define SLEW 0x4 /* Has Slew Control, NPCM_GP_N_OSRC */ +#define SLEWLPC 0x8 /* Has Slew Control, SRCNT.3 */ + +struct npcm_pincfg { + int flag; + int fn0, reg0, bit0; + int fn1, reg1, bit1; + int fn2, reg2, bit2; +}; + +static const struct npcm_pincfg pincfg[] = { + /* PIN FUNCTION 1 FUNCTION 2 FUNCTION 3 FLAGS */ + PINCFG(0, iox1, MFSEL1, 30, none, NONE, 0, none, NONE, 0, 0), + PINCFG(1, iox1, MFSEL1, 30, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(2, iox1, MFSEL1, 30, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(3, iox1, MFSEL1, 30, none, NONE, 0, none, NONE, 0, 0), + PINCFG(4, iox2, MFSEL3, 14, smb1d, I2CSEGSEL, 7, none, NONE, 0, SLEW), + PINCFG(5, iox2, MFSEL3, 14, smb1d, I2CSEGSEL, 7, none, NONE, 0, SLEW), + PINCFG(6, iox2, MFSEL3, 14, smb2d, I2CSEGSEL, 10, none, NONE, 0, SLEW), + PINCFG(7, iox2, MFSEL3, 14, smb2d, I2CSEGSEL, 10, none, NONE, 0, SLEW), + PINCFG(8, lkgpo1, FLOCKR1, 4, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(9, lkgpo2, FLOCKR1, 8, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(10, ioxh, MFSEL3, 18, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(11, ioxh, MFSEL3, 18, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(12, gspi, MFSEL1, 24, smb5b, I2CSEGSEL, 19, none, NONE, 0, SLEW), + PINCFG(13, gspi, MFSEL1, 24, smb5b, I2CSEGSEL, 19, none, NONE, 0, SLEW), + PINCFG(14, gspi, MFSEL1, 24, smb5c, I2CSEGSEL, 20, none, NONE, 0, SLEW), + PINCFG(15, gspi, MFSEL1, 24, smb5c, I2CSEGSEL, 20, none, NONE, 0, SLEW), + PINCFG(16, lkgpo0, FLOCKR1, 0, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(17, pspi2, MFSEL3, 13, smb4den, I2CSEGSEL, 23, none, NONE, 0, DS(8, 12)), + PINCFG(18, pspi2, MFSEL3, 13, smb4b, I2CSEGSEL, 14, none, NONE, 0, DS(8, 12)), + PINCFG(19, pspi2, MFSEL3, 13, smb4b, I2CSEGSEL, 14, none, NONE, 0, DS(8, 12)), + PINCFG(20, smb4c, I2CSEGSEL, 15, smb15, MFSEL3, 8, none, NONE, 0, 0), + PINCFG(21, smb4c, I2CSEGSEL, 15, smb15, MFSEL3, 8, none, NONE, 0, 0), + PINCFG(22, smb4d, I2CSEGSEL, 16, smb14, MFSEL3, 7, none, NONE, 0, 0), + PINCFG(23, smb4d, I2CSEGSEL, 16, smb14, MFSEL3, 7, none, NONE, 0, 0), + PINCFG(24, ioxh, MFSEL3, 18, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(25, ioxh, MFSEL3, 18, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(26, smb5, MFSEL1, 2, none, NONE, 0, none, NONE, 0, 0), + PINCFG(27, smb5, MFSEL1, 2, none, NONE, 0, none, NONE, 0, 0), + PINCFG(28, smb4, MFSEL1, 1, none, NONE, 0, none, NONE, 0, 0), + PINCFG(29, smb4, MFSEL1, 1, none, NONE, 0, none, NONE, 0, 0), + PINCFG(30, smb3, MFSEL1, 0, none, NONE, 0, none, NONE, 0, 0), + PINCFG(31, smb3, MFSEL1, 0, none, NONE, 0, none, NONE, 0, 0), + + PINCFG(32, spi0cs1, MFSEL1, 3, none, NONE, 0, none, NONE, 0, 0), + PINCFG(33, none, NONE, 0, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(34, none, NONE, 0, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(37, smb3c, I2CSEGSEL, 12, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(38, smb3c, I2CSEGSEL, 12, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(39, smb3b, I2CSEGSEL, 11, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(40, smb3b, I2CSEGSEL, 11, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(41, bmcuart0a, MFSEL1, 9, none, NONE, 0, none, NONE, 0, 0), + PINCFG(42, bmcuart0a, MFSEL1, 9, none, NONE, 0, none, NONE, 0, DS(2, 4) | GPO), + PINCFG(43, uart1, MFSEL1, 10, jtag2, MFSEL4, 0, bmcuart1, MFSEL3, 24, 0), + PINCFG(44, uart1, MFSEL1, 10, jtag2, MFSEL4, 0, bmcuart1, MFSEL3, 24, 0), + PINCFG(45, uart1, MFSEL1, 10, jtag2, MFSEL4, 0, none, NONE, 0, 0), + PINCFG(46, uart1, MFSEL1, 10, jtag2, MFSEL4, 0, none, NONE, 0, DS(2, 8)), + PINCFG(47, uart1, MFSEL1, 10, jtag2, MFSEL4, 0, none, NONE, 0, DS(2, 8)), + PINCFG(48, uart2, MFSEL1, 11, bmcuart0b, MFSEL4, 1, none, NONE, 0, GPO), + PINCFG(49, uart2, MFSEL1, 11, bmcuart0b, MFSEL4, 1, none, NONE, 0, 0), + PINCFG(50, uart2, MFSEL1, 11, none, NONE, 0, none, NONE, 0, 0), + PINCFG(51, uart2, MFSEL1, 11, none, NONE, 0, none, NONE, 0, GPO), + PINCFG(52, uart2, MFSEL1, 11, none, NONE, 0, none, NONE, 0, 0), + PINCFG(53, uart2, MFSEL1, 11, none, NONE, 0, none, NONE, 0, GPO), + PINCFG(54, uart2, MFSEL1, 11, none, NONE, 0, none, NONE, 0, 0), + PINCFG(55, uart2, MFSEL1, 11, none, NONE, 0, none, NONE, 0, 0), + PINCFG(56, r1err, MFSEL1, 12, none, NONE, 0, none, NONE, 0, 0), + PINCFG(57, r1md, MFSEL1, 13, none, NONE, 0, none, NONE, 0, DS(2, 4)), + PINCFG(58, r1md, MFSEL1, 13, none, NONE, 0, none, NONE, 0, DS(2, 4)), + PINCFG(59, smb3d, I2CSEGSEL, 13, none, NONE, 0, none, NONE, 0, 0), + PINCFG(60, smb3d, I2CSEGSEL, 13, none, NONE, 0, none, NONE, 0, 0), + PINCFG(61, uart1, MFSEL1, 10, none, NONE, 0, none, NONE, 0, GPO), + PINCFG(62, uart1, MFSEL1, 10, bmcuart1, MFSEL3, 24, none, NONE, 0, GPO), + PINCFG(63, uart1, MFSEL1, 10, bmcuart1, MFSEL3, 24, none, NONE, 0, GPO), + + PINCFG(64, fanin0, MFSEL2, 0, none, NONE, 0, none, NONE, 0, 0), + PINCFG(65, fanin1, MFSEL2, 1, none, NONE, 0, none, NONE, 0, 0), + PINCFG(66, fanin2, MFSEL2, 2, none, NONE, 0, none, NONE, 0, 0), + PINCFG(67, fanin3, MFSEL2, 3, none, NONE, 0, none, NONE, 0, 0), + PINCFG(68, fanin4, MFSEL2, 4, none, NONE, 0, none, NONE, 0, 0), + PINCFG(69, fanin5, MFSEL2, 5, none, NONE, 0, none, NONE, 0, 0), + PINCFG(70, fanin6, MFSEL2, 6, none, NONE, 0, none, NONE, 0, 0), + PINCFG(71, fanin7, MFSEL2, 7, none, NONE, 0, none, NONE, 0, 0), + PINCFG(72, fanin8, MFSEL2, 8, none, NONE, 0, none, NONE, 0, 0), + PINCFG(73, fanin9, MFSEL2, 9, none, NONE, 0, none, NONE, 0, 0), + PINCFG(74, fanin10, MFSEL2, 10, none, NONE, 0, none, NONE, 0, 0), + PINCFG(75, fanin11, MFSEL2, 11, none, NONE, 0, none, NONE, 0, 0), + PINCFG(76, fanin12, MFSEL2, 12, none, NONE, 0, none, NONE, 0, 0), + PINCFG(77, fanin13, MFSEL2, 13, none, NONE, 0, none, NONE, 0, 0), + PINCFG(78, fanin14, MFSEL2, 14, none, NONE, 0, none, NONE, 0, 0), + PINCFG(79, fanin15, MFSEL2, 15, none, NONE, 0, none, NONE, 0, 0), + PINCFG(80, pwm0, MFSEL2, 16, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(81, pwm1, MFSEL2, 17, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(82, pwm2, MFSEL2, 18, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(83, pwm3, MFSEL2, 19, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(84, r2, MFSEL1, 14, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(85, r2, MFSEL1, 14, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(86, r2, MFSEL1, 14, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(87, r2, MFSEL1, 14, none, NONE, 0, none, NONE, 0, 0), + PINCFG(88, r2, MFSEL1, 14, none, NONE, 0, none, NONE, 0, 0), + PINCFG(89, r2, MFSEL1, 14, none, NONE, 0, none, NONE, 0, 0), + PINCFG(90, r2err, MFSEL1, 15, none, NONE, 0, none, NONE, 0, 0), + PINCFG(91, r2md, MFSEL1, 16, none, NONE, 0, none, NONE, 0, DS(2, 4)), + PINCFG(92, r2md, MFSEL1, 16, none, NONE, 0, none, NONE, 0, DS(2, 4)), + PINCFG(93, ga20kbc, MFSEL1, 17, smb5d, I2CSEGSEL, 21, none, NONE, 0, 0), + PINCFG(94, ga20kbc, MFSEL1, 17, smb5d, I2CSEGSEL, 21, none, NONE, 0, 0), + PINCFG(95, lpc, NONE, 0, espi, MFSEL4, 8, gpio, MFSEL1, 26, 0), + + PINCFG(96, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(97, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(98, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(99, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(100, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(101, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(102, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(103, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(104, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(105, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(106, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(107, rg1, MFSEL4, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(108, rg1mdio, MFSEL4, 21, none, NONE, 0, none, NONE, 0, 0), + PINCFG(109, rg1mdio, MFSEL4, 21, none, NONE, 0, none, NONE, 0, 0), + PINCFG(110, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(111, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(112, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(113, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(114, smb0, MFSEL1, 6, none, NONE, 0, none, NONE, 0, 0), + PINCFG(115, smb0, MFSEL1, 6, none, NONE, 0, none, NONE, 0, 0), + PINCFG(116, smb1, MFSEL1, 7, none, NONE, 0, none, NONE, 0, 0), + PINCFG(117, smb1, MFSEL1, 7, none, NONE, 0, none, NONE, 0, 0), + PINCFG(118, smb2, MFSEL1, 8, none, NONE, 0, none, NONE, 0, 0), + PINCFG(119, smb2, MFSEL1, 8, none, NONE, 0, none, NONE, 0, 0), + PINCFG(120, smb2c, I2CSEGSEL, 9, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(121, smb2c, I2CSEGSEL, 9, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(122, smb2b, I2CSEGSEL, 8, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(123, smb2b, I2CSEGSEL, 8, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(124, smb1c, I2CSEGSEL, 6, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(125, smb1c, I2CSEGSEL, 6, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(126, smb1b, I2CSEGSEL, 5, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(127, smb1b, I2CSEGSEL, 5, none, NONE, 0, none, NONE, 0, SLEW), + + PINCFG(128, smb8, MFSEL4, 11, none, NONE, 0, none, NONE, 0, 0), + PINCFG(129, smb8, MFSEL4, 11, none, NONE, 0, none, NONE, 0, 0), + PINCFG(130, smb9, MFSEL4, 12, none, NONE, 0, none, NONE, 0, 0), + PINCFG(131, smb9, MFSEL4, 12, none, NONE, 0, none, NONE, 0, 0), + PINCFG(132, smb10, MFSEL4, 13, none, NONE, 0, none, NONE, 0, 0), + PINCFG(133, smb10, MFSEL4, 13, none, NONE, 0, none, NONE, 0, 0), + PINCFG(134, smb11, MFSEL4, 14, none, NONE, 0, none, NONE, 0, 0), + PINCFG(135, smb11, MFSEL4, 14, none, NONE, 0, none, NONE, 0, 0), + PINCFG(136, sd1, MFSEL3, 12, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(137, sd1, MFSEL3, 12, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(138, sd1, MFSEL3, 12, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(139, sd1, MFSEL3, 12, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(140, sd1, MFSEL3, 12, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(141, sd1, MFSEL3, 12, none, NONE, 0, none, NONE, 0, 0), + PINCFG(142, sd1, MFSEL3, 12, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(143, sd1, MFSEL3, 12, sd1pwr, MFSEL4, 5, none, NONE, 0, 0), + PINCFG(144, pwm4, MFSEL2, 20, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(145, pwm5, MFSEL2, 21, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(146, pwm6, MFSEL2, 22, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(147, pwm7, MFSEL2, 23, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(148, mmc8, MFSEL3, 11, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(149, mmc8, MFSEL3, 11, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(150, mmc8, MFSEL3, 11, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(151, mmc8, MFSEL3, 11, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(152, mmc, MFSEL3, 10, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(153, mmcwp, FLOCKR1, 24, none, NONE, 0, none, NONE, 0, 0), /* Z1/A1 */ + PINCFG(154, mmc, MFSEL3, 10, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(155, mmccd, MFSEL3, 25, mmcrst, MFSEL4, 6, none, NONE, 0, 0), /* Z1/A1 */ + PINCFG(156, mmc, MFSEL3, 10, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(157, mmc, MFSEL3, 10, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(158, mmc, MFSEL3, 10, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(159, mmc, MFSEL3, 10, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + + PINCFG(160, clkout, MFSEL1, 21, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(161, lpc, NONE, 0, espi, MFSEL4, 8, gpio, MFSEL1, 26, DS(8, 12)), + PINCFG(162, serirq, NONE, 0, gpio, MFSEL1, 31, none, NONE, 0, DS(8, 12)), + PINCFG(163, lpc, NONE, 0, espi, MFSEL4, 8, gpio, MFSEL1, 26, 0), + PINCFG(164, lpc, NONE, 0, espi, MFSEL4, 8, gpio, MFSEL1, 26, SLEWLPC), + PINCFG(165, lpc, NONE, 0, espi, MFSEL4, 8, gpio, MFSEL1, 26, SLEWLPC), + PINCFG(166, lpc, NONE, 0, espi, MFSEL4, 8, gpio, MFSEL1, 26, SLEWLPC), + PINCFG(167, lpc, NONE, 0, espi, MFSEL4, 8, gpio, MFSEL1, 26, SLEWLPC), + PINCFG(168, lpcclk, NONE, 0, espi, MFSEL4, 8, gpio, MFSEL3, 16, 0), + PINCFG(169, scipme, MFSEL3, 0, none, NONE, 0, none, NONE, 0, 0), + PINCFG(170, sci, MFSEL1, 22, none, NONE, 0, none, NONE, 0, 0), + PINCFG(171, smb6, MFSEL3, 1, none, NONE, 0, none, NONE, 0, 0), + PINCFG(172, smb6, MFSEL3, 1, none, NONE, 0, none, NONE, 0, 0), + PINCFG(173, smb7, MFSEL3, 2, none, NONE, 0, none, NONE, 0, 0), + PINCFG(174, smb7, MFSEL3, 2, none, NONE, 0, none, NONE, 0, 0), + PINCFG(175, pspi1, MFSEL3, 4, faninx, MFSEL3, 3, none, NONE, 0, DS(8, 12)), + PINCFG(176, pspi1, MFSEL3, 4, faninx, MFSEL3, 3, none, NONE, 0, DS(8, 12)), + PINCFG(177, pspi1, MFSEL3, 4, faninx, MFSEL3, 3, none, NONE, 0, DS(8, 12)), + PINCFG(178, r1, MFSEL3, 9, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(179, r1, MFSEL3, 9, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(180, r1, MFSEL3, 9, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(181, r1, MFSEL3, 9, none, NONE, 0, none, NONE, 0, 0), + PINCFG(182, r1, MFSEL3, 9, none, NONE, 0, none, NONE, 0, 0), + PINCFG(183, spi3, MFSEL4, 16, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(184, spi3, MFSEL4, 16, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW | GPO), + PINCFG(185, spi3, MFSEL4, 16, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW | GPO), + PINCFG(186, spi3, MFSEL4, 16, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(187, spi3cs1, MFSEL4, 17, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(188, spi3quad, MFSEL4, 20, spi3cs2, MFSEL4, 18, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(189, spi3quad, MFSEL4, 20, spi3cs3, MFSEL4, 19, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(190, gpio, FLOCKR1, 20, nprd_smi, NONE, 0, none, NONE, 0, DS(2, 4)), + PINCFG(191, none, NONE, 0, none, NONE, 0, none, NONE, 0, DS(8, 12)), /* XX */ + + PINCFG(192, none, NONE, 0, none, NONE, 0, none, NONE, 0, DS(8, 12)), /* XX */ + PINCFG(193, r1, MFSEL3, 9, none, NONE, 0, none, NONE, 0, 0), + PINCFG(194, smb0b, I2CSEGSEL, 0, none, NONE, 0, none, NONE, 0, 0), + PINCFG(195, smb0b, I2CSEGSEL, 0, none, NONE, 0, none, NONE, 0, 0), + PINCFG(196, smb0c, I2CSEGSEL, 1, none, NONE, 0, none, NONE, 0, 0), + PINCFG(197, smb0den, I2CSEGSEL, 22, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(198, smb0d, I2CSEGSEL, 2, none, NONE, 0, none, NONE, 0, 0), + PINCFG(199, smb0d, I2CSEGSEL, 2, none, NONE, 0, none, NONE, 0, 0), + PINCFG(200, r2, MFSEL1, 14, none, NONE, 0, none, NONE, 0, 0), + PINCFG(201, r1, MFSEL3, 9, none, NONE, 0, none, NONE, 0, 0), + PINCFG(202, smb0c, I2CSEGSEL, 1, none, NONE, 0, none, NONE, 0, 0), + PINCFG(203, faninx, MFSEL3, 3, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(204, ddc, NONE, 0, gpio, MFSEL3, 22, none, NONE, 0, SLEW), + PINCFG(205, ddc, NONE, 0, gpio, MFSEL3, 22, none, NONE, 0, SLEW), + PINCFG(206, ddc, NONE, 0, gpio, MFSEL3, 22, none, NONE, 0, DS(4, 8)), + PINCFG(207, ddc, NONE, 0, gpio, MFSEL3, 22, none, NONE, 0, DS(4, 8)), + PINCFG(208, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(209, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(210, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(211, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(212, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(213, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(214, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(215, rg2, MFSEL4, 24, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(216, rg2mdio, MFSEL4, 23, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(217, rg2mdio, MFSEL4, 23, ddr, MFSEL3, 26, none, NONE, 0, 0), + PINCFG(218, wdog1, MFSEL3, 19, none, NONE, 0, none, NONE, 0, 0), + PINCFG(219, wdog2, MFSEL3, 20, none, NONE, 0, none, NONE, 0, DS(4, 8)), + PINCFG(220, smb12, MFSEL3, 5, none, NONE, 0, none, NONE, 0, 0), + PINCFG(221, smb12, MFSEL3, 5, none, NONE, 0, none, NONE, 0, 0), + PINCFG(222, smb13, MFSEL3, 6, none, NONE, 0, none, NONE, 0, 0), + PINCFG(223, smb13, MFSEL3, 6, none, NONE, 0, none, NONE, 0, 0), + + PINCFG(224, spix, MFSEL4, 27, none, NONE, 0, none, NONE, 0, SLEW), + PINCFG(225, spix, MFSEL4, 27, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW | GPO), + PINCFG(226, spix, MFSEL4, 27, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW | GPO), + PINCFG(227, spix, MFSEL4, 27, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(228, spixcs1, MFSEL4, 28, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(229, spix, MFSEL4, 27, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(230, spix, MFSEL4, 27, none, NONE, 0, none, NONE, 0, DS(8, 12) | SLEW), + PINCFG(231, clkreq, MFSEL4, 9, none, NONE, 0, none, NONE, 0, DS(8, 12)), + PINCFG(253, none, NONE, 0, none, NONE, 0, none, NONE, 0, GPI), /* SDHC1 power */ + PINCFG(254, none, NONE, 0, none, NONE, 0, none, NONE, 0, GPI), /* SDHC2 power */ + PINCFG(255, none, NONE, 0, none, NONE, 0, none, NONE, 0, GPI), /* DACOSEL */ +}; + +/* number, name, drv_data */ +static const struct pinctrl_pin_desc npcm_pins[] = { + PINCTRL_PIN(0, "GPIO0/IOX1DI"), + PINCTRL_PIN(1, "GPIO1/IOX1LD"), + PINCTRL_PIN(2, "GPIO2/IOX1CK"), + PINCTRL_PIN(3, "GPIO3/IOX1D0"), + PINCTRL_PIN(4, "GPIO4/IOX2DI/SMB1DSDA"), + PINCTRL_PIN(5, "GPIO5/IOX2LD/SMB1DSCL"), + PINCTRL_PIN(6, "GPIO6/IOX2CK/SMB2DSDA"), + PINCTRL_PIN(7, "GPIO7/IOX2D0/SMB2DSCL"), + PINCTRL_PIN(8, "GPIO8/LKGPO1"), + PINCTRL_PIN(9, "GPIO9/LKGPO2"), + PINCTRL_PIN(10, "GPIO10/IOXHLD"), + PINCTRL_PIN(11, "GPIO11/IOXHCK"), + PINCTRL_PIN(12, "GPIO12/GSPICK/SMB5BSCL"), + PINCTRL_PIN(13, "GPIO13/GSPIDO/SMB5BSDA"), + PINCTRL_PIN(14, "GPIO14/GSPIDI/SMB5CSCL"), + PINCTRL_PIN(15, "GPIO15/GSPICS/SMB5CSDA"), + PINCTRL_PIN(16, "GPIO16/LKGPO0"), + PINCTRL_PIN(17, "GPIO17/PSPI2DI/SMB4DEN"), + PINCTRL_PIN(18, "GPIO18/PSPI2D0/SMB4BSDA"), + PINCTRL_PIN(19, "GPIO19/PSPI2CK/SMB4BSCL"), + PINCTRL_PIN(20, "GPIO20/SMB4CSDA/SMB15SDA"), + PINCTRL_PIN(21, "GPIO21/SMB4CSCL/SMB15SCL"), + PINCTRL_PIN(22, "GPIO22/SMB4DSDA/SMB14SDA"), + PINCTRL_PIN(23, "GPIO23/SMB4DSCL/SMB14SCL"), + PINCTRL_PIN(24, "GPIO24/IOXHDO"), + PINCTRL_PIN(25, "GPIO25/IOXHDI"), + PINCTRL_PIN(26, "GPIO26/SMB5SDA"), + PINCTRL_PIN(27, "GPIO27/SMB5SCL"), + PINCTRL_PIN(28, "GPIO28/SMB4SDA"), + PINCTRL_PIN(29, "GPIO29/SMB4SCL"), + PINCTRL_PIN(30, "GPIO30/SMB3SDA"), + PINCTRL_PIN(31, "GPIO31/SMB3SCL"), + + PINCTRL_PIN(32, "GPIO32/nSPI0CS1"), + PINCTRL_PIN(33, "SPI0D2"), + PINCTRL_PIN(34, "SPI0D3"), + PINCTRL_PIN(37, "GPIO37/SMB3CSDA"), + PINCTRL_PIN(38, "GPIO38/SMB3CSCL"), + PINCTRL_PIN(39, "GPIO39/SMB3BSDA"), + PINCTRL_PIN(40, "GPIO40/SMB3BSCL"), + PINCTRL_PIN(41, "GPIO41/BSPRXD"), + PINCTRL_PIN(42, "GPO42/BSPTXD/STRAP11"), + PINCTRL_PIN(43, "GPIO43/RXD1/JTMS2/BU1RXD"), + PINCTRL_PIN(44, "GPIO44/nCTS1/JTDI2/BU1CTS"), + PINCTRL_PIN(45, "GPIO45/nDCD1/JTDO2"), + PINCTRL_PIN(46, "GPIO46/nDSR1/JTCK2"), + PINCTRL_PIN(47, "GPIO47/nRI1/JCP_RDY2"), + PINCTRL_PIN(48, "GPIO48/TXD2/BSPTXD"), + PINCTRL_PIN(49, "GPIO49/RXD2/BSPRXD"), + PINCTRL_PIN(50, "GPIO50/nCTS2"), + PINCTRL_PIN(51, "GPO51/nRTS2/STRAP2"), + PINCTRL_PIN(52, "GPIO52/nDCD2"), + PINCTRL_PIN(53, "GPIO53/nDTR2_BOUT2/STRAP1"), + PINCTRL_PIN(54, "GPIO54/nDSR2"), + PINCTRL_PIN(55, "GPIO55/nRI2"), + PINCTRL_PIN(56, "GPIO56/R1RXERR"), + PINCTRL_PIN(57, "GPIO57/R1MDC"), + PINCTRL_PIN(58, "GPIO58/R1MDIO"), + PINCTRL_PIN(59, "GPIO59/SMB3DSDA"), + PINCTRL_PIN(60, "GPIO60/SMB3DSCL"), + PINCTRL_PIN(61, "GPO61/nDTR1_BOUT1/STRAP6"), + PINCTRL_PIN(62, "GPO62/nRTST1/STRAP5"), + PINCTRL_PIN(63, "GPO63/TXD1/STRAP4"), + + PINCTRL_PIN(64, "GPIO64/FANIN0"), + PINCTRL_PIN(65, "GPIO65/FANIN1"), + PINCTRL_PIN(66, "GPIO66/FANIN2"), + PINCTRL_PIN(67, "GPIO67/FANIN3"), + PINCTRL_PIN(68, "GPIO68/FANIN4"), + PINCTRL_PIN(69, "GPIO69/FANIN5"), + PINCTRL_PIN(70, "GPIO70/FANIN6"), + PINCTRL_PIN(71, "GPIO71/FANIN7"), + PINCTRL_PIN(72, "GPIO72/FANIN8"), + PINCTRL_PIN(73, "GPIO73/FANIN9"), + PINCTRL_PIN(74, "GPIO74/FANIN10"), + PINCTRL_PIN(75, "GPIO75/FANIN11"), + PINCTRL_PIN(76, "GPIO76/FANIN12"), + PINCTRL_PIN(77, "GPIO77/FANIN13"), + PINCTRL_PIN(78, "GPIO78/FANIN14"), + PINCTRL_PIN(79, "GPIO79/FANIN15"), + PINCTRL_PIN(80, "GPIO80/PWM0"), + PINCTRL_PIN(81, "GPIO81/PWM1"), + PINCTRL_PIN(82, "GPIO82/PWM2"), + PINCTRL_PIN(83, "GPIO83/PWM3"), + PINCTRL_PIN(84, "GPIO84/R2TXD0"), + PINCTRL_PIN(85, "GPIO85/R2TXD1"), + PINCTRL_PIN(86, "GPIO86/R2TXEN"), + PINCTRL_PIN(87, "GPIO87/R2RXD0"), + PINCTRL_PIN(88, "GPIO88/R2RXD1"), + PINCTRL_PIN(89, "GPIO89/R2CRSDV"), + PINCTRL_PIN(90, "GPIO90/R2RXERR"), + PINCTRL_PIN(91, "GPIO91/R2MDC"), + PINCTRL_PIN(92, "GPIO92/R2MDIO"), + PINCTRL_PIN(93, "GPIO93/GA20/SMB5DSCL"), + PINCTRL_PIN(94, "GPIO94/nKBRST/SMB5DSDA"), + PINCTRL_PIN(95, "GPIO95/nLRESET/nESPIRST"), + + PINCTRL_PIN(96, "GPIO96/RG1TXD0"), + PINCTRL_PIN(97, "GPIO97/RG1TXD1"), + PINCTRL_PIN(98, "GPIO98/RG1TXD2"), + PINCTRL_PIN(99, "GPIO99/RG1TXD3"), + PINCTRL_PIN(100, "GPIO100/RG1TXC"), + PINCTRL_PIN(101, "GPIO101/RG1TXCTL"), + PINCTRL_PIN(102, "GPIO102/RG1RXD0"), + PINCTRL_PIN(103, "GPIO103/RG1RXD1"), + PINCTRL_PIN(104, "GPIO104/RG1RXD2"), + PINCTRL_PIN(105, "GPIO105/RG1RXD3"), + PINCTRL_PIN(106, "GPIO106/RG1RXC"), + PINCTRL_PIN(107, "GPIO107/RG1RXCTL"), + PINCTRL_PIN(108, "GPIO108/RG1MDC"), + PINCTRL_PIN(109, "GPIO109/RG1MDIO"), + PINCTRL_PIN(110, "GPIO110/RG2TXD0/DDRV0"), + PINCTRL_PIN(111, "GPIO111/RG2TXD1/DDRV1"), + PINCTRL_PIN(112, "GPIO112/RG2TXD2/DDRV2"), + PINCTRL_PIN(113, "GPIO113/RG2TXD3/DDRV3"), + PINCTRL_PIN(114, "GPIO114/SMB0SCL"), + PINCTRL_PIN(115, "GPIO115/SMB0SDA"), + PINCTRL_PIN(116, "GPIO116/SMB1SCL"), + PINCTRL_PIN(117, "GPIO117/SMB1SDA"), + PINCTRL_PIN(118, "GPIO118/SMB2SCL"), + PINCTRL_PIN(119, "GPIO119/SMB2SDA"), + PINCTRL_PIN(120, "GPIO120/SMB2CSDA"), + PINCTRL_PIN(121, "GPIO121/SMB2CSCL"), + PINCTRL_PIN(122, "GPIO122/SMB2BSDA"), + PINCTRL_PIN(123, "GPIO123/SMB2BSCL"), + PINCTRL_PIN(124, "GPIO124/SMB1CSDA"), + PINCTRL_PIN(125, "GPIO125/SMB1CSCL"), + PINCTRL_PIN(126, "GPIO126/SMB1BSDA"), + PINCTRL_PIN(127, "GPIO127/SMB1BSCL"), + + PINCTRL_PIN(128, "GPIO128/SMB8SCL"), + PINCTRL_PIN(129, "GPIO129/SMB8SDA"), + PINCTRL_PIN(130, "GPIO130/SMB9SCL"), + PINCTRL_PIN(131, "GPIO131/SMB9SDA"), + PINCTRL_PIN(132, "GPIO132/SMB10SCL"), + PINCTRL_PIN(133, "GPIO133/SMB10SDA"), + PINCTRL_PIN(134, "GPIO134/SMB11SCL"), + PINCTRL_PIN(135, "GPIO135/SMB11SDA"), + PINCTRL_PIN(136, "GPIO136/SD1DT0"), + PINCTRL_PIN(137, "GPIO137/SD1DT1"), + PINCTRL_PIN(138, "GPIO138/SD1DT2"), + PINCTRL_PIN(139, "GPIO139/SD1DT3"), + PINCTRL_PIN(140, "GPIO140/SD1CLK"), + PINCTRL_PIN(141, "GPIO141/SD1WP"), + PINCTRL_PIN(142, "GPIO142/SD1CMD"), + PINCTRL_PIN(143, "GPIO143/SD1CD/SD1PWR"), + PINCTRL_PIN(144, "GPIO144/PWM4"), + PINCTRL_PIN(145, "GPIO145/PWM5"), + PINCTRL_PIN(146, "GPIO146/PWM6"), + PINCTRL_PIN(147, "GPIO147/PWM7"), + PINCTRL_PIN(148, "GPIO148/MMCDT4"), + PINCTRL_PIN(149, "GPIO149/MMCDT5"), + PINCTRL_PIN(150, "GPIO150/MMCDT6"), + PINCTRL_PIN(151, "GPIO151/MMCDT7"), + PINCTRL_PIN(152, "GPIO152/MMCCLK"), + PINCTRL_PIN(153, "GPIO153/MMCWP"), + PINCTRL_PIN(154, "GPIO154/MMCCMD"), + PINCTRL_PIN(155, "GPIO155/nMMCCD/nMMCRST"), + PINCTRL_PIN(156, "GPIO156/MMCDT0"), + PINCTRL_PIN(157, "GPIO157/MMCDT1"), + PINCTRL_PIN(158, "GPIO158/MMCDT2"), + PINCTRL_PIN(159, "GPIO159/MMCDT3"), + + PINCTRL_PIN(160, "GPIO160/CLKOUT/RNGOSCOUT"), + PINCTRL_PIN(161, "GPIO161/nLFRAME/nESPICS"), + PINCTRL_PIN(162, "GPIO162/SERIRQ"), + PINCTRL_PIN(163, "GPIO163/LCLK/ESPICLK"), + PINCTRL_PIN(164, "GPIO164/LAD0/ESPI_IO0"/*dscnt6*/), + PINCTRL_PIN(165, "GPIO165/LAD1/ESPI_IO1"/*dscnt6*/), + PINCTRL_PIN(166, "GPIO166/LAD2/ESPI_IO2"/*dscnt6*/), + PINCTRL_PIN(167, "GPIO167/LAD3/ESPI_IO3"/*dscnt6*/), + PINCTRL_PIN(168, "GPIO168/nCLKRUN/nESPIALERT"), + PINCTRL_PIN(169, "GPIO169/nSCIPME"), + PINCTRL_PIN(170, "GPIO170/nSMI"), + PINCTRL_PIN(171, "GPIO171/SMB6SCL"), + PINCTRL_PIN(172, "GPIO172/SMB6SDA"), + PINCTRL_PIN(173, "GPIO173/SMB7SCL"), + PINCTRL_PIN(174, "GPIO174/SMB7SDA"), + PINCTRL_PIN(175, "GPIO175/PSPI1CK/FANIN19"), + PINCTRL_PIN(176, "GPIO176/PSPI1DO/FANIN18"), + PINCTRL_PIN(177, "GPIO177/PSPI1DI/FANIN17"), + PINCTRL_PIN(178, "GPIO178/R1TXD0"), + PINCTRL_PIN(179, "GPIO179/R1TXD1"), + PINCTRL_PIN(180, "GPIO180/R1TXEN"), + PINCTRL_PIN(181, "GPIO181/R1RXD0"), + PINCTRL_PIN(182, "GPIO182/R1RXD1"), + PINCTRL_PIN(183, "GPIO183/SPI3CK"), + PINCTRL_PIN(184, "GPO184/SPI3D0/STRAP9"), + PINCTRL_PIN(185, "GPO185/SPI3D1/STRAP10"), + PINCTRL_PIN(186, "GPIO186/nSPI3CS0"), + PINCTRL_PIN(187, "GPIO187/nSPI3CS1"), + PINCTRL_PIN(188, "GPIO188/SPI3D2/nSPI3CS2"), + PINCTRL_PIN(189, "GPIO189/SPI3D3/nSPI3CS3"), + PINCTRL_PIN(190, "GPIO190/nPRD_SMI"), + PINCTRL_PIN(191, "GPIO191"), + + PINCTRL_PIN(192, "GPIO192"), + PINCTRL_PIN(193, "GPIO193/R1CRSDV"), + PINCTRL_PIN(194, "GPIO194/SMB0BSCL"), + PINCTRL_PIN(195, "GPIO195/SMB0BSDA"), + PINCTRL_PIN(196, "GPIO196/SMB0CSCL"), + PINCTRL_PIN(197, "GPIO197/SMB0DEN"), + PINCTRL_PIN(198, "GPIO198/SMB0DSDA"), + PINCTRL_PIN(199, "GPIO199/SMB0DSCL"), + PINCTRL_PIN(200, "GPIO200/R2CK"), + PINCTRL_PIN(201, "GPIO201/R1CK"), + PINCTRL_PIN(202, "GPIO202/SMB0CSDA"), + PINCTRL_PIN(203, "GPIO203/FANIN16"), + PINCTRL_PIN(204, "GPIO204/DDC2SCL"), + PINCTRL_PIN(205, "GPIO205/DDC2SDA"), + PINCTRL_PIN(206, "GPIO206/HSYNC2"), + PINCTRL_PIN(207, "GPIO207/VSYNC2"), + PINCTRL_PIN(208, "GPIO208/RG2TXC/DVCK"), + PINCTRL_PIN(209, "GPIO209/RG2TXCTL/DDRV4"), + PINCTRL_PIN(210, "GPIO210/RG2RXD0/DDRV5"), + PINCTRL_PIN(211, "GPIO211/RG2RXD1/DDRV6"), + PINCTRL_PIN(212, "GPIO212/RG2RXD2/DDRV7"), + PINCTRL_PIN(213, "GPIO213/RG2RXD3/DDRV8"), + PINCTRL_PIN(214, "GPIO214/RG2RXC/DDRV9"), + PINCTRL_PIN(215, "GPIO215/RG2RXCTL/DDRV10"), + PINCTRL_PIN(216, "GPIO216/RG2MDC/DDRV11"), + PINCTRL_PIN(217, "GPIO217/RG2MDIO/DVHSYNC"), + PINCTRL_PIN(218, "GPIO218/nWDO1"), + PINCTRL_PIN(219, "GPIO219/nWDO2"), + PINCTRL_PIN(220, "GPIO220/SMB12SCL"), + PINCTRL_PIN(221, "GPIO221/SMB12SDA"), + PINCTRL_PIN(222, "GPIO222/SMB13SCL"), + PINCTRL_PIN(223, "GPIO223/SMB13SDA"), + + PINCTRL_PIN(224, "GPIO224/SPIXCK"), + PINCTRL_PIN(225, "GPO225/SPIXD0/STRAP12"), + PINCTRL_PIN(226, "GPO226/SPIXD1/STRAP13"), + PINCTRL_PIN(227, "GPIO227/nSPIXCS0"), + PINCTRL_PIN(228, "GPIO228/nSPIXCS1"), + PINCTRL_PIN(229, "GPIO229/SPIXD2/STRAP3"), + PINCTRL_PIN(230, "GPIO230/SPIXD3"), + PINCTRL_PIN(231, "GPIO231/nCLKREQ"), + PINCTRL_PIN(255, "GPI255/DACOSEL"), +}; + +static const char *gcr_regname(int reg) +{ + switch (reg) { + case NPCM7XX_GCR_MFSEL1: return "MFSEL1"; + case NPCM7XX_GCR_MFSEL2: return "MFSEL2"; + case NPCM7XX_GCR_MFSEL3: return "MFSEL3"; + case NPCM7XX_GCR_MFSEL4: return "MFSEL4"; + case NPCM7XX_GCR_I2CSEGSEL: return "I2CSEGSEL"; + case NPCM7XX_GCR_FLOCKR1: return "FLOCKR1"; + } + return "xx"; +} + +static void npcm_setmode(struct regmap *gcr_regmap, int reg, int bit, int mode, int pin) +{ + u32 val, mask = (1L << 1)-1; + + if (reg) { + pr_debug("Pin %d: setting reg:%x[%s.%d] = %d\n", pin, + reg, gcr_regname(reg), bit, mode); + + regmap_read(gcr_regmap, reg, &val); + val = val & ~(mask << bit); + regmap_write(gcr_regmap, reg, val | ((mode & mask) << bit)); + } +} + +/* Enable mode in pin group */ +static void npcm_setfunc(struct regmap *gcr_regmap, int pin, int n, int mode) +{ + const struct npcm_pincfg *cfg; + + while (n--) { + cfg = &pincfg[pin++]; + if (mode == fn_gpio || cfg->fn0 == mode || cfg->fn1 == mode + || cfg->fn2 == mode) { + npcm_setmode(gcr_regmap, cfg->reg0, cfg->bit0, + !!(cfg->fn0 == mode), pin-1); + npcm_setmode(gcr_regmap, cfg->reg1, cfg->bit1, + !!(cfg->fn1 == mode), pin-1); + npcm_setmode(gcr_regmap, cfg->reg2, cfg->bit2, + !!(cfg->fn2 == mode), pin-1); + } + } +} + +/* Get slew rate of pin (high/low) */ +static int npcm_get_slew_rate(struct NPCM_GPIO *bank, + struct regmap *gcr_regmap, unsigned int pin) +{ + u32 val; + int gpio = (pin % bank->gc.ngpio); + + if (pincfg[pin].flag & SLEW) + return gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_OSRC); + /* LPC Slew rate in SRCNT register */ + if (pincfg[pin].flag & SLEWLPC) + { + regmap_read(gcr_regmap, NPCM7XX_GCR_SRCNT, &val); + return !!(val & SRCNT_ESPI); + } + return -EINVAL; +} + +/* Get drive strength for a pin, if supported */ +static int npcm_get_drive_strength(struct pinctrl_dev *pctldev, + unsigned int pin) +{ + struct NPCM7xx_pinctrl *npcm = pinctrl_dev_get_drvdata(pctldev); + struct NPCM_GPIO *bank = &npcm->gpio_bank[pin/GPIO_PER_BANK]; + int gpio = (pin % bank->gc.ngpio); + u32 val, ds = 0; + int flg; + + flg = pincfg[pin].flag; + if (flg & DRIVE_STRENGTH_MASK) { + /* Get standard reading */ + val = gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_ODSC); + ds = val ? DSHI(flg) : DSLO(flg); + dev_dbg(bank->gc.parent, " pin %d strength %d = %d\n", pin, val, ds); + } + return ds; +} + +/* Set drive strength for a pin, if supported */ +static int npcm_set_drive_strength(struct NPCM7xx_pinctrl *npcm, + unsigned int pin, int nval) +{ + int v; + struct NPCM_GPIO *bank = &npcm->gpio_bank[pin/GPIO_PER_BANK]; + int gpio = (pin % bank->gc.ngpio); + + v = (pincfg[pin].flag & DRIVE_STRENGTH_MASK); + if (!nval || !v) + return 0; + if (DSLO(v) == nval) { + dev_dbg(bank->gc.parent, " setting pin %d to low strength " + "[%d]\n", pin, nval); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_ODSC); + return 1; + } else if (DSHI(v) == nval) { + dev_dbg(bank->gc.parent, " setting pin %d to high strength " + "[%d]\n", pin, nval); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_ODSC); + return 1; + } + return 0; +} + +/* ================= pinctrl_ops ========================= */ +static void npcm_pin_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, unsigned int offset) +{ + seq_printf(s, "pinctrl_ops.dbg: %d", offset); +} + +static int npcm_get_groups_count(struct pinctrl_dev *pctldev) +{ + struct NPCM7xx_pinctrl *npcm = pinctrl_dev_get_drvdata(pctldev); + + dev_dbg(npcm->dev, "group size: %d\n", ARRAY_SIZE(npcm_groups)); + return ARRAY_SIZE(npcm_groups); +} + +static const char *npcm_get_group_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + return npcm_groups[selector].name; +} + +static int npcm_get_group_pins(struct pinctrl_dev *pctldev, + unsigned int selector, + const unsigned int **pins, + unsigned int *npins) +{ + *npins = npcm_groups[selector].npins; + *pins = npcm_groups[selector].pins; + return 0; +} + +static int npcm_dt_node_to_map(struct pinctrl_dev *pctldev, + struct device_node *np_config, + struct pinctrl_map **map, + u32 *num_maps) +{ + pr_debug("dt_node_to_map: %s\n", np_config->name); + return pinconf_generic_dt_node_to_map(pctldev, np_config, + map, num_maps, + PIN_MAP_TYPE_INVALID); +} + +static void npcm_dt_free_map(struct pinctrl_dev *pctldev, + struct pinctrl_map *map, u32 num_maps) +{ + kfree(map); +} + +static struct pinctrl_ops npcm_pinctrl_ops = { + .get_groups_count = npcm_get_groups_count, + .get_group_name = npcm_get_group_name, + .get_group_pins = npcm_get_group_pins, + .pin_dbg_show = npcm_pin_dbg_show, + .dt_node_to_map = npcm_dt_node_to_map, + .dt_free_map = npcm_dt_free_map, +}; + +/* ================= pinmux_ops ========================= */ +static int npcm_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(npcm_funcs); +} + +static const char *npcm_get_function_name(struct pinctrl_dev *pctldev, + unsigned int function) +{ + return npcm_funcs[function].name; +} + +static int npcm_get_function_groups(struct pinctrl_dev *pctldev, + unsigned int function, + const char * const **groups, + unsigned int * const ngroups) +{ + *ngroups = npcm_funcs[function].ngroups; + *groups = npcm_funcs[function].groups; + return 0; +} + +static int npcm_pinmux_set_mux(struct pinctrl_dev *pctldev, + unsigned int function, + unsigned int group) +{ + struct NPCM7xx_pinctrl *npcm = pinctrl_dev_get_drvdata(pctldev); + + dev_dbg(npcm->dev, "set_mux: %d, %d[%s]\n", function, group, + npcm_groups[group].name); + npcm_setfunc(npcm->gcr_regmap, 0, ARRAY_SIZE(pincfg), group); + return 0; +} + +static int npcm_gpio_request_enable(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int offset) +{ + struct NPCM7xx_pinctrl *npcm = pinctrl_dev_get_drvdata(pctldev); + + if (!range) { + dev_err(npcm->dev, "invalid range\n"); + return -EINVAL; + } + if (!range->gc) { + dev_err(npcm->dev, "invalid gpiochip\n"); + return -EINVAL; + } + /*dev_dbg(npcm->gc.parent, "Enable GPIO %d\n", offset);*/ + npcm_setfunc(npcm->gcr_regmap, offset, 1, fn_gpio); + return 0; +} + +/* Release GPIO back to pinctrl mode */ +static void npcm_gpio_request_free(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int offset) +{ + struct NPCM7xx_pinctrl *npcm = pinctrl_dev_get_drvdata(pctldev); + int virq; + + virq = irq_find_mapping(npcm->domain, offset); + /*dev_dbg(npcm->gc.parent, "Free GPIO %d, irq=%d\n", offset, virq);*/ + if (virq) + irq_dispose_mapping(virq); +} + +/* Set GPIO direction */ +static int npcm_gpio_set_direction(struct pinctrl_dev *pctldev, + struct pinctrl_gpio_range *range, + unsigned int offset, bool input) +{ + struct NPCM7xx_pinctrl *npcm = pinctrl_dev_get_drvdata(pctldev); + struct NPCM_GPIO *bank = &npcm->gpio_bank[offset/GPIO_PER_BANK]; + int gpio = (offset % bank->gc.ngpio); + + dev_dbg(bank->gc.parent, "GPIO Set Direction: %d = %d\n", offset, + input); + if (input) { + gpio_bitop(bank, opSET, gpio, NPCM_GP_N_OEC); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_IEM); + } else { + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_IEM); + gpio_bitop(bank, opSET, gpio, NPCM_GP_N_OES); + } + return 0; +} + +static struct pinmux_ops npcm_pinmux_ops = { + .get_functions_count = npcm_get_functions_count, + .get_function_name = npcm_get_function_name, + .get_function_groups = npcm_get_function_groups, + + .set_mux = npcm_pinmux_set_mux, + + .gpio_request_enable = npcm_gpio_request_enable, + .gpio_disable_free = npcm_gpio_request_free, + .gpio_set_direction = npcm_gpio_set_direction, +}; + +/* ================= pinconf_ops ========================= */ + +/* Get configuration setting for a pin */ +static int npcm_config_get(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *config) +{ + enum pin_config_param param = pinconf_to_config_param(*config); + struct NPCM7xx_pinctrl *npcm = pinctrl_dev_get_drvdata(pctldev); + struct NPCM_GPIO *bank = &npcm->gpio_bank[pin/GPIO_PER_BANK]; + int gpio = (pin % bank->gc.ngpio); + u32 ie, oe, pu, pd; + int rc; + + rc = 0; + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + case PIN_CONFIG_BIAS_PULL_UP: + case PIN_CONFIG_BIAS_PULL_DOWN: + pu = gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_PU); + pd = gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_PD); + if (param == PIN_CONFIG_BIAS_DISABLE) + rc = (!pu && !pd); + else if (param == PIN_CONFIG_BIAS_PULL_UP) + rc = (pu && !pd); + else if (param == PIN_CONFIG_BIAS_PULL_DOWN) + rc = (!pu && pd); + break; + case PIN_CONFIG_OUTPUT: + case PIN_CONFIG_INPUT_ENABLE: + ie = gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_IEM); + oe = gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_OE); + if (param == PIN_CONFIG_INPUT_ENABLE) + rc = (ie && !oe); + else if (param == PIN_CONFIG_OUTPUT) + rc = (!ie && oe); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + rc = !gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_OTYP); + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + rc = gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_OTYP); + break; + case PIN_CONFIG_INPUT_DEBOUNCE: + rc = gpio_bitop(bank, opGETBIT, gpio, NPCM_GP_N_DBNC); + break; + case PIN_CONFIG_DRIVE_STRENGTH: + rc = npcm_get_drive_strength(pctldev, pin); + if (rc) + *config = pinconf_to_config_packed(param, rc * 1000); + break; + case PIN_CONFIG_SLEW_RATE: + rc = npcm_get_slew_rate(bank, npcm->gcr_regmap, pin); + if (rc >= 0) + *config = pinconf_to_config_packed(param, rc); + break; + default: + return -EINVAL; + } + if (!rc) + return -EINVAL; + return 0; +} + +/* Set configuration setting for a pin */ +static int npcm_config_set_one(struct NPCM7xx_pinctrl *npcm, unsigned int pin, + unsigned long config) +{ + enum pin_config_param param = pinconf_to_config_param(config); + u16 arg = pinconf_to_config_argument(config); + struct NPCM_GPIO *bank = &npcm->gpio_bank[pin/GPIO_PER_BANK]; + int gpio = (pin % bank->gc.ngpio); + int rc; + + dev_dbg(bank->gc.parent, "param=%d %d[GPIO]\n", param, pin); + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_PU); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_PD); + break; + case PIN_CONFIG_BIAS_PULL_DOWN: + /* arg: 0=GND, !0=enabled */ + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_PU); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_PD); + break; + case PIN_CONFIG_BIAS_PULL_UP: + /* arg: 0=VDD, !0=enabled */ + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_PD); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_PU); + break; + case PIN_CONFIG_INPUT_ENABLE: + /* arg: 0=disable, 1=enable */ + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + gpio_bitop(bank, opSET, gpio, NPCM_GP_N_OEC); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_IEM); + break; + case PIN_CONFIG_OUTPUT: + /* arg: 0=low, 1=high */ + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_IEM); + gpio_bitop(bank, opSET, gpio, arg ? NPCM_GP_N_DOS : + NPCM_GP_N_DOC); + gpio_bitop(bank, opSET, gpio, NPCM_GP_N_OES); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + gpio_bitop(bank, opCLRBIT, gpio, NPCM_GP_N_OTYP); + break; + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_OTYP); + break; + case PIN_CONFIG_INPUT_DEBOUNCE: + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + gpio_bitop(bank, opSETBIT, gpio, NPCM_GP_N_DBNC); + break; + case PIN_CONFIG_DRIVE_STRENGTH: + /* arg is mA */ + npcm_setfunc(npcm->gcr_regmap, pin, 1, fn_gpio); + rc = npcm_set_drive_strength(npcm, gpio, arg / 1000); + if (!rc) + return -EINVAL; + break; + default: + return -EINVAL; + } + return 0; +} + +/* Set multiple configuration settings for a pin */ +static int npcm_config_set(struct pinctrl_dev *pctldev, unsigned int pin, + unsigned long *configs, + unsigned int num_configs) +{ + struct NPCM7xx_pinctrl *npcm = pinctrl_dev_get_drvdata(pctldev); + int rc; + + while (num_configs--) { + rc = npcm_config_set_one(npcm, pin, *configs++); + if (rc) + return rc; + } + return 0; +} + +static void npcm_config_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, + unsigned int offset) +{ +} + +static void npcm_config_group_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, + unsigned int selector) +{ +} + +static void npcm_config_config_dbg_show(struct pinctrl_dev *pctldev, + struct seq_file *s, + unsigned long config) +{ +} + +static struct pinconf_ops npcm_pinconf_ops = { + .is_generic = true, + .pin_config_get = npcm_config_get, + .pin_config_set = npcm_config_set, + .pin_config_dbg_show = npcm_config_dbg_show, + .pin_config_group_dbg_show = npcm_config_group_dbg_show, + .pin_config_config_dbg_show = npcm_config_config_dbg_show, +}; + +/* ================= pinctrl_desc ========================= */ +static struct pinctrl_desc npcm_pinctrl_desc = { + .name = "npcm-pinctrl", + .pins = npcm_pins, + .npins = ARRAY_SIZE(npcm_pins), + .pctlops = &npcm_pinctrl_ops, + .pmxops = &npcm_pinmux_ops, + .confops = &npcm_pinconf_ops, +// .owner = THIS_MODULE, +}; + +static struct gpio_chip npcm_gc = { + .owner = THIS_MODULE, + .label = "npcmgpio", + .request = npcmgpio_gpio_request, + .free = npcmgpio_gpio_free, + .get_direction = npcmgpio_get_direction, + .direction_input = npcmgpio_direction_input, + .direction_output = npcmgpio_direction_output, + .get = npcmgpio_get_value, + .set = npcmgpio_set_value, + .dbg_show = npcmgpio_dbg_show, +}; + +static int npcm_gpio_register(struct NPCM7xx_pinctrl *pctrl) +{ + int ret; + struct resource res; + int id=0,irq; + struct device_node *np; + struct of_phandle_args pinspec; + + for_each_available_child_of_node(pctrl->dev->of_node, np) { + if (of_find_property(np, "gpio-controller", NULL)) { + + ret = of_address_to_resource(np, 0, &res); + if (ret < 0) { + dev_err(pctrl->dev, "Resource fail for GPIO " + "bank %u: %d\n", id, ret); + goto err; + } + + pctrl->gpio_bank[id].base = ioremap(res.start, + resource_size(&res)); + + irq = irq_of_parse_and_map(np, 0); + if (irq < 0) { + dev_err(pctrl->dev, "No IRQ for GPIO bank %u: " + "%d\n", id, irq); + ret = irq; + goto err; + } + + ret = of_parse_phandle_with_fixed_args(np, + "gpio-ranges", 3, + 0, &pinspec); + if (ret < 0) { + dev_err(pctrl->dev, "gpio-ranges fail for GPIO " + "bank %u: %d\n", id, ret); + goto err; + } + + if (ret) + break; + + spin_lock_init(&pctrl->gpio_bank[id].lock); + pctrl->gpio_bank[id].irq = irq; + pctrl->gpio_bank[id].gc = npcm_gc; + pctrl->gpio_bank[id].irq_chip = npcmgpio_irqchip; + pctrl->gpio_bank[id].gc.parent = pctrl->dev; + pctrl->gpio_bank[id].irqbase = id * GPIO_PER_BANK; + pctrl->gpio_bank[id].pinctrl_id = pinspec.args[0]; + pctrl->gpio_bank[id].gc.base = pinspec.args[1]; + pctrl->gpio_bank[id].gc.ngpio = pinspec.args[2]; + + ret = gpiochip_add_data(&pctrl->gpio_bank[id].gc, + &pctrl->gpio_bank[id]); + if (ret < 0) { + dev_err(pctrl->dev, + "Failed to add GPIO chip %u: %d\n", + id, ret); + goto err; + } + + ret = gpiochip_irqchip_add(&pctrl->gpio_bank[id].gc, + &pctrl->gpio_bank[id].irq_chip, + 0, handle_level_irq, + IRQ_TYPE_NONE); + if (ret < 0) { + dev_err(pctrl->dev, + "Failed to add IRQ chip %u: %d\n", + id, ret); + gpiochip_remove(&pctrl->gpio_bank[id].gc); + goto err; + } + + gpiochip_set_chained_irqchip(&pctrl->gpio_bank[id].gc, + &pctrl->gpio_bank[id].irq_chip, + irq, npcmgpio_irq_handler); + + id++; + } + else + break; + } + + pctrl->bank_num = id; + return 0; + +err: + for (; id > 0; id--) + gpiochip_remove(&pctrl->gpio_bank[id-1].gc); + + pctrl->bank_num = 0; + + return ret; +} + +static int npcm_pinctrl_probe(struct platform_device *pdev) +{ + struct NPCM7xx_pinctrl *pctrl; + int i,ret; + + pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL); + if (!pctrl) + return -ENOMEM; + + pctrl->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, pctrl); + + pctrl->gcr_regmap = + syscon_regmap_lookup_by_compatible("nuvoton,npcm750-gcr"); + if (IS_ERR(pctrl->gcr_regmap)) { + pr_err("%s: didn't find nuvoton,npcm750-gcr\n", __func__); + return IS_ERR(pctrl->gcr_regmap); + } + + ret = npcm_gpio_register(pctrl); + if (ret < 0) { + dev_err(pctrl->dev, "Failed to register gpio %u\n", ret); + return ret; + } + + pctrl->pctldev = devm_pinctrl_register(&pdev->dev, &npcm_pinctrl_desc, + pctrl); + if (IS_ERR(pctrl->pctldev)) { + dev_err(&pdev->dev, "Failed to register pinctrl device\n"); + ret = PTR_ERR(pctrl->pctldev); + i = pctrl->bank_num; + goto err_range; + } + + + for (i = 0 ; i < pctrl->bank_num ; i++) { + ret = gpiochip_add_pin_range(&pctrl->gpio_bank[i].gc, + dev_name(pctrl->dev), + pctrl->gpio_bank[i].pinctrl_id, + pctrl->gpio_bank[i].gc.base, + pctrl->gpio_bank[i].gc.ngpio); + + if (ret < 0) { + dev_err(pctrl->dev, "Failed to add GPIO range %u: %d\n", + i, ret); + gpiochip_remove(&pctrl->gpio_bank[i].gc); + goto err_range; + } + } + + pr_info("Nuvoton Pinctrl driver version %s [%s]\n", DRV_VERSION, + DRV_DATE); + return 0; + +err_range: + for (; i > 0; i--) + gpiochip_remove(&pctrl->gpio_bank[i-1].gc); + + return ret; +} + +static const struct of_device_id npcm_pinctrl_match[] = { + { .compatible = "nuvoton,npcm7xx-pinctrl" }, + { }, +}; +MODULE_DEVICE_TABLE(of, npcm_pinctrl_match); + +static struct platform_driver npcm_pinctrl_driver = { + .probe = npcm_pinctrl_probe, + .driver = { + .name = "npcm-pinctrl", + .of_match_table = npcm_pinctrl_match, + .suppress_bind_attrs = true, + }, +}; + +static int __init npcm_pinctrl_register(void) +{ + return platform_driver_register(&npcm_pinctrl_driver); +} +arch_initcall(npcm_pinctrl_register); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("jordan_hargrave@dell.com"); +MODULE_AUTHOR("tomer.maimon@nuvoton.com"); +MODULE_VERSION(DRV_VERSION); +MODULE_DESCRIPTION("Provide Pinctrl/GPIO methods for NPCM7XX");
diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index c07b4a8..b750a88 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig
@@ -2,6 +2,7 @@ source "drivers/soc/actions/Kconfig" source "drivers/soc/amlogic/Kconfig" +source "drivers/soc/aspeed/Kconfig" source "drivers/soc/atmel/Kconfig" source "drivers/soc/bcm/Kconfig" source "drivers/soc/fsl/Kconfig"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 40523577..13dd07b 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile
@@ -4,6 +4,7 @@ # obj-$(CONFIG_ARCH_ACTIONS) += actions/ +obj-$(CONFIG_ARCH_ASPEED) += aspeed/ obj-$(CONFIG_ARCH_AT91) += atmel/ obj-y += bcm/ obj-$(CONFIG_ARCH_DOVE) += dove/
diff --git a/drivers/soc/aspeed/Kconfig b/drivers/soc/aspeed/Kconfig new file mode 100644 index 0000000..704a4efe --- /dev/null +++ b/drivers/soc/aspeed/Kconfig
@@ -0,0 +1,11 @@ +menu "ASPEED SoC drivers" + +config ASPEED_BMC_MISC + bool "Miscellaneous ASPEED BMC interfaces" + depends on ARCH_ASPEED || COMPILE_TEST + default ARCH_ASPEED + help + Say yes to expose VGA and LPC scratch registers, and other + miscellaneous control interfaces specific to the ASPEED BMC SoCs + +endmenu
diff --git a/drivers/soc/aspeed/Makefile b/drivers/soc/aspeed/Makefile new file mode 100644 index 0000000..d1a80f9 --- /dev/null +++ b/drivers/soc/aspeed/Makefile
@@ -0,0 +1 @@ +obj-$(CONFIG_ASPEED_BMC_MISC) += aspeed-bmc-misc.o
diff --git a/drivers/soc/aspeed/aspeed-bmc-misc.c b/drivers/soc/aspeed/aspeed-bmc-misc.c new file mode 100644 index 0000000..314007b --- /dev/null +++ b/drivers/soc/aspeed/aspeed-bmc-misc.c
@@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corp. + +#include <linux/kobject.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> + +#define DEVICE_NAME "aspeed-bmc-misc" + +struct aspeed_bmc_ctrl { + const char *name; + u32 offset; + u32 mask; + u32 shift; + struct regmap *map; + struct kobj_attribute attr; +}; + +struct aspeed_bmc_misc { + struct device *dev; + struct regmap *map; + struct aspeed_bmc_ctrl *ctrls; + int nr_ctrls; +}; + +static int aspeed_bmc_misc_parse_dt_child(struct device_node *child, + struct aspeed_bmc_ctrl *ctrl) +{ + int rc; + + /* Example child: + * + * ilpc2ahb { + * offset = <0x80>; + * bit-mask = <0x1>; + * bit-shift = <6>; + * label = "foo"; + * } + */ + if (of_property_read_string(child, "label", &ctrl->name)) + ctrl->name = child->name; + + rc = of_property_read_u32(child, "offset", &ctrl->offset); + if (rc < 0) + return rc; + + rc = of_property_read_u32(child, "bit-mask", &ctrl->mask); + if (rc < 0) + return rc; + + rc = of_property_read_u32(child, "bit-shift", &ctrl->shift); + if (rc < 0) + return rc; + + ctrl->mask <<= ctrl->shift; + + return 0; +} + +static int aspeed_bmc_misc_parse_dt(struct aspeed_bmc_misc *bmc, + struct device_node *parent) +{ + struct aspeed_bmc_ctrl *ctrl; + struct device_node *child; + int rc; + + bmc->nr_ctrls = of_get_child_count(parent); + bmc->ctrls = devm_kcalloc(bmc->dev, bmc->nr_ctrls, sizeof(*bmc->ctrls), + GFP_KERNEL); + if (!bmc->ctrls) + return -ENOMEM; + + ctrl = bmc->ctrls; + for_each_child_of_node(parent, child) { + rc = aspeed_bmc_misc_parse_dt_child(child, ctrl++); + if (rc < 0) { + of_node_put(child); + return rc; + } + } + + return 0; +} + +static ssize_t aspeed_bmc_misc_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct aspeed_bmc_ctrl *ctrl; + unsigned int val; + int rc; + + ctrl = container_of(attr, struct aspeed_bmc_ctrl, attr); + rc = regmap_read(ctrl->map, ctrl->offset, &val); + if (rc) + return rc; + + val &= ctrl->mask; + val >>= ctrl->shift; + + return sprintf(buf, "%u\n", val); +} + +static ssize_t aspeed_bmc_misc_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct aspeed_bmc_ctrl *ctrl; + long val; + int rc; + + rc = kstrtol(buf, 0, &val); + if (rc) + return rc; + + ctrl = container_of(attr, struct aspeed_bmc_ctrl, attr); + val <<= ctrl->shift; + rc = regmap_update_bits(ctrl->map, ctrl->offset, ctrl->mask, val); + + return rc < 0 ? rc : count; +} + +static int aspeed_bmc_misc_add_sysfs_attr(struct aspeed_bmc_misc *bmc, + struct aspeed_bmc_ctrl *ctrl) +{ + ctrl->map = bmc->map; + + sysfs_attr_init(&ctrl->attr.attr); + ctrl->attr.attr.name = ctrl->name; + ctrl->attr.attr.mode = 0664; + ctrl->attr.show = aspeed_bmc_misc_show; + ctrl->attr.store = aspeed_bmc_misc_store; + + return sysfs_create_file(&bmc->dev->kobj, &ctrl->attr.attr); +} + +static int aspeed_bmc_misc_populate_sysfs(struct aspeed_bmc_misc *bmc) +{ + int rc; + int i; + + for (i = 0; i < bmc->nr_ctrls; i++) { + rc = aspeed_bmc_misc_add_sysfs_attr(bmc, &bmc->ctrls[i]); + if (rc < 0) + return rc; + } + + return 0; +} + +static int aspeed_bmc_misc_probe(struct platform_device *pdev) +{ + struct aspeed_bmc_misc *bmc; + int rc; + + bmc = devm_kzalloc(&pdev->dev, sizeof(*bmc), GFP_KERNEL); + if (!bmc) + return -ENOMEM; + + bmc->dev = &pdev->dev; + bmc->map = syscon_node_to_regmap(pdev->dev.parent->of_node); + if (IS_ERR(bmc->map)) + return PTR_ERR(bmc->map); + + rc = aspeed_bmc_misc_parse_dt(bmc, pdev->dev.of_node); + if (rc < 0) + return rc; + + return aspeed_bmc_misc_populate_sysfs(bmc); +} + +static const struct of_device_id aspeed_bmc_misc_match[] = { + { .compatible = "aspeed,bmc-misc" }, + { }, +}; + +static struct platform_driver aspeed_bmc_misc = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_bmc_misc_match, + }, + .probe = aspeed_bmc_misc_probe, +}; + +module_platform_driver(aspeed_bmc_misc); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
diff --git a/drivers/tty/serial/8250/8250_aspeed_vuart.c b/drivers/tty/serial/8250/8250_aspeed_vuart.c index 74a408d..435bec40 100644 --- a/drivers/tty/serial/8250/8250_aspeed_vuart.c +++ b/drivers/tty/serial/8250/8250_aspeed_vuart.c
@@ -10,6 +10,8 @@ #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/of_platform.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> #include <linux/clk.h> #include "8250.h" @@ -28,9 +30,18 @@ struct aspeed_vuart { void __iomem *regs; struct clk *clk; int line; + struct timer_list unthrottle_timer; + struct uart_8250_port *port; }; /* + * If we fill the tty flip buffers, we throttle the data ready interrupt + * to prevent dropped characters. This timeout defines how long we wait + * to (conditionally, depending on buffer state) unthrottle. + */ +static const int unthrottle_timeout = HZ/10; + +/* * The VUART is basically two UART 'front ends' connected by their FIFO * (no actual serial line in between). One is on the BMC side (management * controller) and one is on the host CPU side. @@ -179,6 +190,114 @@ static void aspeed_vuart_shutdown(struct uart_port *uart_port) serial8250_do_shutdown(uart_port); } +static void __aspeed_vuart_set_throttle(struct uart_8250_port *up, + bool throttle) +{ + unsigned char irqs = UART_IER_RLSI | UART_IER_RDI; + + up->ier &= ~irqs; + if (!throttle) + up->ier |= irqs; + serial_out(up, UART_IER, up->ier); +} +static void aspeed_vuart_set_throttle(struct uart_port *port, bool throttle) +{ + struct uart_8250_port *up = up_to_u8250p(port); + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + __aspeed_vuart_set_throttle(up, throttle); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void aspeed_vuart_throttle(struct uart_port *port) +{ + aspeed_vuart_set_throttle(port, true); +} + +static void aspeed_vuart_unthrottle(struct uart_port *port) +{ + aspeed_vuart_set_throttle(port, false); +} + +static void aspeed_vuart_unthrottle_exp(struct timer_list *timer) +{ + struct aspeed_vuart *vuart = from_timer(vuart, timer, unthrottle_timer); + struct uart_8250_port *up = vuart->port; + + if (!tty_buffer_space_avail(&up->port.state->port)) { + mod_timer(&vuart->unthrottle_timer, + jiffies + unthrottle_timeout); + return; + } + + aspeed_vuart_unthrottle(&up->port); +} + +/* + * Custom interrupt handler to manage finer-grained flow control. Although we + * have throttle/unthrottle callbacks, we've seen that the VUART device can + * deliver characters faster than the ldisc has a chance to check buffer space + * against the throttle threshold. This results in dropped characters before + * the throttle. + * + * We do this by checking for flip buffer space before RX. If we have no space, + * throttle now and schedule an unthrottle for later, once the ldisc has had + * a chance to drain the buffers. + */ +static int aspeed_vuart_handle_irq(struct uart_port *port) +{ + struct uart_8250_port *up = up_to_u8250p(port); + unsigned int iir, lsr; + unsigned long flags; + int space, count; + + iir = serial_port_in(port, UART_IIR); + + if (iir & UART_IIR_NO_INT) + return 0; + + spin_lock_irqsave(&port->lock, flags); + + lsr = serial_port_in(port, UART_LSR); + + if (lsr & (UART_LSR_DR | UART_LSR_BI)) { + space = tty_buffer_space_avail(&port->state->port); + + if (!space) { + /* throttle and schedule an unthrottle later */ + struct aspeed_vuart *vuart = port->private_data; + __aspeed_vuart_set_throttle(up, true); + + if (!timer_pending(&vuart->unthrottle_timer)) { + vuart->port = up; + mod_timer(&vuart->unthrottle_timer, + jiffies + unthrottle_timeout); + } + + } else { + count = min(space, 256); + + do { + serial8250_read_char(up, lsr); + lsr = serial_in(up, UART_LSR); + if (--count == 0) + break; + } while (lsr & (UART_LSR_DR | UART_LSR_BI)); + + tty_flip_buffer_push(&port->state->port); + } + } + + serial8250_modem_status(up); + if (lsr & UART_LSR_THRE) + serial8250_tx_chars(up); + + spin_unlock_irqrestore(&port->lock, flags); + + return 1; +} + static int aspeed_vuart_probe(struct platform_device *pdev) { struct uart_8250_port port; @@ -195,6 +314,7 @@ static int aspeed_vuart_probe(struct platform_device *pdev) return -ENOMEM; vuart->dev = &pdev->dev; + timer_setup(&vuart->unthrottle_timer, aspeed_vuart_unthrottle_exp, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); vuart->regs = devm_ioremap_resource(&pdev->dev, res); @@ -208,6 +328,9 @@ static int aspeed_vuart_probe(struct platform_device *pdev) port.port.mapsize = resource_size(res); port.port.startup = aspeed_vuart_startup; port.port.shutdown = aspeed_vuart_shutdown; + port.port.throttle = aspeed_vuart_throttle; + port.port.unthrottle = aspeed_vuart_unthrottle; + port.port.status = UPSTAT_SYNC_FIFO; port.port.dev = &pdev->dev; rc = sysfs_create_group(&vuart->dev->kobj, &aspeed_vuart_attr_group); @@ -253,6 +376,7 @@ static int aspeed_vuart_probe(struct platform_device *pdev) port.port.irq = irq_of_parse_and_map(np, 0); port.port.irqflags = IRQF_SHARED; + port.port.handle_irq = aspeed_vuart_handle_irq; port.port.iotype = UPIO_MEM; port.port.type = PORT_16550A; port.port.uartclk = clk; @@ -292,6 +416,7 @@ static int aspeed_vuart_remove(struct platform_device *pdev) { struct aspeed_vuart *vuart = platform_get_drvdata(pdev); + del_timer_sync(&vuart->unthrottle_timer); aspeed_vuart_set_enabled(vuart, false); serial8250_unregister_port(vuart->line); sysfs_remove_group(&vuart->dev->kobj, &aspeed_vuart_attr_group);
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 8d98116..fdda268 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c
@@ -1679,7 +1679,7 @@ static void serial8250_enable_ms(struct uart_port *port) serial8250_rpm_put(up); } -static void serial8250_read_char(struct uart_8250_port *up, unsigned char lsr) +void serial8250_read_char(struct uart_8250_port *up, unsigned char lsr) { struct uart_port *port = &up->port; unsigned char ch; @@ -1739,6 +1739,7 @@ static void serial8250_read_char(struct uart_8250_port *up, unsigned char lsr) uart_insert_char(port, lsr, UART_LSR_OE, ch, flag); } +EXPORT_SYMBOL_GPL(serial8250_read_char); /* * serial8250_rx_chars: processes according to the passed in LSR
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 0466f9f..c47158c 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c
@@ -674,8 +674,8 @@ static void uart_send_xchar(struct tty_struct *tty, char ch) static void uart_throttle(struct tty_struct *tty) { struct uart_state *state = tty->driver_data; + upstat_t mask = UPSTAT_SYNC_FIFO; struct uart_port *port; - upstat_t mask = 0; port = uart_port_ref(state); if (!port) @@ -703,8 +703,8 @@ static void uart_throttle(struct tty_struct *tty) static void uart_unthrottle(struct tty_struct *tty) { struct uart_state *state = tty->driver_data; + upstat_t mask = UPSTAT_SYNC_FIFO; struct uart_port *port; - upstat_t mask = 0; port = uart_port_ref(state); if (!port)
diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 0875d38..b838cae 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig
@@ -438,6 +438,8 @@ dynamically linked module called "udc-xilinx" and force all gadget drivers to also be dynamically linked. +source "drivers/usb/gadget/udc/aspeed-vhub/Kconfig" + # # LAST -- dummy/emulated controller #
diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index ce865b1..897f648 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile
@@ -39,4 +39,5 @@ obj-$(CONFIG_USB_GR_UDC) += gr_udc.o obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o +obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub/ obj-$(CONFIG_USB_BDC_UDC) += bdc/
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/Kconfig b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig new file mode 100644 index 0000000..f0cdf89 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/Kconfig
@@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ +config USB_ASPEED_VHUB + tristate "Aspeed vHub UDC driver" + depends on ARCH_ASPEED || COMPILE_TEST + help + USB peripheral controller for the Aspeed AST2500 family + SoCs supporting the "vHub" functionality and USB2.0
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/Makefile b/drivers/usb/gadget/udc/aspeed-vhub/Makefile new file mode 100644 index 0000000..9f3add6 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/Makefile
@@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0+ +obj-$(CONFIG_USB_ASPEED_VHUB) += aspeed-vhub.o +aspeed-vhub-y := core.o ep0.o epn.o dev.o hub.o +
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c new file mode 100644 index 0000000..db3628be --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c
@@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * core.c - Top level support + * + * Copyright 2017 IBM Corporation + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/prefetch.h> +#include <linux/clk.h> +#include <linux/usb/gadget.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/dma-mapping.h> + +#include "vhub.h" + +void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req, + int status) +{ + bool internal = req->internal; + + EPVDBG(ep, "completing request @%p, status %d\n", req, status); + + list_del_init(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + + if (req->req.dma) { + if (!WARN_ON(!ep->dev)) + usb_gadget_unmap_request(&ep->dev->gadget, + &req->req, ep->epn.is_in); + req->req.dma = 0; + } + + /* + * If this isn't an internal EP0 request, call the core + * to call the gadget completion. + */ + if (!internal) { + spin_unlock(&ep->vhub->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&ep->vhub->lock); + } +} + +void ast_vhub_nuke(struct ast_vhub_ep *ep, int status) +{ + struct ast_vhub_req *req; + + EPDBG(ep, "Nuking\n"); + + /* Beware, lock will be dropped & req-acquired by done() */ + while (!list_empty(&ep->queue)) { + req = list_first_entry(&ep->queue, struct ast_vhub_req, queue); + ast_vhub_done(ep, req, status); + } +} + +struct usb_request *ast_vhub_alloc_request(struct usb_ep *u_ep, + gfp_t gfp_flags) +{ + struct ast_vhub_req *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + return &req->req; +} + +void ast_vhub_free_request(struct usb_ep *u_ep, struct usb_request *u_req) +{ + struct ast_vhub_req *req = to_ast_req(u_req); + + kfree(req); +} + +static irqreturn_t ast_vhub_irq(int irq, void *data) +{ + struct ast_vhub *vhub = data; + irqreturn_t iret = IRQ_NONE; + u32 istat; + + /* Stale interrupt while tearing down */ + if (!vhub->ep0_bufs) + return IRQ_NONE; + + spin_lock(&vhub->lock); + + /* Read and ACK interrupts */ + istat = readl(vhub->regs + AST_VHUB_ISR); + if (!istat) + goto bail; + writel(istat, vhub->regs + AST_VHUB_ISR); + iret = IRQ_HANDLED; + + UDCVDBG(vhub, "irq status=%08x, ep_acks=%08x ep_nacks=%08x\n", + istat, + readl(vhub->regs + AST_VHUB_EP_ACK_ISR), + readl(vhub->regs + AST_VHUB_EP_NACK_ISR)); + + /* Handle generic EPs first */ + if (istat & VHUB_IRQ_EP_POOL_ACK_STALL) { + u32 i, ep_acks = readl(vhub->regs + AST_VHUB_EP_ACK_ISR); + writel(ep_acks, vhub->regs + AST_VHUB_EP_ACK_ISR); + + for (i = 0; ep_acks && i < AST_VHUB_NUM_GEN_EPs; i++) { + u32 mask = VHUB_EP_IRQ(i); + if (ep_acks & mask) { + ast_vhub_epn_ack_irq(&vhub->epns[i]); + ep_acks &= ~mask; + } + } + } + + /* Handle device interrupts */ + if (istat & (VHUB_IRQ_DEVICE1 | + VHUB_IRQ_DEVICE2 | + VHUB_IRQ_DEVICE3 | + VHUB_IRQ_DEVICE4 | + VHUB_IRQ_DEVICE5)) { + if (istat & VHUB_IRQ_DEVICE1) + ast_vhub_dev_irq(&vhub->ports[0].dev); + if (istat & VHUB_IRQ_DEVICE2) + ast_vhub_dev_irq(&vhub->ports[1].dev); + if (istat & VHUB_IRQ_DEVICE3) + ast_vhub_dev_irq(&vhub->ports[2].dev); + if (istat & VHUB_IRQ_DEVICE4) + ast_vhub_dev_irq(&vhub->ports[3].dev); + if (istat & VHUB_IRQ_DEVICE5) + ast_vhub_dev_irq(&vhub->ports[4].dev); + } + + /* Handle top-level vHub EP0 interrupts */ + if (istat & (VHUB_IRQ_HUB_EP0_OUT_ACK_STALL | + VHUB_IRQ_HUB_EP0_IN_ACK_STALL | + VHUB_IRQ_HUB_EP0_SETUP)) { + if (istat & VHUB_IRQ_HUB_EP0_IN_ACK_STALL) + ast_vhub_ep0_handle_ack(&vhub->ep0, true); + if (istat & VHUB_IRQ_HUB_EP0_OUT_ACK_STALL) + ast_vhub_ep0_handle_ack(&vhub->ep0, false); + if (istat & VHUB_IRQ_HUB_EP0_SETUP) + ast_vhub_ep0_handle_setup(&vhub->ep0); + } + + /* Various top level bus events */ + if (istat & (VHUB_IRQ_BUS_RESUME | + VHUB_IRQ_BUS_SUSPEND | + VHUB_IRQ_BUS_RESET)) { + if (istat & VHUB_IRQ_BUS_RESUME) + ast_vhub_hub_resume(vhub); + if (istat & VHUB_IRQ_BUS_SUSPEND) + ast_vhub_hub_suspend(vhub); + if (istat & VHUB_IRQ_BUS_RESET) + ast_vhub_hub_reset(vhub); + } + + bail: + spin_unlock(&vhub->lock); + return iret; +} + +void ast_vhub_init_hw(struct ast_vhub *vhub) +{ + u32 ctrl; + + UDCDBG(vhub,"(Re)Starting HW ...\n"); + + /* Enable PHY */ + ctrl = VHUB_CTRL_PHY_CLK | + VHUB_CTRL_PHY_RESET_DIS; + + /* + * We do *NOT* set the VHUB_CTRL_CLK_STOP_SUSPEND bit + * to stop the logic clock during suspend because + * it causes the registers to become inaccessible and + * we haven't yet figured out a good wayt to bring the + * controller back into life to issue a wakeup. + */ + + /* + * Set some ISO & split control bits according to Aspeed + * recommendation + * + * VHUB_CTRL_ISO_RSP_CTRL: When set tells the HW to respond + * with 0 bytes data packet to ISO IN endpoints when no data + * is available. + * + * VHUB_CTRL_SPLIT_IN: This makes a SOF complete a split IN + * transaction. + */ + ctrl |= VHUB_CTRL_ISO_RSP_CTRL | VHUB_CTRL_SPLIT_IN; + writel(ctrl, vhub->regs + AST_VHUB_CTRL); + udelay(1); + + /* Set descriptor ring size */ + if (AST_VHUB_DESCS_COUNT == 256) { + ctrl |= VHUB_CTRL_LONG_DESC; + writel(ctrl, vhub->regs + AST_VHUB_CTRL); + } else { + BUILD_BUG_ON(AST_VHUB_DESCS_COUNT != 32); + } + + /* Reset all devices */ + writel(VHUB_SW_RESET_ALL, vhub->regs + AST_VHUB_SW_RESET); + udelay(1); + writel(0, vhub->regs + AST_VHUB_SW_RESET); + + /* Disable and cleanup EP ACK/NACK interrupts */ + writel(0, vhub->regs + AST_VHUB_EP_ACK_IER); + writel(0, vhub->regs + AST_VHUB_EP_NACK_IER); + writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_ACK_ISR); + writel(VHUB_EP_IRQ_ALL, vhub->regs + AST_VHUB_EP_NACK_ISR); + + /* Default settings for EP0, enable HW hub EP1 */ + writel(0, vhub->regs + AST_VHUB_EP0_CTRL); + writel(VHUB_EP1_CTRL_RESET_TOGGLE | + VHUB_EP1_CTRL_ENABLE, + vhub->regs + AST_VHUB_EP1_CTRL); + writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG); + + /* Configure EP0 DMA buffer */ + writel(vhub->ep0.buf_dma, vhub->regs + AST_VHUB_EP0_DATA); + + /* Clear address */ + writel(0, vhub->regs + AST_VHUB_CONF); + + /* Pullup hub (activate on host) */ + if (vhub->force_usb1) + ctrl |= VHUB_CTRL_FULL_SPEED_ONLY; + + ctrl |= VHUB_CTRL_UPSTREAM_CONNECT; + writel(ctrl, vhub->regs + AST_VHUB_CTRL); + + /* Enable some interrupts */ + writel(VHUB_IRQ_HUB_EP0_IN_ACK_STALL | + VHUB_IRQ_HUB_EP0_OUT_ACK_STALL | + VHUB_IRQ_HUB_EP0_SETUP | + VHUB_IRQ_EP_POOL_ACK_STALL | + VHUB_IRQ_BUS_RESUME | + VHUB_IRQ_BUS_SUSPEND | + VHUB_IRQ_BUS_RESET, + vhub->regs + AST_VHUB_IER); +} + +static int ast_vhub_remove(struct platform_device *pdev) +{ + struct ast_vhub *vhub = platform_get_drvdata(pdev); + unsigned long flags; + int i; + + if (!vhub || !vhub->regs) + return 0; + + /* Remove devices */ + for (i = 0; i < AST_VHUB_NUM_PORTS; i++) + ast_vhub_del_dev(&vhub->ports[i].dev); + + spin_lock_irqsave(&vhub->lock, flags); + + /* Mask & ack all interrupts */ + writel(0, vhub->regs + AST_VHUB_IER); + writel(VHUB_IRQ_ACK_ALL, vhub->regs + AST_VHUB_ISR); + + /* Pull device, leave PHY enabled */ + writel(VHUB_CTRL_PHY_CLK | + VHUB_CTRL_PHY_RESET_DIS, + vhub->regs + AST_VHUB_CTRL); + + if (vhub->clk) + clk_disable_unprepare(vhub->clk); + + spin_unlock_irqrestore(&vhub->lock, flags); + + if (vhub->ep0_bufs) + dma_free_coherent(&pdev->dev, + AST_VHUB_EP0_MAX_PACKET * + (AST_VHUB_NUM_PORTS + 1), + vhub->ep0_bufs, + vhub->ep0_bufs_dma); + vhub->ep0_bufs = NULL; + + return 0; +} + +static int ast_vhub_probe(struct platform_device *pdev) +{ + enum usb_device_speed max_speed; + struct ast_vhub *vhub; + struct resource *res; + int i, rc = 0; + + vhub = devm_kzalloc(&pdev->dev, sizeof(*vhub), GFP_KERNEL); + if (!vhub) + return -ENOMEM; + + spin_lock_init(&vhub->lock); + vhub->pdev = pdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + vhub->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(vhub->regs)) { + dev_err(&pdev->dev, "Failed to map resources\n"); + return PTR_ERR(vhub->regs); + } + UDCDBG(vhub, "vHub@%pR mapped @%p\n", res, vhub->regs); + + platform_set_drvdata(pdev, vhub); + + vhub->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(vhub->clk)) { + rc = PTR_ERR(vhub->clk); + goto err; + } + rc = clk_prepare_enable(vhub->clk); + if (rc) { + dev_err(&pdev->dev, "Error couldn't enable clock (%d)\n", rc); + goto err; + } + + /* Check if we need to limit the HW to USB1 */ + max_speed = usb_get_maximum_speed(&pdev->dev); + if (max_speed != USB_SPEED_UNKNOWN && max_speed < USB_SPEED_HIGH) + vhub->force_usb1 = true; + + /* Mask & ack all interrupts before installing the handler */ + writel(0, vhub->regs + AST_VHUB_IER); + writel(VHUB_IRQ_ACK_ALL, vhub->regs + AST_VHUB_ISR); + + /* Find interrupt and install handler */ + vhub->irq = platform_get_irq(pdev, 0); + if (vhub->irq < 0) { + dev_err(&pdev->dev, "Failed to get interrupt\n"); + rc = vhub->irq; + goto err; + } + rc = devm_request_irq(&pdev->dev, vhub->irq, ast_vhub_irq, 0, + KBUILD_MODNAME, vhub); + if (rc) { + dev_err(&pdev->dev, "Failed to request interrupt\n"); + goto err; + } + + /* + * Allocate DMA buffers for all EP0s in one chunk, + * one per port and one for the vHub itself + */ + vhub->ep0_bufs = dma_alloc_coherent(&pdev->dev, + AST_VHUB_EP0_MAX_PACKET * + (AST_VHUB_NUM_PORTS + 1), + &vhub->ep0_bufs_dma, GFP_KERNEL); + if (!vhub->ep0_bufs) { + dev_err(&pdev->dev, "Failed to allocate EP0 DMA buffers\n"); + rc = -ENOMEM; + goto err; + } + UDCVDBG(vhub, "EP0 DMA buffers @%p (DMA 0x%08x)\n", + vhub->ep0_bufs, (u32)vhub->ep0_bufs_dma); + + /* Init vHub EP0 */ + ast_vhub_init_ep0(vhub, &vhub->ep0, NULL); + + /* Init devices */ + for (i = 0; i < AST_VHUB_NUM_PORTS && rc == 0; i++) + rc = ast_vhub_init_dev(vhub, i); + if (rc) + goto err; + + /* Init hub emulation */ + ast_vhub_init_hub(vhub); + + /* Initialize HW */ + ast_vhub_init_hw(vhub); + + dev_info(&pdev->dev, "Initialized virtual hub in USB%d mode\n", + vhub->force_usb1 ? 1 : 2); + + return 0; + err: + ast_vhub_remove(pdev); + return rc; +} + +static const struct of_device_id ast_vhub_dt_ids[] = { + { + .compatible = "aspeed,ast2400-usb-vhub", + }, + { + .compatible = "aspeed,ast2500-usb-vhub", + }, + { } +}; +MODULE_DEVICE_TABLE(of, ast_vhub_dt_ids); + +static struct platform_driver ast_vhub_driver = { + .probe = ast_vhub_probe, + .remove = ast_vhub_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ast_vhub_dt_ids, + }, +}; +module_platform_driver(ast_vhub_driver); + +MODULE_DESCRIPTION("Aspeed vHub udc driver"); +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c new file mode 100644 index 0000000..f0233912 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c
@@ -0,0 +1,589 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * dev.c - Individual device/gadget management (ie, a port = a gadget) + * + * Copyright 2017 IBM Corporation + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/prefetch.h> +#include <linux/clk.h> +#include <linux/usb/gadget.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/dma-mapping.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "vhub.h" + +void ast_vhub_dev_irq(struct ast_vhub_dev *d) +{ + u32 istat = readl(d->regs + AST_VHUB_DEV_ISR); + + writel(istat, d->regs + AST_VHUB_DEV_ISR); + + if (istat & VHUV_DEV_IRQ_EP0_IN_ACK_STALL) + ast_vhub_ep0_handle_ack(&d->ep0, true); + if (istat & VHUV_DEV_IRQ_EP0_OUT_ACK_STALL) + ast_vhub_ep0_handle_ack(&d->ep0, false); + if (istat & VHUV_DEV_IRQ_EP0_SETUP) + ast_vhub_ep0_handle_setup(&d->ep0); +} + +static void ast_vhub_dev_enable(struct ast_vhub_dev *d) +{ + u32 reg, hmsk; + + if (d->enabled) + return; + + /* Enable device and its EP0 interrupts */ + reg = VHUB_DEV_EN_ENABLE_PORT | + VHUB_DEV_EN_EP0_IN_ACK_IRQEN | + VHUB_DEV_EN_EP0_OUT_ACK_IRQEN | + VHUB_DEV_EN_EP0_SETUP_IRQEN; + if (d->gadget.speed == USB_SPEED_HIGH) + reg |= VHUB_DEV_EN_SPEED_SEL_HIGH; + writel(reg, d->regs + AST_VHUB_DEV_EN_CTRL); + + /* Enable device interrupt in the hub as well */ + hmsk = VHUB_IRQ_DEVICE1 << d->index; + reg = readl(d->vhub->regs + AST_VHUB_IER); + reg |= hmsk; + writel(reg, d->vhub->regs + AST_VHUB_IER); + + /* Set EP0 DMA buffer address */ + writel(d->ep0.buf_dma, d->regs + AST_VHUB_DEV_EP0_DATA); + + d->enabled = true; +} + +static void ast_vhub_dev_disable(struct ast_vhub_dev *d) +{ + u32 reg, hmsk; + + if (!d->enabled) + return; + + /* Disable device interrupt in the hub */ + hmsk = VHUB_IRQ_DEVICE1 << d->index; + reg = readl(d->vhub->regs + AST_VHUB_IER); + reg &= ~hmsk; + writel(reg, d->vhub->regs + AST_VHUB_IER); + + /* Then disable device */ + writel(0, d->regs + AST_VHUB_DEV_EN_CTRL); + d->gadget.speed = USB_SPEED_UNKNOWN; + d->enabled = false; + d->suspended = false; +} + +static int ast_vhub_dev_feature(struct ast_vhub_dev *d, + u16 wIndex, u16 wValue, + bool is_set) +{ + DDBG(d, "%s_FEATURE(dev val=%02x)\n", + is_set ? "SET" : "CLEAR", wValue); + + if (wValue != USB_DEVICE_REMOTE_WAKEUP) + return std_req_driver; + + d->wakeup_en = is_set; + + return std_req_complete; +} + +static int ast_vhub_ep_feature(struct ast_vhub_dev *d, + u16 wIndex, u16 wValue, bool is_set) +{ + struct ast_vhub_ep *ep; + int ep_num; + + ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK; + DDBG(d, "%s_FEATURE(ep%d val=%02x)\n", + is_set ? "SET" : "CLEAR", ep_num, wValue); + if (ep_num == 0) + return std_req_complete; + if (ep_num >= AST_VHUB_NUM_GEN_EPs || !d->epns[ep_num - 1]) + return std_req_stall; + if (wValue != USB_ENDPOINT_HALT) + return std_req_driver; + + ep = d->epns[ep_num - 1]; + if (WARN_ON(!ep)) + return std_req_stall; + + if (!ep->epn.enabled || !ep->ep.desc || ep->epn.is_iso || + ep->epn.is_in != !!(wIndex & USB_DIR_IN)) + return std_req_stall; + + DDBG(d, "%s stall on EP %d\n", + is_set ? "setting" : "clearing", ep_num); + ep->epn.stalled = is_set; + ast_vhub_update_epn_stall(ep); + + return std_req_complete; +} + +static int ast_vhub_dev_status(struct ast_vhub_dev *d, + u16 wIndex, u16 wValue) +{ + u8 st0; + + DDBG(d, "GET_STATUS(dev)\n"); + + st0 = d->gadget.is_selfpowered << USB_DEVICE_SELF_POWERED; + if (d->wakeup_en) + st0 |= 1 << USB_DEVICE_REMOTE_WAKEUP; + + return ast_vhub_simple_reply(&d->ep0, st0, 0); +} + +static int ast_vhub_ep_status(struct ast_vhub_dev *d, + u16 wIndex, u16 wValue) +{ + int ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK; + struct ast_vhub_ep *ep; + u8 st0 = 0; + + DDBG(d, "GET_STATUS(ep%d)\n", ep_num); + + if (ep_num >= AST_VHUB_NUM_GEN_EPs) + return std_req_stall; + if (ep_num != 0) { + ep = d->epns[ep_num - 1]; + if (!ep) + return std_req_stall; + if (!ep->epn.enabled || !ep->ep.desc || ep->epn.is_iso || + ep->epn.is_in != !!(wIndex & USB_DIR_IN)) + return std_req_stall; + if (ep->epn.stalled) + st0 |= 1 << USB_ENDPOINT_HALT; + } + + return ast_vhub_simple_reply(&d->ep0, st0, 0); +} + +static void ast_vhub_dev_set_address(struct ast_vhub_dev *d, u8 addr) +{ + u32 reg; + + DDBG(d, "SET_ADDRESS: Got address %x\n", addr); + + reg = readl(d->regs + AST_VHUB_DEV_EN_CTRL); + reg &= ~VHUB_DEV_EN_ADDR_MASK; + reg |= VHUB_DEV_EN_SET_ADDR(addr); + writel(reg, d->regs + AST_VHUB_DEV_EN_CTRL); +} + +int ast_vhub_std_dev_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq) +{ + struct ast_vhub_dev *d = ep->dev; + u16 wValue, wIndex; + + /* No driver, we shouldn't be enabled ... */ + if (!d->driver || !d->enabled || d->suspended) { + EPDBG(ep, + "Device is wrong state driver=%p enabled=%d" + " suspended=%d\n", + d->driver, d->enabled, d->suspended); + return std_req_stall; + } + + /* First packet, grab speed */ + if (d->gadget.speed == USB_SPEED_UNKNOWN) { + d->gadget.speed = ep->vhub->speed; + if (d->gadget.speed > d->driver->max_speed) + d->gadget.speed = d->driver->max_speed; + DDBG(d, "fist packet, captured speed %d\n", + d->gadget.speed); + } + + wValue = le16_to_cpu(crq->wValue); + wIndex = le16_to_cpu(crq->wIndex); + + switch ((crq->bRequestType << 8) | crq->bRequest) { + /* SET_ADDRESS */ + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + ast_vhub_dev_set_address(d, wValue); + return std_req_complete; + + /* GET_STATUS */ + case DeviceRequest | USB_REQ_GET_STATUS: + return ast_vhub_dev_status(d, wIndex, wValue); + case InterfaceRequest | USB_REQ_GET_STATUS: + return ast_vhub_simple_reply(ep, 0, 0); + case EndpointRequest | USB_REQ_GET_STATUS: + return ast_vhub_ep_status(d, wIndex, wValue); + + /* SET/CLEAR_FEATURE */ + case DeviceOutRequest | USB_REQ_SET_FEATURE: + return ast_vhub_dev_feature(d, wIndex, wValue, true); + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + return ast_vhub_dev_feature(d, wIndex, wValue, false); + case EndpointOutRequest | USB_REQ_SET_FEATURE: + return ast_vhub_ep_feature(d, wIndex, wValue, true); + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + return ast_vhub_ep_feature(d, wIndex, wValue, false); + } + return std_req_driver; +} + +static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + unsigned long flags; + int rc = -EINVAL; + + spin_lock_irqsave(&d->vhub->lock, flags); + if (!d->wakeup_en) + goto err; + + DDBG(d, "Device initiated wakeup\n"); + + /* Wakeup the host */ + ast_vhub_hub_wake_all(d->vhub); + rc = 0; + err: + spin_unlock_irqrestore(&d->vhub->lock, flags); + return rc; +} + +static int ast_vhub_udc_get_frame(struct usb_gadget* gadget) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + + return (readl(d->vhub->regs + AST_VHUB_USBSTS) >> 16) & 0x7ff; +} + +static void ast_vhub_dev_nuke(struct ast_vhub_dev *d) +{ + unsigned int i; + + for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) { + if (!d->epns[i]) + continue; + ast_vhub_nuke(d->epns[i], -ESHUTDOWN); + } +} + +static int ast_vhub_udc_pullup(struct usb_gadget* gadget, int on) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + unsigned long flags; + + spin_lock_irqsave(&d->vhub->lock, flags); + + DDBG(d, "pullup(%d)\n", on); + + /* Mark disconnected in the hub */ + ast_vhub_device_connect(d->vhub, d->index, on); + + /* + * If enabled, nuke all requests if any (there shouldn't be) + * and disable the port. This will clear the address too. + */ + if (d->enabled) { + ast_vhub_dev_nuke(d); + ast_vhub_dev_disable(d); + } + + spin_unlock_irqrestore(&d->vhub->lock, flags); + + return 0; +} + +static int ast_vhub_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + unsigned long flags; + + spin_lock_irqsave(&d->vhub->lock, flags); + + DDBG(d, "start\n"); + + /* We don't do much more until the hub enables us */ + d->driver = driver; + d->gadget.is_selfpowered = 1; + + spin_unlock_irqrestore(&d->vhub->lock, flags); + + return 0; +} + +static struct usb_ep *ast_vhub_udc_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *ss) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + struct ast_vhub_ep *ep; + struct usb_ep *u_ep; + unsigned int max, addr, i; + + DDBG(d, "Match EP type %d\n", usb_endpoint_type(desc)); + + /* + * First we need to look for an existing unclaimed EP as another + * configuration may have already associated a bunch of EPs with + * this gadget. This duplicates the code in usb_ep_autoconfig_ss() + * unfortunately. + */ + list_for_each_entry(u_ep, &gadget->ep_list, ep_list) { + if (usb_gadget_ep_match_desc(gadget, u_ep, desc, ss)) { + DDBG(d, " -> using existing EP%d\n", + to_ast_ep(u_ep)->d_idx); + return u_ep; + } + } + + /* + * We didn't find one, we need to grab one from the pool. + * + * First let's do some sanity checking + */ + switch(usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + /* Only EP0 can be a control endpoint */ + return NULL; + case USB_ENDPOINT_XFER_ISOC: + /* ISO: limit 1023 bytes full speed, 1024 high/super speed */ + if (gadget_is_dualspeed(gadget)) + max = 1024; + else + max = 1023; + break; + case USB_ENDPOINT_XFER_BULK: + if (gadget_is_dualspeed(gadget)) + max = 512; + else + max = 64; + break; + case USB_ENDPOINT_XFER_INT: + if (gadget_is_dualspeed(gadget)) + max = 1024; + else + max = 64; + break; + } + if (usb_endpoint_maxp(desc) > max) + return NULL; + + /* + * Find a free EP address for that device. We can't + * let the generic code assign these as it would + * create overlapping numbers for IN and OUT which + * we don't support, so also create a suitable name + * that will allow the generic code to use our + * assigned address. + */ + for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) + if (d->epns[i] == NULL) + break; + if (i >= AST_VHUB_NUM_GEN_EPs) + return NULL; + addr = i + 1; + + /* + * Now grab an EP from the shared pool and associate + * it with our device + */ + ep = ast_vhub_alloc_epn(d, addr); + if (!ep) + return NULL; + DDBG(d, "Allocated epn#%d for port EP%d\n", + ep->epn.g_idx, addr); + + return &ep->ep; +} + +static int ast_vhub_udc_stop(struct usb_gadget *gadget) +{ + struct ast_vhub_dev *d = to_ast_dev(gadget); + unsigned long flags; + + spin_lock_irqsave(&d->vhub->lock, flags); + + DDBG(d, "stop\n"); + + d->driver = NULL; + d->gadget.speed = USB_SPEED_UNKNOWN; + + ast_vhub_dev_nuke(d); + + if (d->enabled) + ast_vhub_dev_disable(d); + + spin_unlock_irqrestore(&d->vhub->lock, flags); + + return 0; +} + +static struct usb_gadget_ops ast_vhub_udc_ops = { + .get_frame = ast_vhub_udc_get_frame, + .wakeup = ast_vhub_udc_wakeup, + .pullup = ast_vhub_udc_pullup, + .udc_start = ast_vhub_udc_start, + .udc_stop = ast_vhub_udc_stop, + .match_ep = ast_vhub_udc_match_ep, +}; + +void ast_vhub_dev_suspend(struct ast_vhub_dev *d) +{ + d->suspended = true; + if (d->driver) { + spin_unlock(&d->vhub->lock); + d->driver->suspend(&d->gadget); + spin_lock(&d->vhub->lock); + } +} + +void ast_vhub_dev_resume(struct ast_vhub_dev *d) +{ + d->suspended = false; + if (d->driver) { + spin_unlock(&d->vhub->lock); + d->driver->resume(&d->gadget); + spin_lock(&d->vhub->lock); + } +} + +void ast_vhub_dev_reset(struct ast_vhub_dev *d) +{ + /* + * If speed is not set, we enable the port. If it is, + * send reset to the gadget and reset "speed". + * + * Speed is an indication that we have got the first + * setup packet to the device. + */ + if (d->gadget.speed == USB_SPEED_UNKNOWN && !d->enabled) { + DDBG(d, "Reset at unknown speed of disabled device, enabling...\n"); + ast_vhub_dev_enable(d); + d->suspended = false; + } + if (d->gadget.speed != USB_SPEED_UNKNOWN && d->driver) { + unsigned int i; + + DDBG(d, "Reset at known speed of bound device, resetting...\n"); + spin_unlock(&d->vhub->lock); + d->driver->reset(&d->gadget); + spin_lock(&d->vhub->lock); + + /* + * Disable/re-enable HW, this will clear the address + * and speed setting. + */ + ast_vhub_dev_disable(d); + ast_vhub_dev_enable(d); + + /* Clear stall on all EPs */ + for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) { + struct ast_vhub_ep *ep = d->epns[i]; + + if (ep && ep->epn.stalled) { + ep->epn.stalled = false; + ast_vhub_update_epn_stall(ep); + } + } + + /* Additional cleanups */ + d->wakeup_en = false; + d->suspended = false; + } +} + +void ast_vhub_del_dev(struct ast_vhub_dev *d) +{ + unsigned long flags; + + spin_lock_irqsave(&d->vhub->lock, flags); + if (!d->registered) { + spin_unlock_irqrestore(&d->vhub->lock, flags); + return; + } + d->registered = false; + spin_unlock_irqrestore(&d->vhub->lock, flags); + + usb_del_gadget_udc(&d->gadget); + device_unregister(d->port_dev); +} + +static void ast_vhub_dev_release(struct device *dev) +{ + kfree(dev); +} + +int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx) +{ + struct ast_vhub_dev *d = &vhub->ports[idx].dev; + struct device *parent = &vhub->pdev->dev; + int rc; + + d->vhub = vhub; + d->index = idx; + d->name = devm_kasprintf(parent, GFP_KERNEL, "port%d", idx+1); + d->regs = vhub->regs + 0x100 + 0x10 * idx; + + ast_vhub_init_ep0(vhub, &d->ep0, d); + + /* + * The UDC core really needs us to have separate and uniquely + * named "parent" devices for each port so we create a sub device + * here for that purpose + */ + d->port_dev = kzalloc(sizeof(struct device), GFP_KERNEL); + if (!d->port_dev) + return -ENOMEM; + device_initialize(d->port_dev); + d->port_dev->release = ast_vhub_dev_release; + d->port_dev->parent = parent; + dev_set_name(d->port_dev, "%s:p%d", dev_name(parent), idx + 1); + rc = device_add(d->port_dev); + if (rc) + goto fail_add; + + /* Populate gadget */ + INIT_LIST_HEAD(&d->gadget.ep_list); + d->gadget.ops = &ast_vhub_udc_ops; + d->gadget.ep0 = &d->ep0.ep; + d->gadget.name = KBUILD_MODNAME; + if (vhub->force_usb1) + d->gadget.max_speed = USB_SPEED_FULL; + else + d->gadget.max_speed = USB_SPEED_HIGH; + d->gadget.speed = USB_SPEED_UNKNOWN; + d->gadget.dev.of_node = vhub->pdev->dev.of_node; + + rc = usb_add_gadget_udc(d->port_dev, &d->gadget); + if (rc != 0) + goto fail_udc; + d->registered = true; + + return 0; + fail_udc: + device_del(d->port_dev); + fail_add: + put_device(d->port_dev); + + return rc; +}
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/ep0.c b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c new file mode 100644 index 0000000..e2927fb --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/ep0.c
@@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * ep0.c - Endpoint 0 handling + * + * Copyright 2017 IBM Corporation + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/prefetch.h> +#include <linux/clk.h> +#include <linux/usb/gadget.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/dma-mapping.h> + +#include "vhub.h" + +int ast_vhub_reply(struct ast_vhub_ep *ep, char *ptr, int len) +{ + struct usb_request *req = &ep->ep0.req.req; + int rc; + + if (WARN_ON(ep->d_idx != 0)) + return std_req_stall; + if (WARN_ON(!ep->ep0.dir_in)) + return std_req_stall; + if (WARN_ON(len > AST_VHUB_EP0_MAX_PACKET)) + return std_req_stall; + if (WARN_ON(req->status == -EINPROGRESS)) + return std_req_stall; + + req->buf = ptr; + req->length = len; + req->complete = NULL; + req->zero = true; + + /* + * Call internal queue directly after dropping the lock. This is + * safe to do as the reply is always the last thing done when + * processing a SETUP packet, usually as a tail call + */ + spin_unlock(&ep->vhub->lock); + if (ep->ep.ops->queue(&ep->ep, req, GFP_ATOMIC)) + rc = std_req_stall; + else + rc = std_req_data; + spin_lock(&ep->vhub->lock); + return rc; +} + +int __ast_vhub_simple_reply(struct ast_vhub_ep *ep, int len, ...) +{ + u8 *buffer = ep->buf; + unsigned int i; + va_list args; + + va_start(args, len); + + /* Copy data directly into EP buffer */ + for (i = 0; i < len; i++) + buffer[i] = va_arg(args, int); + va_end(args); + + /* req->buf NULL means data is already there */ + return ast_vhub_reply(ep, NULL, len); +} + +void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep) +{ + struct usb_ctrlrequest crq; + enum std_req_rc std_req_rc; + int rc = -ENODEV; + + if (WARN_ON(ep->d_idx != 0)) + return; + + /* + * Grab the setup packet from the chip and byteswap + * interesting fields + */ + memcpy_fromio(&crq, ep->ep0.setup, sizeof(crq)); + + EPDBG(ep, "SETUP packet %02x/%02x/%04x/%04x/%04x [%s] st=%d\n", + crq.bRequestType, crq.bRequest, + le16_to_cpu(crq.wValue), + le16_to_cpu(crq.wIndex), + le16_to_cpu(crq.wLength), + (crq.bRequestType & USB_DIR_IN) ? "in" : "out", + ep->ep0.state); + + /* Check our state, cancel pending requests if needed */ + if (ep->ep0.state != ep0_state_token) { + EPDBG(ep, "wrong state\n"); + ast_vhub_nuke(ep, -EIO); + + /* + * Accept the packet regardless, this seems to happen + * when stalling a SETUP packet that has an OUT data + * phase. + */ + ast_vhub_nuke(ep, 0); + goto stall; + } + + /* Calculate next state for EP0 */ + ep->ep0.state = ep0_state_data; + ep->ep0.dir_in = !!(crq.bRequestType & USB_DIR_IN); + + /* If this is the vHub, we handle requests differently */ + std_req_rc = std_req_driver; + if (ep->dev == NULL) { + if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + std_req_rc = ast_vhub_std_hub_request(ep, &crq); + else if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) + std_req_rc = ast_vhub_class_hub_request(ep, &crq); + else + std_req_rc = std_req_stall; + } else if ((crq.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + std_req_rc = ast_vhub_std_dev_request(ep, &crq); + + /* Act upon result */ + switch(std_req_rc) { + case std_req_complete: + goto complete; + case std_req_stall: + goto stall; + case std_req_driver: + break; + case std_req_data: + return; + } + + /* Pass request up to the gadget driver */ + if (WARN_ON(!ep->dev)) + goto stall; + if (ep->dev->driver) { + EPDBG(ep, "forwarding to gadget...\n"); + spin_unlock(&ep->vhub->lock); + rc = ep->dev->driver->setup(&ep->dev->gadget, &crq); + spin_lock(&ep->vhub->lock); + EPDBG(ep, "driver returned %d\n", rc); + } else { + EPDBG(ep, "no gadget for request !\n"); + } + if (rc >= 0) + return; + + stall: + EPDBG(ep, "stalling\n"); + writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); + ep->ep0.state = ep0_state_status; + ep->ep0.dir_in = false; + return; + + complete: + EPVDBG(ep, "sending [in] status with no data\n"); + writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat); + ep->ep0.state = ep0_state_status; + ep->ep0.dir_in = false; +} + + +static void ast_vhub_ep0_do_send(struct ast_vhub_ep *ep, + struct ast_vhub_req *req) +{ + unsigned int chunk; + u32 reg; + + /* If this is a 0-length request, it's the gadget trying to + * send a status on our behalf. We take it from here. + */ + if (req->req.length == 0) + req->last_desc = 1; + + /* Are we done ? Complete request, otherwise wait for next interrupt */ + if (req->last_desc >= 0) { + EPVDBG(ep, "complete send %d/%d\n", + req->req.actual, req->req.length); + ep->ep0.state = ep0_state_status; + writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat); + ast_vhub_done(ep, req, 0); + return; + } + + /* + * Next chunk cropped to max packet size. Also check if this + * is the last packet + */ + chunk = req->req.length - req->req.actual; + if (chunk > ep->ep.maxpacket) + chunk = ep->ep.maxpacket; + else if ((chunk < ep->ep.maxpacket) || !req->req.zero) + req->last_desc = 1; + + EPVDBG(ep, "send chunk=%d last=%d, req->act=%d mp=%d\n", + chunk, req->last_desc, req->req.actual, ep->ep.maxpacket); + + /* + * Copy data if any (internal requests already have data + * in the EP buffer) + */ + if (chunk && req->req.buf) + memcpy(ep->buf, req->req.buf + req->req.actual, chunk); + + vhub_dma_workaround(ep->buf); + + /* Remember chunk size and trigger send */ + reg = VHUB_EP0_SET_TX_LEN(chunk); + writel(reg, ep->ep0.ctlstat); + writel(reg | VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat); + req->req.actual += chunk; +} + +static void ast_vhub_ep0_rx_prime(struct ast_vhub_ep *ep) +{ + EPVDBG(ep, "rx prime\n"); + + /* Prime endpoint for receiving data */ + writel(VHUB_EP0_RX_BUFF_RDY, ep->ep0.ctlstat); +} + +static void ast_vhub_ep0_do_receive(struct ast_vhub_ep *ep, struct ast_vhub_req *req, + unsigned int len) +{ + unsigned int remain; + int rc = 0; + + /* We are receiving... grab request */ + remain = req->req.length - req->req.actual; + + EPVDBG(ep, "receive got=%d remain=%d\n", len, remain); + + /* Are we getting more than asked ? */ + if (len > remain) { + EPDBG(ep, "receiving too much (ovf: %d) !\n", + len - remain); + len = remain; + rc = -EOVERFLOW; + } + if (len && req->req.buf) + memcpy(req->req.buf + req->req.actual, ep->buf, len); + req->req.actual += len; + + /* Done ? */ + if (len < ep->ep.maxpacket || len == remain) { + ep->ep0.state = ep0_state_status; + writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat); + ast_vhub_done(ep, req, rc); + } else + ast_vhub_ep0_rx_prime(ep); +} + +void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack) +{ + struct ast_vhub_req *req; + struct ast_vhub *vhub = ep->vhub; + struct device *dev = &vhub->pdev->dev; + bool stall = false; + u32 stat; + + /* Read EP0 status */ + stat = readl(ep->ep0.ctlstat); + + /* Grab current request if any */ + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); + + EPVDBG(ep, "ACK status=%08x,state=%d is_in=%d in_ack=%d req=%p\n", + stat, ep->ep0.state, ep->ep0.dir_in, in_ack, req); + + switch(ep->ep0.state) { + case ep0_state_token: + /* There should be no request queued in that state... */ + if (req) { + dev_warn(dev, "request present while in TOKEN state\n"); + ast_vhub_nuke(ep, -EINVAL); + } + dev_warn(dev, "ack while in TOKEN state\n"); + stall = true; + break; + case ep0_state_data: + /* Check the state bits corresponding to our direction */ + if ((ep->ep0.dir_in && (stat & VHUB_EP0_TX_BUFF_RDY)) || + (!ep->ep0.dir_in && (stat & VHUB_EP0_RX_BUFF_RDY)) || + (ep->ep0.dir_in != in_ack)) { + dev_warn(dev, "irq state mismatch"); + stall = true; + break; + } + /* + * We are in data phase and there's no request, something is + * wrong, stall + */ + if (!req) { + dev_warn(dev, "data phase, no request\n"); + stall = true; + break; + } + + /* We have a request, handle data transfers */ + if (ep->ep0.dir_in) + ast_vhub_ep0_do_send(ep, req); + else + ast_vhub_ep0_do_receive(ep, req, VHUB_EP0_RX_LEN(stat)); + return; + case ep0_state_status: + /* Nuke stale requests */ + if (req) { + dev_warn(dev, "request present while in STATUS state\n"); + ast_vhub_nuke(ep, -EINVAL); + } + + /* + * If the status phase completes with the wrong ack, stall + * the endpoint just in case, to abort whatever the host + * was doing. + */ + if (ep->ep0.dir_in == in_ack) { + dev_warn(dev, "status direction mismatch\n"); + stall = true; + } + } + + /* Reset to token state */ + ep->ep0.state = ep0_state_token; + if (stall) + writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); +} + +static int ast_vhub_ep0_queue(struct usb_ep* u_ep, struct usb_request *u_req, + gfp_t gfp_flags) +{ + struct ast_vhub_req *req = to_ast_req(u_req); + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + struct device *dev = &vhub->pdev->dev; + unsigned long flags; + + /* Paranoid cheks */ + if (!u_req || (!u_req->complete && !req->internal)) { + dev_warn(dev, "Bogus EP0 request ! u_req=%p\n", u_req); + if (u_req) { + dev_warn(dev, "complete=%p internal=%d\n", + u_req->complete, req->internal); + } + return -EINVAL; + } + + /* Not endpoint 0 ? */ + if (WARN_ON(ep->d_idx != 0)) + return -EINVAL; + + /* Disabled device */ + if (ep->dev && (!ep->dev->enabled || ep->dev->suspended)) + return -ESHUTDOWN; + + /* Data, no buffer and not internal ? */ + if (u_req->length && !u_req->buf && !req->internal) { + dev_warn(dev, "Request with no buffer !\n"); + return -EINVAL; + } + + EPVDBG(ep, "enqueue req @%p\n", req); + EPVDBG(ep, " l=%d zero=%d noshort=%d is_in=%d\n", + u_req->length, u_req->zero, + u_req->short_not_ok, ep->ep0.dir_in); + + /* Initialize request progress fields */ + u_req->status = -EINPROGRESS; + u_req->actual = 0; + req->last_desc = -1; + req->active = false; + + spin_lock_irqsave(&vhub->lock, flags); + + /* EP0 can only support a single request at a time */ + if (!list_empty(&ep->queue) || ep->ep0.state == ep0_state_token) { + dev_warn(dev, "EP0: Request in wrong state\n"); + spin_unlock_irqrestore(&vhub->lock, flags); + return -EBUSY; + } + + /* Add request to list and kick processing if empty */ + list_add_tail(&req->queue, &ep->queue); + + if (ep->ep0.dir_in) { + /* IN request, send data */ + ast_vhub_ep0_do_send(ep, req); + } else if (u_req->length == 0) { + /* 0-len request, send completion as rx */ + EPVDBG(ep, "0-length rx completion\n"); + ep->ep0.state = ep0_state_status; + writel(VHUB_EP0_TX_BUFF_RDY, ep->ep0.ctlstat); + ast_vhub_done(ep, req, 0); + } else { + /* OUT request, start receiver */ + ast_vhub_ep0_rx_prime(ep); + } + + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static int ast_vhub_ep0_dequeue(struct usb_ep* u_ep, struct usb_request *u_req) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + struct ast_vhub_req *req; + unsigned long flags; + int rc = -EINVAL; + + spin_lock_irqsave(&vhub->lock, flags); + + /* Only one request can be in the queue */ + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); + + /* Is it ours ? */ + if (req && u_req == &req->req) { + EPVDBG(ep, "dequeue req @%p\n", req); + + /* + * We don't have to deal with "active" as all + * DMAs go to the EP buffers, not the request. + */ + ast_vhub_done(ep, req, -ECONNRESET); + + /* We do stall the EP to clean things up in HW */ + writel(VHUB_EP0_CTRL_STALL, ep->ep0.ctlstat); + ep->ep0.state = ep0_state_status; + ep->ep0.dir_in = false; + rc = 0; + } + spin_unlock_irqrestore(&vhub->lock, flags); + return rc; +} + + +static const struct usb_ep_ops ast_vhub_ep0_ops = { + .queue = ast_vhub_ep0_queue, + .dequeue = ast_vhub_ep0_dequeue, + .alloc_request = ast_vhub_alloc_request, + .free_request = ast_vhub_free_request, +}; + +void ast_vhub_init_ep0(struct ast_vhub *vhub, struct ast_vhub_ep *ep, + struct ast_vhub_dev *dev) +{ + memset(ep, 0, sizeof(*ep)); + + INIT_LIST_HEAD(&ep->ep.ep_list); + INIT_LIST_HEAD(&ep->queue); + ep->ep.ops = &ast_vhub_ep0_ops; + ep->ep.name = "ep0"; + ep->ep.caps.type_control = true; + usb_ep_set_maxpacket_limit(&ep->ep, AST_VHUB_EP0_MAX_PACKET); + ep->d_idx = 0; + ep->dev = dev; + ep->vhub = vhub; + ep->ep0.state = ep0_state_token; + INIT_LIST_HEAD(&ep->ep0.req.queue); + ep->ep0.req.internal = true; + + /* Small difference between vHub and devices */ + if (dev) { + ep->ep0.ctlstat = dev->regs + AST_VHUB_DEV_EP0_CTRL; + ep->ep0.setup = vhub->regs + + AST_VHUB_SETUP0 + 8 * (dev->index + 1); + ep->buf = vhub->ep0_bufs + + AST_VHUB_EP0_MAX_PACKET * (dev->index + 1); + ep->buf_dma = vhub->ep0_bufs_dma + + AST_VHUB_EP0_MAX_PACKET * (dev->index + 1); + } else { + ep->ep0.ctlstat = vhub->regs + AST_VHUB_EP0_CTRL; + ep->ep0.setup = vhub->regs + AST_VHUB_SETUP0; + ep->buf = vhub->ep0_bufs; + ep->buf_dma = vhub->ep0_bufs_dma; + } +}
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/epn.c b/drivers/usb/gadget/udc/aspeed-vhub/epn.c new file mode 100644 index 0000000..5939eb1 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/epn.c
@@ -0,0 +1,851 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * epn.c - Generic endpoints management + * + * Copyright 2017 IBM Corporation + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/prefetch.h> +#include <linux/clk.h> +#include <linux/usb/gadget.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/dma-mapping.h> + +#include "vhub.h" + +#define EXTRA_CHECKS + +#ifdef EXTRA_CHECKS +#define CHECK(ep, expr, fmt...) \ + do { \ + if (!(expr)) EPDBG(ep, "CHECK:" fmt); \ + } while(0) +#else +#define CHECK(ep, expr, fmt...) do { } while(0) +#endif + +static void ast_vhub_epn_kick(struct ast_vhub_ep *ep, struct ast_vhub_req *req) +{ + unsigned int act = req->req.actual; + unsigned int len = req->req.length; + unsigned int chunk; + + /* There should be no DMA ongoing */ + WARN_ON(req->active); + + /* Calculate next chunk size */ + chunk = len - act; + if (chunk > ep->ep.maxpacket) + chunk = ep->ep.maxpacket; + else if ((chunk < ep->ep.maxpacket) || !req->req.zero) + req->last_desc = 1; + + EPVDBG(ep, "kick req %p act=%d/%d chunk=%d last=%d\n", + req, act, len, chunk, req->last_desc); + + /* If DMA unavailable, using staging EP buffer */ + if (!req->req.dma) { + + /* For IN transfers, copy data over first */ + if (ep->epn.is_in) { + memcpy(ep->buf, req->req.buf + act, chunk); + vhub_dma_workaround(ep->buf); + } + writel(ep->buf_dma, ep->epn.regs + AST_VHUB_EP_DESC_BASE); + } else { + if (ep->epn.is_in) + vhub_dma_workaround(req->req.buf); + writel(req->req.dma + act, ep->epn.regs + AST_VHUB_EP_DESC_BASE); + } + + /* Start DMA */ + req->active = true; + writel(VHUB_EP_DMA_SET_TX_SIZE(chunk), + ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + writel(VHUB_EP_DMA_SET_TX_SIZE(chunk) | VHUB_EP_DMA_SINGLE_KICK, + ep->epn.regs + AST_VHUB_EP_DESC_STATUS); +} + +static void ast_vhub_epn_handle_ack(struct ast_vhub_ep *ep) +{ + struct ast_vhub_req *req; + unsigned int len; + u32 stat; + + /* Read EP status */ + stat = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + + /* Grab current request if any */ + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); + + EPVDBG(ep, "ACK status=%08x is_in=%d, req=%p (active=%d)\n", + stat, ep->epn.is_in, req, req ? req->active : 0); + + /* In absence of a request, bail out, must have been dequeued */ + if (!req) + return; + + /* + * Request not active, move on to processing queue, active request + * was probably dequeued + */ + if (!req->active) + goto next_chunk; + + /* Check if HW has moved on */ + if (VHUB_EP_DMA_RPTR(stat) != 0) { + EPDBG(ep, "DMA read pointer not 0 !\n"); + return; + } + + /* No current DMA ongoing */ + req->active = false; + + /* Grab lenght out of HW */ + len = VHUB_EP_DMA_TX_SIZE(stat); + + /* If not using DMA, copy data out if needed */ + if (!req->req.dma && !ep->epn.is_in && len) + memcpy(req->req.buf + req->req.actual, ep->buf, len); + + /* Adjust size */ + req->req.actual += len; + + /* Check for short packet */ + if (len < ep->ep.maxpacket) + req->last_desc = 1; + + /* That's it ? complete the request and pick a new one */ + if (req->last_desc >= 0) { + ast_vhub_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, + queue); + + /* + * Due to lock dropping inside "done" the next request could + * already be active, so check for that and bail if needed. + */ + if (!req || req->active) + return; + } + + next_chunk: + ast_vhub_epn_kick(ep, req); +} + +static inline unsigned int ast_vhub_count_free_descs(struct ast_vhub_ep *ep) +{ + /* + * d_next == d_last means descriptor list empty to HW, + * thus we can only have AST_VHUB_DESCS_COUNT-1 descriptors + * in the list + */ + return (ep->epn.d_last + AST_VHUB_DESCS_COUNT - ep->epn.d_next - 1) & + (AST_VHUB_DESCS_COUNT - 1); +} + +static void ast_vhub_epn_kick_desc(struct ast_vhub_ep *ep, + struct ast_vhub_req *req) +{ + struct ast_vhub_desc *desc = NULL; + unsigned int act = req->act_count; + unsigned int len = req->req.length; + unsigned int chunk; + + /* Mark request active if not already */ + req->active = true; + + /* If the request was already completely written, do nothing */ + if (req->last_desc >= 0) + return; + + EPVDBG(ep, "kick act=%d/%d chunk_max=%d free_descs=%d\n", + act, len, ep->epn.chunk_max, ast_vhub_count_free_descs(ep)); + + /* While we can create descriptors */ + while (ast_vhub_count_free_descs(ep) && req->last_desc < 0) { + unsigned int d_num; + + /* Grab next free descriptor */ + d_num = ep->epn.d_next; + desc = &ep->epn.descs[d_num]; + ep->epn.d_next = (d_num + 1) & (AST_VHUB_DESCS_COUNT - 1); + + /* Calculate next chunk size */ + chunk = len - act; + if (chunk <= ep->epn.chunk_max) { + /* + * Is this the last packet ? Because of having up to 8 + * packets in a descriptor we can't just compare "chunk" + * with ep.maxpacket. We have to see if it's a multiple + * of it to know if we have to send a zero packet. + * Sadly that involves a modulo which is a bit expensive + * but probably still better than not doing it. + */ + if (!chunk || !req->req.zero || (chunk % ep->ep.maxpacket) != 0) + req->last_desc = d_num; + } else { + chunk = ep->epn.chunk_max; + } + + EPVDBG(ep, " chunk: act=%d/%d chunk=%d last=%d desc=%d free=%d\n", + act, len, chunk, req->last_desc, d_num, + ast_vhub_count_free_descs(ep)); + + /* Populate descriptor */ + desc->w0 = cpu_to_le32(req->req.dma + act); + + /* Interrupt if end of request or no more descriptors */ + + /* + * TODO: Be smarter about it, if we don't have enough + * descriptors request an interrupt before queue empty + * or so in order to be able to populate more before + * the HW runs out. This isn't a problem at the moment + * as we use 256 descriptors and only put at most one + * request in the ring. + */ + desc->w1 = cpu_to_le32(VHUB_DSC1_IN_SET_LEN(chunk)); + if (req->last_desc >= 0 || !ast_vhub_count_free_descs(ep)) + desc->w1 |= cpu_to_le32(VHUB_DSC1_IN_INTERRUPT); + + /* Account packet */ + req->act_count = act = act + chunk; + } + + if (likely(desc)) + vhub_dma_workaround(desc); + + /* Tell HW about new descriptors */ + writel(VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next), + ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + + EPVDBG(ep, "HW kicked, d_next=%d dstat=%08x\n", + ep->epn.d_next, readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS)); +} + +static void ast_vhub_epn_handle_ack_desc(struct ast_vhub_ep *ep) +{ + struct ast_vhub_req *req; + unsigned int len, d_last; + u32 stat, stat1; + + /* Read EP status, workaround HW race */ + do { + stat = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + stat1 = readl(ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + } while(stat != stat1); + + /* Extract RPTR */ + d_last = VHUB_EP_DMA_RPTR(stat); + + /* Grab current request if any */ + req = list_first_entry_or_null(&ep->queue, struct ast_vhub_req, queue); + + EPVDBG(ep, "ACK status=%08x is_in=%d ep->d_last=%d..%d\n", + stat, ep->epn.is_in, ep->epn.d_last, d_last); + + /* Check all completed descriptors */ + while (ep->epn.d_last != d_last) { + struct ast_vhub_desc *desc; + unsigned int d_num; + bool is_last_desc; + + /* Grab next completed descriptor */ + d_num = ep->epn.d_last; + desc = &ep->epn.descs[d_num]; + ep->epn.d_last = (d_num + 1) & (AST_VHUB_DESCS_COUNT - 1); + + /* Grab len out of descriptor */ + len = VHUB_DSC1_IN_LEN(le32_to_cpu(desc->w1)); + + EPVDBG(ep, " desc %d len=%d req=%p (act=%d)\n", + d_num, len, req, req ? req->active : 0); + + /* If no active request pending, move on */ + if (!req || !req->active) + continue; + + /* Adjust size */ + req->req.actual += len; + + /* Is that the last chunk ? */ + is_last_desc = req->last_desc == d_num; + CHECK(ep, is_last_desc == (len < ep->ep.maxpacket || + (req->req.actual >= req->req.length && + !req->req.zero)), + "Last packet discrepancy: last_desc=%d len=%d r.act=%d " + "r.len=%d r.zero=%d mp=%d\n", + is_last_desc, len, req->req.actual, req->req.length, + req->req.zero, ep->ep.maxpacket); + + if (is_last_desc) { + /* + * Because we can only have one request at a time + * in our descriptor list in this implementation, + * d_last and ep->d_last should now be equal + */ + CHECK(ep, d_last == ep->epn.d_last, + "DMA read ptr mismatch %d vs %d\n", + d_last, ep->epn.d_last); + + /* Note: done will drop and re-acquire the lock */ + ast_vhub_done(ep, req, 0); + req = list_first_entry_or_null(&ep->queue, + struct ast_vhub_req, + queue); + break; + } + } + + /* More work ? */ + if (req) + ast_vhub_epn_kick_desc(ep, req); +} + +void ast_vhub_epn_ack_irq(struct ast_vhub_ep *ep) +{ + if (ep->epn.desc_mode) + ast_vhub_epn_handle_ack_desc(ep); + else + ast_vhub_epn_handle_ack(ep); +} + +static int ast_vhub_epn_queue(struct usb_ep* u_ep, struct usb_request *u_req, + gfp_t gfp_flags) +{ + struct ast_vhub_req *req = to_ast_req(u_req); + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + unsigned long flags; + bool empty; + int rc; + + /* Paranoid checks */ + if (!u_req || !u_req->complete || !u_req->buf) { + dev_warn(&vhub->pdev->dev, "Bogus EPn request ! u_req=%p\n", u_req); + if (u_req) { + dev_warn(&vhub->pdev->dev, "complete=%p internal=%d\n", + u_req->complete, req->internal); + } + return -EINVAL; + } + + /* Endpoint enabled ? */ + if (!ep->epn.enabled || !u_ep->desc || !ep->dev || !ep->d_idx || + !ep->dev->enabled || ep->dev->suspended) { + EPDBG(ep,"Enqueing request on wrong or disabled EP\n"); + return -ESHUTDOWN; + } + + /* Map request for DMA if possible. For now, the rule for DMA is + * that: + * + * * For single stage mode (no descriptors): + * + * - The buffer is aligned to a 8 bytes boundary (HW requirement) + * - For a OUT endpoint, the request size is a multiple of the EP + * packet size (otherwise the controller will DMA past the end + * of the buffer if the host is sending a too long packet). + * + * * For descriptor mode (tx only for now), always. + * + * We could relax the latter by making the decision to use the bounce + * buffer based on the size of a given *segment* of the request rather + * than the whole request. + */ + if (ep->epn.desc_mode || + ((((unsigned long)u_req->buf & 7) == 0) && + (ep->epn.is_in || !(u_req->length & (u_ep->maxpacket - 1))))) { + rc = usb_gadget_map_request(&ep->dev->gadget, u_req, + ep->epn.is_in); + if (rc) { + dev_warn(&vhub->pdev->dev, + "Request mapping failure %d\n", rc); + return rc; + } + } else + u_req->dma = 0; + + EPVDBG(ep, "enqueue req @%p\n", req); + EPVDBG(ep, " l=%d dma=0x%x zero=%d noshort=%d noirq=%d is_in=%d\n", + u_req->length, (u32)u_req->dma, u_req->zero, + u_req->short_not_ok, u_req->no_interrupt, + ep->epn.is_in); + + /* Initialize request progress fields */ + u_req->status = -EINPROGRESS; + u_req->actual = 0; + req->act_count = 0; + req->active = false; + req->last_desc = -1; + spin_lock_irqsave(&vhub->lock, flags); + empty = list_empty(&ep->queue); + + /* Add request to list and kick processing if empty */ + list_add_tail(&req->queue, &ep->queue); + if (empty) { + if (ep->epn.desc_mode) + ast_vhub_epn_kick_desc(ep, req); + else + ast_vhub_epn_kick(ep, req); + } + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static void ast_vhub_stop_active_req(struct ast_vhub_ep *ep, + bool restart_ep) +{ + u32 state, reg, loops; + + /* Stop DMA activity */ + writel(0, ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + + /* Wait for it to complete */ + for (loops = 0; loops < 1000; loops++) { + state = readl(ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + state = VHUB_EP_DMA_PROC_STATUS(state); + if (state == EP_DMA_PROC_RX_IDLE || + state == EP_DMA_PROC_TX_IDLE) + break; + udelay(1); + } + if (loops >= 1000) + dev_warn(&ep->vhub->pdev->dev, "Timeout waiting for DMA\n"); + + /* If we don't have to restart the endpoint, that's it */ + if (!restart_ep) + return; + + /* Restart the endpoint */ + if (ep->epn.desc_mode) { + /* + * Take out descriptors by resetting the DMA read + * pointer to be equal to the CPU write pointer. + * + * Note: If we ever support creating descriptors for + * requests that aren't the head of the queue, we + * may have to do something more complex here, + * especially if the request being taken out is + * not the current head descriptors. + */ + reg = VHUB_EP_DMA_SET_RPTR(ep->epn.d_next) | + VHUB_EP_DMA_SET_CPU_WPTR(ep->epn.d_next); + writel(reg, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + + /* Then turn it back on */ + writel(ep->epn.dma_conf, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + } else { + /* Single mode: just turn it back on */ + writel(ep->epn.dma_conf, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + } +} + +static int ast_vhub_epn_dequeue(struct usb_ep* u_ep, struct usb_request *u_req) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + struct ast_vhub_req *req; + unsigned long flags; + int rc = -EINVAL; + + spin_lock_irqsave(&vhub->lock, flags); + + /* Make sure it's actually queued on this endpoint */ + list_for_each_entry (req, &ep->queue, queue) { + if (&req->req == u_req) + break; + } + + if (&req->req == u_req) { + EPVDBG(ep, "dequeue req @%p active=%d\n", + req, req->active); + if (req->active) + ast_vhub_stop_active_req(ep, true); + ast_vhub_done(ep, req, -ECONNRESET); + rc = 0; + } + + spin_unlock_irqrestore(&vhub->lock, flags); + return rc; +} + +void ast_vhub_update_epn_stall(struct ast_vhub_ep *ep) +{ + u32 reg; + + if (WARN_ON(ep->d_idx == 0)) + return; + reg = readl(ep->epn.regs + AST_VHUB_EP_CONFIG); + if (ep->epn.stalled || ep->epn.wedged) + reg |= VHUB_EP_CFG_STALL_CTRL; + else + reg &= ~VHUB_EP_CFG_STALL_CTRL; + writel(reg, ep->epn.regs + AST_VHUB_EP_CONFIG); + + if (!ep->epn.stalled && !ep->epn.wedged) + writel(VHUB_EP_TOGGLE_SET_EPNUM(ep->epn.g_idx), + ep->vhub->regs + AST_VHUB_EP_TOGGLE); +} + +static int ast_vhub_set_halt_and_wedge(struct usb_ep* u_ep, bool halt, + bool wedge) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + unsigned long flags; + + EPDBG(ep, "Set halt (%d) & wedge (%d)\n", halt, wedge); + + if (!u_ep || !u_ep->desc) + return -EINVAL; + if (ep->d_idx == 0) + return 0; + if (ep->epn.is_iso) + return -EOPNOTSUPP; + + spin_lock_irqsave(&vhub->lock, flags); + + /* Fail with still-busy IN endpoints */ + if (halt && ep->epn.is_in && !list_empty(&ep->queue)) { + spin_unlock_irqrestore(&vhub->lock, flags); + return -EAGAIN; + } + ep->epn.stalled = halt; + ep->epn.wedged = wedge; + ast_vhub_update_epn_stall(ep); + + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static int ast_vhub_epn_set_halt(struct usb_ep *u_ep, int value) +{ + return ast_vhub_set_halt_and_wedge(u_ep, value != 0, false); +} + +static int ast_vhub_epn_set_wedge(struct usb_ep *u_ep) +{ + return ast_vhub_set_halt_and_wedge(u_ep, true, true); +} + +static int ast_vhub_epn_disable(struct usb_ep* u_ep) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub *vhub = ep->vhub; + unsigned long flags; + u32 imask, ep_ier; + + EPDBG(ep, "Disabling !\n"); + + spin_lock_irqsave(&vhub->lock, flags); + + ep->epn.enabled = false; + + /* Stop active DMA if any */ + ast_vhub_stop_active_req(ep, false); + + /* Disable endpoint */ + writel(0, ep->epn.regs + AST_VHUB_EP_CONFIG); + + /* Disable ACK interrupt */ + imask = VHUB_EP_IRQ(ep->epn.g_idx); + ep_ier = readl(vhub->regs + AST_VHUB_EP_ACK_IER); + ep_ier &= ~imask; + writel(ep_ier, vhub->regs + AST_VHUB_EP_ACK_IER); + writel(imask, vhub->regs + AST_VHUB_EP_ACK_ISR); + + /* Nuke all pending requests */ + ast_vhub_nuke(ep, -ESHUTDOWN); + + /* No more descriptor associated with request */ + ep->ep.desc = NULL; + + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static int ast_vhub_epn_enable(struct usb_ep* u_ep, + const struct usb_endpoint_descriptor *desc) +{ + static const char *ep_type_string[] __maybe_unused = { "ctrl", + "isoc", + "bulk", + "intr" }; + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + struct ast_vhub_dev *dev; + struct ast_vhub *vhub; + u16 maxpacket, type; + unsigned long flags; + u32 ep_conf, ep_ier, imask; + + /* Check arguments */ + if (!u_ep || !desc) + return -EINVAL; + + maxpacket = usb_endpoint_maxp(desc); + if (!ep->d_idx || !ep->dev || + desc->bDescriptorType != USB_DT_ENDPOINT || + maxpacket == 0 || maxpacket > ep->ep.maxpacket) { + EPDBG(ep, "Invalid EP enable,d_idx=%d,dev=%p,type=%d,mp=%d/%d\n", + ep->d_idx, ep->dev, desc->bDescriptorType, + maxpacket, ep->ep.maxpacket); + return -EINVAL; + } + if (ep->d_idx != usb_endpoint_num(desc)) { + EPDBG(ep, "EP number mismatch !\n"); + return -EINVAL; + } + + if (ep->epn.enabled) { + EPDBG(ep, "Already enabled\n"); + return -EBUSY; + } + dev = ep->dev; + vhub = ep->vhub; + + /* Check device state */ + if (!dev->driver) { + EPDBG(ep, "Bogus device state: driver=%p speed=%d\n", + dev->driver, dev->gadget.speed); + return -ESHUTDOWN; + } + + /* Grab some info from the descriptor */ + ep->epn.is_in = usb_endpoint_dir_in(desc); + ep->ep.maxpacket = maxpacket; + type = usb_endpoint_type(desc); + ep->epn.d_next = ep->epn.d_last = 0; + ep->epn.is_iso = false; + ep->epn.stalled = false; + ep->epn.wedged = false; + + EPDBG(ep, "Enabling [%s] %s num %d maxpacket=%d\n", + ep->epn.is_in ? "in" : "out", ep_type_string[type], + usb_endpoint_num(desc), maxpacket); + + /* Can we use DMA descriptor mode ? */ + ep->epn.desc_mode = ep->epn.descs && ep->epn.is_in; + if (ep->epn.desc_mode) + memset(ep->epn.descs, 0, 8 * AST_VHUB_DESCS_COUNT); + + /* + * Large send function can send up to 8 packets from + * one descriptor with a limit of 4095 bytes. + */ + ep->epn.chunk_max = ep->ep.maxpacket; + if (ep->epn.is_in) { + ep->epn.chunk_max <<= 3; + while (ep->epn.chunk_max > 4095) + ep->epn.chunk_max -= ep->ep.maxpacket; + } + + switch(type) { + case USB_ENDPOINT_XFER_CONTROL: + EPDBG(ep, "Only one control endpoint\n"); + return -EINVAL; + case USB_ENDPOINT_XFER_INT: + ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_INT); + break; + case USB_ENDPOINT_XFER_BULK: + ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_BULK); + break; + case USB_ENDPOINT_XFER_ISOC: + ep_conf = VHUB_EP_CFG_SET_TYPE(EP_TYPE_ISO); + ep->epn.is_iso = true; + break; + default: + return -EINVAL; + } + + /* Encode the rest of the EP config register */ + if (maxpacket < 1024) + ep_conf |= VHUB_EP_CFG_SET_MAX_PKT(maxpacket); + if (!ep->epn.is_in) + ep_conf |= VHUB_EP_CFG_DIR_OUT; + ep_conf |= VHUB_EP_CFG_SET_EP_NUM(usb_endpoint_num(desc)); + ep_conf |= VHUB_EP_CFG_ENABLE; + ep_conf |= VHUB_EP_CFG_SET_DEV(dev->index + 1); + EPVDBG(ep, "config=%08x\n", ep_conf); + + spin_lock_irqsave(&vhub->lock, flags); + + /* Disable HW and reset DMA */ + writel(0, ep->epn.regs + AST_VHUB_EP_CONFIG); + writel(VHUB_EP_DMA_CTRL_RESET, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + + /* Configure and enable */ + writel(ep_conf, ep->epn.regs + AST_VHUB_EP_CONFIG); + + if (ep->epn.desc_mode) { + /* Clear DMA status, including the DMA read ptr */ + writel(0, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + + /* Set descriptor base */ + writel(ep->epn.descs_dma, + ep->epn.regs + AST_VHUB_EP_DESC_BASE); + + /* Set base DMA config value */ + ep->epn.dma_conf = VHUB_EP_DMA_DESC_MODE; + if (ep->epn.is_in) + ep->epn.dma_conf |= VHUB_EP_DMA_IN_LONG_MODE; + + /* First reset and disable all operations */ + writel(ep->epn.dma_conf | VHUB_EP_DMA_CTRL_RESET, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + + /* Enable descriptor mode */ + writel(ep->epn.dma_conf, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + } else { + /* Set base DMA config value */ + ep->epn.dma_conf = VHUB_EP_DMA_SINGLE_STAGE; + + /* Reset and switch to single stage mode */ + writel(ep->epn.dma_conf | VHUB_EP_DMA_CTRL_RESET, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + writel(ep->epn.dma_conf, + ep->epn.regs + AST_VHUB_EP_DMA_CTLSTAT); + writel(0, ep->epn.regs + AST_VHUB_EP_DESC_STATUS); + } + + /* Cleanup data toggle just in case */ + writel(VHUB_EP_TOGGLE_SET_EPNUM(ep->epn.g_idx), + vhub->regs + AST_VHUB_EP_TOGGLE); + + /* Cleanup and enable ACK interrupt */ + imask = VHUB_EP_IRQ(ep->epn.g_idx); + writel(imask, vhub->regs + AST_VHUB_EP_ACK_ISR); + ep_ier = readl(vhub->regs + AST_VHUB_EP_ACK_IER); + ep_ier |= imask; + writel(ep_ier, vhub->regs + AST_VHUB_EP_ACK_IER); + + /* Woot, we are online ! */ + ep->epn.enabled = true; + + spin_unlock_irqrestore(&vhub->lock, flags); + + return 0; +} + +static void ast_vhub_epn_dispose(struct usb_ep *u_ep) +{ + struct ast_vhub_ep *ep = to_ast_ep(u_ep); + + if (WARN_ON(!ep->dev || !ep->d_idx)) + return; + + EPDBG(ep, "Releasing endpoint\n"); + + /* Take it out of the EP list */ + list_del_init(&ep->ep.ep_list); + + /* Mark the address free in the device */ + ep->dev->epns[ep->d_idx - 1] = NULL; + + /* Free name & DMA buffers */ + kfree(ep->ep.name); + ep->ep.name = NULL; + dma_free_coherent(&ep->vhub->pdev->dev, + AST_VHUB_EPn_MAX_PACKET + + 8 * AST_VHUB_DESCS_COUNT, + ep->buf, ep->buf_dma); + ep->buf = NULL; + ep->epn.descs = NULL; + + /* Mark free */ + ep->dev = NULL; +} + +static const struct usb_ep_ops ast_vhub_epn_ops = { + .enable = ast_vhub_epn_enable, + .disable = ast_vhub_epn_disable, + .dispose = ast_vhub_epn_dispose, + .queue = ast_vhub_epn_queue, + .dequeue = ast_vhub_epn_dequeue, + .set_halt = ast_vhub_epn_set_halt, + .set_wedge = ast_vhub_epn_set_wedge, + .alloc_request = ast_vhub_alloc_request, + .free_request = ast_vhub_free_request, +}; + +struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr) +{ + struct ast_vhub *vhub = d->vhub; + struct ast_vhub_ep *ep; + unsigned long flags; + int i; + + /* Find a free one (no device) */ + spin_lock_irqsave(&vhub->lock, flags); + for (i = 0; i < AST_VHUB_NUM_GEN_EPs; i++) + if (vhub->epns[i].dev == NULL) + break; + if (i >= AST_VHUB_NUM_GEN_EPs) { + spin_unlock_irqrestore(&vhub->lock, flags); + return NULL; + } + + /* Set it up */ + ep = &vhub->epns[i]; + ep->dev = d; + spin_unlock_irqrestore(&vhub->lock, flags); + + DDBG(d, "Allocating gen EP %d for addr %d\n", i, addr); + INIT_LIST_HEAD(&ep->queue); + ep->d_idx = addr; + ep->vhub = vhub; + ep->ep.ops = &ast_vhub_epn_ops; + ep->ep.name = kasprintf(GFP_KERNEL, "ep%d", addr); + d->epns[addr-1] = ep; + ep->epn.g_idx = i; + ep->epn.regs = vhub->regs + 0x200 + (i * 0x10); + + ep->buf = dma_alloc_coherent(&vhub->pdev->dev, + AST_VHUB_EPn_MAX_PACKET + + 8 * AST_VHUB_DESCS_COUNT, + &ep->buf_dma, GFP_KERNEL); + if (!ep->buf) { + kfree(ep->ep.name); + ep->ep.name = NULL; + return NULL; + } + ep->epn.descs = ep->buf + AST_VHUB_EPn_MAX_PACKET; + ep->epn.descs_dma = ep->buf_dma + AST_VHUB_EPn_MAX_PACKET; + + usb_ep_set_maxpacket_limit(&ep->ep, AST_VHUB_EPn_MAX_PACKET); + list_add_tail(&ep->ep.ep_list, &d->gadget.ep_list); + ep->ep.caps.type_iso = true; + ep->ep.caps.type_bulk = true; + ep->ep.caps.type_int = true; + ep->ep.caps.dir_in = true; + ep->ep.caps.dir_out = true; + + return ep; +}
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c new file mode 100644 index 0000000..35ba0e5 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c
@@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * aspeed-vhub -- Driver for Aspeed SoC "vHub" USB gadget + * + * hub.c - virtual hub handling + * + * Copyright 2017 IBM Corporation + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/prefetch.h> +#include <linux/clk.h> +#include <linux/usb/gadget.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/dma-mapping.h> +#include <linux/bcd.h> +#include <linux/version.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "vhub.h" + +/* usb 2.0 hub device descriptor + * + * A few things we may want to improve here: + * + * - We may need to indicate TT support + * - We may need a device qualifier descriptor + * as devices can pretend to be usb1 or 2 + * - Make vid/did overridable + * - make it look like usb1 if usb1 mode forced + */ +#define KERNEL_REL bin2bcd(((LINUX_VERSION_CODE >> 16) & 0x0ff)) +#define KERNEL_VER bin2bcd(((LINUX_VERSION_CODE >> 8) & 0x0ff)) + +enum { + AST_VHUB_STR_MANUF = 3, + AST_VHUB_STR_PRODUCT = 2, + AST_VHUB_STR_SERIAL = 1, +}; + +static const struct usb_device_descriptor ast_vhub_dev_desc = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_HUB, + .bDeviceSubClass = 0, + .bDeviceProtocol = 1, + .bMaxPacketSize0 = 64, + .idVendor = cpu_to_le16(0x1d6b), + .idProduct = cpu_to_le16(0x0107), + .bcdDevice = cpu_to_le16(0x0100), + .iManufacturer = AST_VHUB_STR_MANUF, + .iProduct = AST_VHUB_STR_PRODUCT, + .iSerialNumber = AST_VHUB_STR_SERIAL, + .bNumConfigurations = 1, +}; + +/* Patches to the above when forcing USB1 mode */ +static void ast_vhub_patch_dev_desc_usb1(struct usb_device_descriptor *desc) +{ + desc->bcdUSB = cpu_to_le16(0x0100); + desc->bDeviceProtocol = 0; +} + +/* + * Configuration descriptor: same comments as above + * regarding handling USB1 mode. + */ + +/* + * We don't use sizeof() as Linux definition of + * struct usb_endpoint_descriptor contains 2 + * extra bytes + */ +#define AST_VHUB_CONF_DESC_SIZE (USB_DT_CONFIG_SIZE + \ + USB_DT_INTERFACE_SIZE + \ + USB_DT_ENDPOINT_SIZE) + +static const struct ast_vhub_full_cdesc { + struct usb_config_descriptor cfg; + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor ep; +} __attribute__ ((packed)) ast_vhub_conf_desc = { + .cfg = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = cpu_to_le16(AST_VHUB_CONF_DESC_SIZE), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = USB_CONFIG_ATT_ONE | + USB_CONFIG_ATT_SELFPOWER | + USB_CONFIG_ATT_WAKEUP, + .bMaxPower = 0, + }, + .intf = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HUB, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 0, + }, + .ep = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(1), + .bInterval = 0x0c, + }, +}; + +#define AST_VHUB_HUB_DESC_SIZE (USB_DT_HUB_NONVAR_SIZE + 2) + +static const struct usb_hub_descriptor ast_vhub_hub_desc = { + .bDescLength = AST_VHUB_HUB_DESC_SIZE, + .bDescriptorType = USB_DT_HUB, + .bNbrPorts = AST_VHUB_NUM_PORTS, + .wHubCharacteristics = cpu_to_le16(HUB_CHAR_NO_LPSM), + .bPwrOn2PwrGood = 10, + .bHubContrCurrent = 0, + .u.hs.DeviceRemovable[0] = 0, + .u.hs.DeviceRemovable[1] = 0xff, +}; + +/* + * These strings converted to UTF-16 must be smaller than + * our EP0 buffer. + */ +static const struct usb_string ast_vhub_str_array[] = { + { + .id = AST_VHUB_STR_SERIAL, + .s = "00000000" + }, + { + .id = AST_VHUB_STR_PRODUCT, + .s = "USB Virtual Hub" + }, + { + .id = AST_VHUB_STR_MANUF, + .s = "Aspeed" + }, + { } +}; + +static const struct usb_gadget_strings ast_vhub_strings = { + .language = 0x0409, + .strings = (struct usb_string *)ast_vhub_str_array +}; + +static int ast_vhub_hub_dev_status(struct ast_vhub_ep *ep, + u16 wIndex, u16 wValue) +{ + u8 st0; + + EPDBG(ep, "GET_STATUS(dev)\n"); + + /* + * Mark it as self-powered, I doubt the BMC is powered off + * the USB bus ... + */ + st0 = 1 << USB_DEVICE_SELF_POWERED; + + /* + * Need to double check how remote wakeup actually works + * on that chip and what triggers it. + */ + if (ep->vhub->wakeup_en) + st0 |= 1 << USB_DEVICE_REMOTE_WAKEUP; + + return ast_vhub_simple_reply(ep, st0, 0); +} + +static int ast_vhub_hub_ep_status(struct ast_vhub_ep *ep, + u16 wIndex, u16 wValue) +{ + int ep_num; + u8 st0 = 0; + + ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK; + EPDBG(ep, "GET_STATUS(ep%d)\n", ep_num); + + /* On the hub we have only EP 0 and 1 */ + if (ep_num == 1) { + if (ep->vhub->ep1_stalled) + st0 |= 1 << USB_ENDPOINT_HALT; + } else if (ep_num != 0) + return std_req_stall; + + return ast_vhub_simple_reply(ep, st0, 0); +} + +static int ast_vhub_hub_dev_feature(struct ast_vhub_ep *ep, + u16 wIndex, u16 wValue, + bool is_set) +{ + EPDBG(ep, "%s_FEATURE(dev val=%02x)\n", + is_set ? "SET" : "CLEAR", wValue); + + if (wValue != USB_DEVICE_REMOTE_WAKEUP) + return std_req_stall; + + ep->vhub->wakeup_en = is_set; + EPDBG(ep, "Hub remote wakeup %s\n", + is_set ? "enabled" : "disabled"); + + return std_req_complete; +} + +static int ast_vhub_hub_ep_feature(struct ast_vhub_ep *ep, + u16 wIndex, u16 wValue, + bool is_set) +{ + int ep_num; + u32 reg; + + ep_num = wIndex & USB_ENDPOINT_NUMBER_MASK; + EPDBG(ep, "%s_FEATURE(ep%d val=%02x)\n", + is_set ? "SET" : "CLEAR", ep_num, wValue); + + if (ep_num > 1) + return std_req_stall; + if (wValue != USB_ENDPOINT_HALT) + return std_req_stall; + if (ep_num == 0) + return std_req_complete; + + EPDBG(ep, "%s stall on EP 1\n", + is_set ? "setting" : "clearing"); + + ep->vhub->ep1_stalled = is_set; + reg = readl(ep->vhub->regs + AST_VHUB_EP1_CTRL); + if (is_set) { + reg |= VHUB_EP1_CTRL_STALL; + } else { + reg &= ~VHUB_EP1_CTRL_STALL; + reg |= VHUB_EP1_CTRL_RESET_TOGGLE; + } + writel(reg, ep->vhub->regs + AST_VHUB_EP1_CTRL); + + return std_req_complete; +} + +static int ast_vhub_rep_desc(struct ast_vhub_ep *ep, + u8 desc_type, u16 len) +{ + size_t dsize; + + EPDBG(ep, "GET_DESCRIPTOR(type:%d)\n", desc_type); + + /* + * Copy first to EP buffer and send from there, so + * we can do some in-place patching if needed. We know + * the EP buffer is big enough but ensure that doesn't + * change. We do that now rather than later after we + * have checked sizes etc... to avoid a gcc bug where + * it thinks len is constant and barfs about read + * overflows in memcpy. + */ + switch(desc_type) { + case USB_DT_DEVICE: + dsize = USB_DT_DEVICE_SIZE; + memcpy(ep->buf, &ast_vhub_dev_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(ast_vhub_dev_desc)); + BUILD_BUG_ON(USB_DT_DEVICE_SIZE >= AST_VHUB_EP0_MAX_PACKET); + break; + case USB_DT_CONFIG: + dsize = AST_VHUB_CONF_DESC_SIZE; + memcpy(ep->buf, &ast_vhub_conf_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(ast_vhub_conf_desc)); + BUILD_BUG_ON(AST_VHUB_CONF_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); + break; + case USB_DT_HUB: + dsize = AST_VHUB_HUB_DESC_SIZE; + memcpy(ep->buf, &ast_vhub_hub_desc, dsize); + BUILD_BUG_ON(dsize > sizeof(ast_vhub_hub_desc)); + BUILD_BUG_ON(AST_VHUB_HUB_DESC_SIZE >= AST_VHUB_EP0_MAX_PACKET); + break; + default: + return std_req_stall; + } + + /* Crop requested length */ + if (len > dsize) + len = dsize; + + /* Patch it if forcing USB1 */ + if (desc_type == USB_DT_DEVICE && ep->vhub->force_usb1) + ast_vhub_patch_dev_desc_usb1(ep->buf); + + /* Shoot it from the EP buffer */ + return ast_vhub_reply(ep, NULL, len); +} + +static int ast_vhub_rep_string(struct ast_vhub_ep *ep, + u8 string_id, u16 lang_id, + u16 len) +{ + int rc = usb_gadget_get_string (&ast_vhub_strings, string_id, ep->buf); + + /* + * This should never happen unless we put too big strings in + * the array above + */ + BUG_ON(rc >= AST_VHUB_EP0_MAX_PACKET); + + if (rc < 0) + return std_req_stall; + + /* Shoot it from the EP buffer */ + return ast_vhub_reply(ep, NULL, min_t(u16, rc, len)); +} + +enum std_req_rc ast_vhub_std_hub_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq) +{ + struct ast_vhub *vhub = ep->vhub; + u16 wValue, wIndex, wLength; + + wValue = le16_to_cpu(crq->wValue); + wIndex = le16_to_cpu(crq->wIndex); + wLength = le16_to_cpu(crq->wLength); + + /* First packet, grab speed */ + if (vhub->speed == USB_SPEED_UNKNOWN) { + u32 ustat = readl(vhub->regs + AST_VHUB_USBSTS); + if (ustat & VHUB_USBSTS_HISPEED) + vhub->speed = USB_SPEED_HIGH; + else + vhub->speed = USB_SPEED_FULL; + UDCDBG(vhub, "USB status=%08x speed=%s\n", ustat, + vhub->speed == USB_SPEED_HIGH ? "high" : "full"); + } + + switch ((crq->bRequestType << 8) | crq->bRequest) { + /* SET_ADDRESS */ + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + EPDBG(ep, "SET_ADDRESS: Got address %x\n", wValue); + writel(wValue, vhub->regs + AST_VHUB_CONF); + return std_req_complete; + + /* GET_STATUS */ + case DeviceRequest | USB_REQ_GET_STATUS: + return ast_vhub_hub_dev_status(ep, wIndex, wValue); + case InterfaceRequest | USB_REQ_GET_STATUS: + return ast_vhub_simple_reply(ep, 0, 0); + case EndpointRequest | USB_REQ_GET_STATUS: + return ast_vhub_hub_ep_status(ep, wIndex, wValue); + + /* SET/CLEAR_FEATURE */ + case DeviceOutRequest | USB_REQ_SET_FEATURE: + return ast_vhub_hub_dev_feature(ep, wIndex, wValue, true); + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + return ast_vhub_hub_dev_feature(ep, wIndex, wValue, false); + case EndpointOutRequest | USB_REQ_SET_FEATURE: + return ast_vhub_hub_ep_feature(ep, wIndex, wValue, true); + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + return ast_vhub_hub_ep_feature(ep, wIndex, wValue, false); + + /* GET/SET_CONFIGURATION */ + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + return ast_vhub_simple_reply(ep, 1); + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + if (wValue != 1) + return std_req_stall; + return std_req_complete; + + /* GET_DESCRIPTOR */ + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch (wValue >> 8) { + case USB_DT_DEVICE: + case USB_DT_CONFIG: + return ast_vhub_rep_desc(ep, wValue >> 8, + wLength); + case USB_DT_STRING: + return ast_vhub_rep_string(ep, wValue & 0xff, + wIndex, wLength); + } + return std_req_stall; + + /* GET/SET_INTERFACE */ + case DeviceRequest | USB_REQ_GET_INTERFACE: + return ast_vhub_simple_reply(ep, 0); + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + if (wValue != 0 || wIndex != 0) + return std_req_stall; + return std_req_complete; + } + return std_req_stall; +} + +static void ast_vhub_update_hub_ep1(struct ast_vhub *vhub, + unsigned int port) +{ + /* Update HW EP1 response */ + u32 reg = readl(vhub->regs + AST_VHUB_EP1_STS_CHG); + u32 pmask = (1 << (port + 1)); + if (vhub->ports[port].change) + reg |= pmask; + else + reg &= ~pmask; + writel(reg, vhub->regs + AST_VHUB_EP1_STS_CHG); +} + +static void ast_vhub_change_port_stat(struct ast_vhub *vhub, + unsigned int port, + u16 clr_flags, + u16 set_flags, + bool set_c) +{ + struct ast_vhub_port *p = &vhub->ports[port]; + u16 prev; + + /* Update port status */ + prev = p->status; + p->status = (prev & ~clr_flags) | set_flags; + DDBG(&p->dev, "port %d status %04x -> %04x (C=%d)\n", + port + 1, prev, p->status, set_c); + + /* Update change bits if needed */ + if (set_c) { + u16 chg = p->status ^ prev; + + /* Only these are relevant for change */ + chg &= USB_PORT_STAT_C_CONNECTION | + USB_PORT_STAT_C_ENABLE | + USB_PORT_STAT_C_SUSPEND | + USB_PORT_STAT_C_OVERCURRENT | + USB_PORT_STAT_C_RESET | + USB_PORT_STAT_C_L1; + p->change |= chg; + + ast_vhub_update_hub_ep1(vhub, port); + } +} + +static void ast_vhub_send_host_wakeup(struct ast_vhub *vhub) +{ + u32 reg = readl(vhub->regs + AST_VHUB_CTRL); + UDCDBG(vhub, "Waking up host !\n"); + reg |= VHUB_CTRL_MANUAL_REMOTE_WAKEUP; + writel(reg, vhub->regs + AST_VHUB_CTRL); +} + +void ast_vhub_device_connect(struct ast_vhub *vhub, + unsigned int port, bool on) +{ + if (on) + ast_vhub_change_port_stat(vhub, port, 0, + USB_PORT_STAT_CONNECTION, true); + else + ast_vhub_change_port_stat(vhub, port, + USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_ENABLE, + 0, true); + + /* + * If the hub is set to wakup the host on connection events + * then send a wakeup. + */ + if (vhub->wakeup_en) + ast_vhub_send_host_wakeup(vhub); +} + +static void ast_vhub_wake_work(struct work_struct *work) +{ + struct ast_vhub *vhub = container_of(work, + struct ast_vhub, + wake_work); + unsigned long flags; + unsigned int i; + + /* + * Wake all sleeping ports. If a port is suspended by + * the host suspend (without explicit state suspend), + * we let the normal host wake path deal with it later. + */ + spin_lock_irqsave(&vhub->lock, flags); + for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + struct ast_vhub_port *p = &vhub->ports[i]; + + if (!(p->status & USB_PORT_STAT_SUSPEND)) + continue; + ast_vhub_change_port_stat(vhub, i, + USB_PORT_STAT_SUSPEND, + 0, true); + ast_vhub_dev_resume(&p->dev); + } + ast_vhub_send_host_wakeup(vhub); + spin_unlock_irqrestore(&vhub->lock, flags); +} + +void ast_vhub_hub_wake_all(struct ast_vhub *vhub) +{ + /* + * A device is trying to wake the world, because this + * can recurse into the device, we break the call chain + * using a work queue + */ + schedule_work(&vhub->wake_work); +} + +static void ast_vhub_port_reset(struct ast_vhub *vhub, u8 port) +{ + struct ast_vhub_port *p = &vhub->ports[port]; + u16 set, clr, speed; + + /* First mark disabled */ + ast_vhub_change_port_stat(vhub, port, + USB_PORT_STAT_ENABLE | + USB_PORT_STAT_SUSPEND, + USB_PORT_STAT_RESET, + false); + + if (!p->dev.driver) + return; + + /* + * This will either "start" the port or reset the + * device if already started... + */ + ast_vhub_dev_reset(&p->dev); + + /* Grab the right speed */ + speed = p->dev.driver->max_speed; + if (speed == USB_SPEED_UNKNOWN || speed > vhub->speed) + speed = vhub->speed; + + switch (speed) { + case USB_SPEED_LOW: + set = USB_PORT_STAT_LOW_SPEED; + clr = USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_FULL: + set = 0; + clr = USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_HIGH: + set = USB_PORT_STAT_HIGH_SPEED; + clr = USB_PORT_STAT_LOW_SPEED; + break; + default: + UDCDBG(vhub, "Unsupported speed %d when" + " connecting device\n", + speed); + return; + } + clr |= USB_PORT_STAT_RESET; + set |= USB_PORT_STAT_ENABLE; + + /* This should ideally be delayed ... */ + ast_vhub_change_port_stat(vhub, port, clr, set, true); +} + +static enum std_req_rc ast_vhub_set_port_feature(struct ast_vhub_ep *ep, + u8 port, u16 feat) +{ + struct ast_vhub *vhub = ep->vhub; + struct ast_vhub_port *p; + + if (port == 0 || port > AST_VHUB_NUM_PORTS) + return std_req_stall; + port--; + p = &vhub->ports[port]; + + switch(feat) { + case USB_PORT_FEAT_SUSPEND: + if (!(p->status & USB_PORT_STAT_ENABLE)) + return std_req_complete; + ast_vhub_change_port_stat(vhub, port, + 0, USB_PORT_STAT_SUSPEND, + false); + ast_vhub_dev_suspend(&p->dev); + return std_req_complete; + case USB_PORT_FEAT_RESET: + EPDBG(ep, "Port reset !\n"); + ast_vhub_port_reset(vhub, port); + return std_req_complete; + case USB_PORT_FEAT_POWER: + /* + * On Power-on, we mark the connected flag changed, + * if there's a connected device, some hosts will + * otherwise fail to detect it. + */ + if (p->status & USB_PORT_STAT_CONNECTION) { + p->change |= USB_PORT_STAT_C_CONNECTION; + ast_vhub_update_hub_ep1(vhub, port); + } + return std_req_complete; + case USB_PORT_FEAT_TEST: + case USB_PORT_FEAT_INDICATOR: + /* We don't do anything with these */ + return std_req_complete; + } + return std_req_stall; +} + +static enum std_req_rc ast_vhub_clr_port_feature(struct ast_vhub_ep *ep, + u8 port, u16 feat) +{ + struct ast_vhub *vhub = ep->vhub; + struct ast_vhub_port *p; + + if (port == 0 || port > AST_VHUB_NUM_PORTS) + return std_req_stall; + port--; + p = &vhub->ports[port]; + + switch(feat) { + case USB_PORT_FEAT_ENABLE: + ast_vhub_change_port_stat(vhub, port, + USB_PORT_STAT_ENABLE | + USB_PORT_STAT_SUSPEND, 0, + false); + ast_vhub_dev_suspend(&p->dev); + return std_req_complete; + case USB_PORT_FEAT_SUSPEND: + if (!(p->status & USB_PORT_STAT_SUSPEND)) + return std_req_complete; + ast_vhub_change_port_stat(vhub, port, + USB_PORT_STAT_SUSPEND, 0, + false); + ast_vhub_dev_resume(&p->dev); + return std_req_complete; + case USB_PORT_FEAT_POWER: + /* We don't do power control */ + return std_req_complete; + case USB_PORT_FEAT_INDICATOR: + /* We don't have indicators */ + return std_req_complete; + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_RESET: + /* Clear state-change feature */ + p->change &= ~(1u << (feat - 16)); + ast_vhub_update_hub_ep1(vhub, port); + return std_req_complete; + } + return std_req_stall; +} + +static enum std_req_rc ast_vhub_get_port_stat(struct ast_vhub_ep *ep, + u8 port) +{ + struct ast_vhub *vhub = ep->vhub; + u16 stat, chg; + + if (port == 0 || port > AST_VHUB_NUM_PORTS) + return std_req_stall; + port--; + + stat = vhub->ports[port].status; + chg = vhub->ports[port].change; + + /* We always have power */ + stat |= USB_PORT_STAT_POWER; + + EPDBG(ep, " port status=%04x change=%04x\n", stat, chg); + + return ast_vhub_simple_reply(ep, + stat & 0xff, + stat >> 8, + chg & 0xff, + chg >> 8); +} + +enum std_req_rc ast_vhub_class_hub_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq) +{ + u16 wValue, wIndex, wLength; + + wValue = le16_to_cpu(crq->wValue); + wIndex = le16_to_cpu(crq->wIndex); + wLength = le16_to_cpu(crq->wLength); + + switch ((crq->bRequestType << 8) | crq->bRequest) { + case GetHubStatus: + EPDBG(ep, "GetHubStatus\n"); + return ast_vhub_simple_reply(ep, 0, 0, 0, 0); + case GetPortStatus: + EPDBG(ep, "GetPortStatus(%d)\n", wIndex & 0xff); + return ast_vhub_get_port_stat(ep, wIndex & 0xf); + case GetHubDescriptor: + if (wValue != (USB_DT_HUB << 8)) + return std_req_stall; + EPDBG(ep, "GetHubDescriptor(%d)\n", wIndex & 0xff); + return ast_vhub_rep_desc(ep, USB_DT_HUB, wLength); + case SetHubFeature: + case ClearHubFeature: + EPDBG(ep, "Get/SetHubFeature(%d)\n", wValue); + /* No feature, just complete the requests */ + if (wValue == C_HUB_LOCAL_POWER || + wValue == C_HUB_OVER_CURRENT) + return std_req_complete; + return std_req_stall; + case SetPortFeature: + EPDBG(ep, "SetPortFeature(%d,%d)\n", wIndex & 0xf, wValue); + return ast_vhub_set_port_feature(ep, wIndex & 0xf, wValue); + case ClearPortFeature: + EPDBG(ep, "ClearPortFeature(%d,%d)\n", wIndex & 0xf, wValue); + return ast_vhub_clr_port_feature(ep, wIndex & 0xf, wValue); + default: + EPDBG(ep, "Unknown class request\n"); + } + return std_req_stall; +} + +void ast_vhub_hub_suspend(struct ast_vhub *vhub) +{ + unsigned int i; + + UDCDBG(vhub, "USB bus suspend\n"); + + if (vhub->suspended) + return; + + vhub->suspended = true; + + /* + * Forward to unsuspended ports without changing + * their connection status. + */ + for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + struct ast_vhub_port *p = &vhub->ports[i]; + + if (!(p->status & USB_PORT_STAT_SUSPEND)) + ast_vhub_dev_suspend(&p->dev); + } +} + +void ast_vhub_hub_resume(struct ast_vhub *vhub) +{ + unsigned int i; + + UDCDBG(vhub, "USB bus resume\n"); + + if (!vhub->suspended) + return; + + vhub->suspended = false; + + /* + * Forward to unsuspended ports without changing + * their connection status. + */ + for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + struct ast_vhub_port *p = &vhub->ports[i]; + + if (!(p->status & USB_PORT_STAT_SUSPEND)) + ast_vhub_dev_resume(&p->dev); + } +} + +void ast_vhub_hub_reset(struct ast_vhub *vhub) +{ + unsigned int i; + + UDCDBG(vhub, "USB bus reset\n"); + + /* + * Is the speed known ? If not we don't care, we aren't + * initialized yet and ports haven't been enabled. + */ + if (vhub->speed == USB_SPEED_UNKNOWN) + return; + + /* We aren't suspended anymore obviously */ + vhub->suspended = false; + + /* No speed set */ + vhub->speed = USB_SPEED_UNKNOWN; + + /* Wakeup not enabled anymore */ + vhub->wakeup_en = false; + + /* + * Clear all port status, disable gadgets and "suspend" + * them. They will be woken up by a port reset. + */ + for (i = 0; i < AST_VHUB_NUM_PORTS; i++) { + struct ast_vhub_port *p = &vhub->ports[i]; + + /* Only keep the connected flag */ + p->status &= USB_PORT_STAT_CONNECTION; + p->change = 0; + + /* Suspend the gadget if any */ + ast_vhub_dev_suspend(&p->dev); + } + + /* Cleanup HW */ + writel(0, vhub->regs + AST_VHUB_CONF); + writel(0, vhub->regs + AST_VHUB_EP0_CTRL); + writel(VHUB_EP1_CTRL_RESET_TOGGLE | + VHUB_EP1_CTRL_ENABLE, + vhub->regs + AST_VHUB_EP1_CTRL); + writel(0, vhub->regs + AST_VHUB_EP1_STS_CHG); +} + +void ast_vhub_init_hub(struct ast_vhub *vhub) +{ + vhub->speed = USB_SPEED_UNKNOWN; + INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); +} +
diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h new file mode 100644 index 0000000..4ed03d3 --- /dev/null +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h
@@ -0,0 +1,547 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#ifndef __ASPEED_VHUB_H +#define __ASPEED_VHUB_H + +/***************************** + * * + * VHUB register definitions * + * * + *****************************/ + +#define AST_VHUB_CTRL 0x00 /* Root Function Control & Status Register */ +#define AST_VHUB_CONF 0x04 /* Root Configuration Setting Register */ +#define AST_VHUB_IER 0x08 /* Interrupt Ctrl Register */ +#define AST_VHUB_ISR 0x0C /* Interrupt Status Register */ +#define AST_VHUB_EP_ACK_IER 0x10 /* Programmable Endpoint Pool ACK Interrupt Enable Register */ +#define AST_VHUB_EP_NACK_IER 0x14 /* Programmable Endpoint Pool NACK Interrupt Enable Register */ +#define AST_VHUB_EP_ACK_ISR 0x18 /* Programmable Endpoint Pool ACK Interrupt Status Register */ +#define AST_VHUB_EP_NACK_ISR 0x1C /* Programmable Endpoint Pool NACK Interrupt Status Register */ +#define AST_VHUB_SW_RESET 0x20 /* Device Controller Soft Reset Enable Register */ +#define AST_VHUB_USBSTS 0x24 /* USB Status Register */ +#define AST_VHUB_EP_TOGGLE 0x28 /* Programmable Endpoint Pool Data Toggle Value Set */ +#define AST_VHUB_ISO_FAIL_ACC 0x2C /* Isochronous Transaction Fail Accumulator */ +#define AST_VHUB_EP0_CTRL 0x30 /* Endpoint 0 Contrl/Status Register */ +#define AST_VHUB_EP0_DATA 0x34 /* Base Address of Endpoint 0 In/OUT Data Buffer Register */ +#define AST_VHUB_EP1_CTRL 0x38 /* Endpoint 1 Contrl/Status Register */ +#define AST_VHUB_EP1_STS_CHG 0x3C /* Endpoint 1 Status Change Bitmap Data */ +#define AST_VHUB_SETUP0 0x80 /* Root Device Setup Data Buffer0 */ +#define AST_VHUB_SETUP1 0x84 /* Root Device Setup Data Buffer1 */ + +/* Main control reg */ +#define VHUB_CTRL_PHY_CLK (1 << 31) +#define VHUB_CTRL_PHY_LOOP_TEST (1 << 25) +#define VHUB_CTRL_DN_PWN (1 << 24) +#define VHUB_CTRL_DP_PWN (1 << 23) +#define VHUB_CTRL_LONG_DESC (1 << 18) +#define VHUB_CTRL_ISO_RSP_CTRL (1 << 17) +#define VHUB_CTRL_SPLIT_IN (1 << 16) +#define VHUB_CTRL_LOOP_T_RESULT (1 << 15) +#define VHUB_CTRL_LOOP_T_STS (1 << 14) +#define VHUB_CTRL_PHY_BIST_RESULT (1 << 13) +#define VHUB_CTRL_PHY_BIST_CTRL (1 << 12) +#define VHUB_CTRL_PHY_RESET_DIS (1 << 11) +#define VHUB_CTRL_SET_TEST_MODE(x) ((x) << 8) +#define VHUB_CTRL_MANUAL_REMOTE_WAKEUP (1 << 4) +#define VHUB_CTRL_AUTO_REMOTE_WAKEUP (1 << 3) +#define VHUB_CTRL_CLK_STOP_SUSPEND (1 << 2) +#define VHUB_CTRL_FULL_SPEED_ONLY (1 << 1) +#define VHUB_CTRL_UPSTREAM_CONNECT (1 << 0) + +/* IER & ISR */ +#define VHUB_IRQ_USB_CMD_DEADLOCK (1 << 18) +#define VHUB_IRQ_EP_POOL_NAK (1 << 17) +#define VHUB_IRQ_EP_POOL_ACK_STALL (1 << 16) +#define VHUB_IRQ_DEVICE5 (1 << 13) +#define VHUB_IRQ_DEVICE4 (1 << 12) +#define VHUB_IRQ_DEVICE3 (1 << 11) +#define VHUB_IRQ_DEVICE2 (1 << 10) +#define VHUB_IRQ_DEVICE1 (1 << 9) +#define VHUB_IRQ_BUS_RESUME (1 << 8) +#define VHUB_IRQ_BUS_SUSPEND (1 << 7) +#define VHUB_IRQ_BUS_RESET (1 << 6) +#define VHUB_IRQ_HUB_EP1_IN_DATA_ACK (1 << 5) +#define VHUB_IRQ_HUB_EP0_IN_DATA_NAK (1 << 4) +#define VHUB_IRQ_HUB_EP0_IN_ACK_STALL (1 << 3) +#define VHUB_IRQ_HUB_EP0_OUT_NAK (1 << 2) +#define VHUB_IRQ_HUB_EP0_OUT_ACK_STALL (1 << 1) +#define VHUB_IRQ_HUB_EP0_SETUP (1 << 0) +#define VHUB_IRQ_ACK_ALL 0x1ff + +/* SW reset reg */ +#define VHUB_SW_RESET_EP_POOL (1 << 9) +#define VHUB_SW_RESET_DMA_CONTROLLER (1 << 8) +#define VHUB_SW_RESET_DEVICE5 (1 << 5) +#define VHUB_SW_RESET_DEVICE4 (1 << 4) +#define VHUB_SW_RESET_DEVICE3 (1 << 3) +#define VHUB_SW_RESET_DEVICE2 (1 << 2) +#define VHUB_SW_RESET_DEVICE1 (1 << 1) +#define VHUB_SW_RESET_ROOT_HUB (1 << 0) +#define VHUB_SW_RESET_ALL (VHUB_SW_RESET_EP_POOL | \ + VHUB_SW_RESET_DMA_CONTROLLER | \ + VHUB_SW_RESET_DEVICE5 | \ + VHUB_SW_RESET_DEVICE4 | \ + VHUB_SW_RESET_DEVICE3 | \ + VHUB_SW_RESET_DEVICE2 | \ + VHUB_SW_RESET_DEVICE1 | \ + VHUB_SW_RESET_ROOT_HUB) +/* EP ACK/NACK IRQ masks */ +#define VHUB_EP_IRQ(n) (1 << (n)) +#define VHUB_EP_IRQ_ALL 0x7fff /* 15 EPs */ + +/* USB status reg */ +#define VHUB_USBSTS_HISPEED (1 << 27) + +/* EP toggle */ +#define VHUB_EP_TOGGLE_VALUE (1 << 8) +#define VHUB_EP_TOGGLE_SET_EPNUM(x) ((x) & 0x1f) + +/* HUB EP0 control */ +#define VHUB_EP0_CTRL_STALL (1 << 0) +#define VHUB_EP0_TX_BUFF_RDY (1 << 1) +#define VHUB_EP0_RX_BUFF_RDY (1 << 2) +#define VHUB_EP0_RX_LEN(x) (((x) >> 16) & 0x7f) +#define VHUB_EP0_SET_TX_LEN(x) (((x) & 0x7f) << 8) + +/* HUB EP1 control */ +#define VHUB_EP1_CTRL_RESET_TOGGLE (1 << 2) +#define VHUB_EP1_CTRL_STALL (1 << 1) +#define VHUB_EP1_CTRL_ENABLE (1 << 0) + +/*********************************** + * * + * per-device register definitions * + * * + ***********************************/ +#define AST_VHUB_DEV_EN_CTRL 0x00 +#define AST_VHUB_DEV_ISR 0x04 +#define AST_VHUB_DEV_EP0_CTRL 0x08 +#define AST_VHUB_DEV_EP0_DATA 0x0c + +/* Device enable control */ +#define VHUB_DEV_EN_SET_ADDR(x) ((x) << 8) +#define VHUB_DEV_EN_ADDR_MASK ((0xff) << 8) +#define VHUB_DEV_EN_EP0_NAK_IRQEN (1 << 6) +#define VHUB_DEV_EN_EP0_IN_ACK_IRQEN (1 << 5) +#define VHUB_DEV_EN_EP0_OUT_NAK_IRQEN (1 << 4) +#define VHUB_DEV_EN_EP0_OUT_ACK_IRQEN (1 << 3) +#define VHUB_DEV_EN_EP0_SETUP_IRQEN (1 << 2) +#define VHUB_DEV_EN_SPEED_SEL_HIGH (1 << 1) +#define VHUB_DEV_EN_ENABLE_PORT (1 << 0) + +/* Interrupt status */ +#define VHUV_DEV_IRQ_EP0_IN_DATA_NACK (1 << 4) +#define VHUV_DEV_IRQ_EP0_IN_ACK_STALL (1 << 3) +#define VHUV_DEV_IRQ_EP0_OUT_DATA_NACK (1 << 2) +#define VHUV_DEV_IRQ_EP0_OUT_ACK_STALL (1 << 1) +#define VHUV_DEV_IRQ_EP0_SETUP (1 << 0) + +/* Control bits. + * + * Note: The driver relies on the bulk of those bits + * matching corresponding vHub EP0 control bits + */ +#define VHUB_DEV_EP0_CTRL_STALL VHUB_EP0_CTRL_STALL +#define VHUB_DEV_EP0_TX_BUFF_RDY VHUB_EP0_TX_BUFF_RDY +#define VHUB_DEV_EP0_RX_BUFF_RDY VHUB_EP0_RX_BUFF_RDY +#define VHUB_DEV_EP0_RX_LEN(x) VHUB_EP0_RX_LEN(x) +#define VHUB_DEV_EP0_SET_TX_LEN(x) VHUB_EP0_SET_TX_LEN(x) + +/************************************* + * * + * per-endpoint register definitions * + * * + *************************************/ + +#define AST_VHUB_EP_CONFIG 0x00 +#define AST_VHUB_EP_DMA_CTLSTAT 0x04 +#define AST_VHUB_EP_DESC_BASE 0x08 +#define AST_VHUB_EP_DESC_STATUS 0x0C + +/* EP config reg */ +#define VHUB_EP_CFG_SET_MAX_PKT(x) (((x) & 0x3ff) << 16) +#define VHUB_EP_CFG_AUTO_DATA_DISABLE (1 << 13) +#define VHUB_EP_CFG_STALL_CTRL (1 << 12) +#define VHUB_EP_CFG_SET_EP_NUM(x) (((x) & 0xf) << 8) +#define VHUB_EP_CFG_SET_TYPE(x) ((x) << 5) +#define EP_TYPE_OFF 0 +#define EP_TYPE_BULK 1 +#define EP_TYPE_INT 2 +#define EP_TYPE_ISO 3 +#define VHUB_EP_CFG_DIR_OUT (1 << 4) +#define VHUB_EP_CFG_SET_DEV(x) ((x) << 1) +#define VHUB_EP_CFG_ENABLE (1 << 0) + +/* EP DMA control */ +#define VHUB_EP_DMA_PROC_STATUS(x) (((x) >> 4) & 0xf) +#define EP_DMA_PROC_RX_IDLE 0 +#define EP_DMA_PROC_TX_IDLE 8 +#define VHUB_EP_DMA_IN_LONG_MODE (1 << 3) +#define VHUB_EP_DMA_OUT_CONTIG_MODE (1 << 3) +#define VHUB_EP_DMA_CTRL_RESET (1 << 2) +#define VHUB_EP_DMA_SINGLE_STAGE (1 << 1) +#define VHUB_EP_DMA_DESC_MODE (1 << 0) + +/* EP DMA status */ +#define VHUB_EP_DMA_SET_TX_SIZE(x) ((x) << 16) +#define VHUB_EP_DMA_TX_SIZE(x) (((x) >> 16) & 0x7ff) +#define VHUB_EP_DMA_RPTR(x) (((x) >> 8) & 0xff) +#define VHUB_EP_DMA_SET_RPTR(x) (((x) & 0xff) << 8) +#define VHUB_EP_DMA_SET_CPU_WPTR(x) (x) +#define VHUB_EP_DMA_SINGLE_KICK (1 << 0) /* WPTR = 1 for single mode */ + +/******************************* + * * + * DMA descriptors definitions * + * * + *******************************/ + +/* Desc W1 IN */ +#define VHUB_DSC1_IN_INTERRUPT (1 << 31) +#define VHUB_DSC1_IN_SPID_DATA0 (0 << 14) +#define VHUB_DSC1_IN_SPID_DATA2 (1 << 14) +#define VHUB_DSC1_IN_SPID_DATA1 (2 << 14) +#define VHUB_DSC1_IN_SPID_MDATA (3 << 14) +#define VHUB_DSC1_IN_SET_LEN(x) ((x) & 0xfff) +#define VHUB_DSC1_IN_LEN(x) ((x) & 0xfff) + +/**************************************** + * * + * Data structures and misc definitions * + * * + ****************************************/ + +#define AST_VHUB_NUM_GEN_EPs 15 /* Generic non-0 EPs */ +#define AST_VHUB_NUM_PORTS 5 /* vHub ports */ +#define AST_VHUB_EP0_MAX_PACKET 64 /* EP0's max packet size */ +#define AST_VHUB_EPn_MAX_PACKET 1024 /* Generic EPs max packet size */ +#define AST_VHUB_DESCS_COUNT 256 /* Use 256 descriptor mode (valid + * values are 256 and 32) + */ + +struct ast_vhub; +struct ast_vhub_dev; + +/* + * DMA descriptor (generic EPs only, currently only used + * for IN endpoints + */ +struct ast_vhub_desc { + __le32 w0; + __le32 w1; +}; + +/* A transfer request, either core-originated or internal */ +struct ast_vhub_req { + struct usb_request req; + struct list_head queue; + + /* Actual count written to descriptors (desc mode only) */ + unsigned int act_count; + + /* + * Desc number of the final packet or -1. For non-desc + * mode (or ep0), any >= 0 value means "last packet" + */ + int last_desc; + + /* Request active (pending DMAs) */ + bool active : 1; + + /* Internal request (don't call back core) */ + bool internal : 1; +}; +#define to_ast_req(__ureq) container_of(__ureq, struct ast_vhub_req, req) + +/* Current state of an EP0 */ +enum ep0_state { + ep0_state_token, + ep0_state_data, + ep0_state_status, +}; + +/* + * An endpoint, either generic, ep0, actual gadget EP + * or internal use vhub EP0. vhub EP1 doesn't have an + * associated structure as it's mostly HW managed. + */ +struct ast_vhub_ep { + struct usb_ep ep; + + /* Request queue */ + struct list_head queue; + + /* EP index in the device, 0 means this is an EP0 */ + unsigned int d_idx; + + /* Dev pointer or NULL for vHub EP0 */ + struct ast_vhub_dev *dev; + + /* vHub itself */ + struct ast_vhub *vhub; + + /* + * DMA buffer for EP0, fallback DMA buffer for misaligned + * OUT transfers for generic EPs + */ + void *buf; + dma_addr_t buf_dma; + + /* The rest depends on the EP type */ + union { + /* EP0 (either device or vhub) */ + struct { + /* + * EP0 registers are "similar" for + * vHub and devices but located in + * different places. + */ + void __iomem *ctlstat; + void __iomem *setup; + + /* Current state & direction */ + enum ep0_state state; + bool dir_in; + + /* Internal use request */ + struct ast_vhub_req req; + } ep0; + + /* Generic endpoint (aka EPn) */ + struct { + /* Registers */ + void __iomem *regs; + + /* Index in global pool (0..14) */ + unsigned int g_idx; + + /* DMA Descriptors */ + struct ast_vhub_desc *descs; + dma_addr_t descs_dma; + unsigned int d_next; + unsigned int d_last; + unsigned int dma_conf; + + /* Max chunk size for IN EPs */ + unsigned int chunk_max; + + /* State flags */ + bool is_in : 1; + bool is_iso : 1; + bool stalled : 1; + bool wedged : 1; + bool enabled : 1; + bool desc_mode : 1; + } epn; + }; +}; +#define to_ast_ep(__uep) container_of(__uep, struct ast_vhub_ep, ep) + +/* A device attached to a vHub port */ +struct ast_vhub_dev { + struct ast_vhub *vhub; + void __iomem *regs; + + /* Device index (0...4) and name string */ + unsigned int index; + const char *name; + + /* sysfs enclosure for the gadget gunk */ + struct device *port_dev; + + /* Link to gadget core */ + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + bool registered : 1; + bool wakeup_en : 1; + bool suspended : 1; + bool enabled : 1; + + /* Endpoint structures */ + struct ast_vhub_ep ep0; + struct ast_vhub_ep *epns[AST_VHUB_NUM_GEN_EPs]; + +}; +#define to_ast_dev(__g) container_of(__g, struct ast_vhub_dev, gadget) + +/* Per vhub port stateinfo structure */ +struct ast_vhub_port { + /* Port status & status change registers */ + u16 status; + u16 change; + + /* Associated device slot */ + struct ast_vhub_dev dev; +}; + +/* Global vhub structure */ +struct ast_vhub { + struct platform_device *pdev; + void __iomem *regs; + int irq; + spinlock_t lock; + struct work_struct wake_work; + struct clk *clk; + + /* EP0 DMA buffers allocated in one chunk */ + void *ep0_bufs; + dma_addr_t ep0_bufs_dma; + + /* EP0 of the vhub itself */ + struct ast_vhub_ep ep0; + + /* State of vhub ep1 */ + bool ep1_stalled : 1; + + /* Per-port info */ + struct ast_vhub_port ports[AST_VHUB_NUM_PORTS]; + + /* Generic EP data structures */ + struct ast_vhub_ep epns[AST_VHUB_NUM_GEN_EPs]; + + /* Upstream bus is suspended ? */ + bool suspended : 1; + + /* Hub itself can signal remote wakeup */ + bool wakeup_en : 1; + + /* Force full speed only */ + bool force_usb1 : 1; + + /* Upstream bus speed captured at bus reset */ + unsigned int speed; +}; + +/* Standard request handlers result codes */ +enum std_req_rc { + std_req_stall = -1, /* Stall requested */ + std_req_complete = 0, /* Request completed with no data */ + std_req_data = 1, /* Request completed with data */ + std_req_driver = 2, /* Pass to driver pls */ +}; + +#ifdef CONFIG_USB_GADGET_VERBOSE +#define UDCVDBG(u, fmt...) dev_dbg(&(u)->pdev->dev, fmt) + +#define EPVDBG(ep, fmt, ...) do { \ + dev_dbg(&(ep)->vhub->pdev->dev, \ + "%s:EP%d " fmt, \ + (ep)->dev ? (ep)->dev->name : "hub", \ + (ep)->d_idx, ##__VA_ARGS__); \ + } while(0) + +#define DVDBG(d, fmt, ...) do { \ + dev_dbg(&(d)->vhub->pdev->dev, \ + "%s " fmt, (d)->name, \ + ##__VA_ARGS__); \ + } while(0) + +#else +#define UDCVDBG(u, fmt...) do { } while(0) +#define EPVDBG(ep, fmt, ...) do { } while(0) +#define DVDBG(d, fmt, ...) do { } while(0) +#endif + +#ifdef CONFIG_USB_GADGET_DEBUG +#define UDCDBG(u, fmt...) dev_dbg(&(u)->pdev->dev, fmt) + +#define EPDBG(ep, fmt, ...) do { \ + dev_dbg(&(ep)->vhub->pdev->dev, \ + "%s:EP%d " fmt, \ + (ep)->dev ? (ep)->dev->name : "hub", \ + (ep)->d_idx, ##__VA_ARGS__); \ + } while(0) + +#define DDBG(d, fmt, ...) do { \ + dev_dbg(&(d)->vhub->pdev->dev, \ + "%s " fmt, (d)->name, \ + ##__VA_ARGS__); \ + } while(0) +#else +#define UDCDBG(u, fmt...) do { } while(0) +#define EPDBG(ep, fmt, ...) do { } while(0) +#define DDBG(d, fmt, ...) do { } while(0) +#endif + +static inline void vhub_dma_workaround(void *addr) +{ + /* + * This works around a confirmed HW issue with the Aspeed chip. + * + * The core uses a different bus to memory than the AHB going to + * the USB device controller. Due to the latter having a higher + * priority than the core for arbitration on that bus, it's + * possible for an MMIO to the device, followed by a DMA by the + * device from memory to all be performed and services before + * a previous store to memory gets completed. + * + * This the following scenario can happen: + * + * - Driver writes to a DMA descriptor (Mbus) + * - Driver writes to the MMIO register to start the DMA (AHB) + * - The gadget sees the second write and sends a read of the + * descriptor to the memory controller (Mbus) + * - The gadget hits memory before the descriptor write + * causing it to read an obsolete value. + * + * Thankfully the problem is limited to the USB gadget device, other + * masters in the SoC all have a lower priority than the core, thus + * ensuring that the store by the core arrives first. + * + * The workaround consists of using a dummy read of the memory before + * doing the MMIO writes. This will ensure that the previous writes + * have been "pushed out". + */ + mb(); + (void)__raw_readl((void __iomem *)addr); +} + +/* core.c */ +void ast_vhub_done(struct ast_vhub_ep *ep, struct ast_vhub_req *req, + int status); +void ast_vhub_nuke(struct ast_vhub_ep *ep, int status); +struct usb_request *ast_vhub_alloc_request(struct usb_ep *u_ep, + gfp_t gfp_flags); +void ast_vhub_free_request(struct usb_ep *u_ep, struct usb_request *u_req); +void ast_vhub_init_hw(struct ast_vhub *vhub); + +/* ep0.c */ +void ast_vhub_ep0_handle_ack(struct ast_vhub_ep *ep, bool in_ack); +void ast_vhub_ep0_handle_setup(struct ast_vhub_ep *ep); +void ast_vhub_init_ep0(struct ast_vhub *vhub, struct ast_vhub_ep *ep, + struct ast_vhub_dev *dev); +int ast_vhub_reply(struct ast_vhub_ep *ep, char *ptr, int len); +int __ast_vhub_simple_reply(struct ast_vhub_ep *ep, int len, ...); +#define ast_vhub_simple_reply(udc, ...) \ + __ast_vhub_simple_reply((udc), \ + sizeof((u8[]) { __VA_ARGS__ })/sizeof(u8), \ + __VA_ARGS__) + +/* hub.c */ +void ast_vhub_init_hub(struct ast_vhub *vhub); +enum std_req_rc ast_vhub_std_hub_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq); +enum std_req_rc ast_vhub_class_hub_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq); +void ast_vhub_device_connect(struct ast_vhub *vhub, unsigned int port, + bool on); +void ast_vhub_hub_suspend(struct ast_vhub *vhub); +void ast_vhub_hub_resume(struct ast_vhub *vhub); +void ast_vhub_hub_reset(struct ast_vhub *vhub); +void ast_vhub_hub_wake_all(struct ast_vhub *vhub); + +/* dev.c */ +int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); +void ast_vhub_del_dev(struct ast_vhub_dev *d); +void ast_vhub_dev_irq(struct ast_vhub_dev *d); +int ast_vhub_std_dev_request(struct ast_vhub_ep *ep, + struct usb_ctrlrequest *crq); + +/* epn.c */ +void ast_vhub_epn_ack_irq(struct ast_vhub_ep *ep); +void ast_vhub_update_epn_stall(struct ast_vhub_ep *ep); +struct ast_vhub_ep *ast_vhub_alloc_epn(struct ast_vhub_dev *d, u8 addr); +void ast_vhub_dev_suspend(struct ast_vhub_dev *d); +void ast_vhub_dev_resume(struct ast_vhub_dev *d); +void ast_vhub_dev_reset(struct ast_vhub_dev *d); + +#endif /* __ASPEED_VHUB_H */
diff --git a/drivers/usb/gadget/usbstring.c b/drivers/usb/gadget/usbstring.c index 566ab26..7c24d1c 100644 --- a/drivers/usb/gadget/usbstring.c +++ b/drivers/usb/gadget/usbstring.c
@@ -33,7 +33,7 @@ * characters (which are also widely used in C strings). */ int -usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf) +usb_gadget_get_string (const struct usb_gadget_strings *table, int id, u8 *buf) { struct usb_string *s; int len;
diff --git a/include/dt-bindings/clock/aspeed-clock.h b/include/dt-bindings/clock/aspeed-clock.h index 8d69b91..4476184 100644 --- a/include/dt-bindings/clock/aspeed-clock.h +++ b/include/dt-bindings/clock/aspeed-clock.h
@@ -38,6 +38,7 @@ #define ASPEED_CLK_MAC 32 #define ASPEED_CLK_BCLK 33 #define ASPEED_CLK_MPLL 34 +#define ASPEED_CLK_24M 35 #define ASPEED_RESET_XDMA 0 #define ASPEED_RESET_MCTP 1 @@ -48,5 +49,6 @@ #define ASPEED_RESET_PECI 6 #define ASPEED_RESET_I2C 7 #define ASPEED_RESET_AHB 8 +#define ASPEED_RESET_CRT1 9 #endif
diff --git a/include/dt-bindings/clock/nuvoton,npcm7xx-clock.h b/include/dt-bindings/clock/nuvoton,npcm7xx-clock.h new file mode 100644 index 0000000..f215226 --- /dev/null +++ b/include/dt-bindings/clock/nuvoton,npcm7xx-clock.h
@@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Nuvoton NPCM7xx Clock Generator binding + * clock binding number for all clocks supportted by nuvoton,npcm7xx-clk + * + * Copyright (C) 2018 Nuvoton Technologies tali.perry@nuvoton.com + * + */ + +#ifndef __DT_BINDINGS_CLOCK_NPCM7XX_H +#define __DT_BINDINGS_CLOCK_NPCM7XX_H + + +#define NPCM7XX_CLK_CPU 0 +#define NPCM7XX_CLK_GFX_PIXEL 1 +#define NPCM7XX_CLK_MC 2 +#define NPCM7XX_CLK_ADC 3 +#define NPCM7XX_CLK_AHB 4 +#define NPCM7XX_CLK_TIMER 5 +#define NPCM7XX_CLK_UART 6 +#define NPCM7XX_CLK_MMC 7 +#define NPCM7XX_CLK_SPI3 8 +#define NPCM7XX_CLK_PCI 9 +#define NPCM7XX_CLK_AXI 10 +#define NPCM7XX_CLK_APB4 11 +#define NPCM7XX_CLK_APB3 12 +#define NPCM7XX_CLK_APB2 13 +#define NPCM7XX_CLK_APB1 14 +#define NPCM7XX_CLK_APB5 15 +#define NPCM7XX_CLK_CLKOUT 16 +#define NPCM7XX_CLK_GFX 17 +#define NPCM7XX_CLK_SU 18 +#define NPCM7XX_CLK_SU48 19 +#define NPCM7XX_CLK_SDHC 20 +#define NPCM7XX_CLK_SPI0 21 +#define NPCM7XX_CLK_SPIX 22 + +#define NPCM7XX_CLK_REFCLK 23 +#define NPCM7XX_CLK_SYSBYPCK 24 +#define NPCM7XX_CLK_MCBYPCK 25 + +#define NPCM7XX_NUM_CLOCKS (NPCM7XX_CLK_MCBYPCK+1) + +#endif
diff --git a/include/linux/compat.h b/include/linux/compat.h index 081281a..f85b6b6 100644 --- a/include/linux/compat.h +++ b/include/linux/compat.h
@@ -70,6 +70,9 @@ */ #ifndef COMPAT_SYSCALL_DEFINEx #define COMPAT_SYSCALL_DEFINEx(x, name, ...) \ + __diag_push(); \ + __diag_ignore(GCC, 8, "-Wattribute-alias", \ + "Type aliasing is used to sanitize syscall arguments");\ asmlinkage long compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \ asmlinkage long compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \ __attribute__((alias(__stringify(__se_compat_sys##name)))); \ @@ -78,8 +81,11 @@ asmlinkage long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \ asmlinkage long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ { \ - return __do_compat_sys##name(__MAP(x,__SC_DELOUSE,__VA_ARGS__));\ + long ret = __do_compat_sys##name(__MAP(x,__SC_DELOUSE,__VA_ARGS__));\ + __MAP(x,__SC_TEST,__VA_ARGS__); \ + return ret; \ } \ + __diag_pop(); \ static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) #endif /* COMPAT_SYSCALL_DEFINEx */
diff --git a/include/linux/compiler-clang.h b/include/linux/compiler-clang.h index 7d98e26..7087446c 100644 --- a/include/linux/compiler-clang.h +++ b/include/linux/compiler-clang.h
@@ -32,3 +32,17 @@ #ifdef __noretpoline #undef __noretpoline #endif + +/* + * Not all versions of clang implement the the type-generic versions + * of the builtin overflow checkers. Fortunately, clang implements + * __has_builtin allowing us to avoid awkward version + * checks. Unfortunately, we don't know which version of gcc clang + * pretends to be, so the macro may or may not be defined. + */ +#undef COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW +#if __has_builtin(__builtin_mul_overflow) && \ + __has_builtin(__builtin_add_overflow) && \ + __has_builtin(__builtin_sub_overflow) +#define COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW 1 +#endif
diff --git a/include/linux/compiler-gcc.h b/include/linux/compiler-gcc.h index f1fa516..573f5a7 100644 --- a/include/linux/compiler-gcc.h +++ b/include/linux/compiler-gcc.h
@@ -358,3 +358,32 @@ * code */ #define uninitialized_var(x) x = x + +#if GCC_VERSION >= 50100 +#define COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW 1 +#endif + +/* + * Turn individual warnings and errors on and off locally, depending + * on version. + */ +#define __diag_GCC(version, severity, s) \ + __diag_GCC_ ## version(__diag_GCC_ ## severity s) + +/* Severity used in pragma directives */ +#define __diag_GCC_ignore ignored +#define __diag_GCC_warn warning +#define __diag_GCC_error error + +/* Compilers before gcc-4.6 do not understand "#pragma GCC diagnostic push" */ +#if GCC_VERSION >= 40600 +#define __diag_str1(s) #s +#define __diag_str(s) __diag_str1(s) +#define __diag(s) _Pragma(__diag_str(GCC diagnostic s)) +#endif + +#if GCC_VERSION >= 80000 +#define __diag_GCC_8(s) __diag(s) +#else +#define __diag_GCC_8(s) +#endif
diff --git a/include/linux/compiler-intel.h b/include/linux/compiler-intel.h index bfa0816..547cdc9 100644 --- a/include/linux/compiler-intel.h +++ b/include/linux/compiler-intel.h
@@ -44,3 +44,7 @@ #define __builtin_bswap16 _bswap16 #endif +/* + * icc defines __GNUC__, but does not implement the builtin overflow checkers. + */ +#undef COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h index 6b79a9b..a8ba6b0 100644 --- a/include/linux/compiler_types.h +++ b/include/linux/compiler_types.h
@@ -271,4 +271,22 @@ struct ftrace_likely_data { # define __native_word(t) (sizeof(t) == sizeof(char) || sizeof(t) == sizeof(short) || sizeof(t) == sizeof(int) || sizeof(t) == sizeof(long)) #endif +#ifndef __diag +#define __diag(string) +#endif + +#ifndef __diag_GCC +#define __diag_GCC(version, severity, string) +#endif + +#define __diag_push() __diag(push) +#define __diag_pop() __diag(pop) + +#define __diag_ignore(compiler, version, option, comment) \ + __diag_ ## compiler(version, ignore, option) +#define __diag_warn(compiler, version, option, comment) \ + __diag_ ## compiler(version, warn, option) +#define __diag_error(compiler, version, option, comment) \ + __diag_ ## compiler(version, error, option) + #endif /* __LINUX_COMPILER_TYPES_H */
diff --git a/include/linux/device.h b/include/linux/device.h index 4779569..96249d79 100644 --- a/include/linux/device.h +++ b/include/linux/device.h
@@ -688,6 +688,10 @@ extern void devm_free_pages(struct device *dev, unsigned long addr); void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res); +void __iomem *devm_of_iomap(struct device *dev, + struct device_node *node, int index, + resource_size_t *size); + /* allows to add/remove a custom action to devres stack */ int devm_add_action(struct device *dev, void (*action)(void *), void *data); void devm_remove_action(struct device *dev, void (*action)(void *), void *data);
diff --git a/include/linux/fsi-occ.h b/include/linux/fsi-occ.h new file mode 100644 index 0000000..4810368 --- /dev/null +++ b/include/linux/fsi-occ.h
@@ -0,0 +1,36 @@ +/* + * Copyright (C) IBM Corporation 2017 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef LINUX_FSI_OCC_H +#define LINUX_FSI_OCC_H + +struct device; + +#define OCC_RESP_CMD_IN_PRG 0xFF +#define OCC_RESP_SUCCESS 0 +#define OCC_RESP_CMD_INVAL 0x11 +#define OCC_RESP_CMD_LEN_INVAL 0x12 +#define OCC_RESP_DATA_INVAL 0x13 +#define OCC_RESP_CHKSUM_ERR 0x14 +#define OCC_RESP_INT_ERR 0x15 +#define OCC_RESP_BAD_STATE 0x16 +#define OCC_RESP_CRIT_EXCEPT 0xE0 +#define OCC_RESP_CRIT_INIT 0xE1 +#define OCC_RESP_CRIT_WATCHDOG 0xE2 +#define OCC_RESP_CRIT_OCB 0xE3 +#define OCC_RESP_CRIT_HW 0xE4 + +extern int fsi_occ_submit(struct device *dev, const void *request, size_t req_len, + void *response, size_t *resp_len); + +#endif /* LINUX_FSI_OCC_H */
diff --git a/include/linux/fsi-sbefifo.h b/include/linux/fsi-sbefifo.h new file mode 100644 index 0000000..9f8dcfd --- /dev/null +++ b/include/linux/fsi-sbefifo.h
@@ -0,0 +1,36 @@ +/* + * SBEFIFO FSI Client device driver + * + * Copyright (C) IBM Corporation 2017 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef LINUX_FSI_SBEFIFO_H +#define LINUX_FSI_SBEFIFO_H + +#define SBEFIFO_CMD_PUT_OCC_SRAM 0xa404 +#define SBEFIFO_CMD_GET_OCC_SRAM 0xa403 +#define SBEFIFO_CMD_GET_SBE_FFDC 0xa801 + +#define SBEFIFO_MAX_FFDC_SIZE 0x2000 + +struct device; + +int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len, + __be32 *response, size_t *resp_len); + +int sbefifo_parse_status(struct device *dev, u16 cmd, __be32 *response, + size_t resp_len, size_t *data_len); + +struct fsi_device; +struct fsi_device *sbefifo_get_fsidev(struct device *dev); + +#endif /* LINUX_FSI_SBEFIFO_H */
diff --git a/include/linux/fsi.h b/include/linux/fsi.h index 141fd38..ec3be0d 100644 --- a/include/linux/fsi.h +++ b/include/linux/fsi.h
@@ -76,8 +76,18 @@ extern int fsi_slave_read(struct fsi_slave *slave, uint32_t addr, extern int fsi_slave_write(struct fsi_slave *slave, uint32_t addr, const void *val, size_t size); - - extern struct bus_type fsi_bus_type; +extern const struct device_type fsi_cdev_type; + +enum fsi_dev_type { + fsi_dev_cfam, + fsi_dev_sbefifo, + fsi_dev_scom, + fsi_dev_occ +}; + +extern int fsi_get_new_minor(struct fsi_device *fdev, enum fsi_dev_type type, + dev_t *out_dev, int *out_index); +extern void fsi_free_minor(dev_t dev); #endif /* LINUX_FSI_H */
diff --git a/include/linux/gpio/aspeed.h b/include/linux/gpio/aspeed.h new file mode 100644 index 0000000..1bfb3cd --- /dev/null +++ b/include/linux/gpio/aspeed.h
@@ -0,0 +1,15 @@ +#ifndef __GPIO_ASPEED_H +#define __GPIO_ASPEED_H + +struct aspeed_gpio_copro_ops { + int (*request_access)(void *data); + int (*release_access)(void *data); +}; + +int aspeed_gpio_copro_grab_gpio(struct gpio_desc *desc, + u16 *vreg_offset, u16 *dreg_offset, u8 *bit); +int aspeed_gpio_copro_release_gpio(struct gpio_desc *desc); +int aspeed_gpio_copro_set_ops(const struct aspeed_gpio_copro_ops *ops, void *data); + + +#endif /* __GPIO_ASPEED_H */
diff --git a/include/linux/overflow.h b/include/linux/overflow.h new file mode 100644 index 0000000..c8890ec --- /dev/null +++ b/include/linux/overflow.h
@@ -0,0 +1,205 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +#ifndef __LINUX_OVERFLOW_H +#define __LINUX_OVERFLOW_H + +#include <linux/compiler.h> + +/* + * In the fallback code below, we need to compute the minimum and + * maximum values representable in a given type. These macros may also + * be useful elsewhere, so we provide them outside the + * COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW block. + * + * It would seem more obvious to do something like + * + * #define type_min(T) (T)(is_signed_type(T) ? (T)1 << (8*sizeof(T)-1) : 0) + * #define type_max(T) (T)(is_signed_type(T) ? ((T)1 << (8*sizeof(T)-1)) - 1 : ~(T)0) + * + * Unfortunately, the middle expressions, strictly speaking, have + * undefined behaviour, and at least some versions of gcc warn about + * the type_max expression (but not if -fsanitize=undefined is in + * effect; in that case, the warning is deferred to runtime...). + * + * The slightly excessive casting in type_min is to make sure the + * macros also produce sensible values for the exotic type _Bool. [The + * overflow checkers only almost work for _Bool, but that's + * a-feature-not-a-bug, since people shouldn't be doing arithmetic on + * _Bools. Besides, the gcc builtins don't allow _Bool* as third + * argument.] + * + * Idea stolen from + * https://mail-index.netbsd.org/tech-misc/2007/02/05/0000.html - + * credit to Christian Biere. + */ +#define is_signed_type(type) (((type)(-1)) < (type)1) +#define __type_half_max(type) ((type)1 << (8*sizeof(type) - 1 - is_signed_type(type))) +#define type_max(T) ((T)((__type_half_max(T) - 1) + __type_half_max(T))) +#define type_min(T) ((T)((T)-type_max(T)-(T)1)) + + +#ifdef COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW +/* + * For simplicity and code hygiene, the fallback code below insists on + * a, b and *d having the same type (similar to the min() and max() + * macros), whereas gcc's type-generic overflow checkers accept + * different types. Hence we don't just make check_add_overflow an + * alias for __builtin_add_overflow, but add type checks similar to + * below. + */ +#define check_add_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + __builtin_add_overflow(__a, __b, __d); \ +}) + +#define check_sub_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + __builtin_sub_overflow(__a, __b, __d); \ +}) + +#define check_mul_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + __builtin_mul_overflow(__a, __b, __d); \ +}) + +#else + + +/* Checking for unsigned overflow is relatively easy without causing UB. */ +#define __unsigned_add_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + *__d = __a + __b; \ + *__d < __a; \ +}) +#define __unsigned_sub_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + *__d = __a - __b; \ + __a < __b; \ +}) +/* + * If one of a or b is a compile-time constant, this avoids a division. + */ +#define __unsigned_mul_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + *__d = __a * __b; \ + __builtin_constant_p(__b) ? \ + __b > 0 && __a > type_max(typeof(__a)) / __b : \ + __a > 0 && __b > type_max(typeof(__b)) / __a; \ +}) + +/* + * For signed types, detecting overflow is much harder, especially if + * we want to avoid UB. But the interface of these macros is such that + * we must provide a result in *d, and in fact we must produce the + * result promised by gcc's builtins, which is simply the possibly + * wrapped-around value. Fortunately, we can just formally do the + * operations in the widest relevant unsigned type (u64) and then + * truncate the result - gcc is smart enough to generate the same code + * with and without the (u64) casts. + */ + +/* + * Adding two signed integers can overflow only if they have the same + * sign, and overflow has happened iff the result has the opposite + * sign. + */ +#define __signed_add_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + *__d = (u64)__a + (u64)__b; \ + (((~(__a ^ __b)) & (*__d ^ __a)) \ + & type_min(typeof(__a))) != 0; \ +}) + +/* + * Subtraction is similar, except that overflow can now happen only + * when the signs are opposite. In this case, overflow has happened if + * the result has the opposite sign of a. + */ +#define __signed_sub_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + *__d = (u64)__a - (u64)__b; \ + ((((__a ^ __b)) & (*__d ^ __a)) \ + & type_min(typeof(__a))) != 0; \ +}) + +/* + * Signed multiplication is rather hard. gcc always follows C99, so + * division is truncated towards 0. This means that we can write the + * overflow check like this: + * + * (a > 0 && (b > MAX/a || b < MIN/a)) || + * (a < -1 && (b > MIN/a || b < MAX/a) || + * (a == -1 && b == MIN) + * + * The redundant casts of -1 are to silence an annoying -Wtype-limits + * (included in -Wextra) warning: When the type is u8 or u16, the + * __b_c_e in check_mul_overflow obviously selects + * __unsigned_mul_overflow, but unfortunately gcc still parses this + * code and warns about the limited range of __b. + */ + +#define __signed_mul_overflow(a, b, d) ({ \ + typeof(a) __a = (a); \ + typeof(b) __b = (b); \ + typeof(d) __d = (d); \ + typeof(a) __tmax = type_max(typeof(a)); \ + typeof(a) __tmin = type_min(typeof(a)); \ + (void) (&__a == &__b); \ + (void) (&__a == __d); \ + *__d = (u64)__a * (u64)__b; \ + (__b > 0 && (__a > __tmax/__b || __a < __tmin/__b)) || \ + (__b < (typeof(__b))-1 && (__a > __tmin/__b || __a < __tmax/__b)) || \ + (__b == (typeof(__b))-1 && __a == __tmin); \ +}) + + +#define check_add_overflow(a, b, d) \ + __builtin_choose_expr(is_signed_type(typeof(a)), \ + __signed_add_overflow(a, b, d), \ + __unsigned_add_overflow(a, b, d)) + +#define check_sub_overflow(a, b, d) \ + __builtin_choose_expr(is_signed_type(typeof(a)), \ + __signed_sub_overflow(a, b, d), \ + __unsigned_sub_overflow(a, b, d)) + +#define check_mul_overflow(a, b, d) \ + __builtin_choose_expr(is_signed_type(typeof(a)), \ + __signed_mul_overflow(a, b, d), \ + __unsigned_mul_overflow(a, b, d)) + + +#endif /* COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW */ + +#endif /* __LINUX_OVERFLOW_H */
diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h index a27ef5f..76b9db7 100644 --- a/include/linux/serial_8250.h +++ b/include/linux/serial_8250.h
@@ -163,6 +163,7 @@ extern void serial8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl); extern int fsl8250_handle_irq(struct uart_port *port); int serial8250_handle_irq(struct uart_port *port, unsigned int iir); unsigned char serial8250_rx_chars(struct uart_8250_port *up, unsigned char lsr); +void serial8250_read_char(struct uart_8250_port *up, unsigned char lsr); void serial8250_tx_chars(struct uart_8250_port *up); unsigned int serial8250_modem_status(struct uart_8250_port *up); void serial8250_init_port(struct uart_8250_port *up);
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 3361cc8..06ea4ee 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h
@@ -233,6 +233,7 @@ struct uart_port { #define UPSTAT_AUTORTS ((__force upstat_t) (1 << 2)) #define UPSTAT_AUTOCTS ((__force upstat_t) (1 << 3)) #define UPSTAT_AUTOXOFF ((__force upstat_t) (1 << 4)) +#define UPSTAT_SYNC_FIFO ((__force upstat_t) (1 << 5)) int hw_stopped; /* sw-assisted CTS flow state */ unsigned int mctrl; /* current modem ctrl settings */
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 70fcda1..8ac7ddd 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h
@@ -230,6 +230,9 @@ static inline int is_syscall_trace_event(struct trace_event_call *tp_event) */ #ifndef __SYSCALL_DEFINEx #define __SYSCALL_DEFINEx(x, name, ...) \ + __diag_push(); \ + __diag_ignore(GCC, 8, "-Wattribute-alias", \ + "Type aliasing is used to sanitize syscall arguments");\ asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \ __attribute__((alias(__stringify(__se_sys##name)))); \ ALLOW_ERROR_INJECTION(sys##name, ERRNO); \ @@ -242,6 +245,7 @@ static inline int is_syscall_trace_event(struct trace_event_call *tp_event) __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \ return ret; \ } \ + __diag_pop(); \ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) #endif /* __SYSCALL_DEFINEx */
diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 847f423..e5cd84a 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h
@@ -763,7 +763,7 @@ struct usb_gadget_string_container { }; /* put descriptor for string with that id into buf (buflen >= 256) */ -int usb_gadget_get_string(struct usb_gadget_strings *table, int id, u8 *buf); +int usb_gadget_get_string(const struct usb_gadget_strings *table, int id, u8 *buf); /*-------------------------------------------------------------------------*/
diff --git a/include/trace/events/fsi_master_ast_cf.h b/include/trace/events/fsi_master_ast_cf.h new file mode 100644 index 0000000..a0fdfa5 --- /dev/null +++ b/include/trace/events/fsi_master_ast_cf.h
@@ -0,0 +1,150 @@ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM fsi_master_ast_cf + +#if !defined(_TRACE_FSI_MASTER_ACF_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_FSI_MASTER_ACF_H + +#include <linux/tracepoint.h> + +TRACE_EVENT(fsi_master_acf_copro_command, + TP_PROTO(const struct fsi_master_acf *master, uint32_t op), + TP_ARGS(master, op), + TP_STRUCT__entry( + __field(int, master_idx) + __field(uint32_t, op) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->op = op; + ), + TP_printk("fsi-acf%d command %08x", + __entry->master_idx, __entry->op + ) +); + +TRACE_EVENT(fsi_master_acf_send_request, + TP_PROTO(const struct fsi_master_acf *master, const struct fsi_msg *cmd, u8 rbits), + TP_ARGS(master, cmd, rbits), + TP_STRUCT__entry( + __field(int, master_idx) + __field(uint64_t, msg) + __field(u8, bits) + __field(u8, rbits) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->msg = cmd->msg; + __entry->bits = cmd->bits; + __entry->rbits = rbits; + ), + TP_printk("fsi-acf%d cmd: %016llx/%d/%d", + __entry->master_idx, (unsigned long long)__entry->msg, + __entry->bits, __entry->rbits + ) +); + +TRACE_EVENT(fsi_master_acf_copro_response, + TP_PROTO(const struct fsi_master_acf *master, u8 rtag, u8 rcrc, __be32 rdata, bool crc_ok), + TP_ARGS(master, rtag, rcrc, rdata, crc_ok), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u8, rtag) + __field(u8, rcrc) + __field(u32, rdata) + __field(bool, crc_ok) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->rtag = rtag; + __entry->rcrc = rcrc; + __entry->rdata = be32_to_cpu(rdata); + __entry->crc_ok = crc_ok; + ), + TP_printk("fsi-acf%d rsp: tag=%04x crc=%04x data=%08x %c\n", + __entry->master_idx, __entry->rtag, __entry->rcrc, + __entry->rdata, __entry->crc_ok ? ' ' : '!' + ) +); + +TRACE_EVENT(fsi_master_acf_crc_rsp_error, + TP_PROTO(const struct fsi_master_acf *master, int retries), + TP_ARGS(master, retries), + TP_STRUCT__entry( + __field(int, master_idx) + __field(int, retries) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->retries = retries; + ), + TP_printk("fsi-acf%d CRC error in response retry %d", + __entry->master_idx, __entry->retries + ) +); + +TRACE_EVENT(fsi_master_acf_poll_response_busy, + TP_PROTO(const struct fsi_master_acf *master, int busy_count), + TP_ARGS(master, busy_count), + TP_STRUCT__entry( + __field(int, master_idx) + __field(int, busy_count) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->busy_count = busy_count; + ), + TP_printk("fsi-acf%d: device reported busy %d times", + __entry->master_idx, __entry->busy_count + ) +); + +TRACE_EVENT(fsi_master_acf_cmd_abs_addr, + TP_PROTO(const struct fsi_master_acf *master, u32 addr), + TP_ARGS(master, addr), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u32, addr) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->addr = addr; + ), + TP_printk("fsi-acf%d: Sending ABS_ADR %06x", + __entry->master_idx, __entry->addr + ) +); + +TRACE_EVENT(fsi_master_acf_cmd_rel_addr, + TP_PROTO(const struct fsi_master_acf *master, u32 rel_addr), + TP_ARGS(master, rel_addr), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u32, rel_addr) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->rel_addr = rel_addr; + ), + TP_printk("fsi-acf%d: Sending REL_ADR %03x", + __entry->master_idx, __entry->rel_addr + ) +); + +TRACE_EVENT(fsi_master_acf_cmd_same_addr, + TP_PROTO(const struct fsi_master_acf *master), + TP_ARGS(master), + TP_STRUCT__entry( + __field(int, master_idx) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + ), + TP_printk("fsi-acf%d: Sending SAME_ADR", + __entry->master_idx + ) +); + +#endif /* _TRACE_FSI_MASTER_ACF_H */ + +#include <trace/define_trace.h>
diff --git a/include/trace/events/fsi_master_gpio.h b/include/trace/events/fsi_master_gpio.h index f95cf32..70ef66e 100644 --- a/include/trace/events/fsi_master_gpio.h +++ b/include/trace/events/fsi_master_gpio.h
@@ -50,6 +50,22 @@ TRACE_EVENT(fsi_master_gpio_out, ) ); +TRACE_EVENT(fsi_master_gpio_clock_zeros, + TP_PROTO(const struct fsi_master_gpio *master, int clocks), + TP_ARGS(master, clocks), + TP_STRUCT__entry( + __field(int, master_idx) + __field(int, clocks) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->clocks = clocks; + ), + TP_printk("fsi-gpio%d clock %d zeros", + __entry->master_idx, __entry->clocks + ) +); + TRACE_EVENT(fsi_master_gpio_break, TP_PROTO(const struct fsi_master_gpio *master), TP_ARGS(master), @@ -64,6 +80,92 @@ TRACE_EVENT(fsi_master_gpio_break, ) ); +TRACE_EVENT(fsi_master_gpio_crc_cmd_error, + TP_PROTO(const struct fsi_master_gpio *master), + TP_ARGS(master), + TP_STRUCT__entry( + __field(int, master_idx) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + ), + TP_printk("fsi-gpio%d ----CRC command retry---", + __entry->master_idx + ) +); + +TRACE_EVENT(fsi_master_gpio_crc_rsp_error, + TP_PROTO(const struct fsi_master_gpio *master), + TP_ARGS(master), + TP_STRUCT__entry( + __field(int, master_idx) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + ), + TP_printk("fsi-gpio%d ----CRC response---", + __entry->master_idx + ) +); + +TRACE_EVENT(fsi_master_gpio_poll_response_busy, + TP_PROTO(const struct fsi_master_gpio *master, int busy), + TP_ARGS(master, busy), + TP_STRUCT__entry( + __field(int, master_idx) + __field(int, busy) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->busy = busy; + ), + TP_printk("fsi-gpio%d: device reported busy %d times", + __entry->master_idx, __entry->busy) +); + +TRACE_EVENT(fsi_master_gpio_cmd_abs_addr, + TP_PROTO(const struct fsi_master_gpio *master, u32 addr), + TP_ARGS(master, addr), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u32, addr) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->addr = addr; + ), + TP_printk("fsi-gpio%d: Sending ABS_ADR %06x", + __entry->master_idx, __entry->addr) +); + +TRACE_EVENT(fsi_master_gpio_cmd_rel_addr, + TP_PROTO(const struct fsi_master_gpio *master, u32 rel_addr), + TP_ARGS(master, rel_addr), + TP_STRUCT__entry( + __field(int, master_idx) + __field(u32, rel_addr) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + __entry->rel_addr = rel_addr; + ), + TP_printk("fsi-gpio%d: Sending REL_ADR %03x", + __entry->master_idx, __entry->rel_addr) +); + +TRACE_EVENT(fsi_master_gpio_cmd_same_addr, + TP_PROTO(const struct fsi_master_gpio *master), + TP_ARGS(master), + TP_STRUCT__entry( + __field(int, master_idx) + ), + TP_fast_assign( + __entry->master_idx = master->master.idx; + ), + TP_printk("fsi-gpio%d: Sending SAME_ADR", + __entry->master_idx) +); + #endif /* _TRACE_FSI_MASTER_GPIO_H */ #include <trace/define_trace.h>
diff --git a/include/uapi/linux/fsi.h b/include/uapi/linux/fsi.h new file mode 100644 index 0000000..645045d --- /dev/null +++ b/include/uapi/linux/fsi.h
@@ -0,0 +1,57 @@ +#ifndef _UAPI_LINUX_FSI_H +#define _UAPI_LINUX_FSI_H + +#include <linux/ioctl.h> +#include <linux/types.h> + +/* + * /dev/scom "raw" ioctl interface + * + * The driver supports a high level "read/write" interface which + * handles retries and converts the status to Linux error codes, + * however low level tools an debugger need to access the "raw" + * HW status information and interpret it themselves, so this + * ioctl interface is also provided for their use case. + */ + +/* Structure for SCOM read/write */ +struct scom_access { + __u64 addr; /* SCOM address, supports indirect */ + __u64 data; /* SCOM data (in for write, out for read) */ + __u64 mask; /* Data mask for writes */ + __u32 intf_errors; /* Interface error flags */ +#define SCOM_INTF_ERR_PARITY 0x00000001 /* Parity error */ +#define SCOM_INTF_ERR_PROTECTION 0x00000002 /* Blocked by secure boot */ +#define SCOM_INTF_ERR_ABORT 0x00000004 /* PIB reset during access */ +#define SCOM_INTF_ERR_UNKNOWN 0x80000000 /* Unknown error */ + /* + * Note: Any other bit set in intf_errors need to be considered as an + * error. Future implementations may define new error conditions. The + * pib_status below is only valid if intf_errors is 0. + */ + __u8 pib_status; /* 3-bit PIB status */ +#define SCOM_PIB_SUCCESS 0 /* Access successful */ +#define SCOM_PIB_BLOCKED 1 /* PIB blocked, pls retry */ +#define SCOM_PIB_OFFLINE 2 /* Chiplet offline */ +#define SCOM_PIB_PARTIAL 3 /* Partial good */ +#define SCOM_PIB_BAD_ADDR 4 /* Invalid address */ +#define SCOM_PIB_CLK_ERR 5 /* Clock error */ +#define SCOM_PIB_PARITY_ERR 6 /* Parity error on the PIB bus */ +#define SCOM_PIB_TIMEOUT 7 /* Bus timeout */ + __u8 pad; +}; + +/* Flags for SCOM check */ +#define SCOM_CHECK_SUPPORTED 0x00000001 /* Interface supported */ +#define SCOM_CHECK_PROTECTED 0x00000002 /* Interface blocked by secure boot */ + +/* Flags for SCOM reset */ +#define SCOM_RESET_INTF 0x00000001 /* Reset interface */ +#define SCOM_RESET_PIB 0x00000002 /* Reset PIB */ + +#define FSI_SCOM_CHECK _IOR('s', 0x00, __u32) +#define FSI_SCOM_READ _IOWR('s', 0x01, struct scom_access) +#define FSI_SCOM_WRITE _IOWR('s', 0x02, struct scom_access) +#define FSI_SCOM_RESET _IOW('s', 0x03, __u32) + +#endif /* _UAPI_LINUX_FSI_H */
diff --git a/lib/devres.c b/lib/devres.c index 5bec112..faccf1a 100644 --- a/lib/devres.c +++ b/lib/devres.c
@@ -4,6 +4,7 @@ #include <linux/io.h> #include <linux/gfp.h> #include <linux/export.h> +#include <linux/of_address.h> enum devm_ioremap_type { DEVM_IOREMAP = 0, @@ -162,6 +163,41 @@ void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res) } EXPORT_SYMBOL(devm_ioremap_resource); +/* + * devm_of_iomap - Requests a resource and maps the memory mapped IO + * for a given device_node managed by a given device + * + * Checks that a resource is a valid memory region, requests the memory + * region and ioremaps it. All operations are managed and will be undone + * on driver detach of the device. + * + * This is to be used when a device requests/maps resources described + * by other device tree nodes (children or otherwise). + * + * @dev: The device "managing" the resource + * @node: The device-tree node where the resource resides + * @index: index of the MMIO range in the "reg" property + * @size: Returns the size of the resource (pass NULL if not needed) + * Returns a pointer to the requested and mapped memory or an ERR_PTR() encoded + * error code on failure. Usage example: + * + * base = devm_of_iomap(&pdev->dev, node, 0, NULL); + * if (IS_ERR(base)) + * return PTR_ERR(base); + */ +void __iomem *devm_of_iomap(struct device *dev, struct device_node *node, int index, + resource_size_t *size) +{ + struct resource res; + + if (of_address_to_resource(node, index, &res)) + return IOMEM_ERR_PTR(-EINVAL); + if (size) + *size = resource_size(&res); + return devm_ioremap_resource(dev, &res); +} +EXPORT_SYMBOL(devm_of_iomap); + #ifdef CONFIG_HAS_IOPORT_MAP /* * Generic iomap devres
diff --git a/net/core/devlink.c b/net/core/devlink.c index ad13173..bc98007 100644 --- a/net/core/devlink.c +++ b/net/core/devlink.c
@@ -1807,7 +1807,6 @@ static int devlink_dpipe_tables_fill(struct genl_info *info, nla_put_failure: err = -EMSGSIZE; err_table_put: - genlmsg_cancel(skb, hdr); nlmsg_free(skb); return err; } @@ -2013,7 +2012,6 @@ int devlink_dpipe_entry_ctx_prepare(struct devlink_dpipe_dump_ctx *dump_ctx) return 0; nla_put_failure: - genlmsg_cancel(dump_ctx->skb, dump_ctx->hdr); nlmsg_free(dump_ctx->skb); return -EMSGSIZE; } @@ -2230,7 +2228,6 @@ static int devlink_dpipe_headers_fill(struct genl_info *info, nla_put_failure: err = -EMSGSIZE; err_table_put: - genlmsg_cancel(skb, hdr); nlmsg_free(skb); return err; } @@ -2532,7 +2529,6 @@ static int devlink_resource_fill(struct genl_info *info, err = -EMSGSIZE; err_resource_put: err_skb_send_alloc: - genlmsg_cancel(skb, hdr); nlmsg_free(skb); return err; }
diff --git a/net/ipv6/seg6.c b/net/ipv6/seg6.c index 7f5621d..0fdf2a5 100644 --- a/net/ipv6/seg6.c +++ b/net/ipv6/seg6.c
@@ -226,7 +226,6 @@ static int seg6_genl_get_tunsrc(struct sk_buff *skb, struct genl_info *info) nla_put_failure: rcu_read_unlock(); - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -ENOMEM;
diff --git a/net/ncsi/internal.h b/net/ncsi/internal.h index 8da8431..8055e39 100644 --- a/net/ncsi/internal.h +++ b/net/ncsi/internal.h
@@ -68,15 +68,6 @@ enum { NCSI_MODE_MAX }; -enum { - NCSI_FILTER_BASE = 0, - NCSI_FILTER_VLAN = 0, - NCSI_FILTER_UC, - NCSI_FILTER_MC, - NCSI_FILTER_MIXED, - NCSI_FILTER_MAX -}; - struct ncsi_channel_version { u32 version; /* Supported BCD encoded NCSI version */ u32 alpha2; /* Supported BCD encoded NCSI version */ @@ -98,11 +89,18 @@ struct ncsi_channel_mode { u32 data[8]; /* Data entries */ }; -struct ncsi_channel_filter { - u32 index; /* Index of channel filters */ - u32 total; /* Total entries in the filter table */ - u64 bitmap; /* Bitmap of valid entries */ - u32 data[]; /* Data for the valid entries */ +struct ncsi_channel_mac_filter { + u8 n_uc; + u8 n_mc; + u8 n_mixed; + u64 bitmap; + unsigned char *addrs; +}; + +struct ncsi_channel_vlan_filter { + u8 n_vids; + u64 bitmap; + u16 *vids; }; struct ncsi_channel_stats { @@ -186,7 +184,9 @@ struct ncsi_channel { struct ncsi_channel_version version; struct ncsi_channel_cap caps[NCSI_CAP_MAX]; struct ncsi_channel_mode modes[NCSI_MODE_MAX]; - struct ncsi_channel_filter *filters[NCSI_FILTER_MAX]; + /* Filtering Settings */ + struct ncsi_channel_mac_filter mac_filter; + struct ncsi_channel_vlan_filter vlan_filter; struct ncsi_channel_stats stats; struct { struct timer_list timer; @@ -320,10 +320,6 @@ extern spinlock_t ncsi_dev_lock; list_for_each_entry_rcu(nc, &np->channels, node) /* Resources */ -u32 *ncsi_get_filter(struct ncsi_channel *nc, int table, int index); -int ncsi_find_filter(struct ncsi_channel *nc, int table, void *data); -int ncsi_add_filter(struct ncsi_channel *nc, int table, void *data); -int ncsi_remove_filter(struct ncsi_channel *nc, int table, int index); void ncsi_start_channel_monitor(struct ncsi_channel *nc); void ncsi_stop_channel_monitor(struct ncsi_channel *nc); struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np,
diff --git a/net/ncsi/ncsi-aen.c b/net/ncsi/ncsi-aen.c index e7b05de..25e483e 100644 --- a/net/ncsi/ncsi-aen.c +++ b/net/ncsi/ncsi-aen.c
@@ -73,8 +73,8 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, ncm->data[2] = data; ncm->data[4] = ntohl(lsc->oem_status); - netdev_info(ndp->ndev.dev, "NCSI: LSC AEN - channel %u state %s\n", - nc->id, data & 0x1 ? "up" : "down"); + netdev_dbg(ndp->ndev.dev, "NCSI: LSC AEN - channel %u state %s\n", + nc->id, data & 0x1 ? "up" : "down"); chained = !list_empty(&nc->link); state = nc->state; @@ -148,9 +148,9 @@ static int ncsi_aen_handler_hncdsc(struct ncsi_dev_priv *ndp, hncdsc = (struct ncsi_aen_hncdsc_pkt *)h; ncm->data[3] = ntohl(hncdsc->status); spin_unlock_irqrestore(&nc->lock, flags); - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "NCSI: host driver %srunning on channel %u\n", - ncm->data[3] & 0x1 ? "" : "not ", nc->id); + netdev_dbg(ndp->ndev.dev, + "NCSI: host driver %srunning on channel %u\n", + ncm->data[3] & 0x1 ? "" : "not ", nc->id); return 0; }
diff --git a/net/ncsi/ncsi-manage.c b/net/ncsi/ncsi-manage.c index c3695ba..09128476 100644 --- a/net/ncsi/ncsi-manage.c +++ b/net/ncsi/ncsi-manage.c
@@ -27,125 +27,6 @@ LIST_HEAD(ncsi_dev_list); DEFINE_SPINLOCK(ncsi_dev_lock); -static inline int ncsi_filter_size(int table) -{ - int sizes[] = { 2, 6, 6, 6 }; - - BUILD_BUG_ON(ARRAY_SIZE(sizes) != NCSI_FILTER_MAX); - if (table < NCSI_FILTER_BASE || table >= NCSI_FILTER_MAX) - return -EINVAL; - - return sizes[table]; -} - -u32 *ncsi_get_filter(struct ncsi_channel *nc, int table, int index) -{ - struct ncsi_channel_filter *ncf; - int size; - - ncf = nc->filters[table]; - if (!ncf) - return NULL; - - size = ncsi_filter_size(table); - if (size < 0) - return NULL; - - return ncf->data + size * index; -} - -/* Find the first active filter in a filter table that matches the given - * data parameter. If data is NULL, this returns the first active filter. - */ -int ncsi_find_filter(struct ncsi_channel *nc, int table, void *data) -{ - struct ncsi_channel_filter *ncf; - void *bitmap; - int index, size; - unsigned long flags; - - ncf = nc->filters[table]; - if (!ncf) - return -ENXIO; - - size = ncsi_filter_size(table); - if (size < 0) - return size; - - spin_lock_irqsave(&nc->lock, flags); - bitmap = (void *)&ncf->bitmap; - index = -1; - while ((index = find_next_bit(bitmap, ncf->total, index + 1)) - < ncf->total) { - if (!data || !memcmp(ncf->data + size * index, data, size)) { - spin_unlock_irqrestore(&nc->lock, flags); - return index; - } - } - spin_unlock_irqrestore(&nc->lock, flags); - - return -ENOENT; -} - -int ncsi_add_filter(struct ncsi_channel *nc, int table, void *data) -{ - struct ncsi_channel_filter *ncf; - int index, size; - void *bitmap; - unsigned long flags; - - size = ncsi_filter_size(table); - if (size < 0) - return size; - - index = ncsi_find_filter(nc, table, data); - if (index >= 0) - return index; - - ncf = nc->filters[table]; - if (!ncf) - return -ENODEV; - - spin_lock_irqsave(&nc->lock, flags); - bitmap = (void *)&ncf->bitmap; - do { - index = find_next_zero_bit(bitmap, ncf->total, 0); - if (index >= ncf->total) { - spin_unlock_irqrestore(&nc->lock, flags); - return -ENOSPC; - } - } while (test_and_set_bit(index, bitmap)); - - memcpy(ncf->data + size * index, data, size); - spin_unlock_irqrestore(&nc->lock, flags); - - return index; -} - -int ncsi_remove_filter(struct ncsi_channel *nc, int table, int index) -{ - struct ncsi_channel_filter *ncf; - int size; - void *bitmap; - unsigned long flags; - - size = ncsi_filter_size(table); - if (size < 0) - return size; - - ncf = nc->filters[table]; - if (!ncf || index >= ncf->total) - return -ENODEV; - - spin_lock_irqsave(&nc->lock, flags); - bitmap = (void *)&ncf->bitmap; - if (test_and_clear_bit(index, bitmap)) - memset(ncf->data + size * index, 0, size); - spin_unlock_irqrestore(&nc->lock, flags); - - return 0; -} - static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down) { struct ncsi_dev *nd = &ndp->ndev; @@ -339,20 +220,13 @@ struct ncsi_channel *ncsi_add_channel(struct ncsi_package *np, unsigned char id) static void ncsi_remove_channel(struct ncsi_channel *nc) { struct ncsi_package *np = nc->package; - struct ncsi_channel_filter *ncf; unsigned long flags; - int i; + + spin_lock_irqsave(&nc->lock, flags); /* Release filters */ - spin_lock_irqsave(&nc->lock, flags); - for (i = 0; i < NCSI_FILTER_MAX; i++) { - ncf = nc->filters[i]; - if (!ncf) - continue; - - nc->filters[i] = NULL; - kfree(ncf); - } + kfree(nc->mac_filter.addrs); + kfree(nc->vlan_filter.vids); nc->state = NCSI_CHANNEL_INACTIVE; spin_unlock_irqrestore(&nc->lock, flags); @@ -670,32 +544,26 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) static int clear_one_vid(struct ncsi_dev_priv *ndp, struct ncsi_channel *nc, struct ncsi_cmd_arg *nca) { + struct ncsi_channel_vlan_filter *ncf; + unsigned long flags; + void *bitmap; int index; - u32 *data; u16 vid; - index = ncsi_find_filter(nc, NCSI_FILTER_VLAN, NULL); - if (index < 0) { - /* Filter table empty */ + ncf = &nc->vlan_filter; + bitmap = &ncf->bitmap; + + spin_lock_irqsave(&nc->lock, flags); + index = find_next_bit(bitmap, ncf->n_vids, 0); + if (index >= ncf->n_vids) { + spin_unlock_irqrestore(&nc->lock, flags); return -1; } + vid = ncf->vids[index]; - data = ncsi_get_filter(nc, NCSI_FILTER_VLAN, index); - if (!data) { - netdev_err(ndp->ndev.dev, - "NCSI: failed to retrieve filter %d\n", index); - /* Set the VLAN id to 0 - this will still disable the entry in - * the filter table, but we won't know what it was. - */ - vid = 0; - } else { - vid = *(u16 *)data; - } - - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "NCSI: removed vlan tag %u at index %d\n", - vid, index + 1); - ncsi_remove_filter(nc, NCSI_FILTER_VLAN, index); + clear_bit(index, bitmap); + ncf->vids[index] = 0; + spin_unlock_irqrestore(&nc->lock, flags); nca->type = NCSI_PKT_CMD_SVF; nca->words[1] = vid; @@ -711,45 +579,55 @@ static int clear_one_vid(struct ncsi_dev_priv *ndp, struct ncsi_channel *nc, static int set_one_vid(struct ncsi_dev_priv *ndp, struct ncsi_channel *nc, struct ncsi_cmd_arg *nca) { + struct ncsi_channel_vlan_filter *ncf; struct vlan_vid *vlan = NULL; - int index = 0; + unsigned long flags; + int i, index; + void *bitmap; + u16 vid; + if (list_empty(&ndp->vlan_vids)) + return -1; + + ncf = &nc->vlan_filter; + bitmap = &ncf->bitmap; + + spin_lock_irqsave(&nc->lock, flags); + + rcu_read_lock(); list_for_each_entry_rcu(vlan, &ndp->vlan_vids, list) { - index = ncsi_find_filter(nc, NCSI_FILTER_VLAN, &vlan->vid); - if (index < 0) { - /* New tag to add */ - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "NCSI: new vlan id to set: %u\n", - vlan->vid); + vid = vlan->vid; + for (i = 0; i < ncf->n_vids; i++) + if (ncf->vids[i] == vid) { + vid = 0; + break; + } + if (vid) break; - } - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "vid %u already at filter pos %d\n", - vlan->vid, index); } + rcu_read_unlock(); - if (!vlan || index >= 0) { - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "no vlan ids left to set\n"); + if (!vid) { + /* No VLAN ID is not set */ + spin_unlock_irqrestore(&nc->lock, flags); return -1; } - index = ncsi_add_filter(nc, NCSI_FILTER_VLAN, &vlan->vid); - if (index < 0) { + index = find_next_zero_bit(bitmap, ncf->n_vids, 0); + if (index < 0 || index >= ncf->n_vids) { netdev_err(ndp->ndev.dev, - "Failed to add new VLAN tag, error %d\n", index); - if (index == -ENOSPC) - netdev_err(ndp->ndev.dev, - "Channel %u already has all VLAN filters set\n", - nc->id); + "Channel %u already has all VLAN filters set\n", + nc->id); + spin_unlock_irqrestore(&nc->lock, flags); return -1; } - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "NCSI: set vid %u in packet, index %u\n", - vlan->vid, index + 1); + ncf->vids[index] = vid; + set_bit(index, bitmap); + spin_unlock_irqrestore(&nc->lock, flags); + nca->type = NCSI_PKT_CMD_SVF; - nca->words[1] = vlan->vid; + nca->words[1] = vid; /* HW filter index starts at 1 */ nca->bytes[6] = index + 1; nca->bytes[7] = 0x01; @@ -910,8 +788,8 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) } break; case ncsi_dev_state_config_done: - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "NCSI: channel %u config done\n", nc->id); + netdev_dbg(ndp->ndev.dev, "NCSI: channel %u config done\n", + nc->id); spin_lock_irqsave(&nc->lock, flags); if (nc->reconfigure_needed) { /* This channel's configuration has been updated @@ -926,8 +804,7 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) list_add_tail_rcu(&nc->link, &ndp->channel_queue); spin_unlock_irqrestore(&ndp->lock, flags); - netdev_printk(KERN_DEBUG, dev, - "Dirty NCSI channel state reset\n"); + netdev_dbg(dev, "Dirty NCSI channel state reset\n"); ncsi_process_next_channel(ndp); break; } @@ -938,9 +815,9 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) } else { hot_nc = NULL; nc->state = NCSI_CHANNEL_INACTIVE; - netdev_warn(ndp->ndev.dev, - "NCSI: channel %u link down after config\n", - nc->id); + netdev_dbg(ndp->ndev.dev, + "NCSI: channel %u link down after config\n", + nc->id); } spin_unlock_irqrestore(&nc->lock, flags); @@ -1030,9 +907,9 @@ static int ncsi_choose_active_channel(struct ncsi_dev_priv *ndp) } ncm = &found->modes[NCSI_MODE_LINK]; - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "NCSI: Channel %u added to queue (link %s)\n", - found->id, ncm->data[2] & 0x1 ? "up" : "down"); + netdev_dbg(ndp->ndev.dev, + "NCSI: Channel %u added to queue (link %s)\n", + found->id, ncm->data[2] & 0x1 ? "up" : "down"); out: spin_lock_irqsave(&ndp->lock, flags); @@ -1321,14 +1198,14 @@ int ncsi_process_next_channel(struct ncsi_dev_priv *ndp) switch (old_state) { case NCSI_CHANNEL_INACTIVE: ndp->ndev.state = ncsi_dev_state_config; - netdev_info(ndp->ndev.dev, "NCSI: configuring channel %u\n", - nc->id); + netdev_dbg(ndp->ndev.dev, "NCSI: configuring channel %u\n", + nc->id); ncsi_configure_channel(ndp); break; case NCSI_CHANNEL_ACTIVE: ndp->ndev.state = ncsi_dev_state_suspend; - netdev_info(ndp->ndev.dev, "NCSI: suspending channel %u\n", - nc->id); + netdev_dbg(ndp->ndev.dev, "NCSI: suspending channel %u\n", + nc->id); ncsi_suspend_channel(ndp); break; default: @@ -1348,8 +1225,6 @@ int ncsi_process_next_channel(struct ncsi_dev_priv *ndp) return ncsi_choose_active_channel(ndp); } - netdev_printk(KERN_DEBUG, ndp->ndev.dev, - "NCSI: No more channels to process\n"); ncsi_report_link(ndp, false); return -ENODEV; } @@ -1440,9 +1315,9 @@ static int ncsi_kick_channels(struct ncsi_dev_priv *ndp) if ((ndp->ndev.state & 0xff00) == ncsi_dev_state_config || !list_empty(&nc->link)) { - netdev_printk(KERN_DEBUG, nd->dev, - "NCSI: channel %p marked dirty\n", - nc); + netdev_dbg(nd->dev, + "NCSI: channel %p marked dirty\n", + nc); nc->reconfigure_needed = true; } spin_unlock_irqrestore(&nc->lock, flags); @@ -1460,8 +1335,7 @@ static int ncsi_kick_channels(struct ncsi_dev_priv *ndp) list_add_tail_rcu(&nc->link, &ndp->channel_queue); spin_unlock_irqrestore(&ndp->lock, flags); - netdev_printk(KERN_DEBUG, nd->dev, - "NCSI: kicked channel %p\n", nc); + netdev_dbg(nd->dev, "NCSI: kicked channel %p\n", nc); n++; } } @@ -1492,8 +1366,8 @@ int ncsi_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) list_for_each_entry_rcu(vlan, &ndp->vlan_vids, list) { n_vids++; if (vlan->vid == vid) { - netdev_printk(KERN_DEBUG, dev, - "NCSI: vid %u already registered\n", vid); + netdev_dbg(dev, "NCSI: vid %u already registered\n", + vid); return 0; } } @@ -1512,7 +1386,7 @@ int ncsi_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) vlan->vid = vid; list_add_rcu(&vlan->list, &ndp->vlan_vids); - netdev_printk(KERN_DEBUG, dev, "NCSI: Added new vid %u\n", vid); + netdev_dbg(dev, "NCSI: Added new vid %u\n", vid); found = ncsi_kick_channels(ndp) != 0; @@ -1541,8 +1415,7 @@ int ncsi_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid) /* Remove the VLAN id from our internal list */ list_for_each_entry_safe(vlan, tmp, &ndp->vlan_vids, list) if (vlan->vid == vid) { - netdev_printk(KERN_DEBUG, dev, - "NCSI: vid %u found, removing\n", vid); + netdev_dbg(dev, "NCSI: vid %u found, removing\n", vid); list_del_rcu(&vlan->list); found = true; kfree(vlan); @@ -1669,7 +1542,7 @@ void ncsi_stop_dev(struct ncsi_dev *nd) } } - netdev_printk(KERN_DEBUG, ndp->ndev.dev, "NCSI: Stopping device\n"); + netdev_dbg(ndp->ndev.dev, "NCSI: Stopping device\n"); ncsi_report_link(ndp, true); } EXPORT_SYMBOL_GPL(ncsi_stop_dev);
diff --git a/net/ncsi/ncsi-netlink.c b/net/ncsi/ncsi-netlink.c index 41cede4..82e6edf 100644 --- a/net/ncsi/ncsi-netlink.c +++ b/net/ncsi/ncsi-netlink.c
@@ -58,10 +58,9 @@ static int ncsi_write_channel_info(struct sk_buff *skb, struct ncsi_dev_priv *ndp, struct ncsi_channel *nc) { - struct nlattr *vid_nest; - struct ncsi_channel_filter *ncf; + struct ncsi_channel_vlan_filter *ncf; struct ncsi_channel_mode *m; - u32 *data; + struct nlattr *vid_nest; int i; nla_put_u32(skb, NCSI_CHANNEL_ATTR_ID, nc->id); @@ -79,18 +78,13 @@ static int ncsi_write_channel_info(struct sk_buff *skb, vid_nest = nla_nest_start(skb, NCSI_CHANNEL_ATTR_VLAN_LIST); if (!vid_nest) return -ENOMEM; - ncf = nc->filters[NCSI_FILTER_VLAN]; + ncf = &nc->vlan_filter; i = -1; - if (ncf) { - while ((i = find_next_bit((void *)&ncf->bitmap, ncf->total, - i + 1)) < ncf->total) { - data = ncsi_get_filter(nc, NCSI_FILTER_VLAN, i); - /* Uninitialised channels will have 'zero' vlan ids */ - if (!data || !*data) - continue; + while ((i = find_next_bit((void *)&ncf->bitmap, ncf->n_vids, + i + 1)) < ncf->n_vids) { + if (ncf->vids[i]) nla_put_u16(skb, NCSI_CHANNEL_ATTR_VLAN_ID, - *(u16 *)data); - } + ncf->vids[i]); } nla_nest_end(skb, vid_nest); @@ -207,7 +201,6 @@ static int ncsi_pkg_info_nl(struct sk_buff *msg, struct genl_info *info) return genlmsg_reply(skb, info); err: - genlmsg_cancel(skb, hdr); kfree_skb(skb); return rc; }
diff --git a/net/ncsi/ncsi-rsp.c b/net/ncsi/ncsi-rsp.c index efd933f..930c1d3 100644 --- a/net/ncsi/ncsi-rsp.c +++ b/net/ncsi/ncsi-rsp.c
@@ -334,9 +334,9 @@ static int ncsi_rsp_handler_svf(struct ncsi_request *nr) struct ncsi_rsp_pkt *rsp; struct ncsi_dev_priv *ndp = nr->ndp; struct ncsi_channel *nc; - struct ncsi_channel_filter *ncf; - unsigned short vlan; - int ret; + struct ncsi_channel_vlan_filter *ncf; + unsigned long flags; + void *bitmap; /* Find the package and channel */ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp); @@ -346,22 +346,23 @@ static int ncsi_rsp_handler_svf(struct ncsi_request *nr) return -ENODEV; cmd = (struct ncsi_cmd_svf_pkt *)skb_network_header(nr->cmd); - ncf = nc->filters[NCSI_FILTER_VLAN]; - if (!ncf) - return -ENOENT; - if (cmd->index >= ncf->total) + ncf = &nc->vlan_filter; + if (cmd->index == 0 || cmd->index > ncf->n_vids) return -ERANGE; - /* Add or remove the VLAN filter */ + /* Add or remove the VLAN filter. Remember HW indexes from 1 */ + spin_lock_irqsave(&nc->lock, flags); + bitmap = &ncf->bitmap; if (!(cmd->enable & 0x1)) { - /* HW indexes from 1 */ - ret = ncsi_remove_filter(nc, NCSI_FILTER_VLAN, cmd->index - 1); + if (test_and_clear_bit(cmd->index - 1, bitmap)) + ncf->vids[cmd->index - 1] = 0; } else { - vlan = ntohs(cmd->vlan); - ret = ncsi_add_filter(nc, NCSI_FILTER_VLAN, &vlan); + set_bit(cmd->index - 1, bitmap); + ncf->vids[cmd->index - 1] = ntohs(cmd->vlan); } + spin_unlock_irqrestore(&nc->lock, flags); - return ret; + return 0; } static int ncsi_rsp_handler_ev(struct ncsi_request *nr) @@ -422,8 +423,12 @@ static int ncsi_rsp_handler_sma(struct ncsi_request *nr) struct ncsi_rsp_pkt *rsp; struct ncsi_dev_priv *ndp = nr->ndp; struct ncsi_channel *nc; - struct ncsi_channel_filter *ncf; + struct ncsi_channel_mac_filter *ncf; + unsigned long flags; void *bitmap; + bool enabled; + int index; + /* Find the package and channel */ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->rsp); @@ -436,31 +441,24 @@ static int ncsi_rsp_handler_sma(struct ncsi_request *nr) * isn't supported yet. */ cmd = (struct ncsi_cmd_sma_pkt *)skb_network_header(nr->cmd); - switch (cmd->at_e >> 5) { - case 0x0: /* UC address */ - ncf = nc->filters[NCSI_FILTER_UC]; - break; - case 0x1: /* MC address */ - ncf = nc->filters[NCSI_FILTER_MC]; - break; - default: - return -EINVAL; - } + enabled = cmd->at_e & 0x1; + ncf = &nc->mac_filter; + bitmap = &ncf->bitmap; - /* Sanity check on the filter */ - if (!ncf) - return -ENOENT; - else if (cmd->index >= ncf->total) + if (cmd->index == 0 || + cmd->index > ncf->n_uc + ncf->n_mc + ncf->n_mixed) return -ERANGE; - bitmap = &ncf->bitmap; - if (cmd->at_e & 0x1) { - set_bit(cmd->index, bitmap); - memcpy(ncf->data + 6 * cmd->index, cmd->mac, 6); + index = (cmd->index - 1) * ETH_ALEN; + spin_lock_irqsave(&nc->lock, flags); + if (enabled) { + set_bit(cmd->index - 1, bitmap); + memcpy(&ncf->addrs[index], cmd->mac, ETH_ALEN); } else { - clear_bit(cmd->index, bitmap); - memset(ncf->data + 6 * cmd->index, 0, 6); + clear_bit(cmd->index - 1, bitmap); + memset(&ncf->addrs[index], 0, ETH_ALEN); } + spin_unlock_irqrestore(&nc->lock, flags); return 0; } @@ -631,9 +629,7 @@ static int ncsi_rsp_handler_gc(struct ncsi_request *nr) struct ncsi_rsp_gc_pkt *rsp; struct ncsi_dev_priv *ndp = nr->ndp; struct ncsi_channel *nc; - struct ncsi_channel_filter *ncf; - size_t size, entry_size; - int cnt, i; + size_t size; /* Find the channel */ rsp = (struct ncsi_rsp_gc_pkt *)skb_network_header(nr->rsp); @@ -655,64 +651,40 @@ static int ncsi_rsp_handler_gc(struct ncsi_request *nr) nc->caps[NCSI_CAP_VLAN].cap = rsp->vlan_mode & NCSI_CAP_VLAN_MASK; - /* Build filters */ - for (i = 0; i < NCSI_FILTER_MAX; i++) { - switch (i) { - case NCSI_FILTER_VLAN: - cnt = rsp->vlan_cnt; - entry_size = 2; - break; - case NCSI_FILTER_MIXED: - cnt = rsp->mixed_cnt; - entry_size = 6; - break; - case NCSI_FILTER_MC: - cnt = rsp->mc_cnt; - entry_size = 6; - break; - case NCSI_FILTER_UC: - cnt = rsp->uc_cnt; - entry_size = 6; - break; - default: - continue; - } + size = (rsp->uc_cnt + rsp->mc_cnt + rsp->mixed_cnt) * ETH_ALEN; + nc->mac_filter.addrs = kzalloc(size, GFP_ATOMIC); + if (!nc->mac_filter.addrs) + return -ENOMEM; + nc->mac_filter.n_uc = rsp->uc_cnt; + nc->mac_filter.n_mc = rsp->mc_cnt; + nc->mac_filter.n_mixed = rsp->mixed_cnt; - if (!cnt || nc->filters[i]) - continue; - - size = sizeof(*ncf) + cnt * entry_size; - ncf = kzalloc(size, GFP_ATOMIC); - if (!ncf) { - pr_warn("%s: Cannot alloc filter table (%d)\n", - __func__, i); - return -ENOMEM; - } - - ncf->index = i; - ncf->total = cnt; - if (i == NCSI_FILTER_VLAN) { - /* Set VLAN filters active so they are cleared in - * first configuration state - */ - ncf->bitmap = U64_MAX; - } else { - ncf->bitmap = 0x0ul; - } - nc->filters[i] = ncf; - } + nc->vlan_filter.vids = kcalloc(rsp->vlan_cnt, + sizeof(*nc->vlan_filter.vids), + GFP_ATOMIC); + if (!nc->vlan_filter.vids) + return -ENOMEM; + /* Set VLAN filters active so they are cleared in the first + * configuration state + */ + nc->vlan_filter.bitmap = U64_MAX; + nc->vlan_filter.n_vids = rsp->vlan_cnt; return 0; } static int ncsi_rsp_handler_gp(struct ncsi_request *nr) { - struct ncsi_rsp_gp_pkt *rsp; + struct ncsi_channel_vlan_filter *ncvf; + struct ncsi_channel_mac_filter *ncmf; struct ncsi_dev_priv *ndp = nr->ndp; + struct ncsi_rsp_gp_pkt *rsp; struct ncsi_channel *nc; - unsigned short enable, vlan; + unsigned short enable; unsigned char *pdata; - int table, i; + unsigned long flags; + void *bitmap; + int i; /* Find the channel */ rsp = (struct ncsi_rsp_gp_pkt *)skb_network_header(nr->rsp); @@ -746,36 +718,33 @@ static int ncsi_rsp_handler_gp(struct ncsi_request *nr) /* MAC addresses filter table */ pdata = (unsigned char *)rsp + 48; enable = rsp->mac_enable; + ncmf = &nc->mac_filter; + spin_lock_irqsave(&nc->lock, flags); + bitmap = &ncmf->bitmap; for (i = 0; i < rsp->mac_cnt; i++, pdata += 6) { - if (i >= (nc->filters[NCSI_FILTER_UC]->total + - nc->filters[NCSI_FILTER_MC]->total)) - table = NCSI_FILTER_MIXED; - else if (i >= nc->filters[NCSI_FILTER_UC]->total) - table = NCSI_FILTER_MC; - else - table = NCSI_FILTER_UC; - if (!(enable & (0x1 << i))) - continue; + clear_bit(i, bitmap); + else + set_bit(i, bitmap); - if (ncsi_find_filter(nc, table, pdata) >= 0) - continue; - - ncsi_add_filter(nc, table, pdata); + memcpy(&ncmf->addrs[i * ETH_ALEN], pdata, ETH_ALEN); } + spin_unlock_irqrestore(&nc->lock, flags); /* VLAN filter table */ enable = ntohs(rsp->vlan_enable); + ncvf = &nc->vlan_filter; + bitmap = &ncvf->bitmap; + spin_lock_irqsave(&nc->lock, flags); for (i = 0; i < rsp->vlan_cnt; i++, pdata += 2) { if (!(enable & (0x1 << i))) - continue; + clear_bit(i, bitmap); + else + set_bit(i, bitmap); - vlan = ntohs(*(__be16 *)pdata); - if (ncsi_find_filter(nc, NCSI_FILTER_VLAN, &vlan) >= 0) - continue; - - ncsi_add_filter(nc, NCSI_FILTER_VLAN, &vlan); + ncvf->vids[i] = ntohs(*(__be16 *)pdata); } + spin_unlock_irqrestore(&nc->lock, flags); return 0; }
diff --git a/net/nfc/netlink.c b/net/nfc/netlink.c index f018eaf..376181c 100644 --- a/net/nfc/netlink.c +++ b/net/nfc/netlink.c
@@ -206,7 +206,6 @@ int nfc_genl_targets_found(struct nfc_dev *dev) return genlmsg_multicast(&nfc_genl_family, msg, 0, 0, GFP_ATOMIC); nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -237,7 +236,6 @@ int nfc_genl_target_lost(struct nfc_dev *dev, u32 target_idx) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -269,7 +267,6 @@ int nfc_genl_tm_activated(struct nfc_dev *dev, u32 protocol) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -299,7 +296,6 @@ int nfc_genl_tm_deactivated(struct nfc_dev *dev) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -340,7 +336,6 @@ int nfc_genl_device_added(struct nfc_dev *dev) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -370,7 +365,6 @@ int nfc_genl_device_removed(struct nfc_dev *dev) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -434,8 +428,6 @@ int nfc_genl_llc_send_sdres(struct nfc_dev *dev, struct hlist_head *sdres_list) return genlmsg_multicast(&nfc_genl_family, msg, 0, 0, GFP_ATOMIC); nla_put_failure: - genlmsg_cancel(msg, hdr); - free_msg: nlmsg_free(msg); @@ -470,7 +462,6 @@ int nfc_genl_se_added(struct nfc_dev *dev, u32 se_idx, u16 type) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -501,7 +492,6 @@ int nfc_genl_se_removed(struct nfc_dev *dev, u32 se_idx) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -546,7 +536,6 @@ int nfc_genl_se_transaction(struct nfc_dev *dev, u8 se_idx, return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: /* evt_transaction is no more used */ devm_kfree(&dev->dev, evt_transaction); @@ -585,7 +574,6 @@ int nfc_genl_se_connectivity(struct nfc_dev *dev, u8 se_idx) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -703,7 +691,6 @@ int nfc_genl_dep_link_up_event(struct nfc_dev *dev, u32 target_idx, return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -735,7 +722,6 @@ int nfc_genl_dep_link_down_event(struct nfc_dev *dev) return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -1030,7 +1016,6 @@ static int nfc_genl_send_params(struct sk_buff *msg, return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); return -EMSGSIZE; } @@ -1290,7 +1275,6 @@ int nfc_genl_fw_download_done(struct nfc_dev *dev, const char *firmware_name, return 0; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); return -EMSGSIZE; @@ -1507,7 +1491,6 @@ static void se_io_cb(void *context, u8 *apdu, size_t apdu_len, int err) return; nla_put_failure: - genlmsg_cancel(msg, hdr); free_msg: nlmsg_free(msg); kfree(ctx);