diff --git a/config/boards/aw-som-a20.conf.disabled b/config/boards/aw-som-a20.wip similarity index 100% rename from config/boards/aw-som-a20.conf.disabled rename to config/boards/aw-som-a20.wip diff --git a/config/boards/bananapim3.wip b/config/boards/bananapim3.wip new file mode 100644 index 000000000..ad50d9d67 --- /dev/null +++ b/config/boards/bananapim3.wip @@ -0,0 +1,15 @@ +# A83T octa core 2Gb SoC Wifi +BOARD_NAME="Banana Pi M3" +LINUXFAMILY="sun8i" +BOOTCONFIG="Sinovoip_BPI_M3_defconfig" +MODULES="" +MODULES_NEXT="" +CLI_TARGET="" +DESKTOP_TARGET="" +KERNEL_TARGET="next,dev" +# +BOARDRATING="" +HARDWARE="https://linux-sunxi.org/Banana_Pi_M3" +FORUMS="http://forum.armbian.com/index.php/forum/11-other-boards/" +LEGACY="hide" +MAINLINE="hide" diff --git a/patch/kernel/sunxi-next/a83t-banana-m3/a83t-banana-m3.patch b/patch/kernel/sunxi-next/a83t-banana-m3/a83t-banana-m3.patch new file mode 100644 index 000000000..5d442bfe0 --- /dev/null +++ b/patch/kernel/sunxi-next/a83t-banana-m3/a83t-banana-m3.patch @@ -0,0 +1,609 @@ +diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile +index 309cce5..64ad31f 100644 +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -695,6 +695,7 @@ dtb-$(CONFIG_MACH_SUN8I) += \ + sun8i-a33-sinlinx-sina33.dtb \ + sun8i-a83t-allwinner-h8homlet-v2.dtb \ + sun8i-a83t-cubietruck-plus.dtb \ ++ sun8i-a83t-sinovoip-bpi-m3.dtb \ + sun8i-h3-bananapi-m2-plus.dtb \ + sun8i-h3-orangepi-2.dtb \ + sun8i-h3-orangepi-one.dtbs \ +diff --git a/arch/arm/boot/dts/sun8i-a83t-sinovoip-bpi-m3.dts b/arch/arm/boot/dts/sun8i-a83t-sinovoip-bpi-m3.dts +new file mode 100755 +index 0000000..d4db5f8 +--- /dev/null ++++ b/arch/arm/boot/dts/sun8i-a83t-sinovoip-bpi-m3.dts +@@ -0,0 +1,131 @@ ++/* ++ * Copyright 2016 Vishnu Patekar ++ * Vishnu Patekar ++ * ++ * This file is dual-licensed: you can use it either under the terms ++ * of the GPL or the X11 license, at your option. Note that this dual ++ * licensing only applies to this file, and not this project as a ++ * whole. ++ * ++ * a) This file 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 file 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. ++ * ++ * Or, alternatively, ++ * ++ * b) Permission is hereby granted, free of charge, to any person ++ * obtaining a copy of this software and associated documentation ++ * files (the "Software"), to deal in the Software without ++ * restriction, including without limitation the rights to use, ++ * copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the ++ * Software is furnished to do so, subject to the following ++ * conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES ++ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT ++ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, ++ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ++ * OTHER DEALINGS IN THE SOFTWARE. ++ */ ++ ++/dts-v1/; ++#include "sun8i-a83t.dtsi" ++#include "sunxi-common-regulators.dtsi" ++ ++/ { ++ model = "Sinovoip BananaPi M3 v1.2"; ++ compatible = "sinovoip,bpi-m3", "allwinner,sun8i-a83t"; ++ ++ aliases { ++ serial0 = &uart0; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++}; ++ ++&ehci0 { ++ /* Terminus Tech FE 1.1s 4-port USB 2.0 hub here */ ++ status = "okay"; ++ ++ /* TODO GL830 USB-to-SATA bridge downstream w/ GPIO power controls */ ++}; ++ ++&emac { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&emac_rgmii_pins>; ++ phy = <&phy1>; ++ phy-mode = "rgmii"; ++ status = "okay"; ++ allwinner,rx-delay = <7>; ++ allwinner,tx-delay = <7>; ++ /* TODO: add regluator supplies for phy */ ++ ++ phy1: ethernet-phy@1 { ++ reg = <1>; ++ }; ++}; ++ ++&mmc0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mmc0_pins_a>, <&mmc0_cd_pin_reference_design>; ++ vmmc-supply = <®_vcc3v0>; ++ cd-gpios = <&pio 5 6 GPIO_ACTIVE_HIGH>; /* PF6 */ ++ bus-width = <4>; ++ cd-inverted; ++ status = "okay"; ++}; ++ ++&mmc2 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&mmc2_8bit_pins>; ++ vmmc-supply = <®_vcc3v0>; ++ bus-width = <8>; ++ non-removable; ++ cap-mmc-hw-reset; ++ status = "okay"; ++}; ++ ++&r_rsb { ++ status = "okay"; ++}; ++ ++®_usb1_vbus { ++ gpio = <&pio 3 24 GPIO_ACTIVE_HIGH>; /* PD24 */ ++ status = "okay"; ++}; ++ ++&uart0 { ++ pinctrl-names = "default"; ++ pinctrl-0 = <&uart0_pins_b>; ++ status = "okay"; ++}; ++ ++&usbphy { ++ usb1_vbus-supply = <®_usb1_vbus>; ++ status = "okay"; ++}; ++ ++&usb_otg { ++ /* VBUS detection/drive support in PMIC required for OTG */ ++ dr_mode = "host"; ++ status = "okay"; ++}; ++ ++&usb1_vbus_pin_a { ++ allwinner,pins = "PD24"; ++}; +diff --git a/arch/arm/boot/dts/sun8i-a83t.dtsi b/arch/arm/boot/dts/sun8i-a83t.dtsi +old mode 100644 +new mode 100755 +index d3473f8..708d8b5 +--- a/arch/arm/boot/dts/sun8i-a83t.dtsi ++++ b/arch/arm/boot/dts/sun8i-a83t.dtsi +@@ -146,6 +146,175 @@ + clocks = <&osc16M>; + clock-output-names = "osc16M-d512"; + }; ++ ++ pll6: clk@01c20028 { ++ #clock-cells = <0>; ++ compatible = "allwinner,sun9i-a80-pll4-clk"; ++ reg = <0x01c20028 0x4>; ++ clocks = <&osc24M>; ++ clock-output-names = "pll6"; ++ }; ++ ++ pll6d2: pll6d2_clk { ++ #clock-cells = <0>; ++ compatible = "fixed-factor-clock"; ++ clock-div = <2>; ++ clock-mult = <1>; ++ clocks = <&pll6>; ++ clock-output-names = "pll6d2"; ++ }; ++ ++ ahb1: clk@01c20054 { ++ #clock-cells = <0>; ++ compatible = "allwinner,sun8i-a83t-ahb1-clk"; ++ reg = <0x01c20054 0x4>; ++ clocks = <&osc16Md512>, <&osc24M>, <&pll6>, <&pll6>; ++ clock-output-names = "ahb1"; ++ }; ++ ++ apb1: apb1_clk@01c20054 { ++ #clock-cells = <0>; ++ compatible = "allwinner,sun8i-a83t-apb1-clk"; ++ reg = <0x01c20054 0x4>; ++ clocks = <&ahb1>; ++ clock-output-names = "apb1"; ++ }; ++ ++ apb2: clk@01c20058 { ++ #clock-cells = <0>; ++ compatible = "allwinner,sun4i-a10-apb1-clk"; ++ reg = <0x01c20058 0x4>; ++ clocks = <&osc16Md512>, <&osc24M>, <&pll6>, <&pll6>; ++ clock-output-names = "apb2"; ++ }; ++ ++ ahb2: clk@01c2005c { ++ #clock-cells = <0>; ++ compatible = "allwinner,sun8i-h3-ahb2-clk"; ++ reg = <0x01c2005c 0x4>; ++ clocks = <&ahb1>, <&pll6d2>; ++ clock-output-names = "ahb2"; ++ }; ++ ++ bus_gates: clk@01c20060 { ++ #clock-cells = <1>; ++ compatible = "allwinner,sun8i-a83t-bus-gates-clk"; ++ reg = <0x01c20060 0x10>; ++ clocks = <&ahb1>, <&ahb2>, <&apb1>, <&apb2>; ++ clock-names = "ahb1", "ahb2", "apb1", "apb2"; ++ clock-indices = <1>, <5>, <6>, ++ <8>, <9>, <10>, ++ <13>, <14>, <17>, ++ <19>, <20>, ++ <21>, <24>, ++ <26>, <27>, ++ <29>, <32>, ++ <36>, <37>, ++ <40>, <43>, ++ <44>, <52>, <53>, ++ <54>, <65>, ++ <69>, <76>, <77>, ++ <78>, <79>, <96>, ++ <97>, <98>, ++ <112>, <113>, ++ <114>, <115>, ++ <116>; ++ clock-output-names = "bus_mipidsi", "bus_ss", "bus_dma", ++ "bus_mmc0", "bus_mmc1", "bus_mmc2", ++ "bus_nand", "bus_sdram", "bus_emac", ++ "bus_hstimer", "bus_spi0", ++ "bus_spi1", "bus_usb_otg", ++ "bus_ehci0", "bus_ehci1", ++ "bus_ohci0", "bus_ve", ++ "bus_lcd0", "bus_lcd1", ++ "bus_csi", "bus_hdmi", ++ "bus_de", "bus_gpu", "bus_msgbox", ++ "bus_spinlock", "bus_spdif", ++ "bus_pio", "bus_i2s0", "bus_i2s1", ++ "bus_i2s2", "bus_tdm", "bus_i2c0", ++ "bus_i2c1", "bus_i2c2", ++ "bus_uart0", "bus_uart1", ++ "bus_uart2", "bus_uart3", ++ "bus_uart4"; ++ }; ++ ++ mmc0_clk: clk@01c20088 { ++ #clock-cells = <1>; ++ compatible = "allwinner,sun4i-a10-mmc-clk"; ++ reg = <0x01c20088 0x4>; ++ clocks = <&osc24M>, <&pll6>; ++ clock-output-names = "mmc0", ++ "mmc0_output", ++ "mmc0_sample"; ++ }; ++ ++ mmc1_clk: clk@01c2008c { ++ #clock-cells = <1>; ++ compatible = "allwinner,sun4i-a10-mmc-clk"; ++ reg = <0x01c2008c 0x4>; ++ clocks = <&osc24M>, <&pll6>; ++ clock-output-names = "mmc1", ++ "mmc1_output", ++ "mmc1_sample"; ++ }; ++ ++ mmc2_clk: clk@01c20090 { ++ #clock-cells = <1>; ++ compatible = "allwinner,sun4i-a10-mmc-clk"; ++ reg = <0x01c20090 0x4>; ++ clocks = <&osc24M>, <&pll6>; ++ clock-output-names = "mmc2", ++ "mmc2_output", ++ "mmc2_sample"; ++ }; ++ ++ cpus_clk: clk@01f01400 { ++ compatible = "allwinner,sun9i-a80-cpus-clk"; ++ reg = <0x01f01400 0x4>; ++ #clock-cells = <0>; ++ clocks = <&osc16Md512>, <&osc24M>, <&pll6>, <&osc16M>; ++ clock-output-names = "cpus"; ++ }; ++ ++ ahb0: ahb0_clk { ++ compatible = "fixed-factor-clock"; ++ #clock-cells = <0>; ++ clock-div = <1>; ++ clock-mult = <1>; ++ clocks = <&cpus_clk>; ++ clock-output-names = "ahb0"; ++ }; ++ ++ apb0: clk@01f0140c { ++ compatible = "allwinner,sun8i-a23-apb0-clk"; ++ reg = <0x01f0140c 0x4>; ++ #clock-cells = <0>; ++ clocks = <&ahb0>; ++ clock-output-names = "apb0"; ++ }; ++ ++ apb0_gates: clk@01f01428 { ++ compatible = "allwinner,sun8i-a83t-apb0-gates-clk"; ++ reg = <0x01f01428 0x4>; ++ #clock-cells = <1>; ++ clocks = <&apb0>; ++ clock-indices = <0>, <1>, ++ <2>, <3>, ++ <4>, <6>, <7>; ++ clock-output-names = "apb0_pio", "apb0_ir", ++ "apb0_timer", "apb0_rsb", ++ "apb0_uart", "apb0_i2c0", "apb0_twd"; ++ }; ++ ++ usb_clk: clk@01c200cc { ++ #clock-cells = <1>; ++ #reset-cells = <1>; ++ compatible = "allwinner,sun8i-a23-usb-clk"; ++ reg = <0x01c200cc 0x4>; ++ clocks = <&osc24M>; ++ clock-output-names = "usb_phy0", "usb_phy1", "usb_hsic", ++ "usb_hsic_12M", "usb_ohci0"; ++ }; + }; + + soc { +@@ -154,18 +323,156 @@ + #size-cells = <1>; + ranges; + ++ mmc0: mmc@01c0f000 { ++ compatible = "allwinner,sun5i-a13-mmc"; ++ reg = <0x01c0f000 0x1000>; ++ clocks = <&bus_gates 8>, ++ <&mmc0_clk 0>, ++ <&mmc0_clk 1>, ++ <&mmc0_clk 2>; ++ clock-names = "ahb", ++ "mmc", ++ "output", ++ "sample"; ++ resets = <&ahb_reset 8>; ++ reset-names = "ahb"; ++ interrupts = ; ++ status = "disabled"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ ++ mmc1: mmc@01c10000 { ++ compatible = "allwinner,sun5i-a13-mmc"; ++ reg = <0x01c10000 0x1000>; ++ clocks = <&bus_gates 9>, ++ <&mmc1_clk 0>, ++ <&mmc1_clk 1>, ++ <&mmc1_clk 2>; ++ clock-names = "ahb", ++ "mmc", ++ "output", ++ "sample"; ++ resets = <&ahb_reset 9>; ++ reset-names = "ahb"; ++ interrupts = ; ++ status = "disabled"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ ++ mmc2: mmc@01c11000 { ++ compatible = "allwinner,sun5i-a13-mmc"; ++ reg = <0x01c11000 0x1000>; ++ clocks = <&bus_gates 10>, ++ <&mmc2_clk 0>, ++ <&mmc2_clk 1>, ++ <&mmc2_clk 2>; ++ clock-names = "ahb", ++ "mmc", ++ "output", ++ "sample"; ++ resets = <&ahb_reset 10>; ++ reset-names = "ahb"; ++ interrupts = ; ++ status = "disabled"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ ++ usb_otg: usb@01c19000 { ++ compatible = "allwinner,sun8i-a33-musb"; ++ reg = <0x01c19000 0x0400>; ++ clocks = <&bus_gates 24>; ++ resets = <&ahb_reset 24>; ++ interrupts = ; ++ interrupt-names = "mc"; ++ phys = <&usbphy 0>; ++ phy-names = "usb"; ++ extcon = <&usbphy 0>; ++ status = "disabled"; ++ }; ++ ++ usbphy: phy@01c19400 { ++ compatible = "allwinner,sun8i-a83t-usb-phy"; ++ reg = <0x01c19400 0x10>, ++ <0x01c1a800 0x4>, ++ <0x01c1b800 0x4>; ++ reg-names = "phy_ctrl", ++ "pmu1", ++ "pmu2"; ++ clocks = <&usb_clk 8>, ++ <&usb_clk 9>, ++ <&usb_clk 10>; ++ clock-names = "usb0_phy", ++ "usb1_phy", ++ "usb2_phy"; ++ resets = <&usb_clk 0>, ++ <&usb_clk 1>, ++ <&usb_clk 2>; ++ reset-names = "usb0_reset", ++ "usb1_reset", ++ "usb2_reset"; ++ status = "disabled"; ++ #phy-cells = <1>; ++ }; ++ ++ ehci0: usb@01c1a000 { ++ compatible = "allwinner,sun8i-a83t-ehci", "generic-ehci"; ++ reg = <0x01c1a000 0x100>; ++ interrupts = ; ++ clocks = <&bus_gates 26>; ++ resets = <&ahb_reset 26>; ++ phys = <&usbphy 1>; ++ phy-names = "usb"; ++ status = "disabled"; ++ }; ++ ++ ohci0: usb@01c1a400 { ++ compatible = "allwinner,sun8i-a83t-ohci", "generic-ohci"; ++ reg = <0x01c1a400 0x100>; ++ interrupts = ; ++ clocks = <&bus_gates 29>, <&usb_clk 16>; ++ resets = <&ahb_reset 29>; ++ phys = <&usbphy 1>; ++ phy-names = "usb"; ++ status = "disabled"; ++ }; ++ ++ ehci1: usb@01c1b000 { ++ compatible = "allwinner,sun8i-a83t-ehci", "generic-ehci"; ++ reg = <0x01c1b000 0x100>; ++ interrupts = ; ++ clocks = <&bus_gates 27>, <&usb_clk 11>; ++ resets = <&ahb_reset 27>; ++ phys = <&usbphy 2>; ++ phy-names = "usb"; ++ status = "disabled"; ++ }; ++ + pio: pinctrl@01c20800 { + compatible = "allwinner,sun8i-a83t-pinctrl"; + interrupts = , + , + ; + reg = <0x01c20800 0x400>; +- clocks = <&osc24M>; ++ clocks = <&bus_gates 69>; + gpio-controller; + interrupt-controller; + #interrupt-cells = <3>; + #gpio-cells = <3>; + ++ emac_rgmii_pins: emac@0 { ++ allwinner,pins = "PD2", "PD3", "PD4", ++ "PD5", "PD6", "PD7", ++ "PD11", "PD12", "PD13", ++ "PD14", "PD18", "PD19", ++ "PD21", "PD22", "PD23"; ++ allwinner,function = "gmac"; ++ allwinner,drive = ; ++ allwinner,pull = ; ++ }; ++ + mmc0_pins_a: mmc0@0 { + allwinner,pins = "PF0", "PF1", "PF2", + "PF3", "PF4", "PF5"; +@@ -174,6 +481,23 @@ + allwinner,pull = ; + }; + ++ mmc0_cd_pin_reference_design: mmc0_cd_pin@0 { ++ allwinner,pins = "PF6"; ++ allwinner,function = "gpio_in"; ++ allwinner,drive = ; ++ allwinner,pull = ; ++ }; ++ ++ mmc2_8bit_pins: mmc2_8bit { ++ allwinner,pins = "PC5", "PC6", "PC8", ++ "PC9", "PC10", "PC11", ++ "PC12", "PC13", "PC14", ++ "PC15", "PC16"; ++ allwinner,function = "mmc2"; ++ allwinner,drive = ; ++ allwinner,pull = ; ++ }; ++ + uart0_pins_a: uart0@0 { + allwinner,pins = "PF2", "PF4"; + allwinner,function = "uart0"; +@@ -189,6 +513,24 @@ + }; + }; + ++ ahb_reset: reset@01c202c0 { ++ reg = <0x01c202c0 0xc>; ++ compatible = "allwinner,sun6i-a31-clock-reset"; ++ #reset-cells = <1>; ++ }; ++ ++ apb1_reset: reset@01c202d0 { ++ reg = <0x01c202d0 0x4>; ++ compatible = "allwinner,sun6i-a31-clock-reset"; ++ #reset-cells = <1>; ++ }; ++ ++ apb2_reset: reset@01c202d8 { ++ reg = <0x01c202d8 0x4>; ++ compatible = "allwinner,sun6i-a31-clock-reset"; ++ #reset-cells = <1>; ++ }; ++ + timer@01c20c00 { + compatible = "allwinner,sun4i-a10-timer"; + reg = <0x01c20c00 0xa0>; +@@ -210,10 +552,24 @@ + interrupts = ; + reg-shift = <2>; + reg-io-width = <4>; +- clocks = <&osc24M>; ++ clocks = <&bus_gates 112>; ++ resets = <&apb2_reset 16>; + status = "disabled"; + }; + ++ emac: ethernet@1c30000 { ++ compatible = "allwinner,sun8i-a83t-emac"; ++ reg = <0x01c30000 0x104>, <0x01c00030 0x4>; ++ reg-names = "emac", "syscon"; ++ interrupts = ; ++ clocks = <&bus_gates 17>; ++ clock-names = "ahb"; ++ resets = <&ahb_reset 17>; ++ reset-names = "ahb"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ + gic: interrupt-controller@01c81000 { + compatible = "arm,cortex-a7-gic", "arm,cortex-a15-gic"; + reg = <0x01c81000 0x1000>, +@@ -224,5 +580,44 @@ + #interrupt-cells = <3>; + interrupts = ; + }; ++ ++ apb0_reset: reset@01f014b0 { ++ reg = <0x01f014b0 0x4>; ++ compatible = "allwinner,sun6i-a31-clock-reset"; ++ #reset-cells = <1>; ++ }; ++ ++ r_pio: pinctrl@01f02c00 { ++ compatible = "allwinner,sun8i-a83t-r-pinctrl"; ++ reg = <0x01f02c00 0x400>; ++ interrupts = ; ++ clocks = <&apb0_gates 0>; ++ resets = <&apb0_reset 0>; ++ gpio-controller; ++ interrupt-controller; ++ #interrupt-cells = <3>; ++ #gpio-cells = <3>; ++ ++ r_rsb_pins: r_rsb { ++ allwinner,pins = "PL0", "PL1"; ++ allwinner,function = "s_rsb"; ++ allwinner,drive = ; ++ allwinner,pull = ; ++ }; ++ }; ++ ++ r_rsb: i2c@01f03400 { ++ compatible = "allwinner,sun8i-a23-rsb"; ++ reg = <0x01f03400 0x400>; ++ interrupts = ; ++ clocks = <&apb0_gates 3>; ++ clock-frequency = <3000000>; ++ resets = <&apb0_reset 3>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&r_rsb_pins>; ++ status = "disabled"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; + }; + }; diff --git a/patch/kernel/sunxi-next/a83t-banana-m3/clk_sunxi_Add_APB1_clock_for_A83T.patch b/patch/kernel/sunxi-next/a83t-banana-m3/clk_sunxi_Add_APB1_clock_for_A83T.patch new file mode 100644 index 000000000..6e7da44ae --- /dev/null +++ b/patch/kernel/sunxi-next/a83t-banana-m3/clk_sunxi_Add_APB1_clock_for_A83T.patch @@ -0,0 +1,59 @@ +From 945e1f30db394cd21308e05a217d6575c13b1064 Mon Sep 17 00:00:00 2001 +From: Vishnu Patekar +Date: Thu, 17 Mar 2016 00:04:26 +0800 +Subject: [PATCH] clk: sunxi: Add APB1 clock for A83T + +APB1 is similar to sun4i-a10-apb0-clk, except different dividers. + +This adds support for apb1 on A83T. + +Signed-off-by: Vishnu Patekar +Acked-by: Rob Herring +--- + Documentation/devicetree/bindings/clock/sunxi.txt | 1 + + drivers/clk/sunxi/clk-sunxi.c | 13 +++++++++++++ + 2 files changed, 14 insertions(+) + +diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt +index cc4ff9e..dbb18f0 100644 +--- a/Documentation/devicetree/bindings/clock/sunxi.txt ++++ b/Documentation/devicetree/bindings/clock/sunxi.txt +@@ -59,6 +59,7 @@ Required properties: + "allwinner,sun6i-a31-apb1-gates-clk" - for the APB1 gates on A31 + "allwinner,sun7i-a20-apb1-gates-clk" - for the APB1 gates on A20 + "allwinner,sun8i-a23-apb1-gates-clk" - for the APB1 gates on A23 ++ "allwinner,sun8i-a83t-apb1-clk" - for the APB1 clock on A83T + "allwinner,sun9i-a80-apb1-gates-clk" - for the APB1 gates on A80 + "allwinner,sun6i-a31-apb2-gates-clk" - for the APB2 gates on A31 + "allwinner,sun8i-a23-apb2-gates-clk" - for the APB2 gates on A23 +diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c +index ed01fdd..8674d9b 100644 +--- a/drivers/clk/sunxi/clk-sunxi.c ++++ b/drivers/clk/sunxi/clk-sunxi.c +@@ -863,6 +863,12 @@ static const struct div_data sun4i_apb0_data __initconst = { + .table = sun4i_apb0_table, + }; + ++static const struct div_data sun8i_a83t_apb1_data __initconst = { ++ .shift = 8, ++ .pow = 0, ++ .width = 2, ++}; ++ + static void __init sunxi_divider_clk_setup(struct device_node *node, + const struct div_data *data) + { +@@ -929,6 +935,13 @@ static void __init sun4i_apb0_clk_setup(struct device_node *node) + CLK_OF_DECLARE(sun4i_apb0, "allwinner,sun4i-a10-apb0-clk", + sun4i_apb0_clk_setup); + ++static void __init sun8i_a83t_apb1_clk_setup(struct device_node *node) ++{ ++ sunxi_divider_clk_setup(node, &sun8i_a83t_apb1_data); ++} ++CLK_OF_DECLARE(sun8i_a83t_apb1, "allwinner,sun8i-a83t-apb1-clk", ++ sun8i_a83t_apb1_clk_setup); ++ + static void __init sun4i_axi_clk_setup(struct device_node *node) + { + sunxi_divider_clk_setup(node, &sun4i_axi_data); diff --git a/patch/kernel/sunxi-next/a83t-banana-m3/clk_sunxi_add_ahb1_clock_for_A83T.patch b/patch/kernel/sunxi-next/a83t-banana-m3/clk_sunxi_add_ahb1_clock_for_A83T.patch new file mode 100644 index 000000000..da053d766 --- /dev/null +++ b/patch/kernel/sunxi-next/a83t-banana-m3/clk_sunxi_add_ahb1_clock_for_A83T.patch @@ -0,0 +1,129 @@ +From 91dbafd0ff4d49aa36d0a033a515bd63100c4132 Mon Sep 17 00:00:00 2001 +From: Vishnu Patekar +Date: Thu, 17 Mar 2016 00:04:25 +0800 +Subject: [PATCH] clk: sunxi: add ahb1 clock for A83T + +AHB1 on A83T is similar to ahb1 on A31, except parents are different. +clock index 0b1x is PLL6. + +Signed-off-by: Vishnu Patekar +Acked-by: Chen-Yu Tsai +Acked-by: Rob Herring +--- + Documentation/devicetree/bindings/clock/sunxi.txt | 1 + + drivers/clk/sunxi/clk-sunxi.c | 76 +++++++++++++++++++++++ + 2 files changed, 77 insertions(+) + +diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt +index 8f7619d..cc4ff9e 100644 +--- a/Documentation/devicetree/bindings/clock/sunxi.txt ++++ b/Documentation/devicetree/bindings/clock/sunxi.txt +@@ -31,6 +31,7 @@ Required properties: + "allwinner,sun6i-a31-ar100-clk" - for the AR100 on A31 + "allwinner,sun9i-a80-cpus-clk" - for the CPUS on A80 + "allwinner,sun6i-a31-ahb1-clk" - for the AHB1 clock on A31 ++ "allwinner,sun8i-a83t-ahb1-clk" - for the AHB1 clock on A83T + "allwinner,sun8i-h3-ahb2-clk" - for the AHB2 clock on H3 + "allwinner,sun6i-a31-ahb1-gates-clk" - for the AHB1 gates on A31 + "allwinner,sun8i-a23-ahb1-gates-clk" - for the AHB1 gates on A23 +diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c +index 838b22a..ed01fdd 100644 +--- a/drivers/clk/sunxi/clk-sunxi.c ++++ b/drivers/clk/sunxi/clk-sunxi.c +@@ -344,6 +344,67 @@ static void sun6i_ahb1_recalc(struct factors_request *req) + req->rate >>= req->p; + } + ++#define SUN8I_A83T_AHB1_PARENT_PLL6 2 ++/** ++ * sun8i_a83t_get_ahb_factors() - calculates m, p factors for AHB ++ * AHB rate is calculated as follows ++ * rate = parent_rate >> p ++ * ++ * if parent is pll6, then ++ * parent_rate = pll6 rate / (m + 1) ++ */ ++ ++static void sun8i_a83t_get_ahb1_factors(struct factors_request *req) ++{ ++ u8 div, calcp, calcm = 1; ++ ++ /* ++ * clock can only divide, so we will never be able to achieve ++ * frequencies higher than the parent frequency ++ */ ++ if (req->parent_rate && req->rate > req->parent_rate) ++ req->rate = req->parent_rate; ++ ++ div = DIV_ROUND_UP(req->parent_rate, req->rate); ++ ++ /* calculate pre-divider if parent is pll6 */ ++ if (req->parent_index >= SUN8I_A83T_AHB1_PARENT_PLL6) { ++ if (div < 4) ++ calcp = 0; ++ else if (div / 2 < 4) ++ calcp = 1; ++ else if (div / 4 < 4) ++ calcp = 2; ++ else ++ calcp = 3; ++ ++ calcm = DIV_ROUND_UP(div, 1 << calcp); ++ } else { ++ calcp = __roundup_pow_of_two(div); ++ calcp = calcp > 3 ? 3 : calcp; ++ } ++ ++ req->rate = (req->parent_rate / calcm) >> calcp; ++ req->p = calcp; ++ req->m = calcm - 1; ++} ++ ++/** ++* sun8i_a83t_ahb1_recalc() - calculates AHB clock rate from m, p factors and ++* parent index ++*/ ++static void sun8i_a83t_ahb1_recalc(struct factors_request *req) ++{ ++ req->rate = req->parent_rate; ++ ++/* apply pre-divider first if parent is pll6 */ ++ if (req->parent_index >= SUN6I_AHB1_PARENT_PLL6) ++ req->rate /= req->m + 1; ++ ++ /* clk divider */ ++ req->rate >>= req->p; ++} ++ + /** + * sun4i_get_apb1_factors() - calculates m, p factors for APB1 + * APB1 rate is calculated as follows +@@ -546,6 +607,14 @@ static const struct factors_data sun6i_ahb1_data __initconst = { + .recalc = sun6i_ahb1_recalc, + }; + ++static const struct factors_data sun8i_a83t_ahb1_data __initconst = { ++ .mux = 12, ++ .muxmask = BIT(1) | BIT(0), ++ .table = &sun6i_ahb1_config, ++ .getter = sun8i_a83t_get_ahb1_factors, ++ .recalc = sun8i_a83t_ahb1_recalc, ++}; ++ + static const struct factors_data sun4i_apb1_data __initconst = { + .mux = 24, + .muxmask = BIT(1) | BIT(0), +@@ -618,6 +687,13 @@ static void __init sun6i_ahb1_clk_setup(struct device_node *node) + CLK_OF_DECLARE(sun6i_a31_ahb1, "allwinner,sun6i-a31-ahb1-clk", + sun6i_ahb1_clk_setup); + ++static void __init sun8i_a83t_ahb1_clk_setup(struct device_node *node) ++{ ++ sunxi_factors_clk_setup(node, &sun8i_a83t_ahb1_data); ++} ++CLK_OF_DECLARE(sun8i_a83t_ahb1, "allwinner,sun8i-a83t-ahb1-clk", ++ sun8i_a83t_ahb1_clk_setup); ++ + static void __init sun4i_apb1_clk_setup(struct device_node *node) + { + sunxi_factors_clk_setup(node, &sun4i_apb1_data); diff --git a/patch/kernel/sunxi-next/a83t-banana-m3/documentation_devicetree_add_Allwinner_sun8i-emac_bindings.patch b/patch/kernel/sunxi-next/a83t-banana-m3/documentation_devicetree_add_Allwinner_sun8i-emac_bindings.patch new file mode 100644 index 000000000..189b54324 --- /dev/null +++ b/patch/kernel/sunxi-next/a83t-banana-m3/documentation_devicetree_add_Allwinner_sun8i-emac_bindings.patch @@ -0,0 +1,83 @@ +From e56d82cee6826e9be2a90ef87651d39ccb12b9e2 Mon Sep 17 00:00:00 2001 +From: LABBE Corentin +Date: Tue, 1 Mar 2016 15:00:43 +0100 +Subject: [PATCH] Documentation: devicetree: add Allwinner sun8i-emac bindings + +Add documentation for the Allwinner sun8i-emac. + +Signed-off-by: LABBE Corentin +--- + .../bindings/net/allwinner,sun8i-emac.txt | 64 ++++++++++++++++++++++ + 1 file changed, 64 insertions(+) + create mode 100644 Documentation/devicetree/bindings/net/allwinner,sun8i-emac.txt + +diff --git a/Documentation/devicetree/bindings/net/allwinner,sun8i-emac.txt b/Documentation/devicetree/bindings/net/allwinner,sun8i-emac.txt +new file mode 100644 +index 0000000..cf71a71 +--- /dev/null ++++ b/Documentation/devicetree/bindings/net/allwinner,sun8i-emac.txt +@@ -0,0 +1,64 @@ ++* Allwinner sun8i EMAC ethernet controller ++ ++Required properties: ++- compatible: "allwinner,sun8i-a83t-emac", "allwinner,sun8i-h3-emac", ++ or "allwinner,sun50i-a64-emac" ++- reg: address and length of the register sets for the device. ++- reg-names: should be "emac" and "syscon", matching the register sets ++- interrupts: interrupt for the device ++- clocks: A phandle to the reference clock for this device ++- clock-names: should be "ahb" ++- resets: A phandle to the reset control for this device ++- reset-names: should be "ahb" ++- phy-mode: See ethernet.txt ++- phy or phy-handle: See ethernet.txt ++- #address-cells: shall be 1 ++- #size-cells: shall be 0 ++ ++"allwinner,sun8i-h3-emac" also requires: ++- clocks: an extra phandle to the reference clock for the EPHY ++- clock-names: an extra "ephy" entry matching the clocks property ++- resets: an extra phandle to the reset control for the EPHY ++- resets-names: an extra "ephy" entry matching the resets property ++ ++See ethernet.txt in the same directory for generic bindings for ethernet ++controllers. ++ ++The device node referenced by "phy" or "phy-handle" should be a child node ++of this node. See phy.txt for the generic PHY bindings. ++ ++Optional properties: ++- phy-supply: phandle to a regulator if the PHY needs one ++- phy-io-supply: phandle to a regulator if the PHY needs a another one for I/O. ++ This is sometimes found with RGMII PHYs, which use a second ++ regulator for the lower I/O voltage. ++- allwinner,tx-delay: The setting of the TX clock delay chain ++- allwinner,rx-delay: The setting of the RX clock delay chain ++ ++The TX/RX clock delay chain settings are board specific. ++ ++Optional properties for "allwinner,sun8i-h3-emac": ++- allwinner,use-internal-phy: Use the H3 SoC's internal E(thernet) PHY ++- allwinner,leds-active-low: EPHY LEDs are active low ++ ++When the internal PHY is requested, the implementation shall configure the ++internal PHY to use the address specified in the child PHY node. ++ ++Example: ++ ++emac: ethernet@01c0b000 { ++ compatible = "allwinner,sun8i-h3-emac"; ++ reg = <0x01c0b000 0x1000>; ++ interrupts = ; ++ clocks = <&bus_gates 17>, <&bus_gates 128>; ++ clock-names = "ahb", "ephy"; ++ resets = <&ahb_rst 17>, <&ahb_rst 66>; ++ reset-names = "ahb", "ephy"; ++ phy = <&phy1>; ++ allwinner,use-internal-phy; ++ allwinner,leds-active-low; ++ ++ phy1: ethernet-phy@1 { ++ reg = <1>; ++ }; ++}; diff --git a/patch/kernel/sunxi-next/a83t-banana-m3/ethernet_Add_myself_as_maintainers_of_sun8i-emac.patch b/patch/kernel/sunxi-next/a83t-banana-m3/ethernet_Add_myself_as_maintainers_of_sun8i-emac.patch new file mode 100644 index 000000000..882487194 --- /dev/null +++ b/patch/kernel/sunxi-next/a83t-banana-m3/ethernet_Add_myself_as_maintainers_of_sun8i-emac.patch @@ -0,0 +1,29 @@ +From fb374e770653dc6fbdf9656592141572b97c6f8e Mon Sep 17 00:00:00 2001 +From: LABBE Corentin +Date: Thu, 31 Mar 2016 19:28:54 +0200 +Subject: [PATCH] ethernet: Add myself as maintainers of sun8i-emac + +TODO + +Signed-off-by: LABBE Corentin +--- + MAINTAINERS | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/MAINTAINERS b/MAINTAINERS +index 7304d2e..47f04f5 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -581,6 +581,12 @@ S: Maintained + F: Documentation/i2c/busses/i2c-ali1563 + F: drivers/i2c/busses/i2c-ali1563.c + ++ALLWINNER SUN8I-EMAC ETHERNET DRIVER ++M: Corentin Labbe ++L: netdev@vger.kernel.org ++S: Maintained ++F: drivers/net/ethernet/allwinner/sun8i-emac.c ++ + ALLWINNER SECURITY SYSTEM + M: Corentin Labbe + L: linux-crypto@vger.kernel.org diff --git a/patch/kernel/sunxi-next/a83t-banana-m3/ethernet_add-sun8i-emac-driver.patch b/patch/kernel/sunxi-next/a83t-banana-m3/ethernet_add-sun8i-emac-driver.patch new file mode 100644 index 000000000..7419e34b1 --- /dev/null +++ b/patch/kernel/sunxi-next/a83t-banana-m3/ethernet_add-sun8i-emac-driver.patch @@ -0,0 +1,2007 @@ +From 3cf28bcaaafef838508af784a09441200d2a4182 Mon Sep 17 00:00:00 2001 +From: LABBE Corentin +Date: Thu, 14 Jan 2016 11:40:23 +0100 +Subject: [PATCH] ethernet: add sun8i-emac driver + +This patch add support for sun8i-emac ethernet MAC hardware. +It could be found in Allwinner H3/A83T/A64 SoCs. + +Signed-off-by: LABBE Corentin +--- + drivers/net/ethernet/allwinner/Kconfig | 12 + + drivers/net/ethernet/allwinner/Makefile | 1 + + drivers/net/ethernet/allwinner/sun8i-emac.c | 1955 +++++++++++++++++++++++++++ + 3 files changed, 1968 insertions(+) + create mode 100644 drivers/net/ethernet/allwinner/sun8i-emac.c + +diff --git a/drivers/net/ethernet/allwinner/Kconfig b/drivers/net/ethernet/allwinner/Kconfig +index 47da7e7..668a117 100644 +--- a/drivers/net/ethernet/allwinner/Kconfig ++++ b/drivers/net/ethernet/allwinner/Kconfig +@@ -33,4 +33,16 @@ config SUN4I_EMAC + To compile this driver as a module, choose M here. The module + will be called sun4i-emac. + ++config SUN8I_EMAC ++ tristate "Allwinner H3 EMAC support" ++ depends on ARCH_SUNXI || COMPILE_TEST ++ depends on OF ++ select MII ++ select PHYLIB ++ ---help--- ++ Support for Allwinner H3 EMAC ethernet driver. ++ ++ To compile this driver as a module, choose M here. The module ++ will be called sun8i-emac. ++ + endif # NET_VENDOR_ALLWINNER +diff --git a/drivers/net/ethernet/allwinner/Makefile b/drivers/net/ethernet/allwinner/Makefile +index 03129f7..8bd1693c 100644 +--- a/drivers/net/ethernet/allwinner/Makefile ++++ b/drivers/net/ethernet/allwinner/Makefile +@@ -3,3 +3,4 @@ + # + + obj-$(CONFIG_SUN4I_EMAC) += sun4i-emac.o ++obj-$(CONFIG_SUN8I_EMAC) += sun8i-emac.o +diff --git a/drivers/net/ethernet/allwinner/sun8i-emac.c b/drivers/net/ethernet/allwinner/sun8i-emac.c +new file mode 100644 +index 0000000..af8ed5b +--- /dev/null ++++ b/drivers/net/ethernet/allwinner/sun8i-emac.c +@@ -0,0 +1,1955 @@ ++/* ++ * sun8i-h3-emac driver ++ * ++ * Copyright (C) 2015-2016 Corentin LABBE ++ * ++ * This is the driver for Allwinner Ethernet MAC found in H3/A83T/A64 SoC ++ * ++ * This is a mono block driver that need to be splited: ++ * - A classic ethernet MAC driver ++ * - A PHY driver ++ * - A clk driver ++ * ++ * TODO: ++ * - NAPI ++ * - conditional SG handling (useful ?) ++ * - MAC filtering ++ * - Jumbo frame ++ * - features rx-all (NETIF_F_RXALL_BIT) ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define SUN8I_EMAC_BASIC_CTL0 0x00 ++#define SUN8I_EMAC_BASIC_CTL1 0x04 ++ ++#define SUN8I_EMAC_MDIO_CMD 0x48 ++#define SUN8I_EMAC_MDIO_DATA 0x4C ++ ++#define SUN8I_EMAC_RX_CTL0 0x24 ++#define SUN8I_EMAC_RX_CTL1 0x28 ++ ++#define SUN8I_EMAC_TX_CTL0 0x10 ++#define SUN8I_EMAC_TX_CTL1 0x14 ++ ++#define SUN8I_EMAC_TX_FLOW_CTL 0x1C ++ ++#define SUN8I_EMAC_RX_FRM_FLT 0x38 ++ ++#define SUN8I_EMAC_INT_STA 0x08 ++#define SUN8I_EMAC_INT_EN 0x0C ++#define SUN8I_EMAC_RGMII_STA 0xD0 ++ ++#define SUN8I_EMAC_TX_DMA_STA 0xB0 ++#define SUN8I_EMAC_TX_CUR_DESC 0xB4 ++#define SUN8I_EMAC_TX_CUR_BUF 0xB8 ++#define SUN8I_EMAC_RX_DMA_STA 0xC0 ++ ++#define MDIO_CMD_MII_BUSY BIT(0) ++#define MDIO_CMD_MII_WRITE BIT(1) ++#define MDIO_CMD_MII_PHY_REG_ADDR_MASK GENMASK(8, 4) ++#define MDIO_CMD_MII_PHY_REG_ADDR_SHIFT 4 ++#define MDIO_CMD_MII_PHY_ADDR_MASK GENMASK(16, 12) ++#define MDIO_CMD_MII_PHY_ADDR_SHIFT 12 ++ ++#define SUN8I_EMAC_MACADDR_HI 0x50 ++#define SUN8I_EMAC_MACADDR_LO 0x54 ++ ++#define SUN8I_EMAC_RX_DESC_LIST 0x34 ++#define SUN8I_EMAC_TX_DESC_LIST 0x20 ++ ++#define SUN8I_EMAC_TX_DO_CRC (BIT(27) | BIT(28)) ++#define SUN8I_EMAC_RX_DO_CRC BIT(27) ++#define SUN8I_EMAC_RX_STRIP_FCS BIT(28) ++ ++#define SUN8I_COULD_BE_USED_BY_DMA BIT(31) ++ ++#define FLOW_RX 1 ++#define FLOW_TX 2 ++ ++/* describe how data from skb are DMA mapped */ ++#define MAP_SINGLE 1 ++#define MAP_PAGE 2 ++ ++enum emac_variant { ++ A83T_EMAC, ++ H3_EMAC, ++ A64_EMAC, ++}; ++ ++struct ethtool_str { ++ char name[ETH_GSTRING_LEN]; ++}; ++ ++static const struct ethtool_str estats_str[] = { ++ /* errors */ ++ { "rx_payload_error" }, ++ { "rx_CRC_error" }, ++ { "rx_phy_error" }, ++ { "rx_length_error" }, ++ { "rx_col_error" }, ++ { "rx_header_error" }, ++ { "rx_overflow_error" }, ++ { "rx_saf_error" }, ++ { "rx_daf_error" }, ++ { "rx_buf_error" }, ++ /* misc infos */ ++ { "tx_stop_queue" }, ++ { "rx_dma_ua" }, ++ { "rx_dma_stop" }, ++ { "tx_dma_ua" }, ++ { "tx_dma_stop" }, ++ { "rx_hw_csum" }, ++ { "tx_hw_csum" }, ++ /* interrupts */ ++ { "rx_early_int" }, ++ { "tx_early_int" }, ++ { "tx_underflow_int" }, ++ /* debug */ ++ { "tx_used_desc" }, ++}; ++ ++struct sun8i_emac_stats { ++ u64 rx_payload_error; ++ u64 rx_crc_error; ++ u64 rx_phy_error; ++ u64 rx_length_error; ++ u64 rx_col_error; ++ u64 rx_header_error; ++ u64 rx_overflow_error; ++ u64 rx_saf_fail; ++ u64 rx_daf_fail; ++ u64 rx_buf_error; ++ ++ u64 tx_stop_queue; ++ u64 rx_dma_ua; ++ u64 rx_dma_stop; ++ u64 tx_dma_ua; ++ u64 tx_dma_stop; ++ u64 rx_hw_csum; ++ u64 tx_hw_csum; ++ ++ u64 rx_early_int; ++ u64 tx_early_int; ++ u64 tx_underflow_int; ++ u64 tx_used_desc; ++}; ++ ++/* The datasheet said that each descriptor can transfers up to 4096bytes ++ * But latter, a register documentation reduce that value to 2048 ++ * Anyway using 2048 cause strange behaviours and even BSP driver use 2047 ++ */ ++#define DESC_BUF_MAX 2044 ++#if (DESC_BUF_MAX < (ETH_FRAME_LEN + 4)) ++#error "DESC_BUF_MAX must be set at minimum to ETH_FRAME_LEN + 4" ++#endif ++ ++/* MAGIC value for knowing if a descriptor is available or not */ ++#define DCLEAN (BIT(16) | BIT(14) | BIT(12) | BIT(10) | BIT(9)) ++ ++/* Structure of DMA descriptor used by the hardware */ ++struct dma_desc { ++ u32 status; /* status of the descriptor */ ++ u32 st; /* Information on the frame */ ++ u32 buf_addr; /* physical address of the frame data */ ++ u32 next; /* physical address of next dma_desc */ ++} __packed __aligned(4); ++ ++/* Benched on OPIPC with 100M, setting more than 256 does not give any ++ * perf boost ++ */ ++static int nbdesc_tx = 256; ++module_param(nbdesc_tx, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(nbdesc_tx, "Number of descriptors in the TX list"); ++static int nbdesc_rx = 128; ++module_param(nbdesc_rx, int, S_IRUGO | S_IWUSR); ++MODULE_PARM_DESC(nbdesc_rx, "Number of descriptors in the RX list"); ++ ++struct sun8i_emac_priv { ++ void __iomem *base; ++ void __iomem *syscon; ++ int irq; ++ struct device *dev; ++ struct net_device *ndev; ++ struct mii_bus *mdio; ++ spinlock_t lock;/* for adjust_link */ ++ spinlock_t tx_lock;/* control the access of transmit descriptors */ ++ int duplex; ++ int speed; ++ int link; ++ int phy_interface; ++ enum emac_variant variant; ++ struct device_node *phy_node; ++ struct clk *ahb_clk; ++ struct clk *ephy_clk; ++ bool use_internal_phy; ++ ++ struct reset_control *rst; ++ struct reset_control *rst_ephy; ++ ++ struct dma_desc *dd_rx __aligned(4); ++ dma_addr_t dd_rx_phy __aligned(4); ++ struct dma_desc *dd_tx __aligned(4); ++ dma_addr_t dd_tx_phy __aligned(4); ++ struct sk_buff **rx_sk; ++ struct sk_buff **tx_sk; ++ int *tx_map; ++ ++ int tx_slot; ++ int tx_dirty; ++ int rx_dirty; ++ struct sun8i_emac_stats estats; ++ u32 msg_enable; ++ int flow_ctrl; ++ int pause; ++}; ++ ++static void rb_inc(int *p, const int max) ++{ ++ (*p)++; ++ if (*p >= max) ++ *p = 0; ++} ++ ++/* Return the number of contiguous free descriptors ++ * starting from tx_slot ++ */ ++static int rb_tx_numfreedesc(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ ++ if (priv->tx_slot < priv->tx_dirty) ++ return priv->tx_dirty - priv->tx_slot; ++ ++ return (nbdesc_tx - priv->tx_slot) + priv->tx_dirty; ++} ++ ++/* Allocate a skb in a DMA descriptor ++ * ++ * @i index of slot to fill ++*/ ++static int sun8i_emac_rx_sk(struct net_device *ndev, int i) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct dma_desc *ddesc; ++ struct sk_buff *sk; ++ ++ ddesc = priv->dd_rx + i; ++ ++ ddesc->st = 0; ++ ++ sk = netdev_alloc_skb_ip_align(ndev, DESC_BUF_MAX); ++ if (!sk) ++ return -ENOMEM; ++ ++ /* should not happen */ ++ if (unlikely(priv->rx_sk[i])) ++ dev_warn(priv->dev, "BUG: Leaking a skbuff\n"); ++ ++ priv->rx_sk[i] = sk; ++ ++ ddesc->buf_addr = dma_map_single(priv->dev, sk->data, ++ DESC_BUF_MAX, DMA_FROM_DEVICE); ++ if (dma_mapping_error(priv->dev, ddesc->buf_addr)) { ++ dev_err(priv->dev, "ERROR: Cannot dma_map RX buffer\n"); ++ dev_kfree_skb(sk); ++ return -EFAULT; ++ } ++ ddesc->st |= DESC_BUF_MAX; ++ ddesc->status = BIT(31); ++ ++ return 0; ++} ++ ++/* Set MAC address for slot index ++ * @addr: the MAC address to set ++ * @index: The index of slot where to set address. ++ * The slot 0 is the main MACaddr ++ */ ++static void sun8i_emac_set_macaddr(struct sun8i_emac_priv *priv, ++ const u8 *addr, int index) ++{ ++ u32 v; ++ ++ dev_info(priv->dev, "device MAC address slot %d %02x:%02x:%02x:%02x:%02x:%02x\n", ++ index, addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); ++ ++ v = (addr[5] << 8) | addr[4]; ++ writel(v, priv->base + SUN8I_EMAC_MACADDR_HI + index * 8); ++ v = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0]; ++ writel(v, priv->base + SUN8I_EMAC_MACADDR_LO + index * 8); ++} ++ ++static void sun8i_emac_set_link_mode(struct sun8i_emac_priv *priv) ++{ ++ u32 v; ++ ++ v = readl(priv->base + SUN8I_EMAC_BASIC_CTL0); ++ ++ if (priv->duplex) ++ v |= BIT(0); ++ else ++ v &= ~BIT(0); ++ ++ v &= ~0x0C; ++ switch (priv->speed) { ++ case 1000: ++ break; ++ case 100: ++ v |= BIT(2); ++ v |= BIT(3); ++ break; ++ case 10: ++ v |= BIT(3); ++ break; ++ } ++ ++ writel(v, priv->base + SUN8I_EMAC_BASIC_CTL0); ++} ++ ++static void sun8i_emac_flow_ctrl(struct sun8i_emac_priv *priv, int duplex, ++ int fc, int pause) ++{ ++ u32 flow = 0; ++ ++ netif_dbg(priv, link, priv->ndev, "%s %d %d %d\n", __func__, ++ duplex, fc, pause); ++ ++ flow = readl(priv->base + SUN8I_EMAC_RX_CTL0); ++ if (fc & FLOW_RX) ++ flow |= BIT(16); ++ else ++ flow &= ~BIT(16); ++ writel(flow, priv->base + SUN8I_EMAC_RX_CTL0); ++ ++ flow = readl(priv->base + SUN8I_EMAC_TX_FLOW_CTL); ++ if (fc & FLOW_TX) ++ flow |= BIT(0); ++ else ++ flow &= ~BIT(0); ++ writel(flow, priv->base + SUN8I_EMAC_TX_FLOW_CTL); ++} ++ ++/* Grab a frame into a skb from descriptor number i */ ++static int sun8i_emac_rx_from_ddesc(struct net_device *ndev, int i) ++{ ++ struct sk_buff *skb; ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct dma_desc *ddesc = priv->dd_rx + i; ++ int frame_len; ++ int crc_checked = 0; ++ ++ if (ndev->features & NETIF_F_RXCSUM) ++ crc_checked = 1; ++ ++ /* bit0/bit7 work only on IPv4/IPv6 TCP traffic, ++ * (not on ARP for example) so we dont raise rx_errors/discard frame ++ */ ++ /* the checksum or length of received frame's payload is wrong*/ ++ if (ddesc->status & BIT(0)) { ++ priv->estats.rx_payload_error++; ++ crc_checked = 0; ++ } ++ if (ddesc->status & BIT(1)) { ++ priv->ndev->stats.rx_errors++; ++ priv->ndev->stats.rx_crc_errors++; ++ priv->estats.rx_crc_error++; ++ goto discard_frame; ++ } ++ if ((ddesc->status & BIT(3))) { ++ priv->ndev->stats.rx_errors++; ++ priv->estats.rx_phy_error++; ++ goto discard_frame; ++ } ++ if ((ddesc->status & BIT(4))) { ++ priv->ndev->stats.rx_errors++; ++ priv->ndev->stats.rx_length_errors++; ++ priv->estats.rx_length_error++; ++ goto discard_frame; ++ } ++ if ((ddesc->status & BIT(6))) { ++ priv->ndev->stats.rx_errors++; ++ priv->estats.rx_col_error++; ++ goto discard_frame; ++ } ++ if ((ddesc->status & BIT(7))) { ++ priv->estats.rx_header_error++; ++ crc_checked = 0; ++ } ++ if ((ddesc->status & BIT(11))) { ++ priv->ndev->stats.rx_over_errors++; ++ priv->estats.rx_overflow_error++; ++ goto discard_frame; ++ } ++ if ((ddesc->status & BIT(14))) { ++ priv->ndev->stats.rx_errors++; ++ priv->estats.rx_buf_error++; ++ goto discard_frame; ++ } ++ ++ if ((ddesc->status & BIT(9)) == 0) { ++ /* begin of a Jumbo frame */ ++ dev_warn(priv->dev, "This should not happen\n"); ++ goto discard_frame; ++ } ++ frame_len = (ddesc->status >> 16) & 0x3FFF; ++ if (!(ndev->features & NETIF_F_RXFCS)) ++ frame_len -= ETH_FCS_LEN; ++ ++ skb = priv->rx_sk[i]; ++ ++ netif_dbg(priv, rx_status, priv->ndev, ++ "%s from %02d %pad len=%d status=%x st=%x\n", ++ __func__, i, &ddesc, frame_len, ddesc->status, ddesc->st); ++ ++ skb_put(skb, frame_len); ++ ++ dma_unmap_single(priv->dev, ddesc->buf_addr, DESC_BUF_MAX, ++ DMA_FROM_DEVICE); ++ skb->protocol = eth_type_trans(skb, priv->ndev); ++ if (crc_checked) { ++ skb->ip_summed = CHECKSUM_UNNECESSARY; ++ priv->estats.rx_hw_csum++; ++ } else { ++ skb->ip_summed = CHECKSUM_PARTIAL; ++ } ++ skb->dev = priv->ndev; ++ ++ priv->ndev->stats.rx_packets++; ++ priv->ndev->stats.rx_bytes += frame_len; ++ priv->rx_sk[i] = NULL; ++ ++ /* this frame is not the last */ ++ if ((ddesc->status & BIT(8)) == 0) { ++ dev_warn(priv->dev, "Multi frame not implemented currlen=%d\n", ++ frame_len); ++ } ++ ++ sun8i_emac_rx_sk(ndev, i); ++ ++ netif_rx(skb); ++ ++ return 0; ++ /* If the frame need to be dropped, we simply reuse the buffer */ ++discard_frame: ++ ddesc->st = DESC_BUF_MAX; ++ ddesc->status = BIT(31); ++ return 0; ++} ++ ++/* Cycle over RX DMA descriptors for finding frame to receive ++ */ ++static int sun8i_emac_receive_all(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct dma_desc *ddesc; ++ ++ ddesc = priv->dd_rx + priv->rx_dirty; ++ while (!(ddesc->status & BIT(31))) { ++ sun8i_emac_rx_from_ddesc(ndev, priv->rx_dirty); ++ rb_inc(&priv->rx_dirty, nbdesc_rx); ++ ddesc = priv->dd_rx + priv->rx_dirty; ++ }; ++ ++ return 0; ++} ++ ++/* iterate over dma_desc for finding completed xmit. ++ * Called from interrupt context, so no need to spinlock tx ++ * ++ * The problem is: how to know that a descriptor is sent and not just in ++ * preparation. ++ * Need to have status=0 and st set but this is the state of first frame just ++ * before setting the own-by-DMA bit. ++ * The solution is to used the artificial value DCLEAN. ++ */ ++static int sun8i_emac_complete_xmit(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct dma_desc *ddesc; ++ int frame_len; ++ ++ do { ++ ddesc = priv->dd_tx + priv->tx_dirty; ++ ++ if (ddesc->status & BIT(31)) { ++ dev_info(priv->dev, "BUG: DMA still set %d %d\n", ++ priv->tx_dirty, priv->tx_slot); ++ return 0; ++ } ++ ++ if (ddesc->status == DCLEAN) ++ return 0; ++ ++ if (ddesc->status == 0 && !ddesc->st) { ++ dev_info(priv->dev, "BUG: reached the void %d %d\n", ++ priv->tx_dirty, priv->tx_slot); ++ return 0; ++ } ++ ++ /* TX_UNDERFLOW_ERR */ ++ if (ddesc->status & BIT(1)) ++ priv->ndev->stats.tx_errors++; ++ /* TX_DEFER_ERR */ ++ if (ddesc->status & BIT(2)) ++ priv->ndev->stats.tx_errors++; ++ /* BIT 6:3 numbers of collisions */ ++ if (ddesc->status & 0x78) ++ priv->ndev->stats.collisions += ++ (ddesc->status & 0x78) >> 3; ++ /* TX_COL_ERR_1 */ ++ if (ddesc->status & BIT(8)) ++ priv->ndev->stats.tx_errors++; ++ /* TX_COL_ERR_0 */ ++ if (ddesc->status & BIT(9)) ++ priv->ndev->stats.tx_errors++; ++ /* TX_CRS_ERR */ ++ if (ddesc->status & BIT(10)) ++ priv->ndev->stats.tx_carrier_errors++; ++ /* TX_PAYLOAD_ERR */ ++ if (ddesc->status & BIT(12)) ++ priv->ndev->stats.tx_errors++; ++ /* TX_LENGTH_ERR */ ++ if (ddesc->status & BIT(14)) ++ priv->ndev->stats.tx_errors++; ++ /* TX_HEADER_ERR */ ++ if (ddesc->status & BIT(16)) ++ priv->ndev->stats.tx_errors++; ++ frame_len = ddesc->st & 0x3FFF; ++ if (priv->tx_map[priv->tx_dirty] == MAP_SINGLE) ++ dma_unmap_single(priv->dev, ddesc->buf_addr, ++ frame_len, DMA_TO_DEVICE); ++ else ++ dma_unmap_page(priv->dev, ddesc->buf_addr, ++ frame_len, DMA_TO_DEVICE); ++ /* we can free skb only on last frame */ ++ if (priv->tx_sk[priv->tx_dirty] && (ddesc->st & BIT(30))) ++ dev_kfree_skb_irq(priv->tx_sk[priv->tx_dirty]); ++ ++ priv->tx_sk[priv->tx_dirty] = NULL; ++ priv->tx_map[priv->tx_dirty] = 0; ++ ddesc->status = DCLEAN; ++ ddesc->st = 0; ++ ++ rb_inc(&priv->tx_dirty, nbdesc_tx); ++ ddesc = priv->dd_tx + priv->tx_dirty; ++ } while (ddesc->st && !(ddesc->status & BIT(31))); ++ ++ if (netif_queue_stopped(ndev) && ++ rb_tx_numfreedesc(ndev) > MAX_SKB_FRAGS + 1) ++ netif_wake_queue(ndev); ++ ++ return 0; ++} ++ ++static int sun8i_mdio_read(struct mii_bus *bus, int phy_addr, int phy_reg) ++{ ++ struct net_device *ndev = bus->priv; ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ int err; ++ u32 reg; ++ ++ err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg, ++ !(reg & MDIO_CMD_MII_BUSY), 100, 10000); ++ if (err) { ++ dev_err(priv->dev, "%s timeout %x\n", __func__, reg); ++ return err; ++ } ++ ++ reg &= ~MDIO_CMD_MII_WRITE; ++ reg &= ~MDIO_CMD_MII_PHY_REG_ADDR_MASK; ++ reg |= (phy_reg << MDIO_CMD_MII_PHY_REG_ADDR_SHIFT) & ++ MDIO_CMD_MII_PHY_REG_ADDR_MASK; ++ ++ reg &= ~MDIO_CMD_MII_PHY_ADDR_MASK; ++ ++ reg |= (phy_addr << MDIO_CMD_MII_PHY_ADDR_SHIFT) & ++ MDIO_CMD_MII_PHY_ADDR_MASK; ++ ++ reg |= MDIO_CMD_MII_BUSY; ++ ++ writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD); ++ ++ err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg, ++ !(reg & MDIO_CMD_MII_BUSY), 100, 10000); ++ ++ if (err) { ++ dev_err(priv->dev, "%s timeout %x\n", __func__, reg); ++ return err; ++ } ++ ++ return readl(priv->base + SUN8I_EMAC_MDIO_DATA); ++} ++ ++static int sun8i_mdio_write(struct mii_bus *bus, int phy_addr, int phy_reg, ++ u16 data) ++{ ++ struct net_device *ndev = bus->priv; ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ u32 reg; ++ int err; ++ ++ err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg, ++ !(reg & MDIO_CMD_MII_BUSY), 100, 10000); ++ if (err) { ++ dev_err(priv->dev, "%s timeout %x\n", __func__, reg); ++ return err; ++ } ++ ++ reg &= ~MDIO_CMD_MII_PHY_REG_ADDR_MASK; ++ reg |= (phy_reg << MDIO_CMD_MII_PHY_REG_ADDR_SHIFT) & ++ MDIO_CMD_MII_PHY_REG_ADDR_MASK; ++ ++ reg &= ~MDIO_CMD_MII_PHY_ADDR_MASK; ++ reg |= (phy_addr << MDIO_CMD_MII_PHY_ADDR_SHIFT) & ++ MDIO_CMD_MII_PHY_ADDR_MASK; ++ ++ reg |= MDIO_CMD_MII_WRITE; ++ reg |= MDIO_CMD_MII_BUSY; ++ ++ writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD); ++ writel(data, priv->base + SUN8I_EMAC_MDIO_DATA); ++ dev_dbg(priv->dev, "%s %d %d %x %x\n", __func__, phy_addr, phy_reg, ++ reg, data); ++ ++ err = readl_poll_timeout(priv->base + SUN8I_EMAC_MDIO_CMD, reg, ++ !(reg & MDIO_CMD_MII_BUSY), 100, 10000); ++ if (err) { ++ dev_err(priv->dev, "%s timeout %x\n", __func__, reg); ++ return err; ++ } ++ ++ return 0; ++} ++ ++static int sun8i_emac_mdio_register(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct mii_bus *bus; ++ int ret; ++ ++ bus = devm_mdiobus_alloc(priv->dev); ++ if (!bus) { ++ netdev_err(ndev, "Failed to allocate new mdio bus\n"); ++ return -ENOMEM; ++ } ++ ++ bus->name = dev_name(priv->dev); ++ bus->read = &sun8i_mdio_read; ++ bus->write = &sun8i_mdio_write; ++ snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%x", bus->name, 0); ++ ++ bus->parent = priv->dev; ++ bus->priv = ndev; ++ ++ ret = of_mdiobus_register(bus, priv->dev->of_node); ++ if (ret) { ++ netdev_err(ndev, "Could not register as MDIO bus: %d\n", ret); ++ return ret; ++ } ++ ++ priv->mdio = bus; ++ ++ return 0; ++} ++ ++static void sun8i_emac_adjust_link(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct phy_device *phydev = ndev->phydev; ++ unsigned long flags; ++ int new_state = 0; ++ ++ netif_dbg(priv, link, priv->ndev, ++ "%s link=%x duplex=%x speed=%x\n", __func__, ++ phydev->link, phydev->duplex, phydev->speed); ++ if (!phydev) ++ return; ++ ++ spin_lock_irqsave(&priv->lock, flags); ++ ++ if (phydev->link) { ++ if (phydev->duplex != priv->duplex) { ++ new_state = 1; ++ priv->duplex = phydev->duplex; ++ } ++ if (phydev->pause) ++ sun8i_emac_flow_ctrl(priv, phydev->duplex, ++ priv->flow_ctrl, priv->pause); ++ ++ if (phydev->speed != priv->speed) { ++ new_state = 1; ++ priv->speed = phydev->speed; ++ } ++ ++ if (priv->link == 0) { ++ new_state = 1; ++ priv->link = phydev->link; ++ } ++ ++ netif_dbg(priv, link, priv->ndev, ++ "%s new=%d link=%d pause=%d\n", ++ __func__, new_state, priv->link, phydev->pause); ++ if (new_state) ++ sun8i_emac_set_link_mode(priv); ++ } else if (priv->link != phydev->link) { ++ new_state = 1; ++ priv->link = 0; ++ priv->speed = 0; ++ priv->duplex = -1; ++ } ++ ++ if (new_state) ++ phy_print_status(phydev); ++ ++ spin_unlock_irqrestore(&priv->lock, flags); ++} ++ ++/* H3 specific bits for EPHY */ ++#define H3_EPHY_ADDR_SHIFT 20 ++#define H3_EPHY_LED_POL BIT(17) /* 1: active low, 0: active high */ ++#define H3_EPHY_SHUTDOWN BIT(16) /* 1: shutdown, 0: power up */ ++#define H3_EPHY_SELECT BIT(15) /* 1: internal PHY, 0: external PHY */ ++#define H3_EPHY_DEFAULT_VALUE 0x58000 ++#define H3_EPHY_DEFAULT_MASK GENMASK(31, 15) ++ ++/* H3/A64 specific bits */ ++#define SC_RMII_EN BIT(13) /* 1: enable RMII (overrides EPIT) */ ++ ++/* Generic system control EMAC_CLK bits */ ++#define SC_ETXDC_MASK GENMASK(2, 0) ++#define SC_ETXDC_SHIFT 10 ++#define SC_ERXDC_MASK GENMASK(4, 0) ++#define SC_ERXDC_SHIFT 5 ++#define SC_EPIT BIT(2) /* 1: RGMII, 0: MII */ ++#define SC_ETCS_MASK GENMASK(1, 0) ++#define SC_ETCS_MII 0x0 ++#define SC_ETCS_EXT_GMII 0x1 ++#define SC_ETCS_INT_GMII 0x2 ++ ++static int sun8i_emac_set_syscon_ephy(struct net_device *ndev, u32 *reg) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct device_node *node = priv->dev->of_node; ++ int ret; ++ ++ *reg &= ~H3_EPHY_DEFAULT_MASK; ++ *reg |= H3_EPHY_DEFAULT_VALUE; ++ ++ if (!priv->use_internal_phy) { ++ /* switch to external PHY interface */ ++ *reg &= ~H3_EPHY_SELECT; ++ return 0; ++ } ++ ++ if (priv->phy_interface != PHY_INTERFACE_MODE_MII) { ++ netdev_warn(ndev, ++ "Internal PHY requested, forcing MII mode.\n"); ++ priv->phy_interface = PHY_INTERFACE_MODE_MII; ++ } ++ ++ *reg |= H3_EPHY_SELECT; ++ *reg &= ~H3_EPHY_SHUTDOWN; ++ ++ if (of_property_read_bool(node, "allwinner,leds-active-low")) ++ *reg |= H3_EPHY_LED_POL; ++ ++ ret = of_mdio_parse_addr(priv->dev, priv->phy_node); ++ if (ret < 0) ++ return ret; ++ ++ /* of_mdio_parse_addr returns a valid (0 ~ 31) PHY ++ * address. No need to mask it again. ++ */ ++ *reg |= ret << H3_EPHY_ADDR_SHIFT; ++ ++ return 0; ++} ++ ++static int sun8i_emac_set_syscon(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct device_node *node = priv->dev->of_node; ++ int ret; ++ u32 reg, val; ++ ++ reg = readl(priv->syscon); ++ ++ if (priv->variant == H3_EMAC) { ++ ret = sun8i_emac_set_syscon_ephy(ndev, ®); ++ if (ret) ++ return ret; ++ } ++ ++ if (!of_property_read_u32(node, "allwinner,tx-delay", &val)) { ++ if (val <= SC_ETXDC_MASK) { ++ reg &= ~(SC_ETXDC_MASK << SC_ETXDC_SHIFT); ++ reg |= (val << SC_ETXDC_SHIFT); ++ } else { ++ netdev_warn(ndev, "invalid TX clock delay: %d\n", val); ++ } ++ } ++ ++ if (!of_property_read_u32(node, "allwinner,rx-delay", &val)) { ++ if (val <= SC_ERXDC_MASK) { ++ reg &= ~(SC_ERXDC_MASK << SC_ERXDC_SHIFT); ++ reg |= (val << SC_ERXDC_SHIFT); ++ } else { ++ netdev_warn(ndev, "invalid RX clock delay: %d\n", val); ++ } ++ } ++ ++ /* Clear interface mode bits */ ++ reg &= ~(SC_ETCS_MASK | SC_EPIT); ++ if (priv->variant == H3_EMAC || priv->variant == A64_EMAC) ++ reg &= ~SC_RMII_EN; ++ ++ switch (priv->phy_interface) { ++ case PHY_INTERFACE_MODE_MII: ++ /* default */ ++ break; ++ case PHY_INTERFACE_MODE_RGMII: ++ reg |= SC_EPIT | SC_ETCS_INT_GMII; ++ break; ++ case PHY_INTERFACE_MODE_RMII: ++ if (priv->variant == H3_EMAC || priv->variant == A64_EMAC) { ++ reg |= SC_RMII_EN | SC_ETCS_EXT_GMII; ++ break; ++ } ++ /* RMII not supported on A83T */ ++ default: ++ netdev_err(ndev, "unsupported interface mode: %s", ++ phy_modes(priv->phy_interface)); ++ return -EINVAL; ++ } ++ ++ writel(reg, priv->syscon); ++ ++ return 0; ++} ++ ++static void sun8i_emac_unset_syscon(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ u32 reg = 0; ++ ++ if (priv->variant == H3_EMAC) ++ reg = H3_EPHY_DEFAULT_VALUE; ++ ++ writel(reg, priv->syscon); ++} ++ ++static void sun8i_emac_set_mdc(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ unsigned long rate; ++ u32 reg; ++ ++ rate = clk_get_rate(priv->ahb_clk); ++ if (rate > 160000000) ++ reg = 0x3 << 20; /* AHB / 128 */ ++ else if (rate > 80000000) ++ reg = 0x2 << 20; /* AHB / 64 */ ++ else if (rate > 40000000) ++ reg = 0x1 << 20; /* AHB / 32 */ ++ else ++ reg = 0x0 << 20; /* AHB / 16 */ ++ netif_dbg(priv, link, ndev, "MDC auto : %x\n", reg); ++ writel(reg, priv->base + SUN8I_EMAC_MDIO_CMD); ++} ++ ++static int sun8i_emac_init(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct device_node *node = priv->dev->of_node; ++ const u8 *addr; ++ int ret; ++ ++ /* Try to get MAC address from DT, or assign a random one */ ++ addr = of_get_mac_address(node); ++ if (addr) ++ ether_addr_copy(ndev->dev_addr, addr); ++ else ++ eth_hw_addr_random(ndev); ++ ++ priv->phy_node = of_parse_phandle(node, "phy", 0); ++ if (!priv->phy_node) { ++ netdev_err(ndev, "no associated PHY\n"); ++ return -ENODEV; ++ } ++ ++ priv->phy_interface = of_get_phy_mode(node); ++ if (priv->phy_interface < 0) { ++ netdev_err(ndev, "PHY interface mode node unspecified\n"); ++ return priv->phy_interface; ++ } ++ ++ /* Set interface mode (and configure internal PHY on H3) */ ++ ret = sun8i_emac_set_syscon(ndev); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(priv->ahb_clk); ++ if (ret) { ++ netdev_err(ndev, "Could not enable ahb clock\n"); ++ goto err_clk; ++ } ++ ++ if (priv->rst) { ++ ret = reset_control_deassert(priv->rst); ++ if (ret) { ++ netdev_err(ndev, "Could not deassert reset\n"); ++ goto err_reset; ++ } ++ } ++ ++ if (priv->ephy_clk) { ++ ret = clk_prepare_enable(priv->ephy_clk); ++ if (ret) { ++ netdev_err(ndev, "Could not enable EPHY clock\n"); ++ goto err_ephy_clk; ++ } ++ } ++ ++ if (priv->rst_ephy) { ++ ret = reset_control_deassert(priv->rst_ephy); ++ if (ret) { ++ netdev_err(ndev, "Could not deassert EPHY reset\n"); ++ goto err_ephy_reset; ++ } ++ } ++ ++ sun8i_emac_set_mdc(ndev); ++ ++ ret = sun8i_emac_mdio_register(ndev); ++ if (ret) ++ goto err_mdio_register; ++ ++ return 0; ++ ++err_mdio_register: ++ if (priv->rst_ephy) ++ reset_control_assert(priv->rst_ephy); ++err_ephy_reset: ++ if (priv->ephy_clk) ++ clk_disable_unprepare(priv->ephy_clk); ++err_ephy_clk: ++ if (priv->rst) ++ reset_control_assert(priv->rst); ++err_reset: ++ clk_disable_unprepare(priv->ahb_clk); ++err_clk: ++ sun8i_emac_unset_syscon(ndev); ++ return ret; ++} ++ ++static void sun8i_emac_uninit(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ ++ mdiobus_unregister(priv->mdio); ++ ++ if (priv->rst_ephy) ++ reset_control_assert(priv->rst_ephy); ++ ++ if (priv->ephy_clk) ++ clk_disable_unprepare(priv->ephy_clk); ++ ++ if (priv->rst) ++ reset_control_assert(priv->rst); ++ ++ clk_disable_unprepare(priv->ahb_clk); ++ ++ sun8i_emac_unset_syscon(ndev); ++} ++ ++static int sun8i_emac_mdio_probe(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct phy_device *phydev = NULL; ++ ++ phydev = of_phy_connect(ndev, priv->phy_node, &sun8i_emac_adjust_link, ++ 0, priv->phy_interface); ++ ++ if (!phydev) { ++ netdev_err(ndev, "Could not attach to PHY\n"); ++ return -ENODEV; ++ } ++ ++ phy_attached_info(phydev); ++ ++ /* mask with MAC supported features */ ++ phydev->supported &= PHY_GBIT_FEATURES; ++ phydev->advertising = phydev->supported; ++ ++ priv->link = 0; ++ priv->speed = 0; ++ priv->duplex = -1; ++ ++ return 0; ++} ++ ++static int sun8i_emac_open(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ int err; ++ u32 v; ++ struct dma_desc *ddesc; ++ int i; ++ ++ if (nbdesc_tx < MAX_SKB_FRAGS + 1) { ++ dev_err(priv->dev, "The number of TX descriptors is too low"); ++ return -EINVAL; ++ } ++ ++ err = sun8i_emac_mdio_probe(ndev); ++ if (err) ++ return err; ++ ++ /* Do SOFT RST */ ++ v = readl(priv->base + SUN8I_EMAC_BASIC_CTL1); ++ writel(v | 0x01, priv->base + SUN8I_EMAC_BASIC_CTL1); ++ ++ err = readl_poll_timeout(priv->base + SUN8I_EMAC_BASIC_CTL1, v, ++ !(v & 0x01), 100, 10000); ++ if (err) { ++ dev_err(priv->dev, "EMAC reset timeout\n"); ++ err = -EFAULT; ++ goto err_emac_timeout; ++ } ++ ++ sun8i_emac_set_mdc(ndev); ++ ++ /* DMA */ ++ v = (8 << 24);/* burst len */ ++ writel(v, priv->base + SUN8I_EMAC_BASIC_CTL1); ++ ++ /* it seems that hardware complety ignore interrupt configuration */ ++#define RX_INT BIT(8) ++#define TX_INT BIT(0) ++#define TX_UNF_INT BIT(4) ++ writel(RX_INT | TX_INT | TX_UNF_INT, priv->base + SUN8I_EMAC_INT_EN); ++ ++ v = readl(priv->base + SUN8I_EMAC_RX_CTL0); ++ /* CHECK_CRC */ ++ if (ndev->features & NETIF_F_RXCSUM) ++ v |= SUN8I_EMAC_RX_DO_CRC; ++ else ++ v &= ~SUN8I_EMAC_RX_DO_CRC; ++ /* STRIP_FCS */ ++ if (ndev->features & NETIF_F_RXFCS) ++ v &= ~SUN8I_EMAC_RX_STRIP_FCS; ++ else ++ v |= SUN8I_EMAC_RX_STRIP_FCS; ++ writel(v, priv->base + SUN8I_EMAC_RX_CTL0); ++ ++ v = readl(priv->base + SUN8I_EMAC_TX_CTL1); ++ /* TX_MD Transmission starts after a full frame located in TX DMA FIFO*/ ++ v |= BIT(1); ++ writel(v, priv->base + SUN8I_EMAC_TX_CTL1); ++ ++ v = readl(priv->base + SUN8I_EMAC_RX_CTL1); ++ /* RX_MD RX DMA reads data from RX DMA FIFO to host memory after a ++ * complete frame has been written to RX DMA FIFO ++ */ ++ v |= BIT(1); ++ writel(v, priv->base + SUN8I_EMAC_RX_CTL1); ++ ++ sun8i_emac_set_macaddr(priv, ndev->dev_addr, 0); ++ ++ priv->tx_slot = 0; ++ priv->tx_dirty = 0; ++ priv->rx_dirty = 0; ++ ++ priv->rx_sk = kcalloc(nbdesc_rx, sizeof(struct sk_buff *), GFP_KERNEL); ++ if (!priv->rx_sk) { ++ err = -ENOMEM; ++ goto rx_sk_error; ++ } ++ priv->tx_sk = kcalloc(nbdesc_tx, sizeof(struct sk_buff *), GFP_KERNEL); ++ if (!priv->tx_sk) { ++ err = -ENOMEM; ++ goto tx_sk_error; ++ } ++ priv->tx_map = kcalloc(nbdesc_tx, sizeof(int), GFP_KERNEL); ++ if (!priv->tx_map) { ++ err = -ENOMEM; ++ goto tx_map_error; ++ } ++ ++ priv->dd_rx = dma_alloc_coherent(priv->dev, ++ nbdesc_rx * sizeof(struct dma_desc), ++ &priv->dd_rx_phy, ++ GFP_KERNEL); ++ if (!priv->dd_rx) { ++ dev_err(priv->dev, "ERROR: cannot DMA RX"); ++ err = -ENOMEM; ++ goto dma_rx_error; ++ } ++ memset(priv->dd_rx, 0, nbdesc_rx * sizeof(struct dma_desc)); ++ ddesc = priv->dd_rx; ++ for (i = 0; i < nbdesc_rx; i++) { ++ sun8i_emac_rx_sk(ndev, i); ++ ddesc->next = (u32)priv->dd_rx_phy + (i + 1) ++ * sizeof(struct dma_desc); ++ ddesc++; ++ } ++ /* last descriptor point back to first one */ ++ ddesc--; ++ ddesc->next = (u32)priv->dd_rx_phy; ++ ++ priv->dd_tx = dma_alloc_coherent(priv->dev, ++ nbdesc_tx * sizeof(struct dma_desc), ++ &priv->dd_tx_phy, ++ GFP_KERNEL); ++ if (!priv->dd_tx) { ++ dev_err(priv->dev, "ERROR: cannot DMA TX"); ++ err = -ENOMEM; ++ goto dma_tx_error; ++ } ++ memset(priv->dd_tx, 0, nbdesc_tx * sizeof(struct dma_desc)); ++ ddesc = priv->dd_tx; ++ for (i = 0; i < nbdesc_tx; i++) { ++ ddesc->status = DCLEAN; ++ ddesc->st = 0; ++ ddesc->next = (u32)(priv->dd_tx_phy + (i + 1) ++ * sizeof(struct dma_desc)); ++ ddesc++; ++ } ++ /* last descriptor point back to first one */ ++ ddesc--; ++ ddesc->next = (u32)priv->dd_tx_phy; ++ i--; ++ ++ if (ndev->phydev) ++ phy_start(ndev->phydev); ++ ++ /* write start of rx ring descriptor */ ++ writel(priv->dd_rx_phy, priv->base + SUN8I_EMAC_RX_DESC_LIST); ++ /* start RX DMA */ ++ v = readl(priv->base + SUN8I_EMAC_RX_CTL1); ++ v |= BIT(30); ++ writel(v, priv->base + SUN8I_EMAC_RX_CTL1); ++ ++ /* write start of tx ring descriptor */ ++ writel(priv->dd_tx_phy, priv->base + SUN8I_EMAC_TX_DESC_LIST); ++ /* start TX DMA */ ++ v = readl(priv->base + SUN8I_EMAC_TX_CTL1); ++ v |= BIT(30); ++ writel(v, priv->base + SUN8I_EMAC_TX_CTL1); ++ ++ /* activate transmitter */ ++ v = readl(priv->base + SUN8I_EMAC_TX_CTL0); ++ v |= BIT(31); ++ writel(v, priv->base + SUN8I_EMAC_TX_CTL0); ++ ++ /* activate receiver */ ++ v = readl(priv->base + SUN8I_EMAC_RX_CTL0); ++ v |= BIT(31); ++ writel(v, priv->base + SUN8I_EMAC_RX_CTL0); ++ ++ netif_start_queue(ndev); ++ ++ return 0; ++dma_tx_error: ++ dma_free_coherent(priv->dev, nbdesc_rx * sizeof(struct dma_desc), ++ priv->dd_rx, priv->dd_rx_phy); ++dma_rx_error: ++ kfree(priv->tx_map); ++tx_map_error: ++ kfree(priv->tx_sk); ++tx_sk_error: ++ kfree(priv->rx_sk); ++rx_sk_error: ++err_emac_timeout: ++ phy_disconnect(ndev->phydev); ++ return err; ++} ++ ++/* Clean the tx ring of any accepted skb for xmit */ ++static void sun8i_emac_tx_clean(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ int i; ++ struct dma_desc *ddesc; ++ int frame_len; ++ ++ for (i = 0; i < nbdesc_tx; i++) { ++ if (priv->tx_sk[i]) { ++ ddesc = priv->dd_tx + i; ++ frame_len = ddesc->st & 0x3FFF; ++ switch (priv->tx_map[i]) { ++ case MAP_SINGLE: ++ dma_unmap_single(priv->dev, ddesc->buf_addr, ++ frame_len, DMA_TO_DEVICE); ++ break; ++ case MAP_PAGE: ++ dma_unmap_page(priv->dev, ddesc->buf_addr, ++ frame_len, DMA_TO_DEVICE); ++ break; ++ default: ++ dev_err(priv->dev, "Trying to free an empty slot\n"); ++ continue; ++ } ++ dev_kfree_skb_any(priv->tx_sk[i]); ++ priv->tx_sk[i] = NULL; ++ ddesc->st = 0; ++ ddesc->status = DCLEAN; ++ } ++ } ++} ++ ++static int sun8i_emac_stop(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ int i; ++ struct dma_desc *ddesc; ++ ++ /* Stop receiver */ ++ writel(0, priv->base + SUN8I_EMAC_RX_CTL0); ++ writel(0, priv->base + SUN8I_EMAC_RX_CTL1); ++ /* Stop transmitter */ ++ writel(0, priv->base + SUN8I_EMAC_TX_CTL0); ++ writel(0, priv->base + SUN8I_EMAC_TX_CTL1); ++ ++ netif_stop_queue(ndev); ++ netif_carrier_off(ndev); ++ ++ phy_stop(ndev->phydev); ++ phy_disconnect(ndev->phydev); ++ ++ /* clean RX ring */ ++ for (i = 0; i < nbdesc_rx; i++) ++ if (priv->rx_sk[i]) { ++ ddesc = priv->dd_rx + i; ++ dma_unmap_single(priv->dev, ddesc->buf_addr, ++ DESC_BUF_MAX, DMA_FROM_DEVICE); ++ dev_kfree_skb_any(priv->rx_sk[i]); ++ priv->rx_sk[i] = NULL; ++ } ++ sun8i_emac_tx_clean(ndev); ++ ++ kfree(priv->rx_sk); ++ kfree(priv->tx_sk); ++ kfree(priv->tx_map); ++ ++ dma_free_coherent(priv->dev, nbdesc_rx * sizeof(struct dma_desc), ++ priv->dd_rx, priv->dd_rx_phy); ++ dma_free_coherent(priv->dev, nbdesc_tx * sizeof(struct dma_desc), ++ priv->dd_tx, priv->dd_tx_phy); ++ ++ return 0; ++} ++ ++static netdev_tx_t sun8i_emac_xmit(struct sk_buff *skb, struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct dma_desc *ddesc; ++ struct dma_desc *first; ++ int i = 0, rbd_first; ++ unsigned int len, fraglen; ++ u32 v; ++ int n; ++ int nf; ++ const skb_frag_t *frag; ++ int do_csum = 0; ++ ++ len = skb_headlen(skb); ++ if (len < ETH_ZLEN) { ++ if (skb_padto(skb, ETH_ZLEN)) ++ return NETDEV_TX_OK; ++ len = ETH_ZLEN; ++ } ++ n = skb_shinfo(skb)->nr_frags; ++ ++ if (skb->ip_summed == CHECKSUM_PARTIAL) { ++ do_csum = 1; ++ priv->estats.tx_hw_csum++; ++ } ++ netif_dbg(priv, tx_queued, ndev, "%s len=%u skblen=%u %x\n", __func__, ++ len, skb->len, ++ (skb->ip_summed == CHECKSUM_PARTIAL)); ++ ++ spin_lock(&priv->tx_lock); ++ ++ /* check for contigous space ++ * We need at least 1(skb->data) + n(numfrags) + 1(one clean slot) ++ */ ++ if (rb_tx_numfreedesc(ndev) < n + 2) { ++ dev_err_ratelimited(priv->dev, "BUG!: TX is full %d %d\n", ++ priv->tx_dirty, priv->tx_slot); ++ netif_stop_queue(ndev); ++ spin_unlock(&priv->tx_lock); ++ return NETDEV_TX_BUSY; ++ } ++ i = priv->tx_slot; ++ ++ ddesc = priv->dd_tx + i; ++ first = priv->dd_tx + i; ++ rbd_first = i; ++ ++ priv->tx_slot = (i + 1 + n) % nbdesc_tx; ++ ++ ddesc->buf_addr = dma_map_single(priv->dev, skb->data, len, ++ DMA_TO_DEVICE); ++ if (dma_mapping_error(priv->dev, ddesc->buf_addr)) { ++ dev_err(priv->dev, "ERROR: Cannot dmamap buf\n"); ++ goto xmit_error; ++ } ++ priv->tx_map[i] = MAP_SINGLE; ++ priv->tx_sk[i] = skb; ++ priv->ndev->stats.tx_packets++; ++ priv->ndev->stats.tx_bytes += len; ++ ++ ddesc->st = len; ++ /* undocumented bit that make it works */ ++ ddesc->st |= BIT(24); ++ if (do_csum) ++ ddesc->st |= SUN8I_EMAC_TX_DO_CRC; ++ ++ /* handle fragmented skb, one descriptor per fragment */ ++ for (nf = 0; nf < n; nf++) { ++ frag = &skb_shinfo(skb)->frags[nf]; ++ rb_inc(&i, nbdesc_tx); ++ priv->tx_sk[i] = skb; ++ ddesc = priv->dd_tx + i; ++ fraglen = skb_frag_size(frag); ++ ddesc->st = fraglen; ++ priv->ndev->stats.tx_bytes += fraglen; ++ ddesc->st |= BIT(24); ++ if (do_csum) ++ ddesc->st |= SUN8I_EMAC_TX_DO_CRC; ++ ++ ddesc->buf_addr = skb_frag_dma_map(priv->dev, frag, 0, ++ fraglen, DMA_TO_DEVICE); ++ if (dma_mapping_error(priv->dev, ddesc->buf_addr)) { ++ dev_err(priv->dev, "DMA MAP ERROR\n"); ++ goto xmit_error; ++ } ++ priv->tx_map[i] = MAP_PAGE; ++ ddesc->status = BIT(31); ++ } ++ ++ /* frame end */ ++ ddesc->st |= BIT(30); ++ /* We want an interrupt after transmission */ ++ ddesc->st |= BIT(31); ++ ++ rb_inc(&i, nbdesc_tx); ++ ++ /* frame begin */ ++ first->st |= BIT(29); ++ first->status = BIT(31); ++ priv->tx_slot = i; ++ ++ /* Trying to optimize this (recording DMA start/stop) seems ++ * to lead to errors. So we always start DMA. ++ */ ++ v = readl(priv->base + SUN8I_EMAC_TX_CTL1); ++ /* TX DMA START */ ++ v |= BIT(31); ++ /* Start an run DMA */ ++ v |= BIT(30); ++ writel(v, priv->base + SUN8I_EMAC_TX_CTL1); ++ ++ if (rb_tx_numfreedesc(ndev) < MAX_SKB_FRAGS + 1) { ++ netif_stop_queue(ndev); ++ priv->estats.tx_stop_queue++; ++ } ++ priv->estats.tx_used_desc = rb_tx_numfreedesc(ndev); ++ ++ spin_unlock(&priv->tx_lock); ++ ++ return NETDEV_TX_OK; ++ ++xmit_error: ++ /* destroy skb and return TX OK Documentation/DMA-API-HOWTO.txt */ ++ /* clean descritors from rbd_first to i */ ++ ddesc->st = 0; ++ ddesc->status = DCLEAN; ++ do { ++ ddesc = priv->dd_tx + rbd_first; ++ ddesc->st = 0; ++ ddesc->status = DCLEAN; ++ rb_inc(&rbd_first, nbdesc_tx); ++ } while (rbd_first != i); ++ spin_unlock(&priv->tx_lock); ++ dev_kfree_skb_any(skb); ++ return NETDEV_TX_OK; ++} ++ ++static int sun8i_emac_change_mtu(struct net_device *ndev, int new_mtu) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ int max_mtu; ++ ++ dev_info(priv->dev, "%s set MTU to %d\n", __func__, new_mtu); ++ ++ if (netif_running(ndev)) { ++ dev_err(priv->dev, "%s: must be stopped to change its MTU\n", ++ ndev->name); ++ return -EBUSY; ++ } ++ ++ max_mtu = SKB_MAX_HEAD(NET_SKB_PAD + NET_IP_ALIGN); ++ ++ if ((new_mtu < 68) || (new_mtu > max_mtu)) { ++ dev_err(priv->dev, "%s: invalid MTU, max MTU is: %d\n", ++ ndev->name, max_mtu); ++ return -EINVAL; ++ } ++ ++ ndev->mtu = new_mtu; ++ netdev_update_features(ndev); ++ return 0; ++} ++ ++static netdev_features_t sun8i_emac_fix_features(struct net_device *ndev, ++ netdev_features_t features) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ ++ netif_dbg(priv, drv, ndev, "%s %llx\n", __func__, features); ++ return features; ++} ++ ++static int sun8i_emac_set_features(struct net_device *ndev, ++ netdev_features_t features) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ u32 v; ++ ++ v = readl(priv->base + SUN8I_EMAC_BASIC_CTL0); ++ if (features & NETIF_F_LOOPBACK && netif_running(ndev)) { ++ netif_info(priv, hw, ndev, "Set loopback features"); ++ v |= BIT(1); ++ } else { ++ netif_info(priv, hw, ndev, "Unset loopback features"); ++ v &= ~BIT(1); ++ } ++ writel(v, priv->base + SUN8I_EMAC_BASIC_CTL0); ++ ++ /* TODO do func setup_receiver */ ++ v = readl(priv->base + SUN8I_EMAC_RX_CTL0); ++ if (features & NETIF_F_RXCSUM) { ++ v |= SUN8I_EMAC_RX_DO_CRC; ++ netif_info(priv, hw, ndev, "Doing RX CRC check by hardware"); ++ } else { ++ v &= ~SUN8I_EMAC_RX_DO_CRC; ++ netif_info(priv, hw, ndev, "No RX CRC check by hardware"); ++ } ++ if (features & NETIF_F_RXFCS) { ++ v &= ~SUN8I_EMAC_RX_STRIP_FCS; ++ netif_info(priv, hw, ndev, "Keep FCS"); ++ } else { ++ v |= SUN8I_EMAC_RX_STRIP_FCS; ++ netif_info(priv, hw, ndev, "Strip FCS"); ++ } ++ writel(v, priv->base + SUN8I_EMAC_RX_CTL0); ++ ++ netif_dbg(priv, drv, ndev, "%s %llx %x\n", __func__, features, v); ++ ++ return 0; ++} ++ ++static void sun8i_emac_set_rx_mode(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ u32 v = 0; ++ int i = 0; ++ struct netdev_hw_addr *ha; ++ ++ /* Receive all multicast frames */ ++ v |= BIT(16); ++ /* Receive all control frames */ ++ v |= BIT(13); ++ if (ndev->flags & IFF_PROMISC) ++ v |= BIT(1); ++ if (netdev_uc_count(ndev) > 7) { ++ v |= BIT(1); ++ } else { ++ netdev_for_each_uc_addr(ha, ndev) { ++ i++; ++ sun8i_emac_set_macaddr(priv, ha->addr, i); ++ } ++ } ++ writel(v, priv->base + SUN8I_EMAC_RX_FRM_FLT); ++} ++ ++/* TODO Need a way to test that case */ ++static void sun8i_emac_tx_timeout(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ u32 v; ++ ++ dev_info(priv->dev, "%s\n", __func__); ++ netif_stop_queue(ndev); ++ ++ spin_lock(&priv->tx_lock); ++ ++ v = readl(priv->base + SUN8I_EMAC_TX_CTL0); ++ v &= ~BIT(31); ++ writel(v, priv->base + SUN8I_EMAC_TX_CTL0); ++ ++ v = readl(priv->base + SUN8I_EMAC_TX_CTL1); ++ v &= ~BIT(31); ++ v &= ~BIT(30); ++ writel(v, priv->base + SUN8I_EMAC_TX_CTL1); ++ ++ sun8i_emac_tx_clean(ndev); ++ priv->tx_slot = 0; ++ priv->tx_dirty = 0; ++ ++ /* write start of tx ring descriptor */ ++ writel(priv->dd_tx_phy, priv->base + SUN8I_EMAC_TX_DESC_LIST); ++ ++ v = readl(priv->base + SUN8I_EMAC_TX_CTL0); ++ v |= BIT(31); ++ writel(v, priv->base + SUN8I_EMAC_TX_CTL0); ++ ++ v = readl(priv->base + SUN8I_EMAC_TX_CTL1); ++ v |= BIT(31); ++ v |= BIT(30); ++ writel(v, priv->base + SUN8I_EMAC_TX_CTL1); ++ ++ spin_unlock(&priv->tx_lock); ++ ++ netdev_reset_queue(ndev); ++ ++ ndev->stats.tx_errors++; ++ netif_wake_queue(ndev); ++} ++ ++static int sun8i_emac_ioctl(struct net_device *ndev, struct ifreq *rq, int cmd) ++{ ++ struct phy_device *phydev = ndev->phydev; ++ ++ if (!netif_running(ndev)) ++ return -EINVAL; ++ ++ if (!phydev) ++ return -ENODEV; ++ ++ return phy_mii_ioctl(phydev, rq, cmd); ++} ++ ++static int sun8i_emac_check_if_running(struct net_device *ndev) ++{ ++ if (!netif_running(ndev)) ++ return -EBUSY; ++ return 0; ++} ++ ++static int sun8i_emac_get_sset_count(struct net_device *ndev, int sset) ++{ ++ switch (sset) { ++ case ETH_SS_STATS: ++ return ARRAY_SIZE(estats_str); ++ } ++ return -EOPNOTSUPP; ++} ++ ++static int sun8i_emac_ethtool_get_settings(struct net_device *ndev, ++ struct ethtool_cmd *cmd) ++{ ++ struct phy_device *phy = ndev->phydev; ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ ++ if (!phy) { ++ netdev_err(ndev, "%s: %s: PHY is not registered\n", ++ __func__, ndev->name); ++ return -ENODEV; ++ } ++ ++ if (!netif_running(ndev)) { ++ dev_err(priv->dev, "interface disabled: we cannot track link speed / duplex setting\n"); ++ return -EBUSY; ++ } ++ ++ cmd->transceiver = XCVR_INTERNAL; ++ return phy_ethtool_gset(phy, cmd); ++} ++ ++static int sun8i_emac_ethtool_set_settings(struct net_device *ndev, ++ struct ethtool_cmd *cmd) ++{ ++ struct phy_device *phy = ndev->phydev; ++ ++ return phy_ethtool_sset(phy, cmd); ++} ++ ++static void sun8i_emac_ethtool_getdrvinfo(struct net_device *ndev, ++ struct ethtool_drvinfo *info) ++{ ++ strlcpy(info->driver, "sun8i_emac", sizeof(info->driver)); ++ strcpy(info->version, "00"); ++ info->fw_version[0] = '\0'; ++} ++ ++static void sun8i_emac_ethtool_stats(struct net_device *ndev, ++ struct ethtool_stats *dummy, u64 *data) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ ++ memcpy(data, &priv->estats, ++ sun8i_emac_get_sset_count(ndev, ETH_SS_STATS) * sizeof(u64)); ++} ++ ++static void sun8i_emac_ethtool_strings(struct net_device *dev, u32 stringset, ++ u8 *buffer) ++{ ++ switch (stringset) { ++ case ETH_SS_STATS: ++ memcpy(buffer, &estats_str, ++ sun8i_emac_get_sset_count(dev, ETH_SS_STATS) * ++ sizeof(struct ethtool_str)); ++ break; ++ } ++} ++ ++static u32 sun8i_emac_ethtool_getmsglevel(struct net_device *ndev) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ ++ return priv->msg_enable; ++} ++ ++static void sun8i_emac_ethtool_setmsglevel(struct net_device *ndev, u32 level) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ ++ priv->msg_enable = level; ++} ++ ++static void sun8i_emac_get_pauseparam(struct net_device *ndev, ++ struct ethtool_pauseparam *pause) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ ++ pause->rx_pause = 0; ++ pause->tx_pause = 0; ++ pause->autoneg = ndev->phydev->autoneg; ++ ++ if (priv->flow_ctrl & FLOW_RX) ++ pause->rx_pause = 1; ++ if (priv->flow_ctrl & FLOW_TX) ++ pause->tx_pause = 1; ++} ++ ++static int sun8i_emac_set_pauseparam(struct net_device *ndev, ++ struct ethtool_pauseparam *pause) ++{ ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ struct phy_device *phy = ndev->phydev; ++ int new_pause = 0; ++ int ret = 0; ++ ++ if (pause->rx_pause) ++ new_pause |= FLOW_RX; ++ if (pause->tx_pause) ++ new_pause |= FLOW_TX; ++ ++ priv->flow_ctrl = new_pause; ++ phy->autoneg = pause->autoneg; ++ ++ if (phy->autoneg) { ++ if (netif_running(ndev)) ++ ret = phy_start_aneg(phy); ++ } else { ++ sun8i_emac_flow_ctrl(priv, phy->duplex, priv->flow_ctrl, ++ priv->pause); ++ } ++ return ret; ++} ++ ++static const struct ethtool_ops sun8i_emac_ethtool_ops = { ++ .begin = sun8i_emac_check_if_running, ++ .get_settings = sun8i_emac_ethtool_get_settings, ++ .set_settings = sun8i_emac_ethtool_set_settings, ++ .get_link = ethtool_op_get_link, ++ .get_pauseparam = sun8i_emac_get_pauseparam, ++ .set_pauseparam = sun8i_emac_set_pauseparam, ++ .get_ethtool_stats = sun8i_emac_ethtool_stats, ++ .get_strings = sun8i_emac_ethtool_strings, ++ .get_wol = NULL, ++ .set_wol = NULL, ++ .get_sset_count = sun8i_emac_get_sset_count, ++ .get_drvinfo = sun8i_emac_ethtool_getdrvinfo, ++ .get_msglevel = sun8i_emac_ethtool_getmsglevel, ++ .set_msglevel = sun8i_emac_ethtool_setmsglevel, ++}; ++ ++static const struct net_device_ops sun8i_emac_netdev_ops = { ++ .ndo_init = sun8i_emac_init, ++ .ndo_uninit = sun8i_emac_uninit, ++ .ndo_open = sun8i_emac_open, ++ .ndo_start_xmit = sun8i_emac_xmit, ++ .ndo_stop = sun8i_emac_stop, ++ .ndo_change_mtu = sun8i_emac_change_mtu, ++ .ndo_fix_features = sun8i_emac_fix_features, ++ .ndo_set_rx_mode = sun8i_emac_set_rx_mode, ++ .ndo_tx_timeout = sun8i_emac_tx_timeout, ++ .ndo_do_ioctl = sun8i_emac_ioctl, ++ .ndo_set_mac_address = eth_mac_addr, ++ .ndo_set_features = sun8i_emac_set_features, ++}; ++ ++static irqreturn_t sun8i_emac_dma_interrupt(int irq, void *dev_id) ++{ ++ struct net_device *ndev = (struct net_device *)dev_id; ++ struct sun8i_emac_priv *priv = netdev_priv(ndev); ++ u32 v, u; ++ ++ v = readl(priv->base + SUN8I_EMAC_INT_STA); ++ ++ netif_info(priv, intr, ndev, "%s %x\n", __func__, v); ++ ++ /* When this bit is asserted, a frame transmission is completed. */ ++ if (v & BIT(0)) ++ sun8i_emac_complete_xmit(ndev); ++ ++ /* When this bit is asserted, the TX DMA FSM is stopped. */ ++ if (v & BIT(1)) ++ priv->estats.tx_dma_stop++; ++ ++ /* When this asserted, the TX DMA can not acquire next TX descriptor ++ * and TX DMA FSM is suspended. ++ */ ++ if (v & BIT(2)) ++ priv->estats.tx_dma_ua++; ++ ++ if (v & BIT(3)) ++ netif_dbg(priv, intr, ndev, "Unhandled interrupt TX TIMEOUT\n"); ++ ++ if (v & BIT(4)) { ++ netif_dbg(priv, intr, ndev, "Unhandled interrupt TX underflow\n"); ++ priv->estats.tx_underflow_int++; ++ } ++ ++ /* When this bit asserted , the frame is transmitted to FIFO totally. */ ++ if (v & BIT(5)) { ++ netif_dbg(priv, intr, ndev, "Unhandled interrupt TX_EARLY_INT\n"); ++ priv->estats.tx_early_int++; ++ } ++ ++ /* When this bit is asserted, a frame reception is completed */ ++ if (v & BIT(8)) ++ sun8i_emac_receive_all(ndev); ++ ++ /* When this asserted, the RX DMA can not acquire next TX descriptor ++ * and RX DMA FSM is suspended. ++ */ ++ if (v & BIT(9)) { ++ u = readl(priv->base + SUN8I_EMAC_RX_CTL1); ++ dev_info(priv->dev, "Re-run RX DMA %x\n", u); ++ writel(u | BIT(31), priv->base + SUN8I_EMAC_RX_CTL1); ++ priv->estats.rx_dma_ua++; ++ } ++ ++ if (v & BIT(10)) { ++ netif_dbg(priv, intr, ndev, "Unhandled interrupt RX_DMA_STOPPED_INT\n"); ++ priv->estats.rx_dma_stop++; ++ } ++ if (v & BIT(11)) ++ netif_dbg(priv, intr, ndev, "Unhandled interrupt RX_TIMEOUT\n"); ++ if (v & BIT(12)) ++ netif_dbg(priv, intr, ndev, "Unhandled interrupt RX OVERFLOW\n"); ++ if (v & BIT(13)) { ++ netif_dbg(priv, intr, ndev, "Unhandled interrupt RX EARLY\n"); ++ priv->estats.rx_early_int++; ++ } ++ if (v & BIT(16)) ++ netif_dbg(priv, intr, ndev, "Unhandled interrupt RGMII\n"); ++ ++ /* the datasheet state those register as read-only ++ * but nothing work without writing to it ++ */ ++ writel(v & 0x3FFF, priv->base + SUN8I_EMAC_INT_STA); ++ ++ return IRQ_HANDLED; ++} ++ ++static int sun8i_emac_probe(struct platform_device *pdev) ++{ ++ struct resource *res; ++ struct device_node *np = pdev->dev.of_node; ++ struct sun8i_emac_priv *priv; ++ struct net_device *ndev; ++ int ret; ++ ++ ndev = alloc_etherdev(sizeof(*priv)); ++ if (!ndev) ++ return -ENOMEM; ++ ++ SET_NETDEV_DEV(ndev, &pdev->dev); ++ priv = netdev_priv(ndev); ++ platform_set_drvdata(pdev, ndev); ++ ++ priv->variant = (enum emac_variant)of_device_get_match_data(&pdev->dev); ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ priv->base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(priv->base)) { ++ ret = PTR_ERR(priv->base); ++ dev_err(&pdev->dev, "Cannot request MMIO: %d\n", ret); ++ goto probe_err; ++ } ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "syscon"); ++ priv->syscon = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(priv->syscon)) { ++ ret = PTR_ERR(priv->syscon); ++ dev_err(&pdev->dev, ++ "Cannot map system control registers: %d\n", ret); ++ goto probe_err; ++ } ++ ++ priv->irq = platform_get_irq(pdev, 0); ++ if (priv->irq < 0) { ++ ret = priv->irq; ++ dev_err(&pdev->dev, "Cannot claim IRQ: %d\n", ret); ++ goto probe_err; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, priv->irq, sun8i_emac_dma_interrupt, ++ 0, dev_name(&pdev->dev), ndev); ++ if (ret) { ++ dev_err(&pdev->dev, "Cannot request IRQ: %d\n", ret); ++ goto probe_err; ++ } ++ ++ priv->ahb_clk = devm_clk_get(&pdev->dev, "ahb"); ++ if (IS_ERR(priv->ahb_clk)) { ++ ret = PTR_ERR(priv->ahb_clk); ++ dev_err(&pdev->dev, "Cannot get AHB clock err=%d\n", ret); ++ goto probe_err; ++ } ++ ++ priv->rst = devm_reset_control_get_optional(&pdev->dev, "ahb"); ++ if (IS_ERR(priv->rst)) { ++ ret = PTR_ERR(priv->rst); ++ if (ret == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ dev_info(&pdev->dev, "no mac reset control found %d\n", ret); ++ priv->rst = NULL; ++ } ++ ++ if (priv->variant == H3_EMAC) ++ priv->use_internal_phy = ++ of_property_read_bool(np, "allwinner,use-internal-phy"); ++ ++ if (priv->use_internal_phy) { ++ priv->ephy_clk = devm_clk_get(&pdev->dev, "ephy"); ++ if (IS_ERR(priv->ephy_clk)) { ++ ret = PTR_ERR(priv->ephy_clk); ++ dev_err(&pdev->dev, "Cannot get EPHY clock err=%d\n", ++ ret); ++ goto probe_err; ++ } ++ ++ priv->rst_ephy = devm_reset_control_get_optional(&pdev->dev, ++ "ephy"); ++ if (IS_ERR(priv->rst_ephy)) { ++ ret = PTR_ERR(priv->rst_ephy); ++ if (ret == -EPROBE_DEFER) ++ goto probe_err; ++ dev_info(&pdev->dev, ++ "no EPHY reset control found %d\n", ret); ++ priv->rst_ephy = NULL; ++ } ++ } ++ ++ spin_lock_init(&priv->lock); ++ spin_lock_init(&priv->tx_lock); ++ ++ ndev->netdev_ops = &sun8i_emac_netdev_ops; ++ ndev->ethtool_ops = &sun8i_emac_ethtool_ops; ++ ++ priv->ndev = ndev; ++ priv->dev = &pdev->dev; ++ ++ ndev->base_addr = (unsigned long)priv->base; ++ ndev->irq = priv->irq; ++ ++ ndev->hw_features = NETIF_F_SG | NETIF_F_HIGHDMA; ++ ndev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_IPV6_CSUM | ++ NETIF_F_RXCSUM; ++ ndev->features |= ndev->hw_features; ++ ndev->hw_features |= NETIF_F_RXFCS; ++ ndev->hw_features |= NETIF_F_RXALL; ++ ndev->hw_features |= NETIF_F_LOOPBACK; ++ ndev->priv_flags |= IFF_UNICAST_FLT; ++ ++ ndev->watchdog_timeo = msecs_to_jiffies(5000); ++ ++ ret = register_netdev(ndev); ++ if (ret) { ++ dev_err(&pdev->dev, "ERROR: Register %s failed\n", ndev->name); ++ goto probe_err; ++ } ++ ++ return 0; ++ ++probe_err: ++ free_netdev(ndev); ++ return ret; ++} ++ ++static int sun8i_emac_remove(struct platform_device *pdev) ++{ ++ struct net_device *ndev = platform_get_drvdata(pdev); ++ ++ unregister_netdev(ndev); ++ platform_set_drvdata(pdev, NULL); ++ free_netdev(ndev); ++ ++ return 0; ++} ++ ++static const struct of_device_id sun8i_emac_of_match_table[] = { ++ { .compatible = "allwinner,sun8i-a83t-emac", ++ .data = (void *)A83T_EMAC }, ++ { .compatible = "allwinner,sun8i-h3-emac", ++ .data = (void *)H3_EMAC }, ++ { .compatible = "allwinner,sun50i-a64-emac", ++ .data = (void *)A64_EMAC }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, sun8i_emac_of_match_table); ++ ++static struct platform_driver sun8i_emac_driver = { ++ .probe = sun8i_emac_probe, ++ .remove = sun8i_emac_remove, ++ .driver = { ++ .name = "sun8i-emac", ++ .of_match_table = sun8i_emac_of_match_table, ++ }, ++}; ++ ++module_platform_driver(sun8i_emac_driver); ++ ++MODULE_DESCRIPTION("SUN8I Ethernet driver"); ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("LABBE Corentin +Date: Thu, 17 Mar 2016 00:04:24 +0800 +Subject: [PATCH] pinctrl: sunxi: Add A83T R_PIO controller + +The A83T has R_PIO pin controller, it's same as A23, execpt A83T +interrupt bit is 6th and A83T has one extra pin PL12. + +Signed-off-by: Vishnu Patekar +Acked-by: Chen-Yu Tsai +Acked-by: Rob Herring +--- + .../bindings/pinctrl/allwinner,sunxi-pinctrl.txt | 1 + + drivers/pinctrl/sunxi/Kconfig | 5 + + drivers/pinctrl/sunxi/Makefile | 1 + + drivers/pinctrl/sunxi/pinctrl-sun8i-a83t-r.c | 117 +++++++++++++++++++++ + 4 files changed, 124 insertions(+) + create mode 100644 drivers/pinctrl/sunxi/pinctrl-sun8i-a83t-r.c + +diff --git a/Documentation/devicetree/bindings/pinctrl/allwinner,sunxi-pinctrl.txt b/Documentation/devicetree/bindings/pinctrl/allwinner,sunxi-pinctrl.txt +index 6961722..56f2eb4 100644 +--- a/Documentation/devicetree/bindings/pinctrl/allwinner,sunxi-pinctrl.txt ++++ b/Documentation/devicetree/bindings/pinctrl/allwinner,sunxi-pinctrl.txt +@@ -20,6 +20,7 @@ Required properties: + "allwinner,sun9i-a80-pinctrl" + "allwinner,sun9i-a80-r-pinctrl" + "allwinner,sun8i-a83t-pinctrl" ++ "allwinner,sun8i-a83t-r-pinctrl" + "allwinner,sun8i-h3-pinctrl" + "allwinner,sun8i-h3-r-pinctrl" + "allwinner,sun50i-a64-pinctrl" +diff --git a/drivers/pinctrl/sunxi/Kconfig b/drivers/pinctrl/sunxi/Kconfig +index aaf075b..390f3e2 100644 +--- a/drivers/pinctrl/sunxi/Kconfig ++++ b/drivers/pinctrl/sunxi/Kconfig +@@ -51,6 +51,11 @@ config PINCTRL_SUN8I_A23_R + depends on RESET_CONTROLLER + select PINCTRL_SUNXI + ++config PINCTRL_SUN8I_A83T_R ++ def_bool MACH_SUN8I ++ depends on RESET_CONTROLLER ++ select PINCTRL_SUNXI ++ + config PINCTRL_SUN8I_H3 + def_bool MACH_SUN8I + select PINCTRL_SUNXI +diff --git a/drivers/pinctrl/sunxi/Makefile b/drivers/pinctrl/sunxi/Makefile +index 2d8b64e..6b8d6a2 100644 +--- a/drivers/pinctrl/sunxi/Makefile ++++ b/drivers/pinctrl/sunxi/Makefile +@@ -14,6 +14,7 @@ obj-$(CONFIG_PINCTRL_SUN8I_A23_R) += pinctrl-sun8i-a23-r.o + obj-$(CONFIG_PINCTRL_SUN8I_A33) += pinctrl-sun8i-a33.o + obj-$(CONFIG_PINCTRL_SUN50I_A64) += pinctrl-sun50i-a64.o + obj-$(CONFIG_PINCTRL_SUN8I_A83T) += pinctrl-sun8i-a83t.o ++obj-$(CONFIG_PINCTRL_SUN8I_A83T_R) += pinctrl-sun8i-a83t-r.o + obj-$(CONFIG_PINCTRL_SUN8I_H3) += pinctrl-sun8i-h3.o + obj-$(CONFIG_PINCTRL_SUN8I_H3_R) += pinctrl-sun8i-h3-r.o + obj-$(CONFIG_PINCTRL_SUN9I_A80) += pinctrl-sun9i-a80.o +diff --git a/drivers/pinctrl/sunxi/pinctrl-sun8i-a83t-r.c b/drivers/pinctrl/sunxi/pinctrl-sun8i-a83t-r.c +new file mode 100644 +index 0000000..5b88921 +--- /dev/null ++++ b/drivers/pinctrl/sunxi/pinctrl-sun8i-a83t-r.c +@@ -0,0 +1,117 @@ ++/* ++ * Allwinner A83T SoCs special pins pinctrl driver. ++ * ++ * Copyright (C) 2016 Vishnu Patekar ++ * Vishnu Patekar ++ * ++ * Based on pinctrl-sun8i-a23.c, which is: ++ * Copyright (C) 2014 Chen-Yu Tsai ++ * Copyright (C) 2014 Maxime Ripard ++ * ++ * This file is licensed under the terms of the GNU General Public ++ * License version 2. This program is licensed "as is" without any ++ * warranty of any kind, whether express or implied. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "pinctrl-sunxi.h" ++ ++static const struct sunxi_desc_pin sun8i_a83t_r_pins[] = { ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 0), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_rsb"), /* SCK */ ++ SUNXI_FUNCTION(0x3, "s_twi"), /* SCK */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 0)), /* PL_EINT0 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 1), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_rsb"), /* SDA */ ++ SUNXI_FUNCTION(0x3, "s_twi"), /* SDA */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 1)), /* PL_EINT1 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 2), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_uart"), /* TX */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 2)), /* PL_EINT2 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 3), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_uart"), /* RX */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 3)), /* PL_EINT3 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 4), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_jtag"), /* MS */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 4)), /* PL_EINT4 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 5), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_jtag"), /* CK */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 5)), /* PL_EINT5 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 6), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_jtag"), /* DO */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 6)), /* PL_EINT6 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 7), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_jtag"), /* DI */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 7)), /* PL_EINT7 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 8), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_twi"), /* SCK */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 8)), /* PL_EINT8 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 9), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_twi"), /* SDA */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 9)), /* PL_EINT9 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 10), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_pwm"), ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 10)), /* PL_EINT10 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 11), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 11)), /* PL_EINT11 */ ++ SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 12), ++ SUNXI_FUNCTION(0x0, "gpio_in"), ++ SUNXI_FUNCTION(0x1, "gpio_out"), ++ SUNXI_FUNCTION(0x2, "s_cir"), /* RX */ ++ SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 11)), /* PL_EINT12 */ ++}; ++ ++static const struct sunxi_pinctrl_desc sun8i_a83t_r_pinctrl_data = { ++ .pins = sun8i_a83t_r_pins, ++ .npins = ARRAY_SIZE(sun8i_a83t_r_pins), ++ .pin_base = PL_BASE, ++ .irq_banks = 1, ++}; ++ ++static int sun8i_a83t_r_pinctrl_probe(struct platform_device *pdev) ++{ ++ return sunxi_pinctrl_init(pdev, &sun8i_a83t_r_pinctrl_data); ++} ++ ++static const struct of_device_id sun8i_a83t_r_pinctrl_match[] = { ++ { .compatible = "allwinner,sun8i-a83t-r-pinctrl", }, ++ {} ++}; ++ ++static struct platform_driver sun8i_a83t_r_pinctrl_driver = { ++ .probe = sun8i_a83t_r_pinctrl_probe, ++ .driver = { ++ .name = "sun8i-a83t-r-pinctrl", ++ .of_match_table = sun8i_a83t_r_pinctrl_match, ++ }, ++}; ++builtin_platform_driver(sun8i_a83t_r_pinctrl_driver); diff --git a/patch/kernel/sunxi-next/a83t-banana-m3/sun8i-emac_add-optionnal-phy-regulator.patch b/patch/kernel/sunxi-next/a83t-banana-m3/sun8i-emac_add-optionnal-phy-regulator.patch new file mode 100644 index 000000000..52ec53b57 --- /dev/null +++ b/patch/kernel/sunxi-next/a83t-banana-m3/sun8i-emac_add-optionnal-phy-regulator.patch @@ -0,0 +1,101 @@ +From 9c6c022225f4af72b5d4a595cea2dbc7f8cda981 Mon Sep 17 00:00:00 2001 +From: LABBE Corentin +Date: Wed, 18 May 2016 11:35:25 +0200 +Subject: [PATCH] ethernet: sun8i-emac: add optionnal phy regulator + +Signed-off-by: LABBE Corentin +--- + drivers/net/ethernet/allwinner/sun8i-emac.c | 52 +++++++++++++++++++++++++++++ + 1 file changed, 52 insertions(+) + +diff --git a/drivers/net/ethernet/allwinner/sun8i-emac.c b/drivers/net/ethernet/allwinner/sun8i-emac.c +index af8ed5b..720711f 100644 +--- a/drivers/net/ethernet/allwinner/sun8i-emac.c ++++ b/drivers/net/ethernet/allwinner/sun8i-emac.c +@@ -204,6 +204,8 @@ struct sun8i_emac_priv { + struct device_node *phy_node; + struct clk *ahb_clk; + struct clk *ephy_clk; ++ struct regulator *regulator; ++ struct regulator *regulator_io; + bool use_internal_phy; + + struct reset_control *rst; +@@ -941,6 +943,18 @@ static int sun8i_emac_init(struct net_device *ndev) + } + } + ++ if (priv->regulator) { ++ ret = regulator_enable(priv->regulator); ++ if (ret) ++ goto err_regulator; ++ } ++ ++ if (priv->regulator_io) { ++ ret = regulator_enable(priv->regulator_io); ++ if (ret) ++ goto err_regulator_io; ++ } ++ + sun8i_emac_set_mdc(ndev); + + ret = sun8i_emac_mdio_register(ndev); +@@ -950,6 +964,12 @@ static int sun8i_emac_init(struct net_device *ndev) + return 0; + + err_mdio_register: ++ if (priv->regulator_io) ++ regulator_disable(priv->regulator_io); ++err_regulator_io: ++ if (priv->regulator) ++ regulator_disable(priv->regulator); ++err_regulator: + if (priv->rst_ephy) + reset_control_assert(priv->rst_ephy); + err_ephy_reset: +@@ -971,6 +991,12 @@ static void sun8i_emac_uninit(struct net_device *ndev) + + mdiobus_unregister(priv->mdio); + ++ if (priv->regulator_io) ++ regulator_disable(priv->regulator_io); ++ ++ if (priv->regulator) ++ regulator_disable(priv->regulator); ++ + if (priv->rst_ephy) + reset_control_assert(priv->rst_ephy); + +@@ -1881,6 +1907,32 @@ static int sun8i_emac_probe(struct platform_device *pdev) + } + } + ++ /* Optional regulator for PHY */ ++ priv->regulator = devm_regulator_get_optional(&pdev->dev, "phy"); ++ if (IS_ERR(priv->regulator)) { ++ if (PTR_ERR(priv->regulator) == -EPROBE_DEFER) { ++ ret = -EPROBE_DEFER; ++ goto probe_err; ++ } ++ dev_dbg(&pdev->dev, "no PHY regulator found\n"); ++ priv->regulator = NULL; ++ } else { ++ dev_info(&pdev->dev, "PHY regulator found\n"); ++ } ++ ++ /* Optional regulator for PHY I/O */ ++ priv->regulator_io = devm_regulator_get_optional(&pdev->dev, "phy_io"); ++ if (IS_ERR(priv->regulator_io)) { ++ if (PTR_ERR(priv->regulator_io) == -EPROBE_DEFER) { ++ ret = -EPROBE_DEFER; ++ goto probe_err; ++ } ++ dev_dbg(&pdev->dev, "no PHY I/O regulator found\n"); ++ priv->regulator_io = NULL; ++ } else { ++ dev_info(&pdev->dev, "PHY IO regulator found\n"); ++ } ++ + spin_lock_init(&priv->lock); + spin_lock_init(&priv->tx_lock); +