summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt13
-rw-r--r--Documentation/devicetree/bindings/mtd/aspeed-smc.txt73
-rw-r--r--Documentation/devicetree/bindings/serial/8250.txt1
-rw-r--r--Documentation/devicetree/bindings/vendor-prefixes.txt1
-rw-r--r--Documentation/hwmon/adm127529
-rw-r--r--arch/arm/Kconfig2
-rw-r--r--arch/arm/Kconfig.debug1
-rw-r--r--arch/arm/Makefile1
-rw-r--r--arch/arm/boot/dts/Makefile5
-rw-r--r--arch/arm/boot/dts/aspeed-bmc-opp-barreleye.dts147
-rw-r--r--arch/arm/boot/dts/aspeed-bmc-opp-firestone.dts136
-rw-r--r--arch/arm/boot/dts/aspeed-bmc-opp-flash-layout.dtsi28
-rw-r--r--arch/arm/boot/dts/aspeed-bmc-opp-garrison.dts85
-rw-r--r--arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts99
-rw-r--r--arch/arm/boot/dts/ast2400.dtsi361
-rw-r--r--arch/arm/configs/aspeed_defconfig163
-rw-r--r--arch/arm/mach-aspeed/Kconfig27
-rw-r--r--arch/arm/mach-aspeed/Makefile3
-rwxr-xr-xarch/arm/mach-aspeed/aspeed.c261
-rw-r--r--arch/arm/mach-aspeed/ast2400.h117
-rw-r--r--drivers/clocksource/Makefile1
-rw-r--r--drivers/clocksource/moxart_timer.c92
-rw-r--r--drivers/gpio/Kconfig8
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/gpio-aspeed.c448
-rw-r--r--drivers/hwmon/Kconfig13
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/pmbus/Kconfig4
-rw-r--r--drivers/hwmon/pmbus/adm1275.c84
-rw-r--r--drivers/hwmon/power8_occ_i2c.c1254
-rw-r--r--drivers/i2c/busses/Kconfig11
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/i2c-aspeed.c905
-rw-r--r--drivers/i2c/i2c-core.c1
-rw-r--r--drivers/irqchip/Makefile1
-rw-r--r--drivers/irqchip/irq-aspeed-vic.c259
-rw-r--r--drivers/misc/Kconfig5
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/bt-host.c420
-rw-r--r--drivers/mtd/spi-nor/Kconfig11
-rw-r--r--drivers/mtd/spi-nor/Makefile1
-rw-r--r--drivers/mtd/spi-nor/aspeed-smc.c563
-rw-r--r--drivers/net/ethernet/faraday/ftgmac100.c268
-rw-r--r--drivers/net/phy/broadcom.c13
-rw-r--r--drivers/rtc/Kconfig11
-rw-r--r--drivers/rtc/Makefile1
-rw-r--r--drivers/rtc/rtc-aspeed.c150
-rw-r--r--drivers/tty/serial/Kconfig10
-rw-r--r--drivers/tty/serial/Makefile1
-rw-r--r--drivers/tty/serial/aspeed-vuart.c333
-rw-r--r--drivers/watchdog/Kconfig10
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/aspeed_wdt.c224
-rw-r--r--include/linux/brcmphy.h2
-rw-r--r--include/net/ncsi.h59
-rw-r--r--include/uapi/linux/Kbuild1
-rw-r--r--include/uapi/linux/bt-host.h18
-rw-r--r--include/uapi/linux/if_ether.h1
-rw-r--r--include/uapi/linux/ncsi.h200
-rw-r--r--net/Kconfig1
-rw-r--r--net/Makefile1
-rw-r--r--net/ncsi/Kconfig10
-rw-r--r--net/ncsi/Makefile5
-rw-r--r--net/ncsi/internal.h159
-rw-r--r--net/ncsi/ncsi-aen.c197
-rw-r--r--net/ncsi/ncsi-cmd.c371
-rw-r--r--net/ncsi/ncsi-manage.c989
-rw-r--r--net/ncsi/ncsi-pkt.h391
-rw-r--r--net/ncsi/ncsi-rsp.c1171
69 files changed, 10142 insertions, 94 deletions
diff --git a/Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt b/Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt
new file mode 100644
index 000000000000..9aab2df8c5ed
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-ibm-occ.txt
@@ -0,0 +1,13 @@
+HWMON i2c driver for IBM POWER CPU OCC (On Chip Controller)
+
+Required properties:
+ - compatible: must be "ibm,power8-occ-i2c"
+ - reg: physical address
+
+Example:
+i2c3: i2c-bus@100 {
+ occ@50 {
+ compatible = "ibm,occ-i2c";
+ reg = <0x50>;
+ };
+};
diff --git a/Documentation/devicetree/bindings/mtd/aspeed-smc.txt b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt
new file mode 100644
index 000000000000..e17aea843651
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/aspeed-smc.txt
@@ -0,0 +1,73 @@
+* Aspeed Static Memory controller in SPI mode
+* Aspeed SPI Flash Controller
+
+Required properties:
+ - compatible : Should be "aspeed,fmc" for Static Memory Controller (AST2400, AST2300?), or
+ "aspeed,smc" for the SPI flash controller
+ - reg : the first contains the register location and length,
+ the second through nth contains the memory mapping address and length
+ for the access window for each chips select
+ - interrupts : Should contain the interrupt for the dma device if fmc
+ - clocks : The APB clock input to the controller
+ - #address-cells : must be 1 corresponding to chip select child binding
+ - #size-cells : must be 0 corresponding to chip select child binding
+
+
+Child node required properties:
+ - reg : must contain chip select number in first cell of address, must
+ be 1 tuple long
+ - compatible : may contain "vendor,part", must include "jedec,spi-nor"
+ (see spi-nor.txt binding).
+
+Child node optional properties:
+ - label - (optional) name to assign to mtd, default os assigned
+ - spi-max-frequency - (optional) max frequency of spi bus (XXX max if missing)
+ - spi-cpol - (optional) Empty property indicating device requires
+ inverse clock polarity (CPOL) mode (boolean)
+ - spi-cpha - (optional) Empty property indicating device requires
+ shifted clock phase (CPHA) mode (boolean)
+ - spi-tx-bus-width - (optional) The bus width(number of data wires) that
+ used for MOSI. Defaults to 1 if not present.
+ - spi-rx-bus-width - (optional) The bus width(number of data wires) that
+ used for MOSI. Defaults to 1 if not present.
+
+Child node optional properties:
+ - see mtd/partiton.txt for partitioning bindings and mtd naming
+
+
+Example:
+
+fmc: fmc@1e620000 {
+ compatible = "aspeed,fmc";
+ reg = < 0x1e620000 0x94
+ 0x20000000 0x02000000
+ 0x22000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ flash@0 {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ label = "bmc";
+ /* spi-max-frequency = <>; */
+ /* m25p,fast-read; */
+ #address-cells = <1>;
+ #size-cells = <1>;
+ boot@0 {
+ label = "boot-loader";
+ reg = < 0 0x8000 >
+ }
+ image@8000 {
+ label = "kernel-image";
+ reg = < 0x8000 0x1f8000 >
+ }
+ };
+ flash@1 {
+ reg = < 1 >;
+ compatible = "jedec,spi-nor" ;
+ label = "alt";
+ /* spi-max-frequency = <>; */
+ status = "fail";
+ /* m25p,fast-read; */
+ };
+};
+
diff --git a/Documentation/devicetree/bindings/serial/8250.txt b/Documentation/devicetree/bindings/serial/8250.txt
index 91d5ab0e60fc..1b887f252601 100644
--- a/Documentation/devicetree/bindings/serial/8250.txt
+++ b/Documentation/devicetree/bindings/serial/8250.txt
@@ -20,6 +20,7 @@ Required properties:
- "altr,16550-FIFO128"
- "fsl,16550-FIFO64"
- "fsl,ns16550"
+ - "aspeed,vuart"
- "serial" if the port type is unknown.
- reg : offset and length of the register set for the device.
- interrupts : should contain uart interrupt.
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 55df1d444e9f..78d969b3d1d2 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -28,6 +28,7 @@ arm ARM Ltd.
armadeus ARMadeus Systems SARL
artesyn Artesyn Embedded Technologies Inc.
asahi-kasei Asahi Kasei Corp.
+aspeed ASPEED TECHNOLOGY Inc.
atmel Atmel Corporation
auo AU Optronics Corporation
avago Avago Technologies
diff --git a/Documentation/hwmon/adm1275 b/Documentation/hwmon/adm1275
index d697229e3c18..791bc0bd91e6 100644
--- a/Documentation/hwmon/adm1275
+++ b/Documentation/hwmon/adm1275
@@ -14,6 +14,10 @@ Supported chips:
Prefix: 'adm1276'
Addresses scanned: -
Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1276.pdf
+ * Analog Devices ADM1278
+ Prefix: 'adm1278'
+ Addresses scanned: -
+ Datasheet: www.analog.com/static/imported-files/data_sheets/ADM1278.pdf
* Analog Devices ADM1293/ADM1294
Prefix: 'adm1293', 'adm1294'
Addresses scanned: -
@@ -25,13 +29,15 @@ Author: Guenter Roeck <linux@roeck-us.net>
Description
-----------
-This driver supports hardware montoring for Analog Devices ADM1075, ADM1275,
-ADM1276, ADM1293, and ADM1294 Hot-Swap Controller and Digital Power Monitors.
+This driver supports hardware monitoring for Analog Devices ADM1075, ADM1275,
+ADM1276, ADM1278, ADM1293, and ADM1294 Hot-Swap Controller and Digital
+Power Monitors.
-ADM1075, ADM1275, ADM1276, ADM1293, and ADM1294 are hot-swap controllers that
-allow a circuit board to be removed from or inserted into a live backplane.
-They also feature current and voltage readback via an integrated 12
-bit analog-to-digital converter (ADC), accessed using a PMBus interface.
+ADM1075, ADM1275, ADM1276, ADM1278, ADM1293, and ADM1294 are hot-swap
+controllers that allow a circuit board to be removed from or inserted into
+a live backplane. They also feature current and voltage readback via an
+integrated 12 bit analog-to-digital converter (ADC), accessed using a
+PMBus interface.
The driver is a client driver to the core PMBus driver. Please see
Documentation/hwmon/pmbus for details on PMBus client drivers.
@@ -96,3 +102,14 @@ power1_reset_history Write any value to reset history.
Power attributes are supported on ADM1075, ADM1276,
ADM1293, and ADM1294.
+
+temp1_input Chip temperature.
+ Temperature attributes are only available on ADM1278.
+temp1_max Maximum chip temperature.
+temp1_max_alarm Temperature alarm.
+temp1_crit Critical chip temperature.
+temp1_crit_alarm Critical temperature high alarm.
+temp1_highest Highest observed temperature.
+temp1_reset_history Write any value to reset history.
+
+ Temperature attributes are supported on ADM1278.
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 34e1569a11ee..bdbac1103783 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -860,6 +860,8 @@ source "arch/arm/mach-meson/Kconfig"
source "arch/arm/mach-moxart/Kconfig"
+source "arch/arm/mach-aspeed/Kconfig"
+
source "arch/arm/mach-mv78xx0/Kconfig"
source "arch/arm/mach-imx/Kconfig"
diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug
index ddbb361267d8..847149e05ad5 100644
--- a/arch/arm/Kconfig.debug
+++ b/arch/arm/Kconfig.debug
@@ -1202,6 +1202,7 @@ choice
config DEBUG_LL_UART_8250
bool "Kernel low-level debugging via 8250 UART"
+ select DEBUG_UART_8250
help
Say Y here if you wish the debug print routes to direct
their output to an 8250 UART. You can use this option
diff --git a/arch/arm/Makefile b/arch/arm/Makefile
index 2c2b28ee4811..299cc137b1ce 100644
--- a/arch/arm/Makefile
+++ b/arch/arm/Makefile
@@ -184,6 +184,7 @@ machine-$(CONFIG_ARCH_LPC32XX) += lpc32xx
machine-$(CONFIG_ARCH_MESON) += meson
machine-$(CONFIG_ARCH_MMP) += mmp
machine-$(CONFIG_ARCH_MOXART) += moxart
+machine-$(CONFIG_ARCH_ASPEED) += aspeed
machine-$(CONFIG_ARCH_MV78XX0) += mv78xx0
machine-$(CONFIG_ARCH_MVEBU) += mvebu
machine-$(CONFIG_ARCH_MXC) += imx
diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index 30bbc3746130..38460b307640 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -777,6 +777,11 @@ dtb-$(CONFIG_ARCH_MEDIATEK) += \
mt8127-moose.dtb \
mt8135-evbp1.dtb
dtb-$(CONFIG_ARCH_ZX) += zx296702-ad1.dtb
+dtb-$(CONFIG_MACH_OPP_PALMETTO_BMC) += \
+ aspeed-bmc-opp-palmetto.dtb \
+ aspeed-bmc-opp-barreleye.dtb \
+ aspeed-bmc-opp-firestone.dtb \
+ aspeed-bmc-opp-garrison.dtb
endif
dtstree := $(srctree)/$(src)
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-barreleye.dts b/arch/arm/boot/dts/aspeed-bmc-opp-barreleye.dts
new file mode 100644
index 000000000000..3146ea908dc4
--- /dev/null
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-barreleye.dts
@@ -0,0 +1,147 @@
+/dts-v1/;
+
+#include "ast2400.dtsi"
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+ model = "Barrelye BMC";
+ compatible = "rackspace,barreleye-bmc", "aspeed,ast2400";
+ ahb {
+ mac0: ethernet@1e660000 {
+ use-nc-si;
+ no-hw-checksum;
+ };
+
+ fmc@1e620000 {
+ reg = < 0x1e620000 0x94
+ 0x20000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "aspeed,fmc";
+ flash@0 {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ /*
+ * Possibly required props:
+ * spi-max-frequency = <>
+ * spi-tx-bus-width = <>
+ * spi-rx-bus-width = <>
+ * m25p,fast-read
+ * spi-cpol if inverse clock polarity (CPOL)
+ * spi-cpha if shifted clock phase (CPHA)
+ */
+#include "aspeed-bmc-opp-flash-layout.dtsi"
+ };
+ };
+ spi@1e630000 {
+ reg = < 0x1e630000 0x18
+ 0x30000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "aspeed,smc";
+ flash {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ label = "pnor";
+ /* spi-max-frequency = <>; */
+ /* m25p,fast-read; */
+ };
+ };
+ apb {
+ i2c: i2c@1e78a040 {
+ i2c0: i2c-bus@40 {
+ eeprom@50 {
+ compatible = "atmel,24c256";
+ reg = <0x50>;
+ pagesize = <64>;
+ };
+ rtc@68 {
+ compatible = "dallas,ds3231";
+ reg = <0x68>;
+ // interrupts = <GPIOF0>
+ };
+ lm75@4a {
+ compatible = "national,lm75";
+ reg = <0x4a>;
+ };
+ };
+ i2c3: i2c-bus@100 {
+ occ@50 {
+ compatible = "ibm,occ-i2c";
+ reg = <0x50>;
+ };
+ occ@51 {
+ compatible = "ibm,occ-i2c";
+ reg = <0x51>;
+ };
+ };
+ i2c4: i2c-bus@140 {
+ adm1278@10 {
+ // P12V_a for CPU0
+ compatible = "adi,adm1278";
+ reg = <0x10>;
+ sense-resistor = <500>;
+ };
+ eeprom@54 {
+ compatible = "atmel,24c256";
+ reg = <0x54>;
+ pagesize = <64>;
+ };
+ };
+ i2c5: i2c-bus@180 {
+ adm1278@10 {
+ // P12V_b for CPU1
+ compatible = "adi,adm1278";
+ reg = <0x10>;
+ sense-resistor = <500>;
+ };
+ };
+ i2c6: i2c-bus@1c0 {
+ nct7904@2d {
+ compatible = "nuvoton,nct7904";
+ reg = <0x2d>;
+ };
+ nct7904@2e {
+ compatible = "nuvoton,nct7904";
+ reg = <0x2e>;
+ };
+ eeprom@51 {
+ compatible = "atmel,24c02";
+ reg = <0x51>;
+ pagesize = <8>;
+ };
+ eeprom@55 {
+ compatible = "atmel,24c02";
+ reg = <0x55>;
+ pagesize = <8>;
+ };
+ adm1278@10 {
+ // P12V_c for HDD and IO board
+ compatible = "adi,adm1278";
+ reg = <0x10>;
+ sense-resistor = <500>;
+ };
+ };
+ };
+ };
+ };
+
+
+ leds {
+ compatible = "gpio-leds";
+
+ heartbeat {
+ gpios = <&gpio 140 GPIO_ACTIVE_HIGH>;
+ default-state = "keep";
+ };
+ identify {
+ gpios = <&gpio 58 GPIO_ACTIVE_LOW>;
+ default-state = "keep";
+ };
+ beep {
+ gpios = <&gpio 111 GPIO_ACTIVE_HIGH>;
+ default-state = "keep";
+ };
+ };
+};
+
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-firestone.dts b/arch/arm/boot/dts/aspeed-bmc-opp-firestone.dts
new file mode 100644
index 000000000000..51b03b7f73a0
--- /dev/null
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-firestone.dts
@@ -0,0 +1,136 @@
+/dts-v1/;
+
+#include "ast2400.dtsi"
+
+/ {
+ model = "Firestone BMC";
+ compatible = "ibm,firestone-bmc", "aspeed,ast2400";
+
+ aliases {
+ serial0 = &uart5;
+ };
+
+ chosen {
+ stdout-path = &uart5;
+ bootargs = "console=ttyS4,38400";
+ };
+
+ memory {
+ reg = < 0x40000000 0x20000000 >;
+ };
+
+ ahb {
+ fmc@1e620000 {
+ reg = < 0x1e620000 0x94
+ 0x20000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "aspeed,fmc";
+ flash@0 {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ /*
+ * Possibly required props:
+ * spi-max-frequency = <>
+ * spi-tx-bus-width = <>
+ * spi-rx-bus-width = <>
+ * m25p,fast-read
+ * spi-cpol if inverse clock polarity (CPOL)
+ * spi-cpha if shifted clock phase (CPHA)
+ */
+#include "aspeed-bmc-opp-flash-layout.dtsi"
+ };
+ };
+ mac0: ethernet@1e660000 {
+ no-hw-checksum;
+ };
+ spi@1e630000 {
+ reg = < 0x1e630000 0x18
+ 0x30000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "aspeed,smc";
+ flash {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ label = "pnor";
+ /* spi-max-frequency = <>; */
+ /* m25p,fast-read; */
+ };
+ };
+ apb {
+ i2c: i2c@1e78a040 {
+ i2c0: i2c-bus@40 {
+ // grounded
+ };
+ i2c1: i2c-bus@80 {
+ // grounded
+ };
+ i2c2: i2c-bus@c0 {
+ // i2c mux
+ };
+ i2c3: i2c-bus@100 {
+ // i2c hub PCA9516A
+ };
+ i2c4: i2c-bus@140 {
+ // turismo
+ };
+ i2c5: i2c-bus@180 {
+ tmp423@98 {
+ compatible = "ti,tmp423";
+ reg = <0x80000098>;
+ };
+ };
+ i2c6: i2c-bus@1c0 {
+ // nc
+ };
+ i2c7: i2c-bus@300 {
+ // nc
+ };
+ i2c8: i2c-bus@340 {
+ // FSI
+ status = "disabled";
+ };
+ i2c9: i2c-bus@380 {
+ // 4 way mux
+ };
+ i2c10: i2c-bus@3c0 {
+ // 4 way mux
+ };
+ i2c11: i2c-bus@400 {
+ status = "okay";
+ leds@c0 {
+ compatible = "pca,pca9552led";
+ reg = <0x800000c0>;
+ // led9 - led_fault_n
+ // led10 - pwr_led_n
+ // led11 - rear_id_led_n
+ };
+ rtc@d0 {
+ compatible = "dallas,ds3231";
+ reg = <0x800000d0>;
+ };
+ si5338a@e2 {
+ // SiLabs clock generator
+ reg =<0x800000e2>;
+ };
+
+ idt@d6 {
+ // IDT 9DBV0641 clock buffer
+ reg = <0x800000d6>;
+ };
+
+ tpm@a3 {
+ reg = <0x800000a3>;
+ };
+ };
+ i2c12: i2c-bus@440 {
+ // i2c hub PCA9516A
+ };
+ i2c13: i2c-bus@480 {
+ // i2c hub PCA9516A
+ };
+ };
+ };
+ };
+};
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-flash-layout.dtsi b/arch/arm/boot/dts/aspeed-bmc-opp-flash-layout.dtsi
new file mode 100644
index 000000000000..ca8639b52f6d
--- /dev/null
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-flash-layout.dtsi
@@ -0,0 +1,28 @@
+/* This file is the label for the bmc primary flash and its partitions */
+ label = "bmc";
+ #address-cells = < 1 >;
+ #size-cells = < 1 >;
+ u-boot {
+ reg = < 0 0x60000 >;
+ label = "u-boot";
+ };
+ u-boot-env {
+ reg = < 0x60000 0x20000 >;
+ label = "u-boot-env";
+ };
+ kernel {
+ reg = < 0x80000 0x280000 >;
+ label = "kernel";
+ };
+ initramfs {
+ reg = < 0x300000 0x1c0000 >;
+ label = "initramfs";
+ };
+ rofs {
+ reg = < 0x4c0000 0x1740000 >;
+ label = "rofs";
+ };
+ rwfs {
+ reg = < 0x1c00000 0x400000 >;
+ label = "rwfs";
+ };
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-garrison.dts b/arch/arm/boot/dts/aspeed-bmc-opp-garrison.dts
new file mode 100644
index 000000000000..8fcf14777701
--- /dev/null
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-garrison.dts
@@ -0,0 +1,85 @@
+/dts-v1/;
+
+#include "ast2400.dtsi"
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+ model = "Garrison BMC";
+ compatible = "ibm,garrison-bmc", "aspeed,ast2400";
+
+ ahb {
+ mac0: ethernet@1e660000 {
+ use-nc-si;
+ no-hw-checksum;
+ };
+
+ fmc@1e620000 {
+ reg = < 0x1e620000 0x94
+ 0x20000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "aspeed,fmc";
+ flash@0 {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ /*
+ * Possibly required props:
+ * spi-max-frequency = <>
+ * spi-tx-bus-width = <>
+ * spi-rx-bus-width = <>
+ * m25p,fast-read
+ * spi-cpol if inverse clock polarity (CPOL)
+ * spi-cpha if shifted clock phase (CPHA)
+ */
+#include "aspeed-bmc-opp-flash-layout.dtsi"
+ };
+ };
+ spi@1e630000 {
+ reg = < 0x1e630000 0x18
+ 0x30000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "aspeed,smc";
+ flash {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ label = "pnor";
+ /* spi-max-frequency = <>; */
+ /* m25p,fast-read; */
+ };
+ };
+ apb {
+ i2c: i2c@1e78a040 {
+ i2c4: i2c-bus@140 {
+ occ@50 {
+ compatible = "ibm,occ-i2c";
+ reg = <0x50>;
+ };
+ };
+ i2c5: i2c-bus@180 {
+ occ@50 {
+ compatible = "ibm,occ-i2c";
+ reg = <0x50>;
+ };
+ };
+ i2c10: i2c-bus@3c0 {
+ status = "okay";
+ };
+ i2c11: i2c-bus@400 {
+ status = "okay";
+
+ rtc@68 {
+ compatible = "dallas,ds3231";
+ reg = <0x68>;
+ };
+ };
+ i2c12: i2c-bus@440 {
+ status = "okay";
+ };
+ i2c13: i2c-bus@480 {
+ status = "okay";
+ };
+ };
+ };
+ };
+};
diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
new file mode 100644
index 000000000000..cde194e7ca13
--- /dev/null
+++ b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
@@ -0,0 +1,99 @@
+/dts-v1/;
+
+#include "ast2400.dtsi"
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+ model = "Palmetto BMC";
+ compatible = "tyan,palmetto-bmc", "aspeed,ast2400";
+
+ ahb {
+ mac0: ethernet@1e660000 {
+ use-nc-si;
+ no-hw-checksum;
+ };
+
+ fmc@1e620000 {
+ reg = < 0x1e620000 0x94
+ 0x20000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "aspeed,fmc";
+ flash@0 {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ /*
+ * Possibly required props:
+ * spi-max-frequency = <>
+ * spi-tx-bus-width = <>
+ * spi-rx-bus-width = <>
+ * m25p,fast-read
+ * spi-cpol if inverse clock polarity (CPOL)
+ * spi-cpha if shifted clock phase (CPHA)
+ */
+#include "aspeed-bmc-opp-flash-layout.dtsi"
+ };
+ };
+ spi@1e630000 {
+ reg = < 0x1e630000 0x18
+ 0x30000000 0x02000000 >;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "aspeed,smc";
+ flash {
+ reg = < 0 >;
+ compatible = "jedec,spi-nor" ;
+ label = "pnor";
+ /* spi-max-frequency = <>; */
+ /* m25p,fast-read; */
+ };
+ };
+ apb {
+ i2c: i2c@1e78a040 {
+ i2c0: i2c-bus@40 {
+ eeprom@50 {
+ compatible = "atmel,24c256";
+ reg = <0x50>;
+ pagesize = <64>;
+ };
+ rtc@68 {
+ compatible = "dallas,ds3231";
+ reg = <0x68>;
+ // interrupts = <GPIOF0>
+ };
+ };
+
+ i2c2: i2c-bus@c0 {
+ tmp423@4c {
+ compatible = "ti,tmp423";
+ reg = <0x4c>;
+ };
+ };
+
+ i2c3: i2c-bus@100 {
+ occ@50 {
+ compatible = "ibm,occ-i2c";
+ reg = <0x50>;
+ };
+ };
+ };
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ heartbeat {
+ gpios = <&gpio 140 GPIO_ACTIVE_LOW>;
+ };
+
+ power {
+ gpios = <&gpio 141 GPIO_ACTIVE_LOW>;
+ };
+
+ identify {
+ gpios = <&gpio 2 GPIO_ACTIVE_LOW>;
+ };
+
+ };
+};
diff --git a/arch/arm/boot/dts/ast2400.dtsi b/arch/arm/boot/dts/ast2400.dtsi
new file mode 100644
index 000000000000..2198fd46696d
--- /dev/null
+++ b/arch/arm/boot/dts/ast2400.dtsi
@@ -0,0 +1,361 @@
+#include "skeleton.dtsi"
+
+/ {
+ model = "Palmetto BMC";
+ compatible = "tyan,palmetto-bmc", "aspeed,ast2400";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ interrupt-parent = <&vic>;
+
+ aliases {
+ serial0 = &uart5;
+ };
+
+ chosen {
+ stdout-path = &uart5;
+ bootargs = "console=ttyS4,38400";
+ };
+
+ memory {
+ reg = < 0x40000000 0x10000000 >;
+ };
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu@0 {
+ compatible = "arm,arm926ej-s";
+ device_type = "cpu";
+ reg = <0>;
+ };
+ };
+
+ // FIXME
+ clocks {
+ // Do a proper driver... for now, we know the straps
+ // and uboot config on palmetto are:
+ // - CLKIN is 48Mhz
+ // - HPLL is 384Mhz
+ // - CPU:AHB is strapped 2:1
+ // - PCLK is HPLL/8 = 48Mhz
+ clk_apb: clk_apb {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <48000000>;
+ };
+ clk_hpll: clk_hpll {
+ #clock-cells = <0>;
+ compatible = "fixed-clock";
+ clock-frequency = <384000000>;
+ };
+ };
+
+ ahb {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ vic: interrupt-controller@1e6c0080 {
+ compatible = "aspeed,new-vic";
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ valid-sources = < 0xffffffff 0x0007ffff>;
+ reg = <0x1e6c0080 0x80>;
+ };
+
+ mac0: ethernet@1e660000 {
+ compatible = "faraday,ftgmac100", "aspeed,ast2400-mac";
+ reg = <0x1e660000 0x180>;
+ interrupts = <2>;
+ };
+
+ apb {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ sram@1e720000 {
+ compatible = "mmio-sram";
+ reg = <0x1e720000 0x8000>; // 32K
+ };
+
+ ibt@1e789140 {
+ compatible = "aspeed,bt-host";
+ reg = <0x1e789140 0x18>;
+ interrupts = <8>;
+ };
+
+ i2c: i2c@1e78a040 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ #interrupt-cells = <1>;
+
+ compatible = "aspeed,ast2400-i2c-controller";
+ reg = <0x1e78a000 0x40>;
+ ranges = <0 0x1e78a000 0x1000>;
+ interrupts = <12>;
+ clocks = <&clk_apb>;
+ clock-ranges;
+ interrupt-controller;
+
+ i2c0: i2c-bus@40 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x40 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <0>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <0>;
+ interrupt-parent = <&i2c>;
+ };
+
+ i2c1: i2c-bus@80 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x80 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <1>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <1>;
+ };
+
+ i2c2: i2c-bus@c0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0xC0 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <2>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <2>;
+ };
+
+ i2c3: i2c-bus@100 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x100 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <3>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <3>;
+ };
+
+ i2c4: i2c-bus@140 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x140 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <4>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <4>;
+ };
+
+ i2c5: i2c-bus@180 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x180 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <5>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <5>;
+ };
+
+ i2c6: i2c-bus@1c0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x1C0 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <6>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <6>;
+ };
+
+ i2c7: i2c-bus@300 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x300 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <7>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <7>;
+ };
+
+ i2c8: i2c-bus@340 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x340 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <8>;
+ clock-frequency = <100000>;
+ status = "okay";
+ interrupts = <8>;
+ };
+
+ i2c9: i2c-bus@380 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x380 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <9>;
+ clock-frequency = <100000>;
+ status = "disabled";
+ interrupts = <9>;
+ };
+
+ i2c10: i2c-bus@3c0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x3c0 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <10>;
+ clock-frequency = <100000>;
+ status = "disabled";
+ interrupts = <10>;
+ };
+
+ i2c11: i2c-bus@400 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x400 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <11>;
+ clock-frequency = <100000>;
+ status = "disabled";
+ interrupts = <11>;
+ };
+
+ i2c12: i2c-bus@440 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x440 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <12>;
+ clock-frequency = <100000>;
+ status = "disabled";
+ interrupts = <12>;
+ };
+
+ i2c13: i2c-bus@480 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x480 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <13>;
+ clock-frequency = <100000>;
+ status = "disabled";
+ interrupts = <13>;
+ };
+
+ };
+
+ syscon: syscon@1e6e2000 {
+ compatible = "aspeed,syscon", "syscon";
+ reg = <0x1e6e2000 0x1a8>;
+ interrupts = <19>;
+ clocks = <&clk_apb>;
+ status = "okay";
+ };
+
+ wdt: wdt@1e785000 {
+ compatible = "aspeed,wdt", "wdt";
+ reg = <0x1e785000 0x1c4>;
+ interrupts = <27>;
+ clocks = <&clk_apb>;
+ };
+
+ rtc: rtc@1e781000 {
+ compatible = "aspeed,rtc";
+ reg = <0x1e781000 0x18>;
+ status = "disabled";
+ };
+
+ timer: timer@98400000 {
+ compatible = "aspeed,timer";
+ reg = <0x1e782000 0x90>;
+ // The moxart_timer driver registers only one interrupt
+ // and assumes it's for timer 1
+ //interrupts = <16 17 18 35 36 37 38 39>;
+ interrupts = <16>;
+ clocks = <&clk_apb>;
+ };
+
+ gpio: gpio@1e780000 {
+ #gpio-cells = <2>;
+ gpio-controller;
+ compatible = "aspeed,ast2400-gpio";
+ reg = <0x1e780000 0x1000>;
+ interrupts = <20>;
+ };
+
+ uart1: serial@1e783000 {
+ compatible = "ns16550a";
+ reg = <0x1e783000 0x1000>;
+ reg-shift = <2>;
+ interrupts = <9>;
+ clock-frequency = < 1843200 >;
+ no-loopback-test;
+ };
+ uart2: serial@1e78d000 {
+ compatible = "ns16550a";
+ reg = <0x1e78d000 0x1000>;
+ reg-shift = <2>;
+ interrupts = <32>;
+ clock-frequency = < 1843200 >;
+ no-loopback-test;
+ };
+ /* APSS UART */
+ uart3: serial@1e78e000 {
+ compatible = "ns16550a";
+ reg = <0x1e78e000 0x1000>;
+ reg-shift = <2>;
+ interrupts = <33>;
+ clock-frequency = < 1843200 >;
+ no-loopback-test;
+ };
+
+ /* Host UART */
+ uart4: serial@1e78f000 {
+ compatible = "ns16550a";
+ reg = <0x1e78f000 0x1000>;
+ reg-shift = <2>;
+ interrupts = <34>;
+ clock-frequency = < 1843200 >;
+ current-speed = < 115200 >;
+ no-loopback-test;
+ };
+#if 1
+ /* BMC UART */
+ uart5: serial@1e784000 {
+ compatible = "ns16550a";
+ reg = <0x1e784000 0x1000>;
+ reg-shift = <2>;
+ interrupts = <10>;
+ clock-frequency = < 1843200 >;
+ current-speed = < 38400 >;
+ no-loopback-test;
+ };
+#endif
+
+ uart6: vuart@1e787000 {
+ compatible = "aspeed,vuart";
+ reg = <0x1e787000 0x1000>;
+ reg-shift = <2>;
+ interrupts = <8>;
+ clock-frequency = < 1843200 >;
+ current-speed = < 38400 >;
+ no-loopback-test;
+ };
+
+ };
+ };
+};
diff --git a/arch/arm/configs/aspeed_defconfig b/arch/arm/configs/aspeed_defconfig
new file mode 100644
index 000000000000..dbb8e6ba4bb3
--- /dev/null
+++ b/arch/arm/configs/aspeed_defconfig
@@ -0,0 +1,163 @@
+CONFIG_KERNEL_XZ=y
+CONFIG_SYSVIPC=y
+CONFIG_FHANDLE=y
+CONFIG_IRQ_DOMAIN_DEBUG=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_LOG_BUF_SHIFT=14
+CONFIG_CGROUPS=y
+CONFIG_BLK_DEV_INITRD=y
+# CONFIG_RD_BZIP2 is not set
+# CONFIG_RD_LZMA is not set
+# CONFIG_RD_LZO is not set
+# CONFIG_RD_LZ4 is not set
+CONFIG_CC_OPTIMIZE_FOR_SIZE=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_SHMEM=y
+# CONFIG_AIO is not set
+CONFIG_EMBEDDED=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_SLAB=y
+CONFIG_CC_STACKPROTECTOR_STRONG=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+# CONFIG_BLOCK is not set
+# CONFIG_ARCH_MULTI_V7 is not set
+CONFIG_ARCH_ASPEED=y
+CONFIG_MACH_OPP_PALMETTO_BMC=y
+CONFIG_ARM_KERNMEM_PERMS=y
+CONFIG_AEABI=y
+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
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_INET=y
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_BOOTP=y
+CONFIG_IP_PNP_RARP=y
+# CONFIG_INET_XFRM_MODE_TRANSPORT is not set
+# 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_NET_NCSI=y
+# CONFIG_WIRELESS is not set
+CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug"
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+# CONFIG_PREVENT_FIRMWARE_BUILD is not set
+CONFIG_MTD=y
+CONFIG_MTD_PARTITIONED_MASTER=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_ASPEED_FLASH_SPI=y
+CONFIG_ASPEED_BT_IPMI_HOST=y
+CONFIG_EEPROM_AT24=y
+CONFIG_EEPROM_93CX6=y
+CONFIG_NETDEVICES=y
+# 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_EZCHIP is not set
+CONFIG_FTGMAC100=y
+# CONFIG_NET_VENDOR_HISILICON is not set
+# CONFIG_NET_VENDOR_INTEL is not set
+# CONFIG_NET_VENDOR_MARVELL is not set
+# CONFIG_NET_VENDOR_MICREL is not set
+# CONFIG_NET_VENDOR_NATSEMI is not set
+# CONFIG_NET_VENDOR_QUALCOMM is not set
+# CONFIG_NET_VENDOR_RENESAS is not set
+# CONFIG_NET_VENDOR_ROCKER is not set
+# CONFIG_NET_VENDOR_SAMSUNG is not set
+# CONFIG_NET_VENDOR_SEEQ is not set
+# CONFIG_NET_VENDOR_SMSC is not set
+# CONFIG_NET_VENDOR_STMICRO is not set
+# CONFIG_NET_VENDOR_SYNOPSYS is not set
+# CONFIG_NET_VENDOR_VIA is not set
+# CONFIG_NET_VENDOR_WIZNET is not set
+CONFIG_BROADCOM_PHY=y
+# CONFIG_WLAN is not set
+# CONFIG_INPUT is not set
+# CONFIG_SERIO is not set
+# CONFIG_VT is not set
+# CONFIG_LEGACY_PTYS is not set
+# CONFIG_DEVKMEM is not set
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SERIAL_8250_NR_UARTS=6
+CONFIG_SERIAL_8250_RUNTIME_UARTS=6
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_ASPEED_VUART=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_HW_RANDOM=y
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_SLAVE_EEPROM=y
+CONFIG_GPIO_ASPEED=y
+CONFIG_SENSORS_POWER8_OCC_I2C=y
+CONFIG_PMBUS=y
+CONFIG_SENSORS_ADM1275=y
+CONFIG_SENSORS_TMP421=y
+CONFIG_WATCHDOG=y
+CONFIG_ASPEED_24xx_WATCHDOG=y
+# CONFIG_USB_SUPPORT is not set
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_TRIGGERS=y
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_GPIO=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_DS1307=y
+# CONFIG_IOMMU_SUPPORT is not set
+CONFIG_FIRMWARE_MEMMAP=y
+CONFIG_FANOTIFY=y
+CONFIG_OVERLAY_FS=y
+CONFIG_CONFIGFS_FS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_TMPFS_XATTR=y
+CONFIG_JFFS2_FS=y
+CONFIG_JFFS2_SUMMARY=y
+CONFIG_NFS_FS=y
+# CONFIG_NFS_V2 is not set
+CONFIG_NFS_V3_ACL=y
+CONFIG_ROOT_NFS=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_STRIP_ASM_SYMS=y
+CONFIG_DEBUG_KMEMLEAK=y
+CONFIG_DEBUG_SHIRQ=y
+CONFIG_LOCKUP_DETECTOR=y
+# CONFIG_SCHED_DEBUG is not set
+CONFIG_SCHED_STACK_END_CHECK=y
+CONFIG_DEBUG_RT_MUTEXES=y
+CONFIG_DEBUG_WW_MUTEX_SLOWPATH=y
+# CONFIG_FTRACE is not set
+CONFIG_MEMTEST=y
+CONFIG_DEBUG_USER=y
+CONFIG_DEBUG_LL=y
+CONFIG_DEBUG_LL_UART_8250=y
+CONFIG_DEBUG_UART_PHYS=0x1e784000
+CONFIG_DEBUG_UART_VIRT=0xe8784000
+CONFIG_EARLY_PRINTK=y
+CONFIG_DEBUG_SET_MODULE_RONX=y
+# CONFIG_CRYPTO_ECHAINIV is not set
+# CONFIG_CRYPTO_HW is not set
+# CONFIG_XZ_DEC_X86 is not set
+# CONFIG_XZ_DEC_POWERPC is not set
+# CONFIG_XZ_DEC_IA64 is not set
+# CONFIG_XZ_DEC_SPARC is not set
diff --git a/arch/arm/mach-aspeed/Kconfig b/arch/arm/mach-aspeed/Kconfig
new file mode 100644
index 000000000000..7941e0312298
--- /dev/null
+++ b/arch/arm/mach-aspeed/Kconfig
@@ -0,0 +1,27 @@
+menuconfig ARCH_ASPEED
+ bool "ASpeed BMC SoCs" if ARCH_MULTI_V5
+ select CPU_ARM926T
+ select CLKSRC_MMIO
+ select GENERIC_IRQ_CHIP
+ select ARCH_REQUIRE_GPIOLIB
+ select I2C_ASPEED
+ select PHYLIB if NETDEVICES
+ select MFD_SYSCON
+ select SRAM
+ help
+ Say Y here if you want to run your kernel on hardware with an
+ ASpeed BMC SoC. Tested on AST2400
+
+if ARCH_ASPEED
+
+config MACH_OPP_PALMETTO_BMC
+ bool "OpenPower Palmetto"
+ depends on ARCH_ASPEED
+ select RTC_DRV_ASPEED
+ select ASPEED_GPIO
+ select GPIO_SYSFS
+ help
+ Say Y here if you intend to run this kernel on the BMC
+ of an OpenPower "Palmetto" eval board
+
+endif
diff --git a/arch/arm/mach-aspeed/Makefile b/arch/arm/mach-aspeed/Makefile
new file mode 100644
index 000000000000..3a4f025dd520
--- /dev/null
+++ b/arch/arm/mach-aspeed/Makefile
@@ -0,0 +1,3 @@
+# Object file lists.
+
+obj-$(CONFIG_ARCH_ASPEED) += aspeed.o
diff --git a/arch/arm/mach-aspeed/aspeed.c b/arch/arm/mach-aspeed/aspeed.c
new file mode 100755
index 000000000000..1102b896cab0
--- /dev/null
+++ b/arch/arm/mach-aspeed/aspeed.c
@@ -0,0 +1,261 @@
+#include <linux/init.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/clk-provider.h>
+#include <asm/mach-types.h>
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+#include "ast2400.h"
+
+// XXX TEMP HACKERY
+//
+// To be replaced by proper clock, pinmux and syscon drivers operating
+// from DT parameters
+
+static void __init aspeed_dt_init(void)
+{
+ of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
+}
+
+static const struct of_device_id aspeed_clk_match[] __initconst = {
+ {
+ .compatible = "fixed-clock",
+ .data = of_fixed_clk_setup,
+ },
+ {}
+};
+
+void __init aspeed_clk_init(void __iomem *base)
+{
+ of_clk_init(aspeed_clk_match);
+}
+
+#define AST_IO_VA 0xf0000000
+#define AST_IO_PA 0x1e600000
+#define AST_IO_SZ 0x00200000
+
+#define AST_IO(__pa) ((void __iomem *)(((__pa) & 0x001fffff) | AST_IO_VA))
+
+static struct map_desc aspeed_io_desc[] __initdata __maybe_unused = {
+ {
+ .virtual = AST_IO_VA,
+ .pfn = __phys_to_pfn(AST_IO_PA),
+ .length = AST_IO_SZ,
+ .type = MT_DEVICE
+ },
+};
+
+
+#define UART_RBR 0
+#define UART_IER 1
+#define UART_FCR 2
+#define UART_LCR 3
+#define UART_MCR 4
+#define UART_LSR 5
+#define UART_MSR 6
+#define UART_SCR 7
+#define UART_THR UART_RBR
+#define UART_IIR UART_FCR
+#define UART_DLL UART_RBR
+#define UART_DLM UART_IER
+#define UART_DLAB UART_LCR
+
+#define LSR_DR 0x01 /* Data ready */
+#define LSR_OE 0x02 /* Overrun */
+#define LSR_PE 0x04 /* Parity error */
+#define LSR_FE 0x08 /* Framing error */
+#define LSR_BI 0x10 /* Break */
+#define LSR_THRE 0x20 /* Xmit holding register empty */
+#define LSR_TEMT 0x40 /* Xmitter empty */
+#define LSR_ERR 0x80 /* Error */
+
+#define LCR_DLAB 0x80
+
+static void ast_uart_out(unsigned int reg, u8 data)
+{
+ writeb(data, AST_IO(0x1E78F000 + reg * 4));
+}
+
+static void ast_host_uart_setup(unsigned int speed, unsigned int clock)
+{
+ unsigned int dll, base_bauds;
+
+ if (clock == 0)
+ clock = 1843200;
+ if (speed == 0)
+ speed = 9600;
+
+ base_bauds = clock / 16;
+ dll = base_bauds / speed;
+
+ ast_uart_out(UART_LCR, 0x00);
+ ast_uart_out(UART_IER, 0xff);
+ ast_uart_out(UART_IER, 0x00);
+ ast_uart_out(UART_LCR, LCR_DLAB);
+ ast_uart_out(UART_DLL, dll & 0xff);
+ ast_uart_out(UART_DLM, dll >> 8);
+ /* 8 data, 1 stop, no parity */
+ ast_uart_out(UART_LCR, 0x3);
+ /* RTS/DTR */
+ ast_uart_out(UART_MCR, 0x3);
+ /* Clear & enable FIFOs */
+ ast_uart_out(UART_FCR, 0x7);
+}
+
+static void __init do_common_setup(void)
+{
+ /* Enable LPC FWH cycles, Enable LPC to AHB bridge */
+ writel(0x00000500, AST_IO(AST_BASE_LPC | 0x80));
+
+ /* Flash controller */
+ writel(0x00000003, AST_IO(AST_BASE_SPI | 0x00));
+ writel(0x00002404, AST_IO(AST_BASE_SPI | 0x04));
+
+ /* Set UART routing */
+ writel(0x00000000, AST_IO(AST_BASE_LPC | 0x9c));
+
+ /* SCU setup */
+ writel(0x01C000FF, AST_IO(AST_BASE_SCU | 0x88));
+ writel(0xC1C000FF, AST_IO(AST_BASE_SCU | 0x8c));
+ writel(0x003FA008, AST_IO(AST_BASE_SCU | 0x90));
+
+ /* Setup scratch registers */
+ writel(0x00000042, AST_IO(AST_BASE_LPC | 0x170));
+ writel(0x00008000, AST_IO(AST_BASE_LPC | 0x174));
+}
+
+static void __init do_barreleye_setup(void)
+{
+ u32 reg;
+
+ do_common_setup();
+
+ /* Setup PNOR address mapping for 64M flash */
+ writel(0x30000C00, AST_IO(AST_BASE_LPC | 0x88));
+ writel(0xFC0003FF, AST_IO(AST_BASE_LPC | 0x8C));
+
+ /* GPIO setup */
+ writel(0x9E82FCE7, AST_IO(AST_BASE_GPIO | 0x00));
+ writel(0x0370E677, AST_IO(AST_BASE_GPIO | 0x04));
+
+ /* SCU setup */
+ writel(0x01C00000, AST_IO(AST_BASE_SCU | 0x88));
+
+ /* To enable GPIOE0 pass through function debounce mode */
+ writel(0x010FFFFF, AST_IO(AST_BASE_SCU | 0xA8));
+
+ /*
+ * Do read/modify/write on power gpio to prevent resetting power on
+ * reboot
+ */
+ reg = readl(AST_IO(AST_BASE_GPIO | 0x20));
+ reg |= 0xCFC8F7FD;
+ writel(reg, AST_IO(AST_BASE_GPIO | 0x20));
+ writel(0xC738F20A, AST_IO(AST_BASE_GPIO | 0x24));
+ writel(0x0031FFAF, AST_IO(AST_BASE_GPIO | 0x80));
+
+ /* Select TIMER3 as debounce timer */
+ writel(0x00000001, AST_IO(AST_BASE_GPIO | 0x48));
+ writel(0x00000001, AST_IO(AST_BASE_GPIO | 0x4C));
+
+ /* Set debounce timer to 480000 cycles, with a pclk of 48MHz,
+ * corresponds to 20 ms. This time was found by experimentation */
+ writel(0x000EA600, AST_IO(AST_BASE_GPIO | 0x58));
+}
+
+static void __init do_palmetto_setup(void)
+{
+ do_common_setup();
+
+ /* Setup PNOR address mapping for 32M flash */
+ writel(0x30000E00, AST_IO(AST_BASE_LPC | 0x88));
+ writel(0xFE0001FF, AST_IO(AST_BASE_LPC | 0x8C));
+
+ /* GPIO setup */
+ writel(0x13008CE7, AST_IO(AST_BASE_GPIO | 0x00));
+ writel(0x0370E677, AST_IO(AST_BASE_GPIO | 0x04));
+ writel(0xDF48F7FF, AST_IO(AST_BASE_GPIO | 0x20));
+ writel(0xC738F202, AST_IO(AST_BASE_GPIO | 0x24));
+
+ /* SCU setup */
+ writel(0x01C0007F, AST_IO(AST_BASE_SCU | 0x88));
+}
+
+static void __init do_garrison_setup(void)
+{
+ do_common_setup();
+
+ /* Setup PNOR address mapping for 64M flash */
+ writel(0x30000C00, AST_IO(AST_BASE_LPC | 0x88));
+ writel(0xFC0003FF, AST_IO(AST_BASE_LPC | 0x8C));
+
+ /* SCU setup */
+ writel(0xd7000000, AST_IO(AST_BASE_SCU | 0x88));
+}
+
+#define SCU_PASSWORD 0x1688A8A8
+
+static void __init aspeed_init_early(void)
+{
+ u32 reg;
+
+ /* Unlock SCU */
+ writel(SCU_PASSWORD, AST_IO(AST_BASE_SCU));
+
+ /* Reset AHB bridges */
+ writel(0x02, AST_IO(AST_BASE_SCU | 0x04));
+
+ // XXX UART stuff to fix to pinmux & co
+ writel(0x02010023, AST_IO(AST_BASE_LPC | 0x9c));
+ writel(0xcb000000, AST_IO(AST_BASE_SCU | 0x80));
+ writel(0x00fff0c0, AST_IO(AST_BASE_SCU | 0x84));
+ writel(0x10CC5E80, AST_IO(AST_BASE_SCU | 0x0c));
+
+ /* We enable the UART clock divisor in the SCU's misc control
+ * register, as the baud rates in aspeed.dtb all assume that the
+ * divisor is active
+ */
+ reg = readl(AST_IO(AST_BASE_SCU | 0x2c));
+ writel(reg | 0x00001000, AST_IO(AST_BASE_SCU | 0x2c));
+ ast_host_uart_setup(115200,0);
+
+ writel(0, AST_IO(AST_BASE_WDT | 0x0c));
+ writel(0, AST_IO(AST_BASE_WDT | 0x2c));
+
+ /*
+ * Ensure all IPs except GPIO and LPC are reset on watchdog expiry
+ */
+ writel(0x001fdff3, AST_IO(AST_BASE_SCU | 0x9c));
+
+ /*
+ * Temporary setup of AST registers until pinmux driver is complete
+ */
+ if (of_machine_is_compatible("rackspace,barreleye-bmc"))
+ do_barreleye_setup();
+ if (of_machine_is_compatible("tyan,palmetto-bmc"))
+ do_palmetto_setup();
+ if (of_machine_is_compatible("ibm,garrison-bmc"))
+ do_garrison_setup();
+
+}
+
+static void __init aspeed_map_io(void)
+{
+ iotable_init(aspeed_io_desc, ARRAY_SIZE(aspeed_io_desc));
+ debug_ll_io_init();
+
+ printk("SOC Rev: %08x\n", readl(AST_IO(AST_BASE_SCU | 0x7c)));
+}
+
+static const char *const aspeed_dt_match[] __initconst = {
+ "aspeed,ast2400",
+ NULL,
+};
+
+DT_MACHINE_START(aspeed_dt, "ASpeed SoC")
+ .map_io = aspeed_map_io,
+ .init_early = aspeed_init_early,
+ .init_machine = aspeed_dt_init,
+ .dt_compat = aspeed_dt_match,
+MACHINE_END
diff --git a/arch/arm/mach-aspeed/ast2400.h b/arch/arm/mach-aspeed/ast2400.h
new file mode 100644
index 000000000000..1daf85cab5d6
--- /dev/null
+++ b/arch/arm/mach-aspeed/ast2400.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2015 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 AST2400_H
+#define AST2400_H
+
+/* Periperhal base addresses */
+#define AST_BASE_LEGACY_SRAM 0x10000000 /* Legacy BMC Static Memory */
+#define AST_BASE_LEGACY_SMC 0x16000000 /* Legacy BMC Static Memory Controller (SMC) */
+#define AST_BASE_APBC 0x1E600000 /* AHB Bus Controller (AHBC) */
+#define AST_BASE_FMC 0x1E620000 /* New BMC Static Memory Controller (FMC) */
+#define AST_BASE_SPI 0x1E630000 /* SPI Memory Controller */
+#define AST_BASE_MIC 0x1E640000 /* Memory Integrity Check Controller (MIC) */
+#define AST_BASE_MAC1 0x1E660000 /* Fast Ethernet MAC Controller #1 (MAC1) */
+#define AST_BASE_MAC2 0x1E680000 /* Fast Ethernet MAC Controller #2 (MAC2) */
+#define AST_BASE_USB2HUB 0x1E6A0000 /* USB2.0 Hub Controller */
+#define AST_BASE_USB2HC 0x1E6A1000 /* USB2.0 Host Controller */
+#define AST_BASE_USB1HC 0x1E6B0000 /* USB1.1 Host Controller */
+#define AST_BASE_VIC 0x1E6C0000 /* Interrupt Controller (VIC) */
+#define AST_BASE_MMC 0x1E6E0000 /* SDRAM Controller (MMC) */
+#define AST_BASE_USB1 0x1E6E1000 /* USB1.1 Controller */
+#define AST_BASE_SCU 0x1E6E2000 /* System Control Unit (SCU) */
+#define AST_BASE_HACE 0x1E6E3000 /* Hash & Crypto Engine (HACE) */
+#define AST_BASE_JTAG 0x1E6E4000 /* JTAG Master */
+#define AST_BASE_CRT 0x1E6E6000 /* Graphics Display Controller (CRT) */
+#define AST_BASE_DMA 0x1E6E7000 /* X-DMA Controller */
+#define AST_BASE_MCTP 0x1E6E8000 /* MCTP Controller */
+#define AST_BASE_ADC 0x1E6E9000 /* ADC Voltage Monitor */
+#define AST_BASE_LPCPLUS 0x1E6EC000 /* LPC+ Controller */
+#define AST_BASE_VIDEO 0x1E700000 /* Video Engine */
+#define AST_BASE_SRAM 0x1E720000 /* 32KB SRAM */
+#define AST_BASE_SDIO 0x1E740000 /* SD/SDIO Controller */
+#define AST_BASE_2D 0x1E760000 /* 2D Engine */
+#define AST_BASE_GPIO 0x1E780000 /* GPIO Controller */
+#define AST_BASE_RTC 0x1E781000 /* Real-Time Clock (RTC) */
+#define AST_BASE_TIMER 0x1E782000 /* Timer #1 ∼ #8 Controller */
+#define AST_BASE_UART1 0x1E783000 /* UART - #1 (LPC UART1) */
+#define AST_BASE_UART5 0x1E784000 /* UART - #5 (BMC Debug) */
+#define AST_BASE_WDT 0x1E785000 /* Watchdog Timer (WDT) */
+#define AST_BASE_PWM 0x1E786000 /* PWM & Fan Tacho Controller */
+#define AST_BASE_VUART 0x1E787000 /* Virtual UART (VUART) */
+#define AST_BASE_PUART 0x1E788000 /* Pass Through UART (PUART) */
+#define AST_BASE_LPC 0x1E789000 /* LPC Controller */
+#define AST_BASE_I2C 0x1E78A000 /* I2C/SMBus Controller */
+#define AST_BASE_PECI 0x1E78B000 /* PECI Controller */
+#define AST_BASE_UART2 0x1E78D000 /* UART - #2 (LPC UART2) */
+#define AST_BASE_UART3 0x1E78E000 /* UART - #3 */
+#define AST_BASE_UART4 0x1E78F000 /* UART - #4 */
+
+/* Memory */
+#define AST_BASE_BMCSRAM 0x20000000 /* BMC Static Memory */
+#define AST_BASE_SPIMEM 0x30000000 /* SPI Flash Memory */
+#define AST_BASE_SDRAM 0x40000000 /* SDRAM */
+#define AST_BASE_LPCBRIDGE 0x60000000 /* AHB Bus to LPC Bus Bridge */
+#define AST_BASE_LPCPBRIDGE 0x70000000 /* AHB Bus to LPC+ Bus Bridge */
+
+/* BMC interrupt sources */
+#define AST_ID_SDRAM 0 /* SDRAM interrupt */
+#define AST_ID_MIC 1 /* MIC interrupt */
+#define AST_ID_MAC1 2 /* MAC1 interrupt */
+#define AST_ID_MAC2 3 /* MAC2 interrupt */
+#define AST_ID_CRYPTO 4 /* Crypto interrupt */
+#define AST_ID_USB2 5 /* USB 2.0 Hub/Host interrupt */
+#define AST_ID_XDMA 6 /* X-DMA interrupt */
+#define AST_ID_VIDEO 7 /* Video Engine interrupt */
+#define AST_ID_LPC 8 /* LPC interrupt */
+#define AST_ID_UART1 9 /* UART1 interrupt */
+#define AST_ID_UART5 10 /* UART5 interrupt */
+#define AST_ID_11 11 /* Reserved */
+#define AST_ID_I2C 12 /* I2C/SMBus interrupt */
+#define AST_ID_USB1HID 13 /* USB 1.1 HID interrupt */
+#define AST_ID_USB1HOST 14 /* USB 1.1 Host interrupt */
+#define AST_ID_PECI 15 /* PECI interrupt */
+#define AST_ID_TIMER1 16 /* Timer 1 interrupt */
+#define AST_ID_TIMER2 17 /* Timer 2 interrupt */
+#define AST_ID_TIMER3 18 /* Timer 3 interrupt */
+#define AST_ID_SMC 19 /* SMC interrupt */
+#define AST_ID_GPIO 20 /* GPIO interrupt */
+#define AST_ID_SCU 21 /* SCU interrupt */
+#define AST_ID_RTC 22 /* RTC alarm interrupt */
+#define AST_ID_23 23 /* Reserved */
+#define AST_ID_24 24 /* Reserved */
+#define AST_ID_GRAPHICS 25 /* Graphics CRT interrupt */
+#define AST_ID_SDIO 26 /* SD/SDIO interrupt */
+#define AST_ID_WDT 27 /* WDT alarm interrupt */
+#define AST_ID_PWM 28 /* PWM/Tachometer interrupt */
+#define AST_ID_2D 29 /* Graphics 2D interrupt */
+#define AST_ID_WAKEUP 30 /* System Wakeup Control */
+#define AST_ID_ADC 31 /* ADC interrupt */
+#define AST_ID_UART2 32 /* UART2 interrupt */
+#define AST_ID_UART3 33 /* UART3 interrupt */
+#define AST_ID_UART4 34 /* UART4 interrupt */
+#define AST_ID_TIMER4 35 /* Timer 4 interrupt */
+#define AST_ID_TIMER5 36 /* Timer 5 interrupt */
+#define AST_ID_TIMER6 37 /* Timer 6 interrupt */
+#define AST_ID_TIMER38 38 /* Timer 7 interrupt */
+#define AST_ID_TIMER39 39 /* Timer 8 interrupt */
+#define AST_ID_SGPIOMASTER 40 /* SGPIO Master interrupt */
+#define AST_ID_SGPIOSLAVE 41 /* SGPIO Slave interrupt */
+#define AST_ID_MCTP 42 /* MCTP interrupt */
+#define AST_ID_JTAG 43 /* JTAG Master interrupt */
+#define AST_ID_44 44 /* Reserved */
+#define AST_ID_COPRO 45 /* Coprocessor interrupt */
+#define AST_ID_MAILBOX 46 /* MailBox interrupt */
+#define AST_ID_GPIOL1 47 /* GPIOL1 direct input */
+#define AST_ID_GPIOL3 48 /* GPIOL3 direct input */
+#define AST_ID_GPIOM1 49 /* GPIOM1 direct input */
+#define AST_ID_GPIOM3 50 /* GPIOM3 direct input */
+
+#endif /*AST2400_H*/
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 56bd16e77ae3..c871365458e2 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o
obj-$(CONFIG_ARCH_CLPS711X) += clps711x-timer.o
obj-$(CONFIG_ARCH_ATLAS7) += timer-atlas7.o
obj-$(CONFIG_ARCH_MOXART) += moxart_timer.o
+obj-$(CONFIG_ARCH_ASPEED) += moxart_timer.o
obj-$(CONFIG_ARCH_MXS) += mxs_timer.o
obj-$(CONFIG_CLKSRC_PXA) += pxa_timer.o
obj-$(CONFIG_ARCH_PRIMA2) += timer-prima2.o
diff --git a/drivers/clocksource/moxart_timer.c b/drivers/clocksource/moxart_timer.c
index 19857af651c1..bd8dbf388e24 100644
--- a/drivers/clocksource/moxart_timer.c
+++ b/drivers/clocksource/moxart_timer.c
@@ -36,45 +36,66 @@
#define TIMER_INTR_MASK 0x38
/*
- * TIMER_CR flags:
+ * Moxart TIMER_CR flags:
*
* TIMEREG_CR_*_CLOCK 0: PCLK, 1: EXT1CLK
* TIMEREG_CR_*_INT overflow interrupt enable bit
*/
-#define TIMEREG_CR_1_ENABLE BIT(0)
-#define TIMEREG_CR_1_CLOCK BIT(1)
-#define TIMEREG_CR_1_INT BIT(2)
-#define TIMEREG_CR_2_ENABLE BIT(3)
-#define TIMEREG_CR_2_CLOCK BIT(4)
-#define TIMEREG_CR_2_INT BIT(5)
-#define TIMEREG_CR_3_ENABLE BIT(6)
-#define TIMEREG_CR_3_CLOCK BIT(7)
-#define TIMEREG_CR_3_INT BIT(8)
-#define TIMEREG_CR_COUNT_UP BIT(9)
-
-#define TIMER1_ENABLE (TIMEREG_CR_2_ENABLE | TIMEREG_CR_1_ENABLE)
-#define TIMER1_DISABLE (TIMEREG_CR_2_ENABLE)
+#define MOXART_CR_1_ENABLE BIT(0)
+#define MOXART_CR_1_CLOCK BIT(1)
+#define MOXART_CR_1_INT BIT(2)
+#define MOXART_CR_2_ENABLE BIT(3)
+#define MOXART_CR_2_CLOCK BIT(4)
+#define MOXART_CR_2_INT BIT(5)
+#define MOXART_CR_3_ENABLE BIT(6)
+#define MOXART_CR_3_CLOCK BIT(7)
+#define MOXART_CR_3_INT BIT(8)
+#define MOXART_CR_COUNT_UP BIT(9)
+
+#define MOXART_TIMER1_ENABLE (MOXART_CR_2_ENABLE | MOXART_CR_1_ENABLE)
+#define MOXART_TIMER1_DISABLE (MOXART_CR_2_ENABLE)
+
+/*
+ * The ASpeed variant of the IP block has a different layout
+ * for the control register
+ */
+#define ASPEED_CR_1_ENABLE BIT(0)
+#define ASPEED_CR_1_CLOCK BIT(1)
+#define ASPEED_CR_1_INT BIT(2)
+#define ASPEED_CR_2_ENABLE BIT(4)
+#define ASPEED_CR_2_CLOCK BIT(5)
+#define ASPEED_CR_2_INT BIT(6)
+#define ASPEED_CR_3_ENABLE BIT(8)
+#define ASPEED_CR_3_CLOCK BIT(9)
+#define ASPEED_CR_3_INT BIT(10)
+
+#define ASPEED_TIMER1_ENABLE (ASPEED_CR_2_ENABLE | ASPEED_CR_1_ENABLE)
+#define ASPEED_TIMER1_DISABLE (ASPEED_CR_2_ENABLE)
static void __iomem *base;
static unsigned int clock_count_per_tick;
+static unsigned int t1_disable_val, t1_enable_val;
static int moxart_shutdown(struct clock_event_device *evt)
{
- writel(TIMER1_DISABLE, base + TIMER_CR);
+ writel(t1_disable_val, base + TIMER_CR);
return 0;
}
static int moxart_set_oneshot(struct clock_event_device *evt)
{
- writel(TIMER1_DISABLE, base + TIMER_CR);
+ writel(t1_disable_val, base + TIMER_CR);
writel(~0, base + TIMER1_BASE + REG_LOAD);
return 0;
}
static int moxart_set_periodic(struct clock_event_device *evt)
{
+ writel(t1_disable_val, base + TIMER_CR);
writel(clock_count_per_tick, base + TIMER1_BASE + REG_LOAD);
- writel(TIMER1_ENABLE, base + TIMER_CR);
+ writel(0, base + TIMER1_BASE + REG_MATCH1);
+ writel(t1_enable_val, base + TIMER_CR);
+
return 0;
}
@@ -83,12 +104,12 @@ static int moxart_clkevt_next_event(unsigned long cycles,
{
u32 u;
- writel(TIMER1_DISABLE, base + TIMER_CR);
+ writel(t1_disable_val, base + TIMER_CR);
u = readl(base + TIMER1_BASE + REG_COUNT) - cycles;
writel(u, base + TIMER1_BASE + REG_MATCH1);
- writel(TIMER1_ENABLE, base + TIMER_CR);
+ writel(t1_enable_val, base + TIMER_CR);
return 0;
}
@@ -119,7 +140,7 @@ static struct irqaction moxart_timer_irq = {
.dev_id = &moxart_clockevent,
};
-static void __init moxart_timer_init(struct device_node *node)
+static void __init __moxart_timer_init(struct device_node *node)
{
int ret, irq;
unsigned long pclk;
@@ -150,8 +171,21 @@ static void __init moxart_timer_init(struct device_node *node)
clock_count_per_tick = DIV_ROUND_CLOSEST(pclk, HZ);
+ /* Clean up match registers we will still get an occasional interrupt
+ * from timer 2 but I haven't enabled it for now
+ */
+ writel(0, base + TIMER1_BASE + REG_MATCH1);
+ writel(0, base + TIMER1_BASE + REG_MATCH2);
+ writel(0, base + TIMER2_BASE + REG_MATCH1);
+ writel(0, base + TIMER2_BASE + REG_MATCH2);
+
+ /* Start timer 2 rolling as our main wall clock source, keep timer 1
+ * disabled
+ */
+ writel(0, base + TIMER_CR);
writel(~0, base + TIMER2_BASE + REG_LOAD);
- writel(TIMEREG_CR_2_ENABLE, base + TIMER_CR);
+ writel(t1_disable_val, base + TIMER_CR);
+
moxart_clockevent.cpumask = cpumask_of(0);
moxart_clockevent.irq = irq;
@@ -165,4 +199,20 @@ static void __init moxart_timer_init(struct device_node *node)
clockevents_config_and_register(&moxart_clockevent, pclk,
0x4, 0xfffffffe);
}
+
+static void __init moxart_timer_init(struct device_node *node)
+{
+ t1_enable_val = MOXART_TIMER1_ENABLE;
+ t1_disable_val = MOXART_TIMER1_DISABLE;
+ __moxart_timer_init(node);
+}
+
+static void __init aspeed_timer_init(struct device_node *node)
+{
+ t1_enable_val = ASPEED_TIMER1_ENABLE;
+ t1_disable_val = ASPEED_TIMER1_DISABLE;
+ __moxart_timer_init(node);
+}
+
CLOCKSOURCE_OF_DECLARE(moxart, "moxa,moxart-timer", moxart_timer_init);
+CLOCKSOURCE_OF_DECLARE(aspeed, "aspeed,timer", aspeed_timer_init);
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 469dc378adeb..1225d92ab0d6 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -127,6 +127,13 @@ config GPIO_AMDPT
driver for GPIO functionality on Promontory IOHub
Require ACPI ASL code to enumerate as a platform device.
+config GPIO_ASPEED
+ bool "Aspeed AST2400 GPIO support"
+ depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+ select GENERIC_IRQ_CHIP
+ help
+ Say Y here to support Aspeed AST2400 GPIO.
+
config GPIO_BCM_KONA
bool "Broadcom Kona GPIO"
depends on OF_GPIO && (ARCH_BCM_MOBILE || COMPILE_TEST)
@@ -983,7 +990,6 @@ config GPIO_SODAVILLE
select GENERIC_IRQ_CHIP
help
Say Y here to support Intel Sodaville GPIO.
-
endmenu
menu "SPI GPIO expanders"
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 986dbd838cea..9eb249a6822b 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_GPIO_AMD8111) += gpio-amd8111.o
obj-$(CONFIG_GPIO_AMDPT) += gpio-amdpt.o
obj-$(CONFIG_GPIO_ARIZONA) += gpio-arizona.o
obj-$(CONFIG_ATH79) += gpio-ath79.o
+obj-$(CONFIG_GPIO_ASPEED) += gpio-aspeed.o
obj-$(CONFIG_GPIO_BCM_KONA) += gpio-bcm-kona.o
obj-$(CONFIG_GPIO_BRCMSTB) += gpio-brcmstb.o
obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o
diff --git a/drivers/gpio/gpio-aspeed.c b/drivers/gpio/gpio-aspeed.c
new file mode 100644
index 000000000000..87dfcdf6bc6f
--- /dev/null
+++ b/drivers/gpio/gpio-aspeed.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2015 IBM Corp.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/driver.h>
+
+struct aspeed_gpio {
+ struct gpio_chip chip;
+ spinlock_t lock;
+ void __iomem *base;
+ int irq;
+ struct irq_chip irq_chip;
+ struct irq_domain *irq_domain;
+};
+
+struct aspeed_gpio_bank {
+ uint16_t val_regs;
+ uint16_t irq_regs;
+ const char names[4];
+};
+
+static const struct aspeed_gpio_bank aspeed_gpio_banks[] = {
+ {
+ .val_regs = 0x0000,
+ .irq_regs = 0x0008,
+ .names = { 'A', 'B', 'C', 'D' },
+ },
+ {
+ .val_regs = 0x0020,
+ .irq_regs = 0x0028,
+ .names = { 'E', 'F', 'G', 'H' },
+ },
+ {
+ .val_regs = 0x0070,
+ .irq_regs = 0x0098,
+ .names = { 'I', 'J', 'K', 'L' },
+ },
+ {
+ .val_regs = 0x0078,
+ .irq_regs = 0x00e8,
+ .names = { 'M', 'N', 'O', 'P' },
+ },
+ {
+ .val_regs = 0x0080,
+ .irq_regs = 0x0118,
+ .names = { 'Q', 'R', 'S', 'T' },
+ },
+ {
+ .val_regs = 0x0088,
+ .irq_regs = 0x0148,
+ .names = { 'U', 'V', 'W', 'X' },
+ },
+};
+
+#define GPIO_BANK(x) ((x) >> 5)
+#define GPIO_OFFSET(x) ((x) & 0x1f)
+#define GPIO_BIT(x) BIT(GPIO_OFFSET(x))
+
+#define GPIO_DATA 0x00
+#define GPIO_DIR 0x04
+
+#define GPIO_IRQ_ENABLE 0x00
+#define GPIO_IRQ_TYPE0 0x04
+#define GPIO_IRQ_TYPE1 0x08
+#define GPIO_IRQ_TYPE2 0x0c
+#define GPIO_IRQ_STATUS 0x10
+
+static inline struct aspeed_gpio *to_aspeed_gpio(struct gpio_chip *chip)
+{
+ return container_of(chip, struct aspeed_gpio, chip);
+}
+
+static const struct aspeed_gpio_bank *to_bank(unsigned int offset)
+{
+ unsigned int bank = GPIO_BANK(offset);
+ WARN_ON(bank > ARRAY_SIZE(aspeed_gpio_banks));
+ return &aspeed_gpio_banks[bank];
+}
+
+static void *bank_val_reg(struct aspeed_gpio *gpio,
+ const struct aspeed_gpio_bank *bank,
+ unsigned int reg)
+{
+ return gpio->base + bank->val_regs + reg;
+}
+
+static void *bank_irq_reg(struct aspeed_gpio *gpio,
+ const struct aspeed_gpio_bank *bank,
+ unsigned int reg)
+{
+ return gpio->base + bank->irq_regs + reg;
+}
+
+static int aspeed_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+
+ return !!(ioread32(bank_val_reg(gpio, bank, GPIO_DATA))
+ & GPIO_BIT(offset));
+}
+
+static void __aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+ u32 reg;
+ void __iomem *addr;
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+
+ addr = bank_val_reg(gpio, bank, GPIO_DATA);
+
+ reg = ioread32(addr);
+ if (val)
+ reg |= GPIO_BIT(offset);
+ else
+ reg &= ~GPIO_BIT(offset);
+
+ iowrite32(reg, addr);
+}
+
+static void aspeed_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio->lock, flags);
+
+ __aspeed_gpio_set(gc, offset, val);
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static int aspeed_gpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+ u32 reg;
+
+ 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));
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return 0;
+}
+
+static int aspeed_gpio_dir_out(struct gpio_chip *gc,
+ unsigned int offset, int val)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(gc);
+ const struct aspeed_gpio_bank *bank = to_bank(offset);
+ unsigned long flags;
+ u32 reg;
+
+ 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));
+
+ __aspeed_gpio_set(gc, offset, val);
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+
+ return 0;
+}
+
+static inline int irqd_to_aspeed_gpio_data(struct irq_data *d,
+ struct aspeed_gpio **gpio,
+ const struct aspeed_gpio_bank **bank,
+ u32 *bit)
+{
+ int offset;
+
+ offset = irqd_to_hwirq(d);
+
+ *gpio = irq_data_get_irq_chip_data(d);
+ *bank = to_bank(offset);
+ *bit = GPIO_BIT(offset);
+
+ return 0;
+}
+
+static void aspeed_gpio_irq_ack(struct irq_data *d)
+{
+ const struct aspeed_gpio_bank *bank;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ void *status_addr;
+ u32 bit;
+ int rc;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit);
+ if (rc)
+ return;
+
+ status_addr = bank_irq_reg(gpio, bank, GPIO_IRQ_STATUS);
+
+ spin_lock_irqsave(&gpio->lock, flags);
+ iowrite32(bit, status_addr);
+ spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static void __aspeed_gpio_irq_set_mask(struct irq_data *d, bool set)
+{
+ const struct aspeed_gpio_bank *bank;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ u32 reg, bit;
+ void *addr;
+ int rc;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit);
+ if (rc)
+ return;
+
+ addr = bank_irq_reg(gpio, bank, GPIO_IRQ_ENABLE);
+
+ spin_lock_irqsave(&gpio->lock, flags);
+
+ reg = ioread32(addr);
+ if (set)
+ reg |= bit;
+ else
+ reg &= bit;
+ iowrite32(reg, addr);
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+}
+
+static void aspeed_gpio_irq_mask(struct irq_data *d)
+{
+ __aspeed_gpio_irq_set_mask(d, false);
+}
+
+static void aspeed_gpio_irq_unmask(struct irq_data *d)
+{
+ __aspeed_gpio_irq_set_mask(d, true);
+}
+
+static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type)
+{
+ u32 type0, type1, type2, bit, reg;
+ const struct aspeed_gpio_bank *bank;
+ irq_flow_handler_t handler;
+ struct aspeed_gpio *gpio;
+ unsigned long flags;
+ void *addr;
+ int rc;
+
+ rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit);
+ if (rc)
+ return -EINVAL;
+
+ type0 = type1 = type2 = 0;
+
+ switch (type & IRQ_TYPE_SENSE_MASK) {
+ case IRQ_TYPE_EDGE_BOTH:
+ type2 |= bit;
+ case IRQ_TYPE_EDGE_RISING:
+ type0 |= bit;
+ case IRQ_TYPE_EDGE_FALLING:
+ handler = handle_edge_irq;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ type0 |= bit;
+ case IRQ_TYPE_LEVEL_LOW:
+ type1 |= bit;
+ handler = handle_level_irq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&gpio->lock, flags);
+
+ addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE0);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type0;
+ iowrite32(reg, addr);
+
+ addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE1);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type1;
+ iowrite32(reg, addr);
+
+ addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE2);
+ reg = ioread32(addr);
+ reg = (reg & ~bit) | type2;
+ iowrite32(reg, addr);
+
+ spin_unlock_irqrestore(&gpio->lock, flags);
+
+ irq_set_handler_locked(d, handler);
+
+ return 0;
+}
+
+static void aspeed_gpio_irq_handler(struct irq_desc *desc)
+{
+ struct aspeed_gpio *gpio = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int i, p, girq;
+ unsigned long reg;
+
+ chained_irq_enter(chip, 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(gpio, bank, GPIO_IRQ_STATUS));
+
+ for_each_set_bit(p, &reg, 32) {
+ girq = irq_find_mapping(gpio->irq_domain, i * 32 + p);
+ generic_handle_irq(girq);
+ }
+
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+static struct irq_chip aspeed_gpio_irqchip = {
+ .name = "aspeed-gpio",
+ .irq_ack = aspeed_gpio_irq_ack,
+ .irq_mask = aspeed_gpio_irq_mask,
+ .irq_unmask = aspeed_gpio_irq_unmask,
+ .irq_set_type = aspeed_gpio_set_type,
+};
+
+static int aspeed_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ struct aspeed_gpio *gpio = to_aspeed_gpio(chip);
+ return irq_find_mapping(gpio->irq_domain, offset);
+}
+
+static void aspeed_gpio_setup_irqs(struct aspeed_gpio *gpio,
+ struct platform_device *pdev)
+{
+ int i, irq;
+
+ /* request our upstream IRQ */
+ gpio->irq = platform_get_irq(pdev, 0);
+ if (gpio->irq < 0)
+ return;
+
+ /* establish our irq domain to provide IRQs for each extended bank */
+ gpio->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
+ gpio->chip.ngpio, &irq_domain_simple_ops, NULL);
+ if (!gpio->irq_domain)
+ return;
+
+ for (i = 0; i < gpio->chip.ngpio; i++) {
+ irq = irq_create_mapping(gpio->irq_domain, i);
+ irq_set_chip_data(irq, gpio);
+ irq_set_chip_and_handler(irq, &aspeed_gpio_irqchip,
+ handle_simple_irq);
+ irq_set_probe(irq);
+ }
+
+ irq_set_chained_handler_and_data(gpio->irq,
+ aspeed_gpio_irq_handler, gpio);
+}
+
+
+static int __init aspeed_gpio_probe(struct platform_device *pdev)
+{
+ struct aspeed_gpio *gpio;
+ struct resource *res;
+ int rc;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENXIO;
+
+ gpio->base = devm_ioremap_resource(&pdev->dev, res);
+ if (!gpio->base)
+ return -ENOMEM;
+
+ spin_lock_init(&gpio->lock);
+
+ gpio->chip.ngpio = ARRAY_SIZE(aspeed_gpio_banks) * 32;
+
+ gpio->chip.dev = &pdev->dev;
+ gpio->chip.direction_input = aspeed_gpio_dir_in;
+ gpio->chip.direction_output = aspeed_gpio_dir_out;
+ gpio->chip.get = aspeed_gpio_get;
+ gpio->chip.set = aspeed_gpio_set;
+ gpio->chip.to_irq = aspeed_gpio_to_irq;
+ gpio->chip.label = dev_name(&pdev->dev);
+ gpio->chip.base = -1;
+
+ platform_set_drvdata(pdev, gpio);
+
+ rc = gpiochip_add(&gpio->chip);
+ if (rc < 0)
+ return rc;
+
+ aspeed_gpio_setup_irqs(gpio, pdev);
+
+ return 0;
+}
+
+static int aspeed_gpio_remove(struct platform_device *pdev)
+{
+ struct aspeed_gpio *gpio = platform_get_drvdata(pdev);
+
+ gpiochip_remove(&gpio->chip);
+ return 0;
+}
+
+static const struct of_device_id aspeed_gpio_of_table[] = {
+ { .compatible = "aspeed,ast2400-gpio" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, aspeed_gpio_of_table);
+
+static struct platform_driver aspeed_gpio_driver = {
+ .remove = aspeed_gpio_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = aspeed_gpio_of_table,
+ },
+};
+
+module_platform_driver_probe(aspeed_gpio_driver, aspeed_gpio_probe);
+
+MODULE_DESCRIPTION("Aspeed AST2400 GPIO Driver");
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 80a73bfc1a65..399ff70347a0 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1186,6 +1186,19 @@ config SENSORS_NCT7904
This driver can also be built as a module. If so, the module
will be called nct7904.
+config SENSORS_POWER8_OCC_I2C
+ tristate "Power8 On Chip Controller i2c driver"
+ depends on I2C
+ help
+ If you say yes here you get support to monitor Power8 CPU
+ sensors via the On Chip Controller (OCC) over the i2c bus.
+
+ Generally this is used by management controllers such as a BMC
+ on an OpenPower system.
+
+ This driver can also be built as a module. If so, the module
+ will be called power8_occ_i2c.
+
config SENSORS_PCF8591
tristate "Philips PCF8591 ADC/DAC"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 12a32398fdcc..8ee6a7a4dcb6 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -125,6 +125,7 @@ obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o
obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o
obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o
obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o
+obj-$(CONFIG_SENSORS_POWER8_OCC_I2C) += power8_occ_i2c.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index df6ebb2b8f0f..ea8a50f46f2e 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -31,8 +31,8 @@ config SENSORS_ADM1275
default n
help
If you say yes here you get hardware monitoring support for Analog
- Devices ADM1075, ADM1275, ADM1276, ADM1293, and ADM1294 Hot-Swap
- Controller and Digital Power Monitors.
+ Devices ADM1075, ADM1275, ADM1276, ADM1278, ADM1293, and ADM1294
+ Hot-Swap Controller and Digital Power Monitors.
This driver can also be built as a module. If so, the module will
be called adm1275.
diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c
index 188af4c89f40..006b1eff758c 100644
--- a/drivers/hwmon/pmbus/adm1275.c
+++ b/drivers/hwmon/pmbus/adm1275.c
@@ -22,9 +22,10 @@
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/bitops.h>
+#include <linux/of.h>
#include "pmbus.h"
-enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 };
+enum chips { adm1075, adm1275, adm1276, adm1278, adm1293, adm1294 };
#define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0)
#define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5)
@@ -41,6 +42,10 @@ enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 };
#define ADM1075_IRANGE_25 BIT(3)
#define ADM1075_IRANGE_MASK (BIT(3) | BIT(4))
+#define ADM1278_TEMP1_EN BIT(3)
+#define ADM1278_VIN_EN BIT(2)
+#define ADM1278_VOUT_EN BIT(1)
+
#define ADM1293_IRANGE_25 0
#define ADM1293_IRANGE_50 BIT(6)
#define ADM1293_IRANGE_100 BIT(7)
@@ -54,6 +59,7 @@ enum chips { adm1075, adm1275, adm1276, adm1293, adm1294 };
#define ADM1293_VAUX_EN BIT(1)
+#define ADM1278_PEAK_TEMP 0xd7
#define ADM1275_IOUT_WARN2_LIMIT 0xd7
#define ADM1275_DEVICE_CONFIG 0xd8
@@ -80,6 +86,7 @@ struct adm1275_data {
bool have_iout_min;
bool have_pin_min;
bool have_pin_max;
+ bool have_temp_max;
struct pmbus_driver_info info;
};
@@ -113,6 +120,13 @@ static const struct coefficients adm1276_coefficients[] = {
[4] = { 2115, 0, -1 }, /* power, vrange not set */
};
+static const struct coefficients adm1278_coefficients[] = {
+ [0] = { 19599, 0, -2 }, /* voltage */
+ [1] = { 800, 20475, -1 }, /* current */
+ [2] = { 6123, 0, -2 }, /* power */
+ [3] = { 42, 31880, -1 }, /* temperature */
+};
+
static const struct coefficients adm1293_coefficients[] = {
[0] = { 3333, -1, 0 }, /* voltage, vrange 1.2V */
[1] = { 5552, -5, -1 }, /* voltage, vrange 7.4V */
@@ -196,6 +210,11 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
return -ENXIO;
ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN);
break;
+ case PMBUS_VIRT_READ_TEMP_MAX:
+ if (!data->have_temp_max)
+ return -ENXIO;
+ ret = pmbus_read_word_data(client, 0, ADM1278_PEAK_TEMP);
+ break;
case PMBUS_VIRT_RESET_IOUT_HISTORY:
case PMBUS_VIRT_RESET_VOUT_HISTORY:
case PMBUS_VIRT_RESET_VIN_HISTORY:
@@ -204,6 +223,10 @@ static int adm1275_read_word_data(struct i2c_client *client, int page, int reg)
if (!data->have_pin_max)
return -ENXIO;
break;
+ case PMBUS_VIRT_RESET_TEMP_HISTORY:
+ if (!data->have_temp_max)
+ return -ENXIO;
+ break;
default:
ret = -ENODATA;
break;
@@ -245,6 +268,9 @@ static int adm1275_write_word_data(struct i2c_client *client, int page, int reg,
ret = pmbus_write_word_data(client, 0,
ADM1293_PIN_MIN, 0);
break;
+ case PMBUS_VIRT_RESET_TEMP_HISTORY:
+ ret = pmbus_write_word_data(client, 0, ADM1278_PEAK_TEMP, 0);
+ break;
default:
ret = -ENODATA;
break;
@@ -312,12 +338,26 @@ static const struct i2c_device_id adm1275_id[] = {
{ "adm1075", adm1075 },
{ "adm1275", adm1275 },
{ "adm1276", adm1276 },
+ { "adm1278", adm1278 },
{ "adm1293", adm1293 },
{ "adm1294", adm1294 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adm1275_id);
+#ifdef CONFIG_OF
+static const struct of_device_id adm1275_of_match[] = {
+ { .compatible = "adi,adm1075" },
+ { .compatible = "adi,adm1275" },
+ { .compatible = "adi,adm1276" },
+ { .compatible = "adi,adm1278" },
+ { .compatible = "adi,adm1293" },
+ { .compatible = "adi,adm1294" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adm1275_of_match);
+#endif
+
static int adm1275_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
@@ -329,6 +369,7 @@ static int adm1275_probe(struct i2c_client *client,
const struct i2c_device_id *mid;
const struct coefficients *coefficients;
int vindex = -1, voindex = -1, cindex = -1, pindex = -1;
+ int tindex = -1;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_BYTE_DATA
@@ -386,6 +427,7 @@ static int adm1275_probe(struct i2c_client *client,
info->format[PSC_VOLTAGE_OUT] = direct;
info->format[PSC_CURRENT_OUT] = direct;
info->format[PSC_POWER] = direct;
+ info->format[PSC_TEMPERATURE] = direct;
info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT;
info->read_word_data = adm1275_read_word_data;
@@ -460,6 +502,40 @@ static int adm1275_probe(struct i2c_client *client,
info->func[0] |=
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
break;
+ case adm1278:
+ data->have_vout = true;
+ data->have_pin_max = true;
+ data->have_temp_max = true;
+
+ coefficients = adm1278_coefficients;
+ vindex = 0;
+ cindex = 1;
+ pindex = 2;
+ tindex = 3;
+
+ info->func[0] |= PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT;
+
+ /* By default when reset VOUT is not enabled */
+ if (!(config & ADM1278_VOUT_EN)) {
+ config |= ADM1278_VOUT_EN;
+ ret = i2c_smbus_write_byte_data(client,
+ ADM1275_PMON_CONFIG, (u8)config);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Fail to write ADM1275_PMON_CONFIG\n");
+ return ret;
+ }
+ }
+
+ if (config & ADM1278_TEMP1_EN)
+ info->func[0] |=
+ PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP;
+ if (config & ADM1278_VIN_EN)
+ info->func[0] |= PMBUS_HAVE_VIN;
+ if (config & ADM1278_VOUT_EN)
+ info->func[0] |=
+ PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT;
+ break;
case adm1293:
case adm1294:
data->have_iout_min = true;
@@ -537,6 +613,11 @@ static int adm1275_probe(struct i2c_client *client,
info->b[PSC_POWER] = coefficients[pindex].b;
info->R[PSC_POWER] = coefficients[pindex].R;
}
+ if (tindex >= 0) {
+ info->m[PSC_TEMPERATURE] = coefficients[tindex].m;
+ info->b[PSC_TEMPERATURE] = coefficients[tindex].b;
+ info->R[PSC_TEMPERATURE] = coefficients[tindex].R;
+ }
return pmbus_do_probe(client, id, info);
}
@@ -544,6 +625,7 @@ static int adm1275_probe(struct i2c_client *client,
static struct i2c_driver adm1275_driver = {
.driver = {
.name = "adm1275",
+ .of_match_table = of_match_ptr(adm1275_of_match),
},
.probe = adm1275_probe,
.remove = pmbus_do_remove,
diff --git a/drivers/hwmon/power8_occ_i2c.c b/drivers/hwmon/power8_occ_i2c.c
new file mode 100644
index 000000000000..6de0e76ae21b
--- /dev/null
+++ b/drivers/hwmon/power8_occ_i2c.c
@@ -0,0 +1,1254 @@
+/*
+ * OCC HWMON driver - read IBM Power8 On Chip Controller sensor data via
+ * i2c.
+ *
+ * Copyright 2015 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#define OCC_I2C_ADDR 0x50
+#define OCC_I2C_NAME "occ-i2c"
+
+#define OCC_DATA_MAX 4096 /* 4KB at most */
+/* i2c read and write occ sensors */
+#define I2C_READ_ERROR 1
+#define I2C_WRITE_ERROR 2
+
+/* Defined in POWER8 Processor Registers Specification */
+/* To generate attn to OCC */
+#define ATTN_DATA 0x0006B035
+/* For BMC to read/write SRAM */
+#define OCB_ADDRESS 0x0006B070
+#define OCB_DATA 0x0006B075
+#define OCB_STATUS_CONTROL_AND 0x0006B072
+#define OCB_STATUS_CONTROL_OR 0x0006B073
+/* See definition in:
+ * https://github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf
+ */
+#define OCC_COMMAND_ADDR 0xFFFF6000
+#define OCC_RESPONSE_ADDR 0xFFFF7000
+
+#define MAX_SENSOR_ATTR_LEN 32
+
+enum sensor_t {
+ freq,
+ temp,
+ power,
+ caps,
+ MAX_OCC_SENSOR_TYPE
+};
+
+/* OCC sensor data format */
+struct occ_sensor {
+ uint16_t sensor_id;
+ uint16_t value;
+};
+
+struct power_sensor {
+ uint16_t sensor_id;
+ uint32_t update_tag;
+ uint32_t accumulator;
+ uint16_t value;
+};
+
+struct caps_sensor {
+ uint16_t curr_powercap;
+ uint16_t curr_powerreading;
+ uint16_t norm_powercap;
+ uint16_t max_powercap;
+ uint16_t min_powercap;
+ uint16_t user_powerlimit;
+};
+
+struct sensor_data_block {
+ uint8_t sensor_type[4];
+ uint8_t reserved0;
+ uint8_t sensor_format;
+ uint8_t sensor_length;
+ uint8_t sensor_num;
+ struct occ_sensor *sensor;
+ struct power_sensor *power;
+ struct caps_sensor *caps;
+};
+
+struct occ_poll_header {
+ uint8_t status;
+ uint8_t ext_status;
+ uint8_t occs_present;
+ uint8_t config;
+ uint8_t occ_state;
+ uint8_t reserved0;
+ uint8_t reserved1;
+ uint8_t error_log_id;
+ uint32_t error_log_addr_start;
+ uint16_t error_log_length;
+ uint8_t reserved2;
+ uint8_t reserved3;
+ uint8_t occ_code_level[16];
+ uint8_t sensor_eye_catcher[6];
+ uint8_t sensor_block_num;
+ uint8_t sensor_data_version;
+};
+
+struct occ_response {
+ uint8_t sequence_num;
+ uint8_t cmd_type;
+ uint8_t rtn_status;
+ uint16_t data_length;
+ struct occ_poll_header header;
+ struct sensor_data_block *blocks;
+ uint16_t chk_sum;
+ int sensor_block_id[MAX_OCC_SENSOR_TYPE];
+};
+
+struct sensor_attr_data {
+ enum sensor_t type;
+ uint32_t hwmon_index;
+ uint32_t attr_id;
+ char name[MAX_SENSOR_ATTR_LEN];
+ struct device_attribute dev_attr;
+};
+
+struct sensor_group {
+ char *name;
+ struct sensor_attr_data *sattr;
+ struct attribute_group group;
+};
+
+/* data private to each client */
+struct occ_drv_data {
+ struct i2c_client *client;
+ struct device *hwmon_dev;
+ struct mutex update_lock;
+ bool valid;
+ unsigned long last_updated;
+ /* Minimum timer interval for sampling In jiffies */
+ unsigned long update_interval;
+ unsigned long occ_online;
+ uint16_t user_powercap;
+ struct occ_response occ_resp;
+ struct sensor_group sensor_groups[MAX_OCC_SENSOR_TYPE];
+};
+
+static void deinit_occ_resp_buf(struct occ_response *p)
+{
+ int i;
+
+ if (!p)
+ return;
+
+ if (!p->blocks)
+ return;
+
+ for (i = 0; i < p->header.sensor_block_num; i++) {
+ kfree(p->blocks[i].sensor);
+ kfree(p->blocks[i].power);
+ kfree(p->blocks[i].caps);
+ }
+
+ kfree(p->blocks);
+
+ memset(p, 0, sizeof(*p));
+
+ for (i = 0; i < ARRAY_SIZE(p->sensor_block_id); i++)
+ p->sensor_block_id[i] = -1;
+}
+
+static ssize_t occ_i2c_read(struct i2c_client *client, void *buf, size_t count)
+{
+ WARN_ON(count > OCC_DATA_MAX);
+
+ dev_dbg(&client->dev, "i2c_read: reading %zu bytes @0x%x.\n",
+ count, client->addr);
+ return i2c_master_recv(client, buf, count);
+}
+
+static ssize_t occ_i2c_write(struct i2c_client *client, const void *buf,
+ size_t count)
+{
+ WARN_ON(count > OCC_DATA_MAX);
+
+ dev_dbg(&client->dev, "i2c_write: writing %zu bytes @0x%x.\n",
+ count, client->addr);
+ return i2c_master_send(client, buf, count);
+}
+
+/* read 8-byte value and put into data[offset] */
+static int occ_getscomb(struct i2c_client *client, uint32_t address,
+ uint8_t *data, int offset)
+{
+ uint32_t ret;
+ char buf[8];
+ int i;
+
+ /* P8 i2c slave requires address to be shifted by 1 */
+ address = address << 1;
+
+ ret = occ_i2c_write(client, &address,
+ sizeof(address));
+
+ if (ret != sizeof(address))
+ return -I2C_WRITE_ERROR;
+
+ ret = occ_i2c_read(client, buf, sizeof(buf));
+ if (ret != sizeof(buf))
+ return -I2C_READ_ERROR;
+
+ for (i = 0; i < 8; i++)
+ data[offset + i] = buf[7 - i];
+
+ return 0;
+}
+
+static int occ_putscom(struct i2c_client *client, uint32_t address,
+ uint32_t data0, uint32_t data1)
+{
+ uint32_t buf[3];
+ uint32_t ret;
+
+ /* P8 i2c slave requires address to be shifted by 1 */
+ address = address << 1;
+
+ buf[0] = address;
+ buf[1] = data1;
+ buf[2] = data0;
+
+ ret = occ_i2c_write(client, buf, sizeof(buf));
+ if (ret != sizeof(buf))
+ return I2C_WRITE_ERROR;
+
+ return 0;
+}
+
+static void *occ_get_sensor_by_type(struct occ_response *resp, enum sensor_t t)
+{
+ void *sensor;
+
+ if (!resp->blocks)
+ return NULL;
+
+ if (resp->sensor_block_id[t] == -1)
+ return NULL;
+
+ switch (t) {
+ case temp:
+ case freq:
+ sensor = resp->blocks[resp->sensor_block_id[t]].sensor;
+ break;
+ case power:
+ sensor = resp->blocks[resp->sensor_block_id[t]].power;
+ break;
+ case caps:
+ sensor = resp->blocks[resp->sensor_block_id[t]].caps;
+ break;
+ default:
+ sensor = NULL;
+ }
+
+ return sensor;
+}
+
+static int occ_renew_sensor(struct occ_response *resp, uint8_t sensor_length,
+ uint8_t sensor_num, enum sensor_t t, int block)
+{
+ void *sensor;
+ int ret;
+
+ sensor = occ_get_sensor_by_type(resp, t);
+
+ /* empty sensor block, release older sensor data */
+ if (sensor_num == 0 || sensor_length == 0) {
+ kfree(sensor);
+ return -1;
+ }
+
+ if (!sensor || sensor_num !=
+ resp->blocks[resp->sensor_block_id[t]].sensor_num) {
+ kfree(sensor);
+ switch (t) {
+ case temp:
+ case freq:
+ resp->blocks[block].sensor =
+ kcalloc(sensor_num,
+ sizeof(struct occ_sensor), GFP_KERNEL);
+ if (!resp->blocks[block].sensor) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ break;
+ case power:
+ resp->blocks[block].power =
+ kcalloc(sensor_num,
+ sizeof(struct power_sensor),
+ GFP_KERNEL);
+ if (!resp->blocks[block].power) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ break;
+ case caps:
+ resp->blocks[block].caps =
+ kcalloc(sensor_num,
+ sizeof(struct caps_sensor), GFP_KERNEL);
+ if (!resp->blocks[block].caps) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ break;
+ default:
+ ret = -ENOMEM;
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ deinit_occ_resp_buf(resp);
+ return ret;
+}
+
+#define RESP_DATA_LENGTH 3
+#define RESP_HEADER_OFFSET 5
+#define SENSOR_STR_OFFSET 37
+#define SENSOR_BLOCK_NUM_OFFSET 43
+#define SENSOR_BLOCK_OFFSET 45
+
+static inline uint16_t get_occdata_length(uint8_t *data)
+{
+ return be16_to_cpup((const __be16 *)&data[RESP_DATA_LENGTH]);
+}
+
+static int parse_occ_response(struct i2c_client *client,
+ uint8_t *data, struct occ_response *resp)
+{
+ int b;
+ int s;
+ int ret;
+ int dnum = SENSOR_BLOCK_OFFSET;
+ struct occ_sensor *f_sensor;
+ struct occ_sensor *t_sensor;
+ struct power_sensor *p_sensor;
+ struct caps_sensor *c_sensor;
+ uint8_t sensor_block_num;
+ uint8_t sensor_type[4];
+ uint8_t sensor_format;
+ uint8_t sensor_length;
+ uint8_t sensor_num;
+
+ /* check if the data is valid */
+ if (strncmp(&data[SENSOR_STR_OFFSET], "SENSOR", 6) != 0) {
+ dev_dbg(&client->dev,
+ "ERROR: no SENSOR String in response\n");
+ ret = -1;
+ goto err;
+ }
+
+ sensor_block_num = data[SENSOR_BLOCK_NUM_OFFSET];
+ if (sensor_block_num == 0) {
+ dev_dbg(&client->dev, "ERROR: SENSOR block num is 0\n");
+ ret = -1;
+ goto err;
+ }
+
+ /* if sensor block has changed, re-malloc */
+ if (sensor_block_num != resp->header.sensor_block_num) {
+ deinit_occ_resp_buf(resp);
+ resp->blocks = kcalloc(sensor_block_num,
+ sizeof(struct sensor_data_block), GFP_KERNEL);
+ if (!resp->blocks)
+ return -ENOMEM;
+ }
+
+ memcpy(&resp->header, &data[RESP_HEADER_OFFSET], sizeof(resp->header));
+ resp->header.error_log_addr_start =
+ be32_to_cpu(resp->header.error_log_addr_start);
+ resp->header.error_log_length =
+ be16_to_cpu(resp->header.error_log_length);
+
+ dev_dbg(&client->dev, "Reading %d sensor blocks\n",
+ resp->header.sensor_block_num);
+ for (b = 0; b < sensor_block_num; b++) {
+ /* 8-byte sensor block head */
+ strncpy(sensor_type, &data[dnum], 4);
+ sensor_format = data[dnum+5];
+ sensor_length = data[dnum+6];
+ sensor_num = data[dnum+7];
+ dnum = dnum + 8;
+
+ dev_dbg(&client->dev,
+ "sensor block[%d]: type: %s, sensor_num: %d\n",
+ b, sensor_type, sensor_num);
+
+ if (strncmp(sensor_type, "FREQ", 4) == 0) {
+ ret = occ_renew_sensor(resp, sensor_length,
+ sensor_num, freq, b);
+ if (ret)
+ continue;
+
+ resp->sensor_block_id[freq] = b;
+ for (s = 0; s < sensor_num; s++) {
+ f_sensor = &resp->blocks[b].sensor[s];
+ f_sensor->sensor_id =
+ be16_to_cpup((const __be16 *)
+ &data[dnum]);
+ f_sensor->value = be16_to_cpup((const __be16 *)
+ &data[dnum+2]);
+ dev_dbg(&client->dev,
+ "sensor[%d]-[%d]: id: %u, value: %u\n",
+ b, s, f_sensor->sensor_id,
+ f_sensor->value);
+ dnum = dnum + sensor_length;
+ }
+ } else if (strncmp(sensor_type, "TEMP", 4) == 0) {
+ ret = occ_renew_sensor(resp, sensor_length,
+ sensor_num, temp, b);
+ if (ret)
+ continue;
+
+ resp->sensor_block_id[temp] = b;
+ for (s = 0; s < sensor_num; s++) {
+ t_sensor = &resp->blocks[b].sensor[s];
+ t_sensor->sensor_id =
+ be16_to_cpup((const __be16 *)
+ &data[dnum]);
+ t_sensor->value = be16_to_cpup((const __be16 *)
+ &data[dnum+2]);
+ dev_dbg(&client->dev,
+ "sensor[%d]-[%d]: id: %u, value: %u\n",
+ b, s, t_sensor->sensor_id,
+ t_sensor->value);
+ dnum = dnum + sensor_length;
+ }
+ } else if (strncmp(sensor_type, "POWR", 4) == 0) {
+ ret = occ_renew_sensor(resp, sensor_length,
+ sensor_num, power, b);
+ if (ret)
+ continue;
+
+ resp->sensor_block_id[power] = b;
+ for (s = 0; s < sensor_num; s++) {
+ p_sensor = &resp->blocks[b].power[s];
+ p_sensor->sensor_id =
+ be16_to_cpup((const __be16 *)
+ &data[dnum]);
+ p_sensor->update_tag =
+ be32_to_cpup((const __be32 *)
+ &data[dnum+2]);
+ p_sensor->accumulator =
+ be32_to_cpup((const __be32 *)
+ &data[dnum+6]);
+ p_sensor->value = be16_to_cpup((const __be16 *)
+ &data[dnum+10]);
+
+ dev_dbg(&client->dev,
+ "sensor[%d]-[%d]: id: %u, value: %u\n",
+ b, s, p_sensor->sensor_id,
+ p_sensor->value);
+
+ dnum = dnum + sensor_length;
+ }
+ } else if (strncmp(sensor_type, "CAPS", 4) == 0) {
+ ret = occ_renew_sensor(resp, sensor_length,
+ sensor_num, caps, b);
+ if (ret)
+ continue;
+
+ resp->sensor_block_id[caps] = b;
+ for (s = 0; s < sensor_num; s++) {
+ c_sensor = &resp->blocks[b].caps[s];
+ c_sensor->curr_powercap =
+ be16_to_cpup((const __be16 *)
+ &data[dnum]);
+ c_sensor->curr_powerreading =
+ be16_to_cpup((const __be16 *)
+ &data[dnum+2]);
+ c_sensor->norm_powercap =
+ be16_to_cpup((const __be16 *)
+ &data[dnum+4]);
+ c_sensor->max_powercap =
+ be16_to_cpup((const __be16 *)
+ &data[dnum+6]);
+ c_sensor->min_powercap =
+ be16_to_cpup((const __be16 *)
+ &data[dnum+8]);
+ c_sensor->user_powerlimit =
+ be16_to_cpup((const __be16 *)
+ &data[dnum+10]);
+
+ dnum = dnum + sensor_length;
+ dev_dbg(&client->dev, "CAPS sensor #%d:\n", s);
+ dev_dbg(&client->dev, "curr_powercap is %x\n",
+ c_sensor->curr_powercap);
+ dev_dbg(&client->dev,
+ "curr_powerreading is %x\n",
+ c_sensor->curr_powerreading);
+ dev_dbg(&client->dev, "norm_powercap is %x\n",
+ c_sensor->norm_powercap);
+ dev_dbg(&client->dev, "max_powercap is %x\n",
+ c_sensor->max_powercap);
+ dev_dbg(&client->dev, "min_powercap is %x\n",
+ c_sensor->min_powercap);
+ dev_dbg(&client->dev, "user_powerlimit is %x\n",
+ c_sensor->user_powerlimit);
+ }
+
+ } else {
+ dev_dbg(&client->dev,
+ "ERROR: sensor type %s not supported\n",
+ resp->blocks[b].sensor_type);
+ ret = -1;
+ goto err;
+ }
+
+ strncpy(resp->blocks[b].sensor_type, sensor_type, 4);
+ resp->blocks[b].sensor_format = sensor_format;
+ resp->blocks[b].sensor_length = sensor_length;
+ resp->blocks[b].sensor_num = sensor_num;
+ }
+
+ return 0;
+err:
+ deinit_occ_resp_buf(resp);
+ return ret;
+}
+
+
+/* Refer to OCC interface document for OCC command format
+ * https://github.com/open-power/docs/blob/master/occ/OCC_OpenPwr_FW_Interfaces.pdf
+ */
+static uint8_t occ_send_cmd(struct i2c_client *client, uint8_t seq,
+ uint8_t type, uint16_t length, uint8_t *data, uint8_t *resp)
+{
+ uint32_t cmd1, cmd2;
+ uint16_t checksum;
+ int i;
+
+ length = cpu_to_le16(length);
+ cmd1 = (seq << 24) | (type << 16) | length;
+ memcpy(&cmd2, data, length);
+ cmd2 <<= ((4 - length) * 8);
+
+ /* checksum: sum of every bytes of cmd1, cmd2 */
+ checksum = 0;
+ for (i = 0; i < 4; i++)
+ checksum += (cmd1 >> (i * 8)) & 0xFF;
+ for (i = 0; i < 4; i++)
+ checksum += (cmd2 >> (i * 8)) & 0xFF;
+ cmd2 |= checksum << ((2 - length) * 8);
+
+ /* Init OCB */
+ occ_putscom(client, OCB_STATUS_CONTROL_OR, 0x08000000, 0x00000000);
+ occ_putscom(client, OCB_STATUS_CONTROL_AND, 0xFBFFFFFF, 0xFFFFFFFF);
+
+ /* Send command */
+ occ_putscom(client, OCB_ADDRESS, OCC_COMMAND_ADDR, 0x00000000);
+ occ_putscom(client, OCB_ADDRESS, OCC_COMMAND_ADDR, 0x00000000);
+ occ_putscom(client, OCB_DATA, cmd1, cmd2);
+
+ /* Trigger attention */
+ occ_putscom(client, ATTN_DATA, 0x01010000, 0x00000000);
+
+ /* Get response data */
+ occ_putscom(client, OCB_ADDRESS, OCC_RESPONSE_ADDR, 0x00000000);
+ occ_getscomb(client, OCB_DATA, resp, 0);
+
+ /* return status */
+ return resp[2];
+}
+
+static int occ_get_all(struct i2c_client *client, struct occ_response *occ_resp)
+{
+ uint8_t *occ_data;
+ uint16_t num_bytes;
+ int i;
+ int ret;
+ uint8_t poll_cmd_data;
+
+ poll_cmd_data = 0x10;
+
+ /*
+ * TODO: fetch header, and then allocate the rest of the buffer based
+ * on the header size. Assuming the OCC has a fixed sized header
+ */
+ occ_data = devm_kzalloc(&client->dev, OCC_DATA_MAX, GFP_KERNEL);
+
+ ret = occ_send_cmd(client, 0, 0, 1, &poll_cmd_data, occ_data);
+ if (ret) {
+ dev_err(&client->dev, "ERROR: OCC Poll: 0x%x\n", ret);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ num_bytes = get_occdata_length(occ_data);
+
+ dev_dbg(&client->dev, "OCC data length: %d\n", num_bytes);
+
+ if (num_bytes > OCC_DATA_MAX) {
+ dev_dbg(&client->dev, "ERROR: OCC data length must be < 4KB\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (num_bytes <= 0) {
+ dev_dbg(&client->dev, "ERROR: OCC data length is zero\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* read remaining data */
+ for (i = 8; i < num_bytes + 8; i = i + 8)
+ occ_getscomb(client, OCB_DATA, occ_data, i);
+
+ ret = parse_occ_response(client, occ_data, occ_resp);
+
+out:
+ devm_kfree(&client->dev, occ_data);
+ return ret;
+}
+
+
+static int occ_update_device(struct device *dev)
+{
+ struct occ_drv_data *data = dev_get_drvdata(dev);
+ struct i2c_client *client = data->client;
+ int ret = 0;
+
+ mutex_lock(&data->update_lock);
+
+ if (time_after(jiffies, data->last_updated + data->update_interval)
+ || !data->valid) {
+ data->valid = 1;
+ ret = occ_get_all(client, &data->occ_resp);
+ if (ret)
+ data->valid = 0;
+ data->last_updated = jiffies;
+ }
+ mutex_unlock(&data->update_lock);
+
+ return ret;
+}
+
+
+static void *occ_get_sensor(struct device *hwmon_dev, enum sensor_t t)
+{
+ struct device *dev = hwmon_dev->parent;
+ struct occ_drv_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = occ_update_device(dev);
+ if (ret != 0) {
+ dev_dbg(dev, "ERROR: cannot get occ sensor data: %d\n", ret);
+ return NULL;
+ }
+
+ return occ_get_sensor_by_type(&data->occ_resp, t);
+}
+
+static int occ_get_sensor_value(struct device *hwmon_dev, enum sensor_t t,
+ int index)
+{
+ void *sensor;
+
+ if (t == caps)
+ return -1;
+
+ sensor = occ_get_sensor(hwmon_dev, t);
+
+ if (!sensor)
+ return -1;
+
+ if (t == power)
+ return ((struct power_sensor *)sensor)[index].value;
+
+ return ((struct occ_sensor *)sensor)[index].value;
+}
+
+static int occ_get_sensor_id(struct device *hwmon_dev, enum sensor_t t,
+ int index)
+{
+ void *sensor;
+
+ if (t == caps)
+ return -1;
+
+ sensor = occ_get_sensor(hwmon_dev, t);
+
+ if (!sensor)
+ return -1;
+
+ if (t == power)
+ return ((struct power_sensor *)sensor)[index].sensor_id;
+
+ return ((struct occ_sensor *)sensor)[index].sensor_id;
+}
+
+/* sysfs attributes for occ hwmon device */
+
+static ssize_t show_input(struct device *hwmon_dev,
+ struct device_attribute *da, char *buf)
+{
+ struct sensor_attr_data *sdata = container_of(da,
+ struct sensor_attr_data, dev_attr);
+ int val;
+
+ val = occ_get_sensor_value(hwmon_dev, sdata->type,
+ sdata->hwmon_index - 1);
+ if (sdata->type == temp)
+ /* in millidegree Celsius */
+ val *= 1000;
+
+ return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static ssize_t show_label(struct device *hwmon_dev,
+ struct device_attribute *da, char *buf)
+{
+ struct sensor_attr_data *sdata = container_of(da,
+ struct sensor_attr_data, dev_attr);
+ int val;
+
+ val = occ_get_sensor_id(hwmon_dev, sdata->type,
+ sdata->hwmon_index - 1);
+
+ return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static ssize_t show_caps(struct device *hwmon_dev,
+ struct device_attribute *da, char *buf)
+{
+ struct sensor_attr_data *sdata = container_of(da,
+ struct sensor_attr_data, dev_attr);
+ int nr = sdata->attr_id;
+ int n = sdata->hwmon_index - 1;
+ struct caps_sensor *sensor;
+ int val;
+
+ sensor = occ_get_sensor(hwmon_dev, caps);
+ if (!sensor) {
+ val = -1;
+ return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+ }
+
+ switch (nr) {
+ case 0:
+ val = sensor[n].curr_powercap;
+ break;
+ case 1:
+ val = sensor[n].curr_powerreading;
+ break;
+ case 2:
+ val = sensor[n].norm_powercap;
+ break;
+ case 3:
+ val = sensor[n].max_powercap;
+ break;
+ case 4:
+ val = sensor[n].min_powercap;
+ break;
+ case 5:
+ val = sensor[n].user_powerlimit;
+ break;
+ default:
+ val = -1;
+ }
+
+ return snprintf(buf, PAGE_SIZE - 1, "%d\n", val);
+}
+
+static ssize_t show_update_interval(struct device *hwmon_dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev = hwmon_dev->parent;
+ struct occ_drv_data *data = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE - 1, "%u\n",
+ jiffies_to_msecs(data->update_interval));
+}
+
+static ssize_t set_update_interval(struct device *hwmon_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct device *dev = hwmon_dev->parent;
+ struct occ_drv_data *data = dev_get_drvdata(dev);
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err)
+ return err;
+
+ data->update_interval = msecs_to_jiffies(val);
+ return count;
+}
+static DEVICE_ATTR(update_interval, S_IWUSR | S_IRUGO,
+ show_update_interval, set_update_interval);
+
+static ssize_t show_name(struct device *hwmon_dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE - 1, "%s\n", OCC_I2C_NAME);
+}
+static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
+
+static ssize_t show_user_powercap(struct device *hwmon_dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct device *dev = hwmon_dev->parent;
+ struct occ_drv_data *data = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE - 1, "%u\n", data->user_powercap);
+}
+
+
+static ssize_t set_user_powercap(struct device *hwmon_dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct device *dev = hwmon_dev->parent;
+ struct occ_drv_data *data = dev_get_drvdata(dev);
+ struct i2c_client *client = data->client;
+ uint16_t val;
+ uint8_t resp[8];
+ int err;
+
+ err = kstrtou16(buf, 10, &val);
+ if (err)
+ return err;
+
+ dev_dbg(dev, "set user powercap to: %u\n", val);
+ val = cpu_to_le16(val);
+ err = occ_send_cmd(client, 0, 0x22, 2, (uint8_t *)&val, resp);
+ if (err != 0) {
+ dev_dbg(dev,
+ "ERROR: Set User Powercap: wrong return status: %x\n",
+ err);
+ if (err == 0x13)
+ dev_info(dev,
+ "ERROR: set invalid powercap value: %x\n", val);
+ return -EINVAL;
+ }
+ data->user_powercap = val;
+ return count;
+}
+static DEVICE_ATTR(user_powercap, S_IWUSR | S_IRUGO,
+ show_user_powercap, set_user_powercap);
+
+static void deinit_sensor_groups(struct device *hwmon_dev,
+ struct sensor_group *sensor_groups)
+{
+ int cnt;
+
+ for (cnt = 0; cnt < MAX_OCC_SENSOR_TYPE; cnt++) {
+ if (sensor_groups[cnt].group.attrs)
+ devm_kfree(hwmon_dev, sensor_groups[cnt].group.attrs);
+ if (sensor_groups[cnt].sattr)
+ devm_kfree(hwmon_dev, sensor_groups[cnt].sattr);
+ sensor_groups[cnt].group.attrs = NULL;
+ sensor_groups[cnt].sattr = NULL;
+ }
+}
+
+static void occ_remove_hwmon_attrs(struct device *hwmon_dev)
+{
+ struct occ_drv_data *data = dev_get_drvdata(hwmon_dev->parent);
+ struct sensor_group *sensor_groups = data->sensor_groups;
+ int i;
+
+ if (!hwmon_dev)
+ return;
+
+ device_remove_file(hwmon_dev, &dev_attr_update_interval);
+ device_remove_file(hwmon_dev, &dev_attr_name);
+ device_remove_file(hwmon_dev, &dev_attr_user_powercap);
+
+ for (i = 0; i < MAX_OCC_SENSOR_TYPE; i++)
+ sysfs_remove_group(&hwmon_dev->kobj, &sensor_groups[i].group);
+
+ deinit_sensor_groups(hwmon_dev, sensor_groups);
+}
+
+static void sensor_attr_init(struct sensor_attr_data *sdata,
+ char *sensor_group_name,
+ char *attr_name,
+ ssize_t (*show)(struct device *dev,
+ struct device_attribute *attr,
+ char *buf))
+{
+ sysfs_attr_init(&sdata->dev_attr.attr);
+
+ snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s",
+ sensor_group_name, sdata->hwmon_index, attr_name);
+ sdata->dev_attr.attr.name = sdata->name;
+ sdata->dev_attr.attr.mode = S_IRUGO;
+ sdata->dev_attr.show = show;
+}
+
+/* create hwmon sensor sysfs attributes */
+static int create_sensor_group(struct device *hwmon_dev, enum sensor_t type,
+ int sensor_num)
+{
+ struct occ_drv_data *data = dev_get_drvdata(hwmon_dev->parent);
+ struct sensor_group *sensor_groups = data->sensor_groups;
+ struct sensor_attr_data *sdata;
+ int ret;
+ int cnt;
+
+ /* each sensor has 'label' and 'input' attributes */
+ sensor_groups[type].group.attrs = devm_kzalloc(hwmon_dev,
+ sizeof(struct attribute *) *
+ sensor_num * 2 + 1, GFP_KERNEL);
+ if (!sensor_groups[type].group.attrs) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ sensor_groups[type].sattr = devm_kzalloc(hwmon_dev,
+ sizeof(struct sensor_attr_data) *
+ sensor_num * 2, GFP_KERNEL);
+ if (!sensor_groups[type].sattr) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ for (cnt = 0; cnt < sensor_num; cnt++) {
+ sdata = &sensor_groups[type].sattr[cnt];
+ /* hwomon attributes index starts from 1 */
+ sdata->hwmon_index = cnt + 1;
+ sdata->type = type;
+ sensor_attr_init(sdata, sensor_groups[type].name, "input",
+ show_input);
+ sensor_groups[type].group.attrs[cnt] = &sdata->dev_attr.attr;
+
+ sdata = &sensor_groups[type].sattr[cnt + sensor_num];
+ sdata->hwmon_index = cnt + 1;
+ sdata->type = type;
+ sensor_attr_init(sdata, sensor_groups[type].name, "label",
+ show_label);
+ sensor_groups[type].group.attrs[cnt + sensor_num] =
+ &sdata->dev_attr.attr;
+ }
+
+ ret = sysfs_create_group(&hwmon_dev->kobj, &sensor_groups[type].group);
+ if (ret)
+ goto err;
+
+ return ret;
+err:
+ deinit_sensor_groups(hwmon_dev, sensor_groups);
+ return ret;
+}
+
+static void caps_sensor_attr_init(struct sensor_attr_data *sdata,
+ char *attr_name, uint32_t hwmon_index,
+ uint32_t attr_id)
+{
+ sdata->type = caps;
+ sdata->hwmon_index = hwmon_index;
+ sdata->attr_id = attr_id;
+
+ /* FIXME, to be compatible with user space app, we do not
+ * generate caps1_* attributes.
+ */
+ if (sdata->hwmon_index == 1)
+ snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s_%s",
+ "caps", attr_name);
+ else
+ snprintf(sdata->name, MAX_SENSOR_ATTR_LEN, "%s%d_%s",
+ "caps", sdata->hwmon_index, attr_name);
+
+ sysfs_attr_init(&sdata->dev_attr.attr);
+ sdata->dev_attr.attr.name = sdata->name;
+ sdata->dev_attr.attr.mode = S_IRUGO;
+ sdata->dev_attr.show = show_caps;
+}
+
+static char *caps_sensor_name[] = {
+ "curr_powercap",
+ "curr_powerreading",
+ "norm_powercap",
+ "max_powercap",
+ "min_powercap",
+ "user_powerlimit",
+};
+
+static int create_caps_sensor_group(struct device *hwmon_dev, int sensor_num)
+{
+ struct occ_drv_data *data = dev_get_drvdata(hwmon_dev->parent);
+ struct sensor_group *sensor_groups = data->sensor_groups;
+ int field_num = ARRAY_SIZE(caps_sensor_name);
+ struct sensor_attr_data *sdata;
+ int ret;
+ int cnt;
+ int i;
+
+ sensor_groups[caps].group.attrs = devm_kzalloc(hwmon_dev,
+ sizeof(struct attribute *) *
+ sensor_num * field_num + 1,
+ GFP_KERNEL);
+ if (!sensor_groups[caps].group.attrs) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ sensor_groups[caps].sattr = devm_kzalloc(hwmon_dev,
+ sizeof(struct sensor_attr_data) *
+ sensor_num * field_num,
+ GFP_KERNEL);
+ if (!sensor_groups[caps].sattr) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ for (cnt = 0; cnt < sensor_num; cnt++) {
+ for (i = 0; i < field_num; i++) {
+ sdata = &sensor_groups[caps].sattr[cnt * field_num + i];
+ caps_sensor_attr_init(sdata, caps_sensor_name[i],
+ cnt + 1, i);
+ sensor_groups[caps].group.attrs[cnt * field_num + i] =
+ &sdata->dev_attr.attr;
+ }
+ }
+
+ ret = sysfs_create_group(&hwmon_dev->kobj, &sensor_groups[caps].group);
+ if (ret)
+ goto err;
+
+ return ret;
+err:
+ deinit_sensor_groups(hwmon_dev, sensor_groups);
+ return ret;
+}
+
+static int occ_create_hwmon_attrs(struct device *dev)
+{
+ struct occ_drv_data *drv_data = dev_get_drvdata(dev);
+ struct device *hwmon_dev = drv_data->hwmon_dev;
+ struct sensor_group *sensor_groups = drv_data->sensor_groups;
+ int i;
+ int sensor_num;
+ int ret;
+ struct occ_response *rsp;
+ enum sensor_t t;
+
+ rsp = &drv_data->occ_resp;
+
+ for (i = 0; i < ARRAY_SIZE(rsp->sensor_block_id); i++)
+ rsp->sensor_block_id[i] = -1;
+
+ /* read sensor data from occ. */
+ ret = occ_update_device(dev);
+ if (ret != 0) {
+ dev_dbg(dev, "ERROR: cannot get occ sensor data: %d\n", ret);
+ return ret;
+ }
+ if (!rsp->blocks)
+ return -1;
+
+ ret = device_create_file(hwmon_dev, &dev_attr_name);
+ if (ret)
+ goto error;
+
+ ret = device_create_file(hwmon_dev, &dev_attr_update_interval);
+ if (ret)
+ goto error;
+
+ if (rsp->sensor_block_id[caps] >= 0) {
+ /* user powercap: only for master OCC */
+ ret = device_create_file(hwmon_dev, &dev_attr_user_powercap);
+ if (ret)
+ goto error;
+ }
+
+ sensor_groups[freq].name = "freq";
+ sensor_groups[temp].name = "temp";
+ sensor_groups[power].name = "power";
+ sensor_groups[caps].name = "caps";
+
+ for (t = 0; t < MAX_OCC_SENSOR_TYPE; t++) {
+ if (rsp->sensor_block_id[t] < 0)
+ continue;
+
+ sensor_num =
+ rsp->blocks[rsp->sensor_block_id[t]].sensor_num;
+ if (t == caps)
+ ret = create_caps_sensor_group(hwmon_dev, sensor_num);
+ else
+ ret = create_sensor_group(hwmon_dev, t, sensor_num);
+ if (ret)
+ goto error;
+ }
+
+ return 0;
+error:
+ dev_err(dev, "ERROR: cannot create hwmon attributes\n");
+ occ_remove_hwmon_attrs(drv_data->hwmon_dev);
+ return ret;
+}
+
+static ssize_t show_occ_online(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct occ_drv_data *data = dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE - 1, "%lu\n", data->occ_online);
+}
+
+static ssize_t set_occ_online(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct occ_drv_data *data = dev_get_drvdata(dev);
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err)
+ return err;
+
+ if (val == 1) {
+ if (data->occ_online == 1)
+ return count;
+
+ /* populate hwmon sysfs attr using sensor data */
+ dev_dbg(dev, "occ register hwmon @0x%x\n", data->client->addr);
+
+ data->hwmon_dev = hwmon_device_register(dev);
+ if (IS_ERR(data->hwmon_dev))
+ return PTR_ERR(data->hwmon_dev);
+
+ err = occ_create_hwmon_attrs(dev);
+ if (err) {
+ hwmon_device_unregister(data->hwmon_dev);
+ return err;
+ }
+ data->hwmon_dev->parent = dev;
+ } else if (val == 0) {
+ if (data->occ_online == 0)
+ return count;
+
+ occ_remove_hwmon_attrs(data->hwmon_dev);
+ hwmon_device_unregister(data->hwmon_dev);
+ data->hwmon_dev = NULL;
+ } else
+ return -EINVAL;
+
+ data->occ_online = val;
+ return count;
+}
+
+static DEVICE_ATTR(online, S_IWUSR | S_IRUGO,
+ show_occ_online, set_occ_online);
+
+static int occ_create_i2c_sysfs_attr(struct device *dev)
+{
+ /* create an i2c sysfs attribute, to indicate whether OCC is active */
+ return device_create_file(dev, &dev_attr_online);
+}
+
+
+/* device probe and removal */
+
+enum occ_type {
+ occ_id,
+};
+
+static int occ_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct occ_drv_data *data;
+
+ data = devm_kzalloc(dev, sizeof(struct occ_drv_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = client;
+ i2c_set_clientdata(client, data);
+ mutex_init(&data->update_lock);
+ data->update_interval = HZ;
+
+ occ_create_i2c_sysfs_attr(dev);
+
+ dev_info(dev, "occ i2c driver ready: i2c addr@0x%x\n", client->addr);
+
+ return 0;
+}
+
+static int occ_remove(struct i2c_client *client)
+{
+ struct occ_drv_data *data = i2c_get_clientdata(client);
+
+ /* free allocated sensor memory */
+ deinit_occ_resp_buf(&data->occ_resp);
+
+ device_remove_file(&client->dev, &dev_attr_online);
+
+ if (!data->hwmon_dev)
+ return 0;
+
+ occ_remove_hwmon_attrs(data->hwmon_dev);
+ hwmon_device_unregister(data->hwmon_dev);
+ return 0;
+}
+
+/* used by old-style board info. */
+static const struct i2c_device_id occ_ids[] = {
+ { OCC_I2C_NAME, occ_id, },
+ { /* LIST END */ }
+};
+MODULE_DEVICE_TABLE(i2c, occ_ids);
+
+/* use by device table */
+static const struct of_device_id i2c_occ_of_match[] = {
+ {.compatible = "ibm,occ-i2c"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, i2c_occ_of_match);
+
+/* i2c-core uses i2c-detect() to detect device in bellow address list.
+ * If exists, address will be assigned to client.
+ * It is also possible to read address from device table.
+ */
+static const unsigned short normal_i2c[] = {0x50, 0x51, I2C_CLIENT_END };
+
+static struct i2c_driver occ_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = OCC_I2C_NAME,
+ .pm = NULL,
+ .of_match_table = i2c_occ_of_match,
+ },
+ .probe = occ_probe,
+ .remove = occ_remove,
+ .id_table = occ_ids,
+ .address_list = normal_i2c,
+};
+
+module_i2c_driver(occ_driver);
+
+MODULE_AUTHOR("Li Yi <shliyi@cn.ibm.com>");
+MODULE_DESCRIPTION("BMC OCC hwmon driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7b0aa82ea38b..0a15bf25c534 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -993,6 +993,17 @@ config I2C_RCAR
This driver can also be built as a module. If so, the module
will be called i2c-rcar.
+config I2C_ASPEED
+ tristate "Aspeed AST2xxx SoC I2C Controller"
+ depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+ select I2C_SLAVE
+ help
+ If you say yes to this option, support will be included for the
+ Aspeed AST2xxx SoC I2C controller.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-aspeed.
+
comment "External I2C/SMBus adapter drivers"
config I2C_DIOLAN_U2C
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 37f2819b4560..49631cdf5ea5 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o
obj-$(CONFIG_I2C_XLR) += i2c-xlr.o
obj-$(CONFIG_I2C_XLP9XX) += i2c-xlp9xx.o
obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o
+obj-$(CONFIG_I2C_ASPEED) += i2c-aspeed.o
# External I2C/SMBus adapter drivers
obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
new file mode 100644
index 000000000000..d0d8e7ff803a
--- /dev/null
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -0,0 +1,905 @@
+/*
+ * I2C adapter for the ASPEED I2C bus access.
+ *
+ * Copyright (C) 2012-2020 ASPEED Technology Inc.
+ * Copyright 2015 IBM Corporation
+ *
+ * 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.
+ *
+ * History:
+ * 2012.07.26: Initial version [Ryan Chen]
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+
+#include <linux/dma-mapping.h>
+
+#include <asm/irq.h>
+#include <asm/io.h>
+
+#if defined(CONFIG_COLDFIRE)
+#include <asm/arch/regs-iic.h>
+#include <asm/arch/ast_i2c.h>
+#else
+//#include <plat/regs-iic.h>
+//#include <plat/ast_i2c.h>
+#endif
+
+#define BYTE_MODE 0
+#define BUFF_MODE 1
+#define DEC_DMA_MODE 2
+#define INC_DMA_MODE 3
+
+/* I2C Register */
+#define I2C_FUN_CTRL_REG 0x00
+#define I2C_AC_TIMING_REG1 0x04
+#define I2C_AC_TIMING_REG2 0x08
+#define I2C_INTR_CTRL_REG 0x0c
+#define I2C_INTR_STS_REG 0x10
+#define I2C_CMD_REG 0x14
+#define I2C_DEV_ADDR_REG 0x18
+#define I2C_BUF_CTRL_REG 0x1c
+#define I2C_BYTE_BUF_REG 0x20
+#define I2C_DMA_BASE_REG 0x24
+#define I2C_DMA_LEN_REG 0x28
+
+#define AST_I2C_DMA_SIZE 0
+#define AST_I2C_PAGE_SIZE 256
+#define MASTER_XFER_MODE BUFF_MODE
+#define SLAVE_XFER_MODE BYTE_MODE
+#define NUM_BUS 14
+
+/*AST I2C Register Definition */
+// if defined(AST_SOC_G4)
+#define AST_I2C_POOL_BUFF_2048
+#define AST_I2C_GLOBAL_REG 0x00
+#define AST_I2C_DEVICE1 0x40
+#define AST_I2C_DEVICE2 0x80
+#define AST_I2C_DEVICE3 0xc0
+#define AST_I2C_DEVICE4 0x100
+#define AST_I2C_DEVICE5 0x140
+#define AST_I2C_DEVICE6 0x180
+#define AST_I2C_DEVICE7 0x1c0
+#define AST_I2C_BUFFER_POOL2 0x200
+#define AST_I2C_DEVICE8 0x300
+#define AST_I2C_DEVICE9 0x340
+#define AST_I2C_DEVICE10 0x380
+#define AST_I2C_DEVICE11 0x3c0
+#define AST_I2C_DEVICE12 0x400
+#define AST_I2C_DEVICE13 0x440
+#define AST_I2C_DEVICE14 0x480
+#define AST_I2C_BUFFER_POOL1 0x800
+
+/* Gloable Register Definition */
+/* 0x00 : I2C Interrupt Status Register */
+/* 0x08 : I2C Interrupt Target Assignment */
+
+/* Device Register Definition */
+/* 0x00 : I2CD Function Control Register */
+#define AST_I2CD_BUFF_SEL_MASK (0x7 << 20)
+#define AST_I2CD_BUFF_SEL(x) (x << 20) // page 0 ~ 7
+#define AST_I2CD_M_SDA_LOCK_EN (0x1 << 16)
+#define AST_I2CD_MULTI_MASTER_DIS (0x1 << 15)
+#define AST_I2CD_M_SCL_DRIVE_EN (0x1 << 14)
+#define AST_I2CD_MSB_STS (0x1 << 9)
+#define AST_I2CD_SDA_DRIVE_1T_EN (0x1 << 8)
+#define AST_I2CD_M_SDA_DRIVE_1T_EN (0x1 << 7)
+#define AST_I2CD_M_HIGH_SPEED_EN (0x1 << 6)
+#define AST_I2CD_DEF_ADDR_EN (0x1 << 5)
+#define AST_I2CD_DEF_ALERT_EN (0x1 << 4)
+#define AST_I2CD_DEF_ARP_EN (0x1 << 3)
+#define AST_I2CD_DEF_GCALL_EN (0x1 << 2)
+#define AST_I2CD_SLAVE_EN (0x1 << 1)
+#define AST_I2CD_MASTER_EN (0x1 )
+
+/* 0x04 : I2CD Clock and AC Timing Control Register #1 */
+#define AST_I2CD_tBUF (0x1 << 28) // 0~7
+#define AST_I2CD_tHDSTA (0x1 << 24) // 0~7
+#define AST_I2CD_tACST (0x1 << 20) // 0~7
+#define AST_I2CD_tCKHIGH (0x1 << 16) // 0~7
+#define AST_I2CD_tCKLOW (0x1 << 12) // 0~7
+#define AST_I2CD_tHDDAT (0x1 << 10) // 0~7
+#define AST_I2CD_CLK_TO_BASE_DIV (0x1 << 8) // 0~3
+#define AST_I2CD_CLK_BASE_DIV (0x1 ) // 0~0xf
+
+/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
+#define AST_I2CD_tTIMEOUT (0x1 ) // 0~7
+#define AST_NO_TIMEOUT_CTRL 0x0
+
+
+/* 0x0c : I2CD Interrupt Control Register &
+ * 0x10 : I2CD Interrupt Status Register
+ *
+ * These share bit definitions, so use the same values for the enable &
+ * status bits.
+ */
+#define AST_I2CD_INTR_SDA_DL_TIMEOUT (0x1 << 14)
+#define AST_I2CD_INTR_BUS_RECOVER_DONE (0x1 << 13)
+#define AST_I2CD_INTR_SMBUS_ALERT (0x1 << 12)
+#define AST_I2CD_INTR_SMBUS_ARP_ADDR (0x1 << 11)
+#define AST_I2CD_INTR_SMBUS_DEV_ALERT_ADDR (0x1 << 10)
+#define AST_I2CD_INTR_SMBUS_DEF_ADDR (0x1 << 9)
+#define AST_I2CD_INTR_GCALL_ADDR (0x1 << 8)
+#define AST_I2CD_INTR_SLAVE_MATCH (0x1 << 7)
+#define AST_I2CD_INTR_SCL_TIMEOUT (0x1 << 6)
+#define AST_I2CD_INTR_ABNORMAL (0x1 << 5)
+#define AST_I2CD_INTR_NORMAL_STOP (0x1 << 4)
+#define AST_I2CD_INTR_ARBIT_LOSS (0x1 << 3)
+#define AST_I2CD_INTR_RX_DONE (0x1 << 2)
+#define AST_I2CD_INTR_TX_NAK (0x1 << 1)
+#define AST_I2CD_INTR_TX_ACK (0x1 << 0)
+
+/* 0x14 : I2CD Command/Status Register */
+#define AST_I2CD_SDA_OE (0x1 << 28)
+#define AST_I2CD_SDA_O (0x1 << 27)
+#define AST_I2CD_SCL_OE (0x1 << 26)
+#define AST_I2CD_SCL_O (0x1 << 25)
+#define AST_I2CD_TX_TIMING (0x1 << 24) // 0 ~3
+#define AST_I2CD_TX_STATUS (0x1 << 23)
+// Tx State Machine
+#define AST_I2CD_IDLE 0x0
+#define AST_I2CD_MACTIVE 0x8
+#define AST_I2CD_MSTART 0x9
+#define AST_I2CD_MSTARTR 0xa
+#define AST_I2CD_MSTOP 0xb
+#define AST_I2CD_MTXD 0xc
+#define AST_I2CD_MRXACK 0xd
+#define AST_I2CD_MRXD 0xe
+#define AST_I2CD_MTXACK 0xf
+#define AST_I2CD_SWAIT 0x1
+#define AST_I2CD_SRXD 0x4
+#define AST_I2CD_STXACK 0x5
+#define AST_I2CD_STXD 0x6
+#define AST_I2CD_SRXACK 0x7
+#define AST_I2CD_RECOVER 0x3
+
+#define AST_I2CD_SCL_LINE_STS (0x1 << 18)
+#define AST_I2CD_SDA_LINE_STS (0x1 << 17)
+#define AST_I2CD_BUS_BUSY_STS (0x1 << 16)
+#define AST_I2CD_SDA_OE_OUT_DIR (0x1 << 15)
+#define AST_I2CD_SDA_O_OUT_DIR (0x1 << 14)
+#define AST_I2CD_SCL_OE_OUT_DIR (0x1 << 13)
+#define AST_I2CD_SCL_O_OUT_DIR (0x1 << 12)
+#define AST_I2CD_BUS_RECOVER_CMD_EN (0x1 << 11)
+#define AST_I2CD_S_ALT_EN (0x1 << 10)
+// 0 : DMA Buffer, 1: Pool Buffer
+//AST1070 DMA register
+#define AST_I2CD_RX_DMA_ENABLE (0x1 << 9)
+#define AST_I2CD_TX_DMA_ENABLE (0x1 << 8)
+
+/* Command Bit */
+#define AST_I2CD_RX_BUFF_ENABLE (0x1 << 7)
+#define AST_I2CD_TX_BUFF_ENABLE (0x1 << 6)
+#define AST_I2CD_M_STOP_CMD (0x1 << 5)
+#define AST_I2CD_M_S_RX_CMD_LAST (0x1 << 4)
+#define AST_I2CD_M_RX_CMD (0x1 << 3)
+#define AST_I2CD_S_TX_CMD (0x1 << 2)
+#define AST_I2CD_M_TX_CMD (0x1 << 1)
+#define AST_I2CD_M_START_CMD (0x1 )
+
+/* 0x18 : I2CD Slave Device Address Register */
+
+/* 0x1C : I2CD Pool Buffer Control Register */
+#define AST_I2CD_RX_BUF_ADDR_GET(x) ((x>> 24)& 0xff)
+#define AST_I2CD_RX_BUF_END_ADDR_SET(x) (x << 16)
+#define AST_I2CD_TX_DATA_BUF_END_SET(x) ((x&0xff) << 8)
+#define AST_I2CD_TX_DATA_BUF_GET(x) ((x >>8) & 0xff)
+#define AST_I2CD_BUF_BASE_ADDR_SET(x) (x & 0x3f)
+
+/* 0x20 : I2CD Transmit/Receive Byte Buffer Register */
+#define AST_I2CD_GET_MODE(x) ((x >> 8) & 0x1)
+
+#define AST_I2CD_RX_BYTE_BUFFER (0xff << 8)
+#define AST_I2CD_TX_BYTE_BUFFER (0xff )
+
+//1. usage flag , 2 size, 3. request address
+/* Use platform_data instead of module parameters */
+/* Fast Mode = 400 kHz, Standard = 100 kHz */
+//static int clock = 100; /* Default: 100 kHz */
+
+/* bitmask of commands that we wait for, in the cmd_pending mask */
+#define AST_I2CD_CMDS (AST_I2CD_BUS_RECOVER_CMD_EN | \
+ AST_I2CD_M_STOP_CMD | \
+ AST_I2CD_M_RX_CMD | \
+ AST_I2CD_M_TX_CMD)
+
+static const int ast_i2c_n_busses = 14;
+
+struct ast_i2c_bus {
+ /* TODO: find a better way to do this */
+ struct ast_i2c_dev *i2c_dev;
+ struct device *dev;
+
+ void __iomem *base; /* virtual */
+ u32 state; //I2C xfer mode state matchine
+ struct i2c_adapter adap;
+ u32 bus_clk;
+ struct clk *pclk;
+ int irq;
+
+ /* i2c transfer state. this is accessed from both process and IRQ
+ * context, so is protected by cmd_lock */
+ spinlock_t cmd_lock;
+ bool send_start;
+ bool send_stop; /* last message of an xfer? */
+ bool query_len;
+ struct i2c_msg *msg; /* current tx/rx message */
+ int msg_pos; /* current byte position in message */
+
+ struct completion cmd_complete;
+ u32 cmd_sent;
+ u32 cmd_pending;
+ u32 cmd_err;
+};
+
+struct ast_i2c_controller {
+ struct device *dev;
+ void __iomem *base;
+ int irq;
+ struct irq_domain *irq_domain;
+};
+
+static inline void ast_i2c_write(struct ast_i2c_bus *bus, u32 val, u32 reg)
+{
+ writel(val, bus->base + reg);
+}
+
+static inline u32 ast_i2c_read(struct ast_i2c_bus *bus, u32 reg)
+{
+ return readl(bus->base + reg);
+}
+
+static u32 select_i2c_clock(struct ast_i2c_bus *bus)
+{
+ unsigned int inc = 0, div, divider_ratio;
+ u32 SCL_Low, SCL_High, data;
+
+ divider_ratio = clk_get_rate(bus->pclk) / bus->bus_clk;
+ for (div = 0; divider_ratio >= 16; div++) {
+ inc |= (divider_ratio & 1);
+ divider_ratio >>= 1;
+ }
+ divider_ratio += inc;
+ SCL_Low = (divider_ratio >> 1) - 1;
+ SCL_High = divider_ratio - SCL_Low - 2;
+ data = 0x77700300 | (SCL_High << 16) | (SCL_Low << 12) | div;
+ return data;
+}
+
+static void ast_i2c_dev_init(struct ast_i2c_bus *bus)
+{
+ /* reset device: disable master & slave functions */
+ ast_i2c_write(bus, 0, I2C_FUN_CTRL_REG);
+
+ dev_dbg(bus->dev, "bus_clk %u, pclk %lu\n",
+ bus->bus_clk, clk_get_rate(bus->pclk));
+
+ /* Set AC Timing */
+ if(bus->bus_clk / 1000 > 400) {
+ ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG) |
+ AST_I2CD_M_HIGH_SPEED_EN |
+ AST_I2CD_M_SDA_DRIVE_1T_EN |
+ AST_I2CD_SDA_DRIVE_1T_EN,
+ I2C_FUN_CTRL_REG);
+
+ ast_i2c_write(bus, 0x3, I2C_AC_TIMING_REG2);
+ ast_i2c_write(bus, select_i2c_clock(bus), I2C_AC_TIMING_REG1);
+ } else {
+ ast_i2c_write(bus, select_i2c_clock(bus), I2C_AC_TIMING_REG1);
+ ast_i2c_write(bus, AST_NO_TIMEOUT_CTRL, I2C_AC_TIMING_REG2);
+ }
+
+ dev_dbg(bus->dev, "reg1: %x, reg2: %x, fun_ctrl: %x\n",
+ ast_i2c_read(bus, I2C_AC_TIMING_REG1),
+ ast_i2c_read(bus, I2C_AC_TIMING_REG2),
+ ast_i2c_read(bus, I2C_FUN_CTRL_REG));
+
+ /* Enable Master Mode */
+ ast_i2c_write(bus, ast_i2c_read(bus, I2C_FUN_CTRL_REG)
+ | AST_I2CD_MASTER_EN, I2C_FUN_CTRL_REG);
+
+
+ /* Set interrupt generation of I2C controller */
+ ast_i2c_write(bus, AST_I2CD_INTR_SDA_DL_TIMEOUT |
+ AST_I2CD_INTR_BUS_RECOVER_DONE |
+ AST_I2CD_INTR_SMBUS_ALERT |
+ AST_I2CD_INTR_SCL_TIMEOUT |
+ AST_I2CD_INTR_ABNORMAL |
+ AST_I2CD_INTR_NORMAL_STOP |
+ AST_I2CD_INTR_ARBIT_LOSS |
+ AST_I2CD_INTR_RX_DONE |
+ AST_I2CD_INTR_TX_NAK |
+ AST_I2CD_INTR_TX_ACK,
+ I2C_INTR_CTRL_REG);
+
+}
+
+static void ast_i2c_issue_cmd(struct ast_i2c_bus *bus, u32 cmd)
+{
+ dev_dbg(bus->dev, "issuing cmd: %x\n", cmd);
+ bus->cmd_err = 0;
+ bus->cmd_sent = cmd;
+ bus->cmd_pending = cmd & AST_I2CD_CMDS;
+ ast_i2c_write(bus, cmd, I2C_CMD_REG);
+}
+
+static int ast_i2c_issue_oob_command(struct ast_i2c_bus *bus, u32 cmd)
+{
+ spin_lock_irq(&bus->cmd_lock);
+ init_completion(&bus->cmd_complete);
+ ast_i2c_issue_cmd(bus, cmd);
+ spin_unlock_irq(&bus->cmd_lock);
+ return wait_for_completion_interruptible_timeout(&bus->cmd_complete,
+ bus->adap.timeout*HZ);
+}
+
+static u8 ast_i2c_bus_error_recover(struct ast_i2c_bus *bus)
+{
+ u32 sts, i;
+ int r;
+
+ //Check 0x14's SDA and SCL status
+ sts = ast_i2c_read(bus,I2C_CMD_REG);
+
+ if ((sts & AST_I2CD_SDA_LINE_STS) && (sts & AST_I2CD_SCL_LINE_STS)) {
+ //Means bus is idle.
+ dev_dbg(bus->dev,
+ "I2C bus is idle. I2C slave doesn't exist?!\n");
+ return -1;
+ }
+
+ dev_dbg(bus->dev, "I2C bus hung (status %x), attempting recovery\n",
+ sts);
+
+ if ((sts & AST_I2CD_SDA_LINE_STS) && !(sts & AST_I2CD_SCL_LINE_STS)) {
+ //if SDA == 1 and SCL == 0, it means the master is locking the bus.
+ //Send a stop command to unlock the bus.
+ dev_dbg(bus->dev, "I2C's master is locking the bus, try to stop it.\n");
+
+ init_completion(&bus->cmd_complete);
+
+ ast_i2c_write(bus, AST_I2CD_M_STOP_CMD, I2C_CMD_REG);
+
+ r = wait_for_completion_interruptible_timeout(&bus->cmd_complete,
+ bus->adap.timeout*HZ);
+
+ if (bus->cmd_err) {
+ dev_dbg(bus->dev, "recovery error \n");
+ return -1;
+ }
+
+ if (r == 0) {
+ dev_dbg(bus->dev, "recovery timed out\n");
+ return -1;
+ } else {
+ dev_dbg(bus->dev, "Recovery successfully\n");
+ return 0;
+ }
+
+ } else if (!(sts & AST_I2CD_SDA_LINE_STS)) {
+ //else if SDA == 0, the device is dead. We need to reset the bus
+ //And do the recovery command.
+ dev_dbg(bus->dev, "I2C's slave is dead, try to recover it\n");
+ for (i = 0; i < 2; i++) {
+ ast_i2c_dev_init(bus);
+ ast_i2c_issue_oob_command(bus,
+ AST_I2CD_BUS_RECOVER_CMD_EN);
+ if (bus->cmd_err != 0) {
+ dev_dbg(bus->dev, "ERROR!! Failed to do recovery command(0x%08x)\n", bus->cmd_err);
+ return -1;
+ }
+ //Check 0x14's SDA and SCL status
+ sts = ast_i2c_read(bus,I2C_CMD_REG);
+ if (sts & AST_I2CD_SDA_LINE_STS) //Recover OK
+ break;
+ }
+ if (i == 2) {
+ dev_dbg(bus->dev, "ERROR!! recover failed\n");
+ return -1;
+ }
+ } else {
+ dev_dbg(bus->dev, "Don't know how to handle this case?!\n");
+ return -1;
+ }
+ dev_dbg(bus->dev, "Recovery successfully\n");
+ return 0;
+}
+
+static int ast_i2c_wait_bus_not_busy(struct ast_i2c_bus *bus)
+{
+ int timeout = 2; //TODO number
+
+ while (ast_i2c_read(bus, I2C_CMD_REG) & AST_I2CD_BUS_BUSY_STS) {
+ ast_i2c_bus_error_recover(bus);
+ if(timeout <= 0)
+ break;
+ timeout--;
+ msleep(2);
+ }
+
+ return timeout <= 0 ? EAGAIN : 0;
+}
+
+static bool ast_i2c_do_byte_xfer(struct ast_i2c_bus *bus)
+{
+ u32 cmd, data;
+
+ if (bus->send_start) {
+ dev_dbg(bus->dev, "%s %c: addr %x start, len %d\n", __func__,
+ bus->msg->flags & I2C_M_RD ? 'R' : 'W',
+ bus->msg->addr, bus->msg->len);
+
+ data = bus->msg->addr << 1;
+ if (bus->msg->flags & I2C_M_RD)
+ data |= 0x1;
+
+ cmd = AST_I2CD_M_TX_CMD | AST_I2CD_M_START_CMD;
+ if (bus->send_stop && bus->msg->len == 0)
+ cmd |= AST_I2CD_M_STOP_CMD;
+
+ ast_i2c_write(bus, data, I2C_BYTE_BUF_REG);
+ ast_i2c_issue_cmd(bus, cmd);
+
+ } else if (bus->msg_pos < bus->msg->len){
+ bool is_last = bus->msg_pos + 1 == bus->msg->len;
+
+ dev_dbg(bus->dev, "%s %c%c: addr %x xfer %d, len %d\n",
+ __func__,
+ bus->msg->flags & I2C_M_RD ? 'R' : 'W',
+ bus->send_stop && is_last ? 'T' : '-',
+ bus->msg->addr,
+ bus->msg_pos, bus->msg->len);
+
+ if (bus->msg->flags & I2C_M_RD) {
+ cmd = AST_I2CD_M_RX_CMD;
+ if (bus->send_stop && is_last && !bus->query_len)
+ cmd |= AST_I2CD_M_S_RX_CMD_LAST |
+ AST_I2CD_M_STOP_CMD;
+
+ } else {
+ cmd = AST_I2CD_M_TX_CMD;
+ ast_i2c_write(bus, bus->msg->buf[bus->msg_pos],
+ I2C_BYTE_BUF_REG);
+
+ if (bus->send_stop && is_last)
+ cmd |= AST_I2CD_M_STOP_CMD;
+ }
+ ast_i2c_issue_cmd(bus, cmd);
+
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+//TX/Rx Done
+static void ast_i2c_master_xfer_done(struct ast_i2c_bus *bus)
+{
+ bool next_msg_queued;
+
+ dev_dbg(bus->dev, "%s xfer %d%c\n", __func__,
+ bus->msg_pos,
+ bus->send_start ? 'S' : ' ');
+
+ if (bus->send_start) {
+ bus->send_start = false;
+ } else {
+
+ if (bus->msg->flags & I2C_M_RD) {
+ uint8_t data;
+
+ data = (ast_i2c_read(bus, I2C_BYTE_BUF_REG) &
+ AST_I2CD_RX_BYTE_BUFFER) >> 8;
+
+ if (bus->query_len) {
+ bus->msg->len += data;
+ bus->query_len = false;
+ dev_dbg(bus->dev, "got rx len: %d\n",
+ bus->msg->len -1);
+ }
+ bus->msg->buf[bus->msg_pos] = data;
+ }
+ bus->msg_pos++;
+ }
+
+ /* queue the next message. If there's none left, we notify the
+ * waiter */
+ next_msg_queued = ast_i2c_do_byte_xfer(bus);
+ if (!next_msg_queued)
+ complete(&bus->cmd_complete);
+}
+
+static irqreturn_t ast_i2c_bus_irq(int irq, void *dev_id)
+{
+ struct ast_i2c_bus *bus = dev_id;
+
+ const u32 errs = AST_I2CD_INTR_ARBIT_LOSS |
+ AST_I2CD_INTR_ABNORMAL |
+ AST_I2CD_INTR_SCL_TIMEOUT |
+ AST_I2CD_INTR_SDA_DL_TIMEOUT |
+ AST_I2CD_INTR_TX_NAK;
+ u32 sts, cmd;
+
+ spin_lock(&bus->cmd_lock);
+
+ cmd = ast_i2c_read(bus, I2C_CMD_REG);
+ sts = ast_i2c_read(bus, I2C_INTR_STS_REG);
+
+ dev_dbg(bus->dev, "irq! status 0x%08x, cmd 0x%08x\n", sts, cmd);
+
+ sts &= 0x7fff;
+ bus->state = cmd >> 19 & 0xf;
+
+ /* ack everything */
+ ast_i2c_write(bus, sts, I2C_INTR_STS_REG);
+
+ bus->cmd_err |= sts & errs;
+
+ /**
+ * Mask-out pending commands that this interrupt has indicated are
+ * complete. These checks need to cover all of the possible bits set
+ * in the AST_I2CD_CMDS bitmask.
+ */
+ if (sts & AST_I2CD_INTR_TX_ACK)
+ bus->cmd_pending &= ~AST_I2CD_M_TX_CMD;
+
+ if (sts & AST_I2CD_INTR_RX_DONE)
+ bus->cmd_pending &= ~AST_I2CD_M_RX_CMD;
+
+ if (sts & AST_I2CD_INTR_NORMAL_STOP)
+ bus->cmd_pending &= ~AST_I2CD_M_STOP_CMD;
+
+ if (sts & AST_I2CD_INTR_BUS_RECOVER_DONE)
+ bus->cmd_pending &= ~AST_I2CD_BUS_RECOVER_CMD_EN;
+
+ /* if we've seen an error, notify our waiter */
+ if (bus->cmd_err) {
+ complete(&bus->cmd_complete);
+
+ /* still have work to do? We'll wait for the corresponding IRQ(s) for
+ * that to complete. */
+ } else if (bus->cmd_pending) {
+ dev_dbg(bus->dev, "cmds pending: 0x%x\n", bus->cmd_pending);
+
+ /* message transfer complete */
+ } else if (bus->msg) {
+ ast_i2c_master_xfer_done(bus);
+
+ /* other (non-message) command complete: recovery, error stop. Notify
+ * waiters. */
+ } else if (bus->cmd_sent) {
+ complete(&bus->cmd_complete);
+
+ } else {
+ dev_err(bus->dev, "Invalid state (msg %p, pending %x)?",
+ bus->msg, bus->cmd_pending);
+ }
+
+ spin_unlock(&bus->cmd_lock);
+
+ return IRQ_HANDLED;
+}
+
+static int ast_i2c_do_msgs_xfer(struct ast_i2c_bus *bus,
+ struct i2c_msg *msgs, int num)
+{
+ unsigned long flags;
+ int i, ret = 0;
+ u32 err, cmd;
+
+ for (i = 0; i < num; i++) {
+
+ spin_lock_irqsave(&bus->cmd_lock, flags);
+ bus->msg = &msgs[i];
+ bus->msg_pos = 0;
+ bus->query_len = bus->msg->flags & I2C_M_RECV_LEN;
+ bus->send_start = !(bus->msg->flags & I2C_M_NOSTART);
+ bus->send_stop = !!(num == i+1);
+ init_completion(&bus->cmd_complete);
+
+ ast_i2c_do_byte_xfer(bus);
+ spin_unlock_irqrestore(&bus->cmd_lock, flags);
+
+ ret = wait_for_completion_interruptible_timeout(
+ &bus->cmd_complete,
+ bus->adap.timeout * HZ);
+
+ spin_lock_irqsave(&bus->cmd_lock, flags);
+ err = bus->cmd_err;
+ cmd = bus->cmd_sent;
+ bus->cmd_sent = 0;
+ bus->msg = NULL;
+ spin_unlock_irqrestore(&bus->cmd_lock, flags);
+
+ if (!ret) {
+ dev_dbg(bus->dev, "controller timed out\n");
+ return -EIO;
+ }
+
+ if (err != 0) {
+ if (cmd & AST_I2CD_M_STOP_CMD) {
+ return -ETIMEDOUT;
+ } else {
+ dev_dbg(bus->dev, "send stop\n");
+ ast_i2c_issue_oob_command(bus,
+ AST_I2CD_M_STOP_CMD);
+ return -EAGAIN;
+ }
+ }
+ }
+
+ return num;
+}
+
+static int ast_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct ast_i2c_bus *bus = adap->algo_data;
+ int ret, i;
+ int sts;
+
+ sts = ast_i2c_read(bus, I2C_CMD_REG);
+ dev_dbg(bus->dev, "state[%x], SCL[%d], SDA[%d], BUS[%d]\n",
+ (sts >> 19) & 0xf,
+ (sts >> 18) & 0x1,
+ (sts >> 17) & 0x1,
+ (sts >> 16) & 1);
+ /*
+ * Wait for the bus to become free.
+ */
+
+ ret = ast_i2c_wait_bus_not_busy(bus);
+ if (ret) {
+ dev_err(&adap->dev, "i2c_ast: timeout waiting for bus free\n");
+ goto out;
+ }
+
+ for (i = adap->retries; i >= 0; i--) {
+ if (i != 0)
+ dev_dbg(&adap->dev, "Do retrying transmission [%d]\n",i);
+
+ ret = ast_i2c_do_msgs_xfer(bus, msgs, num);
+ if (ret != -EAGAIN)
+ goto out;
+
+ udelay(100);
+ }
+
+ ret = -EREMOTEIO;
+out:
+
+ return ret;
+}
+
+static u32 ast_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm i2c_ast_algorithm = {
+ .master_xfer = ast_i2c_xfer,
+ .functionality = ast_i2c_functionality,
+};
+
+static int ast_i2c_probe_bus(struct platform_device *pdev)
+{
+ struct ast_i2c_bus *bus;
+ struct resource *res;
+ int ret, bus_num;
+
+ bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
+ if (!bus)
+ return -ENOMEM;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "bus", &bus_num);
+ if (ret)
+ return -ENXIO;
+
+ bus->pclk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(bus->pclk)) {
+ dev_err(&pdev->dev, "clk_get failed\n");
+ return PTR_ERR(bus->pclk);
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ bus->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(bus->base))
+ return PTR_ERR(bus->base);
+
+ bus->irq = platform_get_irq(pdev, 0);
+ if (bus->irq < 0) {
+ dev_err(&pdev->dev, "platform_get_irq failed\n");
+ return -ENXIO;
+ }
+
+ ret = devm_request_irq(&pdev->dev, bus->irq, ast_i2c_bus_irq,
+ 0, dev_name(&pdev->dev), bus);
+ if (ret) {
+ dev_err(&pdev->dev, "devm_request_irq failed\n");
+ return -ENXIO;
+ }
+
+ /* Initialize the I2C adapter */
+ spin_lock_init(&bus->cmd_lock);
+ bus->adap.nr = bus_num;
+ bus->adap.owner = THIS_MODULE;
+ bus->adap.retries = 0;
+ bus->adap.timeout = 5;
+ bus->adap.algo = &i2c_ast_algorithm;
+ bus->adap.algo_data = bus;
+ bus->adap.dev.parent = &pdev->dev;
+ bus->adap.dev.of_node = pdev->dev.of_node;
+ snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c-%d",
+ bus_num);
+
+ bus->dev = &pdev->dev;
+
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "clock-frequency", &bus->bus_clk);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Could not read clock-frequency property\n");
+ bus->bus_clk = 100000;
+ }
+
+ ast_i2c_dev_init(bus);
+
+ ret = i2c_add_numbered_adapter(&bus->adap);
+ if (ret < 0)
+ return -ENXIO;
+
+ dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
+ bus->adap.nr, bus->irq);
+
+ return 0;
+}
+
+static void noop(struct irq_data *data) { }
+
+static struct irq_chip ast_i2c_irqchip = {
+ .name = "ast-i2c",
+ .irq_unmask = noop,
+ .irq_mask = noop,
+};
+
+static void ast_i2c_controller_irq(struct irq_desc *desc)
+{
+ struct ast_i2c_controller *c = irq_desc_get_handler_data(desc);
+ unsigned long p, status;
+ unsigned int bus_irq;
+
+ status = readl(c->base);
+ for_each_set_bit(p, &status, ast_i2c_n_busses) {
+ bus_irq = irq_find_mapping(c->irq_domain, p);
+ generic_handle_irq(bus_irq);
+ }
+}
+
+static int ast_i2c_probe_controller(struct platform_device *pdev)
+{
+ struct ast_i2c_controller *controller;
+ struct device_node *np;
+ struct resource *res;
+ int i, irq;
+
+ controller = kzalloc(sizeof(*controller), GFP_KERNEL);
+ if (!controller)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ controller->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(controller->base))
+ return PTR_ERR(controller->base);
+
+ controller->irq = platform_get_irq(pdev, 0);
+ if (controller->irq < 0) {
+ dev_err(&pdev->dev, "no platform IRQ\n");
+ return -ENXIO;
+ }
+
+ controller->irq_domain = irq_domain_add_linear(pdev->dev.of_node,
+ ast_i2c_n_busses, &irq_domain_simple_ops, NULL);
+ if (!controller->irq_domain) {
+ dev_err(&pdev->dev, "no IRQ domain\n");
+ return -ENXIO;
+ }
+ controller->irq_domain->name = "ast-i2c-domain";
+
+ for (i = 0; i < ast_i2c_n_busses; i++) {
+ irq = irq_create_mapping(controller->irq_domain, i);
+ irq_set_chip_data(irq, controller);
+ irq_set_chip_and_handler(irq, &ast_i2c_irqchip,
+ handle_simple_irq);
+ }
+
+ irq_set_chained_handler_and_data(controller->irq,
+ ast_i2c_controller_irq, controller);
+
+ controller->dev = &pdev->dev;
+
+ platform_set_drvdata(pdev, controller);
+
+ dev_info(controller->dev, "i2c controller registered, irq %d\n",
+ controller->irq);
+
+ for_each_child_of_node(pdev->dev.of_node, np) {
+ int ret;
+ u32 bus_num;
+ char bus_id[sizeof("i2c-12345")];
+
+ /*
+ * Set a useful name derived from the bus number; the device
+ * tree should provide us with one that corresponds to the
+ * hardware numbering. If the property is missing the
+ * probe would fail so just skip it here.
+ */
+
+ ret = of_property_read_u32(np, "bus", &bus_num);
+ if (ret)
+ continue;
+
+ ret = snprintf(bus_id, sizeof(bus_id), "i2c-%u", bus_num);
+ if (ret >= sizeof(bus_id))
+ continue;
+
+ of_platform_device_create(np, bus_id, &pdev->dev);
+ of_node_put(np);
+ }
+
+ return 0;
+}
+
+static int ast_i2c_probe(struct platform_device *pdev)
+{
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "aspeed,ast2400-i2c-controller"))
+ return ast_i2c_probe_controller(pdev);
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "aspeed,ast2400-i2c-bus"))
+ return ast_i2c_probe_bus(pdev);
+
+ return -ENODEV;
+}
+
+static const struct of_device_id ast_i2c_of_table[] = {
+ { .compatible = "aspeed,ast2400-i2c-controller", },
+ { .compatible = "aspeed,ast2400-i2c-bus", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ast_i2c_of_table);
+
+static struct platform_driver i2c_ast_driver = {
+ .probe = ast_i2c_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = ast_i2c_of_table,
+ },
+};
+
+module_platform_driver(i2c_ast_driver);
+
+MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
+MODULE_DESCRIPTION("ASPEED AST I2C Bus Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ast_i2c");
diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c
index d625167357cc..58dee76cacc4 100644
--- a/drivers/i2c/i2c-core.c
+++ b/drivers/i2c/i2c-core.c
@@ -537,7 +537,6 @@ static int i2c_device_uevent(struct device *dev, struct kobj_uevent_env *env)
if (add_uevent_var(env, "MODALIAS=%s%s",
I2C_MODULE_PREFIX, client->name))
return -ENOMEM;
- dev_dbg(dev, "uevent\n");
return 0;
}
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 177f78f6e6d6..b3aa8940c823 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -55,3 +55,4 @@ obj-$(CONFIG_RENESAS_H8S_INTC) += irq-renesas-h8s.o
obj-$(CONFIG_ARCH_SA1100) += irq-sa11x0.o
obj-$(CONFIG_INGENIC_IRQ) += irq-ingenic.o
obj-$(CONFIG_IMX_GPCV2) += irq-imx-gpcv2.o
+obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o
diff --git a/drivers/irqchip/irq-aspeed-vic.c b/drivers/irqchip/irq-aspeed-vic.c
new file mode 100644
index 000000000000..ce029cc75177
--- /dev/null
+++ b/drivers/irqchip/irq-aspeed-vic.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2015 - Ben Herrenschmidt, IBM Corp.
+ *
+ * Driver for Aspeed "new" VIC as found in SoC generation 3 and later
+ *
+ * Based on irq-vic.c:
+ *
+ * Copyright (C) 1999 - 2003 ARM Limited
+ * Copyright (C) 2000 Deep Blue Solutions Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/syscore_ops.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+
+#include <asm/exception.h>
+#include <asm/irq.h>
+
+
+//#define DBG(fmt...) do { printk("AVIC " fmt); } while(0)
+#define DBG(fmt...) do { } while(0)
+
+/* These definitions correspond to the "new mapping" of the
+ * register set that interleaves "high" and "low". The offsets
+ * below are for the "low" register, add 4 to get to the high one
+ */
+#define AVIC_IRQ_STATUS 0x00
+#define AVIC_FIQ_STATUS 0x08
+#define AVIC_RAW_STATUS 0x10
+#define AVIC_INT_SELECT 0x18
+#define AVIC_INT_ENABLE 0x20
+#define AVIC_INT_ENABLE_CLR 0x28
+#define AVIC_INT_TRIGGER 0x30
+#define AVIC_INT_TRIGGER_CLR 0x38
+#define AVIC_INT_SENSE 0x40
+#define AVIC_INT_DUAL_EDGE 0x48
+#define AVIC_INT_EVENT 0x50
+#define AVIC_EDGE_CLR 0x58
+#define AVIC_EDGE_STATUS 0x60
+
+struct aspeed_vic {
+ void __iomem *base;
+ u32 valid_sources[2];
+ u32 edge_sources[2];
+ struct irq_domain *dom;
+};
+static struct aspeed_vic *system_avic;
+
+static void vic_init_hw(struct aspeed_vic *vic)
+{
+ u32 sense;
+
+ /* Disable all interrupts */
+ writel(0xffffffff, vic->base + AVIC_INT_ENABLE_CLR);
+ writel(0xffffffff, vic->base + AVIC_INT_ENABLE_CLR + 4);
+
+ /* Make sure no soft trigger is on */
+ writel(0xffffffff, vic->base + AVIC_INT_TRIGGER_CLR);
+ writel(0xffffffff, vic->base + AVIC_INT_TRIGGER_CLR + 4);
+
+ /* Set everything to be IRQ */
+ writel(0, vic->base + AVIC_INT_SELECT);
+ writel(0, vic->base + AVIC_INT_SELECT + 4);
+
+ /* Some interrupts have a programable high/low level trigger
+ * (4 GPIO direct inputs), for now we assume this was configured
+ * by firmware. We read which ones are edge now.
+ */
+ sense = readl(vic->base + AVIC_INT_SENSE);
+ vic->edge_sources[0] = ~sense;
+ sense = readl(vic->base + AVIC_INT_SENSE + 4);
+ vic->edge_sources[1] = ~sense;
+
+ /* Clear edge detection latches */
+ writel(0xffffffff, vic->base + AVIC_EDGE_CLR);
+ writel(0xffffffff, vic->base + AVIC_EDGE_CLR + 4);
+}
+
+static void __exception_irq_entry avic_handle_irq(struct pt_regs *regs)
+{
+ struct aspeed_vic *vic = system_avic;
+ u32 stat, irq;
+ u32 loops = 0;
+
+ /* We handle interrupts in a loop, is that necessary ? TBD */
+ for (;;) {
+ irq = 0;
+ stat = readl_relaxed(vic->base + AVIC_IRQ_STATUS);
+ if (!stat) {
+ stat = readl_relaxed(vic->base + AVIC_IRQ_STATUS + 4);
+ irq = 32;
+ }
+ if (stat == 0)
+ break;
+ irq += ffs(stat) - 1;
+ if (irq != 16)
+ DBG("irq=%d\n", irq);
+ handle_domain_irq(vic->dom, irq, regs);
+ loops++;
+ }
+ if (loops == 0)
+ DBG("S!\n");
+}
+
+static void avic_ack_irq(struct irq_data *d)
+{
+ struct aspeed_vic *vic = irq_data_get_irq_chip_data(d);
+ unsigned int sidx = d->hwirq >> 5;
+ unsigned int sbit = 1u << (d->hwirq & 0x1f);
+
+ if (d->hwirq != 16)
+ DBG("ACK %ld\n", d->hwirq);
+ /* Clear edge latch for edge interrupts, nop for level */
+ if (vic->edge_sources[sidx] & sbit)
+ writel(sbit, vic->base + AVIC_EDGE_CLR + sidx * 4);
+}
+
+static void avic_mask_irq(struct irq_data *d)
+{
+ struct aspeed_vic *vic = irq_data_get_irq_chip_data(d);
+ unsigned int sidx = d->hwirq >> 5;
+ unsigned int sbit = 1u << (d->hwirq & 0x1f);
+
+ if (d->hwirq != 16)
+ DBG("MASK %ld\n", d->hwirq);
+ writel(sbit, vic->base + AVIC_INT_ENABLE_CLR + sidx * 4);
+}
+
+static void avic_unmask_irq(struct irq_data *d)
+{
+ struct aspeed_vic *vic = irq_data_get_irq_chip_data(d);
+ unsigned int sidx = d->hwirq >> 5;
+ unsigned int sbit = 1u << (d->hwirq & 0x1f);
+
+ if (d->hwirq != 16)
+ DBG("UNMASK %ld\n", d->hwirq);
+ writel(sbit, vic->base + AVIC_INT_ENABLE + sidx * 4);
+}
+
+/* For level irq, faster than going through a nop "ack" and mask */
+static void avic_mask_ack_irq(struct irq_data *d)
+{
+ struct aspeed_vic *vic = irq_data_get_irq_chip_data(d);
+ unsigned int sidx = d->hwirq >> 5;
+ unsigned int sbit = 1u << (d->hwirq & 0x1f);
+
+ if (d->hwirq != 16)
+ DBG("MASK_ACK %ld\n", d->hwirq);
+
+ /* First mask */
+ writel(sbit, vic->base + AVIC_INT_ENABLE_CLR + sidx * 4);
+
+ /* Then clear edge latch for edge interrupts */
+ if (vic->edge_sources[sidx] & sbit)
+ writel(sbit, vic->base + AVIC_EDGE_CLR + sidx * 4);
+}
+
+static struct irq_chip avic_chip = {
+ .name = "AVIC",
+ .irq_ack = avic_ack_irq,
+ .irq_mask = avic_mask_irq,
+ .irq_unmask = avic_unmask_irq,
+ .irq_mask_ack = avic_mask_ack_irq,
+};
+
+static int avic_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hwirq)
+{
+ struct aspeed_vic *vic = d->host_data;
+ unsigned int sidx = hwirq >> 5;
+ unsigned int sbit = 1u << (hwirq & 0x1f);
+
+ /* Check if interrupt exists */
+ if (sidx > 1 || !(vic->valid_sources[sidx] & sbit))
+ return -EPERM;
+ DBG("MAP %d edge %d\n", hwirq, !!(vic->edge_sources[sidx] & sbit));
+ if (vic->edge_sources[sidx] & sbit)
+ irq_set_chip_and_handler(irq, &avic_chip, handle_edge_irq);
+ else
+ irq_set_chip_and_handler(irq, &avic_chip, handle_level_irq);
+ irq_set_chip_data(irq, vic);
+ irq_set_probe(irq);
+ return 0;
+}
+
+static struct irq_domain_ops avic_dom_ops = {
+ .map = avic_map,
+ .xlate = irq_domain_xlate_onetwocell,
+};
+
+static int __init avic_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ void __iomem *regs;
+ struct aspeed_vic *vic;
+
+ if (WARN(parent, "non-root Aspeed VIC not supported"))
+ return -EINVAL;
+ if (WARN(system_avic, "duplicate Aspeed VIC not supported"))
+ return -EINVAL;
+
+ regs = of_iomap(node, 0);
+ if (WARN_ON(!regs))
+ return -EIO;
+
+ vic = kzalloc(sizeof(struct aspeed_vic), GFP_KERNEL);
+ if (WARN_ON(!vic)) {
+ iounmap(regs);
+ return -ENOMEM;
+ }
+ vic->base = regs;
+
+ of_property_read_u32_index(node, "valid-sources", 0,
+ &vic->valid_sources[0]);
+ of_property_read_u32_index(node, "valid-sources", 1,
+ &vic->valid_sources[1]);
+
+ /* Initialize soures, all masked */
+ vic_init_hw(vic);
+
+ /* Ready to receive interrupts */
+ system_avic = vic;
+ set_handle_irq(avic_handle_irq);
+
+ /* Register our domain. XXX Count valid sources */
+ vic->dom = irq_domain_add_simple(node, 64, 0,
+ &avic_dom_ops, vic);
+
+ pr_info("Aspeed VIC Initiallized\n");
+
+ return 0;
+}
+
+IRQCHIP_DECLARE(aspeed_new_vic, "aspeed,new-vic", avic_of_init);
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 4bf7d50b1bc7..d09106d0b5d5 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -525,6 +525,11 @@ config VEXPRESS_SYSCFG
bus. System Configuration interface is one of the possible means
of generating transactions on this bus.
+config ASPEED_BT_IPMI_HOST
+ tristate "BT IPMI host driver"
+ help
+ Support for the Aspeed BT ipmi host.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 537d7f3b78da..019bb2601e02 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE) += genwqe/
obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
+obj-$(CONFIG_ASPEED_BT_IPMI_HOST) += bt-host.o
diff --git a/drivers/misc/bt-host.c b/drivers/misc/bt-host.c
new file mode 100644
index 000000000000..105d3fc664a4
--- /dev/null
+++ b/drivers/misc/bt-host.c
@@ -0,0 +1,420 @@
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/bt-host.h>
+
+#define DEVICE_NAME "bt-host"
+
+#define BT_IO_BASE 0xe4
+#define BT_IRQ 10
+
+#define BT_CR0 0x0
+#define BT_CR0_IO_BASE 16
+#define BT_CR0_IRQ 12
+#define BT_CR0_EN_CLR_SLV_RDP 0x8
+#define BT_CR0_EN_CLR_SLV_WRP 0x4
+#define BT_CR0_ENABLE_IBT 0x1
+#define BT_CR1 0x4
+#define BT_CR1_IRQ_H2B 0x01
+#define BT_CR1_IRQ_HBUSY 0x40
+#define BT_CR2 0x8
+#define BT_CR2_IRQ_H2B 0x01
+#define BT_CR2_IRQ_HBUSY 0x40
+#define BT_CR3 0xc
+#define BT_CTRL 0x10
+#define BT_CTRL_B_BUSY 0x80
+#define BT_CTRL_H_BUSY 0x40
+#define BT_CTRL_OEM0 0x20
+#define BT_CTRL_SMS_ATN 0x10
+#define BT_CTRL_B2H_ATN 0x08
+#define BT_CTRL_H2B_ATN 0x04
+#define BT_CTRL_CLR_RD_PTR 0x02
+#define BT_CTRL_CLR_WR_PTR 0x01
+#define BT_BMC2HOST 0x14
+#define BT_INTMASK 0x18
+#define BT_INTMASK_B2H_IRQEN 0x01
+#define BT_INTMASK_B2H_IRQ 0x02
+#define BT_INTMASK_BMC_HWRST 0x80
+
+struct bt_host {
+ struct device dev;
+ struct miscdevice miscdev;
+ void *base;
+ int open_count;
+ int irq;
+ wait_queue_head_t queue;
+ struct timer_list poll_timer;
+};
+
+static u8 bt_inb(struct bt_host *bt_host, int reg)
+{
+ return ioread8(bt_host->base + reg);
+}
+
+static void bt_outb(struct bt_host *bt_host, u8 data, int reg)
+{
+ iowrite8(data, bt_host->base + reg);
+}
+
+static void clr_rd_ptr(struct bt_host *bt_host)
+{
+ bt_outb(bt_host, BT_CTRL_CLR_RD_PTR, BT_CTRL);
+}
+
+static void clr_wr_ptr(struct bt_host *bt_host)
+{
+ bt_outb(bt_host, BT_CTRL_CLR_WR_PTR, BT_CTRL);
+}
+
+static void clr_h2b_atn(struct bt_host *bt_host)
+{
+ bt_outb(bt_host, BT_CTRL_H2B_ATN, BT_CTRL);
+}
+
+static void set_b_busy(struct bt_host *bt_host)
+{
+ if (!(bt_inb(bt_host, BT_CTRL) & BT_CTRL_B_BUSY))
+ bt_outb(bt_host, BT_CTRL_B_BUSY, BT_CTRL);
+}
+
+static void clr_b_busy(struct bt_host *bt_host)
+{
+ if (bt_inb(bt_host, BT_CTRL) & BT_CTRL_B_BUSY)
+ bt_outb(bt_host, BT_CTRL_B_BUSY, BT_CTRL);
+}
+
+static void set_b2h_atn(struct bt_host *bt_host)
+{
+ bt_outb(bt_host, BT_CTRL_B2H_ATN, BT_CTRL);
+}
+
+static u8 bt_read(struct bt_host *bt_host)
+{
+ return bt_inb(bt_host, BT_BMC2HOST);
+}
+
+static void bt_write(struct bt_host *bt_host, u8 c)
+{
+ bt_outb(bt_host, c, BT_BMC2HOST);
+}
+
+static void set_sms_atn(struct bt_host *bt_host)
+{
+ bt_outb(bt_host, BT_CTRL_SMS_ATN, BT_CTRL);
+}
+
+static struct bt_host *file_bt_host(struct file *file)
+{
+ return container_of(file->private_data, struct bt_host, miscdev);
+}
+
+static int bt_host_open(struct inode *inode, struct file *file)
+{
+ struct bt_host *bt_host = file_bt_host(file);
+
+ clr_b_busy(bt_host);
+
+ return 0;
+}
+
+static ssize_t bt_host_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct bt_host *bt_host = file_bt_host(file);
+ char __user *p = buf;
+ u8 len;
+
+ if (!access_ok(VERIFY_WRITE, buf, count))
+ return -EFAULT;
+
+ WARN_ON(*ppos);
+
+ if (wait_event_interruptible(bt_host->queue,
+ bt_inb(bt_host, BT_CTRL) & BT_CTRL_H2B_ATN))
+ return -ERESTARTSYS;
+
+ set_b_busy(bt_host);
+ clr_h2b_atn(bt_host);
+ clr_rd_ptr(bt_host);
+
+ len = bt_read(bt_host);
+ __put_user(len, p++);
+
+ /* We pass the length back as well */
+ if (len + 1 > count)
+ len = count - 1;
+
+ while(len) {
+ if (__put_user(bt_read(bt_host), p))
+ return -EFAULT;
+ len--; p++;
+ }
+
+ clr_b_busy(bt_host);
+
+ return p - buf;
+}
+
+static ssize_t bt_host_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct bt_host *bt_host = file_bt_host(file);
+ const char __user *p = buf;
+ u8 c;
+
+ if (!access_ok(VERIFY_READ, buf, count))
+ return -EFAULT;
+
+ WARN_ON(*ppos);
+
+ /* There's no interrupt for clearing host busy so we have to
+ * poll */
+ if (wait_event_interruptible(bt_host->queue,
+ !(bt_inb(bt_host, BT_CTRL) &
+ (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN))))
+ return -ERESTARTSYS;
+
+ clr_wr_ptr(bt_host);
+
+ while (count) {
+ if (__get_user(c, p))
+ return -EFAULT;
+
+ bt_write(bt_host, c);
+ count--; p++;
+ }
+
+ set_b2h_atn(bt_host);
+
+ return p - buf;
+}
+
+static long bt_host_ioctl(struct file *file, unsigned int cmd,
+ unsigned long param)
+{
+ struct bt_host *bt_host = file_bt_host(file);
+ switch (cmd) {
+ case BT_HOST_IOCTL_SMS_ATN:
+ set_sms_atn(bt_host);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int bt_host_release(struct inode *inode, struct file *file)
+{
+ struct bt_host *bt_host = file_bt_host(file);
+ set_b_busy(bt_host);
+ return 0;
+}
+
+static unsigned int bt_host_poll(struct file *file, poll_table *wait)
+{
+ struct bt_host *bt_host = file_bt_host(file);
+ unsigned int mask = 0;
+ uint8_t ctrl;
+
+ poll_wait(file, &bt_host->queue, wait);
+
+ ctrl = bt_inb(bt_host, BT_CTRL);
+
+ if (ctrl & BT_CTRL_H2B_ATN)
+ mask |= POLLIN;
+
+ if (!(ctrl & (BT_CTRL_H_BUSY | BT_CTRL_B2H_ATN)))
+ mask |= POLLOUT;
+
+ return mask;
+}
+
+static const struct file_operations bt_host_fops = {
+ .owner = THIS_MODULE,
+ .open = bt_host_open,
+ .read = bt_host_read,
+ .write = bt_host_write,
+ .release = bt_host_release,
+ .poll = bt_host_poll,
+ .unlocked_ioctl = bt_host_ioctl,
+};
+
+static void poll_timer(unsigned long data)
+{
+ struct bt_host *bt_host = (void *)data;
+ bt_host->poll_timer.expires += msecs_to_jiffies(500);
+ wake_up(&bt_host->queue);
+ add_timer(&bt_host->poll_timer);
+}
+
+irqreturn_t bt_host_irq(int irq, void *arg)
+{
+ struct bt_host *bt_host = arg;
+ uint32_t reg;
+
+ reg = ioread32(bt_host->base + BT_CR2);
+ reg &= BT_CR2_IRQ_H2B | BT_CR2_IRQ_HBUSY;
+ if (!reg)
+ return IRQ_NONE;
+
+ /* ack pending IRQs */
+ iowrite32(reg, bt_host->base + BT_CR2);
+
+ wake_up(&bt_host->queue);
+ return IRQ_HANDLED;
+}
+
+static int bt_host_config_irq(struct bt_host *bt_host,
+ struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ uint32_t reg;
+ int rc;
+
+ bt_host->irq = irq_of_parse_and_map(dev->of_node, 0);
+ if (!bt_host->irq)
+ return -ENODEV;
+
+ rc = devm_request_irq(dev, bt_host->irq, bt_host_irq, IRQF_SHARED,
+ DEVICE_NAME, bt_host);
+ if (rc < 0) {
+ dev_warn(dev, "Unable to request IRQ %d\n", bt_host->irq);
+ bt_host->irq = 0;
+ return rc;
+ }
+
+ /* Configure IRQs on the host clearing the H2B and HBUSY bits;
+ * H2B will be asserted when the host has data for us; HBUSY
+ * will be cleared (along with B2H) when we can write the next
+ * message to the BT buffer */
+ reg = ioread32(bt_host->base + BT_CR1);
+ reg |= BT_CR1_IRQ_H2B | BT_CR1_IRQ_HBUSY;
+ iowrite32(reg, bt_host->base + BT_CR1);
+
+ return 0;
+}
+
+static int bt_host_probe(struct platform_device *pdev)
+{
+ struct bt_host *bt_host;
+ struct device *dev;
+ struct resource *res;
+ int rc;
+
+ if (!pdev || !pdev->dev.of_node)
+ return -ENODEV;
+
+ dev = &pdev->dev;
+ dev_info(dev, "Found bt host device\n");
+
+ bt_host = devm_kzalloc(dev, sizeof(*bt_host), GFP_KERNEL);
+ if (!bt_host)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, bt_host);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "Unable to find resources\n");
+ rc = -ENXIO;
+ goto out_free;
+ }
+
+ bt_host->base = devm_ioremap_resource(&pdev->dev, res);
+ if (!bt_host->base) {
+ rc = -ENOMEM;
+ goto out_free;
+ }
+
+ init_waitqueue_head(&bt_host->queue);
+
+ bt_host->miscdev.minor = MISC_DYNAMIC_MINOR,
+ bt_host->miscdev.name = DEVICE_NAME,
+ bt_host->miscdev.fops = &bt_host_fops,
+ bt_host->miscdev.parent = dev;
+ rc = misc_register(&bt_host->miscdev);
+ if (rc) {
+ dev_err(dev, "Unable to register device\n");
+ goto out_unmap;
+ }
+
+ bt_host_config_irq(bt_host, pdev);
+
+ if (bt_host->irq) {
+ dev_info(dev, "Using IRQ %d\n", bt_host->irq);
+ } else {
+ dev_info(dev, "No IRQ; using timer\n");
+ init_timer(&bt_host->poll_timer);
+ bt_host->poll_timer.function = poll_timer;
+ bt_host->poll_timer.data = (unsigned long)bt_host;
+ bt_host->poll_timer.expires = jiffies + msecs_to_jiffies(10);
+ add_timer(&bt_host->poll_timer);
+ }
+
+ iowrite32((BT_IO_BASE << BT_CR0_IO_BASE) |
+ (BT_IRQ << BT_CR0_IRQ) |
+ BT_CR0_EN_CLR_SLV_RDP |
+ BT_CR0_EN_CLR_SLV_WRP |
+ BT_CR0_ENABLE_IBT,
+ bt_host->base + BT_CR0);
+
+ clr_b_busy(bt_host);
+
+ return 0;
+
+out_unmap:
+ devm_iounmap(&pdev->dev, bt_host->base);
+
+out_free:
+ devm_kfree(dev, bt_host);
+ return rc;
+
+}
+
+static int bt_host_remove(struct platform_device *pdev)
+{
+ struct bt_host *bt_host = dev_get_drvdata(&pdev->dev);
+ misc_deregister(&bt_host->miscdev);
+ if (!bt_host->irq)
+ del_timer_sync(&bt_host->poll_timer);
+ devm_iounmap(&pdev->dev, bt_host->base);
+ devm_kfree(&pdev->dev, bt_host);
+ bt_host = NULL;
+
+ return 0;
+}
+
+static const struct of_device_id bt_host_match[] = {
+ { .compatible = "aspeed,bt-host" },
+ { },
+};
+
+static struct platform_driver bt_host_driver = {
+ .driver = {
+ .name = DEVICE_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = bt_host_match,
+ },
+ .probe = bt_host_probe,
+ .remove = bt_host_remove,
+};
+
+module_platform_driver(bt_host_driver);
+
+MODULE_DEVICE_TABLE(of, bt_host_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alistair Popple <alistair@popple.id.au>");
+MODULE_DESCRIPTION("Linux device interface to the BT interface");
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 2fe2a7e90fa9..4a4465465d60 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -41,4 +41,15 @@ config SPI_NXP_SPIFI
Flash. Enable this option if you have a device with a SPIFI
controller and want to access the Flash as a mtd device.
+config ASPEED_FLASH_SPI
+ tristate "Aspeed flash controllers in SPI mode"
+ depends on HAS_IOMEM && OF
+ depends on ARCH_ASPEED || COMPILE_TEST
+ # IO_SPACE_LIMIT must be equivalent to (~0UL)
+ depends on !NEED_MACH_IO_H
+ help
+ This enables support for the New Static Memory Controller (FMC)
+ in the AST2400 when attached to SPI nor chips, and support for
+ the SPI Memory controller (SPI) for the BIOS.
+
endif # MTD_SPI_NOR
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index e53333ef8582..ec286105333b 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
+obj-$(CONFIG_ASPEED_FLASH_SPI) += aspeed-smc.o
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
diff --git a/drivers/mtd/spi-nor/aspeed-smc.c b/drivers/mtd/spi-nor/aspeed-smc.c
new file mode 100644
index 000000000000..ed55f283b619
--- /dev/null
+++ b/drivers/mtd/spi-nor/aspeed-smc.c
@@ -0,0 +1,563 @@
+/*
+ * ASPEED Static Memory Controller driver
+ * Copyright 2016 IBM Corporation
+ *
+ * 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.
+ *
+ */
+
+/* See comment by aspeed_smc_from_fifo */
+#ifdef CONFIG_ARM
+#define IO_SPACE_LIMIT (~0UL)
+#endif
+
+#include <linux/bug.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/sysfs.h>
+
+/*
+ * On the arm architecture, as of Linux version 4.3, memcpy_fromio
+ * stutters discarding some of the bytes read if the destination is
+ * unaligned, so we can't use it for reading from a fifo to a buffer
+ * of unknown alignment. Instead use the ins (l, w, b) family
+ * to read from the fifo. However, ARM tries to hide io port
+ * accesses from drivers unless there is a PCMCIA or PCI device, so
+ * we define the limit before all include files. There is a
+ * check in probe to make sure this will work, as long as the
+ * architecture uses an identity iomap.
+ */
+
+static void aspeed_smc_from_fifo(void *buf, const void __iomem *iop, size_t len)
+{
+ unsigned long io = (__force unsigned long)iop;
+
+ if (!len)
+ return;
+
+ /* Expect a 4 byte input port. Otherwise just read bytes */
+ if (unlikely(io & 3)) {
+ insb(io, buf, len);
+ return;
+ }
+
+ /* Align target to word: first byte then half word */
+ if ((unsigned long)buf & 1) {
+ *(u8 *)buf = inb(io);
+ buf++;
+ len--;
+ }
+ if (((unsigned long)buf & 2) && (len >= 2)) {
+ *(u16 *)buf = inw(io);
+ buf += 2;
+ len -= 2;
+ }
+ /* Transfer words, then remaining halfword and remaining byte */
+ if (len >= 4) {
+ insl(io, buf, len >> 2);
+ buf += len & ~3;
+ }
+ if (len & 2) {
+ *(u16 *)buf = inw(io);
+ buf += 2;
+ }
+ if (len & 1) {
+ *(u8 *)buf = inb(io);
+ }
+}
+
+static void aspeed_smc_to_fifo(void __iomem *iop, const void *buf, size_t len)
+{
+ unsigned long io = (__force unsigned long)iop;
+
+ if (!len)
+ return;
+
+ /* Expect a 4 byte output port. Otherwise just write bytes */
+ if (io & 3) {
+ outsb(io, buf, len);
+ return;
+ }
+
+ /* Align target to word: first byte then half word */
+ if ((unsigned long)buf & 1) {
+ outb(*(u8 *)buf, io);
+ buf++;
+ len--;
+ }
+ if (((unsigned long)buf & 2) && (len >= 2)) {
+ outw(*(u16 *)buf, io);
+ buf += 2;
+ len -= 2;
+ }
+ /* Transfer words, then remaining halfword and remaining byte */
+ if (len >= 4) {
+ outsl(io, buf, len >> 2);
+ buf += len & ~(size_t)3;
+ }
+ if (len & 2) {
+ outw(*(u16 *)buf, io);
+ buf += 2;
+ }
+ if (len & 1) {
+ outb(*(u8 *)buf, io);
+ }
+}
+
+enum smc_flash_type {
+ smc_type_nor = 0, /* controller connected to nor flash */
+ smc_type_nand = 1, /* controller connected to nand flash */
+ smc_type_spi = 2, /* controller connected to spi flash */
+};
+
+struct aspeed_smc_info {
+ u8 nce; /* number of chip enables */
+ u8 maxwidth; /* max width of spi bus */
+ bool hasdma; /* has dma engine */
+ bool hastype; /* type shift for ce 0 in cfg reg */
+ u8 we0; /* we shift for ce 0 in cfg reg */
+ u8 ctl0; /* offset in regs of ctl for ce 0 */
+ u8 cfg; /* offset in regs of cfg */
+ u8 time; /* offset in regs of timing */
+ u8 misc; /* offset in regs of misc settings */
+};
+
+static struct aspeed_smc_info fmc_info = {
+ .nce = 5,
+ .maxwidth = 4,
+ .hasdma = true,
+ .hastype = true,
+ .we0 = 16,
+ .ctl0 = 0x10,
+ .cfg = 0x00,
+ .time = 0x54,
+ .misc = 0x50,
+};
+
+static struct aspeed_smc_info smc_info = {
+ .nce = 1,
+ .maxwidth = 2,
+ .hasdma = false,
+ .hastype = false,
+ .we0 = 0,
+ .ctl0 = 0x04,
+ .cfg = 0x00,
+ .time = 0x14,
+ .misc = 0x10,
+};
+
+enum smc_ctl_reg_value {
+ smc_base, /* base value without mode for other commands */
+ smc_read, /* command reg for (maybe fast) reads */
+ smc_write, /* command reg for writes with timings */
+ smc_num_ctl_reg_values /* last value to get count of commands */
+};
+
+struct aspeed_smc_controller;
+
+struct aspeed_smc_chip {
+ struct aspeed_smc_controller *controller;
+ __le32 __iomem *ctl; /* control register */
+ void __iomem *base; /* base of chip window */
+ __le32 ctl_val[smc_num_ctl_reg_values]; /* controls with timing */
+ enum smc_flash_type type; /* what type of flash */
+ struct spi_nor nor;
+};
+
+struct aspeed_smc_controller {
+ struct mutex mutex; /* controller access mutex */
+ const struct aspeed_smc_info *info; /* type info of controller */
+ void __iomem *regs; /* controller registers */
+ struct aspeed_smc_chip *chips[0]; /* attached chips */
+};
+
+#define CONTROL_SPI_AAF_MODE BIT(31)
+#define CONTROL_SPI_IO_MODE_MASK GENMASK(30, 28)
+#define CONTROL_SPI_IO_DUAL_DATA BIT(29)
+#define CONTROL_SPI_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28))
+#define CONTROL_SPI_IO_QUAD_DATA BIT(30)
+#define CONTROL_SPI_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28))
+#define CONTROL_SPI_CE_INACTIVE_SHIFT 24
+#define CONTROL_SPI_CE_INACTIVE_MASK GENMASK(27, CONTROL_SPI_CE_INACTIVE_SHIFT)
+/* 0 = 16T ... 15 = 1T T=HCLK */
+#define CONTROL_SPI_COMMAND_SHIFT 16
+#define CONTROL_SPI_DUMMY_CYCLE_COMMAND_OUTPUT BIT(15)
+#define CONTROL_SPI_IO_DUMMY_CYCLES_HI BIT(14)
+#define CONTROL_SPI_IO_DUMMY_CYCLES_HI_SHIFT (14 - 2)
+#define CONTROL_SPI_IO_ADDRESS_4B BIT(13) /* FMC, LEGACY */
+#define CONTROL_SPI_CLK_DIV4 BIT(13) /* BIOS */
+#define CONTROL_SPI_RW_MERGE BIT(12)
+#define CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT 6
+#define CONTROL_SPI_IO_DUMMY_CYCLES_LO GENMASK(7, CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT)
+#define CONTROL_SPI_IO_DUMMY_CYCLES_MASK (CONTROL_SPI_IO_DUMMY_CYCLES_HI | \
+ CONTROL_SPI_IO_DUMMY_CYCLES_LO)
+#define CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT 8
+#define CONTROL_SPI_CLOCK_FREQ_SEL_MASK GENMASK(11, CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT)
+#define CONTROL_SPI_LSB_FIRST BIT(5)
+#define CONTROL_SPI_CLOCK_MODE_3 BIT(4)
+#define CONTROL_SPI_IN_DUAL_DATA BIT(3)
+#define CONTROL_SPI_CE_STOP_ACTIVE_CONTROL BIT(2)
+#define CONTROL_SPI_COMMAND_MODE_MASK GENMASK(1, 0)
+#define CONTROL_SPI_COMMAND_MODE_NORMAL (0)
+#define CONTROL_SPI_COMMAND_MODE_FREAD (1)
+#define CONTROL_SPI_COMMAND_MODE_WRITE (2)
+#define CONTROL_SPI_COMMAND_MODE_USER (3)
+
+#define CONTROL_SPI_KEEP_MASK (CONTROL_SPI_AAF_MODE | \
+ CONTROL_SPI_CE_INACTIVE_MASK | CONTROL_SPI_IO_ADDRESS_4B | \
+ CONTROL_SPI_IO_DUMMY_CYCLES_MASK | CONTROL_SPI_CLOCK_FREQ_SEL_MASK | \
+ CONTROL_SPI_LSB_FIRST | CONTROL_SPI_CLOCK_MODE_3)
+
+#define CONTROL_SPI_CLK_DIV4 BIT(13) /* BIOS */
+
+static u32 spi_control_fill_opcode(u8 opcode)
+{
+ return ((u32)(opcode)) << CONTROL_SPI_COMMAND_SHIFT;
+}
+
+static void aspeed_smc_start_user(struct spi_nor *nor)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+ u32 ctl = chip->ctl_val[smc_base];
+
+ mutex_lock(&chip->controller->mutex);
+
+ ctl |= CONTROL_SPI_COMMAND_MODE_USER |
+ CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
+ writel(ctl, chip->ctl);
+
+ ctl &= ~CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
+ writel(ctl, chip->ctl);
+}
+
+static void aspeed_smc_stop_user(struct spi_nor *nor)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ u32 ctl = chip->ctl_val[smc_read];
+ u32 ctl2 = ctl | CONTROL_SPI_COMMAND_MODE_USER |
+ CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
+
+ writel(ctl2, chip->ctl); /* stop user CE control */
+ writel(ctl, chip->ctl); /* default to fread or read */
+
+ mutex_unlock(&chip->controller->mutex);
+}
+
+static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ aspeed_smc_start_user(nor);
+ aspeed_smc_to_fifo(chip->base, &opcode, 1);
+ aspeed_smc_from_fifo(buf, chip->base, len);
+ aspeed_smc_stop_user(nor);
+
+ return 0;
+}
+
+static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
+ int len)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ aspeed_smc_start_user(nor);
+ aspeed_smc_to_fifo(chip->base, &opcode, 1);
+ aspeed_smc_to_fifo(chip->base, buf, len);
+ aspeed_smc_stop_user(nor);
+
+ return 0;
+}
+
+static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+ __be32 temp;
+ u32 cmdaddr;
+
+ switch (nor->addr_width) {
+ default:
+ WARN_ONCE(1, "Unexpected address width %u, defaulting to 3\n",
+ nor->addr_width);
+ /* FALLTHROUGH */
+ case 3:
+ cmdaddr = addr & 0xFFFFFF;
+
+ cmdaddr |= (u32)cmd << 24;
+
+ temp = cpu_to_be32(cmdaddr);
+ aspeed_smc_to_fifo(chip->base, &temp, 4);
+ break;
+ case 4:
+ temp = cpu_to_be32(addr);
+ aspeed_smc_to_fifo(chip->base, &cmd, 1);
+ aspeed_smc_to_fifo(chip->base, &temp, 4);
+ break;
+ }
+}
+
+static int aspeed_smc_read_user(struct spi_nor *nor, loff_t from, size_t len,
+ size_t *retlen, u_char *read_buf)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ aspeed_smc_start_user(nor);
+ aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from);
+ aspeed_smc_from_fifo(read_buf, chip->base, len);
+ *retlen += len;
+ aspeed_smc_stop_user(nor);
+
+ return 0;
+}
+
+static void aspeed_smc_write_user(struct spi_nor *nor, loff_t to, size_t len,
+ size_t *retlen, const u_char *write_buf)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ aspeed_smc_start_user(nor);
+ aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to);
+ aspeed_smc_to_fifo(chip->base, write_buf, len);
+ *retlen += len;
+ aspeed_smc_stop_user(nor);
+}
+
+static int aspeed_smc_erase(struct spi_nor *nor, loff_t offs)
+{
+ aspeed_smc_start_user(nor);
+ aspeed_smc_send_cmd_addr(nor, nor->erase_opcode, offs);
+ aspeed_smc_stop_user(nor);
+
+ return 0;
+}
+
+static int aspeed_smc_remove(struct platform_device *dev)
+{
+ struct aspeed_smc_chip *chip;
+ struct aspeed_smc_controller *controller = platform_get_drvdata(dev);
+ int n;
+
+ for (n = 0; n < controller->info->nce; n++) {
+ chip = controller->chips[n];
+ if (chip)
+ mtd_device_unregister(&chip->nor.mtd);
+ }
+
+ return 0;
+}
+
+const struct of_device_id aspeed_smc_matches[] = {
+ { .compatible = "aspeed,fmc", .data = &fmc_info },
+ { .compatible = "aspeed,smc", .data = &smc_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, aspeed_smc_matches);
+
+static struct platform_device *
+of_platform_device_create_or_find(struct device_node *child,
+ struct device *parent)
+{
+ struct platform_device *cdev;
+
+ cdev = of_platform_device_create(child, NULL, parent);
+ if (!cdev)
+ cdev = of_find_device_by_node(child);
+ return cdev;
+}
+
+static int aspeed_smc_probe(struct platform_device *dev)
+{
+ struct aspeed_smc_controller *controller;
+ const struct of_device_id *match;
+ const struct aspeed_smc_info *info;
+ struct resource *r;
+ void __iomem *regs;
+ struct device_node *child;
+ int err = 0;
+ unsigned int n;
+
+ /*
+ * This driver passes ioremap addresses to io port accessors.
+ * This works on arm if the IO_SPACE_LIMIT does not truncate
+ * the address.
+ */
+ if (~(unsigned long)IO_SPACE_LIMIT)
+ return -ENODEV;
+
+ match = of_match_device(aspeed_smc_matches, &dev->dev);
+ if (!match || !match->data)
+ return -ENODEV;
+ info = match->data;
+ r = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&dev->dev, r);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ controller = devm_kzalloc(&dev->dev, sizeof(*controller) +
+ info->nce * sizeof(controller->chips[0]), GFP_KERNEL);
+ if (!controller)
+ return -ENOMEM;
+ platform_set_drvdata(dev, controller);
+ controller->regs = regs;
+ controller->info = info;
+ mutex_init(&controller->mutex);
+
+ /* XXX turn off legacy mode if fmc ? */
+ /* XXX handshake to enable access to SMC (bios) controller w/ host? */
+
+ for_each_available_child_of_node(dev->dev.of_node, child) {
+ struct mtd_part_parser_data ppdata;
+ struct platform_device *cdev;
+ struct aspeed_smc_chip *chip;
+ u32 reg;
+
+ if (!of_device_is_compatible(child, "jedec,spi-nor"))
+ continue; /* XXX consider nand, nor children */
+
+
+ /*
+ * create a platform device from the of node.
+ * if the device already was created (eg from
+ * a prior bind/unbind cycle) use it
+ *
+ * XXX The child name will become the default mtd
+ * name in ioctl and /proc/mtd. Should we default
+ * to node->name (without unit)? The name must be
+ * unique among all platform devices. (Name would
+ * replace NULL in create call below).
+ * ... Or we can just encourage the label attribute.
+ *
+ * The only reason to do the child here is to use it in
+ * dev_err below for duplicate chip id. We could use
+ * the controller dev.
+ */
+ cdev = of_platform_device_create_or_find(child, &dev->dev);
+ if (!cdev)
+ continue;
+
+ err = of_property_read_u32(child, "reg", &n);
+ if (err == -EINVAL && info->nce == 1)
+ n = 0;
+ else if (err || n >= info->nce)
+ continue;
+ if (controller->chips[n]) {
+ dev_err(&cdev->dev,
+ "chip-id %u already in use in use by %s\n",
+ n, dev_name(controller->chips[n]->nor.dev));
+ continue;
+ }
+ chip = devm_kzalloc(&dev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ continue;
+
+ r = platform_get_resource(dev, IORESOURCE_MEM, n + 1);
+ chip->base = devm_ioremap_resource(&dev->dev, r);
+
+ if (!chip->base)
+ continue;
+ chip->controller = controller;
+ chip->ctl = controller->regs + info->ctl0 + n * 4;
+
+ /* dt said its spi. xxx Set it in controller if has_type */
+ chip->type = smc_type_spi;
+
+ /*
+ * Always turn on write enable bit in config register to
+ * allow opcodes to be sent in user mode.
+ */
+ mutex_lock(&controller->mutex);
+ reg = readl(controller->regs + info->cfg);
+ dev_dbg(&dev->dev, "flash config was %08x\n", reg);
+ reg |= 1 << (info->we0 + n); /* WEn */
+ writel(reg, controller->regs + info->cfg);
+ mutex_unlock(&controller->mutex);
+
+ /* XXX check resource within fmc CEx Segment Address Register */
+ /* XXX -- see dt vs jedec id vs bootloader */
+ /* XXX check / program clock phase/polarity, only 0 or 3 */
+
+ /*
+ * Read the existing control register to get basic values.
+ *
+ * XXX probably need more sanitation.
+ * XXX do we trust the bootloader or the device tree?
+ * spi-nor.c trusts jtag id over passed ids.
+ */
+ reg = readl(chip->ctl);
+ chip->ctl_val[smc_base] = reg & CONTROL_SPI_KEEP_MASK;
+
+ if ((reg & CONTROL_SPI_COMMAND_MODE_MASK) ==
+ CONTROL_SPI_COMMAND_MODE_NORMAL)
+ chip->ctl_val[smc_read] = reg;
+ else
+ chip->ctl_val[smc_read] = chip->ctl_val[smc_base] |
+ CONTROL_SPI_COMMAND_MODE_NORMAL;
+
+ chip->nor.dev = &cdev->dev;
+ chip->nor.priv = chip;
+ chip->nor.flash_node = child;
+ chip->nor.mtd.name = of_get_property(child, "label", NULL);
+ chip->nor.erase = aspeed_smc_erase;
+ chip->nor.read = aspeed_smc_read_user;
+ chip->nor.write = aspeed_smc_write_user;
+ chip->nor.read_reg = aspeed_smc_read_reg;
+ chip->nor.write_reg = aspeed_smc_write_reg;
+
+ /*
+ * XXX use of property and controller info width to choose
+ * SPI_NOR_QUAD , SPI_NOR_DUAL
+ */
+ err = spi_nor_scan(&chip->nor, NULL, SPI_NOR_NORMAL);
+ if (err)
+ continue;
+
+ chip->ctl_val[smc_write] = chip->ctl_val[smc_base] |
+ spi_control_fill_opcode(chip->nor.program_opcode) |
+ CONTROL_SPI_COMMAND_MODE_WRITE;
+
+ /* XXX intrepret nor flags into controller settings */
+ /* XXX enable fast read here */
+ /* XXX check if resource size big enough for chip */
+
+ memset(&ppdata, 0, sizeof(ppdata));
+ ppdata.of_node = cdev->dev.of_node;
+ err = mtd_device_parse_register(&chip->nor.mtd, NULL, &ppdata, NULL, 0);
+ if (err)
+ continue;
+ controller->chips[n] = chip;
+ }
+
+ /* did we register any children? */
+ for (n = 0; n < info->nce; n++)
+ if (controller->chips[n])
+ break;
+
+ if (n == info->nce)
+ return -ENODEV;
+
+ return 0;
+}
+
+static struct platform_driver aspeed_smc_driver = {
+ .probe = aspeed_smc_probe,
+ .remove = aspeed_smc_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = aspeed_smc_matches,
+ }
+};
+
+module_platform_driver(aspeed_smc_driver);
+
+MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver");
+MODULE_AUTHOR("Milton Miller");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/faraday/ftgmac100.c b/drivers/net/ethernet/faraday/ftgmac100.c
index 6d0c5d5eea6d..7eae1978b696 100644
--- a/drivers/net/ethernet/faraday/ftgmac100.c
+++ b/drivers/net/ethernet/faraday/ftgmac100.c
@@ -31,6 +31,7 @@
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <net/ip.h>
+#include <net/ncsi.h>
#include "ftgmac100.h"
@@ -68,12 +69,16 @@ struct ftgmac100 {
struct net_device *netdev;
struct device *dev;
+ struct ncsi_dev *ndev;
struct napi_struct napi;
struct mii_bus *mii_bus;
int phy_irq[PHY_MAX_ADDR];
struct phy_device *phydev;
int old_speed;
+
+ bool use_ncsi;
+ bool enabled;
};
static int ftgmac100_alloc_rx_page(struct ftgmac100 *priv,
@@ -86,7 +91,6 @@ static int ftgmac100_alloc_rx_page(struct ftgmac100 *priv,
FTGMAC100_INT_XPKT_ETH | \
FTGMAC100_INT_XPKT_LOST | \
FTGMAC100_INT_AHB_ERR | \
- FTGMAC100_INT_PHYSTS_CHG | \
FTGMAC100_INT_RPKT_BUF | \
FTGMAC100_INT_NO_RXBUF)
@@ -134,7 +138,7 @@ static int ftgmac100_reset_hw(struct ftgmac100 *priv)
return -EIO;
}
-static void ftgmac100_set_mac(struct ftgmac100 *priv, const unsigned char *mac)
+static void ftgmac100_do_set_mac(struct ftgmac100 *priv, const unsigned char *mac)
{
unsigned int maddr = mac[0] << 8 | mac[1];
unsigned int laddr = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5];
@@ -143,6 +147,57 @@ static void ftgmac100_set_mac(struct ftgmac100 *priv, const unsigned char *mac)
iowrite32(laddr, priv->base + FTGMAC100_OFFSET_MAC_LADR);
}
+static void ftgmac100_setup_mac(struct ftgmac100 *priv)
+{
+ unsigned char mac[6];
+ unsigned int m;
+ unsigned int l;
+
+ /* XXX TODO: Read from device-tree if provided */
+
+ m = ioread32(priv->base + FTGMAC100_OFFSET_MAC_MADR);
+ l = ioread32(priv->base + FTGMAC100_OFFSET_MAC_LADR);
+
+ mac[0] = (m >> 8) & 0xff;
+ mac[1] = (m ) & 0xff;
+ mac[2] = (l >> 24) & 0xff;
+ mac[3] = (l >> 16) & 0xff;
+ mac[4] = (l >> 8) & 0xff;
+ mac[5] = (l ) & 0xff;
+
+ /* XXX Temp workaround for u-boot garbage */
+ if (!is_valid_ether_addr(mac)) {
+ mac[5] = (m >> 8) & 0xff;
+ mac[4] = (m ) & 0xff;
+ mac[3] = (l >> 24) & 0xff;
+ mac[2] = (l >> 16) & 0xff;
+ mac[1] = (l >> 8) & 0xff;
+ mac[0] = (l ) & 0xff;
+ }
+
+ if (!is_valid_ether_addr(mac)) {
+ eth_hw_addr_random(priv->netdev);
+ dev_info(priv->dev, "Generated random MAC address %pM\n",
+ priv->netdev->dev_addr);
+ } else {
+ dev_info(priv->dev, "Read MAC address from chip %pM\n", mac);
+ memcpy(priv->netdev->dev_addr, mac, 6);
+ }
+}
+
+static int ftgmac100_set_mac_addr(struct net_device *dev, void *p)
+{
+ struct ftgmac100 *priv = netdev_priv(dev);
+
+ int ret = eth_prepare_mac_addr_change(dev, p);
+ if (ret < 0)
+ return ret;
+ eth_commit_mac_addr_change(dev, p);
+ ftgmac100_do_set_mac(priv, dev->dev_addr);
+
+ return 0;
+}
+
static void ftgmac100_init_hw(struct ftgmac100 *priv)
{
/* setup ring buffer base registers */
@@ -157,7 +212,7 @@ static void ftgmac100_init_hw(struct ftgmac100 *priv)
iowrite32(FTGMAC100_APTC_RXPOLL_CNT(1), priv->base + FTGMAC100_OFFSET_APTC);
- ftgmac100_set_mac(priv, priv->netdev->dev_addr);
+ ftgmac100_do_set_mac(priv, priv->netdev->dev_addr);
}
#define MACCR_ENABLE_ALL (FTGMAC100_MACCR_TXDMA_EN | \
@@ -956,6 +1011,8 @@ static int ftgmac100_get_settings(struct net_device *netdev,
{
struct ftgmac100 *priv = netdev_priv(netdev);
+ if (!priv->phydev)
+ return -EINVAL;
return phy_ethtool_gset(priv->phydev, cmd);
}
@@ -964,6 +1021,8 @@ static int ftgmac100_set_settings(struct net_device *netdev,
{
struct ftgmac100 *priv = netdev_priv(netdev);
+ if (!priv->phydev)
+ return -EINVAL;
return phy_ethtool_sset(priv->phydev, cmd);
}
@@ -982,7 +1041,11 @@ static irqreturn_t ftgmac100_interrupt(int irq, void *dev_id)
struct net_device *netdev = dev_id;
struct ftgmac100 *priv = netdev_priv(netdev);
- if (likely(netif_running(netdev))) {
+ /* When running in NCSI mode, the interface should be
+ * ready to receive or transmit NCSI packet before it's
+ * opened.
+ */
+ if (likely(priv->use_ncsi || netif_running(netdev))) {
/* Disable interrupts for polling */
iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
napi_schedule(&priv->napi);
@@ -1036,13 +1099,12 @@ static int ftgmac100_poll(struct napi_struct *napi, int budget)
}
if (status & (FTGMAC100_INT_NO_RXBUF | FTGMAC100_INT_RPKT_LOST |
- FTGMAC100_INT_AHB_ERR | FTGMAC100_INT_PHYSTS_CHG)) {
+ FTGMAC100_INT_AHB_ERR)) {
if (net_ratelimit())
- netdev_info(netdev, "[ISR] = 0x%x: %s%s%s%s\n", status,
+ netdev_info(netdev, "[ISR] = 0x%x: %s%s%s\n", status,
status & FTGMAC100_INT_NO_RXBUF ? "NO_RXBUF " : "",
status & FTGMAC100_INT_RPKT_LOST ? "RPKT_LOST " : "",
- status & FTGMAC100_INT_AHB_ERR ? "AHB_ERR " : "",
- status & FTGMAC100_INT_PHYSTS_CHG ? "PHYSTS_CHG" : "");
+ status & FTGMAC100_INT_AHB_ERR ? "AHB_ERR " : "");
if (status & FTGMAC100_INT_NO_RXBUF) {
/* RX buffer unavailable */
@@ -1095,17 +1157,32 @@ static int ftgmac100_open(struct net_device *netdev)
goto err_hw;
ftgmac100_init_hw(priv);
- ftgmac100_start_hw(priv, 10);
+ ftgmac100_start_hw(priv, priv->use_ncsi ? 100 : 10);
- phy_start(priv->phydev);
+ if (priv->phydev)
+ phy_start(priv->phydev);
+ else if (priv->use_ncsi)
+ netif_carrier_on(priv->netdev);
napi_enable(&priv->napi);
netif_start_queue(netdev);
/* enable all interrupts */
iowrite32(INT_MASK_ALL_ENABLED, priv->base + FTGMAC100_OFFSET_IER);
+ /* Start the NCSI device */
+ if (priv->use_ncsi){
+ err = ncsi_start_dev(priv->ndev);
+ if (err)
+ goto err_ncsi;
+ }
+
+ priv->enabled = true;
return 0;
+err_ncsi:
+ napi_disable(&priv->napi);
+ netif_stop_queue(netdev);
+ iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
err_hw:
free_irq(priv->irq, netdev);
err_irq:
@@ -1114,16 +1191,21 @@ err_alloc:
return err;
}
-static int ftgmac100_stop(struct net_device *netdev)
+static int ftgmac100_stop_dev(struct net_device *netdev)
{
struct ftgmac100 *priv = netdev_priv(netdev);
+ if (!priv->enabled)
+ return 0;
+
/* disable all interrupts */
+ priv->enabled = false;
iowrite32(0, priv->base + FTGMAC100_OFFSET_IER);
netif_stop_queue(netdev);
napi_disable(&priv->napi);
- phy_stop(priv->phydev);
+ if (priv->phydev)
+ phy_stop(priv->phydev);
ftgmac100_stop_hw(priv);
free_irq(priv->irq, netdev);
@@ -1132,6 +1214,10 @@ static int ftgmac100_stop(struct net_device *netdev)
return 0;
}
+static int ftgmac100_stop(struct net_device *netdev)
+{
+ return ftgmac100_stop_dev(netdev);
+}
static int ftgmac100_hard_start_xmit(struct sk_buff *skb,
struct net_device *netdev)
{
@@ -1166,18 +1252,78 @@ static int ftgmac100_do_ioctl(struct net_device *netdev, struct ifreq *ifr, int
{
struct ftgmac100 *priv = netdev_priv(netdev);
+ if (!priv->phydev)
+ return -EINVAL;
return phy_mii_ioctl(priv->phydev, ifr, cmd);
}
+static int ftgmac100_setup_mdio(struct ftgmac100 *priv)
+{
+ int i, err = 0;
+
+ /* initialize mdio bus */
+ priv->mii_bus = mdiobus_alloc();
+ if (!priv->mii_bus) {
+ err = -EIO;
+ goto err_alloc_mdiobus;
+ }
+
+ priv->mii_bus->name = "ftgmac100_mdio";
+ snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "ftgmac100_mii");
+
+ priv->mii_bus->priv = priv->netdev;
+ priv->mii_bus->read = ftgmac100_mdiobus_read;
+ priv->mii_bus->write = ftgmac100_mdiobus_write;
+ priv->mii_bus->irq = priv->phy_irq;
+
+ for (i = 0; i < PHY_MAX_ADDR; i++)
+ priv->mii_bus->irq[i] = PHY_POLL;
+
+ err = mdiobus_register(priv->mii_bus);
+ if (err) {
+ dev_err(priv->dev, "Cannot register MDIO bus!\n");
+ goto err_register_mdiobus;
+ }
+
+ err = ftgmac100_mii_probe(priv);
+ if (err) {
+ dev_err(priv->dev, "MII Probe failed!\n");
+ goto err_mii_probe;
+ }
+ return 0;
+
+err_mii_probe:
+ mdiobus_unregister(priv->mii_bus);
+err_register_mdiobus:
+ mdiobus_free(priv->mii_bus);
+err_alloc_mdiobus:
+ return err;
+}
+
+static void ftgmac100_destroy_mdio(struct ftgmac100 *priv)
+{
+ if (!priv->use_ncsi)
+ return;
+ phy_disconnect(priv->phydev);
+ mdiobus_unregister(priv->mii_bus);
+ mdiobus_free(priv->mii_bus);
+}
+
static const struct net_device_ops ftgmac100_netdev_ops = {
.ndo_open = ftgmac100_open,
.ndo_stop = ftgmac100_stop,
.ndo_start_xmit = ftgmac100_hard_start_xmit,
- .ndo_set_mac_address = eth_mac_addr,
+ .ndo_set_mac_address = ftgmac100_set_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_do_ioctl = ftgmac100_do_ioctl,
};
+static void ftgmac100_ncsi_handler(struct ncsi_dev *nd)
+{
+ if (nd->nd_state == ncsi_dev_state_functional)
+ pr_info("NCSI interface %s\n",
+ nd->nd_link_up ? "up" : "down");
+}
/******************************************************************************
* struct platform_driver functions
*****************************************************************************/
@@ -1187,8 +1333,7 @@ static int ftgmac100_probe(struct platform_device *pdev)
int irq;
struct net_device *netdev;
struct ftgmac100 *priv;
- int err;
- int i;
+ int err = 0;
if (!pdev)
return -ENODEV;
@@ -1208,16 +1353,29 @@ static int ftgmac100_probe(struct platform_device *pdev)
goto err_alloc_etherdev;
}
+ /* Check for NCSI mode */
+ priv = netdev_priv(netdev);
SET_NETDEV_DEV(netdev, &pdev->dev);
+ if (pdev->dev.of_node &&
+ of_get_property(pdev->dev.of_node, "use-nc-si", NULL)) {
+ dev_info(&pdev->dev, "Using NCSI interface\n");
+ priv->phydev = NULL;
+ priv->use_ncsi = true;
+ } else {
+ priv->use_ncsi = false;
+ }
netdev->ethtool_ops = &ftgmac100_ethtool_ops;
netdev->netdev_ops = &ftgmac100_netdev_ops;
- netdev->features = NETIF_F_IP_CSUM | NETIF_F_GRO;
+ if (pdev->dev.of_node &&
+ of_get_property(pdev->dev.of_node, "no-hw-checksum", NULL))
+ netdev->features = NETIF_F_GRO;
+ else
+ netdev->features = NETIF_F_IP_CSUM | NETIF_F_GRO;
platform_set_drvdata(pdev, netdev);
/* setup private data */
- priv = netdev_priv(netdev);
priv->netdev = netdev;
priv->dev = &pdev->dev;
@@ -1244,60 +1402,41 @@ static int ftgmac100_probe(struct platform_device *pdev)
priv->irq = irq;
- /* initialize mdio bus */
- priv->mii_bus = mdiobus_alloc();
- if (!priv->mii_bus) {
- err = -EIO;
- goto err_alloc_mdiobus;
- }
-
- priv->mii_bus->name = "ftgmac100_mdio";
- snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "ftgmac100_mii");
-
- priv->mii_bus->priv = netdev;
- priv->mii_bus->read = ftgmac100_mdiobus_read;
- priv->mii_bus->write = ftgmac100_mdiobus_write;
- priv->mii_bus->irq = priv->phy_irq;
-
- for (i = 0; i < PHY_MAX_ADDR; i++)
- priv->mii_bus->irq[i] = PHY_POLL;
+ /* Read MAC address or setup a new one */
+ ftgmac100_setup_mac(priv);
- err = mdiobus_register(priv->mii_bus);
- if (err) {
- dev_err(&pdev->dev, "Cannot register MDIO bus!\n");
- goto err_register_mdiobus;
- }
+ /* Register NCSI device */
+ if (priv->use_ncsi) {
+ priv->ndev = ncsi_register_dev(netdev, ftgmac100_ncsi_handler);
+ if (!priv->ndev)
+ goto err_ncsi_dev;
+ } else {
+ err = ftgmac100_setup_mdio(priv);
- err = ftgmac100_mii_probe(priv);
- if (err) {
- dev_err(&pdev->dev, "MII Probe failed!\n");
- goto err_mii_probe;
+ /* Survive PHY probe failure, chances things will work if the
+ * PHY was setup by the bootloader
+ */
+ if (err)
+ dev_warn(&pdev->dev, "Error %d setting up MDIO\n", err);
}
- /* register network device */
+ /* Register network device */
err = register_netdev(netdev);
if (err) {
dev_err(&pdev->dev, "Failed to register netdev\n");
goto err_register_netdev;
}
- netdev_info(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base);
-
- if (!is_valid_ether_addr(netdev->dev_addr)) {
- eth_hw_addr_random(netdev);
- netdev_info(netdev, "generated random MAC address %pM\n",
- netdev->dev_addr);
- }
+ netdev_dbg(netdev, "irq %d, mapped at %p\n", priv->irq, priv->base);
return 0;
err_register_netdev:
- phy_disconnect(priv->phydev);
-err_mii_probe:
- mdiobus_unregister(priv->mii_bus);
-err_register_mdiobus:
- mdiobus_free(priv->mii_bus);
-err_alloc_mdiobus:
+ if (!priv->use_ncsi)
+ ftgmac100_destroy_mdio(priv);
+ else
+ ncsi_unregister_dev(priv->ndev);
+err_ncsi_dev:
iounmap(priv->base);
err_ioremap:
release_resource(priv->res);
@@ -1318,9 +1457,7 @@ static int __exit ftgmac100_remove(struct platform_device *pdev)
unregister_netdev(netdev);
- phy_disconnect(priv->phydev);
- mdiobus_unregister(priv->mii_bus);
- mdiobus_free(priv->mii_bus);
+ ftgmac100_destroy_mdio(priv);
iounmap(priv->base);
release_resource(priv->res);
@@ -1330,11 +1467,18 @@ static int __exit ftgmac100_remove(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id ftgmac100_of_match[] = {
+ { .compatible = "faraday,ftgmac100" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ftgmac100_of_match);
+
static struct platform_driver ftgmac100_driver = {
.probe = ftgmac100_probe,
- .remove = __exit_p(ftgmac100_remove),
+ .remove = ftgmac100_remove,
.driver = {
- .name = DRV_NAME,
+ .name = DRV_NAME,
+ .of_match_table = ftgmac100_of_match,
},
};
diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c
index 3ce5d9514623..95208b0e36ca 100644
--- a/drivers/net/phy/broadcom.c
+++ b/drivers/net/phy/broadcom.c
@@ -604,6 +604,18 @@ static struct phy_driver broadcom_drivers[] = {
.ack_interrupt = brcm_fet_ack_interrupt,
.config_intr = brcm_fet_config_intr,
.driver = { .owner = THIS_MODULE },
+}, {
+ .phy_id = PHY_ID_BCM54210E,
+ .phy_id_mask = 0xfffffff0,
+ .name = "Broadcom BCM54210E",
+ .features = PHY_BASIC_FEATURES |
+ SUPPORTED_Pause | SUPPORTED_Asym_Pause,
+ .flags = PHY_HAS_MAGICANEG | PHY_HAS_INTERRUPT,
+ .config_init = bcm54xx_config_init,
+ .config_aneg = genphy_config_aneg,
+ .read_status = genphy_read_status,
+ .ack_interrupt = bcm_phy_ack_intr,
+ .config_intr = bcm_phy_config_intr,
} };
module_phy_driver(broadcom_drivers);
@@ -621,6 +633,7 @@ static struct mdio_device_id __maybe_unused broadcom_tbl[] = {
{ PHY_ID_BCM57780, 0xfffffff0 },
{ PHY_ID_BCMAC131, 0xfffffff0 },
{ PHY_ID_BCM5241, 0xfffffff0 },
+ { PHY_ID_BCM54210E, 0xfffffff0},
{ }
};
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 2a524244afec..0979c9c5e8e7 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1612,6 +1612,17 @@ config RTC_DRV_XGENE
This driver can also be built as a module, if so, the module
will be called "rtc-xgene".
+config RTC_DRV_ASPEED
+ tristate "Aspeed AST24xx RTC"
+ depends on HAS_IOMEM
+ depends on ARCH_ASPEED || COMPILE_TEST
+ help
+ If you say yes here you get support for the Aspeed AST24xx SoC real
+ time clocks.
+
+ This driver can also be built as a module, if so, the module
+ will be called "rtc-aspeed".
+
comment "HID Sensor RTC drivers"
config RTC_DRV_HID_SENSOR_TIME
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 231f76451615..52c9ae3091f2 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_RTC_DRV_ABB5ZES3) += rtc-ab-b5ze-s3.o
obj-$(CONFIG_RTC_DRV_ABX80X) += rtc-abx80x.o
obj-$(CONFIG_RTC_DRV_ARMADA38X) += rtc-armada38x.o
obj-$(CONFIG_RTC_DRV_AS3722) += rtc-as3722.o
+obj-$(CONFIG_RTC_DRV_ASPEED) += rtc-aspeed.o
obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o
obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o
obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o
diff --git a/drivers/rtc/rtc-aspeed.c b/drivers/rtc/rtc-aspeed.c
new file mode 100644
index 000000000000..bc5c30ddddfd
--- /dev/null
+++ b/drivers/rtc/rtc-aspeed.c
@@ -0,0 +1,150 @@
+/*
+ * RTC driver for the Aspeed 24xx SoCs
+ *
+ * Copyright 2015 IBM Corp.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/io.h>
+
+struct aspeed_rtc {
+ struct rtc_device *rtc_dev;
+ void __iomem *base;
+ spinlock_t lock;
+};
+
+#define RTC_TIME 0x00
+#define RTC_YEAR 0x04
+#define RTC_CTRL 0x10
+
+#define RTC_UNLOCK 0x02
+#define RTC_ENABLE 0x01
+
+static int aspeed_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct aspeed_rtc *rtc = dev_get_drvdata(dev);
+ unsigned int cent, year, mon, day, hour, min, sec;
+ u32 reg1, reg2;
+
+ do {
+ reg2 = readl(rtc->base + RTC_YEAR);
+ reg1 = readl(rtc->base + RTC_TIME);
+ } while (reg2 != readl(rtc->base + RTC_YEAR));
+
+ day = (reg1 >> 24) & 0x1f;
+ hour = (reg1 >> 16) & 0x1f;
+ min = (reg1 >> 8) & 0x3f;
+ sec = (reg1 >> 0) & 0x3f;
+ cent = (reg2 >> 16) & 0x1f;
+ year = (reg2 >> 8) & 0x7f;
+ /* Month is 1-12 in hardware, and 0-11 in struct rtc_time, however we
+ * are using mktime64 which is 1-12, so no adjustment is necessary */
+ mon = (reg2 >> 0) & 0x0f;
+
+ rtc_time64_to_tm(mktime64(cent*100 + year, mon, day, hour, min, sec),
+ tm);
+
+ return 0;
+}
+
+static int aspeed_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct aspeed_rtc *rtc = dev_get_drvdata(dev);
+ unsigned long flags;
+ u32 reg1, reg2, ctrl;
+ int year, cent;
+
+ /* tm_year counts from 1900 */
+ cent = (tm->tm_year + 1900) / 100;
+ year = tm->tm_year % 100;
+
+ reg1 = (tm->tm_mday << 24) | (tm->tm_hour << 16) | (tm->tm_min << 8) |
+ tm->tm_sec;
+
+ /* Month in struct rtc_time is 0-11, but the hardware is 1-12 */
+ reg2 = ((cent & 0x1f) << 16) | ((year & 0x7f) << 8) |
+ ((tm->tm_mon & 0xf) + 1);
+
+ /* TODO: Do we need to lock? */
+ spin_lock_irqsave(&rtc->lock, flags);
+
+ ctrl = readl(rtc->base + RTC_CTRL);
+ writel(ctrl | RTC_UNLOCK, rtc->base + RTC_CTRL);
+
+ writel(reg1, rtc->base + RTC_TIME);
+ writel(reg2, rtc->base + RTC_YEAR);
+
+ writel(ctrl, rtc->base + RTC_CTRL);
+
+ spin_unlock_irqrestore(&rtc->lock, flags);
+
+ return 0;
+}
+
+static struct rtc_class_ops aspeed_rtc_ops = {
+ .read_time = aspeed_rtc_read_time,
+ .set_time = aspeed_rtc_set_time,
+};
+
+static int aspeed_rtc_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ struct aspeed_rtc *rtc;
+
+ rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
+ if (!rtc)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ rtc->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(rtc->base))
+ return PTR_ERR(rtc->base);
+
+ platform_set_drvdata(pdev, rtc);
+
+ rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,
+ &aspeed_rtc_ops, THIS_MODULE);
+
+ if (IS_ERR(rtc->rtc_dev)) {
+ dev_err(&pdev->dev, "failed to register\n");
+ return PTR_ERR(rtc->rtc_dev);
+ }
+
+ spin_lock_init(&rtc->lock);
+
+ /* Enable RTC and clear the unlock bit */
+ writel(RTC_ENABLE, rtc->base + RTC_CTRL);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id aspeed_rtc_of_match_table[] = {
+ { .compatible = "aspeed,rtc", },
+ {}
+};
+#endif
+
+static struct platform_driver aspeed_rtc_driver = {
+ .driver = {
+ .name = "aspeed-rtc",
+ .of_match_table = of_match_ptr(aspeed_rtc_of_match_table),
+ },
+};
+
+module_platform_driver_probe(aspeed_rtc_driver, aspeed_rtc_probe);
+
+MODULE_DESCRIPTION("Aspeed AST24xx RTC driver");
+MODULE_AUTHOR("Joel Stanley <joel@jms.id.au>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index f38beb28e7ae..79645b0da152 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1094,6 +1094,16 @@ config SERIAL_NETX_CONSOLE
If you have enabled the serial port on the Hilscher NetX SoC
you can make it the console by answering Y to this option.
+config SERIAL_ASPEED_VUART
+ tristate "Aspeed Virtual UART"
+ depends on OF
+ depends on SERIAL_8250
+ help
+ If you want to use the virtual UART (VUART) device on Aspeed
+ BMC platforms, enable this option. This enables the 16550A-
+ compatible device on the local LPC bus, giving a UART device
+ with no physical RS232 connections.
+
config SERIAL_OF_PLATFORM
tristate "Serial port on Open Firmware platform bus"
depends on OF
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 5ab41119b3dc..72dc094ba3fa 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_SERIAL_MSM) += msm_serial.o
obj-$(CONFIG_SERIAL_NETX) += netx-serial.o
obj-$(CONFIG_SERIAL_OF_PLATFORM) += of_serial.o
obj-$(CONFIG_SERIAL_OF_PLATFORM_NWPSERIAL) += nwpserial.o
+obj-$(CONFIG_SERIAL_ASPEED_VUART) += aspeed-vuart.o
obj-$(CONFIG_SERIAL_KGDB_NMI) += kgdb_nmi.o
obj-$(CONFIG_SERIAL_KS8695) += serial_ks8695.o
obj-$(CONFIG_SERIAL_OMAP) += omap-serial.o
diff --git a/drivers/tty/serial/aspeed-vuart.c b/drivers/tty/serial/aspeed-vuart.c
new file mode 100644
index 000000000000..020c8159d25d
--- /dev/null
+++ b/drivers/tty/serial/aspeed-vuart.c
@@ -0,0 +1,333 @@
+/*
+ * Serial Port driver for Aspeed VUART device
+ *
+ * Copyright (C) 2016 Jeremy Kerr <jk@ozlabs.org>, IBM Corp.
+ * Copyright (C) 2006 Arnd Bergmann <arnd@arndb.de>, 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/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+
+#include "8250/8250.h"
+
+#define AST_VUART_GCRA 0x20
+#define AST_VUART_GCRA_VUART_EN 0x01
+#define AST_VUART_GCRA_HOST_TX_DISCARD 0x20
+#define AST_VUART_GCRB 0x24
+#define AST_VUART_GCRB_HOST_SIRQ_MASK 0xf0
+#define AST_VUART_GCRB_HOST_SIRQ_SHIFT 4
+#define AST_VUART_ADDRL 0x28
+#define AST_VUART_ADDRH 0x2c
+
+struct ast_vuart {
+ struct platform_device *pdev;
+ void __iomem *regs;
+ struct clk *clk;
+ int line;
+};
+
+static ssize_t ast_vuart_show_addr(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ast_vuart *vuart = dev_get_drvdata(dev);
+ u16 addr;
+
+ addr = (readb(vuart->regs + AST_VUART_ADDRH) << 8) |
+ (readb(vuart->regs + AST_VUART_ADDRL));
+
+ return snprintf(buf, PAGE_SIZE - 1, "0x%x\n", addr);
+}
+
+static ssize_t ast_vuart_set_addr(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ast_vuart *vuart = dev_get_drvdata(dev);
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 0, &val);
+ if (err)
+ return err;
+
+ writeb((val >> 8) & 0xff, vuart->regs + AST_VUART_ADDRH);
+ writeb((val >> 0) & 0xff, vuart->regs + AST_VUART_ADDRL);
+
+ return count;
+}
+
+static DEVICE_ATTR(lpc_address, S_IWUSR | S_IRUGO,
+ ast_vuart_show_addr, ast_vuart_set_addr);
+
+static ssize_t ast_vuart_show_sirq(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ast_vuart *vuart = dev_get_drvdata(dev);
+ u8 reg;
+
+ reg = readb(vuart->regs + AST_VUART_GCRB);
+ reg &= AST_VUART_GCRB_HOST_SIRQ_MASK;
+ reg >>= AST_VUART_GCRB_HOST_SIRQ_SHIFT;
+
+ return snprintf(buf, PAGE_SIZE - 1, "%u\n", reg);
+}
+
+static ssize_t ast_vuart_set_sirq(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ast_vuart *vuart = dev_get_drvdata(dev);
+ unsigned long val;
+ int err;
+ u8 reg;
+
+ err = kstrtoul(buf, 0, &val);
+ if (err)
+ return err;
+
+ val <<= AST_VUART_GCRB_HOST_SIRQ_SHIFT;
+ val &= AST_VUART_GCRB_HOST_SIRQ_MASK;
+
+ reg = readb(vuart->regs + AST_VUART_GCRB);
+ reg &= ~AST_VUART_GCRB_HOST_SIRQ_MASK;
+ reg |= val;
+ writeb(reg, vuart->regs + AST_VUART_GCRB);
+
+ return count;
+}
+
+static DEVICE_ATTR(sirq, S_IWUSR | S_IRUGO,
+ ast_vuart_show_sirq, ast_vuart_set_sirq);
+
+static void ast_vuart_set_enabled(struct ast_vuart *vuart, bool enabled)
+{
+ u8 reg;
+
+ reg = readb(vuart->regs + AST_VUART_GCRA);
+ reg &= ~AST_VUART_GCRA_VUART_EN;
+ if (enabled)
+ reg |= AST_VUART_GCRA_VUART_EN;
+ writeb(reg, vuart->regs + AST_VUART_GCRA);
+}
+
+static void ast_vuart_set_host_tx_discard(struct ast_vuart *vuart, bool discard)
+{
+ u8 reg;
+
+ reg = readb(vuart->regs + AST_VUART_GCRA);
+
+ /* if the HOST_TX_DISCARD bit is set, discard is *disabled* */
+ reg &= ~AST_VUART_GCRA_HOST_TX_DISCARD;
+ if (!discard)
+ reg |= AST_VUART_GCRA_HOST_TX_DISCARD;
+
+ writeb(reg, vuart->regs + AST_VUART_GCRA);
+}
+
+static int ast_vuart_startup(struct uart_port *uart_port)
+{
+ struct uart_8250_port *uart_8250_port = up_to_u8250p(uart_port);
+ struct ast_vuart *vuart = uart_8250_port->port.private_data;
+ int rc;
+
+ rc = serial8250_do_startup(uart_port);
+ if (rc)
+ return rc;
+
+ ast_vuart_set_host_tx_discard(vuart, false);
+
+ return 0;
+}
+
+static void ast_vuart_shutdown(struct uart_port *uart_port)
+{
+ struct uart_8250_port *uart_8250_port = up_to_u8250p(uart_port);
+ struct ast_vuart *vuart = uart_8250_port->port.private_data;
+
+ ast_vuart_set_host_tx_discard(vuart, true);
+
+ serial8250_do_shutdown(uart_port);
+}
+
+
+/**
+ * The device tree parsing code here is heavily based on that of the of_serial
+ * driver, but we have a few core differences, as we need to use our own
+ * ioremapping for extra register support
+ */
+static int ast_vuart_probe(struct platform_device *pdev)
+{
+ struct uart_8250_port port;
+ struct resource resource;
+ struct ast_vuart *vuart;
+ struct device_node *np;
+ u32 clk, prop;
+ int rc;
+
+ np = pdev->dev.of_node;
+
+ vuart = devm_kzalloc(&pdev->dev, sizeof(*vuart), GFP_KERNEL);
+ if (!vuart)
+ return -ENOMEM;
+
+ vuart->pdev = pdev;
+ rc = of_address_to_resource(np, 0, &resource);
+ if (rc) {
+ dev_warn(&pdev->dev, "invalid address\n");
+ return rc;
+ }
+
+ /* create our own mapping for VUART-specific registers */
+ vuart->regs = devm_ioremap_resource(&pdev->dev, &resource);
+ if (IS_ERR(vuart->regs)) {
+ dev_warn(&pdev->dev, "failed to map registers\n");
+ return PTR_ERR(vuart->regs);
+ }
+
+ memset(&port, 0, sizeof(port));
+ port.port.private_data = vuart;
+ port.port.membase = vuart->regs;
+ port.port.mapbase = resource.start;
+ port.port.mapsize = resource_size(&resource);
+ port.port.startup = ast_vuart_startup;
+ port.port.shutdown = ast_vuart_shutdown;
+
+ if (of_property_read_u32(np, "clock-frequency", &clk)) {
+ vuart->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(vuart->clk)) {
+ dev_warn(&pdev->dev,
+ "clk or clock-frequency not defined\n");
+ return PTR_ERR(vuart->clk);
+ }
+
+ rc = clk_prepare_enable(vuart->clk);
+ if (rc < 0)
+ return rc;
+
+ clk = clk_get_rate(vuart->clk);
+ }
+
+ /* If current-speed was set, then try not to change it. */
+ if (of_property_read_u32(np, "current-speed", &prop) == 0)
+ port.port.custom_divisor = clk / (16 * prop);
+
+ /* Check for shifted address mapping */
+ if (of_property_read_u32(np, "reg-offset", &prop) == 0)
+ port.port.mapbase += prop;
+
+ /* Check for registers offset within the devices address range */
+ if (of_property_read_u32(np, "reg-shift", &prop) == 0)
+ port.port.regshift = prop;
+
+ /* Check for fifo size */
+ if (of_property_read_u32(np, "fifo-size", &prop) == 0)
+ port.port.fifosize = prop;
+
+ /* Check for a fixed line number */
+ rc = of_alias_get_id(np, "serial");
+ if (rc >= 0)
+ port.port.line = rc;
+
+ port.port.irq = irq_of_parse_and_map(np, 0);
+ port.port.iotype = UPIO_MEM;
+ if (of_property_read_u32(np, "reg-io-width", &prop) == 0) {
+ switch (prop) {
+ case 1:
+ port.port.iotype = UPIO_MEM;
+ break;
+ case 4:
+ port.port.iotype = of_device_is_big_endian(np) ?
+ UPIO_MEM32BE : UPIO_MEM32;
+ break;
+ default:
+ dev_warn(&pdev->dev, "unsupported reg-io-width (%d)\n",
+ prop);
+ rc = -EINVAL;
+ goto err_clk_disable;
+ }
+ }
+
+ port.port.type = PORT_16550A;
+ port.port.uartclk = clk;
+ port.port.flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF
+ | UPF_FIXED_PORT | UPF_FIXED_TYPE;
+
+ if (of_find_property(np, "no-loopback-test", NULL))
+ port.port.flags |= UPF_SKIP_TEST;
+
+ port.port.dev = &pdev->dev;
+
+ if (port.port.fifosize)
+ port.capabilities = UART_CAP_FIFO;
+
+ if (of_property_read_bool(pdev->dev.of_node,
+ "auto-flow-control"))
+ port.capabilities |= UART_CAP_AFE;
+
+ rc = serial8250_register_8250_port(&port);
+ if (rc < 0)
+ goto err_clk_disable;
+
+
+ vuart->line = rc;
+ ast_vuart_set_enabled(vuart, true);
+ ast_vuart_set_host_tx_discard(vuart, true);
+ platform_set_drvdata(pdev, vuart);
+
+ /* extra sysfs control */
+ rc = device_create_file(&pdev->dev, &dev_attr_lpc_address);
+ if (rc)
+ dev_warn(&pdev->dev, "can't create lpc_address file\n");
+ rc = device_create_file(&pdev->dev, &dev_attr_sirq);
+ if (rc)
+ dev_warn(&pdev->dev, "can't create sirq file\n");
+
+ return 0;
+
+err_clk_disable:
+ if (vuart->clk)
+ clk_disable_unprepare(vuart->clk);
+
+ irq_dispose_mapping(port.port.irq);
+ return rc;
+}
+
+static int ast_vuart_remove(struct platform_device *pdev)
+{
+ struct ast_vuart *vuart = platform_get_drvdata(pdev);
+
+ ast_vuart_set_enabled(vuart, false);
+
+ if (vuart->clk)
+ clk_disable_unprepare(vuart->clk);
+ return 0;
+}
+
+static const struct of_device_id ast_vuart_table[] = {
+ { .compatible = "aspeed,vuart" },
+ { },
+};
+
+static struct platform_driver ast_vuart_driver = {
+ .driver = {
+ .name = "aspeed-vuart",
+ .of_match_table = ast_vuart_table,
+ },
+ .probe = ast_vuart_probe,
+ .remove = ast_vuart_remove,
+};
+
+module_platform_driver(ast_vuart_driver);
+
+MODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for Aspeed VUART device");
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 1c427beffadd..48dddc7159e2 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -578,6 +578,16 @@ config LPC18XX_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called lpc18xx_wdt.
+config ASPEED_24xx_WATCHDOG
+ tristate "Aspeed 23xx 24xx SoCs watchdog support"
+ depends on (ARCH_ASPEED || COMPILE_TEST) && OF
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ in Apseed 23xx or 24xx BMC SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called aspeed_wdt.
+
# AVR32 Architecture
config AT32AP700X_WDT
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 53d4827ddfe1..f4c9e4f1d85c 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
obj-$(CONFIG_DIGICOLOR_WATCHDOG) += digicolor_wdt.o
obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o
obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o
+obj-$(CONFIG_ASPEED_24xx_WATCHDOG) += aspeed_wdt.o
# AVR32 Architecture
obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c
new file mode 100644
index 000000000000..a1ea4c286e6c
--- /dev/null
+++ b/drivers/watchdog/aspeed_wdt.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2015 IBM Corp.
+ *
+ * Joel Stanley <joel@jms.id.au>
+ *
+ * Based on the qcom-watchdog driver
+ *
+ * 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/of.h>
+#include <linux/reboot.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+struct aspeed_wdt {
+ struct watchdog_device wdd;
+ struct clk *clk;
+ unsigned long rate;
+ struct notifier_block restart_nb;
+ void __iomem *base;
+};
+
+static const struct of_device_id aspeed_wdt_of_table[] = {
+ { .compatible = "aspeed,wdt", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table);
+
+#define WDT_STATUS 0x00
+#define WDT_RELOAD_VALUE 0x04
+#define WDT_RESTART 0x08
+#define WDT_CTRL 0x0C
+#define WDT_CTRL_RESET_MODE_SOC (0x00 << 5)
+#define WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5)
+#define WDT_CTRL_RESET_SYSTEM (0x1 << 1)
+#define WDT_CTRL_ENABLE (0x1 << 0)
+
+#define WDT_RESTART_MAGIC 0x4755
+
+static void aspeed_wdt_enable(struct aspeed_wdt *wdt, int count)
+{
+ u32 ctrl = WDT_CTRL_RESET_MODE_SOC | WDT_CTRL_RESET_SYSTEM |
+ WDT_CTRL_ENABLE;
+
+ writel(0, wdt->base + WDT_CTRL);
+ writel(count, wdt->base + WDT_RELOAD_VALUE);
+ writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART);
+ writel(ctrl, wdt->base + WDT_CTRL);
+}
+
+static int aspeed_wdt_start(struct watchdog_device *wdd)
+{
+ struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd);
+ dev_dbg(wdd->dev, "starting with timeout of %d (rate %lu)\n",
+ wdd->timeout, wdt->rate);
+ aspeed_wdt_enable(wdt, wdd->timeout * wdt->rate);
+ return 0;
+}
+
+static int aspeed_wdt_stop(struct watchdog_device *wdd)
+{
+ struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd);
+
+ writel(0, wdt->base + WDT_CTRL);
+ return 0;
+}
+
+static int aspeed_wdt_ping(struct watchdog_device *wdd)
+{
+ struct aspeed_wdt *wdt = container_of(wdd, struct aspeed_wdt, wdd);
+
+ dev_dbg(wdd->dev, "ping\n");
+ writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART);
+ return 0;
+}
+
+static int aspeed_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ dev_dbg(wdd->dev, "timeout set to %u\n", timeout);
+ wdd->timeout = timeout;
+ return aspeed_wdt_start(wdd);
+}
+
+static int aspeed_wdt_restart(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct aspeed_wdt *wdt = container_of(nb,
+ struct aspeed_wdt, restart_nb);
+
+ /*
+ * Trigger watchdog bite:
+ * Setup reload count to be 128ms, and enable WDT.
+ */
+ aspeed_wdt_enable(wdt, 128 * wdt->rate / 1000);
+
+ return NOTIFY_DONE;
+}
+
+static const struct watchdog_ops aspeed_wdt_ops = {
+ .start = aspeed_wdt_start,
+ .stop = aspeed_wdt_stop,
+ .ping = aspeed_wdt_ping,
+ .set_timeout = aspeed_wdt_set_timeout,
+ .owner = THIS_MODULE,
+};
+
+static const struct watchdog_info aspeed_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE
+ | WDIOF_SETTIMEOUT,
+ .identity = KBUILD_MODNAME,
+};
+
+static int aspeed_wdt_remove(struct platform_device *pdev)
+{
+ struct aspeed_wdt *wdt = platform_get_drvdata(pdev);
+
+ unregister_restart_handler(&wdt->restart_nb);
+ watchdog_unregister_device(&wdt->wdd);
+ clk_disable_unprepare(wdt->clk);
+ return 0;
+}
+
+static int aspeed_wdt_probe(struct platform_device *pdev)
+{
+ struct aspeed_wdt *wdt;
+ struct resource *res;
+ int ret;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ wdt->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ wdt->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(wdt->clk)) {
+ dev_err(&pdev->dev, "failed to get input clock\n");
+ return PTR_ERR(wdt->clk);
+ }
+
+ ret = clk_prepare_enable(wdt->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to setup clock\n");
+ goto err;
+ }
+
+ /*
+ * We use the clock rate to calculate the max timeout, so ensure it's
+ * not zero to avoid a divide-by-zero exception.
+ *
+ * WATCHDOG_CORE assumes units of seconds, if the WDT is clocked such
+ * that it would bite before a second elapses it's usefulness is
+ * limited. Bail if this is the case.
+ */
+ wdt->rate = clk_get_rate(wdt->clk);
+ if (wdt->rate == 0 ||
+ wdt->rate > 0x10000000U) {
+ dev_err(&pdev->dev, "invalid clock rate\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ wdt->wdd.dev = &pdev->dev;
+ wdt->wdd.info = &aspeed_wdt_info;
+ wdt->wdd.ops = &aspeed_wdt_ops;
+ wdt->wdd.min_timeout = 1;
+ wdt->wdd.max_timeout = 0x10000000U / wdt->rate;
+
+ /*
+ * If 'timeout-sec' unspecified in devicetree, assume a 30 second
+ * default, unless the max timeout is less than 30 seconds, then use
+ * the max instead.
+ */
+ wdt->wdd.timeout = min(wdt->wdd.max_timeout, 30U);
+ watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev);
+
+ ret = watchdog_register_device(&wdt->wdd);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register\n");
+ goto err;
+ }
+
+ /*
+ * WDT restart notifier has priority 0 (use as a last resort)
+ */
+ wdt->restart_nb.notifier_call = aspeed_wdt_restart;
+ ret = register_restart_handler(&wdt->restart_nb);
+ if (ret)
+ dev_err(&pdev->dev, "failed to setup restart handler\n");
+
+ dev_info(&pdev->dev, "rate %lu, max timeout %u, timeout %d\n",
+ wdt->rate, wdt->wdd.max_timeout, wdt->wdd.timeout);
+
+ platform_set_drvdata(pdev, wdt);
+ return 0;
+
+err:
+ clk_disable_unprepare(wdt->clk);
+ return ret;
+}
+
+static struct platform_driver aspeed_watchdog_driver = {
+ .probe = aspeed_wdt_probe,
+ .remove = aspeed_wdt_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = of_match_ptr(aspeed_wdt_of_table),
+ },
+};
+module_platform_driver(aspeed_watchdog_driver);
+
+MODULE_DESCRIPTION("Aspeed AST23/4xx Watchdog Driver");
diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h
index 59f4a7304419..3d644da25e12 100644
--- a/include/linux/brcmphy.h
+++ b/include/linux/brcmphy.h
@@ -32,6 +32,8 @@
#define PHY_ID_BCM_CYGNUS 0xae025200
+#define PHY_ID_BCM54210E 0x600d84a0
+
#define PHY_BCM_OUI_MASK 0xfffffc00
#define PHY_BCM_OUI_1 0x00206000
#define PHY_BCM_OUI_2 0x0143bc00
diff --git a/include/net/ncsi.h b/include/net/ncsi.h
new file mode 100644
index 000000000000..fa50ab515c08
--- /dev/null
+++ b/include/net/ncsi.h
@@ -0,0 +1,59 @@
+#ifndef __NET_NCSI_H
+#define __NET_NCSI_H
+
+#include <uapi/linux/ncsi.h>
+
+/*
+ * The NCSI device states seen from external. More NCSI device states are
+ * only visible internally (in net/ncsi/internal.h). When the NCSI device
+ * is registered, it's in ncsi_dev_state_registered state. The state
+ * ncsi_dev_state_start is used to drive to choose active package and
+ * channel. After that, its state is changed to ncsi_dev_state_functional.
+ *
+ * The state ncsi_dev_state_stop helps to shut down the currently active
+ * package and channel while ncsi_dev_state_config helps to reconfigure
+ * them.
+ */
+enum {
+ ncsi_dev_state_registered = 0x0000,
+ ncsi_dev_state_functional = 0x0100,
+ ncsi_dev_state_probe = 0x0200,
+ ncsi_dev_state_config = 0x0300,
+ ncsi_dev_state_suspend = 0x0400,
+};
+
+struct ncsi_dev {
+ int nd_state;
+ int nd_link_up;
+ struct net_device *nd_dev;
+ void (*nd_handler)(struct ncsi_dev *ndev);
+};
+
+#ifdef CONFIG_NET_NCSI
+struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
+ void (*notifier)(struct ncsi_dev *nd));
+int ncsi_start_dev(struct ncsi_dev *nd);
+int ncsi_suspend_dev(struct ncsi_dev *nd);
+void ncsi_unregister_dev(struct ncsi_dev *nd);
+#else /* !CONFIG_NET_NCSI */
+static inline struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
+ void (*notifier)(struct ncsi_dev *nd))
+{
+ return NULL;
+}
+
+static inline int ncsi_start_dev(struct ncsi_dev *nd)
+{
+ return -ENOTTY;
+}
+
+static inline int ncsi_suspend_dev(struct ncsi_dev *nd)
+{
+ return -ENOTTY;
+}
+
+void inline ncsi_unregister_dev(struct ncsi_dev *nd)
+{
+}
+#endif /* CONFIG_NET_NCSI */
+#endif /* __NET_NCSI_H */
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index ebd10e624598..47184e8efeb0 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -74,6 +74,7 @@ header-y += bpf_common.h
header-y += bpf.h
header-y += bpqether.h
header-y += bsg.h
+header-y += bt-host.h
header-y += btrfs.h
header-y += can.h
header-y += capability.h
diff --git a/include/uapi/linux/bt-host.h b/include/uapi/linux/bt-host.h
new file mode 100644
index 000000000000..a4298d9e7e26
--- /dev/null
+++ b/include/uapi/linux/bt-host.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2015 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 _UAPI_LINUX_BT_HOST_H
+#define _UAPI_LINUX_BT_HOST_H
+
+#include <linux/ioctl.h>
+
+#define __BT_HOST_IOCTL_MAGIC 0xb1
+#define BT_HOST_IOCTL_SMS_ATN _IO(__BT_HOST_IOCTL_MAGIC, 0x00)
+
+#endif /* _UAPI_LINUX_BT_HOST_H */
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index ea9221b0331a..b20e9e678d2f 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -86,6 +86,7 @@
#define ETH_P_8021AH 0x88E7 /* 802.1ah Backbone Service Tag */
#define ETH_P_MVRP 0x88F5 /* 802.1Q MVRP */
#define ETH_P_1588 0x88F7 /* IEEE 1588 Timesync */
+#define ETH_P_NCSI 0x88F8 /* NCSI protocol */
#define ETH_P_PRP 0x88FB /* IEC 62439-3 PRP/HSRv0 */
#define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */
#define ETH_P_TDLS 0x890D /* TDLS */
diff --git a/include/uapi/linux/ncsi.h b/include/uapi/linux/ncsi.h
new file mode 100644
index 000000000000..9a3d18025a40
--- /dev/null
+++ b/include/uapi/linux/ncsi.h
@@ -0,0 +1,200 @@
+#ifndef _UAPI_LINUX_NCSI_H
+#define _UAPI_LINUX_NCSI_H
+
+/* NCSI netlink message type */
+enum {
+ NCSI_MSG_BASE = 16,
+ NCSI_MSG_GET_LAYOUT = 16,
+ NCSI_MSG_GET_VERSION,
+ NCSI_MSG_GET_CAP,
+ NCSI_MSG_GET_MODE,
+ NCSI_MSG_GET_FILTER,
+ NCSI_MSG_GET_STATS,
+ NCSI_MSG_SET_MODE,
+ NCSI_MSG_SET_FILTER,
+ NCSI_MSG_MAX
+};
+
+
+/* NCSI channel capabilities */
+enum {
+ NCSI_CAP_BASE = 0,
+ NCSI_CAP_GENERIC = 0,
+ NCSI_CAP_BC,
+ NCSI_CAP_MC,
+ NCSI_CAP_BUFFER,
+ NCSI_CAP_AEN,
+ NCSI_CAP_VLAN,
+ NCSI_CAP_MAX
+};
+
+enum {
+ NCSI_CAP_GENERIC_HWA = 0x01, /* HW arbitration */
+ NCSI_CAP_GENERIC_HDS = 0x02, /* HNC driver status change */
+ NCSI_CAP_GENERIC_FC = 0x04, /* HNC to MC flow control */
+ NCSI_CAP_GENERIC_FC1 = 0x08, /* MC to HNC flow control */
+ NCSI_CAP_GENERIC_MC = 0x10, /* Global multicast filtering */
+ NCSI_CAP_GENERIC_MASK = 0x1f,
+ NCSI_CAP_BC_ARP = 0x01, /* ARP packet filtering */
+ NCSI_CAP_BC_DHCPC = 0x02, /* DHCP client filtering */
+ NCSI_CAP_BC_DHCPS = 0x04, /* DHCP server filtering */
+ NCSI_CAP_BC_NETBIOS = 0x08, /* NetBIOS packet filtering */
+ NCSI_CAP_BC_MASK = 0x0f,
+ NCSI_CAP_MC_NEIGHBOR = 0x01, /* IPv6 neighbor filtering */
+ NCSI_CAP_MC_ROUTER = 0x02, /* IPv6 router filering */
+ NCSI_CAP_MC_DHCPv6 = 0x04, /* DHCPv6 filtering */
+ NCSI_CAP_MC_MASK = 0x07,
+ NCSI_CAP_AEN_LSC = 0x01, /* Link status change AEN */
+ NCSI_CAP_AEN_CR = 0x02, /* Configuration required AEN */
+ NCSI_CAP_AEN_HDS = 0x04, /* HNC driver status AEN */
+ NCSI_CAP_AEN_MASK = 0x07,
+ NCSI_CAP_VLAN_ONLY = 0x01, /* VLAN is supported */
+ NCSI_CAP_VLAN_NO = 0x02, /* Filter VLAN and non-VLAN */
+ NCSI_CAP_VLAN_ANY = 0x04, /* Filter Any-and-non-VLAN */
+ NCSI_CAP_VLAN_MASK = 0x07
+};
+
+/* NCSI channel mode */
+enum {
+ NCSI_MODE_BASE = 0,
+ NCSI_MODE_ENABLE = 0,
+ NCSI_MODE_TX_ENABLE,
+ NCSI_MODE_LINK,
+ NCSI_MODE_VLAN,
+ NCSI_MODE_BC,
+ NCSI_MODE_MC,
+ NCSI_MODE_AEN,
+ NCSI_MODE_FC,
+ NCSI_MODE_MAX
+};
+
+/* NCSI channel filters */
+enum {
+ NCSI_FILTER_BASE = 0,
+ NCSI_FILTER_VLAN = 0,
+ NCSI_FILTER_UC,
+ NCSI_FILTER_MC,
+ NCSI_FILTER_MIXED,
+ NCSI_FILTER_MAX
+};
+
+/*
+ * It's put right after netlink message header. Also, it's
+ * used to convey NCSI topology layout.
+ */
+struct ncsi_msg {
+ __u32 nm_flag;
+#define NCSI_FLAG_REQUEST 0x1
+#define NCSI_FLAG_RESPONSE 0x2
+#define NCSI_FLAG_ACTIVE_CHANNEL 0x4
+
+ __u32 nm_ifindex; /* ID of network device */
+ __u32 nm_package_id; /* ID of NCSI package */
+ __u32 nm_channel_id; /* ID of NCSI channel */
+ __u32 nm_index; /* ID of mode, capability or filter */
+ __u32 nm_errcode; /* Error code */
+};
+
+enum {
+ NCSI_SUCCESS,
+ NCSI_ERR_PARAM,
+ NCSI_ERR_NO_MEM,
+ NCSI_ERR_NO_DEV,
+ NCSI_ERR_NOT_ACTIVE,
+ NCSI_ERR_INTERNAL,
+};
+
+/* NCSI channel version */
+struct ncsi_channel_version {
+ __u32 ncv_version; /* Supported BCD encoded NCSI version */
+ __u32 ncv_alpha2; /* Supported BCD encoded NCSI version */
+ __u8 ncv_fw_name[12]; /* Firware name string */
+ __u32 ncv_fw_version; /* Firmware version */
+ __u16 ncv_pci_ids[4]; /* PCI identification */
+ __u32 ncv_mf_id; /* Manufacture ID */
+};
+
+/* NCSI channel capability */
+struct ncsi_channel_cap {
+ __u32 ncc_index; /* Index of channel capabilities */
+ __u32 ncc_cap; /* NCSI channel capability */
+};
+
+/* NCSI channel mode */
+struct ncsi_channel_mode {
+ __u32 ncm_index; /* Index of channel modes */
+ __u32 ncm_enable; /* Enabled or disabled */
+ __u32 ncm_size; /* Valid entries in ncm_data[] */
+ __u32 ncm_data[8]; /* Data entries */
+};
+
+/* NCSI channel filter */
+struct ncsi_channel_filter {
+ __u32 ncf_index; /* Index of channel filters */
+ __u32 ncf_total; /* Total entries in the filter table */
+ __u64 ncf_bitmap; /* Bitmap of valid entries */
+ __u8 ncf_data[]; /* Data for the valid entries */
+};
+
+/* NCSI channel statistics */
+struct ncsi_channel_stats {
+ __u32 ncs_hnc_cnt_hi; /* Counter cleared */
+ __u32 ncs_hnc_cnt_lo; /* Counter cleared */
+ __u32 ncs_hnc_rx_bytes; /* Rx bytes */
+ __u32 ncs_hnc_tx_bytes; /* Tx bytes */
+ __u32 ncs_hnc_rx_uc_pkts; /* Rx UC packets */
+ __u32 ncs_hnc_rx_mc_pkts; /* Rx MC packets */
+ __u32 ncs_hnc_rx_bc_pkts; /* Rx BC packets */
+ __u32 ncs_hnc_tx_uc_pkts; /* Tx UC packets */
+ __u32 ncs_hnc_tx_mc_pkts; /* Tx MC packets */
+ __u32 ncs_hnc_tx_bc_pkts; /* Tx BC packets */
+ __u32 ncs_hnc_fcs_err; /* FCS errors */
+ __u32 ncs_hnc_align_err; /* Alignment errors */
+ __u32 ncs_hnc_false_carrier; /* False carrier detection */
+ __u32 ncs_hnc_runt_pkts; /* Rx runt packets */
+ __u32 ncs_hnc_jabber_pkts; /* Rx jabber packets */
+ __u32 ncs_hnc_rx_pause_xon; /* Rx pause XON frames */
+ __u32 ncs_hnc_rx_pause_xoff; /* Rx XOFF frames */
+ __u32 ncs_hnc_tx_pause_xon; /* Tx XON frames */
+ __u32 ncs_hnc_tx_pause_xoff; /* Tx XOFF frames */
+ __u32 ncs_hnc_tx_s_collision; /* Single collision frames */
+ __u32 ncs_hnc_tx_m_collision; /* Multiple collision frames */
+ __u32 ncs_hnc_l_collision; /* Late collision frames */
+ __u32 ncs_hnc_e_collision; /* Excessive collision frames */
+ __u32 ncs_hnc_rx_ctl_frames; /* Rx control frames */
+ __u32 ncs_hnc_rx_64_frames; /* Rx 64-bytes frames */
+ __u32 ncs_hnc_rx_127_frames; /* Rx 65-127 bytes frames */
+ __u32 ncs_hnc_rx_255_frames; /* Rx 128-255 bytes frames */
+ __u32 ncs_hnc_rx_511_frames; /* Rx 256-511 bytes frames */
+ __u32 ncs_hnc_rx_1023_frames; /* Rx 512-1023 bytes frames */
+ __u32 ncs_hnc_rx_1522_frames; /* Rx 1024-1522 bytes frames */
+ __u32 ncs_hnc_rx_9022_frames; /* Rx 1523-9022 bytes frames */
+ __u32 ncs_hnc_tx_64_frames; /* Tx 64-bytes frames */
+ __u32 ncs_hnc_tx_127_frames; /* Tx 65-127 bytes frames */
+ __u32 ncs_hnc_tx_255_frames; /* Tx 128-255 bytes frames */
+ __u32 ncs_hnc_tx_511_frames; /* Tx 256-511 bytes frames */
+ __u32 ncs_hnc_tx_1023_frames; /* Tx 512-1023 bytes frames */
+ __u32 ncs_hnc_tx_1522_frames; /* Tx 1024-1522 bytes frames */
+ __u32 ncs_hnc_tx_9022_frames; /* Tx 1523-9022 bytes frames */
+ __u32 ncs_hnc_rx_valid_bytes; /* Rx valid bytes */
+ __u32 ncs_hnc_rx_runt_pkts; /* Rx error runt packets */
+ __u32 ncs_hnc_rx_jabber_pkts; /* Rx error jabber packets */
+ __u32 ncs_ncsi_rx_cmds; /* Rx NCSI commands */
+ __u32 ncs_ncsi_dropped_cmds; /* Dropped commands */
+ __u32 ncs_ncsi_cmd_type_errs; /* Command type errors */
+ __u32 ncs_ncsi_cmd_csum_errs; /* Command checksum errors */
+ __u32 ncs_ncsi_rx_pkts; /* Rx NCSI packets */
+ __u32 ncs_ncsi_tx_pkts; /* Tx NCSI packets */
+ __u32 ncs_ncsi_tx_aen_pkts; /* Tx AEN packets */
+ __u32 ncs_pt_tx_pkts; /* Tx packets */
+ __u32 ncs_pt_tx_dropped; /* Tx dropped packets */
+ __u32 ncs_pt_tx_channel_err; /* Tx channel errors */
+ __u32 ncs_pt_tx_us_err; /* Tx undersize errors */
+ __u32 ncs_pt_rx_pkts; /* Rx packets */
+ __u32 ncs_pt_rx_dropped; /* Rx dropped packets */
+ __u32 ncs_pt_rx_channel_err; /* Rx channel errors */
+ __u32 ncs_pt_rx_us_err; /* Rx undersize errors */
+ __u32 ncs_pt_rx_os_err; /* Rx oversize errors */
+};
+
+#endif /* _UAPI_LINUX_NCSI_H */
diff --git a/net/Kconfig b/net/Kconfig
index 127da94ae25e..f320b1fac5a1 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -233,6 +233,7 @@ source "net/mpls/Kconfig"
source "net/hsr/Kconfig"
source "net/switchdev/Kconfig"
source "net/l3mdev/Kconfig"
+source "net/ncsi/Kconfig"
config RPS
bool
diff --git a/net/Makefile b/net/Makefile
index a5d04098dfce..4d81a6ea74a2 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -77,3 +77,4 @@ endif
ifneq ($(CONFIG_NET_L3_MASTER_DEV),)
obj-y += l3mdev/
endif
+obj-$(CONFIG_NET_NCSI) += ncsi/
diff --git a/net/ncsi/Kconfig b/net/ncsi/Kconfig
new file mode 100644
index 000000000000..723f0ebd58e6
--- /dev/null
+++ b/net/ncsi/Kconfig
@@ -0,0 +1,10 @@
+#
+# Configuration for NCSI support
+#
+
+config NET_NCSI
+ bool "NCSI interface support (EXPERIMENTAL)"
+ depends on INET
+ ---help---
+ This module provides NCSI (Network Controller Sideband Interface)
+ support.
diff --git a/net/ncsi/Makefile b/net/ncsi/Makefile
new file mode 100644
index 000000000000..e4094c27538e
--- /dev/null
+++ b/net/ncsi/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for NCSI API
+#
+obj-$(CONFIG_NET_NCSI) += ncsi-cmd.o ncsi-rsp.o ncsi-aen.o \
+ ncsi-manage.o
diff --git a/net/ncsi/internal.h b/net/ncsi/internal.h
new file mode 100644
index 000000000000..9476652f98e2
--- /dev/null
+++ b/net/ncsi/internal.h
@@ -0,0 +1,159 @@
+#ifndef __NCSI_INTERNAL_H__
+#define __NCSI_INTERNAL_H__
+
+struct ncsi_dev_priv;
+struct ncsi_package;
+
+#define NCSI_PACKAGE_INDEX(c) (((c) >> 5) & 0x7)
+#define NCSI_RESERVED_CHANNEL 0x1f
+#define NCSI_CHANNEL_INDEX(c) ((c) & 0x1ffff)
+#define NCSI_TO_CHANNEL(p, c) ((((p) & 0x7) << 5) | ((c) & 0x1ffff))
+
+/* Channel state */
+enum {
+ ncsi_channel_state_deselected_initial,
+ ncsi_channel_state_selected_initial,
+ ncsi_channel_state_deselected_ready,
+ ncsi_channel_state_selected_ready,
+};
+
+struct ncsi_channel {
+ unsigned char nc_id;
+ int nc_state;
+ struct ncsi_package *nc_package;
+ struct ncsi_channel_version nc_version;
+ struct ncsi_channel_cap nc_caps[NCSI_CAP_MAX];
+ struct ncsi_channel_mode nc_modes[NCSI_MODE_MAX];
+ struct ncsi_channel_filter *nc_filters[NCSI_FILTER_MAX];
+ struct ncsi_channel_stats nc_stats;
+ struct list_head nc_node;
+};
+
+struct ncsi_package {
+ unsigned char np_id;
+ struct ncsi_dev_priv *np_ndp;
+ atomic_t np_channel_num;
+ spinlock_t np_channel_lock;
+ struct list_head np_channels;
+ struct list_head np_node;
+};
+
+struct ncsi_req {
+ unsigned char nr_id;
+ bool nr_used;
+ unsigned int nr_flags;
+#define NCSI_REQ_FLAG_EVENT_DRIVEN 1
+ struct ncsi_dev_priv *nr_ndp;
+ struct sk_buff *nr_cmd;
+ struct sk_buff *nr_rsp;
+ struct timer_list nr_timer;
+ bool nr_timer_enabled;
+};
+
+enum {
+ ncsi_dev_state_major = 0xff00,
+ ncsi_dev_state_minor = 0x00ff,
+ ncsi_dev_state_probe_deselect = 0x0201,
+ ncsi_dev_state_probe_package,
+ ncsi_dev_state_probe_channel,
+ ncsi_dev_state_probe_cis,
+ ncsi_dev_state_probe_gvi,
+ ncsi_dev_state_probe_gc,
+ ncsi_dev_state_probe_gls,
+ ncsi_dev_state_probe_dp,
+ ncsi_dev_state_config_sp = 0x0301,
+ ncsi_dev_state_config_cis,
+ ncsi_dev_state_config_sma,
+ ncsi_dev_state_config_ebf,
+ ncsi_dev_state_config_ecnt,
+ ncsi_dev_state_config_ec,
+ ncsi_dev_state_config_ae,
+ ncsi_dev_state_config_gls,
+ ncsi_dev_state_config_done,
+ ncsi_dev_state_suspend_select = 0x0401,
+ ncsi_dev_state_suspend_gls,
+ ncsi_dev_state_suspend_dcnt,
+ ncsi_dev_state_suspend_dc,
+ ncsi_dev_state_suspend_deselect,
+ ncsi_dev_state_suspend_done
+};
+
+struct ncsi_dev_priv {
+ struct ncsi_dev ndp_ndev;
+ int ndp_flags;
+#define NCSI_DEV_PRIV_FLAG_POPULATED 0x1
+#define NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE 0x2
+ struct ncsi_package *ndp_active_package;
+ struct ncsi_channel *ndp_active_channel;
+ atomic_t ndp_package_num;
+ spinlock_t ndp_package_lock;
+ struct list_head ndp_packages;
+ struct ncsi_channel *ndp_hot_channel;
+ atomic_t ndp_pending_reqs;
+ atomic_t ndp_last_req_idx;
+#define NCSI_REQ_START_IDX 1
+ spinlock_t ndp_req_lock;
+ struct ncsi_req ndp_reqs[256];
+ struct work_struct ndp_work;
+ struct packet_type ndp_ptype;
+ struct list_head ndp_node;
+};
+
+struct ncsi_cmd_arg {
+ struct ncsi_dev_priv *nca_ndp;
+ unsigned char nca_type;
+ unsigned char nca_id;
+ unsigned char nca_package;
+ unsigned char nca_channel;
+ unsigned short nca_payload;
+ unsigned int nca_portid;
+ unsigned int nca_req_flags;
+ union {
+ unsigned char nca_bytes[16];
+ unsigned short nca_words[8];
+ unsigned int nca_dwords[4];
+ };
+};
+
+extern struct net *ncsi_net;
+extern struct list_head ncsi_dev_list;
+extern spinlock_t ncsi_dev_lock;
+
+#define TO_NCSI_DEV_PRIV(nd) \
+ container_of(nd, struct ncsi_dev_priv, ndp_ndev)
+#define NCSI_FOR_EACH_DEV(ndp) \
+ list_for_each_entry_rcu(ndp, &ncsi_dev_list, ndp_node)
+#define NCSI_FOR_EACH_PACKAGE(ndp, np) \
+ list_for_each_entry_rcu(np, &ndp->ndp_packages, np_node)
+#define NCSI_FOR_EACH_CHANNEL(np, nc) \
+ list_for_each_entry_rcu(nc, &np->np_channels, nc_node)
+
+/* Resources */
+int ncsi_find_channel_filter(struct ncsi_channel *nc, int table, void *data);
+int ncsi_add_channel_filter(struct ncsi_channel *nc, int table, void *data);
+int ncsi_del_channel_filter(struct ncsi_channel *nc, int table, int index);
+struct ncsi_channel *ncsi_add_channel(struct ncsi_package *np,
+ unsigned char id);
+struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np,
+ unsigned char id);
+struct ncsi_package *ncsi_add_package(struct ncsi_dev_priv *ndp,
+ unsigned char id);
+struct ncsi_package *ncsi_find_package(struct ncsi_dev_priv *ndp,
+ unsigned char id);
+void ncsi_release_package(struct ncsi_package *np);
+void ncsi_find_package_and_channel(struct ncsi_dev_priv *ndp,
+ unsigned char id,
+ struct ncsi_package **np,
+ struct ncsi_channel **nc);
+struct ncsi_req *ncsi_alloc_req(struct ncsi_dev_priv *ndp,
+ unsigned int req_flags);
+void ncsi_free_req(struct ncsi_req *nr);
+struct ncsi_dev *ncsi_find_dev(struct net_device *dev);
+int ncsi_config_dev(struct ncsi_dev *nd);
+
+/* Packet handlers */
+int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca);
+int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *orig_dev);
+int ncsi_aen_handler(struct ncsi_dev_priv *ndp, struct sk_buff *skb);
+#endif /* __NCSI_INTERNAL_H__ */
diff --git a/net/ncsi/ncsi-aen.c b/net/ncsi/ncsi-aen.c
new file mode 100644
index 000000000000..26ac93dde60d
--- /dev/null
+++ b/net/ncsi/ncsi-aen.c
@@ -0,0 +1,197 @@
+/*
+ * Copyright Gavin Shan, IBM Corporation 2015.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+
+#include <net/ncsi.h>
+#include <net/net_namespace.h>
+#include <net/sock.h>
+
+#include "internal.h"
+#include "ncsi-pkt.h"
+
+static int ncsi_validate_aen_pkt(struct ncsi_aen_pkt_hdr *h,
+ const unsigned short payload)
+{
+ unsigned char *stream;
+ __be32 *checksum, csum;
+ __be32 high, low;
+ int i;
+
+ if (h->common.revision != NCSI_PKT_REVISION)
+ return -EINVAL;
+ if (ntohs(h->common.length) != payload)
+ return -EINVAL;
+
+ /*
+ * Validate checksum, which might be zeroes if the
+ * sender doesn't support checksum according to NCSI
+ * specification.
+ */
+ checksum = (__be32 *)((void *)(h + 1) + payload - 4);
+ if (ntohl(*checksum) == 0)
+ return 0;
+
+ csum = 0;
+ stream = (unsigned char *)h;
+ for (i = 0; i < sizeof(*h) + payload - 4; i += 2) {
+ high = stream[i];
+ low = stream[i + 1];
+ csum += ((high << 8) | low);
+ }
+
+ csum = ~csum + 1;
+ if (*checksum != htonl(csum))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp,
+ struct ncsi_aen_pkt_hdr *h)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct ncsi_aen_lsc_pkt *lsc;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_aen_pkt(h, 12);
+ if (ret)
+ return ret;
+
+ /* Find the NCSI channel */
+ ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Update the link status */
+ ncm = &nc->nc_modes[NCSI_MODE_LINK];
+ lsc = (struct ncsi_aen_lsc_pkt *)h;
+ ncm->ncm_data[2] = ntohl(lsc->status);
+ ncm->ncm_data[4] = ntohl(lsc->oem_status);
+ if (!ndp->ndp_active_channel ||
+ ndp->ndp_active_channel != nc)
+ return 0;
+
+ /* If this channel is the active one and the link is down,
+ * we have to choose another channel to be active one.
+ */
+ if (!(ncm->ncm_data[2] & 0x1))
+ ndp->ndp_flags |= NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
+ ncsi_suspend_dev(nd);
+
+ return 0;
+}
+
+static int ncsi_aen_handler_cr(struct ncsi_dev_priv *ndp,
+ struct ncsi_aen_pkt_hdr *h)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct ncsi_channel *nc;
+ int ret;
+
+ ret = ncsi_validate_aen_pkt(h, 4);
+ if (ret)
+ return ret;
+
+ /* Find the NCSI channel */
+ ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* If the channel is active one, we need reconfigure it */
+ if (!ndp->ndp_active_channel ||
+ ndp->ndp_active_channel != nc)
+ return 0;
+
+ ncsi_config_dev(nd);
+
+ return 0;
+}
+
+static int ncsi_aen_handler_hncdsc(struct ncsi_dev_priv *ndp,
+ struct ncsi_aen_pkt_hdr *h)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ struct ncsi_aen_hncdsc_pkt *hncdsc;
+ int ret;
+
+ ret = ncsi_validate_aen_pkt(h, 4);
+ if (ret)
+ return ret;
+
+ /* Find the NCSI channel */
+ ncsi_find_package_and_channel(ndp, h->common.channel, NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* If the channel is active one, we need reconfigure it */
+ ncm = &nc->nc_modes[NCSI_MODE_LINK];
+ hncdsc = (struct ncsi_aen_hncdsc_pkt *)h;
+ ncm->ncm_data[3] = ntohl(hncdsc->status);
+ if (ndp->ndp_active_channel != nc)
+ return 0;
+
+ /* If this channel is the active one and the link doesn't
+ * work, we have to choose another channel to be active one.
+ * The logic here is exactly similar to what we do when link
+ * is down on the active channel.
+ */
+ if (!(ncm->ncm_data[3] & 0x1))
+ ndp->ndp_flags |= NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
+ ncsi_suspend_dev(nd);
+
+ return 0;
+}
+
+static struct ncsi_aen_handler {
+ unsigned char nah_type;
+ int (*nah_handler)(struct ncsi_dev_priv *ndp,
+ struct ncsi_aen_pkt_hdr *h);
+} ncsi_aen_handlers[] = {
+ { NCSI_PKT_AEN_LSC, ncsi_aen_handler_lsc },
+ { NCSI_PKT_AEN_CR, ncsi_aen_handler_cr },
+ { NCSI_PKT_AEN_HNCDSC, ncsi_aen_handler_hncdsc },
+ { 0, NULL }
+};
+
+int ncsi_aen_handler(struct ncsi_dev_priv *ndp, struct sk_buff *skb)
+{
+ struct ncsi_aen_pkt_hdr *h;
+ struct ncsi_aen_handler *nah;
+ int ret;
+
+ /* Find the handler */
+ h = (struct ncsi_aen_pkt_hdr *)skb_network_header(skb);
+ nah = ncsi_aen_handlers;
+ while (nah->nah_handler) {
+ if (nah->nah_type == h->type)
+ break;
+
+ nah++;
+ }
+
+ if (!nah->nah_handler) {
+ pr_warn("NCSI: Received unrecognized AEN packet (0x%x)\n",
+ h->type);
+ return -ENOENT;
+ }
+
+ ret = nah->nah_handler(ndp, h);
+ consume_skb(skb);
+
+ return ret;
+}
diff --git a/net/ncsi/ncsi-cmd.c b/net/ncsi/ncsi-cmd.c
new file mode 100644
index 000000000000..9b0ddb73840d
--- /dev/null
+++ b/net/ncsi/ncsi-cmd.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright Gavin Shan, IBM Corporation 2015.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+
+#include <net/ncsi.h>
+#include <net/net_namespace.h>
+#include <net/sock.h>
+
+#include "internal.h"
+#include "ncsi-pkt.h"
+
+/*
+ * This function should be called after the data area has been
+ * populated completely.
+ */
+static int ncsi_cmd_build_header(struct ncsi_pkt_hdr *h,
+ struct ncsi_cmd_arg *nca)
+{
+ __be32 csum, *checksum;
+ __be32 low, high;
+ unsigned char *stream;
+ int i;
+
+ h->mc_id = 0;
+ h->revision = NCSI_PKT_REVISION;
+ h->reserved = 0;
+ h->id = nca->nca_id;
+ h->type = nca->nca_type;
+ h->channel = NCSI_TO_CHANNEL(nca->nca_package,
+ nca->nca_channel);
+ h->length = htons(nca->nca_payload);
+ h->reserved1[0] = 0;
+ h->reserved1[1] = 0;
+
+ /* Calculate the checksum */
+ csum = 0;
+ stream = (unsigned char *)h;
+ for (i = 0; i < sizeof(*h) + nca->nca_payload; i += 2) {
+ high = stream[i];
+ low = stream[i + 1];
+ csum += ((high << 8) | low);
+ }
+
+ /* Fill with the calculated checksum */
+ checksum = (__be32 *)((void *)(h + 1) + nca->nca_payload);
+ csum = (~csum + 1);
+ *checksum = htonl(csum);
+
+ return 0;
+}
+
+static int ncsi_cmd_handler_default(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_pkt *cmd;
+
+ if (!nca)
+ return 0;
+
+ cmd = (struct ncsi_cmd_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_sp(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_sp_pkt *cmd;
+
+ if (!nca)
+ return 4;
+
+ cmd = (struct ncsi_cmd_sp_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->hw_arbitration = nca->nca_bytes[0];
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_dc(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_dc_pkt *cmd;
+
+ if (!nca)
+ return 4;
+
+ cmd = (struct ncsi_cmd_dc_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->ald = nca->nca_bytes[0];
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_rc(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_rc_pkt *cmd;
+
+ if (!nca)
+ return 4;
+
+ cmd = (struct ncsi_cmd_rc_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_ae(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_ae_pkt *cmd;
+
+ if (!nca)
+ return 8;
+
+ cmd = (struct ncsi_cmd_ae_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->mc_id = nca->nca_bytes[0];
+ cmd->mode = htonl(nca->nca_dwords[1]);
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_sl(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_sl_pkt *cmd;
+
+ if (!nca)
+ return 8;
+
+ cmd = (struct ncsi_cmd_sl_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->mode = htonl(nca->nca_dwords[0]);
+ cmd->oem_mode = htonl(nca->nca_dwords[1]);
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_svf(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_svf_pkt *cmd;
+
+ if (!nca)
+ return 4;
+
+ cmd = (struct ncsi_cmd_svf_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->vlan = htons(nca->nca_words[0]);
+ cmd->index = nca->nca_bytes[2];
+ cmd->enable = nca->nca_bytes[3];
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_ev(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_ev_pkt *cmd;
+
+ if (!nca)
+ return 4;
+
+ cmd = (struct ncsi_cmd_ev_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->mode = nca->nca_bytes[0];
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_sma(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_sma_pkt *cmd;
+ int i;
+
+ if (!nca)
+ return 8;
+
+ cmd = (struct ncsi_cmd_sma_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ for (i = 0; i < 6; i++)
+ cmd->mac[i] = nca->nca_bytes[i];
+ cmd->index = nca->nca_bytes[6];
+ cmd->at_e = nca->nca_bytes[7];
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_ebf(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_ebf_pkt *cmd;
+
+ if (!nca)
+ return 4;
+
+ cmd = (struct ncsi_cmd_ebf_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->mode = htonl(nca->nca_dwords[0]);
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_egmf(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_egmf_pkt *cmd;
+
+ if (!nca)
+ return 4;
+
+ cmd = (struct ncsi_cmd_egmf_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->mode = htonl(nca->nca_dwords[0]);
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static int ncsi_cmd_handler_snfc(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_cmd_snfc_pkt *cmd;
+
+ if (!nca)
+ return 4;
+
+ cmd = (struct ncsi_cmd_snfc_pkt *)skb_put(skb, sizeof(*cmd));
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->mode = nca->nca_bytes[0];
+ return ncsi_cmd_build_header(&cmd->cmd.common, nca);
+}
+
+static struct ncsi_cmd_handler {
+ unsigned char nch_type;
+ int (*nch_handler)(struct sk_buff *skb,
+ struct ncsi_cmd_arg *nca);
+} ncsi_cmd_handlers[] = {
+ { NCSI_PKT_CMD_CIS, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_SP, ncsi_cmd_handler_sp },
+ { NCSI_PKT_CMD_DP, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_EC, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_DC, ncsi_cmd_handler_dc },
+ { NCSI_PKT_CMD_RC, ncsi_cmd_handler_rc },
+ { NCSI_PKT_CMD_ECNT, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_DCNT, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_AE, ncsi_cmd_handler_ae },
+ { NCSI_PKT_CMD_SL, ncsi_cmd_handler_sl },
+ { NCSI_PKT_CMD_GLS, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_SVF, ncsi_cmd_handler_svf },
+ { NCSI_PKT_CMD_EV, ncsi_cmd_handler_ev },
+ { NCSI_PKT_CMD_DV, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_SMA, ncsi_cmd_handler_sma },
+ { NCSI_PKT_CMD_EBF, ncsi_cmd_handler_ebf },
+ { NCSI_PKT_CMD_DBF, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_EGMF, ncsi_cmd_handler_egmf },
+ { NCSI_PKT_CMD_DGMF, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_SNFC, ncsi_cmd_handler_snfc },
+ { NCSI_PKT_CMD_GVI, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_GC, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_GP, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_GCPS, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_GNS, ncsi_cmd_handler_default },
+ { NCSI_PKT_CMD_GNPTS, ncsi_cmd_handler_default },
+ { 0, NULL }
+};
+
+static struct ncsi_req *ncsi_alloc_cmd_req(struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_dev_priv *ndp = nca->nca_ndp;
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct net_device *dev = nd->nd_dev;
+ int hlen = LL_RESERVED_SPACE(dev);
+ int tlen = dev->needed_tailroom;
+ int len = hlen + tlen;
+ struct sk_buff *skb;
+ struct ncsi_req *nr;
+
+ nr = ncsi_alloc_req(ndp, nca->nca_req_flags);
+ if (!nr)
+ return NULL;
+
+ /* NCSI command packet has 16-bytes header, payload,
+ * 4-bytes checksum and optional padding.
+ */
+ len += sizeof(struct ncsi_cmd_pkt_hdr);
+ len += 4;
+ if (nca->nca_payload < 26)
+ len += 26;
+ else
+ len += nca->nca_payload;
+
+ /* Allocate skb */
+ skb = alloc_skb(len, GFP_ATOMIC);
+ if (!skb) {
+ ncsi_free_req(nr);
+ return NULL;
+ }
+
+ nr->nr_cmd = skb;
+ skb_reserve(skb, hlen);
+ skb_reset_network_header(skb);
+
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_NCSI);
+
+ return nr;
+}
+
+int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca)
+{
+ struct ncsi_req *nr;
+ struct ethhdr *eh;
+ struct ncsi_cmd_handler *nch;
+ int i, ret;
+
+ /* Search for the handler */
+ nch = ncsi_cmd_handlers;
+ while (nch->nch_handler) {
+ if (nch->nch_type == nca->nca_type)
+ break;
+ nch++;
+ }
+
+ if (!nch->nch_handler) {
+ pr_info("%s: Cannot send packet with type 0x%x\n",
+ __func__, nca->nca_type);
+ return -ENOENT;
+ }
+
+ /* Get packet payload length and allocate the request */
+ nca->nca_payload = nch->nch_handler(NULL, NULL);
+ nr = ncsi_alloc_cmd_req(nca);
+ if (!nr)
+ return -ENOMEM;
+
+ /* Prepare the packet */
+ nca->nca_id = nr->nr_id;
+ ret = nch->nch_handler(nr->nr_cmd, nca);
+ if (ret)
+ goto out;
+
+ /* Fill the ethernet header */
+ eh = (struct ethhdr *)skb_push(nr->nr_cmd, sizeof(*eh));
+ eh->h_proto = htons(ETH_P_NCSI);
+ for (i = 0; i < ETH_ALEN; i++) {
+ eh->h_dest[i] = 0xff;
+ eh->h_source[i] = 0xff;
+ }
+
+ /* Start the timer for the request that might not have
+ * corresponding response. Given NCSI is an internal
+ * connection a 1 second delay should be sufficient.
+ */
+ mod_timer(&nr->nr_timer, jiffies + 1 * HZ);
+ nr->nr_timer_enabled = true;
+
+ /* Send NCSI packet */
+ skb_get(nr->nr_cmd);
+ ret = dev_queue_xmit(nr->nr_cmd);
+ if (ret)
+ goto out;
+
+ return 0;
+out:
+ ncsi_free_req(nr);
+ return ret;
+}
diff --git a/net/ncsi/ncsi-manage.c b/net/ncsi/ncsi-manage.c
new file mode 100644
index 000000000000..be767a81ccc5
--- /dev/null
+++ b/net/ncsi/ncsi-manage.c
@@ -0,0 +1,989 @@
+/*
+ * Copyright Gavin Shan, IBM Corporation 2015.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+
+#include <net/ncsi.h>
+#include <net/net_namespace.h>
+#include <net/sock.h>
+
+#include "ncsi-pkt.h"
+#include "internal.h"
+
+LIST_HEAD(ncsi_dev_list);
+DEFINE_SPINLOCK(ncsi_dev_lock);
+
+int ncsi_find_channel_filter(struct ncsi_channel *nc, int table, void *data)
+{
+ struct ncsi_channel_filter *ncf;
+ int idx, entry_size;
+ void *bitmap;
+
+ switch (table) {
+ case NCSI_FILTER_VLAN:
+ entry_size = 2;
+ break;
+ case NCSI_FILTER_UC:
+ case NCSI_FILTER_MC:
+ case NCSI_FILTER_MIXED:
+ entry_size = 6;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if the filter table has been initialized */
+ ncf = nc->nc_filters[table];
+ if (!ncf)
+ return -ENODEV;
+
+ /* Check the valid entries one by one */
+ bitmap = (void *)&ncf->ncf_bitmap;
+ idx = -1;
+ while ((idx = find_next_bit(bitmap, ncf->ncf_total, idx+1))
+ < ncf->ncf_total) {
+ if (!memcmp(ncf->ncf_data + entry_size * idx, data, entry_size))
+ return idx;
+ }
+
+ return -ENOENT;
+}
+
+int ncsi_add_channel_filter(struct ncsi_channel *nc, int table, void *data)
+{
+ struct ncsi_channel_filter *ncf;
+ int idx, entry_size;
+ void *bitmap;
+
+ /* Needn't add it if it's already existing */
+ idx = ncsi_find_channel_filter(nc, table, data);
+ if (idx >= 0)
+ return idx;
+
+ switch (table) {
+ case NCSI_FILTER_VLAN:
+ entry_size = 2;
+ break;
+ case NCSI_FILTER_UC:
+ case NCSI_FILTER_MC:
+ case NCSI_FILTER_MIXED:
+ entry_size = 6;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if the filter table has been initialized */
+ ncf = nc->nc_filters[table];
+ if (!ncf)
+ return -ENODEV;
+
+ /* Propagate the filter */
+ bitmap = (void *)&ncf->ncf_bitmap;
+ do {
+ idx = find_next_zero_bit(bitmap, ncf->ncf_total, 0);
+ if (idx >= ncf->ncf_total)
+ return -ENOSPC;
+ } while (test_and_set_bit(idx, bitmap));
+
+ memcpy(ncf->ncf_data + entry_size * idx, data, entry_size);
+ return idx;
+}
+
+int ncsi_del_channel_filter(struct ncsi_channel *nc, int table, int index)
+{
+ struct ncsi_channel_filter *ncf;
+ int entry_size;
+ void *bitmap;
+
+ switch (table) {
+ case NCSI_FILTER_VLAN:
+ entry_size = 2;
+ break;
+ case NCSI_FILTER_UC:
+ case NCSI_FILTER_MC:
+ case NCSI_FILTER_MIXED:
+ entry_size = 6;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Check if the filter table has been initialized */
+ ncf = nc->nc_filters[table];
+ if (!ncf || index >= ncf->ncf_total)
+ return -ENODEV;
+
+ /* Check if the entry is valid */
+ bitmap = (void *)&ncf->ncf_bitmap;
+ if (test_and_clear_bit(index, bitmap))
+ memset(ncf->ncf_data + entry_size * index, 0, entry_size);
+
+ return 0;
+}
+
+struct ncsi_channel *ncsi_add_channel(struct ncsi_package *np, unsigned char id)
+{
+ struct ncsi_channel *nc, *tmp;
+ int index;
+
+ nc = kzalloc(sizeof(*nc), GFP_ATOMIC);
+ if (!nc) {
+ pr_warn("%s: Out of memory !\n", __func__);
+ return NULL;
+ }
+
+ nc->nc_package = np;
+ nc->nc_id = id;
+ for (index = 0; index < NCSI_CAP_MAX; index++)
+ nc->nc_caps[index].ncc_index = index;
+ for (index = 0; index < NCSI_MODE_MAX; index++)
+ nc->nc_modes[index].ncm_index = index;
+
+ spin_lock(&np->np_channel_lock);
+ tmp = ncsi_find_channel(np, id);
+ if (tmp) {
+ spin_unlock(&np->np_channel_lock);
+ kfree(nc);
+ return tmp;
+ }
+ list_add_tail_rcu(&nc->nc_node, &np->np_channels);
+ spin_unlock(&np->np_channel_lock);
+
+ atomic_inc(&np->np_channel_num);
+ return nc;
+}
+
+struct ncsi_channel *ncsi_find_channel(struct ncsi_package *np,
+ unsigned char id)
+{
+ struct ncsi_channel *nc;
+
+ NCSI_FOR_EACH_CHANNEL(np, nc) {
+ if (nc->nc_id == id)
+ return nc;
+ }
+
+ return NULL;
+}
+
+static void ncsi_release_channel(struct ncsi_channel *nc)
+{
+ struct ncsi_dev_priv *ndp = nc->nc_package->np_ndp;
+ struct ncsi_package *np = nc->nc_package;
+ struct ncsi_channel_filter *ncf;
+ int i;
+
+ /* Release filters */
+ for (i = 0; i < NCSI_FILTER_MAX; i++) {
+ ncf = nc->nc_filters[i];
+ if (!ncf)
+ continue;
+
+ nc->nc_filters[i] = NULL;
+ kfree(ncf);
+ }
+
+ /* Update active channel if necessary */
+ if (ndp->ndp_active_channel == nc) {
+ ndp->ndp_active_package = NULL;
+ ndp->ndp_active_channel = NULL;
+ }
+
+ /* Remove and free channel */
+ list_del_rcu(&nc->nc_node);
+ kfree(nc);
+ BUG_ON(atomic_dec_return(&np->np_channel_num) < 0);
+}
+
+struct ncsi_package *ncsi_add_package(struct ncsi_dev_priv *ndp,
+ unsigned char id)
+{
+ struct ncsi_package *np, *tmp;
+
+ np = kzalloc(sizeof(*np), GFP_ATOMIC);
+ if (!np) {
+ pr_warn("%s: Out of memory !\n", __func__);
+ return NULL;
+ }
+
+ np->np_id = id;
+ np->np_ndp = ndp;
+ spin_lock_init(&np->np_channel_lock);
+ INIT_LIST_HEAD(&np->np_channels);
+
+ spin_lock(&ndp->ndp_package_lock);
+ tmp = ncsi_find_package(ndp, id);
+ if (tmp) {
+ spin_unlock(&ndp->ndp_package_lock);
+ kfree(np);
+ return tmp;
+ }
+ list_add_tail_rcu(&np->np_node, &ndp->ndp_packages);
+ spin_unlock(&ndp->ndp_package_lock);
+
+ atomic_inc(&ndp->ndp_package_num);
+ return np;
+}
+
+struct ncsi_package *ncsi_find_package(struct ncsi_dev_priv *ndp,
+ unsigned char id)
+{
+ struct ncsi_package *np;
+
+ NCSI_FOR_EACH_PACKAGE(ndp, np) {
+ if (np->np_id == id)
+ return np;
+ }
+
+ return NULL;
+}
+
+void ncsi_release_package(struct ncsi_package *np)
+{
+ struct ncsi_dev_priv *ndp = np->np_ndp;
+ struct ncsi_channel *nc, *tmp;
+
+ /* Release all child channels */
+ spin_lock(&np->np_channel_lock);
+ list_for_each_entry_safe(nc, tmp, &np->np_channels, nc_node)
+ ncsi_release_channel(nc);
+ spin_unlock(&np->np_channel_lock);
+
+ /* Clear active package if necessary */
+ if (ndp->ndp_active_package == np) {
+ ndp->ndp_active_package = NULL;
+ ndp->ndp_active_channel = NULL;
+ }
+
+ /* Remove and free package */
+ list_del_rcu(&np->np_node);
+ kfree(np);
+
+ /* Decrease number of packages */
+ BUG_ON(atomic_dec_return(&ndp->ndp_package_num) < 0);
+}
+
+void ncsi_find_package_and_channel(struct ncsi_dev_priv *ndp,
+ unsigned char id,
+ struct ncsi_package **np,
+ struct ncsi_channel **nc)
+{
+ struct ncsi_package *p;
+ struct ncsi_channel *c;
+
+ p = ncsi_find_package(ndp, NCSI_PACKAGE_INDEX(id));
+ c = p ? ncsi_find_channel(p, NCSI_CHANNEL_INDEX(id)) : NULL;
+
+ if (np)
+ *np = p;
+ if (nc)
+ *nc = c;
+}
+
+/*
+ * For two consective NCSI commands, the packet IDs shouldn't be
+ * same. Otherwise, the bogus response might be replied. So the
+ * available IDs are allocated in round-robin fasion.
+ */
+struct ncsi_req *ncsi_alloc_req(struct ncsi_dev_priv *ndp,
+ unsigned int req_flags)
+{
+ struct ncsi_req *nr = NULL;
+ int idx, limit = 256;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ndp->ndp_req_lock, flags);
+
+ /* Check if there is one available request until the ceiling */
+ for (idx = atomic_read(&ndp->ndp_last_req_idx); idx < limit; idx++) {
+ if (ndp->ndp_reqs[idx].nr_used)
+ continue;
+
+ nr = &ndp->ndp_reqs[idx];
+ nr->nr_used = true;
+ nr->nr_flags = req_flags;
+ atomic_set(&ndp->ndp_last_req_idx, idx + 1);
+ goto found;
+ }
+
+ /* Fail back to check from the starting cursor */
+ for (idx = NCSI_REQ_START_IDX;
+ idx < atomic_read(&ndp->ndp_last_req_idx); idx++) {
+ if (ndp->ndp_reqs[idx].nr_used)
+ continue;
+
+ nr = &ndp->ndp_reqs[idx];
+ nr->nr_used = true;
+ nr->nr_flags = req_flags;
+ atomic_set(&ndp->ndp_last_req_idx, idx + 1);
+ goto found;
+ }
+
+found:
+ spin_unlock_irqrestore(&ndp->ndp_req_lock, flags);
+ return nr;
+}
+
+void ncsi_free_req(struct ncsi_req *nr)
+{
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct sk_buff *cmd, *rsp;
+ unsigned long flags;
+ bool driven;
+
+ if (nr->nr_timer_enabled) {
+ nr->nr_timer_enabled = false;
+ del_timer_sync(&nr->nr_timer);
+ }
+
+ spin_lock_irqsave(&ndp->ndp_req_lock, flags);
+ cmd = nr->nr_cmd;
+ rsp = nr->nr_rsp;
+ nr->nr_cmd = NULL;
+ nr->nr_rsp = NULL;
+ nr->nr_used = false;
+ driven = !!(nr->nr_flags & NCSI_REQ_FLAG_EVENT_DRIVEN);
+ spin_unlock_irqrestore(&ndp->ndp_req_lock, flags);
+
+ if (driven && cmd && atomic_dec_return(&ndp->ndp_pending_reqs) == 0)
+ schedule_work(&ndp->ndp_work);
+ /* Release command and response */
+ consume_skb(cmd);
+ consume_skb(rsp);
+}
+
+struct ncsi_dev *ncsi_find_dev(struct net_device *dev)
+{
+ struct ncsi_dev_priv *ndp;
+
+ NCSI_FOR_EACH_DEV(ndp) {
+ if (ndp->ndp_ndev.nd_dev == dev)
+ return &ndp->ndp_ndev;
+ }
+
+ return NULL;
+}
+
+static void ncsi_dev_config(struct ncsi_dev_priv *ndp)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct net_device *dev = nd->nd_dev;
+ struct ncsi_package *np = ndp->ndp_active_package;
+ struct ncsi_channel *nc = ndp->ndp_active_channel;
+ struct ncsi_channel *hot_nc = NULL;
+ struct ncsi_cmd_arg nca;
+ unsigned char index;
+ int ret;
+
+ nca.nca_ndp = ndp;
+ nca.nca_req_flags = NCSI_REQ_FLAG_EVENT_DRIVEN;
+
+ /* When we're reconfiguring the active channel, the active package
+ * should be selected and the old setting on the active channel
+ * should be cleared.
+ */
+ switch (nd->nd_state) {
+ case ncsi_dev_state_config:
+ case ncsi_dev_state_config_sp:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ /* Select the specific package */
+ nca.nca_type = NCSI_PKT_CMD_SP;
+ nca.nca_bytes[0] = 1;
+ nca.nca_package = np->np_id;
+ nca.nca_channel = NCSI_RESERVED_CHANNEL;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_config_cis;
+ break;
+ case ncsi_dev_state_config_cis:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ /* Clear initial state */
+ nca.nca_type = NCSI_PKT_CMD_CIS;
+ nca.nca_package = np->np_id;
+ nca.nca_channel = nc->nc_id;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_config_sma;
+ break;
+ case ncsi_dev_state_config_sma:
+ case ncsi_dev_state_config_ebf:
+ case ncsi_dev_state_config_ecnt:
+ case ncsi_dev_state_config_ec:
+ case ncsi_dev_state_config_ae:
+ case ncsi_dev_state_config_gls:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ nca.nca_package = np->np_id;
+ nca.nca_channel = nc->nc_id;
+
+ /* Use first entry in unicast filter table. Note that
+ * the MAC filter table starts from entry 1 instead of
+ * 0.
+ */
+ if (nd->nd_state == ncsi_dev_state_config_sma) {
+ nca.nca_type = NCSI_PKT_CMD_SMA;
+ for (index = 0; index < 6; index++)
+ nca.nca_bytes[index] = dev->dev_addr[index];
+ nca.nca_bytes[6] = 0x1;
+ nca.nca_bytes[7] = 0x1;
+ nd->nd_state = ncsi_dev_state_config_ebf;
+ } else if (nd->nd_state == ncsi_dev_state_config_ebf) {
+ nca.nca_type = NCSI_PKT_CMD_EBF;
+ nca.nca_dwords[0] = nc->nc_caps[NCSI_CAP_BC].ncc_cap;
+ nd->nd_state = ncsi_dev_state_config_ecnt;
+ } else if (nd->nd_state == ncsi_dev_state_config_ecnt) {
+ nca.nca_type = NCSI_PKT_CMD_ECNT;
+ nd->nd_state = ncsi_dev_state_config_ec;
+ } else if (nd->nd_state == ncsi_dev_state_config_ec) {
+ nca.nca_type = NCSI_PKT_CMD_EC;
+ nd->nd_state = ncsi_dev_state_config_ae;
+ } else if (nd->nd_state == ncsi_dev_state_config_ae) {
+ nca.nca_type = NCSI_PKT_CMD_AE;
+ nca.nca_bytes[0] = 0;
+ nca.nca_dwords[1] = 0x7;
+ nd->nd_state = ncsi_dev_state_config_gls;
+ } else if (nd->nd_state == ncsi_dev_state_config_gls) {
+ nca.nca_type = NCSI_PKT_CMD_GLS;
+ nd->nd_state = ncsi_dev_state_config_done;
+ }
+
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ break;
+ case ncsi_dev_state_config_done:
+ nd->nd_state = ncsi_dev_state_functional;
+ nd->nd_link_up = 0;
+ if (nc->nc_modes[NCSI_MODE_LINK].ncm_data[2] & 0x1) {
+ nd->nd_link_up = 1;
+ hot_nc = nc;
+ } else {
+ hot_nc = NULL;
+ }
+
+ /* Update the hot channel */
+ ndp->ndp_hot_channel = hot_nc;
+
+ nd->nd_handler(nd);
+ ndp->ndp_flags &= ~NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
+
+ break;
+ default:
+ pr_debug("%s: Unrecognized NCSI dev state 0x%x\n",
+ __func__, nd->nd_state);
+ return;
+ }
+
+ return;
+
+error:
+ nd->nd_state = ncsi_dev_state_functional;
+ nd->nd_link_up = 0;
+ ndp->ndp_flags &= ~NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE;
+ nd->nd_handler(nd);
+}
+
+static void ncsi_choose_active_channel(struct ncsi_dev_priv *ndp)
+{
+ struct ncsi_package *np;
+ struct ncsi_channel *nc, *hot_nc;
+ struct ncsi_channel_mode *ncm;
+
+ ndp->ndp_active_package = NULL;
+ ndp->ndp_active_channel = NULL;
+
+ hot_nc = ndp->ndp_hot_channel;
+ NCSI_FOR_EACH_PACKAGE(ndp, np) {
+ NCSI_FOR_EACH_CHANNEL(np, nc) {
+ if (!ndp->ndp_active_channel) {
+ ndp->ndp_active_package = np;
+ ndp->ndp_active_channel = nc;
+ }
+
+ if (nc == hot_nc) {
+ ndp->ndp_active_package = np;
+ ndp->ndp_active_channel = nc;
+ }
+
+ ncm = &nc->nc_modes[NCSI_MODE_LINK];
+ if (ncm->ncm_data[2] & 0x1) {
+ ndp->ndp_active_package = np;
+ ndp->ndp_active_channel = nc;
+ return;
+ }
+ }
+ }
+}
+
+static void ncsi_dev_probe(struct ncsi_dev_priv *ndp)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct ncsi_package *np;
+ struct ncsi_channel *nc;
+ struct ncsi_cmd_arg nca;
+ unsigned char index;
+ int ret;
+
+ nca.nca_ndp = ndp;
+ nca.nca_req_flags = NCSI_REQ_FLAG_EVENT_DRIVEN;
+ switch (nd->nd_state) {
+ case ncsi_dev_state_probe:
+ nd->nd_state = ncsi_dev_state_probe_deselect;
+ /* Fall through */
+ case ncsi_dev_state_probe_deselect:
+ atomic_set(&ndp->ndp_pending_reqs, 8);
+
+ /* Deselect all possible packages */
+ nca.nca_type = NCSI_PKT_CMD_DP;
+ nca.nca_channel = NCSI_RESERVED_CHANNEL;
+ for (index = 0; index < 8; index++) {
+ nca.nca_package = index;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ nd->nd_state = ncsi_dev_state_probe_package;
+ break;
+ case ncsi_dev_state_probe_package:
+ atomic_set(&ndp->ndp_pending_reqs, 16);
+
+ /* Select all possible packages */
+ nca.nca_type = NCSI_PKT_CMD_SP;
+ nca.nca_bytes[0] = 1;
+ nca.nca_channel = NCSI_RESERVED_CHANNEL;
+ for (index = 0; index < 8; index++) {
+ nca.nca_package = index;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ /* Disable all possible packages */
+ nca.nca_type = NCSI_PKT_CMD_DP;
+ for (index = 0; index < 8; index++) {
+ nca.nca_package = index;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ nd->nd_state = ncsi_dev_state_probe_channel;
+ break;
+ case ncsi_dev_state_probe_channel:
+ if (!ndp->ndp_active_package)
+ ndp->ndp_active_package = list_first_or_null_rcu(
+ &ndp->ndp_packages,
+ struct ncsi_package,
+ np_node);
+ else if (list_is_last(&ndp->ndp_active_package->np_node,
+ &ndp->ndp_packages))
+ ndp->ndp_active_package = NULL;
+ else
+ ndp->ndp_active_package = list_next_entry(
+ ndp->ndp_active_package,
+ np_node);
+
+ /*
+ * All available packages and channels are enumerated. The
+ * enumeration happens for once when the NCSI interface is
+ * started. So we need continue to start the interface after
+ * the enumeration.
+ *
+ * We have to choose an active channel before configuring it.
+ * Note that we possibly don't have active channel in extreme
+ * situation.
+ */
+ if (!ndp->ndp_active_package) {
+ ndp->ndp_flags |= NCSI_DEV_PRIV_FLAG_POPULATED;
+
+ ncsi_choose_active_channel(ndp);
+ if (!ndp->ndp_active_channel)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_config;
+ return ncsi_dev_config(ndp);
+ }
+
+ /* Select the active package */
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+ nca.nca_type = NCSI_PKT_CMD_SP;
+ nca.nca_bytes[0] = 1;
+ nca.nca_package = ndp->ndp_active_package->np_id;
+ nca.nca_channel = NCSI_RESERVED_CHANNEL;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_probe_cis;
+ break;
+ case ncsi_dev_state_probe_cis:
+ atomic_set(&ndp->ndp_pending_reqs, NCSI_RESERVED_CHANNEL);
+
+ /* Clear initial state */
+ nca.nca_type = NCSI_PKT_CMD_CIS;
+ nca.nca_package = ndp->ndp_active_package->np_id;
+ for (index = 0; index < NCSI_RESERVED_CHANNEL; index++) {
+ nca.nca_channel = index;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ nd->nd_state = ncsi_dev_state_probe_gvi;
+ break;
+ case ncsi_dev_state_probe_gvi:
+ case ncsi_dev_state_probe_gc:
+ case ncsi_dev_state_probe_gls:
+ np = ndp->ndp_active_package;
+ atomic_set(&ndp->ndp_pending_reqs,
+ atomic_read(&np->np_channel_num));
+
+ /* Get version information or get capacity */
+ if (nd->nd_state == ncsi_dev_state_probe_gvi)
+ nca.nca_type = NCSI_PKT_CMD_GVI;
+ else if (nd->nd_state == ncsi_dev_state_probe_gc)
+ nca.nca_type = NCSI_PKT_CMD_GC;
+ else
+ nca.nca_type = NCSI_PKT_CMD_GLS;
+
+ nca.nca_package = np->np_id;
+ NCSI_FOR_EACH_CHANNEL(np, nc) {
+ nca.nca_channel = nc->nc_id;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ if (nd->nd_state == ncsi_dev_state_probe_gvi)
+ nd->nd_state = ncsi_dev_state_probe_gc;
+ else if (nd->nd_state == ncsi_dev_state_probe_gc)
+ nd->nd_state = ncsi_dev_state_probe_gls;
+ else
+ nd->nd_state = ncsi_dev_state_probe_dp;
+ break;
+ case ncsi_dev_state_probe_dp:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ /* Deselect the active package */
+ nca.nca_type = NCSI_PKT_CMD_DP;
+ nca.nca_package = ndp->ndp_active_package->np_id;
+ nca.nca_channel = NCSI_RESERVED_CHANNEL;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ /* Scan channels in next package */
+ nd->nd_state = ncsi_dev_state_probe_channel;
+ break;
+ default:
+ pr_warn("%s: Unrecognized NCSI dev state 0x%x\n",
+ __func__, nd->nd_state);
+ }
+
+ return;
+error:
+ nd->nd_state = ncsi_dev_state_functional;
+ nd->nd_link_up = 0;
+ nd->nd_handler(nd);
+}
+
+static void ncsi_dev_suspend(struct ncsi_dev_priv *ndp)
+{
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+ struct ncsi_package *np = ndp->ndp_active_package;
+ struct ncsi_channel *nc = ndp->ndp_active_channel;
+ struct ncsi_cmd_arg nca;
+ int ret;
+
+ nca.nca_ndp = ndp;
+ nca.nca_req_flags = NCSI_REQ_FLAG_EVENT_DRIVEN;
+ switch (nd->nd_state) {
+ case ncsi_dev_state_suspend:
+ /* If there're no active channel, we're done */
+ if (!ndp->ndp_active_channel)
+ goto error;
+
+ nd->nd_state = ncsi_dev_state_suspend_select;
+ /* Fall through */
+ case ncsi_dev_state_suspend_select:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ nca.nca_type = NCSI_PKT_CMD_SP;
+ nca.nca_package = np->np_id;
+ nca.nca_channel = NCSI_RESERVED_CHANNEL;
+ nca.nca_bytes[0] = 1;
+
+ /* To retrieve the last link states of channels in current
+ * package when current active channel needs fail over to
+ * another one. It means we will possibly select another
+ * channel as next active one. The link states of channels
+ * are most important factor of the selection. So we need
+ * accurate link states. Unfortunately, the link states on
+ * inactive channels can't be updated with LSC AEN in time.
+ */
+ if (ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE)
+ nd->nd_state = ncsi_dev_state_suspend_gls;
+ else
+ nd->nd_state = ncsi_dev_state_suspend_dcnt;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ break;
+ case ncsi_dev_state_suspend_gls:
+ atomic_set(&ndp->ndp_pending_reqs,
+ atomic_read(&np->np_channel_num));
+
+ nca.nca_type = NCSI_PKT_CMD_GLS;
+ nca.nca_package = np->np_id;
+
+ nd->nd_state = ncsi_dev_state_suspend_dcnt;
+ NCSI_FOR_EACH_CHANNEL(np, nc) {
+ nca.nca_channel = nc->nc_id;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+ }
+
+ break;
+ case ncsi_dev_state_suspend_dcnt:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ nca.nca_type = NCSI_PKT_CMD_DCNT;
+ nca.nca_package = np->np_id;
+ nca.nca_channel = nc->nc_id;
+
+ nd->nd_state = ncsi_dev_state_suspend_dc;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ break;
+ case ncsi_dev_state_suspend_dc:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ nca.nca_type = NCSI_PKT_CMD_DC;
+ nca.nca_package = np->np_id;
+ nca.nca_channel = nc->nc_id;
+ nca.nca_bytes[0] = 1;
+
+ nd->nd_state = ncsi_dev_state_suspend_deselect;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ break;
+ case ncsi_dev_state_suspend_deselect:
+ atomic_set(&ndp->ndp_pending_reqs, 1);
+
+ nca.nca_type = NCSI_PKT_CMD_DP;
+ nca.nca_package = np->np_id;
+ nca.nca_channel = NCSI_RESERVED_CHANNEL;
+
+ nd->nd_state = ncsi_dev_state_suspend_done;
+ ret = ncsi_xmit_cmd(&nca);
+ if (ret)
+ goto error;
+
+ break;
+ case ncsi_dev_state_suspend_done:
+ if (ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_CHANGE_ACTIVE)
+ ncsi_choose_active_channel(ndp);
+
+ if (!ndp->ndp_active_channel) {
+ nd->nd_state = ncsi_dev_state_functional;
+ nd->nd_link_up = 0;
+ nd->nd_handler(nd);
+ } else {
+ nd->nd_state = ncsi_dev_state_config;
+ ncsi_dev_config(ndp);
+ }
+
+ break;
+ default:
+ pr_warn("%s: Unsupported NCSI dev state 0x%x\n",
+ __func__, nd->nd_state);
+ }
+
+ return;
+error:
+ nd->nd_state = ncsi_dev_state_functional;
+}
+
+static void ncsi_dev_work(struct work_struct *work)
+{
+ struct ncsi_dev_priv *ndp = container_of(work, struct ncsi_dev_priv,
+ ndp_work);
+ struct ncsi_dev *nd = &ndp->ndp_ndev;
+
+ switch (nd->nd_state & ncsi_dev_state_major) {
+ case ncsi_dev_state_probe:
+ ncsi_dev_probe(ndp);
+ break;
+ case ncsi_dev_state_suspend:
+ ncsi_dev_suspend(ndp);
+ break;
+ case ncsi_dev_state_config:
+ ncsi_dev_config(ndp);
+ break;
+ default:
+ pr_warn("%s: Unsupported NCSI dev state 0x%x\n",
+ __func__, nd->nd_state);
+ }
+}
+
+static void ncsi_req_timeout(unsigned long data)
+{
+ struct ncsi_req *nr = (struct ncsi_req *)data;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ unsigned long flags;
+
+ /* If the request already had associated response,
+ * let the response handler to release it.
+ */
+ spin_lock_irqsave(&ndp->ndp_req_lock, flags);
+ nr->nr_timer_enabled = false;
+ if (nr->nr_rsp || !nr->nr_cmd) {
+ spin_unlock_irqrestore(&ndp->ndp_req_lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&ndp->ndp_req_lock, flags);
+
+ /* Release the request */
+ ncsi_free_req(nr);
+}
+
+struct ncsi_dev *ncsi_register_dev(struct net_device *dev,
+ void (*handler)(struct ncsi_dev *ndev))
+{
+ struct ncsi_dev_priv *ndp;
+ struct ncsi_dev *nd;
+ int idx;
+
+ /* Check if the device has been registered or not */
+ nd = ncsi_find_dev(dev);
+ if (nd)
+ return nd;
+
+ /* Create NCSI device */
+ ndp = kzalloc(sizeof(*ndp), GFP_ATOMIC);
+ if (!ndp) {
+ pr_warn("%s: Out of memory !\n", __func__);
+ return NULL;
+ }
+
+ nd = &ndp->ndp_ndev;
+ nd->nd_state = ncsi_dev_state_registered;
+ nd->nd_dev = dev;
+ nd->nd_handler = handler;
+
+ /* Initialize private NCSI device */
+ spin_lock_init(&ndp->ndp_package_lock);
+ INIT_LIST_HEAD(&ndp->ndp_packages);
+ INIT_WORK(&ndp->ndp_work, ncsi_dev_work);
+ spin_lock_init(&ndp->ndp_req_lock);
+ atomic_set(&ndp->ndp_last_req_idx, NCSI_REQ_START_IDX);
+ for (idx = 0; idx < 256; idx++) {
+ ndp->ndp_reqs[idx].nr_id = idx;
+ ndp->ndp_reqs[idx].nr_ndp = ndp;
+ setup_timer(&ndp->ndp_reqs[idx].nr_timer, ncsi_req_timeout,
+ (unsigned long)&ndp->ndp_reqs[idx]);
+ }
+
+ spin_lock(&ncsi_dev_lock);
+ list_add_tail_rcu(&ndp->ndp_node, &ncsi_dev_list);
+ spin_unlock(&ncsi_dev_lock);
+
+ /* Register NCSI packet receiption handler */
+ ndp->ndp_ptype.type = cpu_to_be16(ETH_P_NCSI);
+ ndp->ndp_ptype.func = ncsi_rcv_rsp;
+ ndp->ndp_ptype.dev = dev;
+ dev_add_pack(&ndp->ndp_ptype);
+
+ return nd;
+}
+EXPORT_SYMBOL_GPL(ncsi_register_dev);
+
+int ncsi_start_dev(struct ncsi_dev *nd)
+{
+ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+
+ if (nd->nd_state != ncsi_dev_state_registered &&
+ nd->nd_state != ncsi_dev_state_functional)
+ return -ENOTTY;
+
+ if (!(ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_POPULATED)) {
+ nd->nd_state = ncsi_dev_state_probe;
+ schedule_work(&ndp->ndp_work);
+ return 0;
+ }
+
+ /* Choose active package and channel */
+ ncsi_choose_active_channel(ndp);
+ if (!ndp->ndp_active_channel)
+ return -ENXIO;
+
+ return ncsi_config_dev(nd);
+}
+EXPORT_SYMBOL_GPL(ncsi_start_dev);
+
+int ncsi_config_dev(struct ncsi_dev *nd)
+{
+ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+
+ if (nd->nd_state != ncsi_dev_state_functional)
+ return -ENOTTY;
+
+ nd->nd_state = ncsi_dev_state_config;
+ schedule_work(&ndp->ndp_work);
+
+ return 0;
+}
+
+int ncsi_suspend_dev(struct ncsi_dev *nd)
+{
+ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+
+ if (nd->nd_state != ncsi_dev_state_functional)
+ return -ENOTTY;
+
+ nd->nd_state = ncsi_dev_state_suspend;
+ schedule_work(&ndp->ndp_work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ncsi_suspend_dev);
+
+void ncsi_unregister_dev(struct ncsi_dev *nd)
+{
+ struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd);
+ struct ncsi_package *np, *tmp;
+
+ dev_remove_pack(&ndp->ndp_ptype);
+
+ spin_lock(&ndp->ndp_package_lock);
+ list_for_each_entry_safe(np, tmp, &ndp->ndp_packages, np_node)
+ ncsi_release_package(np);
+ spin_unlock(&ndp->ndp_package_lock);
+}
+EXPORT_SYMBOL_GPL(ncsi_unregister_dev);
diff --git a/net/ncsi/ncsi-pkt.h b/net/ncsi/ncsi-pkt.h
new file mode 100644
index 000000000000..646f1fc0bd21
--- /dev/null
+++ b/net/ncsi/ncsi-pkt.h
@@ -0,0 +1,391 @@
+#ifndef __NCSI_PKT_H__
+#define __NCSI_PKT_H__
+
+struct ncsi_pkt_hdr {
+ unsigned char mc_id; /* Management controller ID */
+ unsigned char revision; /* NCSI version - 0x01 */
+ unsigned char reserved; /* Reserved */
+ unsigned char id; /* Packet sequence number */
+ unsigned char type; /* Packet type */
+ unsigned char channel; /* Network controller ID */
+ __be16 length; /* Payload length */
+ __be32 reserved1[2]; /* Reserved */
+};
+
+struct ncsi_cmd_pkt_hdr {
+ struct ncsi_pkt_hdr common; /* Common NCSI packet header */
+};
+
+struct ncsi_rsp_pkt_hdr {
+ struct ncsi_pkt_hdr common; /* Common NCSI packet header */
+ __be16 code; /* Response code */
+ __be16 reason; /* Response reason */
+};
+
+struct ncsi_aen_pkt_hdr {
+ struct ncsi_pkt_hdr common; /* Common NCSI packet header */
+ unsigned char reserved2[3]; /* Reserved */
+ unsigned char type; /* AEN packet type */
+};
+
+/* NCSI common command and response packets */
+struct ncsi_cmd_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[26];
+};
+
+struct ncsi_rsp_pkt {
+ struct ncsi_rsp_pkt_hdr rsp; /* Response header */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* Select Package */
+struct ncsi_cmd_sp_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ unsigned char reserved[3]; /* Reserved */
+ unsigned char hw_arbitration; /* HW arbitration */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* Disable Channel */
+struct ncsi_cmd_dc_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ unsigned char reserved[3]; /* Reserved */
+ unsigned char ald; /* Allow link down */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* Reset Channel */
+struct ncsi_cmd_rc_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ __be32 reserved; /* Reserved */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* AEN Enable */
+struct ncsi_cmd_ae_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ unsigned char reserved[3]; /* Reserved */
+ unsigned char mc_id; /* MC ID */
+ __be32 mode; /* AEN working mode */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[18];
+};
+
+/* Set Link */
+struct ncsi_cmd_sl_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ __be32 mode; /* Link working mode */
+ __be32 oem_mode; /* OEM link mode */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[18];
+};
+
+/* Get Link Status */
+struct ncsi_rsp_gls_pkt {
+ struct ncsi_rsp_pkt_hdr rsp; /* Response header */
+ __be32 status; /* Link status */
+ __be32 other; /* Other indications */
+ __be32 oem_status; /* OEM link status */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[10];
+};
+
+/* Set VLAN Filter */
+struct ncsi_cmd_svf_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ __be16 reserved; /* Reserved */
+ __be16 vlan; /* VLAN ID */
+ __be16 reserved1; /* Reserved */
+ unsigned char index; /* VLAN table index */
+ unsigned char enable; /* Enable or disable */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[14];
+};
+
+/* Enable VLAN */
+struct ncsi_cmd_ev_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ unsigned char reserved[3]; /* Reserved */
+ unsigned char mode; /* VLAN filter mode */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* Set MAC Address */
+struct ncsi_cmd_sma_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ unsigned char mac[6]; /* MAC address */
+ unsigned char index; /* MAC table index */
+ unsigned char at_e; /* Addr type and operation */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[18];
+};
+
+/* Enable Broadcast Filter */
+struct ncsi_cmd_ebf_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ __be32 mode; /* Filter mode */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* Enable Global Multicast Filter */
+struct ncsi_cmd_egmf_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ __be32 mode; /* Global MC mode */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* Set NCSI Flow Control */
+struct ncsi_cmd_snfc_pkt {
+ struct ncsi_cmd_pkt_hdr cmd; /* Command header */
+ unsigned char reserved[3]; /* Reserved */
+ unsigned char mode; /* Flow control mode */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* Get Version ID */
+struct ncsi_rsp_gvi_pkt {
+ struct ncsi_rsp_pkt_hdr rsp; /* Response header */
+ __be32 ncsi_version; /* NCSI version */
+ unsigned char reserved[3]; /* Reserved */
+ unsigned char alpha2; /* NCSI version */
+ unsigned char fw_name[12]; /* f/w name string */
+ __be32 fw_version; /* f/w version */
+ __be16 pci_ids[4]; /* PCI IDs */
+ __be32 mf_id; /* Manufacture ID */
+ __be32 checksum;
+};
+
+/* Get Capabilities */
+struct ncsi_rsp_gc_pkt {
+ struct ncsi_rsp_pkt_hdr rsp; /* Response header */
+ __be32 cap; /* Capabilities */
+ __be32 bc_cap; /* Broadcast cap */
+ __be32 mc_cap; /* Multicast cap */
+ __be32 buf_cap; /* Buffering cap */
+ __be32 aen_cap; /* AEN cap */
+ unsigned char vlan_cnt; /* VLAN filter count */
+ unsigned char mixed_cnt; /* Mix filter count */
+ unsigned char mc_cnt; /* MC filter count */
+ unsigned char uc_cnt; /* UC filter count */
+ unsigned char reserved[2]; /* Reserved */
+ unsigned char vlan_mode; /* VLAN mode */
+ unsigned char channel_cnt; /* Channel count */
+ __be32 checksum; /* Checksum */
+};
+
+/* Get Parameters */
+struct ncsi_rsp_gp_pkt {
+ struct ncsi_rsp_pkt_hdr rsp; /* Response header */
+ unsigned char mac_cnt; /* Number of MAC addr */
+ unsigned char reserved[2]; /* Reserved */
+ unsigned char mac_enable; /* MAC addr enable flags */
+ unsigned char vlan_cnt; /* VLAN tag count */
+ unsigned char reserved1; /* Reserved */
+ __be16 vlan_enable; /* VLAN tag enable flags */
+ __be32 link_mode; /* Link setting */
+ __be32 bc_mode; /* BC filter mode */
+ __be32 valid_modes; /* Valid mode parameters */
+ unsigned char vlan_mode; /* VLAN mode */
+ unsigned char fc_mode; /* Flow control mode */
+ unsigned char reserved2[2]; /* Reserved */
+ __be32 aen_mode; /* AEN mode */
+ unsigned char mac[6]; /* Supported MAC addr */
+ __be16 vlan; /* Supported VLAN tags */
+ __be32 checksum; /* Checksum */
+};
+
+/* Get Controller Packet Statistics */
+struct ncsi_rsp_gcps_pkt {
+ struct ncsi_rsp_pkt_hdr rsp; /* Response header */
+ __be32 cnt_hi; /* Counter cleared */
+ __be32 cnt_lo; /* Counter cleared */
+ __be32 rx_bytes; /* Rx bytes */
+ __be32 tx_bytes; /* Tx bytes */
+ __be32 rx_uc_pkts; /* Rx UC packets */
+ __be32 rx_mc_pkts; /* Rx MC packets */
+ __be32 rx_bc_pkts; /* Rx BC packets */
+ __be32 tx_uc_pkts; /* Tx UC packets */
+ __be32 tx_mc_pkts; /* Tx MC packets */
+ __be32 tx_bc_pkts; /* Tx BC packets */
+ __be32 fcs_err; /* FCS errors */
+ __be32 align_err; /* Alignment errors */
+ __be32 false_carrier; /* False carrier detection */
+ __be32 runt_pkts; /* Rx runt packets */
+ __be32 jabber_pkts; /* Rx jabber packets */
+ __be32 rx_pause_xon; /* Rx pause XON frames */
+ __be32 rx_pause_xoff; /* Rx XOFF frames */
+ __be32 tx_pause_xon; /* Tx XON frames */
+ __be32 tx_pause_xoff; /* Tx XOFF frames */
+ __be32 tx_s_collision; /* Single collision frames */
+ __be32 tx_m_collision; /* Multiple collision frames */
+ __be32 l_collision; /* Late collision frames */
+ __be32 e_collision; /* Excessive collision frames */
+ __be32 rx_ctl_frames; /* Rx control frames */
+ __be32 rx_64_frames; /* Rx 64-bytes frames */
+ __be32 rx_127_frames; /* Rx 65-127 bytes frames */
+ __be32 rx_255_frames; /* Rx 128-255 bytes frames */
+ __be32 rx_511_frames; /* Rx 256-511 bytes frames */
+ __be32 rx_1023_frames; /* Rx 512-1023 bytes frames */
+ __be32 rx_1522_frames; /* Rx 1024-1522 bytes frames */
+ __be32 rx_9022_frames; /* Rx 1523-9022 bytes frames */
+ __be32 tx_64_frames; /* Tx 64-bytes frames */
+ __be32 tx_127_frames; /* Tx 65-127 bytes frames */
+ __be32 tx_255_frames; /* Tx 128-255 bytes frames */
+ __be32 tx_511_frames; /* Tx 256-511 bytes frames */
+ __be32 tx_1023_frames; /* Tx 512-1023 bytes frames */
+ __be32 tx_1522_frames; /* Tx 1024-1522 bytes frames */
+ __be32 tx_9022_frames; /* Tx 1523-9022 bytes frames */
+ __be32 rx_valid_bytes; /* Rx valid bytes */
+ __be32 rx_runt_pkts; /* Rx error runt packets */
+ __be32 rx_jabber_pkts; /* Rx error jabber packets */
+ __be32 checksum; /* Checksum */
+};
+
+/* Get NCSI Statistics */
+struct ncsi_rsp_gns_pkt {
+ struct ncsi_rsp_pkt_hdr rsp; /* Response header */
+ __be32 rx_cmds; /* Rx NCSI commands */
+ __be32 dropped_cmds; /* Dropped commands */
+ __be32 cmd_type_errs; /* Command type errors */
+ __be32 cmd_csum_errs; /* Command checksum errors */
+ __be32 rx_pkts; /* Rx NCSI packets */
+ __be32 tx_pkts; /* Tx NCSI packets */
+ __be32 tx_aen_pkts; /* Tx AEN packets */
+ __be32 checksum; /* Checksum */
+};
+
+/* Get NCSI Pass-through Statistics */
+struct ncsi_rsp_gnpts_pkt {
+ struct ncsi_rsp_pkt_hdr rsp; /* Response header */
+ __be32 tx_pkts; /* Tx packets */
+ __be32 tx_dropped; /* Tx dropped packets */
+ __be32 tx_channel_err; /* Tx channel errors */
+ __be32 tx_us_err; /* Tx undersize errors */
+ __be32 rx_pkts; /* Rx packets */
+ __be32 rx_dropped; /* Rx dropped packets */
+ __be32 rx_channel_err; /* Rx channel errors */
+ __be32 rx_us_err; /* Rx undersize errors */
+ __be32 rx_os_err; /* Rx oversize errors */
+ __be32 checksum; /* Checksum */
+};
+
+/* AEN: Link State Change */
+struct ncsi_aen_lsc_pkt {
+ struct ncsi_aen_pkt_hdr aen; /* AEN header */
+ __be32 status; /* Link status */
+ __be32 oem_status; /* OEM link status */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[14];
+};
+
+/* AEN: Configuration Required */
+struct ncsi_aen_cr_pkt {
+ struct ncsi_aen_pkt_hdr aen; /* AEN header */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[22];
+};
+
+/* AEN: Host Network Controller Driver Status Change */
+struct ncsi_aen_hncdsc_pkt {
+ struct ncsi_aen_pkt_hdr aen; /* AEN header */
+ __be32 status; /* Status */
+ __be32 checksum; /* Checksum */
+ unsigned char pad[18];
+};
+
+/* NCSI packet revision */
+#define NCSI_PKT_REVISION 0x01
+
+/* NCSI packet commands */
+#define NCSI_PKT_CMD_CIS 0x00 /* Clear Initial State */
+#define NCSI_PKT_CMD_SP 0x01 /* Select Package */
+#define NCSI_PKT_CMD_DP 0x02 /* Deselect Package */
+#define NCSI_PKT_CMD_EC 0x03 /* Enable Channel */
+#define NCSI_PKT_CMD_DC 0x04 /* Disable Channel */
+#define NCSI_PKT_CMD_RC 0x05 /* Reset Channel */
+#define NCSI_PKT_CMD_ECNT 0x06 /* Enable Channel Network Tx */
+#define NCSI_PKT_CMD_DCNT 0x07 /* Disable Channel Network Tx */
+#define NCSI_PKT_CMD_AE 0x08 /* AEN Enable */
+#define NCSI_PKT_CMD_SL 0x09 /* Set Link */
+#define NCSI_PKT_CMD_GLS 0x0a /* Get Link */
+#define NCSI_PKT_CMD_SVF 0x0b /* Set VLAN Filter */
+#define NCSI_PKT_CMD_EV 0x0c /* Enable VLAN */
+#define NCSI_PKT_CMD_DV 0x0d /* Disable VLAN */
+#define NCSI_PKT_CMD_SMA 0x0e /* Set MAC address */
+#define NCSI_PKT_CMD_EBF 0x10 /* Enable Broadcast Filter */
+#define NCSI_PKT_CMD_DBF 0x11 /* Disable Broadcast Filter */
+#define NCSI_PKT_CMD_EGMF 0x12 /* Enable Global Multicast Filter */
+#define NCSI_PKT_CMD_DGMF 0x13 /* Disable Global Multicast Filter */
+#define NCSI_PKT_CMD_SNFC 0x14 /* Set NCSI Flow Control */
+#define NCSI_PKT_CMD_GVI 0x15 /* Get Version ID */
+#define NCSI_PKT_CMD_GC 0x16 /* Get Capabilities */
+#define NCSI_PKT_CMD_GP 0x17 /* Get Parameters */
+#define NCSI_PKT_CMD_GCPS 0x18 /* Get Controller Packet Statistics */
+#define NCSI_PKT_CMD_GNS 0x19 /* Get NCSI Statistics */
+#define NCSI_PKT_CMD_GNPTS 0x1a /* Get NCSI Pass-throu Statistics */
+#define NCSI_PKT_CMD_OEM 0x50 /* OEM */
+
+/* NCSI packet responses */
+#define NCSI_PKT_RSP_CIS (NCSI_PKT_CMD_CIS + 0x80)
+#define NCSI_PKT_RSP_SP (NCSI_PKT_CMD_SP + 0x80)
+#define NCSI_PKT_RSP_DP (NCSI_PKT_CMD_DP + 0x80)
+#define NCSI_PKT_RSP_EC (NCSI_PKT_CMD_EC + 0x80)
+#define NCSI_PKT_RSP_DC (NCSI_PKT_CMD_DC + 0x80)
+#define NCSI_PKT_RSP_RC (NCSI_PKT_CMD_RC + 0x80)
+#define NCSI_PKT_RSP_ECNT (NCSI_PKT_CMD_ECNT + 0x80)
+#define NCSI_PKT_RSP_DCNT (NCSI_PKT_CMD_DCNT + 0x80)
+#define NCSI_PKT_RSP_AE (NCSI_PKT_CMD_AE + 0x80)
+#define NCSI_PKT_RSP_SL (NCSI_PKT_CMD_SL + 0x80)
+#define NCSI_PKT_RSP_GLS (NCSI_PKT_CMD_GLS + 0x80)
+#define NCSI_PKT_RSP_SVF (NCSI_PKT_CMD_SVF + 0x80)
+#define NCSI_PKT_RSP_EV (NCSI_PKT_CMD_EV + 0x80)
+#define NCSI_PKT_RSP_DV (NCSI_PKT_CMD_DV + 0x80)
+#define NCSI_PKT_RSP_SMA (NCSI_PKT_CMD_SMA + 0x80)
+#define NCSI_PKT_RSP_EBF (NCSI_PKT_CMD_EBF + 0x80)
+#define NCSI_PKT_RSP_DBF (NCSI_PKT_CMD_DBF + 0x80)
+#define NCSI_PKT_RSP_EGMF (NCSI_PKT_CMD_EGMF + 0x80)
+#define NCSI_PKT_RSP_DGMF (NCSI_PKT_CMD_DGMF + 0x80)
+#define NCSI_PKT_RSP_SNFC (NCSI_PKT_CMD_SNFC + 0x80)
+#define NCSI_PKT_RSP_GVI (NCSI_PKT_CMD_GVI + 0x80)
+#define NCSI_PKT_RSP_GC (NCSI_PKT_CMD_GC + 0x80)
+#define NCSI_PKT_RSP_GP (NCSI_PKT_CMD_GP + 0x80)
+#define NCSI_PKT_RSP_GCPS (NCSI_PKT_CMD_GCPS + 0x80)
+#define NCSI_PKT_RSP_GNS (NCSI_PKT_CMD_GNS + 0x80)
+#define NCSI_PKT_RSP_GNPTS (NCSI_PKT_CMD_GNPTS + 0x80)
+#define NCSI_PKT_RSP_OEM (NCSI_PKT_CMD_OEM + 0x80)
+
+/* NCSI packet type: AEN */
+#define NCSI_PKT_AEN 0xFF /* AEN Packet */
+#define NCSI_PKT_AEN_LSC 0x00 /* Link status change */
+#define NCSI_PKT_AEN_CR 0x01 /* Configuration required */
+#define NCSI_PKT_AEN_HNCDSC 0x02 /* HNC driver status change */
+
+/* NCSI response code/reason */
+#define NCSI_PKT_RSP_C_COMPLETED 0x0000 /* Command Completed */
+#define NCSI_PKT_RSP_C_FAILED 0x0001 /* Command Failed */
+#define NCSI_PKT_RSP_C_UNAVAILABLE 0x0002 /* Command Unavailable */
+#define NCSI_PKT_RSP_C_UNSUPPORTED 0x0003 /* Command Unsupported */
+#define NCSI_PKT_RSP_R_NO_ERROR 0x0000 /* No Error */
+#define NCSI_PKT_RSP_R_INTERFACE 0x0001 /* Interface not ready */
+#define NCSI_PKT_RSP_R_PARAM 0x0002 /* Invalid Parameter */
+#define NCSI_PKT_RSP_R_CHANNEL 0x0003 /* Channel not Ready */
+#define NCSI_PKT_RSP_R_PACKAGE 0x0004 /* Package not Ready */
+#define NCSI_PKT_RSP_R_LENGTH 0x0005 /* Invalid payload length */
+#define NCSI_PKT_RSP_R_UNKNOWN 0x7fff /* Command type unsupported */
+
+/* NCSI AEN packet type */
+#define NCSI_PKT_AEN_LSC 0x00 /* Link status change */
+#define NCSI_PKT_AEN_CR 0x01 /* Configuration required */
+#define NCSI_PKT_AEN_HNCDSC 0x02 /* Host driver status change */
+
+#endif /* __NCSI_PKT_H__ */
diff --git a/net/ncsi/ncsi-rsp.c b/net/ncsi/ncsi-rsp.c
new file mode 100644
index 000000000000..c24a1e14de8c
--- /dev/null
+++ b/net/ncsi/ncsi-rsp.c
@@ -0,0 +1,1171 @@
+/*
+ * Copyright Gavin Shan, IBM Corporation 2015.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+
+#include <net/ncsi.h>
+#include <net/net_namespace.h>
+#include <net/sock.h>
+
+#include "internal.h"
+#include "ncsi-pkt.h"
+
+static int ncsi_validate_rsp_pkt(struct ncsi_req *nr,
+ unsigned short payload)
+{
+ struct ncsi_rsp_pkt_hdr *h;
+ unsigned char *stream;
+ __be32 *checksum, csum;
+ __be32 high, low;
+ int i;
+
+ /*
+ * Check NCSI packet header. We don't need validate
+ * the packet type, which should have been checked
+ * before calling this function.
+ */
+ h = (struct ncsi_rsp_pkt_hdr *)skb_network_header(nr->nr_rsp);
+ if (h->common.revision != NCSI_PKT_REVISION)
+ return -EINVAL;
+ if (ntohs(h->common.length) != payload)
+ return -EINVAL;
+
+ /* Check on code and reason */
+ if (ntohs(h->code) != NCSI_PKT_RSP_C_COMPLETED ||
+ ntohs(h->reason) != NCSI_PKT_RSP_R_NO_ERROR)
+ return -EINVAL;
+
+ /*
+ * Validate checksum, which might be zeroes if the
+ * sender doesn't support checksum according to NCSI
+ * specification.
+ */
+ checksum = (__be32 *)((void *)(h + 1) + payload - 4);
+ if (ntohl(*checksum) == 0)
+ return 0;
+
+ csum = 0;
+ stream = (unsigned char *)h;
+ for (i = 0; i < sizeof(*h) + payload - 4; i += 2) {
+ high = stream[i];
+ low = stream[i + 1];
+ csum += ((high << 8) | low);
+ }
+
+ csum = ~csum + 1;
+ if (*checksum != htonl(csum))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_default(struct ncsi_req *nr)
+{
+ return ncsi_validate_rsp_pkt(nr, 0);
+}
+
+static int ncsi_rsp_handler_cis(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_package *np;
+ struct ncsi_channel *nc;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, &np, &nc);
+ if ((ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_POPULATED) && !nc)
+ return -ENXIO;
+
+ /* Add the channel if necessary */
+ if (!nc)
+ nc = ncsi_add_channel(np,
+ NCSI_CHANNEL_INDEX(rsp->rsp.common.channel));
+ else if (nc->nc_state == ncsi_channel_state_deselected_initial)
+ nc->nc_state = ncsi_channel_state_deselected_ready;
+ else if (nc->nc_state == ncsi_channel_state_selected_initial)
+ nc->nc_state = ncsi_channel_state_selected_ready;
+
+ return nc ? 0 : -ENODEV;
+}
+
+static int ncsi_rsp_handler_sp(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_package *np;
+ struct ncsi_channel *nc;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /*
+ * Add the package if it's not existing. Otherwise,
+ * to change the state of its child channels.
+ */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ &np, NULL);
+ if ((ndp->ndp_flags & NCSI_DEV_PRIV_FLAG_POPULATED) && !np)
+ return -ENXIO;
+
+ if (!np) {
+ np = ncsi_add_package(ndp,
+ NCSI_PACKAGE_INDEX(rsp->rsp.common.channel));
+ if (!np)
+ return -ENODEV;
+ }
+
+ NCSI_FOR_EACH_CHANNEL(np, nc) {
+ if (nc->nc_state == ncsi_channel_state_deselected_initial)
+ nc->nc_state = ncsi_channel_state_selected_initial;
+ else if (nc->nc_state == ncsi_channel_state_deselected_ready)
+ nc->nc_state = ncsi_channel_state_selected_ready;
+ }
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_dp(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_package *np;
+ struct ncsi_channel *nc;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ &np, NULL);
+ if (!np)
+ return -ENODEV;
+
+ /* Change state of all channels attached to the package */
+ NCSI_FOR_EACH_CHANNEL(np, nc) {
+ if (nc->nc_state == ncsi_channel_state_selected_initial)
+ nc->nc_state = ncsi_channel_state_deselected_initial;
+ else if (nc->nc_state == ncsi_channel_state_selected_ready)
+ nc->nc_state = ncsi_channel_state_deselected_ready;
+ }
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_ec(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ ncm = &nc->nc_modes[NCSI_MODE_ENABLE];
+ if (ncm->ncm_enable)
+ return -EBUSY;
+
+ ncm->ncm_enable = 1;
+ return 0;
+}
+
+static int ncsi_rsp_handler_dc(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp= nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ ncm = &nc->nc_modes[NCSI_MODE_ENABLE];
+ if (!ncm->ncm_enable)
+ return -EBUSY;
+
+ ncm->ncm_enable = 0;;
+ return 0;
+}
+
+static int ncsi_rsp_handler_rc(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Update state for the specified channel */
+ if (nc->nc_state == ncsi_channel_state_deselected_ready)
+ nc->nc_state = ncsi_channel_state_deselected_initial;
+ else if (nc->nc_state == ncsi_channel_state_selected_ready)
+ nc->nc_state = ncsi_channel_state_selected_initial;
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_ecnt(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ ncm = &nc->nc_modes[NCSI_MODE_TX_ENABLE];
+ if (ncm->ncm_enable)
+ return -EBUSY;
+
+ ncm->ncm_enable = 1;
+ return 0;
+}
+
+static int ncsi_rsp_handler_dcnt(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ ncm = &nc->nc_modes[NCSI_MODE_TX_ENABLE];
+ if (!ncm->ncm_enable)
+ return -EBUSY;
+
+ ncm->ncm_enable = 1;
+ return 0;
+}
+
+static int ncsi_rsp_handler_ae(struct ncsi_req *nr)
+{
+ struct ncsi_cmd_ae_pkt *cmd;
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Check if the AEN has been enabled */
+ ncm = &nc->nc_modes[NCSI_MODE_AEN];
+ if (ncm->ncm_enable)
+ return -EBUSY;
+
+ /* Update to AEN configuration */
+ cmd = (struct ncsi_cmd_ae_pkt *)skb_network_header(nr->nr_cmd);
+ ncm->ncm_enable = 1;
+ ncm->ncm_data[0] = cmd->mc_id;
+ ncm->ncm_data[1] = ntohl(cmd->mode);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_sl(struct ncsi_req *nr)
+{
+ struct ncsi_cmd_sl_pkt *cmd;
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ cmd = (struct ncsi_cmd_sl_pkt *)skb_network_header(nr->nr_cmd);
+ ncm = &nc->nc_modes[NCSI_MODE_LINK];
+ ncm->ncm_data[0] = ntohl(cmd->mode);
+ ncm->ncm_data[1] = ntohl(cmd->oem_mode);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_gls(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_gls_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 16);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_gls_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ ncm = &nc->nc_modes[NCSI_MODE_LINK];
+ ncm->ncm_data[2] = ntohl(rsp->status);
+ ncm->ncm_data[3] = ntohl(rsp->other);
+ ncm->ncm_data[4] = ntohl(rsp->oem_status);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_svf(struct ncsi_req *nr)
+{
+ struct ncsi_cmd_svf_pkt *cmd;
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_filter *ncf;
+ unsigned short vlan;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ cmd = (struct ncsi_cmd_svf_pkt *)skb_network_header(nr->nr_cmd);
+ ncf = nc->nc_filters[NCSI_FILTER_VLAN];
+ if (!ncf)
+ return -ENOENT;
+ if (cmd->index >= ncf->ncf_total)
+ return -ERANGE;
+
+ /* Add or remove the VLAN filter */
+ if (!(cmd->enable & 0x1)) {
+ ret = ncsi_del_channel_filter(nc, NCSI_FILTER_VLAN, cmd->index);
+ } else {
+ vlan = ntohs(cmd->vlan);
+ ret = ncsi_add_channel_filter(nc, NCSI_FILTER_VLAN, &vlan);
+ }
+
+ return ret;
+}
+
+static int ncsi_rsp_handler_ev(struct ncsi_req *nr)
+{
+ struct ncsi_cmd_ev_pkt *cmd;
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Check if VLAN mode has been enabled */
+ ncm = &nc->nc_modes[NCSI_MODE_VLAN];
+ if (ncm->ncm_enable)
+ return -EBUSY;
+
+ /* Update to VLAN mode */
+ cmd = (struct ncsi_cmd_ev_pkt *)skb_network_header(nr->nr_cmd);
+ ncm->ncm_enable = 1;
+ ncm->ncm_data[0] = ntohl(cmd->mode);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_dv(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Check if VLAN mode has been enabled */
+ ncm = &nc->nc_modes[NCSI_MODE_VLAN];
+ if (!ncm->ncm_enable)
+ return -EBUSY;
+
+ /* Update to VLAN mode */
+ ncm->ncm_enable = 0;
+ return 0;
+}
+
+static int ncsi_rsp_handler_sma(struct ncsi_req *nr)
+{
+ struct ncsi_cmd_sma_pkt *cmd;
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_filter *ncf;
+ void *bitmap;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* According to NCSI spec 1.01, the mixed filter table
+ * isn't supported yet.
+ */
+ cmd = (struct ncsi_cmd_sma_pkt *)skb_network_header(nr->nr_cmd);
+ switch (cmd->at_e >> 5) {
+ case 0x0: /* UC address */
+ ncf = nc->nc_filters[NCSI_FILTER_UC];
+ break;
+ case 0x1: /* MC address */
+ ncf = nc->nc_filters[NCSI_FILTER_MC];
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Sanity check on the filter */
+ if (!ncf)
+ return -ENOENT;
+ else if (cmd->index >= ncf->ncf_total)
+ return -ERANGE;
+
+ bitmap = &ncf->ncf_bitmap;
+ if (cmd->at_e & 0x1) {
+ if (test_and_set_bit(cmd->index, bitmap))
+ return -EBUSY;
+ memcpy(ncf->ncf_data + 6 * cmd->index, cmd->mac, 6);
+ } else {
+ if (!test_and_clear_bit(cmd->index, bitmap))
+ return -EBUSY;
+
+ memset(ncf->ncf_data + 6 * cmd->index, 0, 6);
+ }
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_ebf(struct ncsi_req *nr)
+{
+ struct ncsi_cmd_ebf_pkt *cmd;
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the package and channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel, NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Check if broadcast filter has been enabled */
+ ncm = &nc->nc_modes[NCSI_MODE_BC];
+ if (ncm->ncm_enable)
+ return -EBUSY;
+
+ /* Update to broadcast filter mode */
+ cmd = (struct ncsi_cmd_ebf_pkt *)skb_network_header(nr->nr_cmd);
+ ncm->ncm_enable = 1;
+ ncm->ncm_data[0] = ntohl(cmd->mode);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_dbf(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Check if broadcast filter isn't enabled */
+ ncm = &nc->nc_modes[NCSI_MODE_BC];
+ if (!ncm->ncm_enable)
+ return -EBUSY;
+
+ /* Update to broadcast filter mode */
+ ncm->ncm_enable = 0;
+ ncm->ncm_data[0] = 0;
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_egmf(struct ncsi_req *nr)
+{
+ struct ncsi_cmd_egmf_pkt *cmd;
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Check if multicast filter has been enabled */
+ ncm = &nc->nc_modes[NCSI_MODE_MC];
+ if (ncm->ncm_enable)
+ return -EBUSY;
+
+ /* Update to multicast filter mode */
+ cmd = (struct ncsi_cmd_egmf_pkt *)skb_network_header(nr->nr_cmd);
+ ncm->ncm_enable = 1;
+ ncm->ncm_data[0] = ntohl(cmd->mode);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_dgmf(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Check if multicast filter has been enabled */
+ ncm = &nc->nc_modes[NCSI_MODE_MC];
+ if (!ncm->ncm_enable)
+ return -EBUSY;
+
+ /* Update to multicast filter mode */
+ ncm->ncm_enable = 0;
+ ncm->ncm_data[0] = 0;
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_snfc(struct ncsi_req *nr)
+{
+ struct ncsi_cmd_snfc_pkt *cmd;
+ struct ncsi_rsp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_mode *ncm;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 4);
+ if (ret)
+ return ret;
+
+ /* Find the channel */
+ rsp = (struct ncsi_rsp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Check if flow control has been enabled */
+ ncm = &nc->nc_modes[NCSI_MODE_FC];
+ if (ncm->ncm_enable)
+ return -EBUSY;
+
+ /* Update to flow control mode */
+ cmd = (struct ncsi_cmd_snfc_pkt *)skb_network_header(nr->nr_cmd);
+ ncm->ncm_enable = 1;
+ ncm->ncm_data[0] = cmd->mode;
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_gvi(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_gvi_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_version *ncv;
+ int i, ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 36);
+ if (ret)
+ return ret;
+
+ /* Find the channel */
+ rsp = (struct ncsi_rsp_gvi_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Update to channel's version info */
+ ncv = &nc->nc_version;
+ ncv->ncv_version = ntohl(rsp->ncsi_version);
+ ncv->ncv_alpha2 = rsp->alpha2;
+ memcpy(ncv->ncv_fw_name, rsp->fw_name, 12);
+ ncv->ncv_fw_version = ntohl(rsp->fw_version);
+ for (i = 0; i < 4; i++)
+ ncv->ncv_pci_ids[i] = ntohs(rsp->pci_ids[i]);
+ ncv->ncv_mf_id = ntohl(rsp->mf_id);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_gc(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_gc_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_filter *ncf;
+ size_t size, entry_size;
+ int cnt, i, ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 32);
+ if (ret)
+ return ret;
+
+ /* Find the channel */
+ rsp = (struct ncsi_rsp_gc_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Update channel's capabilities */
+ nc->nc_caps[NCSI_CAP_GENERIC].ncc_cap = ntohl(rsp->cap) &
+ NCSI_CAP_GENERIC_MASK;
+ nc->nc_caps[NCSI_CAP_BC].ncc_cap = ntohl(rsp->bc_cap) &
+ NCSI_CAP_BC_MASK;
+ nc->nc_caps[NCSI_CAP_MC].ncc_cap = ntohl(rsp->mc_cap) &
+ NCSI_CAP_MC_MASK;
+ nc->nc_caps[NCSI_CAP_BUFFER].ncc_cap = ntohl(rsp->buf_cap);
+ nc->nc_caps[NCSI_CAP_AEN].ncc_cap = ntohl(rsp->aen_cap) &
+ NCSI_CAP_AEN_MASK;
+ nc->nc_caps[NCSI_CAP_VLAN].ncc_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;
+ }
+
+ if (!cnt || nc->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->ncf_index = i;
+ ncf->ncf_total = cnt;
+ ncf->ncf_bitmap = 0x0ul;
+ nc->nc_filters[i] = ncf;
+ }
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_gp(struct ncsi_req *nr)
+{
+ struct ncsi_pkt_hdr *h;
+ struct ncsi_rsp_gp_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ unsigned short length, enable, vlan;
+ unsigned char *pdata;
+ int table, i, ret;
+
+ /*
+ * The get parameter response packet has variable length.
+ * The payload should be figured out from the packet
+ * header, instead of the fixed one we have for other types
+ * of packets.
+ */
+ h = (struct ncsi_pkt_hdr *)skb_network_header(nr->nr_rsp);
+ length = ntohs(h->length);
+ if (length < 32)
+ return -EINVAL;
+
+ ret = ncsi_validate_rsp_pkt( nr, length);
+ if (ret)
+ return ret;
+
+ /* Find the channel */
+ rsp = (struct ncsi_rsp_gp_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Modes with explicit enabled indications */
+ if (ntohl(rsp->valid_modes) & 0x1) { /* BC filter mode */
+ nc->nc_modes[NCSI_MODE_BC].ncm_enable = 1;
+ nc->nc_modes[NCSI_MODE_BC].ncm_data[0] = ntohl(rsp->bc_mode);
+ }
+ if (ntohl(rsp->valid_modes) & 0x2) /* Channel enabled */
+ nc->nc_modes[NCSI_MODE_ENABLE].ncm_enable = 1;
+ if (ntohl(rsp->valid_modes) & 0x4) /* Channel Tx enabled */
+ nc->nc_modes[NCSI_MODE_TX_ENABLE].ncm_enable = 1;
+ if (ntohl(rsp->valid_modes) & 0x8) /* MC filter mode */
+ nc->nc_modes[NCSI_MODE_MC].ncm_enable = 1;
+
+ /* Modes without explicit enabled indications */
+ nc->nc_modes[NCSI_MODE_LINK].ncm_enable = 1;
+ nc->nc_modes[NCSI_MODE_LINK].ncm_data[0] = ntohl(rsp->link_mode);
+ nc->nc_modes[NCSI_MODE_VLAN].ncm_enable = 1;
+ nc->nc_modes[NCSI_MODE_VLAN].ncm_data[0] = rsp->vlan_mode;
+ nc->nc_modes[NCSI_MODE_FC].ncm_enable = 1;
+ nc->nc_modes[NCSI_MODE_FC].ncm_data[0] = rsp->fc_mode;
+ nc->nc_modes[NCSI_MODE_AEN].ncm_enable = 1;
+ nc->nc_modes[NCSI_MODE_AEN].ncm_data[0] = ntohl(rsp->aen_mode);
+
+ /* MAC addresses filter table */
+ pdata = (unsigned char *)rsp + 48;
+ enable = rsp->mac_enable;
+ for (i = 0; i < rsp->mac_cnt; i++, pdata += 6) {
+ if (i >= (nc->nc_filters[NCSI_FILTER_UC]->ncf_total +
+ nc->nc_filters[NCSI_FILTER_MC]->ncf_total))
+ table = NCSI_FILTER_MIXED;
+ else if (i >= nc->nc_filters[NCSI_FILTER_UC]->ncf_total)
+ table = NCSI_FILTER_MC;
+ else
+ table = NCSI_FILTER_UC;
+
+ if (!(enable & (0x1 << i)))
+ continue;
+
+ if (ncsi_find_channel_filter(nc, table, pdata) >= 0)
+ continue;
+
+ ncsi_add_channel_filter(nc, table, pdata);
+ }
+
+ /* VLAN filter table */
+ enable = ntohs(rsp->vlan_enable);
+ for (i = 0; i < rsp->vlan_cnt; i++, pdata += 2) {
+ if (!(enable & (0x1 << i)))
+ continue;
+
+ vlan = ntohs(*(__be16 *)pdata);
+ if (ncsi_find_channel_filter(nc, NCSI_FILTER_VLAN, &vlan) >= 0)
+ continue;
+
+ ncsi_add_channel_filter(nc, NCSI_FILTER_VLAN, &vlan);
+ }
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_gcps(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_gcps_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_stats *ncs;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 172);
+ if (ret)
+ return ret;
+
+ /* Find the channel */
+ rsp = (struct ncsi_rsp_gcps_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Update HNC's statistics */
+ ncs = &nc->nc_stats;
+ ncs->ncs_hnc_cnt_hi = ntohl(rsp->cnt_hi);
+ ncs->ncs_hnc_cnt_lo = ntohl(rsp->cnt_lo);
+ ncs->ncs_hnc_rx_bytes = ntohl(rsp->rx_bytes);
+ ncs->ncs_hnc_tx_bytes = ntohl(rsp->tx_bytes);
+ ncs->ncs_hnc_rx_uc_pkts = ntohl(rsp->rx_uc_pkts);
+ ncs->ncs_hnc_rx_mc_pkts = ntohl(rsp->rx_mc_pkts);
+ ncs->ncs_hnc_rx_bc_pkts = ntohl(rsp->rx_bc_pkts);
+ ncs->ncs_hnc_tx_uc_pkts = ntohl(rsp->tx_uc_pkts);
+ ncs->ncs_hnc_tx_mc_pkts = ntohl(rsp->tx_mc_pkts);
+ ncs->ncs_hnc_tx_bc_pkts = ntohl(rsp->tx_bc_pkts);
+ ncs->ncs_hnc_fcs_err = ntohl(rsp->fcs_err);
+ ncs->ncs_hnc_align_err = ntohl(rsp->align_err);
+ ncs->ncs_hnc_false_carrier = ntohl(rsp->false_carrier);
+ ncs->ncs_hnc_runt_pkts = ntohl(rsp->runt_pkts);
+ ncs->ncs_hnc_jabber_pkts = ntohl(rsp->jabber_pkts);
+ ncs->ncs_hnc_rx_pause_xon = ntohl(rsp->rx_pause_xon);
+ ncs->ncs_hnc_rx_pause_xoff = ntohl(rsp->rx_pause_xoff);
+ ncs->ncs_hnc_tx_pause_xon = ntohl(rsp->tx_pause_xon);
+ ncs->ncs_hnc_tx_pause_xoff = ntohl(rsp->tx_pause_xoff);
+ ncs->ncs_hnc_tx_s_collision = ntohl(rsp->tx_s_collision);
+ ncs->ncs_hnc_tx_m_collision = ntohl(rsp->tx_m_collision);
+ ncs->ncs_hnc_l_collision = ntohl(rsp->l_collision);
+ ncs->ncs_hnc_e_collision = ntohl(rsp->e_collision);
+ ncs->ncs_hnc_rx_ctl_frames = ntohl(rsp->rx_ctl_frames);
+ ncs->ncs_hnc_rx_64_frames = ntohl(rsp->rx_64_frames);
+ ncs->ncs_hnc_rx_127_frames = ntohl(rsp->rx_127_frames);
+ ncs->ncs_hnc_rx_255_frames = ntohl(rsp->rx_255_frames);
+ ncs->ncs_hnc_rx_511_frames = ntohl(rsp->rx_511_frames);
+ ncs->ncs_hnc_rx_1023_frames = ntohl(rsp->rx_1023_frames);
+ ncs->ncs_hnc_rx_1522_frames = ntohl(rsp->rx_1522_frames);
+ ncs->ncs_hnc_rx_9022_frames = ntohl(rsp->rx_9022_frames);
+ ncs->ncs_hnc_tx_64_frames = ntohl(rsp->tx_64_frames);
+ ncs->ncs_hnc_tx_127_frames = ntohl(rsp->tx_127_frames);
+ ncs->ncs_hnc_tx_255_frames = ntohl(rsp->tx_255_frames);
+ ncs->ncs_hnc_tx_511_frames = ntohl(rsp->tx_511_frames);
+ ncs->ncs_hnc_tx_1023_frames = ntohl(rsp->tx_1023_frames);
+ ncs->ncs_hnc_tx_1522_frames = ntohl(rsp->tx_1522_frames);
+ ncs->ncs_hnc_tx_9022_frames = ntohl(rsp->tx_9022_frames);
+ ncs->ncs_hnc_rx_valid_bytes = ntohl(rsp->rx_valid_bytes);
+ ncs->ncs_hnc_rx_runt_pkts = ntohl(rsp->rx_runt_pkts);
+ ncs->ncs_hnc_rx_jabber_pkts = ntohl(rsp->rx_jabber_pkts);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_gns(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_gns_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_stats *ncs;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 172);
+ if (ret)
+ return ret;
+
+ /* Find the channel */
+ rsp = (struct ncsi_rsp_gns_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Update HNC's statistics */
+ ncs = &nc->nc_stats;
+ ncs->ncs_ncsi_rx_cmds = ntohl(rsp->rx_cmds);
+ ncs->ncs_ncsi_dropped_cmds = ntohl(rsp->dropped_cmds);
+ ncs->ncs_ncsi_cmd_type_errs = ntohl(rsp->cmd_type_errs);
+ ncs->ncs_ncsi_cmd_csum_errs = ntohl(rsp->cmd_csum_errs);
+ ncs->ncs_ncsi_rx_pkts = ntohl(rsp->rx_pkts);
+ ncs->ncs_ncsi_tx_pkts = ntohl(rsp->tx_pkts);
+ ncs->ncs_ncsi_tx_aen_pkts = ntohl(rsp->tx_aen_pkts);
+
+ return 0;
+}
+
+static int ncsi_rsp_handler_gnpts(struct ncsi_req *nr)
+{
+ struct ncsi_rsp_gnpts_pkt *rsp;
+ struct ncsi_dev_priv *ndp = nr->nr_ndp;
+ struct ncsi_channel *nc;
+ struct ncsi_channel_stats *ncs;
+ int ret;
+
+ ret = ncsi_validate_rsp_pkt( nr, 172);
+ if (ret)
+ return ret;
+
+ /* Find the channel */
+ rsp = (struct ncsi_rsp_gnpts_pkt *)skb_network_header(nr->nr_rsp);
+ ncsi_find_package_and_channel(ndp, rsp->rsp.common.channel,
+ NULL, &nc);
+ if (!nc)
+ return -ENODEV;
+
+ /* Update HNC's statistics */
+ ncs = &nc->nc_stats;
+ ncs->ncs_pt_tx_pkts = ntohl(rsp->tx_pkts);
+ ncs->ncs_pt_tx_dropped = ntohl(rsp->tx_dropped);
+ ncs->ncs_pt_tx_channel_err = ntohl(rsp->tx_channel_err);
+ ncs->ncs_pt_tx_us_err = ntohl(rsp->tx_us_err);
+ ncs->ncs_pt_rx_pkts = ntohl(rsp->rx_pkts);
+ ncs->ncs_pt_rx_dropped = ntohl(rsp->rx_dropped);
+ ncs->ncs_pt_rx_channel_err = ntohl(rsp->rx_channel_err);
+ ncs->ncs_pt_rx_us_err = ntohl(rsp->rx_us_err);
+ ncs->ncs_pt_rx_os_err = ntohl(rsp->rx_os_err);
+
+ return 0;
+}
+
+static struct ncsi_rsp_handler {
+ unsigned char nrh_type;
+ int (*nrh_handler)(struct ncsi_req *nr);
+} ncsi_rsp_handlers[] = {
+ { NCSI_PKT_RSP_CIS, ncsi_rsp_handler_cis },
+ { NCSI_PKT_RSP_SP, ncsi_rsp_handler_sp },
+ { NCSI_PKT_RSP_DP, ncsi_rsp_handler_dp },
+ { NCSI_PKT_RSP_EC, ncsi_rsp_handler_ec },
+ { NCSI_PKT_RSP_DC, ncsi_rsp_handler_dc },
+ { NCSI_PKT_RSP_RC, ncsi_rsp_handler_rc },
+ { NCSI_PKT_RSP_ECNT, ncsi_rsp_handler_ecnt },
+ { NCSI_PKT_RSP_DCNT, ncsi_rsp_handler_dcnt },
+ { NCSI_PKT_RSP_AE, ncsi_rsp_handler_ae },
+ { NCSI_PKT_RSP_SL, ncsi_rsp_handler_sl },
+ { NCSI_PKT_RSP_GLS, ncsi_rsp_handler_gls },
+ { NCSI_PKT_RSP_SVF, ncsi_rsp_handler_svf },
+ { NCSI_PKT_RSP_EV, ncsi_rsp_handler_ev },
+ { NCSI_PKT_RSP_DV, ncsi_rsp_handler_dv },
+ { NCSI_PKT_RSP_SMA, ncsi_rsp_handler_sma },
+ { NCSI_PKT_RSP_EBF, ncsi_rsp_handler_ebf },
+ { NCSI_PKT_RSP_DBF, ncsi_rsp_handler_dbf },
+ { NCSI_PKT_RSP_EGMF, ncsi_rsp_handler_egmf },
+ { NCSI_PKT_RSP_DGMF, ncsi_rsp_handler_dgmf },
+ { NCSI_PKT_RSP_SNFC, ncsi_rsp_handler_snfc },
+ { NCSI_PKT_RSP_GVI, ncsi_rsp_handler_gvi },
+ { NCSI_PKT_RSP_GC, ncsi_rsp_handler_gc },
+ { NCSI_PKT_RSP_GP, ncsi_rsp_handler_gp },
+ { NCSI_PKT_RSP_GCPS, ncsi_rsp_handler_gcps },
+ { NCSI_PKT_RSP_GNS, ncsi_rsp_handler_gns },
+ { NCSI_PKT_RSP_GNPTS, ncsi_rsp_handler_gnpts },
+ { NCSI_PKT_RSP_OEM, ncsi_rsp_handler_default },
+ { 0, NULL }
+};
+
+#if 0
+void ncsi_pkt_dump(struct sk_buff *skb)
+{
+ struct skb_shared_info *info = skb_shinfo(skb);
+ skb_frag_t *frag;
+ char *data;
+ int limit, i;
+
+ pr_info("head: 0x%p data: 0x%p tail: 0x%p end: 0x%p\n",
+ skb->head, skb->data,
+ skb_tail_pointer(skb), skb_end_pointer(skb));
+ pr_info("mac_header: 0x%p network_header: 0x%p\n",
+ skb_mac_header(skb), skb_network_header(skb));
+ pr_info("len: 0x%x data_len: 0x%x truesize: 0x%x\n",
+ skb->len, skb->data_len, skb->truesize);
+
+ for (i = 0; i < info->nr_frags; i++) {
+ frag = &info->frags[i];
+ pr_info("FRAG[%d]: 0x%p offset: 0x%x size: 0x%x\n",
+ i, frag->page.p, frag->page_offset, frag->size);
+ }
+
+ data = skb_mac_header(skb);
+ limit = skb->len + sizeof(struct ethhdr);
+ for (i = 0; i < limit; data++, i++) {
+ if (i % 16 == 0)
+ printk("\n%02x ", *data);
+ else
+ printk("%02x ", *data);
+ }
+}
+#endif
+
+int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *orig_dev)
+{
+ struct ncsi_rsp_handler *nrh = NULL;
+ struct ncsi_dev *nd;
+ struct ncsi_dev_priv *ndp;
+ struct ncsi_req *nr;
+ struct ncsi_pkt_hdr *hdr;
+ unsigned long flags;
+ int ret;
+
+ /* Find the NCSI device */
+ nd = ncsi_find_dev(dev);
+ ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL;
+ if (!ndp)
+ return -ENODEV;
+
+ /* Check if it's AEN packet */
+ hdr = (struct ncsi_pkt_hdr *)skb_network_header(skb);
+ if (hdr->type == NCSI_PKT_AEN)
+ return ncsi_aen_handler(ndp, skb);
+
+ /* Find the handler */
+ nrh = ncsi_rsp_handlers;
+ while (nrh->nrh_handler) {
+ if (nrh->nrh_type == hdr->type)
+ break;
+
+ nrh++;
+ }
+
+ if (!nrh->nrh_handler) {
+ pr_warn("NCSI: Received unrecognized packet (0x%x)\n",
+ hdr->type);
+ return -ENOENT;
+ }
+
+ /* Associate with the request */
+ nr = &ndp->ndp_reqs[hdr->id];
+ spin_lock_irqsave(&ndp->ndp_req_lock, flags);
+ if (!nr->nr_used) {
+ spin_unlock_irqrestore(&ndp->ndp_req_lock, flags);
+ return -ENODEV;
+ }
+
+ nr->nr_rsp = skb;
+ if (!nr->nr_timer_enabled) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ /* Process the response */
+ ret = nrh->nrh_handler(nr);
+
+out:
+ spin_unlock_irqrestore(&ndp->ndp_req_lock, flags);
+ ncsi_free_req(nr);
+ return ret;
+}