diff options
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, ®, 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; +} |