diff options
author | Felix Fietkau <nbd@nbd.name> | 2017-04-17 08:43:54 +0200 |
---|---|---|
committer | Felix Fietkau <nbd@nbd.name> | 2017-04-17 08:44:37 +0200 |
commit | 047695a0294fd7046ba45e5490156c2b8e936a81 (patch) | |
tree | de5072e74d685d2b4274b5bfce15d35230b73a05 /target/linux/mvebu/patches-4.4 | |
parent | aefa195749c68aa21bf135a5ec944b91cabc47ca (diff) | |
download | upstream-047695a0294fd7046ba45e5490156c2b8e936a81.tar.gz upstream-047695a0294fd7046ba45e5490156c2b8e936a81.tar.bz2 upstream-047695a0294fd7046ba45e5490156c2b8e936a81.zip |
Revert "mvebu: remove linux 4.4 support"
This reverts commit 51397d7d95d9f5e210a5557f65de1fa21e6f5921.
There are some unresolved random crashes on WRT1900AC v1 that still need
to be sorted out
Signed-off-by: Felix Fietkau <nbd@nbd.name>
Diffstat (limited to 'target/linux/mvebu/patches-4.4')
78 files changed, 13857 insertions, 0 deletions
diff --git a/target/linux/mvebu/patches-4.4/002-add_powertables.patch b/target/linux/mvebu/patches-4.4/002-add_powertables.patch new file mode 100644 index 0000000000..a5a47e4ab2 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/002-add_powertables.patch @@ -0,0 +1,748 @@ +--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts ++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts +@@ -86,12 +86,100 @@ + pcie@2,0 { + /* Port 0, Lane 1 */ + status = "okay"; ++ ++ mwlwifi { ++ marvell,5ghz = <0>; ++ marvell,chainmask = <4 4>; ++ marvell,powertable { ++ FCC = ++ <1 0 0x17 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0x17 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0x17 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>; ++ ++ ETSI = ++ <1 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <12 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>, ++ <13 0 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0xb 0x0 0x0 0x0 0x0 0 0xf>; ++ }; ++ }; + }; + + /* Second mini-PCIe port */ + pcie@3,0 { + /* Port 0, Lane 3 */ + status = "okay"; ++ ++ mwlwifi { ++ marvell,2ghz = <0>; ++ marvell,chainmask = <4 4>; ++ marvell,powertable { ++ FCC = ++ <36 0 0x8 0x8 0x8 0x8 0x8 0x8 0x8 0x8 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <40 0 0x8 0x8 0x8 0x8 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <44 0 0x8 0x8 0x8 0x8 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <48 0 0x8 0x8 0x8 0x8 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <52 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0 0xf>, ++ <56 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0 0xf>, ++ <60 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0 0xf>, ++ <64 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0 0xf>, ++ <100 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <104 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <108 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <112 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <116 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <120 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <124 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <128 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <132 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <136 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <140 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <149 0 0x16 0x16 0x16 0x16 0x14 0x14 0x14 0x14 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>, ++ <153 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>, ++ <157 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>, ++ <161 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>, ++ <165 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0 0xf>; ++ ++ ETSI = ++ <36 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <40 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <44 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <48 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <52 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <56 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <60 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <64 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <100 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <104 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <108 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <112 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <116 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <120 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <124 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <128 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <132 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <136 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <140 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>, ++ <149 0 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xc 0xd 0xd 0xd 0xd 0xc 0xc 0xc 0xc 0 0xf>; ++ }; ++ }; + }; + }; + +--- a/arch/arm/boot/dts/armada-385-linksys-cobra.dts ++++ b/arch/arm/boot/dts/armada-385-linksys-cobra.dts +@@ -100,6 +100,212 @@ + }; + }; + }; ++ ++ pcie-controller { ++ pcie@1,0 { ++ mwlwifi { ++ marvell,2ghz = <0>; ++ marvell,chainmask = <4 4>; ++ marvell,powertable { ++ AU = ++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <100 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <104 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <108 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <112 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <116 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <120 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <124 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <128 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <132 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <136 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <140 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <149 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>, ++ <153 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>, ++ <157 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>, ++ <161 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>, ++ <165 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>; ++ CA = ++ <36 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <40 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <44 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <48 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <149 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <165 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>; ++ CN = ++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <100 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <104 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <108 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <112 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <116 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <120 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <124 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <128 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <132 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <136 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <140 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <149 0 0x14 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <165 0 0x13 0x13 0x13 0x13 0x13 0x13 0x13 0x13 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>; ++ ETSI = ++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <100 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <104 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <108 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <112 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <116 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <120 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <124 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <128 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <132 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <136 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <140 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <149 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>; ++ FCC = ++ <36 0 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0 0xf>, ++ <40 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>, ++ <44 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>, ++ <48 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <149 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <165 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>; ++ }; ++ }; ++ }; ++ ++ pcie@2,0 { ++ mwlwifi { ++ marvell,5ghz = <0>; ++ marvell,chainmask = <4 4>; ++ marvell,powertable { ++ AU = ++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>; ++ CA = ++ <1 0 0x17 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0xe 0xe 0xe 0xe 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0x17 0x12 0x12 0x12 0x13 0x13 0x13 0x13 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>; ++ CN = ++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <12 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <13 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <14 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>; ++ ETSI = ++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <12 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <13 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>; ++ FCC = ++ <1 0 0x17 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0xe 0xe 0xe 0xe 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0x17 0x12 0x12 0x12 0x13 0x13 0x13 0x13 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>; ++ }; ++ }; ++ }; ++ }; + }; + + gpio-leds { +--- a/arch/arm/boot/dts/armada-385-linksys-caiman.dts ++++ b/arch/arm/boot/dts/armada-385-linksys-caiman.dts +@@ -100,6 +100,212 @@ + }; + }; + }; ++ ++ pcie-controller { ++ pcie@1,0 { ++ mwlwifi { ++ marvell,2ghz = <0>; ++ marvell,chainmask = <2 2>; ++ marvell,powertable { ++ AU = ++ <36 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <40 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <44 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <48 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <100 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <104 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <108 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <112 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <116 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <120 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <124 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <128 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <132 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <136 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <140 0 0x17 0x17 0x17 0x17 0x17 0x17 0x17 0x15 0x17 0x17 0x17 0x14 0x17 0x17 0x17 0x14 0 0xf>, ++ <149 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>, ++ <153 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>, ++ <157 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>, ++ <161 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>, ++ <165 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x1a 0x1a 0x17 0x14 0 0xf>; ++ CA = ++ <36 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0 0xf>, ++ <40 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0 0xf>, ++ <44 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0 0xf>, ++ <48 0 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0xf 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <149 0 0x1a 0x1a 0x18 0x17 0x19 0x19 0x17 0x15 0x18 0x18 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>, ++ <153 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>, ++ <157 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>, ++ <161 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>, ++ <165 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>; ++ CN = ++ <36 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <40 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <44 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <48 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <100 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <104 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <108 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <112 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <116 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <120 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <124 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <128 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <132 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <136 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <140 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <149 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x11 0x11 0x11 0x11 0 0xf>, ++ <153 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0 0xf>, ++ <157 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0 0xf>, ++ <161 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0 0xf>, ++ <165 0 0x15 0x15 0x15 0x15 0x16 0x16 0x16 0x15 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0 0xf>; ++ ETSI = ++ <36 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <40 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <44 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <48 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <100 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <104 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <108 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <112 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <116 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <120 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <124 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <128 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <132 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <136 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <140 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>, ++ <149 0 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11 0 0xf>; ++ FCC = ++ <36 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <40 0 0x19 0x19 0x18 0x17 0x19 0x19 0x17 0x15 0x17 0x17 0x17 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <44 0 0x19 0x19 0x18 0x17 0x19 0x19 0x17 0x15 0x17 0x17 0x17 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <48 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x17 0x17 0x17 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <149 0 0x1a 0x1a 0x18 0x17 0x19 0x19 0x17 0x15 0x18 0x18 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>, ++ <153 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>, ++ <157 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>, ++ <161 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>, ++ <165 0 0x1a 0x1a 0x18 0x17 0x1a 0x1a 0x17 0x15 0x1a 0x1a 0x17 0x14 0x15 0x15 0x15 0x14 0 0xf>; ++ }; ++ }; ++ }; ++ ++ pcie@2,0 { ++ mwlwifi { ++ marvell,5ghz = <0>; ++ marvell,chainmask = <2 2>; ++ marvell,powertable { ++ AU = ++ <1 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>; ++ CA = ++ <1 0 0x19 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x10 0x10 0x10 0x10 0x00 0x00 0x00 0x00 0 0xf>, ++ <2 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <3 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <4 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <5 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <6 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <7 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <8 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <9 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <10 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x00 0x00 0x00 0x00 0 0xf>, ++ <11 0 0x19 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x00 0x00 0x00 0x00 0 0xf>; ++ CN = ++ <1 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <12 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <13 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <14 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>; ++ ETSI = ++ <1 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <12 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>, ++ <13 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0x0 0x0 0x0 0x0 0 0xf>; ++ FCC = ++ <1 0 0x19 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x10 0x10 0x10 0x10 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0x1a 0x19 0x18 0x17 0x19 0x19 0x17 0x16 0x14 0x14 0x14 0x14 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0x19 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x0 0x0 0x0 0x0 0 0xf>; ++ }; ++ }; ++ }; ++ }; + }; + + gpio-leds { +--- a/arch/arm/boot/dts/armada-385-linksys-shelby.dts ++++ b/arch/arm/boot/dts/armada-385-linksys-shelby.dts +@@ -100,6 +100,212 @@ + }; + }; + }; ++ ++ pcie-controller { ++ pcie@1,0 { ++ mwlwifi { ++ marvell,2ghz = <0>; ++ marvell,chainmask = <4 4>; ++ marvell,powertable { ++ AU = ++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <100 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <104 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <108 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <112 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <116 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <120 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <124 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <128 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <132 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <136 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <140 0 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0x14 0 0xf>, ++ <149 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>, ++ <153 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>, ++ <157 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>, ++ <161 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>, ++ <165 0 0x19 0x19 0x19 0x17 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0x19 0x19 0x16 0x15 0 0xf>; ++ CA = ++ <36 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <40 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <44 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <48 0 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0x9 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <149 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <165 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>; ++ CN = ++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <100 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <104 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <108 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <112 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <116 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <120 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <124 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <128 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <132 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <136 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <140 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <149 0 0x14 0x14 0x14 0x14 0x13 0x13 0x13 0x13 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>, ++ <165 0 0x13 0x13 0x13 0x13 0x13 0x13 0x13 0x13 0x14 0x14 0x14 0x14 0x10 0x10 0x10 0x10 0 0xf>; ++ ETSI = ++ <36 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <40 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <44 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <48 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <52 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <56 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <60 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <64 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <100 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <104 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <108 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <112 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <116 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <120 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <124 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <128 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <132 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <136 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <140 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>, ++ <149 0 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xd 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0xe 0 0xf>; ++ FCC = ++ <36 0 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0 0xf>, ++ <40 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>, ++ <44 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>, ++ <48 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0xf 0xf 0xf 0xf 0 0xf>, ++ <52 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <56 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <60 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <64 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <100 0 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <104 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x12 0x12 0x12 0x12 0x10 0x10 0x10 0x10 0 0xf>, ++ <108 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <112 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <116 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <120 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <124 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <128 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <132 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <136 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <140 0 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0x11 0x11 0x11 0x11 0x10 0x10 0x10 0x10 0 0xf>, ++ <149 0 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <153 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <157 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <161 0 0x16 0x16 0x16 0x16 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>, ++ <165 0 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0x15 0 0xf>; ++ }; ++ }; ++ }; ++ ++ pcie@2,0 { ++ mwlwifi { ++ marvell,5ghz = <0>; ++ marvell,chainmask = <4 4>; ++ marvell,powertable { ++ AU = ++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>; ++ CA = ++ <1 0 0x17 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0xe 0xe 0xe 0xe 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0x17 0x12 0x12 0x12 0x13 0x13 0x13 0x13 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>; ++ CN = ++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <12 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <13 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <14 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>; ++ ETSI = ++ <1 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <12 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>, ++ <13 0 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0xa 0x0 0x0 0x0 0x0 0 0xf>; ++ FCC = ++ <1 0 0x17 0x10 0x10 0x10 0xf 0xf 0xf 0xf 0xe 0xe 0xe 0xe 0x0 0x0 0x0 0x0 0 0xf>, ++ <2 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <3 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <4 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <5 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <6 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <7 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <8 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <9 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <10 0 0x18 0x16 0x16 0x16 0x16 0x16 0x16 0x14 0x11 0x11 0x11 0x11 0x0 0x0 0x0 0x0 0 0xf>, ++ <11 0 0x17 0x12 0x12 0x12 0x13 0x13 0x13 0x13 0xf 0xf 0xf 0xf 0x0 0x0 0x0 0x0 0 0xf>; ++ }; ++ }; ++ }; ++ }; + }; + + gpio-leds { diff --git a/target/linux/mvebu/patches-4.4/003-add_switch_nodes.patch b/target/linux/mvebu/patches-4.4/003-add_switch_nodes.patch new file mode 100644 index 0000000000..1502b6b142 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/003-add_switch_nodes.patch @@ -0,0 +1,40 @@ +--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts ++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts +@@ -467,6 +467,16 @@ + }; + }; + }; ++ ++ mvsw61xx { ++ compatible = "marvell,88e6172"; ++ status = "okay"; ++ reg = <0x10>; ++ ++ mii-bus = <&mdio>; ++ cpu-port-0 = <5>; ++ cpu-port-1 = <6>; ++ }; + }; + + &pinctrl { +--- a/arch/arm/boot/dts/armada-385-linksys.dtsi ++++ b/arch/arm/boot/dts/armada-385-linksys.dtsi +@@ -309,6 +309,18 @@ + }; + }; + }; ++ ++ mvsw61xx { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "marvell,88e6176"; ++ status = "okay"; ++ reg = <0x10>; ++ ++ mii-bus = <&mdio>; ++ cpu-port-0 = <5>; ++ cpu-port-1 = <6>; ++ }; + }; + + &pinctrl { diff --git a/target/linux/mvebu/patches-4.4/010-build_new_dtbs.patch b/target/linux/mvebu/patches-4.4/010-build_new_dtbs.patch new file mode 100644 index 0000000000..6ba9ee6e82 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/010-build_new_dtbs.patch @@ -0,0 +1,11 @@ +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -748,6 +748,8 @@ dtb-$(CONFIG_MACH_ARMADA_38X) += \ + armada-385-db-ap.dtb \ + armada-385-linksys-caiman.dtb \ + armada-385-linksys-cobra.dtb \ ++ armada-385-linksys-rango.dtb \ ++ armada-385-linksys-shelby.dtb \ + armada-388-db.dtb \ + armada-388-gp.dtb \ + armada-388-rd.dtb diff --git a/target/linux/mvebu/patches-4.4/020-mtd-nand-pxa3xx_nand-add-support-for-partial-chunks.patch b/target/linux/mvebu/patches-4.4/020-mtd-nand-pxa3xx_nand-add-support-for-partial-chunks.patch new file mode 100644 index 0000000000..2e6709781f --- /dev/null +++ b/target/linux/mvebu/patches-4.4/020-mtd-nand-pxa3xx_nand-add-support-for-partial-chunks.patch @@ -0,0 +1,428 @@ +From: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> +Date: Wed, 10 Feb 2016 14:54:21 +0100 +Subject: [PATCH] mtd: nand: pxa3xx_nand: add support for partial chunks + +This commit is needed to properly support the 8-bits ECC configuration +with 4KB pages. + +When pages larger than 2 KB are used on platforms using the PXA3xx +NAND controller, the reading/programming operations need to be split +in chunks of 2 KBs or less because the controller FIFO is limited to +about 2 KB (i.e a bit more than 2 KB to accommodate OOB data). Due to +this requirement, the data layout on NAND is a bit strange, with ECC +interleaved with data, at the end of each chunk. + +When a 4-bits ECC configuration is used with 4 KB pages, the physical +data layout on the NAND looks like this: + +| 2048 data | 32 spare | 30 ECC | 2048 data | 32 spare | 30 ECC | + +So the data chunks have an equal size, 2080 bytes for each chunk, +which the driver supports properly. + +When a 8-bits ECC configuration is used with 4KB pages, the physical +data layout on the NAND looks like this: + +| 1024 data | 30 ECC | 1024 data | 30 ECC | 1024 data | 30 ECC | 1024 data | 30 ECC | 64 spare | 30 ECC | + +So, the spare area is stored in its own chunk, which has a different +size than the other chunks. Since OOB is not used by UBIFS, the initial +implementation of the driver has chosen to not support reading this +additional "spare" chunk of data. + +Unfortunately, Marvell has chosen to store the BBT signature in the +OOB area. Therefore, if the driver doesn't read this spare area, Linux +has no way of finding the BBT. It thinks there is no BBT, and rewrites +one, which U-Boot does not recognize, causing compatibility problems +between the bootloader and the kernel in terms of NAND usage. + +To fix this, this commit implements the support for reading a partial +last chunk. This support is currently only useful for the case of 8 +bits ECC with 4 KB pages, but it will be useful in the future to +enable other configurations such as 12 bits and 16 bits ECC with 4 KB +pages, or 8 bits ECC with 8 KB pages, etc. All those configurations +have a "last" chunk that doesn't have the same size as the other +chunks. + +In order to implement reading of the last chunk, this commit: + + - Adds a number of new fields to the pxa3xx_nand_info to describe how + many full chunks and how many chunks we have, the size of full + chunks and partial chunks, both in terms of data area and spare + area. + + - Fills in the step_chunk_size and step_spare_size variables to + describe how much data and spare should be read/written for the + current read/program step. + + - Reworks the state machine to accommodate doing the additional read + or program step when a last partial chunk is used. + +This commit has been tested on a Marvell Armada 398 DB board, with a +4KB page NAND, tested in both 4 bits ECC and 8 bits ECC +configurations. Robert Jarzmik has tested on some PXA platforms. + +Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> +Tested-by: Robert Jarzmik <robert.jarzmik@free.fr> +Acked-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar> +Signed-off-by: Brian Norris <computersforpeace@gmail.com> +--- + +--- a/drivers/mtd/nand/pxa3xx_nand.c ++++ b/drivers/mtd/nand/pxa3xx_nand.c +@@ -228,15 +228,44 @@ struct pxa3xx_nand_info { + int use_spare; /* use spare ? */ + int need_wait; + +- unsigned int data_size; /* data to be read from FIFO */ +- unsigned int chunk_size; /* split commands chunk size */ +- unsigned int oob_size; ++ /* Amount of real data per full chunk */ ++ unsigned int chunk_size; ++ ++ /* Amount of spare data per full chunk */ + unsigned int spare_size; ++ ++ /* Number of full chunks (i.e chunk_size + spare_size) */ ++ unsigned int nfullchunks; ++ ++ /* ++ * Total number of chunks. If equal to nfullchunks, then there ++ * are only full chunks. Otherwise, there is one last chunk of ++ * size (last_chunk_size + last_spare_size) ++ */ ++ unsigned int ntotalchunks; ++ ++ /* Amount of real data in the last chunk */ ++ unsigned int last_chunk_size; ++ ++ /* Amount of spare data in the last chunk */ ++ unsigned int last_spare_size; ++ + unsigned int ecc_size; + unsigned int ecc_err_cnt; + unsigned int max_bitflips; + int retcode; + ++ /* ++ * Variables only valid during command ++ * execution. step_chunk_size and step_spare_size is the ++ * amount of real data and spare data in the current ++ * chunk. cur_chunk is the current chunk being ++ * read/programmed. ++ */ ++ unsigned int step_chunk_size; ++ unsigned int step_spare_size; ++ unsigned int cur_chunk; ++ + /* cached register value */ + uint32_t reg_ndcr; + uint32_t ndtr0cs0; +@@ -531,25 +560,6 @@ static int pxa3xx_nand_init(struct pxa3x + return 0; + } + +-/* +- * Set the data and OOB size, depending on the selected +- * spare and ECC configuration. +- * Only applicable to READ0, READOOB and PAGEPROG commands. +- */ +-static void pxa3xx_set_datasize(struct pxa3xx_nand_info *info, +- struct mtd_info *mtd) +-{ +- int oob_enable = info->reg_ndcr & NDCR_SPARE_EN; +- +- info->data_size = mtd->writesize; +- if (!oob_enable) +- return; +- +- info->oob_size = info->spare_size; +- if (!info->use_ecc) +- info->oob_size += info->ecc_size; +-} +- + /** + * NOTE: it is a must to set ND_RUN firstly, then write + * command buffer, otherwise, it does not work. +@@ -665,28 +675,28 @@ static void drain_fifo(struct pxa3xx_nan + + static void handle_data_pio(struct pxa3xx_nand_info *info) + { +- unsigned int do_bytes = min(info->data_size, info->chunk_size); +- + switch (info->state) { + case STATE_PIO_WRITING: +- writesl(info->mmio_base + NDDB, +- info->data_buff + info->data_buff_pos, +- DIV_ROUND_UP(do_bytes, 4)); ++ if (info->step_chunk_size) ++ writesl(info->mmio_base + NDDB, ++ info->data_buff + info->data_buff_pos, ++ DIV_ROUND_UP(info->step_chunk_size, 4)); + +- if (info->oob_size > 0) ++ if (info->step_spare_size) + writesl(info->mmio_base + NDDB, + info->oob_buff + info->oob_buff_pos, +- DIV_ROUND_UP(info->oob_size, 4)); ++ DIV_ROUND_UP(info->step_spare_size, 4)); + break; + case STATE_PIO_READING: +- drain_fifo(info, +- info->data_buff + info->data_buff_pos, +- DIV_ROUND_UP(do_bytes, 4)); ++ if (info->step_chunk_size) ++ drain_fifo(info, ++ info->data_buff + info->data_buff_pos, ++ DIV_ROUND_UP(info->step_chunk_size, 4)); + +- if (info->oob_size > 0) ++ if (info->step_spare_size) + drain_fifo(info, + info->oob_buff + info->oob_buff_pos, +- DIV_ROUND_UP(info->oob_size, 4)); ++ DIV_ROUND_UP(info->step_spare_size, 4)); + break; + default: + dev_err(&info->pdev->dev, "%s: invalid state %d\n", __func__, +@@ -695,9 +705,8 @@ static void handle_data_pio(struct pxa3x + } + + /* Update buffer pointers for multi-page read/write */ +- info->data_buff_pos += do_bytes; +- info->oob_buff_pos += info->oob_size; +- info->data_size -= do_bytes; ++ info->data_buff_pos += info->step_chunk_size; ++ info->oob_buff_pos += info->step_spare_size; + } + + static void pxa3xx_nand_data_dma_irq(void *data) +@@ -738,8 +747,9 @@ static void start_data_dma(struct pxa3xx + info->state); + BUG(); + } +- info->sg.length = info->data_size + +- (info->oob_size ? info->spare_size + info->ecc_size : 0); ++ info->sg.length = info->chunk_size; ++ if (info->use_spare) ++ info->sg.length += info->spare_size + info->ecc_size; + dma_map_sg(info->dma_chan->device->dev, &info->sg, 1, info->dma_dir); + + tx = dmaengine_prep_slave_sg(info->dma_chan, &info->sg, 1, direction, +@@ -900,9 +910,11 @@ static void prepare_start_command(struct + /* reset data and oob column point to handle data */ + info->buf_start = 0; + info->buf_count = 0; +- info->oob_size = 0; + info->data_buff_pos = 0; + info->oob_buff_pos = 0; ++ info->step_chunk_size = 0; ++ info->step_spare_size = 0; ++ info->cur_chunk = 0; + info->use_ecc = 0; + info->use_spare = 1; + info->retcode = ERR_NONE; +@@ -914,8 +926,6 @@ static void prepare_start_command(struct + case NAND_CMD_READ0: + case NAND_CMD_PAGEPROG: + info->use_ecc = 1; +- case NAND_CMD_READOOB: +- pxa3xx_set_datasize(info, mtd); + break; + case NAND_CMD_PARAM: + info->use_spare = 0; +@@ -974,6 +984,14 @@ static int prepare_set_command(struct px + if (command == NAND_CMD_READOOB) + info->buf_start += mtd->writesize; + ++ if (info->cur_chunk < info->nfullchunks) { ++ info->step_chunk_size = info->chunk_size; ++ info->step_spare_size = info->spare_size; ++ } else { ++ info->step_chunk_size = info->last_chunk_size; ++ info->step_spare_size = info->last_spare_size; ++ } ++ + /* + * Multiple page read needs an 'extended command type' field, + * which is either naked-read or last-read according to the +@@ -985,8 +1003,8 @@ static int prepare_set_command(struct px + info->ndcb0 |= NDCB0_DBC | (NAND_CMD_READSTART << 8) + | NDCB0_LEN_OVRD + | NDCB0_EXT_CMD_TYPE(ext_cmd_type); +- info->ndcb3 = info->chunk_size + +- info->oob_size; ++ info->ndcb3 = info->step_chunk_size + ++ info->step_spare_size; + } + + set_command_address(info, mtd->writesize, column, page_addr); +@@ -1006,8 +1024,6 @@ static int prepare_set_command(struct px + | NDCB0_EXT_CMD_TYPE(ext_cmd_type) + | addr_cycle + | command; +- /* No data transfer in this case */ +- info->data_size = 0; + exec_cmd = 1; + } + break; +@@ -1019,6 +1035,14 @@ static int prepare_set_command(struct px + break; + } + ++ if (info->cur_chunk < info->nfullchunks) { ++ info->step_chunk_size = info->chunk_size; ++ info->step_spare_size = info->spare_size; ++ } else { ++ info->step_chunk_size = info->last_chunk_size; ++ info->step_spare_size = info->last_spare_size; ++ } ++ + /* Second command setting for large pages */ + if (mtd->writesize > PAGE_CHUNK_SIZE) { + /* +@@ -1029,14 +1053,14 @@ static int prepare_set_command(struct px + info->ndcb0 |= NDCB0_CMD_TYPE(0x1) + | NDCB0_LEN_OVRD + | NDCB0_EXT_CMD_TYPE(ext_cmd_type); +- info->ndcb3 = info->chunk_size + +- info->oob_size; ++ info->ndcb3 = info->step_chunk_size + ++ info->step_spare_size; + + /* + * This is the command dispatch that completes a chunked + * page program operation. + */ +- if (info->data_size == 0) { ++ if (info->cur_chunk == info->ntotalchunks) { + info->ndcb0 = NDCB0_CMD_TYPE(0x1) + | NDCB0_EXT_CMD_TYPE(ext_cmd_type) + | command; +@@ -1063,7 +1087,7 @@ static int prepare_set_command(struct px + | command; + info->ndcb1 = (column & 0xFF); + info->ndcb3 = INIT_BUFFER_SIZE; +- info->data_size = INIT_BUFFER_SIZE; ++ info->step_chunk_size = INIT_BUFFER_SIZE; + break; + + case NAND_CMD_READID: +@@ -1073,7 +1097,7 @@ static int prepare_set_command(struct px + | command; + info->ndcb1 = (column & 0xFF); + +- info->data_size = 8; ++ info->step_chunk_size = 8; + break; + case NAND_CMD_STATUS: + info->buf_count = 1; +@@ -1081,7 +1105,7 @@ static int prepare_set_command(struct px + | NDCB0_ADDR_CYC(1) + | command; + +- info->data_size = 8; ++ info->step_chunk_size = 8; + break; + + case NAND_CMD_ERASE1: +@@ -1220,6 +1244,7 @@ static void nand_cmdfunc_extended(struct + init_completion(&info->dev_ready); + do { + info->state = STATE_PREPARED; ++ + exec_cmd = prepare_set_command(info, command, ext_cmd_type, + column, page_addr); + if (!exec_cmd) { +@@ -1239,22 +1264,30 @@ static void nand_cmdfunc_extended(struct + break; + } + ++ /* Only a few commands need several steps */ ++ if (command != NAND_CMD_PAGEPROG && ++ command != NAND_CMD_READ0 && ++ command != NAND_CMD_READOOB) ++ break; ++ ++ info->cur_chunk++; ++ + /* Check if the sequence is complete */ +- if (info->data_size == 0 && command != NAND_CMD_PAGEPROG) ++ if (info->cur_chunk == info->ntotalchunks && command != NAND_CMD_PAGEPROG) + break; + + /* + * After a splitted program command sequence has issued + * the command dispatch, the command sequence is complete. + */ +- if (info->data_size == 0 && ++ if (info->cur_chunk == (info->ntotalchunks + 1) && + command == NAND_CMD_PAGEPROG && + ext_cmd_type == EXT_CMD_TYPE_DISPATCH) + break; + + if (command == NAND_CMD_READ0 || command == NAND_CMD_READOOB) { + /* Last read: issue a 'last naked read' */ +- if (info->data_size == info->chunk_size) ++ if (info->cur_chunk == info->ntotalchunks - 1) + ext_cmd_type = EXT_CMD_TYPE_LAST_RW; + else + ext_cmd_type = EXT_CMD_TYPE_NAKED_RW; +@@ -1264,7 +1297,7 @@ static void nand_cmdfunc_extended(struct + * the command dispatch must be issued to complete. + */ + } else if (command == NAND_CMD_PAGEPROG && +- info->data_size == 0) { ++ info->cur_chunk == info->ntotalchunks) { + ext_cmd_type = EXT_CMD_TYPE_DISPATCH; + } + } while (1); +@@ -1514,6 +1547,8 @@ static int pxa_ecc_init(struct pxa3xx_na + int strength, int ecc_stepsize, int page_size) + { + if (strength == 1 && ecc_stepsize == 512 && page_size == 2048) { ++ info->nfullchunks = 1; ++ info->ntotalchunks = 1; + info->chunk_size = 2048; + info->spare_size = 40; + info->ecc_size = 24; +@@ -1522,6 +1557,8 @@ static int pxa_ecc_init(struct pxa3xx_na + ecc->strength = 1; + + } else if (strength == 1 && ecc_stepsize == 512 && page_size == 512) { ++ info->nfullchunks = 1; ++ info->ntotalchunks = 1; + info->chunk_size = 512; + info->spare_size = 8; + info->ecc_size = 8; +@@ -1535,6 +1572,8 @@ static int pxa_ecc_init(struct pxa3xx_na + */ + } else if (strength == 4 && ecc_stepsize == 512 && page_size == 2048) { + info->ecc_bch = 1; ++ info->nfullchunks = 1; ++ info->ntotalchunks = 1; + info->chunk_size = 2048; + info->spare_size = 32; + info->ecc_size = 32; +@@ -1545,6 +1584,8 @@ static int pxa_ecc_init(struct pxa3xx_na + + } else if (strength == 4 && ecc_stepsize == 512 && page_size == 4096) { + info->ecc_bch = 1; ++ info->nfullchunks = 2; ++ info->ntotalchunks = 2; + info->chunk_size = 2048; + info->spare_size = 32; + info->ecc_size = 32; +@@ -1559,8 +1600,12 @@ static int pxa_ecc_init(struct pxa3xx_na + */ + } else if (strength == 8 && ecc_stepsize == 512 && page_size == 4096) { + info->ecc_bch = 1; ++ info->nfullchunks = 4; ++ info->ntotalchunks = 5; + info->chunk_size = 1024; + info->spare_size = 0; ++ info->last_chunk_size = 0; ++ info->last_spare_size = 64; + info->ecc_size = 32; + ecc->mode = NAND_ECC_HW; + ecc->size = info->chunk_size; diff --git a/target/linux/mvebu/patches-4.4/021-mtd-pxa3xx_nand-Increase-the-initial-chunk-size.patch b/target/linux/mvebu/patches-4.4/021-mtd-pxa3xx_nand-Increase-the-initial-chunk-size.patch new file mode 100644 index 0000000000..0b0e047c02 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/021-mtd-pxa3xx_nand-Increase-the-initial-chunk-size.patch @@ -0,0 +1,42 @@ +From: =?UTF-8?q?Ezequiel=20Garc=C3=ADa?= <ezequiel@vanguardiasur.com.ar> +Date: Wed, 4 Nov 2015 13:13:41 -0300 +Subject: [PATCH] mtd: pxa3xx_nand: Increase the initial chunk size + +The chunk size represents the size of the data chunks, which +is used by the controllers that allow to split transfered data. + +However, the initial chunk size is used in a non-splitted way, +during device identification. Therefore, it must be large enough +for all the NAND commands issued during device identification. +This includes NAND_CMD_PARAM which was recently changed to +transfer up to 2048 bytes (for the redundant parameter pages). + +Thus, the initial chunk size should be 2048 as well. + +On Armada 370/XP platforms (NFCv2) booted without the keep-config +devicetree property, this commit fixes a timeout on the NAND_CMD_PARAM +command: + + [..] + pxa3xx-nand f10d0000.nand: This platform can't do DMA on this device + pxa3xx-nand f10d0000.nand: Wait time out!!! + nand: device found, Manufacturer ID: 0x2c, Chip ID: 0x38 + nand: Micron MT29F8G08ABABAWP + nand: 1024 MiB, SLC, erase size: 512 KiB, page size: 4096, OOB size: 224 + +Signed-off-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar> +Acked-by: Robert Jarzmik <robert.jarzmik@free.fr> +Signed-off-by: Brian Norris <computersforpeace@gmail.com> +--- + +--- a/drivers/mtd/nand/pxa3xx_nand.c ++++ b/drivers/mtd/nand/pxa3xx_nand.c +@@ -1637,7 +1637,7 @@ static int pxa3xx_nand_scan(struct mtd_i + goto KEEP_CONFIG; + + /* Set a default chunk size */ +- info->chunk_size = 512; ++ info->chunk_size = PAGE_CHUNK_SIZE; + + ret = pxa3xx_nand_config_flash(info); + if (ret) diff --git a/target/linux/mvebu/patches-4.4/022-mtd-pxa3xx_nand-Fix-initial-controller-configuration.patch b/target/linux/mvebu/patches-4.4/022-mtd-pxa3xx_nand-Fix-initial-controller-configuration.patch new file mode 100644 index 0000000000..7d07fb9423 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/022-mtd-pxa3xx_nand-Fix-initial-controller-configuration.patch @@ -0,0 +1,104 @@ +From: =?UTF-8?q?Ezequiel=20Garc=C3=ADa?= <ezequiel@vanguardiasur.com.ar> +Date: Wed, 4 Nov 2015 13:13:42 -0300 +Subject: [PATCH] mtd: pxa3xx_nand: Fix initial controller configuration + +The Data Flash Control Register (NDCR) contains two types +of parameters: those that are needed for device identification, +and those that can only be set after device identification. + +Therefore, the driver can't set them all at once and instead +needs to configure the first group before nand_scan_ident() +and the second group later. + +Let's split pxa3xx_nand_config in two halves, and set the +parameters that depend on the device geometry once this is known. + +Signed-off-by: Ezequiel Garcia <ezequiel@vanguardiasur.com.ar> +Signed-off-by: Brian Norris <computersforpeace@gmail.com> +--- + +--- a/drivers/mtd/nand/pxa3xx_nand.c ++++ b/drivers/mtd/nand/pxa3xx_nand.c +@@ -1420,34 +1420,43 @@ static int pxa3xx_nand_waitfunc(struct m + return NAND_STATUS_READY; + } + +-static int pxa3xx_nand_config_flash(struct pxa3xx_nand_info *info) ++static int pxa3xx_nand_config_ident(struct pxa3xx_nand_info *info) + { + struct platform_device *pdev = info->pdev; + struct pxa3xx_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); +- struct pxa3xx_nand_host *host = info->host[info->cs]; +- struct mtd_info *mtd = host->mtd; +- struct nand_chip *chip = mtd->priv; + +- /* configure default flash values */ ++ /* Configure default flash values */ ++ info->chunk_size = PAGE_CHUNK_SIZE; + info->reg_ndcr = 0x0; /* enable all interrupts */ + info->reg_ndcr |= (pdata->enable_arbiter) ? NDCR_ND_ARB_EN : 0; + info->reg_ndcr |= NDCR_RD_ID_CNT(READ_ID_BYTES); +- info->reg_ndcr |= NDCR_SPARE_EN; /* enable spare by default */ ++ info->reg_ndcr |= NDCR_SPARE_EN; ++ ++ return 0; ++} ++ ++static void pxa3xx_nand_config_tail(struct pxa3xx_nand_info *info) ++{ ++ struct pxa3xx_nand_host *host = info->host[info->cs]; ++ struct mtd_info *mtd = host->mtd; ++ struct nand_chip *chip = mtd->priv; ++ + info->reg_ndcr |= (host->col_addr_cycles == 2) ? NDCR_RA_START : 0; + info->reg_ndcr |= (chip->page_shift == 6) ? NDCR_PG_PER_BLK : 0; + info->reg_ndcr |= (mtd->writesize == 2048) ? NDCR_PAGE_SZ : 0; +- +- return 0; + } + + static int pxa3xx_nand_detect_config(struct pxa3xx_nand_info *info) + { ++ struct platform_device *pdev = info->pdev; ++ struct pxa3xx_nand_platform_data *pdata = dev_get_platdata(&pdev->dev); + uint32_t ndcr = nand_readl(info, NDCR); + + /* Set an initial chunk size */ + info->chunk_size = ndcr & NDCR_PAGE_SZ ? 2048 : 512; + info->reg_ndcr = ndcr & + ~(NDCR_INT_MASK | NDCR_ND_ARB_EN | NFCV1_NDCR_ARB_CNTL); ++ info->reg_ndcr |= (pdata->enable_arbiter) ? NDCR_ND_ARB_EN : 0; + info->ndtr0cs0 = nand_readl(info, NDTR0CS0); + info->ndtr1cs0 = nand_readl(info, NDTR1CS0); + return 0; +@@ -1636,10 +1645,7 @@ static int pxa3xx_nand_scan(struct mtd_i + if (pdata->keep_config && !pxa3xx_nand_detect_config(info)) + goto KEEP_CONFIG; + +- /* Set a default chunk size */ +- info->chunk_size = PAGE_CHUNK_SIZE; +- +- ret = pxa3xx_nand_config_flash(info); ++ ret = pxa3xx_nand_config_ident(info); + if (ret) + return ret; + +@@ -1652,7 +1658,6 @@ static int pxa3xx_nand_scan(struct mtd_i + } + + KEEP_CONFIG: +- info->reg_ndcr |= (pdata->enable_arbiter) ? NDCR_ND_ARB_EN : 0; + if (info->reg_ndcr & NDCR_DWIDTH_M) + chip->options |= NAND_BUSWIDTH_16; + +@@ -1737,6 +1742,10 @@ KEEP_CONFIG: + host->row_addr_cycles = 3; + else + host->row_addr_cycles = 2; ++ ++ if (!pdata->keep_config) ++ pxa3xx_nand_config_tail(info); ++ + return nand_scan_tail(mtd); + } + diff --git a/target/linux/mvebu/patches-4.4/023-bus-mvebu-mbus-provide-api-for-obtaining-IO-and-DRAM.patch b/target/linux/mvebu/patches-4.4/023-bus-mvebu-mbus-provide-api-for-obtaining-IO-and-DRAM.patch new file mode 100644 index 0000000000..58687f36ad --- /dev/null +++ b/target/linux/mvebu/patches-4.4/023-bus-mvebu-mbus-provide-api-for-obtaining-IO-and-DRAM.patch @@ -0,0 +1,94 @@ +From: Marcin Wojtas <mw@semihalf.com> +Date: Mon, 14 Mar 2016 09:39:02 +0100 +Subject: [PATCH] bus: mvebu-mbus: provide api for obtaining IO and DRAM window + information + +This commit enables finding appropriate mbus window and obtaining its +target id and attribute for given physical address in two separate +routines, both for IO and DRAM windows. This functionality +is needed for Armada XP/38x Network Controller's Buffer Manager and +PnC configuration. + +[gregory.clement@free-electrons.com: Fix size test for +mvebu_mbus_get_dram_win_info] + +Signed-off-by: Marcin Wojtas <mw@semihalf.com> +[DRAM window information reference in LKv3.10] +Signed-off-by: Evan Wang <xswang@marvell.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/bus/mvebu-mbus.c ++++ b/drivers/bus/mvebu-mbus.c +@@ -948,6 +948,58 @@ void mvebu_mbus_get_pcie_io_aperture(str + *res = mbus_state.pcie_io_aperture; + } + ++int mvebu_mbus_get_dram_win_info(phys_addr_t phyaddr, u8 *target, u8 *attr) ++{ ++ const struct mbus_dram_target_info *dram; ++ int i; ++ ++ /* Get dram info */ ++ dram = mv_mbus_dram_info(); ++ if (!dram) { ++ pr_err("missing DRAM information\n"); ++ return -ENODEV; ++ } ++ ++ /* Try to find matching DRAM window for phyaddr */ ++ for (i = 0; i < dram->num_cs; i++) { ++ const struct mbus_dram_window *cs = dram->cs + i; ++ ++ if (cs->base <= phyaddr && ++ phyaddr <= (cs->base + cs->size - 1)) { ++ *target = dram->mbus_dram_target_id; ++ *attr = cs->mbus_attr; ++ return 0; ++ } ++ } ++ ++ pr_err("invalid dram address 0x%x\n", phyaddr); ++ return -EINVAL; ++} ++EXPORT_SYMBOL_GPL(mvebu_mbus_get_dram_win_info); ++ ++int mvebu_mbus_get_io_win_info(phys_addr_t phyaddr, u32 *size, u8 *target, ++ u8 *attr) ++{ ++ int win; ++ ++ for (win = 0; win < mbus_state.soc->num_wins; win++) { ++ u64 wbase; ++ int enabled; ++ ++ mvebu_mbus_read_window(&mbus_state, win, &enabled, &wbase, ++ size, target, attr, NULL); ++ ++ if (!enabled) ++ continue; ++ ++ if (wbase <= phyaddr && phyaddr <= wbase + *size) ++ return win; ++ } ++ ++ return -EINVAL; ++} ++EXPORT_SYMBOL_GPL(mvebu_mbus_get_io_win_info); ++ + static __init int mvebu_mbus_debugfs_init(void) + { + struct mvebu_mbus_state *s = &mbus_state; +--- a/include/linux/mbus.h ++++ b/include/linux/mbus.h +@@ -69,6 +69,9 @@ static inline const struct mbus_dram_tar + int mvebu_mbus_save_cpu_target(u32 *store_addr); + void mvebu_mbus_get_pcie_mem_aperture(struct resource *res); + void mvebu_mbus_get_pcie_io_aperture(struct resource *res); ++int mvebu_mbus_get_dram_win_info(phys_addr_t phyaddr, u8 *target, u8 *attr); ++int mvebu_mbus_get_io_win_info(phys_addr_t phyaddr, u32 *size, u8 *target, ++ u8 *attr); + int mvebu_mbus_add_window_remap_by_id(unsigned int target, + unsigned int attribute, + phys_addr_t base, size_t size, diff --git a/target/linux/mvebu/patches-4.4/030-mvneta-consolidate-autoneg-enabling.patch b/target/linux/mvebu/patches-4.4/030-mvneta-consolidate-autoneg-enabling.patch new file mode 100644 index 0000000000..1d98c7decc --- /dev/null +++ b/target/linux/mvebu/patches-4.4/030-mvneta-consolidate-autoneg-enabling.patch @@ -0,0 +1,55 @@ +From: Stas Sergeev <stsp@list.ru> +Date: Wed, 2 Dec 2015 20:33:56 +0300 +Subject: [PATCH] mvneta: consolidate autoneg enabling + +This moves autoneg-related bit manipulations to the single place. + +CC: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> +CC: netdev@vger.kernel.org +CC: linux-kernel@vger.kernel.org + +Signed-off-by: Stas Sergeev <stsp@users.sourceforge.net> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -1067,15 +1067,28 @@ static void mvneta_defaults_set(struct m + MVNETA_GMAC_AN_SPEED_EN | + MVNETA_GMAC_AN_DUPLEX_EN; + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); ++ + val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); + val |= MVNETA_GMAC_1MS_CLOCK_ENABLE; + mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); ++ ++ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); ++ val |= MVNETA_GMAC2_INBAND_AN_ENABLE; ++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); + } else { + val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_AN_SPEED_EN | + MVNETA_GMAC_AN_DUPLEX_EN); + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); ++ ++ val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); ++ val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE; ++ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); ++ ++ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); ++ val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE; ++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); + } + + mvneta_set_ucast_table(pp, -1); +@@ -3230,9 +3243,6 @@ static int mvneta_port_power_up(struct m + return -EINVAL; + } + +- if (pp->use_inband_status) +- ctrl |= MVNETA_GMAC2_INBAND_AN_ENABLE; +- + /* Cancel Port Reset */ + ctrl &= ~MVNETA_GMAC2_PORT_RESET; + mvreg_write(pp, MVNETA_GMAC_CTRL_2, ctrl); diff --git a/target/linux/mvebu/patches-4.4/031-mvneta-implement-ethtool-autonegotiation-control.patch b/target/linux/mvebu/patches-4.4/031-mvneta-implement-ethtool-autonegotiation-control.patch new file mode 100644 index 0000000000..626b511c45 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/031-mvneta-implement-ethtool-autonegotiation-control.patch @@ -0,0 +1,165 @@ +From: Stas Sergeev <stsp@list.ru> +Date: Wed, 2 Dec 2015 20:35:11 +0300 +Subject: [PATCH] mvneta: implement ethtool autonegotiation control + +This patch allows to do +ethtool -s eth0 autoneg off +ethtool -s eth0 autoneg on +to disable or enable autonegotiation at run-time. +Without that functionality, the only way to control the autonegotiation +is to modify the device tree. + +This is needed if you plan to use the same kernel with +different ethernet switches, the ones that support the in-band +status and the ones that not. + +CC: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> +CC: netdev@vger.kernel.org +CC: linux-kernel@vger.kernel.org + +Signed-off-by: Stas Sergeev <stsp@users.sourceforge.net> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -371,7 +371,7 @@ struct mvneta_port { + unsigned int duplex; + unsigned int speed; + unsigned int tx_csum_limit; +- int use_inband_status:1; ++ unsigned int use_inband_status:1; + + u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)]; + }; +@@ -973,6 +973,44 @@ static void mvneta_set_other_mcast_table + mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val); + } + ++static void mvneta_set_autoneg(struct mvneta_port *pp, int enable) ++{ ++ u32 val; ++ ++ if (enable) { ++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); ++ val &= ~(MVNETA_GMAC_FORCE_LINK_PASS | ++ MVNETA_GMAC_FORCE_LINK_DOWN | ++ MVNETA_GMAC_AN_FLOW_CTRL_EN); ++ val |= MVNETA_GMAC_INBAND_AN_ENABLE | ++ MVNETA_GMAC_AN_SPEED_EN | ++ MVNETA_GMAC_AN_DUPLEX_EN; ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); ++ ++ val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); ++ val |= MVNETA_GMAC_1MS_CLOCK_ENABLE; ++ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); ++ ++ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); ++ val |= MVNETA_GMAC2_INBAND_AN_ENABLE; ++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); ++ } else { ++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); ++ val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE | ++ MVNETA_GMAC_AN_SPEED_EN | ++ MVNETA_GMAC_AN_DUPLEX_EN); ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); ++ ++ val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); ++ val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE; ++ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); ++ ++ val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); ++ val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE; ++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); ++ } ++} ++ + /* This method sets defaults to the NETA port: + * Clears interrupt Cause and Mask registers. + * Clears all MAC tables. +@@ -1058,39 +1096,7 @@ static void mvneta_defaults_set(struct m + val &= ~MVNETA_PHY_POLLING_ENABLE; + mvreg_write(pp, MVNETA_UNIT_CONTROL, val); + +- if (pp->use_inband_status) { +- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); +- val &= ~(MVNETA_GMAC_FORCE_LINK_PASS | +- MVNETA_GMAC_FORCE_LINK_DOWN | +- MVNETA_GMAC_AN_FLOW_CTRL_EN); +- val |= MVNETA_GMAC_INBAND_AN_ENABLE | +- MVNETA_GMAC_AN_SPEED_EN | +- MVNETA_GMAC_AN_DUPLEX_EN; +- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); +- +- val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); +- val |= MVNETA_GMAC_1MS_CLOCK_ENABLE; +- mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); +- +- val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); +- val |= MVNETA_GMAC2_INBAND_AN_ENABLE; +- mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); +- } else { +- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); +- val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE | +- MVNETA_GMAC_AN_SPEED_EN | +- MVNETA_GMAC_AN_DUPLEX_EN); +- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); +- +- val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); +- val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE; +- mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); +- +- val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); +- val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE; +- mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); +- } +- ++ mvneta_set_autoneg(pp, pp->use_inband_status); + mvneta_set_ucast_table(pp, -1); + mvneta_set_special_mcast_table(pp, -1); + mvneta_set_other_mcast_table(pp, -1); +@@ -2956,10 +2962,43 @@ int mvneta_ethtool_get_settings(struct n + int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) + { + struct mvneta_port *pp = netdev_priv(dev); ++ struct phy_device *phydev = pp->phy_dev; + +- if (!pp->phy_dev) ++ if (!phydev) + return -ENODEV; + ++ if ((cmd->autoneg == AUTONEG_ENABLE) != pp->use_inband_status) { ++ u32 val; ++ ++ mvneta_set_autoneg(pp, cmd->autoneg == AUTONEG_ENABLE); ++ ++ if (cmd->autoneg == AUTONEG_DISABLE) { ++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); ++ val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | ++ MVNETA_GMAC_CONFIG_GMII_SPEED | ++ MVNETA_GMAC_CONFIG_FULL_DUPLEX); ++ ++ if (phydev->duplex) ++ val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; ++ ++ if (phydev->speed == SPEED_1000) ++ val |= MVNETA_GMAC_CONFIG_GMII_SPEED; ++ else if (phydev->speed == SPEED_100) ++ val |= MVNETA_GMAC_CONFIG_MII_SPEED; ++ ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); ++ } ++ ++ pp->use_inband_status = (cmd->autoneg == AUTONEG_ENABLE); ++ netdev_info(pp->dev, "autoneg status set to %i\n", ++ pp->use_inband_status); ++ ++ if (netif_running(dev)) { ++ mvneta_port_down(pp); ++ mvneta_port_up(pp); ++ } ++ } ++ + return phy_ethtool_sset(pp->phy_dev, cmd); + } + diff --git a/target/linux/mvebu/patches-4.4/032-net-mvneta-Make-the-default-queue-related-for-each-p.patch b/target/linux/mvebu/patches-4.4/032-net-mvneta-Make-the-default-queue-related-for-each-p.patch new file mode 100644 index 0000000000..54c93998f6 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/032-net-mvneta-Make-the-default-queue-related-for-each-p.patch @@ -0,0 +1,131 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Wed, 9 Dec 2015 18:23:48 +0100 +Subject: [PATCH] net: mvneta: Make the default queue related for each port + +Instead of using the same default queue for all the port. Move it in the +port struct. It will allow have a different default queue for each port. + +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -356,6 +356,7 @@ struct mvneta_port { + struct mvneta_tx_queue *txqs; + struct net_device *dev; + struct notifier_block cpu_notifier; ++ int rxq_def; + + /* Core clock */ + struct clk *clk; +@@ -819,7 +820,7 @@ static void mvneta_port_up(struct mvneta + mvreg_write(pp, MVNETA_TXQ_CMD, q_map); + + /* Enable all initialized RXQs. */ +- mvreg_write(pp, MVNETA_RXQ_CMD, BIT(rxq_def)); ++ mvreg_write(pp, MVNETA_RXQ_CMD, BIT(pp->rxq_def)); + } + + /* Stop the Ethernet port activity */ +@@ -1067,7 +1068,7 @@ static void mvneta_defaults_set(struct m + mvreg_write(pp, MVNETA_ACC_MODE, val); + + /* Update val of portCfg register accordingly with all RxQueue types */ +- val = MVNETA_PORT_CONFIG_DEFL_VALUE(rxq_def); ++ val = MVNETA_PORT_CONFIG_DEFL_VALUE(pp->rxq_def); + mvreg_write(pp, MVNETA_PORT_CONFIG, val); + + val = 0; +@@ -2101,19 +2102,19 @@ static void mvneta_set_rx_mode(struct ne + if (dev->flags & IFF_PROMISC) { + /* Accept all: Multicast + Unicast */ + mvneta_rx_unicast_promisc_set(pp, 1); +- mvneta_set_ucast_table(pp, rxq_def); +- mvneta_set_special_mcast_table(pp, rxq_def); +- mvneta_set_other_mcast_table(pp, rxq_def); ++ mvneta_set_ucast_table(pp, pp->rxq_def); ++ mvneta_set_special_mcast_table(pp, pp->rxq_def); ++ mvneta_set_other_mcast_table(pp, pp->rxq_def); + } else { + /* Accept single Unicast */ + mvneta_rx_unicast_promisc_set(pp, 0); + mvneta_set_ucast_table(pp, -1); +- mvneta_mac_addr_set(pp, dev->dev_addr, rxq_def); ++ mvneta_mac_addr_set(pp, dev->dev_addr, pp->rxq_def); + + if (dev->flags & IFF_ALLMULTI) { + /* Accept all multicast */ +- mvneta_set_special_mcast_table(pp, rxq_def); +- mvneta_set_other_mcast_table(pp, rxq_def); ++ mvneta_set_special_mcast_table(pp, pp->rxq_def); ++ mvneta_set_other_mcast_table(pp, pp->rxq_def); + } else { + /* Accept only initialized multicast */ + mvneta_set_special_mcast_table(pp, -1); +@@ -2122,7 +2123,7 @@ static void mvneta_set_rx_mode(struct ne + if (!netdev_mc_empty(dev)) { + netdev_for_each_mc_addr(ha, dev) { + mvneta_mcast_addr_set(pp, ha->addr, +- rxq_def); ++ pp->rxq_def); + } + } + } +@@ -2205,7 +2206,7 @@ static int mvneta_poll(struct napi_struc + * RX packets + */ + cause_rx_tx |= port->cause_rx_tx; +- rx_done = mvneta_rx(pp, budget, &pp->rxqs[rxq_def]); ++ rx_done = mvneta_rx(pp, budget, &pp->rxqs[pp->rxq_def]); + budget -= rx_done; + + if (budget > 0) { +@@ -2418,17 +2419,17 @@ static void mvneta_cleanup_txqs(struct m + /* Cleanup all Rx queues */ + static void mvneta_cleanup_rxqs(struct mvneta_port *pp) + { +- mvneta_rxq_deinit(pp, &pp->rxqs[rxq_def]); ++ mvneta_rxq_deinit(pp, &pp->rxqs[pp->rxq_def]); + } + + + /* Init all Rx queues */ + static int mvneta_setup_rxqs(struct mvneta_port *pp) + { +- int err = mvneta_rxq_init(pp, &pp->rxqs[rxq_def]); ++ int err = mvneta_rxq_init(pp, &pp->rxqs[pp->rxq_def]); + if (err) { + netdev_err(pp->dev, "%s: can't create rxq=%d\n", +- __func__, rxq_def); ++ __func__, pp->rxq_def); + mvneta_cleanup_rxqs(pp); + return err; + } +@@ -2634,7 +2635,7 @@ static int mvneta_set_mac_addr(struct ne + mvneta_mac_addr_set(pp, dev->dev_addr, -1); + + /* Set new addr in hw */ +- mvneta_mac_addr_set(pp, sockaddr->sa_data, rxq_def); ++ mvneta_mac_addr_set(pp, sockaddr->sa_data, pp->rxq_def); + + eth_commit_mac_addr_change(dev, addr); + return 0; +@@ -2753,7 +2754,7 @@ static void mvneta_percpu_elect(struct m + { + int online_cpu_idx, cpu, i = 0; + +- online_cpu_idx = rxq_def % num_online_cpus(); ++ online_cpu_idx = pp->rxq_def % num_online_cpus(); + + for_each_online_cpu(cpu) { + if (i == online_cpu_idx) +@@ -3363,6 +3364,8 @@ static int mvneta_probe(struct platform_ + strcmp(managed, "in-band-status") == 0); + pp->cpu_notifier.notifier_call = mvneta_percpu_notifier; + ++ pp->rxq_def = rxq_def; ++ + pp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pp->clk)) { + err = PTR_ERR(pp->clk); diff --git a/target/linux/mvebu/patches-4.4/033-net-mvneta-Associate-RX-queues-with-each-CPU.patch b/target/linux/mvebu/patches-4.4/033-net-mvneta-Associate-RX-queues-with-each-CPU.patch new file mode 100644 index 0000000000..636c6cf364 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/033-net-mvneta-Associate-RX-queues-with-each-CPU.patch @@ -0,0 +1,278 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Wed, 9 Dec 2015 18:23:49 +0100 +Subject: [PATCH] net: mvneta: Associate RX queues with each CPU + +We enable the percpu interrupt for all the CPU and we just associate a +CPU to a few queue at the neta level. The mapping between the CPUs and +the queues is static. The queues are associated to the CPU module the +number of CPUs. However currently we only use on RX queue for a given +Ethernet port. + +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -110,9 +110,16 @@ + #define MVNETA_CPU_MAP(cpu) (0x2540 + ((cpu) << 2)) + #define MVNETA_CPU_RXQ_ACCESS_ALL_MASK 0x000000ff + #define MVNETA_CPU_TXQ_ACCESS_ALL_MASK 0x0000ff00 ++#define MVNETA_CPU_RXQ_ACCESS(rxq) BIT(rxq) + #define MVNETA_RXQ_TIME_COAL_REG(q) (0x2580 + ((q) << 2)) + +-/* Exception Interrupt Port/Queue Cause register */ ++/* Exception Interrupt Port/Queue Cause register ++ * ++ * Their behavior depend of the mapping done using the PCPX2Q ++ * registers. For a given CPU if the bit associated to a queue is not ++ * set, then for the register a read from this CPU will always return ++ * 0 and a write won't do anything ++ */ + + #define MVNETA_INTR_NEW_CAUSE 0x25a0 + #define MVNETA_INTR_NEW_MASK 0x25a4 +@@ -820,7 +827,13 @@ static void mvneta_port_up(struct mvneta + mvreg_write(pp, MVNETA_TXQ_CMD, q_map); + + /* Enable all initialized RXQs. */ +- mvreg_write(pp, MVNETA_RXQ_CMD, BIT(pp->rxq_def)); ++ for (queue = 0; queue < rxq_number; queue++) { ++ struct mvneta_rx_queue *rxq = &pp->rxqs[queue]; ++ ++ if (rxq->descs != NULL) ++ q_map |= (1 << queue); ++ } ++ mvreg_write(pp, MVNETA_RXQ_CMD, q_map); + } + + /* Stop the Ethernet port activity */ +@@ -1026,6 +1039,7 @@ static void mvneta_defaults_set(struct m + int cpu; + int queue; + u32 val; ++ int max_cpu = num_present_cpus(); + + /* Clear all Cause registers */ + mvreg_write(pp, MVNETA_INTR_NEW_CAUSE, 0); +@@ -1041,13 +1055,23 @@ static void mvneta_defaults_set(struct m + /* Enable MBUS Retry bit16 */ + mvreg_write(pp, MVNETA_MBUS_RETRY, 0x20); + +- /* Set CPU queue access map - all CPUs have access to all RX +- * queues and to all TX queues ++ /* Set CPU queue access map. CPUs are assigned to the RX ++ * queues modulo their number and all the TX queues are ++ * assigned to the CPU associated to the default RX queue. + */ +- for_each_present_cpu(cpu) +- mvreg_write(pp, MVNETA_CPU_MAP(cpu), +- (MVNETA_CPU_RXQ_ACCESS_ALL_MASK | +- MVNETA_CPU_TXQ_ACCESS_ALL_MASK)); ++ for_each_present_cpu(cpu) { ++ int rxq_map = 0, txq_map = 0; ++ int rxq; ++ ++ for (rxq = 0; rxq < rxq_number; rxq++) ++ if ((rxq % max_cpu) == cpu) ++ rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq); ++ ++ if (cpu == rxq_def) ++ txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK; ++ ++ mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map); ++ } + + /* Reset RX and TX DMAs */ + mvreg_write(pp, MVNETA_PORT_RX_RESET, MVNETA_PORT_RX_DMA_RESET); +@@ -2174,6 +2198,7 @@ static int mvneta_poll(struct napi_struc + { + int rx_done = 0; + u32 cause_rx_tx; ++ int rx_queue; + struct mvneta_port *pp = netdev_priv(napi->dev); + struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports); + +@@ -2205,8 +2230,15 @@ static int mvneta_poll(struct napi_struc + /* For the case where the last mvneta_poll did not process all + * RX packets + */ ++ rx_queue = fls(((cause_rx_tx >> 8) & 0xff)); ++ + cause_rx_tx |= port->cause_rx_tx; +- rx_done = mvneta_rx(pp, budget, &pp->rxqs[pp->rxq_def]); ++ ++ if (rx_queue) { ++ rx_queue = rx_queue - 1; ++ rx_done = mvneta_rx(pp, budget, &pp->rxqs[rx_queue]); ++ } ++ + budget -= rx_done; + + if (budget > 0) { +@@ -2419,19 +2451,27 @@ static void mvneta_cleanup_txqs(struct m + /* Cleanup all Rx queues */ + static void mvneta_cleanup_rxqs(struct mvneta_port *pp) + { +- mvneta_rxq_deinit(pp, &pp->rxqs[pp->rxq_def]); ++ int queue; ++ ++ for (queue = 0; queue < txq_number; queue++) ++ mvneta_rxq_deinit(pp, &pp->rxqs[queue]); + } + + + /* Init all Rx queues */ + static int mvneta_setup_rxqs(struct mvneta_port *pp) + { +- int err = mvneta_rxq_init(pp, &pp->rxqs[pp->rxq_def]); +- if (err) { +- netdev_err(pp->dev, "%s: can't create rxq=%d\n", +- __func__, pp->rxq_def); +- mvneta_cleanup_rxqs(pp); +- return err; ++ int queue; ++ ++ for (queue = 0; queue < rxq_number; queue++) { ++ int err = mvneta_rxq_init(pp, &pp->rxqs[queue]); ++ ++ if (err) { ++ netdev_err(pp->dev, "%s: can't create rxq=%d\n", ++ __func__, queue); ++ mvneta_cleanup_rxqs(pp); ++ return err; ++ } + } + + return 0; +@@ -2455,6 +2495,19 @@ static int mvneta_setup_txqs(struct mvne + return 0; + } + ++static void mvneta_percpu_unmask_interrupt(void *arg) ++{ ++ struct mvneta_port *pp = arg; ++ ++ /* All the queue are unmasked, but actually only the ones ++ * maped to this CPU will be unmasked ++ */ ++ mvreg_write(pp, MVNETA_INTR_NEW_MASK, ++ MVNETA_RX_INTR_MASK_ALL | ++ MVNETA_TX_INTR_MASK_ALL | ++ MVNETA_MISCINTR_INTR_MASK); ++} ++ + static void mvneta_start_dev(struct mvneta_port *pp) + { + unsigned int cpu; +@@ -2472,11 +2525,10 @@ static void mvneta_start_dev(struct mvne + napi_enable(&port->napi); + } + +- /* Unmask interrupts */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, +- MVNETA_RX_INTR_MASK(rxq_number) | +- MVNETA_TX_INTR_MASK(txq_number) | +- MVNETA_MISCINTR_INTR_MASK); ++ /* Unmask interrupts. It has to be done from each CPU */ ++ for_each_online_cpu(cpu) ++ smp_call_function_single(cpu, mvneta_percpu_unmask_interrupt, ++ pp, true); + mvreg_write(pp, MVNETA_INTR_MISC_MASK, + MVNETA_CAUSE_PHY_STATUS_CHANGE | + MVNETA_CAUSE_LINK_CHANGE | +@@ -2752,22 +2804,35 @@ static void mvneta_percpu_disable(void * + + static void mvneta_percpu_elect(struct mvneta_port *pp) + { +- int online_cpu_idx, cpu, i = 0; ++ int online_cpu_idx, max_cpu, cpu, i = 0; + + online_cpu_idx = pp->rxq_def % num_online_cpus(); ++ max_cpu = num_present_cpus(); + + for_each_online_cpu(cpu) { +- if (i == online_cpu_idx) +- /* Enable per-CPU interrupt on the one CPU we +- * just elected ++ int rxq_map = 0, txq_map = 0; ++ int rxq; ++ ++ for (rxq = 0; rxq < rxq_number; rxq++) ++ if ((rxq % max_cpu) == cpu) ++ rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq); ++ ++ if (i == online_cpu_idx) { ++ /* Map the default receive queue and transmit ++ * queue to the elected CPU + */ +- smp_call_function_single(cpu, mvneta_percpu_enable, +- pp, true); +- else +- /* Disable per-CPU interrupt on all the other CPU */ +- smp_call_function_single(cpu, mvneta_percpu_disable, +- pp, true); ++ rxq_map |= MVNETA_CPU_RXQ_ACCESS(pp->rxq_def); ++ txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK; ++ } ++ mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map); ++ ++ /* Update the interrupt mask on each CPU according the ++ * new mapping ++ */ ++ smp_call_function_single(cpu, mvneta_percpu_unmask_interrupt, ++ pp, true); + i++; ++ + } + }; + +@@ -2802,12 +2867,22 @@ static int mvneta_percpu_notifier(struct + mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0); + napi_enable(&port->napi); + ++ ++ /* Enable per-CPU interrupts on the CPU that is ++ * brought up. ++ */ ++ smp_call_function_single(cpu, mvneta_percpu_enable, ++ pp, true); ++ + /* Enable per-CPU interrupt on the one CPU we care + * about. + */ + mvneta_percpu_elect(pp); + +- /* Unmask all ethernet port interrupts */ ++ /* Unmask all ethernet port interrupts, as this ++ * notifier is called for each CPU then the CPU to ++ * Queue mapping is applied ++ */ + mvreg_write(pp, MVNETA_INTR_NEW_MASK, + MVNETA_RX_INTR_MASK(rxq_number) | + MVNETA_TX_INTR_MASK(txq_number) | +@@ -2858,7 +2933,7 @@ static int mvneta_percpu_notifier(struct + static int mvneta_open(struct net_device *dev) + { + struct mvneta_port *pp = netdev_priv(dev); +- int ret; ++ int ret, cpu; + + pp->pkt_size = MVNETA_RX_PKT_SIZE(pp->dev->mtu); + pp->frag_size = SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(pp->pkt_size)) + +@@ -2888,8 +2963,13 @@ static int mvneta_open(struct net_device + */ + mvneta_percpu_disable(pp); + +- /* Elect a CPU to handle our RX queue interrupt */ +- mvneta_percpu_elect(pp); ++ /* Enable per-CPU interrupt on all the CPU to handle our RX ++ * queue interrupts ++ */ ++ for_each_online_cpu(cpu) ++ smp_call_function_single(cpu, mvneta_percpu_enable, ++ pp, true); ++ + + /* Register a CPU notifier to handle the case where our CPU + * might be taken offline. diff --git a/target/linux/mvebu/patches-4.4/034-net-mvneta-Add-naive-RSS-support.patch b/target/linux/mvebu/patches-4.4/034-net-mvneta-Add-naive-RSS-support.patch new file mode 100644 index 0000000000..629c44c8a1 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/034-net-mvneta-Add-naive-RSS-support.patch @@ -0,0 +1,191 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Wed, 9 Dec 2015 18:23:50 +0100 +Subject: [PATCH] net: mvneta: Add naive RSS support + +This patch adds the support for the RSS related ethtool +function. Currently it only uses one entry in the indirection table which +allows associating an mvneta interface to a given CPU. + +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Tested-by: Marcin Wojtas <mw@semihalf.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -261,6 +261,11 @@ + + #define MVNETA_TX_MTU_MAX 0x3ffff + ++/* The RSS lookup table actually has 256 entries but we do not use ++ * them yet ++ */ ++#define MVNETA_RSS_LU_TABLE_SIZE 1 ++ + /* TSO header size */ + #define TSO_HEADER_SIZE 128 + +@@ -382,6 +387,8 @@ struct mvneta_port { + unsigned int use_inband_status:1; + + u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)]; ++ ++ u32 indir[MVNETA_RSS_LU_TABLE_SIZE]; + }; + + /* The mvneta_tx_desc and mvneta_rx_desc structures describe the +@@ -1067,7 +1074,7 @@ static void mvneta_defaults_set(struct m + if ((rxq % max_cpu) == cpu) + rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq); + +- if (cpu == rxq_def) ++ if (cpu == pp->rxq_def) + txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK; + + mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map); +@@ -2508,6 +2515,18 @@ static void mvneta_percpu_unmask_interru + MVNETA_MISCINTR_INTR_MASK); + } + ++static void mvneta_percpu_mask_interrupt(void *arg) ++{ ++ struct mvneta_port *pp = arg; ++ ++ /* All the queue are masked, but actually only the ones ++ * maped to this CPU will be masked ++ */ ++ mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0); ++ mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0); ++ mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0); ++} ++ + static void mvneta_start_dev(struct mvneta_port *pp) + { + unsigned int cpu; +@@ -3231,6 +3250,106 @@ static int mvneta_ethtool_get_sset_count + return -EOPNOTSUPP; + } + ++static u32 mvneta_ethtool_get_rxfh_indir_size(struct net_device *dev) ++{ ++ return MVNETA_RSS_LU_TABLE_SIZE; ++} ++ ++static int mvneta_ethtool_get_rxnfc(struct net_device *dev, ++ struct ethtool_rxnfc *info, ++ u32 *rules __always_unused) ++{ ++ switch (info->cmd) { ++ case ETHTOOL_GRXRINGS: ++ info->data = rxq_number; ++ return 0; ++ case ETHTOOL_GRXFH: ++ return -EOPNOTSUPP; ++ default: ++ return -EOPNOTSUPP; ++ } ++} ++ ++static int mvneta_config_rss(struct mvneta_port *pp) ++{ ++ int cpu; ++ u32 val; ++ ++ netif_tx_stop_all_queues(pp->dev); ++ ++ for_each_online_cpu(cpu) ++ smp_call_function_single(cpu, mvneta_percpu_mask_interrupt, ++ pp, true); ++ ++ /* We have to synchronise on the napi of each CPU */ ++ for_each_online_cpu(cpu) { ++ struct mvneta_pcpu_port *pcpu_port = ++ per_cpu_ptr(pp->ports, cpu); ++ ++ napi_synchronize(&pcpu_port->napi); ++ napi_disable(&pcpu_port->napi); ++ } ++ ++ pp->rxq_def = pp->indir[0]; ++ ++ /* Update unicast mapping */ ++ mvneta_set_rx_mode(pp->dev); ++ ++ /* Update val of portCfg register accordingly with all RxQueue types */ ++ val = MVNETA_PORT_CONFIG_DEFL_VALUE(pp->rxq_def); ++ mvreg_write(pp, MVNETA_PORT_CONFIG, val); ++ ++ /* Update the elected CPU matching the new rxq_def */ ++ mvneta_percpu_elect(pp); ++ ++ /* We have to synchronise on the napi of each CPU */ ++ for_each_online_cpu(cpu) { ++ struct mvneta_pcpu_port *pcpu_port = ++ per_cpu_ptr(pp->ports, cpu); ++ ++ napi_enable(&pcpu_port->napi); ++ } ++ ++ netif_tx_start_all_queues(pp->dev); ++ ++ return 0; ++} ++ ++static int mvneta_ethtool_set_rxfh(struct net_device *dev, const u32 *indir, ++ const u8 *key, const u8 hfunc) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ /* We require at least one supported parameter to be changed ++ * and no change in any of the unsupported parameters ++ */ ++ if (key || ++ (hfunc != ETH_RSS_HASH_NO_CHANGE && hfunc != ETH_RSS_HASH_TOP)) ++ return -EOPNOTSUPP; ++ ++ if (!indir) ++ return 0; ++ ++ memcpy(pp->indir, indir, MVNETA_RSS_LU_TABLE_SIZE); ++ ++ return mvneta_config_rss(pp); ++} ++ ++static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, ++ u8 *hfunc) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ ++ if (hfunc) ++ *hfunc = ETH_RSS_HASH_TOP; ++ ++ if (!indir) ++ return 0; ++ ++ memcpy(indir, pp->indir, MVNETA_RSS_LU_TABLE_SIZE); ++ ++ return 0; ++} ++ + static const struct net_device_ops mvneta_netdev_ops = { + .ndo_open = mvneta_open, + .ndo_stop = mvneta_stop, +@@ -3255,6 +3374,10 @@ const struct ethtool_ops mvneta_eth_tool + .get_strings = mvneta_ethtool_get_strings, + .get_ethtool_stats = mvneta_ethtool_get_stats, + .get_sset_count = mvneta_ethtool_get_sset_count, ++ .get_rxfh_indir_size = mvneta_ethtool_get_rxfh_indir_size, ++ .get_rxnfc = mvneta_ethtool_get_rxnfc, ++ .get_rxfh = mvneta_ethtool_get_rxfh, ++ .set_rxfh = mvneta_ethtool_set_rxfh, + }; + + /* Initialize hw */ +@@ -3446,6 +3569,8 @@ static int mvneta_probe(struct platform_ + + pp->rxq_def = rxq_def; + ++ pp->indir[0] = rxq_def; ++ + pp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pp->clk)) { + err = PTR_ERR(pp->clk); diff --git a/target/linux/mvebu/patches-4.4/035-net-mvneta-Configure-XPS-support.patch b/target/linux/mvebu/patches-4.4/035-net-mvneta-Configure-XPS-support.patch new file mode 100644 index 0000000000..5b35b6442b --- /dev/null +++ b/target/linux/mvebu/patches-4.4/035-net-mvneta-Configure-XPS-support.patch @@ -0,0 +1,124 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Wed, 9 Dec 2015 18:23:51 +0100 +Subject: [PATCH] net: mvneta: Configure XPS support + +With this patch each CPU is associated with its own set of TX queues. + +It also setup the XPS with an initial configuration which set the +affinity matching the hardware configuration. + +Suggested-by: Arnd Bergmann <arnd@arndb.de> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -111,6 +111,7 @@ + #define MVNETA_CPU_RXQ_ACCESS_ALL_MASK 0x000000ff + #define MVNETA_CPU_TXQ_ACCESS_ALL_MASK 0x0000ff00 + #define MVNETA_CPU_RXQ_ACCESS(rxq) BIT(rxq) ++#define MVNETA_CPU_TXQ_ACCESS(txq) BIT(txq + 8) + #define MVNETA_RXQ_TIME_COAL_REG(q) (0x2580 + ((q) << 2)) + + /* Exception Interrupt Port/Queue Cause register +@@ -514,6 +515,9 @@ struct mvneta_tx_queue { + + /* DMA address of TSO headers */ + dma_addr_t tso_hdrs_phys; ++ ++ /* Affinity mask for CPUs*/ ++ cpumask_t affinity_mask; + }; + + struct mvneta_rx_queue { +@@ -1062,20 +1066,30 @@ static void mvneta_defaults_set(struct m + /* Enable MBUS Retry bit16 */ + mvreg_write(pp, MVNETA_MBUS_RETRY, 0x20); + +- /* Set CPU queue access map. CPUs are assigned to the RX +- * queues modulo their number and all the TX queues are +- * assigned to the CPU associated to the default RX queue. ++ /* Set CPU queue access map. CPUs are assigned to the RX and ++ * TX queues modulo their number. If there is only one TX ++ * queue then it is assigned to the CPU associated to the ++ * default RX queue. + */ + for_each_present_cpu(cpu) { + int rxq_map = 0, txq_map = 0; +- int rxq; ++ int rxq, txq; + + for (rxq = 0; rxq < rxq_number; rxq++) + if ((rxq % max_cpu) == cpu) + rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq); + +- if (cpu == pp->rxq_def) +- txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK; ++ for (txq = 0; txq < txq_number; txq++) ++ if ((txq % max_cpu) == cpu) ++ txq_map |= MVNETA_CPU_TXQ_ACCESS(txq); ++ ++ /* With only one TX queue we configure a special case ++ * which will allow to get all the irq on a single ++ * CPU ++ */ ++ if (txq_number == 1) ++ txq_map = (cpu == pp->rxq_def) ? ++ MVNETA_CPU_TXQ_ACCESS(1) : 0; + + mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map); + } +@@ -2362,6 +2376,8 @@ static void mvneta_rxq_deinit(struct mvn + static int mvneta_txq_init(struct mvneta_port *pp, + struct mvneta_tx_queue *txq) + { ++ int cpu; ++ + txq->size = pp->tx_ring_size; + + /* A queue must always have room for at least one skb. +@@ -2414,6 +2430,14 @@ static int mvneta_txq_init(struct mvneta + } + mvneta_tx_done_pkts_coal_set(pp, txq, txq->done_pkts_coal); + ++ /* Setup XPS mapping */ ++ if (txq_number > 1) ++ cpu = txq->id % num_present_cpus(); ++ else ++ cpu = pp->rxq_def % num_present_cpus(); ++ cpumask_set_cpu(cpu, &txq->affinity_mask); ++ netif_set_xps_queue(pp->dev, &txq->affinity_mask, txq->id); ++ + return 0; + } + +@@ -2836,13 +2860,23 @@ static void mvneta_percpu_elect(struct m + if ((rxq % max_cpu) == cpu) + rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq); + +- if (i == online_cpu_idx) { +- /* Map the default receive queue and transmit +- * queue to the elected CPU ++ if (i == online_cpu_idx) ++ /* Map the default receive queue queue to the ++ * elected CPU + */ + rxq_map |= MVNETA_CPU_RXQ_ACCESS(pp->rxq_def); +- txq_map = MVNETA_CPU_TXQ_ACCESS_ALL_MASK; +- } ++ ++ /* We update the TX queue map only if we have one ++ * queue. In this case we associate the TX queue to ++ * the CPU bound to the default RX queue ++ */ ++ if (txq_number == 1) ++ txq_map = (i == online_cpu_idx) ? ++ MVNETA_CPU_TXQ_ACCESS(1) : 0; ++ else ++ txq_map = mvreg_read(pp, MVNETA_CPU_MAP(cpu)) & ++ MVNETA_CPU_TXQ_ACCESS_ALL_MASK; ++ + mvreg_write(pp, MVNETA_CPU_MAP(cpu), rxq_map | txq_map); + + /* Update the interrupt mask on each CPU according the diff --git a/target/linux/mvebu/patches-4.4/036-net-mvneta-fix-trivial-cut-off-issue-in-mvneta_ethto.patch b/target/linux/mvebu/patches-4.4/036-net-mvneta-fix-trivial-cut-off-issue-in-mvneta_ethto.patch new file mode 100644 index 0000000000..2bd7a88032 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/036-net-mvneta-fix-trivial-cut-off-issue-in-mvneta_ethto.patch @@ -0,0 +1,46 @@ +From: Jisheng Zhang <jszhang@marvell.com> +Date: Wed, 20 Jan 2016 16:36:25 +0800 +Subject: [PATCH] net: mvneta: fix trivial cut-off issue in + mvneta_ethtool_update_stats + +When s->type is T_REG_64, the high 32bits are lost in val. This patch +fixes this trivial issue. + +Signed-off-by: Jisheng Zhang <jszhang@marvell.com> +Fixes: 9b0cdefa4cd5 ("net: mvneta: add ethtool statistics") +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3242,26 +3242,25 @@ static void mvneta_ethtool_update_stats( + const struct mvneta_statistic *s; + void __iomem *base = pp->base; + u32 high, low, val; ++ u64 val64; + int i; + + for (i = 0, s = mvneta_statistics; + s < mvneta_statistics + ARRAY_SIZE(mvneta_statistics); + s++, i++) { +- val = 0; +- + switch (s->type) { + case T_REG_32: + val = readl_relaxed(base + s->offset); ++ pp->ethtool_stats[i] += val; + break; + case T_REG_64: + /* Docs say to read low 32-bit then high */ + low = readl_relaxed(base + s->offset); + high = readl_relaxed(base + s->offset + 4); +- val = (u64)high << 32 | low; ++ val64 = (u64)high << 32 | low; ++ pp->ethtool_stats[i] += val64; + break; + } +- +- pp->ethtool_stats[i] += val; + } + } + diff --git a/target/linux/mvebu/patches-4.4/037-net-mvneta-Fix-for_each_present_cpu-usage.patch b/target/linux/mvebu/patches-4.4/037-net-mvneta-Fix-for_each_present_cpu-usage.patch new file mode 100644 index 0000000000..9ded5b71fe --- /dev/null +++ b/target/linux/mvebu/patches-4.4/037-net-mvneta-Fix-for_each_present_cpu-usage.patch @@ -0,0 +1,55 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Thu, 4 Feb 2016 22:09:23 +0100 +Subject: [PATCH] net: mvneta: Fix for_each_present_cpu usage + +This patch convert the for_each_present in on_each_cpu, instead of +applying on the present cpus it will be applied only on the online cpus. +This fix a bug reported on +http://thread.gmane.org/gmane.linux.ports.arm.kernel/468173. + +Using the macro on_each_cpu (instead of a for_each_* loop) also ensures +that all the calls will be done all at once. + +Fixes: f86428854480 ("net: mvneta: Statically assign queues to CPUs") +Reported-by: Stefan Roese <stefan.roese@gmail.com> +Suggested-by: Jisheng Zhang <jszhang@marvell.com> +Suggested-by: Russell King <rmk+kernel@arm.linux.org.uk> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -2562,7 +2562,7 @@ static void mvneta_start_dev(struct mvne + mvneta_port_enable(pp); + + /* Enable polling on the port */ +- for_each_present_cpu(cpu) { ++ for_each_online_cpu(cpu) { + struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu); + + napi_enable(&port->napi); +@@ -2587,7 +2587,7 @@ static void mvneta_stop_dev(struct mvnet + + phy_stop(pp->phy_dev); + +- for_each_present_cpu(cpu) { ++ for_each_online_cpu(cpu) { + struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu); + + napi_disable(&port->napi); +@@ -3055,13 +3055,11 @@ err_cleanup_rxqs: + static int mvneta_stop(struct net_device *dev) + { + struct mvneta_port *pp = netdev_priv(dev); +- int cpu; + + mvneta_stop_dev(pp); + mvneta_mdio_remove(pp); + unregister_cpu_notifier(&pp->cpu_notifier); +- for_each_present_cpu(cpu) +- smp_call_function_single(cpu, mvneta_percpu_disable, pp, true); ++ on_each_cpu(mvneta_percpu_disable, pp, true); + free_percpu_irq(dev->irq, pp->ports); + mvneta_cleanup_rxqs(pp); + mvneta_cleanup_txqs(pp); diff --git a/target/linux/mvebu/patches-4.4/038-net-mvneta-Fix-the-CPU-choice-in-mvneta_percpu_elect.patch b/target/linux/mvebu/patches-4.4/038-net-mvneta-Fix-the-CPU-choice-in-mvneta_percpu_elect.patch new file mode 100644 index 0000000000..4fc9ccc76a --- /dev/null +++ b/target/linux/mvebu/patches-4.4/038-net-mvneta-Fix-the-CPU-choice-in-mvneta_percpu_elect.patch @@ -0,0 +1,57 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Thu, 4 Feb 2016 22:09:24 +0100 +Subject: [PATCH] net: mvneta: Fix the CPU choice in mvneta_percpu_elect + +When passing to the management of multiple RX queue, the +mvneta_percpu_elect function was broken. The use of the modulo can lead +to elect the wrong cpu. For example with rxq_def=2, if the CPU 2 goes +offline and then online, we ended with the third RX queue activated in +the same time on CPU 0 and CPU2, which lead to a kernel crash. + +With this fix, we don't try to get "the closer" CPU if the default CPU is +gone, now we just use CPU 0 which always be there. Thanks to this, the +code becomes more readable, easier to maintain and more predicable. + +Cc: stable@vger.kernel.org +Fixes: 2dcf75e2793c ("net: mvneta: Associate RX queues with each CPU") +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -2847,9 +2847,14 @@ static void mvneta_percpu_disable(void * + + static void mvneta_percpu_elect(struct mvneta_port *pp) + { +- int online_cpu_idx, max_cpu, cpu, i = 0; ++ int elected_cpu = 0, max_cpu, cpu, i = 0; ++ ++ /* Use the cpu associated to the rxq when it is online, in all ++ * the other cases, use the cpu 0 which can't be offline. ++ */ ++ if (cpu_online(pp->rxq_def)) ++ elected_cpu = pp->rxq_def; + +- online_cpu_idx = pp->rxq_def % num_online_cpus(); + max_cpu = num_present_cpus(); + + for_each_online_cpu(cpu) { +@@ -2860,7 +2865,7 @@ static void mvneta_percpu_elect(struct m + if ((rxq % max_cpu) == cpu) + rxq_map |= MVNETA_CPU_RXQ_ACCESS(rxq); + +- if (i == online_cpu_idx) ++ if (cpu == elected_cpu) + /* Map the default receive queue queue to the + * elected CPU + */ +@@ -2871,7 +2876,7 @@ static void mvneta_percpu_elect(struct m + * the CPU bound to the default RX queue + */ + if (txq_number == 1) +- txq_map = (i == online_cpu_idx) ? ++ txq_map = (cpu == elected_cpu) ? + MVNETA_CPU_TXQ_ACCESS(1) : 0; + else + txq_map = mvreg_read(pp, MVNETA_CPU_MAP(cpu)) & diff --git a/target/linux/mvebu/patches-4.4/039-net-mvneta-Use-on_each_cpu-when-possible.patch b/target/linux/mvebu/patches-4.4/039-net-mvneta-Use-on_each_cpu-when-possible.patch new file mode 100644 index 0000000000..76257a2a71 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/039-net-mvneta-Use-on_each_cpu-when-possible.patch @@ -0,0 +1,68 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Thu, 4 Feb 2016 22:09:25 +0100 +Subject: [PATCH] net: mvneta: Use on_each_cpu when possible + +Instead of using a for_each_* loop in which we just call the +smp_call_function_single macro, it is more simple to directly use the +on_each_cpu macro. Moreover, this macro ensures that the calls will be +done all at once. + +Suggested-by: Russell King <rmk+kernel@arm.linux.org.uk> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -2553,7 +2553,7 @@ static void mvneta_percpu_mask_interrupt + + static void mvneta_start_dev(struct mvneta_port *pp) + { +- unsigned int cpu; ++ int cpu; + + mvneta_max_rx_size_set(pp, pp->pkt_size); + mvneta_txq_max_tx_size_set(pp, pp->pkt_size); +@@ -2569,9 +2569,8 @@ static void mvneta_start_dev(struct mvne + } + + /* Unmask interrupts. It has to be done from each CPU */ +- for_each_online_cpu(cpu) +- smp_call_function_single(cpu, mvneta_percpu_unmask_interrupt, +- pp, true); ++ on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true); ++ + mvreg_write(pp, MVNETA_INTR_MISC_MASK, + MVNETA_CAUSE_PHY_STATUS_CHANGE | + MVNETA_CAUSE_LINK_CHANGE | +@@ -2991,7 +2990,7 @@ static int mvneta_percpu_notifier(struct + static int mvneta_open(struct net_device *dev) + { + struct mvneta_port *pp = netdev_priv(dev); +- int ret, cpu; ++ int ret; + + pp->pkt_size = MVNETA_RX_PKT_SIZE(pp->dev->mtu); + pp->frag_size = SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(pp->pkt_size)) + +@@ -3024,9 +3023,7 @@ static int mvneta_open(struct net_device + /* Enable per-CPU interrupt on all the CPU to handle our RX + * queue interrupts + */ +- for_each_online_cpu(cpu) +- smp_call_function_single(cpu, mvneta_percpu_enable, +- pp, true); ++ on_each_cpu(mvneta_percpu_enable, pp, true); + + + /* Register a CPU notifier to handle the case where our CPU +@@ -3313,9 +3310,7 @@ static int mvneta_config_rss(struct mvn + + netif_tx_stop_all_queues(pp->dev); + +- for_each_online_cpu(cpu) +- smp_call_function_single(cpu, mvneta_percpu_mask_interrupt, +- pp, true); ++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true); + + /* We have to synchronise on the napi of each CPU */ + for_each_online_cpu(cpu) { diff --git a/target/linux/mvebu/patches-4.4/040-net-mvneta-Modify-the-queue-related-fields-from-each.patch b/target/linux/mvebu/patches-4.4/040-net-mvneta-Modify-the-queue-related-fields-from-each.patch new file mode 100644 index 0000000000..b025777df9 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/040-net-mvneta-Modify-the-queue-related-fields-from-each.patch @@ -0,0 +1,179 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Thu, 4 Feb 2016 22:09:27 +0100 +Subject: [PATCH] net: mvneta: Modify the queue related fields from each cpu + +In the MVNETA_INTR_* registers, the queues related fields are per cpu, +according to the datasheet (comment in [] are added by me): +"In a multi-CPU system, bits of RX[or TX] queues for which the access by +the reading[or writing] CPU is disabled are read as 0, and cannot be +cleared[or written]." + +That means that each time we want to manipulate these bits we had to do +it on each cpu and not only on the current cpu. + +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -1036,6 +1036,43 @@ static void mvneta_set_autoneg(struct mv + } + } + ++static void mvneta_percpu_unmask_interrupt(void *arg) ++{ ++ struct mvneta_port *pp = arg; ++ ++ /* All the queue are unmasked, but actually only the ones ++ * mapped to this CPU will be unmasked ++ */ ++ mvreg_write(pp, MVNETA_INTR_NEW_MASK, ++ MVNETA_RX_INTR_MASK_ALL | ++ MVNETA_TX_INTR_MASK_ALL | ++ MVNETA_MISCINTR_INTR_MASK); ++} ++ ++static void mvneta_percpu_mask_interrupt(void *arg) ++{ ++ struct mvneta_port *pp = arg; ++ ++ /* All the queue are masked, but actually only the ones ++ * mapped to this CPU will be masked ++ */ ++ mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0); ++ mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0); ++ mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0); ++} ++ ++static void mvneta_percpu_clear_intr_cause(void *arg) ++{ ++ struct mvneta_port *pp = arg; ++ ++ /* All the queue are cleared, but actually only the ones ++ * mapped to this CPU will be cleared ++ */ ++ mvreg_write(pp, MVNETA_INTR_NEW_CAUSE, 0); ++ mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0); ++ mvreg_write(pp, MVNETA_INTR_OLD_CAUSE, 0); ++} ++ + /* This method sets defaults to the NETA port: + * Clears interrupt Cause and Mask registers. + * Clears all MAC tables. +@@ -1053,14 +1090,10 @@ static void mvneta_defaults_set(struct m + int max_cpu = num_present_cpus(); + + /* Clear all Cause registers */ +- mvreg_write(pp, MVNETA_INTR_NEW_CAUSE, 0); +- mvreg_write(pp, MVNETA_INTR_OLD_CAUSE, 0); +- mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0); ++ on_each_cpu(mvneta_percpu_clear_intr_cause, pp, true); + + /* Mask all interrupts */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0); ++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true); + mvreg_write(pp, MVNETA_INTR_ENABLE, 0); + + /* Enable MBUS Retry bit16 */ +@@ -2526,31 +2559,6 @@ static int mvneta_setup_txqs(struct mvne + return 0; + } + +-static void mvneta_percpu_unmask_interrupt(void *arg) +-{ +- struct mvneta_port *pp = arg; +- +- /* All the queue are unmasked, but actually only the ones +- * maped to this CPU will be unmasked +- */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, +- MVNETA_RX_INTR_MASK_ALL | +- MVNETA_TX_INTR_MASK_ALL | +- MVNETA_MISCINTR_INTR_MASK); +-} +- +-static void mvneta_percpu_mask_interrupt(void *arg) +-{ +- struct mvneta_port *pp = arg; +- +- /* All the queue are masked, but actually only the ones +- * maped to this CPU will be masked +- */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0); +-} +- + static void mvneta_start_dev(struct mvneta_port *pp) + { + int cpu; +@@ -2601,13 +2609,10 @@ static void mvneta_stop_dev(struct mvnet + mvneta_port_disable(pp); + + /* Clear all ethernet port interrupts */ +- mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0); +- mvreg_write(pp, MVNETA_INTR_OLD_CAUSE, 0); ++ on_each_cpu(mvneta_percpu_clear_intr_cause, pp, true); + + /* Mask all ethernet port interrupts */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0); ++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true); + + mvneta_tx_reset(pp); + mvneta_rx_reset(pp); +@@ -2919,9 +2924,7 @@ static int mvneta_percpu_notifier(struct + } + + /* Mask all ethernet port interrupts */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0); ++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true); + napi_enable(&port->napi); + + +@@ -2936,14 +2939,8 @@ static int mvneta_percpu_notifier(struct + */ + mvneta_percpu_elect(pp); + +- /* Unmask all ethernet port interrupts, as this +- * notifier is called for each CPU then the CPU to +- * Queue mapping is applied +- */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, +- MVNETA_RX_INTR_MASK(rxq_number) | +- MVNETA_TX_INTR_MASK(txq_number) | +- MVNETA_MISCINTR_INTR_MASK); ++ /* Unmask all ethernet port interrupts */ ++ on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true); + mvreg_write(pp, MVNETA_INTR_MISC_MASK, + MVNETA_CAUSE_PHY_STATUS_CHANGE | + MVNETA_CAUSE_LINK_CHANGE | +@@ -2954,9 +2951,7 @@ static int mvneta_percpu_notifier(struct + case CPU_DOWN_PREPARE_FROZEN: + netif_tx_stop_all_queues(pp->dev); + /* Mask all ethernet port interrupts */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_OLD_MASK, 0); +- mvreg_write(pp, MVNETA_INTR_MISC_MASK, 0); ++ on_each_cpu(mvneta_percpu_mask_interrupt, pp, true); + + napi_synchronize(&port->napi); + napi_disable(&port->napi); +@@ -2972,10 +2967,7 @@ static int mvneta_percpu_notifier(struct + /* Check if a new CPU must be elected now this on is down */ + mvneta_percpu_elect(pp); + /* Unmask all ethernet port interrupts */ +- mvreg_write(pp, MVNETA_INTR_NEW_MASK, +- MVNETA_RX_INTR_MASK(rxq_number) | +- MVNETA_TX_INTR_MASK(txq_number) | +- MVNETA_MISCINTR_INTR_MASK); ++ on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true); + mvreg_write(pp, MVNETA_INTR_MISC_MASK, + MVNETA_CAUSE_PHY_STATUS_CHANGE | + MVNETA_CAUSE_LINK_CHANGE | diff --git a/target/linux/mvebu/patches-4.4/041-net-mvneta-The-mvneta_percpu_elect-function-should-b.patch b/target/linux/mvebu/patches-4.4/041-net-mvneta-The-mvneta_percpu_elect-function-should-b.patch new file mode 100644 index 0000000000..a77bef44e4 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/041-net-mvneta-The-mvneta_percpu_elect-function-should-b.patch @@ -0,0 +1,68 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Thu, 4 Feb 2016 22:09:28 +0100 +Subject: [PATCH] net: mvneta: The mvneta_percpu_elect function should be + atomic + +Electing a CPU must be done in an atomic way: it should be done after or +before the removal/insertion of a CPU and this function is not reentrant. + +During the loop of mvneta_percpu_elect we associates the queues to the +CPUs, if there is a topology change during this loop, then the mapping +between the CPUs and the queues could be wrong. During this loop the +interrupt mask is also updating for each CPUs, It should not be changed +in the same time by other part of the driver. + +This patch adds spinlock to create the needed critical sections. + +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -370,6 +370,10 @@ struct mvneta_port { + struct net_device *dev; + struct notifier_block cpu_notifier; + int rxq_def; ++ /* Protect the access to the percpu interrupt registers, ++ * ensuring that the configuration remains coherent. ++ */ ++ spinlock_t lock; + + /* Core clock */ + struct clk *clk; +@@ -2853,6 +2857,12 @@ static void mvneta_percpu_elect(struct m + { + int elected_cpu = 0, max_cpu, cpu, i = 0; + ++ /* Electing a CPU must be done in an atomic way: it should be ++ * done after or before the removal/insertion of a CPU and ++ * this function is not reentrant. ++ */ ++ spin_lock(&pp->lock); ++ + /* Use the cpu associated to the rxq when it is online, in all + * the other cases, use the cpu 0 which can't be offline. + */ +@@ -2896,6 +2906,7 @@ static void mvneta_percpu_elect(struct m + i++; + + } ++ spin_unlock(&pp->lock); + }; + + static int mvneta_percpu_notifier(struct notifier_block *nfb, +@@ -2950,8 +2961,13 @@ static int mvneta_percpu_notifier(struct + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: + netif_tx_stop_all_queues(pp->dev); ++ /* Thanks to this lock we are sure that any pending ++ * cpu election is done ++ */ ++ spin_lock(&pp->lock); + /* Mask all ethernet port interrupts */ + on_each_cpu(mvneta_percpu_mask_interrupt, pp, true); ++ spin_unlock(&pp->lock); + + napi_synchronize(&port->napi); + napi_disable(&port->napi); diff --git a/target/linux/mvebu/patches-4.4/042-net-mvneta-Fix-race-condition-during-stopping.patch b/target/linux/mvebu/patches-4.4/042-net-mvneta-Fix-race-condition-during-stopping.patch new file mode 100644 index 0000000000..9936ebf320 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/042-net-mvneta-Fix-race-condition-during-stopping.patch @@ -0,0 +1,128 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Thu, 4 Feb 2016 22:09:29 +0100 +Subject: [PATCH] net: mvneta: Fix race condition during stopping + +When stopping the port, the CPU notifier are still there whereas the +mvneta_stop_dev function calls mvneta_percpu_disable() on each CPUs. +It was possible to have a new CPU coming at this point which could be +racy. + +This patch adds a flag preventing executing the code notifier for a new +CPU when the port is stopping. It also uses the spinlock introduces +previously. To avoid the deadlock, the lock has been moved outside the +mvneta_percpu_elect function. + +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -374,6 +374,7 @@ struct mvneta_port { + * ensuring that the configuration remains coherent. + */ + spinlock_t lock; ++ bool is_stopped; + + /* Core clock */ + struct clk *clk; +@@ -2853,16 +2854,14 @@ static void mvneta_percpu_disable(void * + disable_percpu_irq(pp->dev->irq); + } + ++/* Electing a CPU must be done in an atomic way: it should be done ++ * after or before the removal/insertion of a CPU and this function is ++ * not reentrant. ++ */ + static void mvneta_percpu_elect(struct mvneta_port *pp) + { + int elected_cpu = 0, max_cpu, cpu, i = 0; + +- /* Electing a CPU must be done in an atomic way: it should be +- * done after or before the removal/insertion of a CPU and +- * this function is not reentrant. +- */ +- spin_lock(&pp->lock); +- + /* Use the cpu associated to the rxq when it is online, in all + * the other cases, use the cpu 0 which can't be offline. + */ +@@ -2906,7 +2905,6 @@ static void mvneta_percpu_elect(struct m + i++; + + } +- spin_unlock(&pp->lock); + }; + + static int mvneta_percpu_notifier(struct notifier_block *nfb, +@@ -2920,6 +2918,14 @@ static int mvneta_percpu_notifier(struct + switch (action) { + case CPU_ONLINE: + case CPU_ONLINE_FROZEN: ++ spin_lock(&pp->lock); ++ /* Configuring the driver for a new CPU while the ++ * driver is stopping is racy, so just avoid it. ++ */ ++ if (pp->is_stopped) { ++ spin_unlock(&pp->lock); ++ break; ++ } + netif_tx_stop_all_queues(pp->dev); + + /* We have to synchronise on tha napi of each CPU +@@ -2957,6 +2963,7 @@ static int mvneta_percpu_notifier(struct + MVNETA_CAUSE_LINK_CHANGE | + MVNETA_CAUSE_PSC_SYNC_CHANGE); + netif_tx_start_all_queues(pp->dev); ++ spin_unlock(&pp->lock); + break; + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: +@@ -2981,7 +2988,9 @@ static int mvneta_percpu_notifier(struct + case CPU_DEAD: + case CPU_DEAD_FROZEN: + /* Check if a new CPU must be elected now this on is down */ ++ spin_lock(&pp->lock); + mvneta_percpu_elect(pp); ++ spin_unlock(&pp->lock); + /* Unmask all ethernet port interrupts */ + on_each_cpu(mvneta_percpu_unmask_interrupt, pp, true); + mvreg_write(pp, MVNETA_INTR_MISC_MASK, +@@ -3033,7 +3042,7 @@ static int mvneta_open(struct net_device + */ + on_each_cpu(mvneta_percpu_enable, pp, true); + +- ++ pp->is_stopped = false; + /* Register a CPU notifier to handle the case where our CPU + * might be taken offline. + */ +@@ -3066,9 +3075,18 @@ static int mvneta_stop(struct net_device + { + struct mvneta_port *pp = netdev_priv(dev); + ++ /* Inform that we are stopping so we don't want to setup the ++ * driver for new CPUs in the notifiers ++ */ ++ spin_lock(&pp->lock); ++ pp->is_stopped = true; + mvneta_stop_dev(pp); + mvneta_mdio_remove(pp); + unregister_cpu_notifier(&pp->cpu_notifier); ++ /* Now that the notifier are unregistered, we can release le ++ * lock ++ */ ++ spin_unlock(&pp->lock); + on_each_cpu(mvneta_percpu_disable, pp, true); + free_percpu_irq(dev->irq, pp->ports); + mvneta_cleanup_rxqs(pp); +@@ -3339,7 +3357,9 @@ static int mvneta_config_rss(struct mvn + mvreg_write(pp, MVNETA_PORT_CONFIG, val); + + /* Update the elected CPU matching the new rxq_def */ ++ spin_lock(&pp->lock); + mvneta_percpu_elect(pp); ++ spin_unlock(&pp->lock); + + /* We have to synchronise on the napi of each CPU */ + for_each_online_cpu(cpu) { diff --git a/target/linux/mvebu/patches-4.4/043-net-mvneta-sort-the-headers-in-alphabetic-order.patch b/target/linux/mvebu/patches-4.4/043-net-mvneta-sort-the-headers-in-alphabetic-order.patch new file mode 100644 index 0000000000..502c2584d9 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/043-net-mvneta-sort-the-headers-in-alphabetic-order.patch @@ -0,0 +1,56 @@ +From: Jisheng Zhang <jszhang@marvell.com> +Date: Wed, 20 Jan 2016 19:27:22 +0800 +Subject: [PATCH] net: mvneta: sort the headers in alphabetic order + +Sorting the headers in alphabetic order will help to reduce the conflict +when adding new headers in the future. + +Signed-off-by: Jisheng Zhang <jszhang@marvell.com> +Acked-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -11,28 +11,28 @@ + * warranty of any kind, whether express or implied. + */ + +-#include <linux/kernel.h> +-#include <linux/netdevice.h> ++#include <linux/clk.h> ++#include <linux/cpu.h> + #include <linux/etherdevice.h> +-#include <linux/platform_device.h> +-#include <linux/skbuff.h> ++#include <linux/if_vlan.h> + #include <linux/inetdevice.h> +-#include <linux/mbus.h> +-#include <linux/module.h> + #include <linux/interrupt.h> +-#include <linux/if_vlan.h> +-#include <net/ip.h> +-#include <net/ipv6.h> + #include <linux/io.h> +-#include <net/tso.h> ++#include <linux/kernel.h> ++#include <linux/mbus.h> ++#include <linux/module.h> ++#include <linux/netdevice.h> + #include <linux/of.h> ++#include <linux/of_address.h> + #include <linux/of_irq.h> + #include <linux/of_mdio.h> + #include <linux/of_net.h> +-#include <linux/of_address.h> + #include <linux/phy.h> +-#include <linux/clk.h> +-#include <linux/cpu.h> ++#include <linux/platform_device.h> ++#include <linux/skbuff.h> ++#include <net/ip.h> ++#include <net/ipv6.h> ++#include <net/tso.h> + + /* Registers */ + #define MVNETA_RXQ_CONFIG_REG(q) (0x1400 + ((q) << 2)) diff --git a/target/linux/mvebu/patches-4.4/044-net-add-a-hardware-buffer-management-helper-API.patch b/target/linux/mvebu/patches-4.4/044-net-add-a-hardware-buffer-management-helper-API.patch new file mode 100644 index 0000000000..d4bc6a0088 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/044-net-add-a-hardware-buffer-management-helper-API.patch @@ -0,0 +1,159 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Mon, 14 Mar 2016 09:39:04 +0100 +Subject: [PATCH] net: add a hardware buffer management helper API + +This basic implementation allows to share code between driver using +hardware buffer management. As the code is hardware agnostic, there is +few helpers, most of the optimization brought by the an HW BM has to be +done at driver level. + +Tested-by: Sebastian Careba <nitroshift@yahoo.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + create mode 100644 include/net/hwbm.h + create mode 100644 net/core/hwbm.c + +--- /dev/null ++++ b/include/net/hwbm.h +@@ -0,0 +1,28 @@ ++#ifndef _HWBM_H ++#define _HWBM_H ++ ++struct hwbm_pool { ++ /* Capacity of the pool */ ++ int size; ++ /* Size of the buffers managed */ ++ int frag_size; ++ /* Number of buffers currently used by this pool */ ++ int buf_num; ++ /* constructor called during alocation */ ++ int (*construct)(struct hwbm_pool *bm_pool, void *buf); ++ /* protect acces to the buffer counter*/ ++ spinlock_t lock; ++ /* private data */ ++ void *priv; ++}; ++#ifdef CONFIG_HWBM ++void hwbm_buf_free(struct hwbm_pool *bm_pool, void *buf); ++int hwbm_pool_refill(struct hwbm_pool *bm_pool, gfp_t gfp); ++int hwbm_pool_add(struct hwbm_pool *bm_pool, unsigned int buf_num, gfp_t gfp); ++#else ++void hwbm_buf_free(struct hwbm_pool *bm_pool, void *buf) {} ++int hwbm_pool_refill(struct hwbm_pool *bm_pool, gfp_t gfp) { return 0; } ++int hwbm_pool_add(struct hwbm_pool *bm_pool, unsigned int buf_num, gfp_t gfp) ++{ return 0; } ++#endif /* CONFIG_HWBM */ ++#endif /* _HWBM_H */ +--- a/net/Kconfig ++++ b/net/Kconfig +@@ -259,6 +259,9 @@ config XPS + depends on SMP + default y + ++config HWBM ++ bool ++ + config CGROUP_NET_PRIO + bool "Network priority cgroup" + depends on CGROUPS +--- a/net/core/Makefile ++++ b/net/core/Makefile +@@ -14,6 +14,7 @@ obj-y += dev.o ethtool.o dev_addr_ + obj-$(CONFIG_SOCK_DIAG) += sock_diag.o + obj-$(CONFIG_XFRM) += flow.o + obj-y += net-sysfs.o ++obj-$(CONFIG_HWBM) += hwbm.o + obj-$(CONFIG_PROC_FS) += net-procfs.o + obj-$(CONFIG_NET_PKTGEN) += pktgen.o + obj-$(CONFIG_NETPOLL) += netpoll.o +--- /dev/null ++++ b/net/core/hwbm.c +@@ -0,0 +1,87 @@ ++/* Support for hardware buffer manager. ++ * ++ * Copyright (C) 2016 Marvell ++ * ++ * Gregory CLEMENT <gregory.clement@free-electrons.com> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ */ ++#include <linux/kernel.h> ++#include <linux/printk.h> ++#include <linux/skbuff.h> ++#include <net/hwbm.h> ++ ++void hwbm_buf_free(struct hwbm_pool *bm_pool, void *buf) ++{ ++ if (likely(bm_pool->frag_size <= PAGE_SIZE)) ++ skb_free_frag(buf); ++ else ++ kfree(buf); ++} ++EXPORT_SYMBOL_GPL(hwbm_buf_free); ++ ++/* Refill processing for HW buffer management */ ++int hwbm_pool_refill(struct hwbm_pool *bm_pool, gfp_t gfp) ++{ ++ int frag_size = bm_pool->frag_size; ++ void *buf; ++ ++ if (likely(frag_size <= PAGE_SIZE)) ++ buf = netdev_alloc_frag(frag_size); ++ else ++ buf = kmalloc(frag_size, gfp); ++ ++ if (!buf) ++ return -ENOMEM; ++ ++ if (bm_pool->construct) ++ if (bm_pool->construct(bm_pool, buf)) { ++ hwbm_buf_free(bm_pool, buf); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(hwbm_pool_refill); ++ ++int hwbm_pool_add(struct hwbm_pool *bm_pool, unsigned int buf_num, gfp_t gfp) ++{ ++ int err, i; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&bm_pool->lock, flags); ++ if (bm_pool->buf_num == bm_pool->size) { ++ pr_warn("pool already filled\n"); ++ return bm_pool->buf_num; ++ } ++ ++ if (buf_num + bm_pool->buf_num > bm_pool->size) { ++ pr_warn("cannot allocate %d buffers for pool\n", ++ buf_num); ++ return 0; ++ } ++ ++ if ((buf_num + bm_pool->buf_num) < bm_pool->buf_num) { ++ pr_warn("Adding %d buffers to the %d current buffers will overflow\n", ++ buf_num, bm_pool->buf_num); ++ return 0; ++ } ++ ++ for (i = 0; i < buf_num; i++) { ++ err = hwbm_pool_refill(bm_pool, gfp); ++ if (err < 0) ++ break; ++ } ++ ++ /* Update BM driver with number of buffers added to pool */ ++ bm_pool->buf_num += i; ++ ++ pr_debug("hwpm pool: %d of %d buffers added\n", i, buf_num); ++ spin_unlock_irqrestore(&bm_pool->lock, flags); ++ ++ return i; ++} ++EXPORT_SYMBOL_GPL(hwbm_pool_add); diff --git a/target/linux/mvebu/patches-4.4/045-net-mvneta-bm-add-support-for-hardware-buffer-manage.patch b/target/linux/mvebu/patches-4.4/045-net-mvneta-bm-add-support-for-hardware-buffer-manage.patch new file mode 100644 index 0000000000..d343b3983f --- /dev/null +++ b/target/linux/mvebu/patches-4.4/045-net-mvneta-bm-add-support-for-hardware-buffer-manage.patch @@ -0,0 +1,1684 @@ +From: Marcin Wojtas <mw@semihalf.com> +Date: Mon, 14 Mar 2016 09:39:03 +0100 +Subject: [PATCH] net: mvneta: bm: add support for hardware buffer management + +Buffer manager (BM) is a dedicated hardware unit that can be used by all +ethernet ports of Armada XP and 38x SoC's. It allows to offload CPU on RX +path by sparing DRAM access on refilling buffer pool, hardware-based +filling of descriptor ring data and better memory utilization due to HW +arbitration for using 'short' pools for small packets. + +Tests performed with A388 SoC working as a network bridge between two +packet generators showed increase of maximum processed 64B packets by +~20k (~555k packets with BM enabled vs ~535 packets without BM). Also +when pushing 1500B-packets with a line rate achieved, CPU load decreased +from around 25% without BM to 20% with BM. + +BM comprise up to 4 buffer pointers' (BP) rings kept in DRAM, which +are called external BP pools - BPPE. Allocating and releasing buffer +pointers (BP) to/from BPPE is performed indirectly by write/read access +to a dedicated internal SRAM, where internal BP pools (BPPI) are placed. +BM hardware controls status of BPPE automatically, as well as assigning +proper buffers to RX descriptors. For more details please refer to +Functional Specification of Armada XP or 38x SoC. + +In order to enable support for a separate hardware block, common for all +ports, a new driver has to be implemented ('mvneta_bm'). It provides +initialization sequence of address space, clocks, registers, SRAM, +empty pools' structures and also obtaining optional configuration +from DT (please refer to device tree binding documentation). mvneta_bm +exposes also a necessary API to mvneta driver, as well as a dedicated +structure with BM information (bm_priv), whose presence is used as a +flag notifying of BM usage by port. It has to be ensured that mvneta_bm +probe is executed prior to the ones in ports' driver. In case BM is not +used or its probe fails, mvneta falls back to use software buffer +management. + +A sequence executed in mvneta_probe function is modified in order to have +an access to needed resources before possible port's BM initialization is +done. According to port-pools mapping provided by DT appropriate registers +are configured and the buffer pools are filled. RX path is modified +accordingly. Becaues the hardware allows a wide variety of configuration +options, following assumptions are made: +* using BM mechanisms can be selectively disabled/enabled basing + on DT configuration among the ports +* 'long' pool's single buffer size is tied to port's MTU +* using 'long' pool by port is obligatory and it cannot be shared +* using 'short' pool for smaller packets is optional +* one 'short' pool can be shared among all ports + +This commit enables hardware buffer management operation cooperating with +existing mvneta driver. New device tree binding documentation is added and +the one of mvneta is updated accordingly. + +[gregory.clement@free-electrons.com: removed the suspend/resume part] + +Signed-off-by: Marcin Wojtas <mw@semihalf.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + create mode 100644 Documentation/devicetree/bindings/net/marvell-neta-bm.txt + create mode 100644 drivers/net/ethernet/marvell/mvneta_bm.c + create mode 100644 drivers/net/ethernet/marvell/mvneta_bm.h + +--- a/Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt ++++ b/Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt +@@ -13,15 +13,30 @@ Optional properties: + Value is presented in bytes. If not used, by default 1600B is set for + "marvell,armada-370-neta" and 9800B for others. + ++Optional properties (valid only for Armada XP/38x): ++ ++- buffer-manager: a phandle to a buffer manager node. Please refer to ++ Documentation/devicetree/bindings/net/marvell-neta-bm.txt ++- bm,pool-long: ID of a pool, that will accept all packets of a size ++ higher than 'short' pool's threshold (if set) and up to MTU value. ++ Obligatory, when the port is supposed to use hardware ++ buffer management. ++- bm,pool-short: ID of a pool, that will be used for accepting ++ packets of a size lower than given threshold. If not set, the port ++ will use a single 'long' pool for all packets, as defined above. ++ + Example: + +-ethernet@d0070000 { ++ethernet@70000 { + compatible = "marvell,armada-370-neta"; +- reg = <0xd0070000 0x2500>; ++ reg = <0x70000 0x2500>; + interrupts = <8>; + clocks = <&gate_clk 4>; + tx-csum-limit = <9800> + status = "okay"; + phy = <&phy0>; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <0>; ++ bm,pool-short = <1>; + }; +--- /dev/null ++++ b/Documentation/devicetree/bindings/net/marvell-neta-bm.txt +@@ -0,0 +1,49 @@ ++* Marvell Armada 380/XP Buffer Manager driver (BM) ++ ++Required properties: ++ ++- compatible: should be "marvell,armada-380-neta-bm". ++- reg: address and length of the register set for the device. ++- clocks: a pointer to the reference clock for this device. ++- internal-mem: a phandle to BM internal SRAM definition. ++ ++Optional properties (port): ++ ++- pool<0 : 3>,capacity: size of external buffer pointers' ring maintained ++ in DRAM. Can be set for each pool (id 0 : 3) separately. The value has ++ to be chosen between 128 and 16352 and it also has to be aligned to 32. ++ Otherwise the driver would adjust a given number or choose default if ++ not set. ++- pool<0 : 3>,pkt-size: maximum size of a packet accepted by a given buffer ++ pointers' pool (id 0 : 3). It will be taken into consideration only when pool ++ type is 'short'. For 'long' ones it would be overridden by port's MTU. ++ If not set a driver will choose a default value. ++ ++In order to see how to hook the BM to a given ethernet port, please ++refer to Documentation/devicetree/bindings/net/marvell-armada-370-neta.txt. ++ ++Example: ++ ++- main node: ++ ++bm: bm@c8000 { ++ compatible = "marvell,armada-380-neta-bm"; ++ reg = <0xc8000 0xac>; ++ clocks = <&gateclk 13>; ++ internal-mem = <&bm_bppi>; ++ status = "okay"; ++ pool2,capacity = <4096>; ++ pool1,pkt-size = <512>; ++}; ++ ++- internal SRAM node: ++ ++bm_bppi: bm-bppi { ++ compatible = "mmio-sram"; ++ reg = <MBUS_ID(0x0c, 0x04) 0 0x100000>; ++ ranges = <0 MBUS_ID(0x0c, 0x04) 0 0x100000>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ clocks = <&gateclk 13>; ++ status = "okay"; ++}; +--- a/drivers/net/ethernet/marvell/Kconfig ++++ b/drivers/net/ethernet/marvell/Kconfig +@@ -40,6 +40,19 @@ config MVMDIO + + This driver is used by the MV643XX_ETH and MVNETA drivers. + ++config MVNETA_BM ++ tristate "Marvell Armada 38x/XP network interface BM support" ++ depends on MVNETA ++ ---help--- ++ This driver supports auxiliary block of the network ++ interface units in the Marvell ARMADA XP and ARMADA 38x SoC ++ family, which is called buffer manager. ++ ++ This driver, when enabled, strictly cooperates with mvneta ++ driver and is common for all network ports of the devices, ++ even for Armada 370 SoC, which doesn't support hardware ++ buffer management. ++ + config MVNETA + tristate "Marvell Armada 370/38x/XP network interface support" + depends on PLAT_ORION +--- a/drivers/net/ethernet/marvell/Makefile ++++ b/drivers/net/ethernet/marvell/Makefile +@@ -4,6 +4,7 @@ + + obj-$(CONFIG_MVMDIO) += mvmdio.o + obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o ++obj-$(CONFIG_MVNETA_BM) += mvneta_bm.o + obj-$(CONFIG_MVNETA) += mvneta.o + obj-$(CONFIG_MVPP2) += mvpp2.o + obj-$(CONFIG_PXA168_ETH) += pxa168_eth.o +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -30,6 +30,7 @@ + #include <linux/phy.h> + #include <linux/platform_device.h> + #include <linux/skbuff.h> ++#include "mvneta_bm.h" + #include <net/ip.h> + #include <net/ipv6.h> + #include <net/tso.h> +@@ -37,6 +38,10 @@ + /* Registers */ + #define MVNETA_RXQ_CONFIG_REG(q) (0x1400 + ((q) << 2)) + #define MVNETA_RXQ_HW_BUF_ALLOC BIT(0) ++#define MVNETA_RXQ_SHORT_POOL_ID_SHIFT 4 ++#define MVNETA_RXQ_SHORT_POOL_ID_MASK 0x30 ++#define MVNETA_RXQ_LONG_POOL_ID_SHIFT 6 ++#define MVNETA_RXQ_LONG_POOL_ID_MASK 0xc0 + #define MVNETA_RXQ_PKT_OFFSET_ALL_MASK (0xf << 8) + #define MVNETA_RXQ_PKT_OFFSET_MASK(offs) ((offs) << 8) + #define MVNETA_RXQ_THRESHOLD_REG(q) (0x14c0 + ((q) << 2)) +@@ -50,6 +55,9 @@ + #define MVNETA_RXQ_STATUS_UPDATE_REG(q) (0x1500 + ((q) << 2)) + #define MVNETA_RXQ_ADD_NON_OCCUPIED_SHIFT 16 + #define MVNETA_RXQ_ADD_NON_OCCUPIED_MAX 255 ++#define MVNETA_PORT_POOL_BUFFER_SZ_REG(pool) (0x1700 + ((pool) << 2)) ++#define MVNETA_PORT_POOL_BUFFER_SZ_SHIFT 3 ++#define MVNETA_PORT_POOL_BUFFER_SZ_MASK 0xfff8 + #define MVNETA_PORT_RX_RESET 0x1cc0 + #define MVNETA_PORT_RX_DMA_RESET BIT(0) + #define MVNETA_PHY_ADDR 0x2000 +@@ -107,6 +115,7 @@ + #define MVNETA_GMAC_CLOCK_DIVIDER 0x24f4 + #define MVNETA_GMAC_1MS_CLOCK_ENABLE BIT(31) + #define MVNETA_ACC_MODE 0x2500 ++#define MVNETA_BM_ADDRESS 0x2504 + #define MVNETA_CPU_MAP(cpu) (0x2540 + ((cpu) << 2)) + #define MVNETA_CPU_RXQ_ACCESS_ALL_MASK 0x000000ff + #define MVNETA_CPU_TXQ_ACCESS_ALL_MASK 0x0000ff00 +@@ -253,7 +262,10 @@ + #define MVNETA_CPU_D_CACHE_LINE_SIZE 32 + #define MVNETA_TX_CSUM_DEF_SIZE 1600 + #define MVNETA_TX_CSUM_MAX_SIZE 9800 +-#define MVNETA_ACC_MODE_EXT 1 ++#define MVNETA_ACC_MODE_EXT1 1 ++#define MVNETA_ACC_MODE_EXT2 2 ++ ++#define MVNETA_MAX_DECODE_WIN 6 + + /* Timeout constants */ + #define MVNETA_TX_DISABLE_TIMEOUT_MSEC 1000 +@@ -293,7 +305,8 @@ + ((addr >= txq->tso_hdrs_phys) && \ + (addr < txq->tso_hdrs_phys + txq->size * TSO_HEADER_SIZE)) + +-#define MVNETA_RX_BUF_SIZE(pkt_size) ((pkt_size) + NET_SKB_PAD) ++#define MVNETA_RX_GET_BM_POOL_ID(rxd) \ ++ (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT) + + struct mvneta_statistic { + unsigned short offset; +@@ -359,6 +372,7 @@ struct mvneta_pcpu_port { + }; + + struct mvneta_port { ++ u8 id; + struct mvneta_pcpu_port __percpu *ports; + struct mvneta_pcpu_stats __percpu *stats; + +@@ -392,6 +406,11 @@ struct mvneta_port { + unsigned int tx_csum_limit; + unsigned int use_inband_status:1; + ++ struct mvneta_bm *bm_priv; ++ struct mvneta_bm_pool *pool_long; ++ struct mvneta_bm_pool *pool_short; ++ int bm_win_id; ++ + u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)]; + + u32 indir[MVNETA_RSS_LU_TABLE_SIZE]; +@@ -417,6 +436,8 @@ struct mvneta_port { + #define MVNETA_TX_L4_CSUM_NOT BIT(31) + + #define MVNETA_RXD_ERR_CRC 0x0 ++#define MVNETA_RXD_BM_POOL_SHIFT 13 ++#define MVNETA_RXD_BM_POOL_MASK (BIT(13) | BIT(14)) + #define MVNETA_RXD_ERR_SUMMARY BIT(16) + #define MVNETA_RXD_ERR_OVERRUN BIT(17) + #define MVNETA_RXD_ERR_LEN BIT(18) +@@ -561,6 +582,9 @@ static int rxq_def; + + static int rx_copybreak __read_mostly = 256; + ++/* HW BM need that each port be identify by a unique ID */ ++static int global_port_id; ++ + #define MVNETA_DRIVER_NAME "mvneta" + #define MVNETA_DRIVER_VERSION "1.0" + +@@ -827,6 +851,214 @@ static void mvneta_rxq_bm_disable(struct + mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val); + } + ++/* Enable buffer management (BM) */ ++static void mvneta_rxq_bm_enable(struct mvneta_port *pp, ++ struct mvneta_rx_queue *rxq) ++{ ++ u32 val; ++ ++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id)); ++ val |= MVNETA_RXQ_HW_BUF_ALLOC; ++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val); ++} ++ ++/* Notify HW about port's assignment of pool for bigger packets */ ++static void mvneta_rxq_long_pool_set(struct mvneta_port *pp, ++ struct mvneta_rx_queue *rxq) ++{ ++ u32 val; ++ ++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id)); ++ val &= ~MVNETA_RXQ_LONG_POOL_ID_MASK; ++ val |= (pp->pool_long->id << MVNETA_RXQ_LONG_POOL_ID_SHIFT); ++ ++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val); ++} ++ ++/* Notify HW about port's assignment of pool for smaller packets */ ++static void mvneta_rxq_short_pool_set(struct mvneta_port *pp, ++ struct mvneta_rx_queue *rxq) ++{ ++ u32 val; ++ ++ val = mvreg_read(pp, MVNETA_RXQ_CONFIG_REG(rxq->id)); ++ val &= ~MVNETA_RXQ_SHORT_POOL_ID_MASK; ++ val |= (pp->pool_short->id << MVNETA_RXQ_SHORT_POOL_ID_SHIFT); ++ ++ mvreg_write(pp, MVNETA_RXQ_CONFIG_REG(rxq->id), val); ++} ++ ++/* Set port's receive buffer size for assigned BM pool */ ++static inline void mvneta_bm_pool_bufsize_set(struct mvneta_port *pp, ++ int buf_size, ++ u8 pool_id) ++{ ++ u32 val; ++ ++ if (!IS_ALIGNED(buf_size, 8)) { ++ dev_warn(pp->dev->dev.parent, ++ "illegal buf_size value %d, round to %d\n", ++ buf_size, ALIGN(buf_size, 8)); ++ buf_size = ALIGN(buf_size, 8); ++ } ++ ++ val = mvreg_read(pp, MVNETA_PORT_POOL_BUFFER_SZ_REG(pool_id)); ++ val |= buf_size & MVNETA_PORT_POOL_BUFFER_SZ_MASK; ++ mvreg_write(pp, MVNETA_PORT_POOL_BUFFER_SZ_REG(pool_id), val); ++} ++ ++/* Configure MBUS window in order to enable access BM internal SRAM */ ++static int mvneta_mbus_io_win_set(struct mvneta_port *pp, u32 base, u32 wsize, ++ u8 target, u8 attr) ++{ ++ u32 win_enable, win_protect; ++ int i; ++ ++ win_enable = mvreg_read(pp, MVNETA_BASE_ADDR_ENABLE); ++ ++ if (pp->bm_win_id < 0) { ++ /* Find first not occupied window */ ++ for (i = 0; i < MVNETA_MAX_DECODE_WIN; i++) { ++ if (win_enable & (1 << i)) { ++ pp->bm_win_id = i; ++ break; ++ } ++ } ++ if (i == MVNETA_MAX_DECODE_WIN) ++ return -ENOMEM; ++ } else { ++ i = pp->bm_win_id; ++ } ++ ++ mvreg_write(pp, MVNETA_WIN_BASE(i), 0); ++ mvreg_write(pp, MVNETA_WIN_SIZE(i), 0); ++ ++ if (i < 4) ++ mvreg_write(pp, MVNETA_WIN_REMAP(i), 0); ++ ++ mvreg_write(pp, MVNETA_WIN_BASE(i), (base & 0xffff0000) | ++ (attr << 8) | target); ++ ++ mvreg_write(pp, MVNETA_WIN_SIZE(i), (wsize - 1) & 0xffff0000); ++ ++ win_protect = mvreg_read(pp, MVNETA_ACCESS_PROTECT_ENABLE); ++ win_protect |= 3 << (2 * i); ++ mvreg_write(pp, MVNETA_ACCESS_PROTECT_ENABLE, win_protect); ++ ++ win_enable &= ~(1 << i); ++ mvreg_write(pp, MVNETA_BASE_ADDR_ENABLE, win_enable); ++ ++ return 0; ++} ++ ++/* Assign and initialize pools for port. In case of fail ++ * buffer manager will remain disabled for current port. ++ */ ++static int mvneta_bm_port_init(struct platform_device *pdev, ++ struct mvneta_port *pp) ++{ ++ struct device_node *dn = pdev->dev.of_node; ++ u32 long_pool_id, short_pool_id, wsize; ++ u8 target, attr; ++ int err; ++ ++ /* Get BM window information */ ++ err = mvebu_mbus_get_io_win_info(pp->bm_priv->bppi_phys_addr, &wsize, ++ &target, &attr); ++ if (err < 0) ++ return err; ++ ++ pp->bm_win_id = -1; ++ ++ /* Open NETA -> BM window */ ++ err = mvneta_mbus_io_win_set(pp, pp->bm_priv->bppi_phys_addr, wsize, ++ target, attr); ++ if (err < 0) { ++ netdev_info(pp->dev, "fail to configure mbus window to BM\n"); ++ return err; ++ } ++ ++ if (of_property_read_u32(dn, "bm,pool-long", &long_pool_id)) { ++ netdev_info(pp->dev, "missing long pool id\n"); ++ return -EINVAL; ++ } ++ ++ /* Create port's long pool depending on mtu */ ++ pp->pool_long = mvneta_bm_pool_use(pp->bm_priv, long_pool_id, ++ MVNETA_BM_LONG, pp->id, ++ MVNETA_RX_PKT_SIZE(pp->dev->mtu)); ++ if (!pp->pool_long) { ++ netdev_info(pp->dev, "fail to obtain long pool for port\n"); ++ return -ENOMEM; ++ } ++ ++ pp->pool_long->port_map |= 1 << pp->id; ++ ++ mvneta_bm_pool_bufsize_set(pp, pp->pool_long->buf_size, ++ pp->pool_long->id); ++ ++ /* If short pool id is not defined, assume using single pool */ ++ if (of_property_read_u32(dn, "bm,pool-short", &short_pool_id)) ++ short_pool_id = long_pool_id; ++ ++ /* Create port's short pool */ ++ pp->pool_short = mvneta_bm_pool_use(pp->bm_priv, short_pool_id, ++ MVNETA_BM_SHORT, pp->id, ++ MVNETA_BM_SHORT_PKT_SIZE); ++ if (!pp->pool_short) { ++ netdev_info(pp->dev, "fail to obtain short pool for port\n"); ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id); ++ return -ENOMEM; ++ } ++ ++ if (short_pool_id != long_pool_id) { ++ pp->pool_short->port_map |= 1 << pp->id; ++ mvneta_bm_pool_bufsize_set(pp, pp->pool_short->buf_size, ++ pp->pool_short->id); ++ } ++ ++ return 0; ++} ++ ++/* Update settings of a pool for bigger packets */ ++static void mvneta_bm_update_mtu(struct mvneta_port *pp, int mtu) ++{ ++ struct mvneta_bm_pool *bm_pool = pp->pool_long; ++ int num; ++ ++ /* Release all buffers from long pool */ ++ mvneta_bm_bufs_free(pp->bm_priv, bm_pool, 1 << pp->id); ++ if (bm_pool->buf_num) { ++ WARN(1, "cannot free all buffers in pool %d\n", ++ bm_pool->id); ++ goto bm_mtu_err; ++ } ++ ++ bm_pool->pkt_size = MVNETA_RX_PKT_SIZE(mtu); ++ bm_pool->buf_size = MVNETA_RX_BUF_SIZE(bm_pool->pkt_size); ++ bm_pool->frag_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + ++ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(bm_pool->pkt_size)); ++ ++ /* Fill entire long pool */ ++ num = mvneta_bm_bufs_add(pp->bm_priv, bm_pool, bm_pool->size); ++ if (num != bm_pool->size) { ++ WARN(1, "pool %d: %d of %d allocated\n", ++ bm_pool->id, num, bm_pool->size); ++ goto bm_mtu_err; ++ } ++ mvneta_bm_pool_bufsize_set(pp, bm_pool->buf_size, bm_pool->id); ++ ++ return; ++ ++bm_mtu_err: ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id); ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short, 1 << pp->id); ++ ++ pp->bm_priv = NULL; ++ mvreg_write(pp, MVNETA_ACC_MODE, MVNETA_ACC_MODE_EXT1); ++ netdev_info(pp->dev, "fail to update MTU, fall back to software BM\n"); ++} ++ + /* Start the Ethernet port RX and TX activity */ + static void mvneta_port_up(struct mvneta_port *pp) + { +@@ -1147,9 +1379,17 @@ static void mvneta_defaults_set(struct m + mvreg_write(pp, MVNETA_PORT_RX_RESET, 0); + + /* Set Port Acceleration Mode */ +- val = MVNETA_ACC_MODE_EXT; ++ if (pp->bm_priv) ++ /* HW buffer management + legacy parser */ ++ val = MVNETA_ACC_MODE_EXT2; ++ else ++ /* SW buffer management + legacy parser */ ++ val = MVNETA_ACC_MODE_EXT1; + mvreg_write(pp, MVNETA_ACC_MODE, val); + ++ if (pp->bm_priv) ++ mvreg_write(pp, MVNETA_BM_ADDRESS, pp->bm_priv->bppi_phys_addr); ++ + /* Update val of portCfg register accordingly with all RxQueue types */ + val = MVNETA_PORT_CONFIG_DEFL_VALUE(pp->rxq_def); + mvreg_write(pp, MVNETA_PORT_CONFIG, val); +@@ -1516,23 +1756,25 @@ static void mvneta_txq_done(struct mvnet + } + } + +-static void *mvneta_frag_alloc(const struct mvneta_port *pp) ++void *mvneta_frag_alloc(unsigned int frag_size) + { +- if (likely(pp->frag_size <= PAGE_SIZE)) +- return netdev_alloc_frag(pp->frag_size); ++ if (likely(frag_size <= PAGE_SIZE)) ++ return netdev_alloc_frag(frag_size); + else +- return kmalloc(pp->frag_size, GFP_ATOMIC); ++ return kmalloc(frag_size, GFP_ATOMIC); + } ++EXPORT_SYMBOL_GPL(mvneta_frag_alloc); + +-static void mvneta_frag_free(const struct mvneta_port *pp, void *data) ++void mvneta_frag_free(unsigned int frag_size, void *data) + { +- if (likely(pp->frag_size <= PAGE_SIZE)) ++ if (likely(frag_size <= PAGE_SIZE)) + skb_free_frag(data); + else + kfree(data); + } ++EXPORT_SYMBOL_GPL(mvneta_frag_free); + +-/* Refill processing */ ++/* Refill processing for SW buffer management */ + static int mvneta_rx_refill(struct mvneta_port *pp, + struct mvneta_rx_desc *rx_desc) + +@@ -1540,7 +1782,7 @@ static int mvneta_rx_refill(struct mvnet + dma_addr_t phys_addr; + void *data; + +- data = mvneta_frag_alloc(pp); ++ data = mvneta_frag_alloc(pp->frag_size); + if (!data) + return -ENOMEM; + +@@ -1548,7 +1790,7 @@ static int mvneta_rx_refill(struct mvnet + MVNETA_RX_BUF_SIZE(pp->pkt_size), + DMA_FROM_DEVICE); + if (unlikely(dma_mapping_error(pp->dev->dev.parent, phys_addr))) { +- mvneta_frag_free(pp, data); ++ mvneta_frag_free(pp->frag_size, data); + return -ENOMEM; + } + +@@ -1594,22 +1836,156 @@ static void mvneta_rxq_drop_pkts(struct + int rx_done, i; + + rx_done = mvneta_rxq_busy_desc_num_get(pp, rxq); ++ if (rx_done) ++ mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done); ++ ++ if (pp->bm_priv) { ++ for (i = 0; i < rx_done; i++) { ++ struct mvneta_rx_desc *rx_desc = ++ mvneta_rxq_next_desc_get(rxq); ++ u8 pool_id = MVNETA_RX_GET_BM_POOL_ID(rx_desc); ++ struct mvneta_bm_pool *bm_pool; ++ ++ bm_pool = &pp->bm_priv->bm_pools[pool_id]; ++ /* Return dropped buffer to the pool */ ++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool, ++ rx_desc->buf_phys_addr); ++ } ++ return; ++ } ++ + for (i = 0; i < rxq->size; i++) { + struct mvneta_rx_desc *rx_desc = rxq->descs + i; + void *data = (void *)rx_desc->buf_cookie; + + dma_unmap_single(pp->dev->dev.parent, rx_desc->buf_phys_addr, + MVNETA_RX_BUF_SIZE(pp->pkt_size), DMA_FROM_DEVICE); +- mvneta_frag_free(pp, data); ++ mvneta_frag_free(pp->frag_size, data); + } ++} + +- if (rx_done) +- mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done); ++/* Main rx processing when using software buffer management */ ++static int mvneta_rx_swbm(struct mvneta_port *pp, int rx_todo, ++ struct mvneta_rx_queue *rxq) ++{ ++ struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports); ++ struct net_device *dev = pp->dev; ++ int rx_done; ++ u32 rcvd_pkts = 0; ++ u32 rcvd_bytes = 0; ++ ++ /* Get number of received packets */ ++ rx_done = mvneta_rxq_busy_desc_num_get(pp, rxq); ++ ++ if (rx_todo > rx_done) ++ rx_todo = rx_done; ++ ++ rx_done = 0; ++ ++ /* Fairness NAPI loop */ ++ while (rx_done < rx_todo) { ++ struct mvneta_rx_desc *rx_desc = mvneta_rxq_next_desc_get(rxq); ++ struct sk_buff *skb; ++ unsigned char *data; ++ dma_addr_t phys_addr; ++ u32 rx_status, frag_size; ++ int rx_bytes, err; ++ ++ rx_done++; ++ rx_status = rx_desc->status; ++ rx_bytes = rx_desc->data_size - (ETH_FCS_LEN + MVNETA_MH_SIZE); ++ data = (unsigned char *)rx_desc->buf_cookie; ++ phys_addr = rx_desc->buf_phys_addr; ++ ++ if (!mvneta_rxq_desc_is_first_last(rx_status) || ++ (rx_status & MVNETA_RXD_ERR_SUMMARY)) { ++err_drop_frame: ++ dev->stats.rx_errors++; ++ mvneta_rx_error(pp, rx_desc); ++ /* leave the descriptor untouched */ ++ continue; ++ } ++ ++ if (rx_bytes <= rx_copybreak) { ++ /* better copy a small frame and not unmap the DMA region */ ++ skb = netdev_alloc_skb_ip_align(dev, rx_bytes); ++ if (unlikely(!skb)) ++ goto err_drop_frame; ++ ++ dma_sync_single_range_for_cpu(dev->dev.parent, ++ rx_desc->buf_phys_addr, ++ MVNETA_MH_SIZE + NET_SKB_PAD, ++ rx_bytes, ++ DMA_FROM_DEVICE); ++ memcpy(skb_put(skb, rx_bytes), ++ data + MVNETA_MH_SIZE + NET_SKB_PAD, ++ rx_bytes); ++ ++ skb->protocol = eth_type_trans(skb, dev); ++ mvneta_rx_csum(pp, rx_status, skb); ++ napi_gro_receive(&port->napi, skb); ++ ++ rcvd_pkts++; ++ rcvd_bytes += rx_bytes; ++ ++ /* leave the descriptor and buffer untouched */ ++ continue; ++ } ++ ++ /* Refill processing */ ++ err = mvneta_rx_refill(pp, rx_desc); ++ if (err) { ++ netdev_err(dev, "Linux processing - Can't refill\n"); ++ rxq->missed++; ++ goto err_drop_frame; ++ } ++ ++ frag_size = pp->frag_size; ++ ++ skb = build_skb(data, frag_size > PAGE_SIZE ? 0 : frag_size); ++ ++ /* After refill old buffer has to be unmapped regardless ++ * the skb is successfully built or not. ++ */ ++ dma_unmap_single(dev->dev.parent, phys_addr, ++ MVNETA_RX_BUF_SIZE(pp->pkt_size), ++ DMA_FROM_DEVICE); ++ ++ if (!skb) ++ goto err_drop_frame; ++ ++ rcvd_pkts++; ++ rcvd_bytes += rx_bytes; ++ ++ /* Linux processing */ ++ skb_reserve(skb, MVNETA_MH_SIZE + NET_SKB_PAD); ++ skb_put(skb, rx_bytes); ++ ++ skb->protocol = eth_type_trans(skb, dev); ++ ++ mvneta_rx_csum(pp, rx_status, skb); ++ ++ napi_gro_receive(&port->napi, skb); ++ } ++ ++ if (rcvd_pkts) { ++ struct mvneta_pcpu_stats *stats = this_cpu_ptr(pp->stats); ++ ++ u64_stats_update_begin(&stats->syncp); ++ stats->rx_packets += rcvd_pkts; ++ stats->rx_bytes += rcvd_bytes; ++ u64_stats_update_end(&stats->syncp); ++ } ++ ++ /* Update rxq management counters */ ++ mvneta_rxq_desc_num_update(pp, rxq, rx_done, rx_done); ++ ++ return rx_done; + } + +-/* Main rx processing */ +-static int mvneta_rx(struct mvneta_port *pp, int rx_todo, +- struct mvneta_rx_queue *rxq) ++/* Main rx processing when using hardware buffer management */ ++static int mvneta_rx_hwbm(struct mvneta_port *pp, int rx_todo, ++ struct mvneta_rx_queue *rxq) + { + struct mvneta_pcpu_port *port = this_cpu_ptr(pp->ports); + struct net_device *dev = pp->dev; +@@ -1628,21 +2004,29 @@ static int mvneta_rx(struct mvneta_port + /* Fairness NAPI loop */ + while (rx_done < rx_todo) { + struct mvneta_rx_desc *rx_desc = mvneta_rxq_next_desc_get(rxq); ++ struct mvneta_bm_pool *bm_pool = NULL; + struct sk_buff *skb; + unsigned char *data; + dma_addr_t phys_addr; +- u32 rx_status; ++ u32 rx_status, frag_size; + int rx_bytes, err; ++ u8 pool_id; + + rx_done++; + rx_status = rx_desc->status; + rx_bytes = rx_desc->data_size - (ETH_FCS_LEN + MVNETA_MH_SIZE); + data = (unsigned char *)rx_desc->buf_cookie; + phys_addr = rx_desc->buf_phys_addr; ++ pool_id = MVNETA_RX_GET_BM_POOL_ID(rx_desc); ++ bm_pool = &pp->bm_priv->bm_pools[pool_id]; + + if (!mvneta_rxq_desc_is_first_last(rx_status) || + (rx_status & MVNETA_RXD_ERR_SUMMARY)) { +- err_drop_frame: ++err_drop_frame_ret_pool: ++ /* Return the buffer to the pool */ ++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool, ++ rx_desc->buf_phys_addr); ++err_drop_frame: + dev->stats.rx_errors++; + mvneta_rx_error(pp, rx_desc); + /* leave the descriptor untouched */ +@@ -1653,7 +2037,7 @@ static int mvneta_rx(struct mvneta_port + /* better copy a small frame and not unmap the DMA region */ + skb = netdev_alloc_skb_ip_align(dev, rx_bytes); + if (unlikely(!skb)) +- goto err_drop_frame; ++ goto err_drop_frame_ret_pool; + + dma_sync_single_range_for_cpu(dev->dev.parent, + rx_desc->buf_phys_addr, +@@ -1671,26 +2055,31 @@ static int mvneta_rx(struct mvneta_port + rcvd_pkts++; + rcvd_bytes += rx_bytes; + ++ /* Return the buffer to the pool */ ++ mvneta_bm_pool_put_bp(pp->bm_priv, bm_pool, ++ rx_desc->buf_phys_addr); ++ + /* leave the descriptor and buffer untouched */ + continue; + } + + /* Refill processing */ +- err = mvneta_rx_refill(pp, rx_desc); ++ err = mvneta_bm_pool_refill(pp->bm_priv, bm_pool); + if (err) { + netdev_err(dev, "Linux processing - Can't refill\n"); + rxq->missed++; +- goto err_drop_frame; ++ goto err_drop_frame_ret_pool; + } + +- skb = build_skb(data, pp->frag_size > PAGE_SIZE ? 0 : pp->frag_size); ++ frag_size = bm_pool->frag_size; ++ ++ skb = build_skb(data, frag_size > PAGE_SIZE ? 0 : frag_size); + + /* After refill old buffer has to be unmapped regardless + * the skb is successfully built or not. + */ +- dma_unmap_single(dev->dev.parent, phys_addr, +- MVNETA_RX_BUF_SIZE(pp->pkt_size), DMA_FROM_DEVICE); +- ++ dma_unmap_single(&pp->bm_priv->pdev->dev, phys_addr, ++ bm_pool->buf_size, DMA_FROM_DEVICE); + if (!skb) + goto err_drop_frame; + +@@ -2295,7 +2684,10 @@ static int mvneta_poll(struct napi_struc + + if (rx_queue) { + rx_queue = rx_queue - 1; +- rx_done = mvneta_rx(pp, budget, &pp->rxqs[rx_queue]); ++ if (pp->bm_priv) ++ rx_done = mvneta_rx_hwbm(pp, budget, &pp->rxqs[rx_queue]); ++ else ++ rx_done = mvneta_rx_swbm(pp, budget, &pp->rxqs[rx_queue]); + } + + budget -= rx_done; +@@ -2384,9 +2776,17 @@ static int mvneta_rxq_init(struct mvneta + mvneta_rx_pkts_coal_set(pp, rxq, rxq->pkts_coal); + mvneta_rx_time_coal_set(pp, rxq, rxq->time_coal); + +- /* Fill RXQ with buffers from RX pool */ +- mvneta_rxq_buf_size_set(pp, rxq, MVNETA_RX_BUF_SIZE(pp->pkt_size)); +- mvneta_rxq_bm_disable(pp, rxq); ++ if (!pp->bm_priv) { ++ /* Fill RXQ with buffers from RX pool */ ++ mvneta_rxq_buf_size_set(pp, rxq, ++ MVNETA_RX_BUF_SIZE(pp->pkt_size)); ++ mvneta_rxq_bm_disable(pp, rxq); ++ } else { ++ mvneta_rxq_bm_enable(pp, rxq); ++ mvneta_rxq_long_pool_set(pp, rxq); ++ mvneta_rxq_short_pool_set(pp, rxq); ++ } ++ + mvneta_rxq_fill(pp, rxq, rxq->size); + + return 0; +@@ -2659,6 +3059,9 @@ static int mvneta_change_mtu(struct net_ + dev->mtu = mtu; + + if (!netif_running(dev)) { ++ if (pp->bm_priv) ++ mvneta_bm_update_mtu(pp, mtu); ++ + netdev_update_features(dev); + return 0; + } +@@ -2671,6 +3074,9 @@ static int mvneta_change_mtu(struct net_ + mvneta_cleanup_txqs(pp); + mvneta_cleanup_rxqs(pp); + ++ if (pp->bm_priv) ++ mvneta_bm_update_mtu(pp, mtu); ++ + pp->pkt_size = MVNETA_RX_PKT_SIZE(dev->mtu); + pp->frag_size = SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(pp->pkt_size)) + + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); +@@ -3563,6 +3969,7 @@ static int mvneta_probe(struct platform_ + struct resource *res; + struct device_node *dn = pdev->dev.of_node; + struct device_node *phy_node; ++ struct device_node *bm_node; + struct mvneta_port *pp; + struct net_device *dev; + const char *dt_mac_addr; +@@ -3690,26 +4097,39 @@ static int mvneta_probe(struct platform_ + + pp->tx_csum_limit = tx_csum_limit; + ++ dram_target_info = mv_mbus_dram_info(); ++ if (dram_target_info) ++ mvneta_conf_mbus_windows(pp, dram_target_info); ++ + pp->tx_ring_size = MVNETA_MAX_TXD; + pp->rx_ring_size = MVNETA_MAX_RXD; + + pp->dev = dev; + SET_NETDEV_DEV(dev, &pdev->dev); + ++ pp->id = global_port_id++; ++ ++ /* Obtain access to BM resources if enabled and already initialized */ ++ bm_node = of_parse_phandle(dn, "buffer-manager", 0); ++ if (bm_node && bm_node->data) { ++ pp->bm_priv = bm_node->data; ++ err = mvneta_bm_port_init(pdev, pp); ++ if (err < 0) { ++ dev_info(&pdev->dev, "use SW buffer management\n"); ++ pp->bm_priv = NULL; ++ } ++ } ++ + err = mvneta_init(&pdev->dev, pp); + if (err < 0) +- goto err_free_stats; ++ goto err_netdev; + + err = mvneta_port_power_up(pp, phy_mode); + if (err < 0) { + dev_err(&pdev->dev, "can't power up port\n"); +- goto err_free_stats; ++ goto err_netdev; + } + +- dram_target_info = mv_mbus_dram_info(); +- if (dram_target_info) +- mvneta_conf_mbus_windows(pp, dram_target_info); +- + for_each_present_cpu(cpu) { + struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu); + +@@ -3744,6 +4164,13 @@ static int mvneta_probe(struct platform_ + + return 0; + ++err_netdev: ++ unregister_netdev(dev); ++ if (pp->bm_priv) { ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id); ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short, ++ 1 << pp->id); ++ } + err_free_stats: + free_percpu(pp->stats); + err_free_ports: +@@ -3773,6 +4200,12 @@ static int mvneta_remove(struct platform + of_node_put(pp->phy_node); + free_netdev(dev); + ++ if (pp->bm_priv) { ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_long, 1 << pp->id); ++ mvneta_bm_pool_destroy(pp->bm_priv, pp->pool_short, ++ 1 << pp->id); ++ } ++ + return 0; + } + +--- /dev/null ++++ b/drivers/net/ethernet/marvell/mvneta_bm.c +@@ -0,0 +1,546 @@ ++/* ++ * Driver for Marvell NETA network controller Buffer Manager. ++ * ++ * Copyright (C) 2015 Marvell ++ * ++ * Marcin Wojtas <mw@semihalf.com> ++ * ++ * 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 <linux/kernel.h> ++#include <linux/genalloc.h> ++#include <linux/platform_device.h> ++#include <linux/netdevice.h> ++#include <linux/skbuff.h> ++#include <linux/mbus.h> ++#include <linux/module.h> ++#include <linux/io.h> ++#include <linux/of.h> ++#include <linux/clk.h> ++#include "mvneta_bm.h" ++ ++#define MVNETA_BM_DRIVER_NAME "mvneta_bm" ++#define MVNETA_BM_DRIVER_VERSION "1.0" ++ ++static void mvneta_bm_write(struct mvneta_bm *priv, u32 offset, u32 data) ++{ ++ writel(data, priv->reg_base + offset); ++} ++ ++static u32 mvneta_bm_read(struct mvneta_bm *priv, u32 offset) ++{ ++ return readl(priv->reg_base + offset); ++} ++ ++static void mvneta_bm_pool_enable(struct mvneta_bm *priv, int pool_id) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_POOL_BASE_REG(pool_id)); ++ val |= MVNETA_BM_POOL_ENABLE_MASK; ++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(pool_id), val); ++ ++ /* Clear BM cause register */ ++ mvneta_bm_write(priv, MVNETA_BM_INTR_CAUSE_REG, 0); ++} ++ ++static void mvneta_bm_pool_disable(struct mvneta_bm *priv, int pool_id) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_POOL_BASE_REG(pool_id)); ++ val &= ~MVNETA_BM_POOL_ENABLE_MASK; ++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(pool_id), val); ++} ++ ++static inline void mvneta_bm_config_set(struct mvneta_bm *priv, u32 mask) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG); ++ val |= mask; ++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val); ++} ++ ++static inline void mvneta_bm_config_clear(struct mvneta_bm *priv, u32 mask) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG); ++ val &= ~mask; ++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val); ++} ++ ++static void mvneta_bm_pool_target_set(struct mvneta_bm *priv, int pool_id, ++ u8 target_id, u8 attr) ++{ ++ u32 val; ++ ++ val = mvneta_bm_read(priv, MVNETA_BM_XBAR_POOL_REG(pool_id)); ++ val &= ~MVNETA_BM_TARGET_ID_MASK(pool_id); ++ val &= ~MVNETA_BM_XBAR_ATTR_MASK(pool_id); ++ val |= MVNETA_BM_TARGET_ID_VAL(pool_id, target_id); ++ val |= MVNETA_BM_XBAR_ATTR_VAL(pool_id, attr); ++ ++ mvneta_bm_write(priv, MVNETA_BM_XBAR_POOL_REG(pool_id), val); ++} ++ ++/* Allocate skb for BM pool */ ++void *mvneta_buf_alloc(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ dma_addr_t *buf_phys_addr) ++{ ++ void *buf; ++ dma_addr_t phys_addr; ++ ++ buf = mvneta_frag_alloc(bm_pool->frag_size); ++ if (!buf) ++ return NULL; ++ ++ /* In order to update buf_cookie field of RX descriptor properly, ++ * BM hardware expects buf virtual address to be placed in the ++ * first four bytes of mapped buffer. ++ */ ++ *(u32 *)buf = (u32)buf; ++ phys_addr = dma_map_single(&priv->pdev->dev, buf, bm_pool->buf_size, ++ DMA_FROM_DEVICE); ++ if (unlikely(dma_mapping_error(&priv->pdev->dev, phys_addr))) { ++ mvneta_frag_free(bm_pool->frag_size, buf); ++ return NULL; ++ } ++ *buf_phys_addr = phys_addr; ++ ++ return buf; ++} ++ ++/* Refill processing for HW buffer management */ ++int mvneta_bm_pool_refill(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) ++{ ++ dma_addr_t buf_phys_addr; ++ void *buf; ++ ++ buf = mvneta_buf_alloc(priv, bm_pool, &buf_phys_addr); ++ if (!buf) ++ return -ENOMEM; ++ ++ mvneta_bm_pool_put_bp(priv, bm_pool, buf_phys_addr); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_pool_refill); ++ ++/* Allocate buffers for the pool */ ++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ int buf_num) ++{ ++ int err, i; ++ ++ if (bm_pool->buf_num == bm_pool->size) { ++ dev_dbg(&priv->pdev->dev, "pool %d already filled\n", ++ bm_pool->id); ++ return bm_pool->buf_num; ++ } ++ ++ if (buf_num < 0 || ++ (buf_num + bm_pool->buf_num > bm_pool->size)) { ++ dev_err(&priv->pdev->dev, ++ "cannot allocate %d buffers for pool %d\n", ++ buf_num, bm_pool->id); ++ return 0; ++ } ++ ++ for (i = 0; i < buf_num; i++) { ++ err = mvneta_bm_pool_refill(priv, bm_pool); ++ if (err < 0) ++ break; ++ } ++ ++ /* Update BM driver with number of buffers added to pool */ ++ bm_pool->buf_num += i; ++ ++ dev_dbg(&priv->pdev->dev, ++ "%s pool %d: pkt_size=%4d, buf_size=%4d, frag_size=%4d\n", ++ bm_pool->type == MVNETA_BM_SHORT ? "short" : "long", ++ bm_pool->id, bm_pool->pkt_size, bm_pool->buf_size, ++ bm_pool->frag_size); ++ ++ dev_dbg(&priv->pdev->dev, ++ "%s pool %d: %d of %d buffers added\n", ++ bm_pool->type == MVNETA_BM_SHORT ? "short" : "long", ++ bm_pool->id, i, buf_num); ++ ++ return i; ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_bufs_add); ++ ++/* Create pool */ ++static int mvneta_bm_pool_create(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) ++{ ++ struct platform_device *pdev = priv->pdev; ++ u8 target_id, attr; ++ int size_bytes, err; ++ ++ size_bytes = sizeof(u32) * bm_pool->size; ++ bm_pool->virt_addr = dma_alloc_coherent(&pdev->dev, size_bytes, ++ &bm_pool->phys_addr, ++ GFP_KERNEL); ++ if (!bm_pool->virt_addr) ++ return -ENOMEM; ++ ++ if (!IS_ALIGNED((u32)bm_pool->virt_addr, MVNETA_BM_POOL_PTR_ALIGN)) { ++ dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, ++ bm_pool->phys_addr); ++ dev_err(&pdev->dev, "BM pool %d is not %d bytes aligned\n", ++ bm_pool->id, MVNETA_BM_POOL_PTR_ALIGN); ++ return -ENOMEM; ++ } ++ ++ err = mvebu_mbus_get_dram_win_info(bm_pool->phys_addr, &target_id, ++ &attr); ++ if (err < 0) { ++ dma_free_coherent(&pdev->dev, size_bytes, bm_pool->virt_addr, ++ bm_pool->phys_addr); ++ return err; ++ } ++ ++ /* Set pool address */ ++ mvneta_bm_write(priv, MVNETA_BM_POOL_BASE_REG(bm_pool->id), ++ bm_pool->phys_addr); ++ ++ mvneta_bm_pool_target_set(priv, bm_pool->id, target_id, attr); ++ mvneta_bm_pool_enable(priv, bm_pool->id); ++ ++ return 0; ++} ++ ++/* Notify the driver that BM pool is being used as specific type and return the ++ * pool pointer on success ++ */ ++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id, ++ enum mvneta_bm_type type, u8 port_id, ++ int pkt_size) ++{ ++ struct mvneta_bm_pool *new_pool = &priv->bm_pools[pool_id]; ++ int num, err; ++ ++ if (new_pool->type == MVNETA_BM_LONG && ++ new_pool->port_map != 1 << port_id) { ++ dev_err(&priv->pdev->dev, ++ "long pool cannot be shared by the ports\n"); ++ return NULL; ++ } ++ ++ if (new_pool->type == MVNETA_BM_SHORT && new_pool->type != type) { ++ dev_err(&priv->pdev->dev, ++ "mixing pools' types between the ports is forbidden\n"); ++ return NULL; ++ } ++ ++ if (new_pool->pkt_size == 0 || type != MVNETA_BM_SHORT) ++ new_pool->pkt_size = pkt_size; ++ ++ /* Allocate buffers in case BM pool hasn't been used yet */ ++ if (new_pool->type == MVNETA_BM_FREE) { ++ new_pool->type = type; ++ new_pool->buf_size = MVNETA_RX_BUF_SIZE(new_pool->pkt_size); ++ new_pool->frag_size = ++ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(new_pool->pkt_size)) + ++ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); ++ ++ /* Create new pool */ ++ err = mvneta_bm_pool_create(priv, new_pool); ++ if (err) { ++ dev_err(&priv->pdev->dev, "fail to create pool %d\n", ++ new_pool->id); ++ return NULL; ++ } ++ ++ /* Allocate buffers for this pool */ ++ num = mvneta_bm_bufs_add(priv, new_pool, new_pool->size); ++ if (num != new_pool->size) { ++ WARN(1, "pool %d: %d of %d allocated\n", ++ new_pool->id, num, new_pool->size); ++ return NULL; ++ } ++ } ++ ++ return new_pool; ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_pool_use); ++ ++/* Free all buffers from the pool */ ++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ u8 port_map) ++{ ++ int i; ++ ++ bm_pool->port_map &= ~port_map; ++ if (bm_pool->port_map) ++ return; ++ ++ mvneta_bm_config_set(priv, MVNETA_BM_EMPTY_LIMIT_MASK); ++ ++ for (i = 0; i < bm_pool->buf_num; i++) { ++ dma_addr_t buf_phys_addr; ++ u32 *vaddr; ++ ++ /* Get buffer physical address (indirect access) */ ++ buf_phys_addr = mvneta_bm_pool_get_bp(priv, bm_pool); ++ ++ /* Work-around to the problems when destroying the pool, ++ * when it occurs that a read access to BPPI returns 0. ++ */ ++ if (buf_phys_addr == 0) ++ continue; ++ ++ vaddr = phys_to_virt(buf_phys_addr); ++ if (!vaddr) ++ break; ++ ++ dma_unmap_single(&priv->pdev->dev, buf_phys_addr, ++ bm_pool->buf_size, DMA_FROM_DEVICE); ++ mvneta_frag_free(bm_pool->frag_size, vaddr); ++ } ++ ++ mvneta_bm_config_clear(priv, MVNETA_BM_EMPTY_LIMIT_MASK); ++ ++ /* Update BM driver with number of buffers removed from pool */ ++ bm_pool->buf_num -= i; ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_bufs_free); ++ ++/* Cleanup pool */ ++void mvneta_bm_pool_destroy(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, u8 port_map) ++{ ++ bm_pool->port_map &= ~port_map; ++ if (bm_pool->port_map) ++ return; ++ ++ bm_pool->type = MVNETA_BM_FREE; ++ ++ mvneta_bm_bufs_free(priv, bm_pool, port_map); ++ if (bm_pool->buf_num) ++ WARN(1, "cannot free all buffers in pool %d\n", bm_pool->id); ++ ++ if (bm_pool->virt_addr) { ++ dma_free_coherent(&priv->pdev->dev, sizeof(u32) * bm_pool->size, ++ bm_pool->virt_addr, bm_pool->phys_addr); ++ bm_pool->virt_addr = NULL; ++ } ++ ++ mvneta_bm_pool_disable(priv, bm_pool->id); ++} ++EXPORT_SYMBOL_GPL(mvneta_bm_pool_destroy); ++ ++static void mvneta_bm_pools_init(struct mvneta_bm *priv) ++{ ++ struct device_node *dn = priv->pdev->dev.of_node; ++ struct mvneta_bm_pool *bm_pool; ++ char prop[15]; ++ u32 size; ++ int i; ++ ++ /* Activate BM unit */ ++ mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_START_MASK); ++ ++ /* Create all pools with maximum size */ ++ for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { ++ bm_pool = &priv->bm_pools[i]; ++ bm_pool->id = i; ++ bm_pool->type = MVNETA_BM_FREE; ++ ++ /* Reset read pointer */ ++ mvneta_bm_write(priv, MVNETA_BM_POOL_READ_PTR_REG(i), 0); ++ ++ /* Reset write pointer */ ++ mvneta_bm_write(priv, MVNETA_BM_POOL_WRITE_PTR_REG(i), 0); ++ ++ /* Configure pool size according to DT or use default value */ ++ sprintf(prop, "pool%d,capacity", i); ++ if (of_property_read_u32(dn, prop, &size)) { ++ size = MVNETA_BM_POOL_CAP_DEF; ++ } else if (size > MVNETA_BM_POOL_CAP_MAX) { ++ dev_warn(&priv->pdev->dev, ++ "Illegal pool %d capacity %d, set to %d\n", ++ i, size, MVNETA_BM_POOL_CAP_MAX); ++ size = MVNETA_BM_POOL_CAP_MAX; ++ } else if (size < MVNETA_BM_POOL_CAP_MIN) { ++ dev_warn(&priv->pdev->dev, ++ "Illegal pool %d capacity %d, set to %d\n", ++ i, size, MVNETA_BM_POOL_CAP_MIN); ++ size = MVNETA_BM_POOL_CAP_MIN; ++ } else if (!IS_ALIGNED(size, MVNETA_BM_POOL_CAP_ALIGN)) { ++ dev_warn(&priv->pdev->dev, ++ "Illegal pool %d capacity %d, round to %d\n", ++ i, size, ALIGN(size, ++ MVNETA_BM_POOL_CAP_ALIGN)); ++ size = ALIGN(size, MVNETA_BM_POOL_CAP_ALIGN); ++ } ++ bm_pool->size = size; ++ ++ mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i), ++ bm_pool->size); ++ ++ /* Obtain custom pkt_size from DT */ ++ sprintf(prop, "pool%d,pkt-size", i); ++ if (of_property_read_u32(dn, prop, &bm_pool->pkt_size)) ++ bm_pool->pkt_size = 0; ++ } ++} ++ ++static void mvneta_bm_default_set(struct mvneta_bm *priv) ++{ ++ u32 val; ++ ++ /* Mask BM all interrupts */ ++ mvneta_bm_write(priv, MVNETA_BM_INTR_MASK_REG, 0); ++ ++ /* Clear BM cause register */ ++ mvneta_bm_write(priv, MVNETA_BM_INTR_CAUSE_REG, 0); ++ ++ /* Set BM configuration register */ ++ val = mvneta_bm_read(priv, MVNETA_BM_CONFIG_REG); ++ ++ /* Reduce MaxInBurstSize from 32 BPs to 16 BPs */ ++ val &= ~MVNETA_BM_MAX_IN_BURST_SIZE_MASK; ++ val |= MVNETA_BM_MAX_IN_BURST_SIZE_16BP; ++ mvneta_bm_write(priv, MVNETA_BM_CONFIG_REG, val); ++} ++ ++static int mvneta_bm_init(struct mvneta_bm *priv) ++{ ++ mvneta_bm_default_set(priv); ++ ++ /* Allocate and initialize BM pools structures */ ++ priv->bm_pools = devm_kcalloc(&priv->pdev->dev, MVNETA_BM_POOLS_NUM, ++ sizeof(struct mvneta_bm_pool), ++ GFP_KERNEL); ++ if (!priv->bm_pools) ++ return -ENOMEM; ++ ++ mvneta_bm_pools_init(priv); ++ ++ return 0; ++} ++ ++static int mvneta_bm_get_sram(struct device_node *dn, ++ struct mvneta_bm *priv) ++{ ++ priv->bppi_pool = of_gen_pool_get(dn, "internal-mem", 0); ++ if (!priv->bppi_pool) ++ return -ENOMEM; ++ ++ priv->bppi_virt_addr = gen_pool_dma_alloc(priv->bppi_pool, ++ MVNETA_BM_BPPI_SIZE, ++ &priv->bppi_phys_addr); ++ if (!priv->bppi_virt_addr) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++static void mvneta_bm_put_sram(struct mvneta_bm *priv) ++{ ++ gen_pool_free(priv->bppi_pool, priv->bppi_phys_addr, ++ MVNETA_BM_BPPI_SIZE); ++} ++ ++static int mvneta_bm_probe(struct platform_device *pdev) ++{ ++ struct device_node *dn = pdev->dev.of_node; ++ struct mvneta_bm *priv; ++ struct resource *res; ++ int err; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(struct mvneta_bm), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ priv->reg_base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(priv->reg_base)) ++ return PTR_ERR(priv->reg_base); ++ ++ priv->clk = devm_clk_get(&pdev->dev, NULL); ++ if (IS_ERR(priv->clk)) ++ return PTR_ERR(priv->clk); ++ err = clk_prepare_enable(priv->clk); ++ if (err < 0) ++ return err; ++ ++ err = mvneta_bm_get_sram(dn, priv); ++ if (err < 0) { ++ dev_err(&pdev->dev, "failed to allocate internal memory\n"); ++ goto err_clk; ++ } ++ ++ priv->pdev = pdev; ++ ++ /* Initialize buffer manager internals */ ++ err = mvneta_bm_init(priv); ++ if (err < 0) { ++ dev_err(&pdev->dev, "failed to initialize controller\n"); ++ goto err_sram; ++ } ++ ++ dn->data = priv; ++ platform_set_drvdata(pdev, priv); ++ ++ dev_info(&pdev->dev, "Buffer Manager for network controller enabled\n"); ++ ++ return 0; ++ ++err_sram: ++ mvneta_bm_put_sram(priv); ++err_clk: ++ clk_disable_unprepare(priv->clk); ++ return err; ++} ++ ++static int mvneta_bm_remove(struct platform_device *pdev) ++{ ++ struct mvneta_bm *priv = platform_get_drvdata(pdev); ++ u8 all_ports_map = 0xff; ++ int i = 0; ++ ++ for (i = 0; i < MVNETA_BM_POOLS_NUM; i++) { ++ struct mvneta_bm_pool *bm_pool = &priv->bm_pools[i]; ++ ++ mvneta_bm_pool_destroy(priv, bm_pool, all_ports_map); ++ } ++ ++ mvneta_bm_put_sram(priv); ++ ++ /* Dectivate BM unit */ ++ mvneta_bm_write(priv, MVNETA_BM_COMMAND_REG, MVNETA_BM_STOP_MASK); ++ ++ clk_disable_unprepare(priv->clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id mvneta_bm_match[] = { ++ { .compatible = "marvell,armada-380-neta-bm" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, mvneta_bm_match); ++ ++static struct platform_driver mvneta_bm_driver = { ++ .probe = mvneta_bm_probe, ++ .remove = mvneta_bm_remove, ++ .driver = { ++ .name = MVNETA_BM_DRIVER_NAME, ++ .of_match_table = mvneta_bm_match, ++ }, ++}; ++ ++module_platform_driver(mvneta_bm_driver); ++ ++MODULE_DESCRIPTION("Marvell NETA Buffer Manager Driver - www.marvell.com"); ++MODULE_AUTHOR("Marcin Wojtas <mw@semihalf.com>"); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/drivers/net/ethernet/marvell/mvneta_bm.h +@@ -0,0 +1,189 @@ ++/* ++ * Driver for Marvell NETA network controller Buffer Manager. ++ * ++ * Copyright (C) 2015 Marvell ++ * ++ * Marcin Wojtas <mw@semihalf.com> ++ * ++ * 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. ++ */ ++ ++#ifndef _MVNETA_BM_H_ ++#define _MVNETA_BM_H_ ++ ++/* BM Configuration Register */ ++#define MVNETA_BM_CONFIG_REG 0x0 ++#define MVNETA_BM_STATUS_MASK 0x30 ++#define MVNETA_BM_ACTIVE_MASK BIT(4) ++#define MVNETA_BM_MAX_IN_BURST_SIZE_MASK 0x60000 ++#define MVNETA_BM_MAX_IN_BURST_SIZE_16BP BIT(18) ++#define MVNETA_BM_EMPTY_LIMIT_MASK BIT(19) ++ ++/* BM Activation Register */ ++#define MVNETA_BM_COMMAND_REG 0x4 ++#define MVNETA_BM_START_MASK BIT(0) ++#define MVNETA_BM_STOP_MASK BIT(1) ++#define MVNETA_BM_PAUSE_MASK BIT(2) ++ ++/* BM Xbar interface Register */ ++#define MVNETA_BM_XBAR_01_REG 0x8 ++#define MVNETA_BM_XBAR_23_REG 0xc ++#define MVNETA_BM_XBAR_POOL_REG(pool) \ ++ (((pool) < 2) ? MVNETA_BM_XBAR_01_REG : MVNETA_BM_XBAR_23_REG) ++#define MVNETA_BM_TARGET_ID_OFFS(pool) (((pool) & 1) ? 16 : 0) ++#define MVNETA_BM_TARGET_ID_MASK(pool) \ ++ (0xf << MVNETA_BM_TARGET_ID_OFFS(pool)) ++#define MVNETA_BM_TARGET_ID_VAL(pool, id) \ ++ ((id) << MVNETA_BM_TARGET_ID_OFFS(pool)) ++#define MVNETA_BM_XBAR_ATTR_OFFS(pool) (((pool) & 1) ? 20 : 4) ++#define MVNETA_BM_XBAR_ATTR_MASK(pool) \ ++ (0xff << MVNETA_BM_XBAR_ATTR_OFFS(pool)) ++#define MVNETA_BM_XBAR_ATTR_VAL(pool, attr) \ ++ ((attr) << MVNETA_BM_XBAR_ATTR_OFFS(pool)) ++ ++/* Address of External Buffer Pointers Pool Register */ ++#define MVNETA_BM_POOL_BASE_REG(pool) (0x10 + ((pool) << 4)) ++#define MVNETA_BM_POOL_ENABLE_MASK BIT(0) ++ ++/* External Buffer Pointers Pool RD pointer Register */ ++#define MVNETA_BM_POOL_READ_PTR_REG(pool) (0x14 + ((pool) << 4)) ++#define MVNETA_BM_POOL_SET_READ_PTR_MASK 0xfffc ++#define MVNETA_BM_POOL_GET_READ_PTR_OFFS 16 ++#define MVNETA_BM_POOL_GET_READ_PTR_MASK 0xfffc0000 ++ ++/* External Buffer Pointers Pool WR pointer */ ++#define MVNETA_BM_POOL_WRITE_PTR_REG(pool) (0x18 + ((pool) << 4)) ++#define MVNETA_BM_POOL_SET_WRITE_PTR_OFFS 0 ++#define MVNETA_BM_POOL_SET_WRITE_PTR_MASK 0xfffc ++#define MVNETA_BM_POOL_GET_WRITE_PTR_OFFS 16 ++#define MVNETA_BM_POOL_GET_WRITE_PTR_MASK 0xfffc0000 ++ ++/* External Buffer Pointers Pool Size Register */ ++#define MVNETA_BM_POOL_SIZE_REG(pool) (0x1c + ((pool) << 4)) ++#define MVNETA_BM_POOL_SIZE_MASK 0x3fff ++ ++/* BM Interrupt Cause Register */ ++#define MVNETA_BM_INTR_CAUSE_REG (0x50) ++ ++/* BM interrupt Mask Register */ ++#define MVNETA_BM_INTR_MASK_REG (0x54) ++ ++/* Other definitions */ ++#define MVNETA_BM_SHORT_PKT_SIZE 256 ++#define MVNETA_BM_POOLS_NUM 4 ++#define MVNETA_BM_POOL_CAP_MIN 128 ++#define MVNETA_BM_POOL_CAP_DEF 2048 ++#define MVNETA_BM_POOL_CAP_MAX \ ++ (16 * 1024 - MVNETA_BM_POOL_CAP_ALIGN) ++#define MVNETA_BM_POOL_CAP_ALIGN 32 ++#define MVNETA_BM_POOL_PTR_ALIGN 32 ++ ++#define MVNETA_BM_POOL_ACCESS_OFFS 8 ++ ++#define MVNETA_BM_BPPI_SIZE 0x100000 ++ ++#define MVNETA_RX_BUF_SIZE(pkt_size) ((pkt_size) + NET_SKB_PAD) ++ ++enum mvneta_bm_type { ++ MVNETA_BM_FREE, ++ MVNETA_BM_LONG, ++ MVNETA_BM_SHORT ++}; ++ ++struct mvneta_bm { ++ void __iomem *reg_base; ++ struct clk *clk; ++ struct platform_device *pdev; ++ ++ struct gen_pool *bppi_pool; ++ /* BPPI virtual base address */ ++ void __iomem *bppi_virt_addr; ++ /* BPPI physical base address */ ++ dma_addr_t bppi_phys_addr; ++ ++ /* BM pools */ ++ struct mvneta_bm_pool *bm_pools; ++}; ++ ++struct mvneta_bm_pool { ++ /* Pool number in the range 0-3 */ ++ u8 id; ++ enum mvneta_bm_type type; ++ ++ /* Buffer Pointers Pool External (BPPE) size in number of bytes */ ++ int size; ++ /* Number of buffers used by this pool */ ++ int buf_num; ++ /* Pool buffer size */ ++ int buf_size; ++ /* Packet size */ ++ int pkt_size; ++ /* Single frag size */ ++ u32 frag_size; ++ ++ /* BPPE virtual base address */ ++ u32 *virt_addr; ++ /* BPPE physical base address */ ++ dma_addr_t phys_addr; ++ ++ /* Ports using BM pool */ ++ u8 port_map; ++ ++ struct mvneta_bm *priv; ++}; ++ ++/* Declarations and definitions */ ++void *mvneta_frag_alloc(unsigned int frag_size); ++void mvneta_frag_free(unsigned int frag_size, void *data); ++ ++#if defined(CONFIG_MVNETA_BM) || defined(CONFIG_MVNETA_BM_MODULE) ++void mvneta_bm_pool_destroy(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, u8 port_map); ++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ u8 port_map); ++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ int buf_num); ++int mvneta_bm_pool_refill(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool); ++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id, ++ enum mvneta_bm_type type, u8 port_id, ++ int pkt_size); ++ ++static inline void mvneta_bm_pool_put_bp(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, ++ dma_addr_t buf_phys_addr) ++{ ++ writel_relaxed(buf_phys_addr, priv->bppi_virt_addr + ++ (bm_pool->id << MVNETA_BM_POOL_ACCESS_OFFS)); ++} ++ ++static inline u32 mvneta_bm_pool_get_bp(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) ++{ ++ return readl_relaxed(priv->bppi_virt_addr + ++ (bm_pool->id << MVNETA_BM_POOL_ACCESS_OFFS)); ++} ++#else ++void mvneta_bm_pool_destroy(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, u8 port_map) {} ++void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ u8 port_map) {} ++int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, ++ int buf_num) { return 0; } ++int mvneta_bm_pool_refill(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) {return 0; } ++struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id, ++ enum mvneta_bm_type type, u8 port_id, ++ int pkt_size) { return NULL; } ++ ++static inline void mvneta_bm_pool_put_bp(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool, ++ dma_addr_t buf_phys_addr) {} ++ ++static inline u32 mvneta_bm_pool_get_bp(struct mvneta_bm *priv, ++ struct mvneta_bm_pool *bm_pool) ++{ return 0; } ++#endif /* CONFIG_MVNETA_BM */ ++#endif diff --git a/target/linux/mvebu/patches-4.4/046-net-mvneta-Use-the-new-hwbm-framework.patch b/target/linux/mvebu/patches-4.4/046-net-mvneta-Use-the-new-hwbm-framework.patch new file mode 100644 index 0000000000..210504c39e --- /dev/null +++ b/target/linux/mvebu/patches-4.4/046-net-mvneta-Use-the-new-hwbm-framework.patch @@ -0,0 +1,359 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Mon, 14 Mar 2016 09:39:05 +0100 +Subject: [PATCH] net: mvneta: Use the new hwbm framework + +Now that the hardware buffer management framework had been introduced, +let's use it. + +Tested-by: Sebastian Careba <nitroshift@yahoo.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/Kconfig ++++ b/drivers/net/ethernet/marvell/Kconfig +@@ -43,6 +43,7 @@ config MVMDIO + config MVNETA_BM + tristate "Marvell Armada 38x/XP network interface BM support" + depends on MVNETA ++ select HWBM + ---help--- + This driver supports auxiliary block of the network + interface units in the Marvell ARMADA XP and ARMADA 38x SoC +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -30,6 +30,7 @@ + #include <linux/phy.h> + #include <linux/platform_device.h> + #include <linux/skbuff.h> ++#include <net/hwbm.h> + #include "mvneta_bm.h" + #include <net/ip.h> + #include <net/ipv6.h> +@@ -1024,11 +1025,12 @@ static int mvneta_bm_port_init(struct pl + static void mvneta_bm_update_mtu(struct mvneta_port *pp, int mtu) + { + struct mvneta_bm_pool *bm_pool = pp->pool_long; ++ struct hwbm_pool *hwbm_pool = &bm_pool->hwbm_pool; + int num; + + /* Release all buffers from long pool */ + mvneta_bm_bufs_free(pp->bm_priv, bm_pool, 1 << pp->id); +- if (bm_pool->buf_num) { ++ if (hwbm_pool->buf_num) { + WARN(1, "cannot free all buffers in pool %d\n", + bm_pool->id); + goto bm_mtu_err; +@@ -1036,14 +1038,14 @@ static void mvneta_bm_update_mtu(struct + + bm_pool->pkt_size = MVNETA_RX_PKT_SIZE(mtu); + bm_pool->buf_size = MVNETA_RX_BUF_SIZE(bm_pool->pkt_size); +- bm_pool->frag_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + +- SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(bm_pool->pkt_size)); ++ hwbm_pool->frag_size = SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + ++ SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(bm_pool->pkt_size)); + + /* Fill entire long pool */ +- num = mvneta_bm_bufs_add(pp->bm_priv, bm_pool, bm_pool->size); +- if (num != bm_pool->size) { ++ num = hwbm_pool_add(hwbm_pool, hwbm_pool->size, GFP_ATOMIC); ++ if (num != hwbm_pool->size) { + WARN(1, "pool %d: %d of %d allocated\n", +- bm_pool->id, num, bm_pool->size); ++ bm_pool->id, num, hwbm_pool->size); + goto bm_mtu_err; + } + mvneta_bm_pool_bufsize_set(pp, bm_pool->buf_size, bm_pool->id); +@@ -2064,14 +2066,14 @@ err_drop_frame: + } + + /* Refill processing */ +- err = mvneta_bm_pool_refill(pp->bm_priv, bm_pool); ++ err = hwbm_pool_refill(&bm_pool->hwbm_pool, GFP_ATOMIC); + if (err) { + netdev_err(dev, "Linux processing - Can't refill\n"); + rxq->missed++; + goto err_drop_frame_ret_pool; + } + +- frag_size = bm_pool->frag_size; ++ frag_size = bm_pool->hwbm_pool.frag_size; + + skb = build_skb(data, frag_size > PAGE_SIZE ? 0 : frag_size); + +--- a/drivers/net/ethernet/marvell/mvneta_bm.c ++++ b/drivers/net/ethernet/marvell/mvneta_bm.c +@@ -10,16 +10,17 @@ + * warranty of any kind, whether express or implied. + */ + +-#include <linux/kernel.h> ++#include <linux/clk.h> + #include <linux/genalloc.h> +-#include <linux/platform_device.h> +-#include <linux/netdevice.h> +-#include <linux/skbuff.h> ++#include <linux/io.h> ++#include <linux/kernel.h> + #include <linux/mbus.h> + #include <linux/module.h> +-#include <linux/io.h> ++#include <linux/netdevice.h> + #include <linux/of.h> +-#include <linux/clk.h> ++#include <linux/platform_device.h> ++#include <linux/skbuff.h> ++#include <net/hwbm.h> + #include "mvneta_bm.h" + + #define MVNETA_BM_DRIVER_NAME "mvneta_bm" +@@ -88,17 +89,13 @@ static void mvneta_bm_pool_target_set(st + mvneta_bm_write(priv, MVNETA_BM_XBAR_POOL_REG(pool_id), val); + } + +-/* Allocate skb for BM pool */ +-void *mvneta_buf_alloc(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, +- dma_addr_t *buf_phys_addr) ++int mvneta_bm_construct(struct hwbm_pool *hwbm_pool, void *buf) + { +- void *buf; ++ struct mvneta_bm_pool *bm_pool = ++ (struct mvneta_bm_pool *)hwbm_pool->priv; ++ struct mvneta_bm *priv = bm_pool->priv; + dma_addr_t phys_addr; + +- buf = mvneta_frag_alloc(bm_pool->frag_size); +- if (!buf) +- return NULL; +- + /* In order to update buf_cookie field of RX descriptor properly, + * BM hardware expects buf virtual address to be placed in the + * first four bytes of mapped buffer. +@@ -106,75 +103,13 @@ void *mvneta_buf_alloc(struct mvneta_bm + *(u32 *)buf = (u32)buf; + phys_addr = dma_map_single(&priv->pdev->dev, buf, bm_pool->buf_size, + DMA_FROM_DEVICE); +- if (unlikely(dma_mapping_error(&priv->pdev->dev, phys_addr))) { +- mvneta_frag_free(bm_pool->frag_size, buf); +- return NULL; +- } +- *buf_phys_addr = phys_addr; +- +- return buf; +-} +- +-/* Refill processing for HW buffer management */ +-int mvneta_bm_pool_refill(struct mvneta_bm *priv, +- struct mvneta_bm_pool *bm_pool) +-{ +- dma_addr_t buf_phys_addr; +- void *buf; +- +- buf = mvneta_buf_alloc(priv, bm_pool, &buf_phys_addr); +- if (!buf) ++ if (unlikely(dma_mapping_error(&priv->pdev->dev, phys_addr))) + return -ENOMEM; + +- mvneta_bm_pool_put_bp(priv, bm_pool, buf_phys_addr); +- ++ mvneta_bm_pool_put_bp(priv, bm_pool, phys_addr); + return 0; + } +-EXPORT_SYMBOL_GPL(mvneta_bm_pool_refill); +- +-/* Allocate buffers for the pool */ +-int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, +- int buf_num) +-{ +- int err, i; +- +- if (bm_pool->buf_num == bm_pool->size) { +- dev_dbg(&priv->pdev->dev, "pool %d already filled\n", +- bm_pool->id); +- return bm_pool->buf_num; +- } +- +- if (buf_num < 0 || +- (buf_num + bm_pool->buf_num > bm_pool->size)) { +- dev_err(&priv->pdev->dev, +- "cannot allocate %d buffers for pool %d\n", +- buf_num, bm_pool->id); +- return 0; +- } +- +- for (i = 0; i < buf_num; i++) { +- err = mvneta_bm_pool_refill(priv, bm_pool); +- if (err < 0) +- break; +- } +- +- /* Update BM driver with number of buffers added to pool */ +- bm_pool->buf_num += i; +- +- dev_dbg(&priv->pdev->dev, +- "%s pool %d: pkt_size=%4d, buf_size=%4d, frag_size=%4d\n", +- bm_pool->type == MVNETA_BM_SHORT ? "short" : "long", +- bm_pool->id, bm_pool->pkt_size, bm_pool->buf_size, +- bm_pool->frag_size); +- +- dev_dbg(&priv->pdev->dev, +- "%s pool %d: %d of %d buffers added\n", +- bm_pool->type == MVNETA_BM_SHORT ? "short" : "long", +- bm_pool->id, i, buf_num); +- +- return i; +-} +-EXPORT_SYMBOL_GPL(mvneta_bm_bufs_add); ++EXPORT_SYMBOL_GPL(mvneta_bm_construct); + + /* Create pool */ + static int mvneta_bm_pool_create(struct mvneta_bm *priv, +@@ -183,8 +118,7 @@ static int mvneta_bm_pool_create(struct + struct platform_device *pdev = priv->pdev; + u8 target_id, attr; + int size_bytes, err; +- +- size_bytes = sizeof(u32) * bm_pool->size; ++ size_bytes = sizeof(u32) * bm_pool->hwbm_pool.size; + bm_pool->virt_addr = dma_alloc_coherent(&pdev->dev, size_bytes, + &bm_pool->phys_addr, + GFP_KERNEL); +@@ -245,11 +179,16 @@ struct mvneta_bm_pool *mvneta_bm_pool_us + + /* Allocate buffers in case BM pool hasn't been used yet */ + if (new_pool->type == MVNETA_BM_FREE) { ++ struct hwbm_pool *hwbm_pool = &new_pool->hwbm_pool; ++ ++ new_pool->priv = priv; + new_pool->type = type; + new_pool->buf_size = MVNETA_RX_BUF_SIZE(new_pool->pkt_size); +- new_pool->frag_size = ++ hwbm_pool->frag_size = + SKB_DATA_ALIGN(MVNETA_RX_BUF_SIZE(new_pool->pkt_size)) + + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); ++ hwbm_pool->construct = mvneta_bm_construct; ++ hwbm_pool->priv = new_pool; + + /* Create new pool */ + err = mvneta_bm_pool_create(priv, new_pool); +@@ -260,10 +199,10 @@ struct mvneta_bm_pool *mvneta_bm_pool_us + } + + /* Allocate buffers for this pool */ +- num = mvneta_bm_bufs_add(priv, new_pool, new_pool->size); +- if (num != new_pool->size) { ++ num = hwbm_pool_add(hwbm_pool, hwbm_pool->size, GFP_ATOMIC); ++ if (num != hwbm_pool->size) { + WARN(1, "pool %d: %d of %d allocated\n", +- new_pool->id, num, new_pool->size); ++ new_pool->id, num, hwbm_pool->size); + return NULL; + } + } +@@ -284,7 +223,7 @@ void mvneta_bm_bufs_free(struct mvneta_b + + mvneta_bm_config_set(priv, MVNETA_BM_EMPTY_LIMIT_MASK); + +- for (i = 0; i < bm_pool->buf_num; i++) { ++ for (i = 0; i < bm_pool->hwbm_pool.buf_num; i++) { + dma_addr_t buf_phys_addr; + u32 *vaddr; + +@@ -303,13 +242,13 @@ void mvneta_bm_bufs_free(struct mvneta_b + + dma_unmap_single(&priv->pdev->dev, buf_phys_addr, + bm_pool->buf_size, DMA_FROM_DEVICE); +- mvneta_frag_free(bm_pool->frag_size, vaddr); ++ hwbm_buf_free(&bm_pool->hwbm_pool, vaddr); + } + + mvneta_bm_config_clear(priv, MVNETA_BM_EMPTY_LIMIT_MASK); + + /* Update BM driver with number of buffers removed from pool */ +- bm_pool->buf_num -= i; ++ bm_pool->hwbm_pool.buf_num -= i; + } + EXPORT_SYMBOL_GPL(mvneta_bm_bufs_free); + +@@ -317,6 +256,7 @@ EXPORT_SYMBOL_GPL(mvneta_bm_bufs_free); + void mvneta_bm_pool_destroy(struct mvneta_bm *priv, + struct mvneta_bm_pool *bm_pool, u8 port_map) + { ++ struct hwbm_pool *hwbm_pool = &bm_pool->hwbm_pool; + bm_pool->port_map &= ~port_map; + if (bm_pool->port_map) + return; +@@ -324,11 +264,12 @@ void mvneta_bm_pool_destroy(struct mvnet + bm_pool->type = MVNETA_BM_FREE; + + mvneta_bm_bufs_free(priv, bm_pool, port_map); +- if (bm_pool->buf_num) ++ if (hwbm_pool->buf_num) + WARN(1, "cannot free all buffers in pool %d\n", bm_pool->id); + + if (bm_pool->virt_addr) { +- dma_free_coherent(&priv->pdev->dev, sizeof(u32) * bm_pool->size, ++ dma_free_coherent(&priv->pdev->dev, ++ sizeof(u32) * hwbm_pool->size, + bm_pool->virt_addr, bm_pool->phys_addr); + bm_pool->virt_addr = NULL; + } +@@ -381,10 +322,10 @@ static void mvneta_bm_pools_init(struct + MVNETA_BM_POOL_CAP_ALIGN)); + size = ALIGN(size, MVNETA_BM_POOL_CAP_ALIGN); + } +- bm_pool->size = size; ++ bm_pool->hwbm_pool.size = size; + + mvneta_bm_write(priv, MVNETA_BM_POOL_SIZE_REG(i), +- bm_pool->size); ++ bm_pool->hwbm_pool.size); + + /* Obtain custom pkt_size from DT */ + sprintf(prop, "pool%d,pkt-size", i); +--- a/drivers/net/ethernet/marvell/mvneta_bm.h ++++ b/drivers/net/ethernet/marvell/mvneta_bm.h +@@ -108,20 +108,15 @@ struct mvneta_bm { + }; + + struct mvneta_bm_pool { ++ struct hwbm_pool hwbm_pool; + /* Pool number in the range 0-3 */ + u8 id; + enum mvneta_bm_type type; + +- /* Buffer Pointers Pool External (BPPE) size in number of bytes */ +- int size; +- /* Number of buffers used by this pool */ +- int buf_num; +- /* Pool buffer size */ +- int buf_size; + /* Packet size */ + int pkt_size; +- /* Single frag size */ +- u32 frag_size; ++ /* Size of the buffer acces through DMA*/ ++ u32 buf_size; + + /* BPPE virtual base address */ + u32 *virt_addr; +@@ -143,8 +138,7 @@ void mvneta_bm_pool_destroy(struct mvnet + struct mvneta_bm_pool *bm_pool, u8 port_map); + void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, + u8 port_map); +-int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, +- int buf_num); ++int mvneta_bm_construct(struct hwbm_pool *hwbm_pool, void *buf); + int mvneta_bm_pool_refill(struct mvneta_bm *priv, + struct mvneta_bm_pool *bm_pool); + struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id, +@@ -170,8 +164,7 @@ void mvneta_bm_pool_destroy(struct mvnet + struct mvneta_bm_pool *bm_pool, u8 port_map) {} + void mvneta_bm_bufs_free(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, + u8 port_map) {} +-int mvneta_bm_bufs_add(struct mvneta_bm *priv, struct mvneta_bm_pool *bm_pool, +- int buf_num) { return 0; } ++int mvneta_bm_construct(struct hwbm_pool *hwbm_pool, void *buf) { return 0; } + int mvneta_bm_pool_refill(struct mvneta_bm *priv, + struct mvneta_bm_pool *bm_pool) {return 0; } + struct mvneta_bm_pool *mvneta_bm_pool_use(struct mvneta_bm *priv, u8 pool_id, diff --git a/target/linux/mvebu/patches-4.4/047-net-mvneta-Fix-spinlock-usage.patch b/target/linux/mvebu/patches-4.4/047-net-mvneta-Fix-spinlock-usage.patch new file mode 100644 index 0000000000..a8e78df2e9 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/047-net-mvneta-Fix-spinlock-usage.patch @@ -0,0 +1,52 @@ +From: Gregory CLEMENT <gregory.clement@free-electrons.com> +Date: Sat, 12 Mar 2016 18:44:17 +0100 +Subject: [PATCH] net: mvneta: Fix spinlock usage + +In the previous patch, the spinlock was not initialized. While it didn't +cause any trouble yet it could be a problem to use it uninitialized. + +The most annoying part was the critical section protected by the spinlock +in mvneta_stop(). Some of the functions could sleep as pointed when +activated CONFIG_DEBUG_ATOMIC_SLEEP. Actually, in mvneta_stop() we only +need to protect the is_stopped flagged, indeed the code of the notifier +for CPU online is protected by the same spinlock, so when we get the +lock, the notifer work is done. + +Reported-by: Patrick Uiterwijk <patrick@puiterwijk.org> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3484,17 +3484,17 @@ static int mvneta_stop(struct net_device + struct mvneta_port *pp = netdev_priv(dev); + + /* Inform that we are stopping so we don't want to setup the +- * driver for new CPUs in the notifiers ++ * driver for new CPUs in the notifiers. The code of the ++ * notifier for CPU online is protected by the same spinlock, ++ * so when we get the lock, the notifer work is done. + */ + spin_lock(&pp->lock); + pp->is_stopped = true; ++ spin_unlock(&pp->lock); ++ + mvneta_stop_dev(pp); + mvneta_mdio_remove(pp); + unregister_cpu_notifier(&pp->cpu_notifier); +- /* Now that the notifier are unregistered, we can release le +- * lock +- */ +- spin_unlock(&pp->lock); + on_each_cpu(mvneta_percpu_disable, pp, true); + free_percpu_irq(dev->irq, pp->ports); + mvneta_cleanup_rxqs(pp); +@@ -4027,6 +4027,7 @@ static int mvneta_probe(struct platform_ + dev->ethtool_ops = &mvneta_eth_tool_ops; + + pp = netdev_priv(dev); ++ spin_lock_init(&pp->lock); + pp->phy_node = phy_node; + pp->phy_interface = phy_mode; + diff --git a/target/linux/mvebu/patches-4.4/048-net-mvneta-fix-error-messages-in-mvneta_port_down-fu.patch b/target/linux/mvebu/patches-4.4/048-net-mvneta-fix-error-messages-in-mvneta_port_down-fu.patch new file mode 100644 index 0000000000..fd1f1ae203 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/048-net-mvneta-fix-error-messages-in-mvneta_port_down-fu.patch @@ -0,0 +1,33 @@ +From: Dmitri Epshtein <dima@marvell.com> +Date: Sat, 12 Mar 2016 18:44:19 +0100 +Subject: [PATCH] net: mvneta: fix error messages in mvneta_port_down function + +This commit corrects error printing when shutting down the port. + +[gregory.clement@free-electrons.com: split initial commit in two +individual changes] +Signed-off-by: Dmitri Epshtein <dima@marvell.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -1105,7 +1105,7 @@ static void mvneta_port_down(struct mvne + do { + if (count++ >= MVNETA_RX_DISABLE_TIMEOUT_MSEC) { + netdev_warn(pp->dev, +- "TIMEOUT for RX stopped ! rx_queue_cmd: 0x08%x\n", ++ "TIMEOUT for RX stopped ! rx_queue_cmd: 0x%08x\n", + val); + break; + } +@@ -1144,7 +1144,7 @@ static void mvneta_port_down(struct mvne + do { + if (count++ >= MVNETA_TX_FIFO_EMPTY_TIMEOUT) { + netdev_warn(pp->dev, +- "TX FIFO empty timeout status=0x08%x\n", ++ "TX FIFO empty timeout status=0x%08x\n", + val); + break; + } diff --git a/target/linux/mvebu/patches-4.4/049-net-mvneta-replace-MVNETA_CPU_D_CACHE_LINE_SIZE-with.patch b/target/linux/mvebu/patches-4.4/049-net-mvneta-replace-MVNETA_CPU_D_CACHE_LINE_SIZE-with.patch new file mode 100644 index 0000000000..c12d98a4e2 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/049-net-mvneta-replace-MVNETA_CPU_D_CACHE_LINE_SIZE-with.patch @@ -0,0 +1,56 @@ +From: Jisheng Zhang <jszhang@marvell.com> +Date: Wed, 30 Mar 2016 19:55:21 +0800 +Subject: [PATCH] net: mvneta: replace MVNETA_CPU_D_CACHE_LINE_SIZE with + L1_CACHE_BYTES + +The mvneta is also used in some Marvell berlin family SoCs which may +have 64bytes cacheline size. Replace the MVNETA_CPU_D_CACHE_LINE_SIZE +usage with L1_CACHE_BYTES. + +And since dma_alloc_coherent() is always cacheline size aligned, so +remove the align checks. + +Signed-off-by: Jisheng Zhang <jszhang@marvell.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -260,7 +260,6 @@ + + #define MVNETA_VLAN_TAG_LEN 4 + +-#define MVNETA_CPU_D_CACHE_LINE_SIZE 32 + #define MVNETA_TX_CSUM_DEF_SIZE 1600 + #define MVNETA_TX_CSUM_MAX_SIZE 9800 + #define MVNETA_ACC_MODE_EXT1 1 +@@ -300,7 +299,7 @@ + #define MVNETA_RX_PKT_SIZE(mtu) \ + ALIGN((mtu) + MVNETA_MH_SIZE + MVNETA_VLAN_TAG_LEN + \ + ETH_HLEN + ETH_FCS_LEN, \ +- MVNETA_CPU_D_CACHE_LINE_SIZE) ++ L1_CACHE_BYTES) + + #define IS_TSO_HEADER(txq, addr) \ + ((addr >= txq->tso_hdrs_phys) && \ +@@ -2762,9 +2761,6 @@ static int mvneta_rxq_init(struct mvneta + if (rxq->descs == NULL) + return -ENOMEM; + +- BUG_ON(rxq->descs != +- PTR_ALIGN(rxq->descs, MVNETA_CPU_D_CACHE_LINE_SIZE)); +- + rxq->last_desc = rxq->size - 1; + + /* Set Rx descriptors queue starting address */ +@@ -2835,10 +2831,6 @@ static int mvneta_txq_init(struct mvneta + if (txq->descs == NULL) + return -ENOMEM; + +- /* Make sure descriptor address is cache line size aligned */ +- BUG_ON(txq->descs != +- PTR_ALIGN(txq->descs, MVNETA_CPU_D_CACHE_LINE_SIZE)); +- + txq->last_desc = txq->size - 1; + + /* Set maximum bandwidth for enabled TXQs */ diff --git a/target/linux/mvebu/patches-4.4/050-net-mvneta-fix-changing-MTU-when-using-per-cpu-proce.patch b/target/linux/mvebu/patches-4.4/050-net-mvneta-fix-changing-MTU-when-using-per-cpu-proce.patch new file mode 100644 index 0000000000..07862008a7 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/050-net-mvneta-fix-changing-MTU-when-using-per-cpu-proce.patch @@ -0,0 +1,75 @@ +From: Marcin Wojtas <mw@semihalf.com> +Date: Fri, 1 Apr 2016 15:21:18 +0200 +Subject: [PATCH] net: mvneta: fix changing MTU when using per-cpu processing + +After enabling per-cpu processing it appeared that under heavy load +changing MTU can result in blocking all port's interrupts and +transmitting data is not possible after the change. + +This commit fixes above issue by disabling percpu interrupts for the +time, when TXQs and RXQs are reconfigured. + +Signed-off-by: Marcin Wojtas <mw@semihalf.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3040,6 +3040,20 @@ static int mvneta_check_mtu_valid(struct + return mtu; + } + ++static void mvneta_percpu_enable(void *arg) ++{ ++ struct mvneta_port *pp = arg; ++ ++ enable_percpu_irq(pp->dev->irq, IRQ_TYPE_NONE); ++} ++ ++static void mvneta_percpu_disable(void *arg) ++{ ++ struct mvneta_port *pp = arg; ++ ++ disable_percpu_irq(pp->dev->irq); ++} ++ + /* Change the device mtu */ + static int mvneta_change_mtu(struct net_device *dev, int mtu) + { +@@ -3064,6 +3078,7 @@ static int mvneta_change_mtu(struct net_ + * reallocation of the queues + */ + mvneta_stop_dev(pp); ++ on_each_cpu(mvneta_percpu_disable, pp, true); + + mvneta_cleanup_txqs(pp); + mvneta_cleanup_rxqs(pp); +@@ -3087,6 +3102,7 @@ static int mvneta_change_mtu(struct net_ + return ret; + } + ++ on_each_cpu(mvneta_percpu_enable, pp, true); + mvneta_start_dev(pp); + mvneta_port_up(pp); + +@@ -3240,20 +3256,6 @@ static void mvneta_mdio_remove(struct mv + pp->phy_dev = NULL; + } + +-static void mvneta_percpu_enable(void *arg) +-{ +- struct mvneta_port *pp = arg; +- +- enable_percpu_irq(pp->dev->irq, IRQ_TYPE_NONE); +-} +- +-static void mvneta_percpu_disable(void *arg) +-{ +- struct mvneta_port *pp = arg; +- +- disable_percpu_irq(pp->dev->irq); +-} +- + /* Electing a CPU must be done in an atomic way: it should be done + * after or before the removal/insertion of a CPU and this function is + * not reentrant. diff --git a/target/linux/mvebu/patches-4.4/051-ARM-dts-armada-38x-add-buffer-manager-nodes.patch b/target/linux/mvebu/patches-4.4/051-ARM-dts-armada-38x-add-buffer-manager-nodes.patch new file mode 100644 index 0000000000..b56de94e3d --- /dev/null +++ b/target/linux/mvebu/patches-4.4/051-ARM-dts-armada-38x-add-buffer-manager-nodes.patch @@ -0,0 +1,53 @@ +From: Marcin Wojtas <mw@semihalf.com> +Date: Mon, 14 Mar 2016 09:38:57 +0100 +Subject: [PATCH] ARM: dts: armada-38x: add buffer manager nodes + +Armada 38x network controller supports hardware buffer management (BM). +Since it is now enabled in mvneta driver, appropriate nodes can be added +to armada-38x.dtsi - for the actual common BM unit (bm@c8000) and its +internal SRAM (bm-bppi), which is used for indirect access to buffer +pointer ring residing in DRAM. + +Pools - ports mapping, bm-bppi entry in 'soc' node's ranges and optional +parameters are supposed to be set in board files. + +Signed-off-by: Marcin Wojtas <mw@semihalf.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/arch/arm/boot/dts/armada-38x.dtsi ++++ b/arch/arm/boot/dts/armada-38x.dtsi +@@ -540,6 +540,14 @@ + status = "disabled"; + }; + ++ bm: bm@c8000 { ++ compatible = "marvell,armada-380-neta-bm"; ++ reg = <0xc8000 0xac>; ++ clocks = <&gateclk 13>; ++ internal-mem = <&bm_bppi>; ++ status = "disabled"; ++ }; ++ + sata@e0000 { + compatible = "marvell,armada-380-ahci"; + reg = <0xe0000 0x2000>; +@@ -618,6 +626,17 @@ + #size-cells = <1>; + ranges = <0 MBUS_ID(0x09, 0x15) 0 0x800>; + }; ++ ++ bm_bppi: bm-bppi { ++ compatible = "mmio-sram"; ++ reg = <MBUS_ID(0x0c, 0x04) 0 0x100000>; ++ ranges = <0 MBUS_ID(0x0c, 0x04) 0 0x100000>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ clocks = <&gateclk 13>; ++ no-memory-wc; ++ status = "disabled"; ++ }; + }; + + clocks { diff --git a/target/linux/mvebu/patches-4.4/052-ARM-dts-armada-xp-add-buffer-manager-nodes.patch b/target/linux/mvebu/patches-4.4/052-ARM-dts-armada-xp-add-buffer-manager-nodes.patch new file mode 100644 index 0000000000..65fafd7101 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/052-ARM-dts-armada-xp-add-buffer-manager-nodes.patch @@ -0,0 +1,53 @@ +From: Marcin Wojtas <mw@semihalf.com> +Date: Mon, 14 Mar 2016 09:38:59 +0100 +Subject: [PATCH] ARM: dts: armada-xp: add buffer manager nodes + +Armada XP network controller supports hardware buffer management (BM). +Since it is now enabled in mvneta driver, appropriate nodes can be added +to armada-xp.dtsi - for the actual common BM unit (bm@c0000) and its +internal SRAM (bm-bppi), which is used for indirect access to buffer +pointer ring residing in DRAM. + +Pools - ports mapping, bm-bppi entry in 'soc' node's ranges and optional +parameters are supposed to be set in board files. + +Signed-off-by: Marcin Wojtas <mw@semihalf.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + +--- a/arch/arm/boot/dts/armada-xp.dtsi ++++ b/arch/arm/boot/dts/armada-xp.dtsi +@@ -253,6 +253,14 @@ + marvell,crypto-sram-size = <0x800>; + }; + ++ bm: bm@c0000 { ++ compatible = "marvell,armada-380-neta-bm"; ++ reg = <0xc0000 0xac>; ++ clocks = <&gateclk 13>; ++ internal-mem = <&bm_bppi>; ++ status = "disabled"; ++ }; ++ + xor@f0900 { + compatible = "marvell,orion-xor"; + reg = <0xF0900 0x100 +@@ -291,6 +299,17 @@ + #size-cells = <1>; + ranges = <0 MBUS_ID(0x09, 0x05) 0 0x800>; + }; ++ ++ bm_bppi: bm-bppi { ++ compatible = "mmio-sram"; ++ reg = <MBUS_ID(0x0c, 0x04) 0 0x100000>; ++ ranges = <0 MBUS_ID(0x0c, 0x04) 0 0x100000>; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ clocks = <&gateclk 13>; ++ no-memory-wc; ++ status = "disabled"; ++ }; + }; + + clocks { diff --git a/target/linux/mvebu/patches-4.4/053-ARM-dts-Add-SolidRun-Armada-388-Clearfog-A1-DT-file.patch b/target/linux/mvebu/patches-4.4/053-ARM-dts-Add-SolidRun-Armada-388-Clearfog-A1-DT-file.patch new file mode 100644 index 0000000000..b25d71071f --- /dev/null +++ b/target/linux/mvebu/patches-4.4/053-ARM-dts-Add-SolidRun-Armada-388-Clearfog-A1-DT-file.patch @@ -0,0 +1,611 @@ +From 4c945e8556ec7ea5b19d4f8721b212f468656e0d Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sun, 6 Dec 2015 21:52:06 +0000 +Subject: [PATCH] ARM: dts: Add SolidRun Armada 388 Clearfog A1 DT file + +Add support for the SolidRun Armada 388 Clearfog A1 board. This board +has an Armada 388 microsom, dedicated gigabit ethernet, six switched +gigabit ethernet ports, SFP cage, two Mini-PCIe/mSATA slots, a m.2 SATA +slot, and a MikroBUS connector to allow MikroBUS modules to be added. + +This DT file adds support for all board facilities with the exception +of full SFP support. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +Acked-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +--- + arch/arm/boot/dts/Makefile | 1 + + arch/arm/boot/dts/armada-388-clearfog.dts | 456 +++++++++++++++++++++ + .../arm/boot/dts/armada-38x-solidrun-microsom.dtsi | 115 ++++++ + 3 files changed, 572 insertions(+) + create mode 100644 arch/arm/boot/dts/armada-388-clearfog.dts + create mode 100644 arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi + +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -750,6 +750,7 @@ dtb-$(CONFIG_MACH_ARMADA_38X) += \ + armada-385-linksys-cobra.dtb \ + armada-385-linksys-rango.dtb \ + armada-385-linksys-shelby.dtb \ ++ armada-388-clearfog.dtb \ + armada-388-db.dtb \ + armada-388-gp.dtb \ + armada-388-rd.dtb +--- /dev/null ++++ b/arch/arm/boot/dts/armada-388-clearfog.dts +@@ -0,0 +1,456 @@ ++/* ++ * Device Tree file for SolidRun Clearfog revision A1 rev 2.0 (88F6828) ++ * ++ * Copyright (C) 2015 Russell King ++ * ++ * This board is in development; the contents of this file work with ++ * the A1 rev 2.0 of the board, which does not represent final ++ * production board. Things will change, don't expect this file to ++ * remain compatible info the future. ++ * ++ * 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 ++ * version 2 as published by the Free Software Foundation. ++ * ++ * 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 , 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 "armada-388.dtsi" ++#include "armada-38x-solidrun-microsom.dtsi" ++ ++/ { ++ model = "SolidRun Clearfog A1"; ++ compatible = "solidrun,clearfog-a1", "marvell,armada388", ++ "marvell,armada385", "marvell,armada380"; ++ ++ aliases { ++ /* So that mvebu u-boot can update the MAC addresses */ ++ ethernet1 = ð0; ++ ethernet2 = ð1; ++ ethernet3 = ð2; ++ }; ++ ++ chosen { ++ stdout-path = "serial0:115200n8"; ++ }; ++ ++ reg_3p3v: regulator-3p3v { ++ compatible = "regulator-fixed"; ++ regulator-name = "3P3V"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ regulator-always-on; ++ }; ++ ++ soc { ++ internal-regs { ++ ethernet@30000 { ++ phy-mode = "sgmii"; ++ status = "okay"; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ ++ ethernet@34000 { ++ phy-mode = "sgmii"; ++ status = "okay"; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ ++ i2c@11000 { ++ /* Is there anything on this? */ ++ clock-frequency = <100000>; ++ pinctrl-0 = <&i2c0_pins>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ /* ++ * PCA9655 GPIO expander, up to 1MHz clock. ++ * 0-CON3 CLKREQ# ++ * 1-CON3 PERST# ++ * 2-CON2 PERST# ++ * 3-CON3 W_DISABLE ++ * 4-CON2 CLKREQ# ++ * 5-USB3 overcurrent ++ * 6-USB3 power ++ * 7-CON2 W_DISABLE ++ * 8-JP4 P1 ++ * 9-JP4 P4 ++ * 10-JP4 P5 ++ * 11-m.2 DEVSLP ++ * 12-SFP_LOS ++ * 13-SFP_TX_FAULT ++ * 14-SFP_TX_DISABLE ++ * 15-SFP_MOD_DEF0 ++ */ ++ expander0: gpio-expander@20 { ++ /* ++ * This is how it should be: ++ * compatible = "onnn,pca9655", ++ * "nxp,pca9555"; ++ * but you can't do this because of ++ * the way I2C works. ++ */ ++ compatible = "nxp,pca9555"; ++ gpio-controller; ++ #gpio-cells = <2>; ++ reg = <0x20>; ++ ++ pcie1_0_clkreq { ++ gpio-hog; ++ gpios = <0 GPIO_ACTIVE_LOW>; ++ input; ++ line-name = "pcie1.0-clkreq"; ++ }; ++ pcie1_0_w_disable { ++ gpio-hog; ++ gpios = <3 GPIO_ACTIVE_LOW>; ++ output-low; ++ line-name = "pcie1.0-w-disable"; ++ }; ++ pcie2_0_clkreq { ++ gpio-hog; ++ gpios = <4 GPIO_ACTIVE_LOW>; ++ input; ++ line-name = "pcie2.0-clkreq"; ++ }; ++ pcie2_0_w_disable { ++ gpio-hog; ++ gpios = <7 GPIO_ACTIVE_LOW>; ++ output-low; ++ line-name = "pcie2.0-w-disable"; ++ }; ++ usb3_ilimit { ++ gpio-hog; ++ gpios = <5 GPIO_ACTIVE_LOW>; ++ input; ++ line-name = "usb3-current-limit"; ++ }; ++ usb3_power { ++ gpio-hog; ++ gpios = <6 GPIO_ACTIVE_HIGH>; ++ output-high; ++ line-name = "usb3-power"; ++ }; ++ m2_devslp { ++ gpio-hog; ++ gpios = <11 GPIO_ACTIVE_HIGH>; ++ output-low; ++ line-name = "m.2 devslp"; ++ }; ++ sfp_los { ++ /* SFP loss of signal */ ++ gpio-hog; ++ gpios = <12 GPIO_ACTIVE_HIGH>; ++ input; ++ line-name = "sfp-los"; ++ }; ++ sfp_tx_fault { ++ /* SFP laser fault */ ++ gpio-hog; ++ gpios = <13 GPIO_ACTIVE_HIGH>; ++ input; ++ line-name = "sfp-tx-fault"; ++ }; ++ sfp_tx_disable { ++ /* SFP transmit disable */ ++ gpio-hog; ++ gpios = <14 GPIO_ACTIVE_HIGH>; ++ output-low; ++ line-name = "sfp-tx-disable"; ++ }; ++ sfp_mod_def0 { ++ /* SFP module present */ ++ gpio-hog; ++ gpios = <15 GPIO_ACTIVE_LOW>; ++ input; ++ line-name = "sfp-mod-def0"; ++ }; ++ }; ++ ++ /* The MCP3021 is 100kHz clock only */ ++ mikrobus_adc: mcp3021@4c { ++ compatible = "microchip,mcp3021"; ++ reg = <0x4c>; ++ }; ++ ++ /* Also something at 0x64 */ ++ }; ++ ++ i2c@11100 { ++ /* ++ * Routed to SFP, mikrobus, and PCIe. ++ * SFP limits this to 100kHz, and requires ++ * an AT24C01A/02/04 with address pins tied ++ * low, which takes addresses 0x50 and 0x51. ++ * Mikrobus doesn't specify beyond an I2C ++ * bus being present. ++ * PCIe uses ARP to assign addresses, or ++ * 0x63-0x64. ++ */ ++ clock-frequency = <100000>; ++ pinctrl-0 = <&clearfog_i2c1_pins>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ }; ++ ++ mdio@72004 { ++ pinctrl-0 = <&mdio_pins>; ++ pinctrl-names = "default"; ++ ++ phy_dedicated: ethernet-phy@0 { ++ /* ++ * Annoyingly, the marvell phy driver ++ * configures the LED register, rather ++ * than preserving reset-loaded setting. ++ * We undo that rubbish here. ++ */ ++ marvell,reg-init = <3 16 0 0x101e>; ++ reg = <0>; ++ }; ++ }; ++ ++ pinctrl@18000 { ++ clearfog_dsa0_clk_pins: clearfog-dsa0-clk-pins { ++ marvell,pins = "mpp46"; ++ marvell,function = "ref"; ++ }; ++ clearfog_dsa0_pins: clearfog-dsa0-pins { ++ marvell,pins = "mpp23", "mpp41"; ++ marvell,function = "gpio"; ++ }; ++ clearfog_i2c1_pins: i2c1-pins { ++ /* SFP, PCIe, mSATA, mikrobus */ ++ marvell,pins = "mpp26", "mpp27"; ++ marvell,function = "i2c1"; ++ }; ++ clearfog_sdhci_cd_pins: clearfog-sdhci-cd-pins { ++ marvell,pins = "mpp20"; ++ marvell,function = "gpio"; ++ }; ++ clearfog_sdhci_pins: clearfog-sdhci-pins { ++ marvell,pins = "mpp21", "mpp28", ++ "mpp37", "mpp38", ++ "mpp39", "mpp40"; ++ marvell,function = "sd0"; ++ }; ++ clearfog_spi1_cs_pins: spi1-cs-pins { ++ marvell,pins = "mpp55"; ++ marvell,function = "spi1"; ++ }; ++ mikro_pins: mikro-pins { ++ /* int: mpp22 rst: mpp29 */ ++ marvell,pins = "mpp22", "mpp29"; ++ marvell,function = "gpio"; ++ }; ++ mikro_spi_pins: mikro-spi-pins { ++ marvell,pins = "mpp43"; ++ marvell,function = "spi1"; ++ }; ++ mikro_uart_pins: mikro-uart-pins { ++ marvell,pins = "mpp24", "mpp25"; ++ marvell,function = "ua1"; ++ }; ++ rear_button_pins: rear-button-pins { ++ marvell,pins = "mpp34"; ++ marvell,function = "gpio"; ++ }; ++ }; ++ ++ sata@a8000 { ++ /* pinctrl? */ ++ status = "okay"; ++ }; ++ ++ sata@e0000 { ++ /* pinctrl? */ ++ status = "okay"; ++ }; ++ ++ sdhci@d8000 { ++ bus-width = <4>; ++ cd-gpios = <&gpio0 20 GPIO_ACTIVE_LOW>; ++ no-1-8-v; ++ pinctrl-0 = <&clearfog_sdhci_pins ++ &clearfog_sdhci_cd_pins>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ vmmc = <®_3p3v>; ++ wp-inverted; ++ }; ++ ++ serial@12100 { ++ /* mikrobus uart */ ++ pinctrl-0 = <&mikro_uart_pins>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ }; ++ ++ spi@10680 { ++ /* ++ * We don't seem to have the W25Q32 on the ++ * A1 Rev 2.0 boards, so disable SPI. ++ * CS0: W25Q32 (doesn't appear to be present) ++ * CS1: ++ * CS2: mikrobus ++ */ ++ pinctrl-0 = <&spi1_pins ++ &clearfog_spi1_cs_pins ++ &mikro_spi_pins>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ ++ spi-flash@0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "w25q32", "jedec,spi-nor"; ++ reg = <0>; /* Chip select 0 */ ++ spi-max-frequency = <3000000>; ++ status = "disabled"; ++ }; ++ }; ++ ++ usb@58000 { ++ /* CON3, nearest power. */ ++ status = "okay"; ++ }; ++ ++ usb3@f0000 { ++ /* CON2, nearest CPU, USB2 only. */ ++ status = "okay"; ++ }; ++ ++ usb3@f8000 { ++ /* CON7 */ ++ status = "okay"; ++ }; ++ }; ++ ++ pcie-controller { ++ status = "okay"; ++ /* ++ * The two PCIe units are accessible through ++ * the mini-PCIe connectors on the board. ++ */ ++ pcie@2,0 { ++ /* Port 1, Lane 0. CON3, nearest power. */ ++ reset-gpios = <&expander0 1 GPIO_ACTIVE_LOW>; ++ status = "okay"; ++ }; ++ pcie@3,0 { ++ /* Port 2, Lane 0. CON2, nearest CPU. */ ++ reset-gpios = <&expander0 2 GPIO_ACTIVE_LOW>; ++ status = "okay"; ++ }; ++ }; ++ }; ++ ++ dsa@0 { ++ compatible = "marvell,dsa"; ++ dsa,ethernet = <ð1>; ++ dsa,mii-bus = <&mdio>; ++ pinctrl-0 = <&clearfog_dsa0_clk_pins &clearfog_dsa0_pins>; ++ pinctrl-names = "default"; ++ #address-cells = <2>; ++ #size-cells = <0>; ++ ++ switch@0 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <4 0>; ++ ++ port@0 { ++ reg = <0>; ++ label = "lan1"; ++ }; ++ ++ port@1 { ++ reg = <1>; ++ label = "lan2"; ++ }; ++ ++ port@2 { ++ reg = <2>; ++ label = "lan3"; ++ }; ++ ++ port@3 { ++ reg = <3>; ++ label = "lan4"; ++ }; ++ ++ port@4 { ++ reg = <4>; ++ label = "lan5"; ++ }; ++ ++ port@5 { ++ reg = <5>; ++ label = "cpu"; ++ }; ++ ++ port@6 { ++ /* 88E1512 external phy */ ++ reg = <6>; ++ label = "lan6"; ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ }; ++ }; ++ ++ gpio-keys { ++ compatible = "gpio-keys"; ++ pinctrl-0 = <&rear_button_pins>; ++ pinctrl-names = "default"; ++ ++ button_0 { ++ /* The rear SW3 button */ ++ label = "Rear Button"; ++ gpios = <&gpio1 2 GPIO_ACTIVE_LOW>; ++ linux,can-disable; ++ linux,code = <BTN_0>; ++ }; ++ }; ++}; +--- /dev/null ++++ b/arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi +@@ -0,0 +1,115 @@ ++/* ++ * Device Tree file for SolidRun Armada 38x Microsom ++ * ++ * Copyright (C) 2015 Russell King ++ * ++ * This board is in development; the contents of this file work with ++ * the A1 rev 2.0 of the board, which does not represent final ++ * production board. Things will change, don't expect this file to ++ * remain compatible info the future. ++ * ++ * 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 ++ * version 2 as published by the Free Software Foundation. ++ * ++ * 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 , 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. ++ */ ++#include <dt-bindings/input/input.h> ++#include <dt-bindings/gpio/gpio.h> ++ ++/ { ++ memory { ++ device_type = "memory"; ++ reg = <0x00000000 0x10000000>; /* 256 MB */ ++ }; ++ ++ soc { ++ ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000 ++ MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000 ++ MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000 ++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>; ++ ++ internal-regs { ++ ethernet@70000 { ++ pinctrl-0 = <&ge0_rgmii_pins>; ++ pinctrl-names = "default"; ++ phy = <&phy_dedicated>; ++ phy-mode = "rgmii-id"; ++ status = "okay"; ++ }; ++ ++ mdio@72004 { ++ /* ++ * Add the phy clock here, so the phy can be ++ * accessed to read its IDs prior to binding ++ * with the driver. ++ */ ++ pinctrl-0 = <&mdio_pins µsom_phy_clk_pins>; ++ pinctrl-names = "default"; ++ ++ phy_dedicated: ethernet-phy@0 { ++ /* ++ * Annoyingly, the marvell phy driver ++ * configures the LED register, rather ++ * than preserving reset-loaded setting. ++ * We undo that rubbish here. ++ */ ++ marvell,reg-init = <3 16 0 0x101e>; ++ reg = <0>; ++ }; ++ }; ++ ++ pinctrl@18000 { ++ microsom_phy_clk_pins: microsom-phy-clk-pins { ++ marvell,pins = "mpp45"; ++ marvell,function = "ref"; ++ }; ++ }; ++ ++ rtc@a3800 { ++ /* ++ * If the rtc doesn't work, run "date reset" ++ * twice in u-boot. ++ */ ++ status = "okay"; ++ }; ++ ++ serial@12000 { ++ pinctrl-0 = <&uart0_pins>; ++ pinctrl-names = "default"; ++ status = "okay"; ++ }; ++ }; ++ }; ++}; diff --git a/target/linux/mvebu/patches-4.4/054-ARM-dts-armada-38x-enable-buffer-manager-support-on-.patch b/target/linux/mvebu/patches-4.4/054-ARM-dts-armada-38x-enable-buffer-manager-support-on-.patch new file mode 100644 index 0000000000..705d503c68 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/054-ARM-dts-armada-38x-enable-buffer-manager-support-on-.patch @@ -0,0 +1,256 @@ +From c49e99c2b25a412623412a461bb751239208b9b3 Mon Sep 17 00:00:00 2001 +From: Marcin Wojtas <mw@semihalf.com> +Date: Mon, 14 Mar 2016 09:38:58 +0100 +Subject: [PATCH] ARM: dts: armada-38x: enable buffer manager support on Armada + 38x boards + +Since mvneta driver supports using hardware buffer management (BM), in +order to use it, board files have to be adjusted accordingly. This commit +enables BM on: +* A385-DB-AP - each port has its own pool for long and common pool for +short packets, +* A388-ClearFog - same as above, +* A388-DB - to each port unique 'short' and 'long' pools are mapped, +* A388-GP - same as above. + +Moreover appropriate entry is added to 'soc' node ranges, as well as "okay" +status for 'bm' and 'bm-bppi' (internal SRAM) nodes. + +[gregory.clement@free-electrons.com: add suppport for the ClearFog board] + +Signed-off-by: Marcin Wojtas <mw@semihalf.com> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +Acked-by: Russell King <rmk+kernel@arm.linux.org.uk> +Signed-off-by: David S. Miller <davem@davemloft.net> +--- + arch/arm/boot/dts/armada-385-db-ap.dts | 20 +++++++++++++++++++- + arch/arm/boot/dts/armada-388-clearfog.dts | 6 ++++++ + arch/arm/boot/dts/armada-388-db.dts | 17 ++++++++++++++++- + arch/arm/boot/dts/armada-388-gp.dts | 17 ++++++++++++++++- + arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi | 15 ++++++++++++++- + 5 files changed, 71 insertions(+), 4 deletions(-) + +--- a/arch/arm/boot/dts/armada-385-db-ap.dts ++++ b/arch/arm/boot/dts/armada-385-db-ap.dts +@@ -61,7 +61,8 @@ + ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000 + MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000 + MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000 +- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>; ++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000 ++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>; + + internal-regs { + spi1: spi@10680 { +@@ -138,12 +139,18 @@ + status = "okay"; + phy = <&phy2>; + phy-mode = "sgmii"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <1>; ++ bm,pool-short = <3>; + }; + + ethernet@34000 { + status = "okay"; + phy = <&phy1>; + phy-mode = "sgmii"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <2>; ++ bm,pool-short = <3>; + }; + + ethernet@70000 { +@@ -157,6 +164,13 @@ + status = "okay"; + phy = <&phy0>; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <0>; ++ bm,pool-short = <3>; ++ }; ++ ++ bm@c8000 { ++ status = "okay"; + }; + + nfc: flash@d0000 { +@@ -178,6 +192,10 @@ + }; + }; + ++ bm-bppi { ++ status = "okay"; ++ }; ++ + pcie-controller { + status = "okay"; + +--- a/arch/arm/boot/dts/armada-388-clearfog.dts ++++ b/arch/arm/boot/dts/armada-388-clearfog.dts +@@ -78,6 +78,9 @@ + internal-regs { + ethernet@30000 { + phy-mode = "sgmii"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <2>; ++ bm,pool-short = <1>; + status = "okay"; + + fixed-link { +@@ -88,6 +91,9 @@ + + ethernet@34000 { + phy-mode = "sgmii"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <3>; ++ bm,pool-short = <1>; + status = "okay"; + + fixed-link { +--- a/arch/arm/boot/dts/armada-388-db.dts ++++ b/arch/arm/boot/dts/armada-388-db.dts +@@ -66,7 +66,8 @@ + ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000 + MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000 + MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000 +- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>; ++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000 ++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>; + + internal-regs { + spi@10600 { +@@ -99,6 +100,9 @@ + status = "okay"; + phy = <&phy1>; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <2>; ++ bm,pool-short = <3>; + }; + + usb@58000 { +@@ -109,6 +113,9 @@ + status = "okay"; + phy = <&phy0>; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <0>; ++ bm,pool-short = <1>; + }; + + mdio@72004 { +@@ -129,6 +136,10 @@ + status = "okay"; + }; + ++ bm@c8000 { ++ status = "okay"; ++ }; ++ + flash@d0000 { + status = "okay"; + num-cs = <1>; +@@ -169,6 +180,10 @@ + }; + }; + ++ bm-bppi { ++ status = "okay"; ++ }; ++ + pcie-controller { + status = "okay"; + /* +--- a/arch/arm/boot/dts/armada-388-gp.dts ++++ b/arch/arm/boot/dts/armada-388-gp.dts +@@ -60,7 +60,8 @@ + ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000 + MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000 + MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000 +- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>; ++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000 ++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>; + + internal-regs { + spi@10600 { +@@ -133,6 +134,9 @@ + status = "okay"; + phy = <&phy1>; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <2>; ++ bm,pool-short = <3>; + }; + + /* CON4 */ +@@ -152,6 +156,9 @@ + status = "okay"; + phy = <&phy0>; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <0>; ++ bm,pool-short = <1>; + }; + + +@@ -186,6 +193,10 @@ + }; + }; + ++ bm@c8000 { ++ status = "okay"; ++ }; ++ + sata@e0000 { + pinctrl-names = "default"; + pinctrl-0 = <&sata2_pins>, <&sata3_pins>; +@@ -240,6 +251,10 @@ + }; + }; + ++ bm-bppi { ++ status = "okay"; ++ }; ++ + pcie-controller { + status = "okay"; + /* +--- a/arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi ++++ b/arch/arm/boot/dts/armada-38x-solidrun-microsom.dtsi +@@ -58,7 +58,8 @@ + ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000 + MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000 + MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000 +- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>; ++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000 ++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>; + + internal-regs { + ethernet@70000 { +@@ -66,6 +67,9 @@ + pinctrl-names = "default"; + phy = <&phy_dedicated>; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <0>; ++ bm,pool-short = <1>; + status = "okay"; + }; + +@@ -110,6 +114,15 @@ + pinctrl-names = "default"; + status = "okay"; + }; ++ ++ bm@c8000 { ++ status = "okay"; ++ }; + }; ++ ++ bm-bppi { ++ status = "okay"; ++ }; ++ + }; + }; diff --git a/target/linux/mvebu/patches-4.4/055-ARM-dts-armada-388-clearfog-remove-duplicate-mdio-en.patch b/target/linux/mvebu/patches-4.4/055-ARM-dts-armada-388-clearfog-remove-duplicate-mdio-en.patch new file mode 100644 index 0000000000..823d514dfa --- /dev/null +++ b/target/linux/mvebu/patches-4.4/055-ARM-dts-armada-388-clearfog-remove-duplicate-mdio-en.patch @@ -0,0 +1,41 @@ +From d261861ab52623e34a25fe6ae76714456edda033 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@armlinux.org.uk> +Date: Sun, 10 Jul 2016 16:27:38 +0100 +Subject: [PATCH] ARM: dts: armada-388-clearfog: remove duplicate mdio entry + +The clearfog DTS should not be defining the on-board phy, this device +is located on the microsom. Remove the duplicated definition. + +Reported-by: Jon Nettleton <jon@solid-run.com> +Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> +Reviewed-by: Andrew Lunn <andrew@lunn.ch> +Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com> +--- + arch/arm/boot/dts/armada-388-clearfog.dts | 16 ---------------- + 1 file changed, 16 deletions(-) + +--- a/arch/arm/boot/dts/armada-388-clearfog.dts ++++ b/arch/arm/boot/dts/armada-388-clearfog.dts +@@ -239,22 +239,6 @@ + status = "okay"; + }; + +- mdio@72004 { +- pinctrl-0 = <&mdio_pins>; +- pinctrl-names = "default"; +- +- phy_dedicated: ethernet-phy@0 { +- /* +- * Annoyingly, the marvell phy driver +- * configures the LED register, rather +- * than preserving reset-loaded setting. +- * We undo that rubbish here. +- */ +- marvell,reg-init = <3 16 0 0x101e>; +- reg = <0>; +- }; +- }; +- + pinctrl@18000 { + clearfog_dsa0_clk_pins: clearfog-dsa0-clk-pins { + marvell,pins = "mpp46"; diff --git a/target/linux/mvebu/patches-4.4/100-find_active_root.patch b/target/linux/mvebu/patches-4.4/100-find_active_root.patch new file mode 100644 index 0000000000..c997f5b342 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/100-find_active_root.patch @@ -0,0 +1,62 @@ +The WRT1900AC among other Linksys routers uses a dual-firmware layout. +Dynamically rename the active partition to "ubi". + +Signed-off-by: Imre Kaloz <kaloz@openwrt.org> + +--- a/drivers/mtd/ofpart.c ++++ b/drivers/mtd/ofpart.c +@@ -25,6 +25,8 @@ static bool node_has_compatible(struct d + return of_get_property(pp, "compatible", NULL); + } + ++static int mangled_rootblock; ++ + static int parse_ofpart_partitions(struct mtd_info *master, + struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +@@ -32,6 +34,7 @@ static int parse_ofpart_partitions(struc + struct device_node *mtd_node; + struct device_node *ofpart_node; + const char *partname; ++ const char *owrtpart = "ubi"; + struct device_node *pp; + int nr_parts, i, ret = 0; + bool dedicated = true; +@@ -110,9 +113,15 @@ static int parse_ofpart_partitions(struc + (*pparts)[i].offset = of_read_number(reg, a_cells); + (*pparts)[i].size = of_read_number(reg + a_cells, s_cells); + +- partname = of_get_property(pp, "label", &len); +- if (!partname) +- partname = of_get_property(pp, "name", &len); ++ if (mangled_rootblock && (i == mangled_rootblock)) { ++ partname = owrtpart; ++ } else { ++ partname = of_get_property(pp, "label", &len); ++ ++ if (!partname) ++ partname = of_get_property(pp, "name", &len); ++ } ++ + (*pparts)[i].name = partname; + + if (of_get_property(pp, "read-only", &len)) +@@ -215,6 +224,18 @@ static int __init ofpart_parser_init(voi + return 0; + } + ++static int __init active_root(char *str) ++{ ++ get_option(&str, &mangled_rootblock); ++ ++ if (!mangled_rootblock) ++ return 1; ++ ++ return 1; ++} ++ ++__setup("mangled_rootblock=", active_root); ++ + static void __exit ofpart_parser_exit(void) + { + deregister_mtd_parser(&ofpart_parser); diff --git a/target/linux/mvebu/patches-4.4/102-revert_i2c_delay.patch b/target/linux/mvebu/patches-4.4/102-revert_i2c_delay.patch new file mode 100644 index 0000000000..f97bba2ec8 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/102-revert_i2c_delay.patch @@ -0,0 +1,15 @@ +--- a/arch/arm/boot/dts/armada-xp.dtsi ++++ b/arch/arm/boot/dts/armada-xp.dtsi +@@ -98,12 +98,10 @@ + + + i2c0: i2c@11000 { +- compatible = "marvell,mv78230-i2c", "marvell,mv64xxx-i2c"; + reg = <0x11000 0x100>; + }; + + i2c1: i2c@11100 { +- compatible = "marvell,mv78230-i2c", "marvell,mv64xxx-i2c"; + reg = <0x11100 0x100>; + }; + diff --git a/target/linux/mvebu/patches-4.4/103-remove-nand-driver-bug.patch b/target/linux/mvebu/patches-4.4/103-remove-nand-driver-bug.patch new file mode 100644 index 0000000000..e9cc027b3c --- /dev/null +++ b/target/linux/mvebu/patches-4.4/103-remove-nand-driver-bug.patch @@ -0,0 +1,13 @@ +Remove a BUG() call that would crash on a race condition that should +otherwise be harmless. + +--- a/drivers/mtd/nand/pxa3xx_nand.c ++++ b/drivers/mtd/nand/pxa3xx_nand.c +@@ -701,7 +701,6 @@ static void handle_data_pio(struct pxa3x + default: + dev_err(&info->pdev->dev, "%s: invalid state %d\n", __func__, + info->state); +- BUG(); + } + + /* Update buffer pointers for multi-page read/write */ diff --git a/target/linux/mvebu/patches-4.4/104-linksys_mamba_disable_keep_config.patch b/target/linux/mvebu/patches-4.4/104-linksys_mamba_disable_keep_config.patch new file mode 100644 index 0000000000..4c6b3115ed --- /dev/null +++ b/target/linux/mvebu/patches-4.4/104-linksys_mamba_disable_keep_config.patch @@ -0,0 +1,10 @@ +--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts ++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts +@@ -298,7 +298,6 @@ + nand@d0000 { + status = "okay"; + num-cs = <1>; +- marvell,nand-keep-config; + marvell,nand-enable-arbiter; + nand-on-flash-bbt; + nand-ecc-strength = <4>; diff --git a/target/linux/mvebu/patches-4.4/106-enable-bm-on-linksys-devices.patch b/target/linux/mvebu/patches-4.4/106-enable-bm-on-linksys-devices.patch new file mode 100644 index 0000000000..e80bc39a69 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/106-enable-bm-on-linksys-devices.patch @@ -0,0 +1,107 @@ +--- a/arch/arm/boot/dts/armada-385-linksys.dtsi ++++ b/arch/arm/boot/dts/armada-385-linksys.dtsi +@@ -59,7 +59,8 @@ + ranges = <MBUS_ID(0xf0, 0x01) 0 0xf1000000 0x100000 + MBUS_ID(0x01, 0x1d) 0 0xfff00000 0x100000 + MBUS_ID(0x09, 0x19) 0 0xf1100000 0x10000 +- MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000>; ++ MBUS_ID(0x09, 0x15) 0 0xf1110000 0x10000 ++ MBUS_ID(0x0c, 0x04) 0 0xf1200000 0x100000>; + + internal-regs { + +@@ -93,6 +94,9 @@ + ethernet@70000 { + status = "okay"; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <0>; ++ bm,pool-short = <3>; + fixed-link { + speed = <1000>; + full-duplex; +@@ -102,6 +106,9 @@ + ethernet@34000 { + status = "okay"; + phy-mode = "sgmii"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <2>; ++ bm,pool-short = <3>; + fixed-link { + speed = <1000>; + full-duplex; +@@ -112,6 +119,10 @@ + status = "okay"; + }; + ++ bm@c8000 { ++ status = "okay"; ++ }; ++ + sata@a8000 { + status = "okay"; + }; +@@ -198,6 +209,10 @@ + }; + }; + ++ bm-bppi { ++ status = "okay"; ++ }; ++ + pcie-controller { + status = "okay"; + +--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts ++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts +@@ -71,7 +71,8 @@ + ranges = <MBUS_ID(0xf0, 0x01) 0 0 0xf1000000 0x100000 + MBUS_ID(0x01, 0x1d) 0 0 0xfff00000 0x100000 + MBUS_ID(0x09, 0x09) 0 0 0xf1100000 0x10000 +- MBUS_ID(0x09, 0x05) 0 0 0xf1110000 0x10000>; ++ MBUS_ID(0x09, 0x05) 0 0 0xf1110000 0x10000 ++ MBUS_ID(0x0c, 0x04) 0 0 0xf1200000 0x100000>; + + pcie-controller { + status = "okay"; +@@ -205,6 +206,9 @@ + pinctrl-names = "default"; + status = "okay"; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <0>; ++ bm,pool-short = <3>; + fixed-link { + speed = <1000>; + full-duplex; +@@ -216,12 +220,19 @@ + pinctrl-names = "default"; + status = "okay"; + phy-mode = "rgmii-id"; ++ buffer-manager = <&bm>; ++ bm,pool-long = <1>; ++ bm,pool-short = <3>; + fixed-link { + speed = <1000>; + full-duplex; + }; + }; + ++ bm@c0000 { ++ status = "okay"; ++ }; ++ + /* USB part of the eSATA/USB 2.0 port */ + usb@50000 { + status = "okay"; +@@ -379,6 +390,10 @@ + }; + }; + }; ++ ++ bm-bppi { ++ status = "okay"; ++ }; + }; + + gpio_keys { diff --git a/target/linux/mvebu/patches-4.4/110-pxa3xxx_revert_irq_thread.patch b/target/linux/mvebu/patches-4.4/110-pxa3xxx_revert_irq_thread.patch new file mode 100644 index 0000000000..30da17deb9 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/110-pxa3xxx_revert_irq_thread.patch @@ -0,0 +1,69 @@ +Revert "mtd: pxa3xx-nand: handle PIO in threaded interrupt" + +This reverts commit 24542257a3b987025d4b998ec2d15e556c98ad3f +This upstream change has been causing spurious timeouts on accesses +to the NAND flash if something else on the system is causing +significant latency. + +Nothing guarantees that the thread will run in time, so the +usual timeout is unreliable. + +Signed-off-by: Felix Fietkau <nbd@nbd.name> + +--- a/drivers/mtd/nand/pxa3xx_nand.c ++++ b/drivers/mtd/nand/pxa3xx_nand.c +@@ -765,24 +765,11 @@ static void start_data_dma(struct pxa3xx + __func__, direction, info->dma_cookie, info->sg.length); + } + +-static irqreturn_t pxa3xx_nand_irq_thread(int irq, void *data) +-{ +- struct pxa3xx_nand_info *info = data; +- +- handle_data_pio(info); +- +- info->state = STATE_CMD_DONE; +- nand_writel(info, NDSR, NDSR_WRDREQ | NDSR_RDDREQ); +- +- return IRQ_HANDLED; +-} +- + static irqreturn_t pxa3xx_nand_irq(int irq, void *devid) + { + struct pxa3xx_nand_info *info = devid; + unsigned int status, is_completed = 0, is_ready = 0; + unsigned int ready, cmd_done; +- irqreturn_t ret = IRQ_HANDLED; + + if (info->cs == 0) { + ready = NDSR_FLASH_RDY; +@@ -824,8 +811,7 @@ static irqreturn_t pxa3xx_nand_irq(int i + } else { + info->state = (status & NDSR_RDDREQ) ? + STATE_PIO_READING : STATE_PIO_WRITING; +- ret = IRQ_WAKE_THREAD; +- goto NORMAL_IRQ_EXIT; ++ handle_data_pio(info); + } + } + if (status & cmd_done) { +@@ -870,7 +856,7 @@ static irqreturn_t pxa3xx_nand_irq(int i + if (is_ready) + complete(&info->dev_ready); + NORMAL_IRQ_EXIT: +- return ret; ++ return IRQ_HANDLED; + } + + static inline int is_buf_blank(uint8_t *buf, size_t len) +@@ -1849,9 +1835,7 @@ static int alloc_nand_resource(struct pl + /* initialize all interrupts to be disabled */ + disable_int(info, NDSR_MASK); + +- ret = request_threaded_irq(irq, pxa3xx_nand_irq, +- pxa3xx_nand_irq_thread, IRQF_ONESHOT, +- pdev->name, info); ++ ret = request_irq(irq, pxa3xx_nand_irq, 0, pdev->name, info); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + goto fail_free_buf; diff --git a/target/linux/mvebu/patches-4.4/120-phy-move-fixed_phy-MII-register-generation-to-a-libr.patch b/target/linux/mvebu/patches-4.4/120-phy-move-fixed_phy-MII-register-generation-to-a-libr.patch new file mode 100644 index 0000000000..00745f1c9b --- /dev/null +++ b/target/linux/mvebu/patches-4.4/120-phy-move-fixed_phy-MII-register-generation-to-a-libr.patch @@ -0,0 +1,306 @@ +From 4d5621372f6e7ddbfd5879602f82073987bcc722 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sun, 20 Sep 2015 09:57:10 +0100 +Subject: [PATCH 709/744] phy: move fixed_phy MII register generation to a + library + +Move the fixed_phy MII register generation to a library to allow other +software phy implementations to use this code. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/Kconfig | 4 ++ + drivers/net/phy/Makefile | 3 +- + drivers/net/phy/fixed_phy.c | 95 ++------------------------------- + drivers/net/phy/swphy.c | 126 ++++++++++++++++++++++++++++++++++++++++++++ + drivers/net/phy/swphy.h | 8 +++ + 5 files changed, 143 insertions(+), 93 deletions(-) + create mode 100644 drivers/net/phy/swphy.c + create mode 100644 drivers/net/phy/swphy.h + +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -26,6 +26,9 @@ config SWCONFIG_LEDS + bool "Switch LED trigger support" + depends on (SWCONFIG && LEDS_TRIGGERS) + ++config SWPHY ++ bool ++ + comment "MII PHY device drivers" + + config AQUANTIA_PHY +@@ -205,6 +208,7 @@ config RTL8306_PHY + config FIXED_PHY + tristate "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs" + depends on PHYLIB ++ select SWPHY + ---help--- + Adds the platform "fixed" MDIO Bus to cover the boards that use + PHYs that are not connected to the real MDIO bus. +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -1,6 +1,7 @@ + # Makefile for Linux PHY drivers + +-libphy-objs := phy.o phy_device.o mdio_bus.o ++libphy-y := phy.o phy_device.o mdio_bus.o ++libphy-$(CONFIG_SWPHY) += swphy.o + + obj-$(CONFIG_MDIO_BOARDINFO) += mdio-boardinfo.o + +--- a/drivers/net/phy/fixed_phy.c ++++ b/drivers/net/phy/fixed_phy.c +@@ -24,6 +24,8 @@ + #include <linux/of.h> + #include <linux/gpio.h> + ++#include "swphy.h" ++ + #define MII_REGS_NUM 29 + + struct fixed_mdio_bus { +@@ -49,101 +51,10 @@ static struct fixed_mdio_bus platform_fm + + static int fixed_phy_update_regs(struct fixed_phy *fp) + { +- u16 bmsr = BMSR_ANEGCAPABLE; +- u16 bmcr = 0; +- u16 lpagb = 0; +- u16 lpa = 0; +- + if (gpio_is_valid(fp->link_gpio)) + fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio); + +- if (fp->status.duplex) { +- switch (fp->status.speed) { +- case 1000: +- bmsr |= BMSR_ESTATEN; +- break; +- case 100: +- bmsr |= BMSR_100FULL; +- break; +- case 10: +- bmsr |= BMSR_10FULL; +- break; +- default: +- break; +- } +- } else { +- switch (fp->status.speed) { +- case 1000: +- bmsr |= BMSR_ESTATEN; +- break; +- case 100: +- bmsr |= BMSR_100HALF; +- break; +- case 10: +- bmsr |= BMSR_10HALF; +- break; +- default: +- break; +- } +- } +- +- if (fp->status.link) { +- bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE; +- +- if (fp->status.duplex) { +- bmcr |= BMCR_FULLDPLX; +- +- switch (fp->status.speed) { +- case 1000: +- bmcr |= BMCR_SPEED1000; +- lpagb |= LPA_1000FULL; +- break; +- case 100: +- bmcr |= BMCR_SPEED100; +- lpa |= LPA_100FULL; +- break; +- case 10: +- lpa |= LPA_10FULL; +- break; +- default: +- pr_warn("fixed phy: unknown speed\n"); +- return -EINVAL; +- } +- } else { +- switch (fp->status.speed) { +- case 1000: +- bmcr |= BMCR_SPEED1000; +- lpagb |= LPA_1000HALF; +- break; +- case 100: +- bmcr |= BMCR_SPEED100; +- lpa |= LPA_100HALF; +- break; +- case 10: +- lpa |= LPA_10HALF; +- break; +- default: +- pr_warn("fixed phy: unknown speed\n"); +- return -EINVAL; +- } +- } +- +- if (fp->status.pause) +- lpa |= LPA_PAUSE_CAP; +- +- if (fp->status.asym_pause) +- lpa |= LPA_PAUSE_ASYM; +- } +- +- fp->regs[MII_PHYSID1] = 0; +- fp->regs[MII_PHYSID2] = 0; +- +- fp->regs[MII_BMSR] = bmsr; +- fp->regs[MII_BMCR] = bmcr; +- fp->regs[MII_LPA] = lpa; +- fp->regs[MII_STAT1000] = lpagb; +- +- return 0; ++ return swphy_update_regs(fp->regs, &fp->status); + } + + static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +--- /dev/null ++++ b/drivers/net/phy/swphy.c +@@ -0,0 +1,126 @@ ++/* ++ * Software PHY emulation ++ * ++ * Code taken from fixed_phy.c by Russell King <rmk+kernel@arm.linux.org.uk> ++ * ++ * Author: Vitaly Bordug <vbordug@ru.mvista.com> ++ * Anton Vorontsov <avorontsov@ru.mvista.com> ++ * ++ * Copyright (c) 2006-2007 MontaVista Software, Inc. ++ * ++ * 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/export.h> ++#include <linux/mii.h> ++#include <linux/phy.h> ++#include <linux/phy_fixed.h> ++ ++#include "swphy.h" ++ ++/** ++ * swphy_update_regs - update MII register array with fixed phy state ++ * @regs: array of 32 registers to update ++ * @state: fixed phy status ++ * ++ * Update the array of MII registers with the fixed phy link, speed, ++ * duplex and pause mode settings. ++ */ ++int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state) ++{ ++ u16 bmsr = BMSR_ANEGCAPABLE; ++ u16 bmcr = 0; ++ u16 lpagb = 0; ++ u16 lpa = 0; ++ ++ if (state->duplex) { ++ switch (state->speed) { ++ case 1000: ++ bmsr |= BMSR_ESTATEN; ++ break; ++ case 100: ++ bmsr |= BMSR_100FULL; ++ break; ++ case 10: ++ bmsr |= BMSR_10FULL; ++ break; ++ default: ++ break; ++ } ++ } else { ++ switch (state->speed) { ++ case 1000: ++ bmsr |= BMSR_ESTATEN; ++ break; ++ case 100: ++ bmsr |= BMSR_100HALF; ++ break; ++ case 10: ++ bmsr |= BMSR_10HALF; ++ break; ++ default: ++ break; ++ } ++ } ++ ++ if (state->link) { ++ bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE; ++ ++ if (state->duplex) { ++ bmcr |= BMCR_FULLDPLX; ++ ++ switch (state->speed) { ++ case 1000: ++ bmcr |= BMCR_SPEED1000; ++ lpagb |= LPA_1000FULL; ++ break; ++ case 100: ++ bmcr |= BMCR_SPEED100; ++ lpa |= LPA_100FULL; ++ break; ++ case 10: ++ lpa |= LPA_10FULL; ++ break; ++ default: ++ pr_warn("swphy: unknown speed\n"); ++ return -EINVAL; ++ } ++ } else { ++ switch (state->speed) { ++ case 1000: ++ bmcr |= BMCR_SPEED1000; ++ lpagb |= LPA_1000HALF; ++ break; ++ case 100: ++ bmcr |= BMCR_SPEED100; ++ lpa |= LPA_100HALF; ++ break; ++ case 10: ++ lpa |= LPA_10HALF; ++ break; ++ default: ++ pr_warn("swphy: unknown speed\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (state->pause) ++ lpa |= LPA_PAUSE_CAP; ++ ++ if (state->asym_pause) ++ lpa |= LPA_PAUSE_ASYM; ++ } ++ ++ regs[MII_PHYSID1] = 0; ++ regs[MII_PHYSID2] = 0; ++ ++ regs[MII_BMSR] = bmsr; ++ regs[MII_BMCR] = bmcr; ++ regs[MII_LPA] = lpa; ++ regs[MII_STAT1000] = lpagb; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(swphy_update_regs); +--- /dev/null ++++ b/drivers/net/phy/swphy.h +@@ -0,0 +1,8 @@ ++#ifndef SWPHY_H ++#define SWPHY_H ++ ++struct fixed_phy_status; ++ ++int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state); ++ ++#endif diff --git a/target/linux/mvebu/patches-4.4/121-phy-convert-swphy-register-generation-to-tabular-for.patch b/target/linux/mvebu/patches-4.4/121-phy-convert-swphy-register-generation-to-tabular-for.patch new file mode 100644 index 0000000000..0d689f39a3 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/121-phy-convert-swphy-register-generation-to-tabular-for.patch @@ -0,0 +1,203 @@ +From cd834fe430f030a63bfa9277bba194e8eef4dbd0 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sun, 20 Sep 2015 10:18:59 +0100 +Subject: [PATCH 710/744] phy: convert swphy register generation to tabular + form + +Convert the swphy register generation to tabular form which allows us +to eliminate multiple switch() statements. This results in a smaller +object code size, more efficient, and easier to add support for faster +speeds. + +Before: + +Idx Name Size VMA LMA File off Algn + 0 .text 00000164 00000000 00000000 00000034 2**2 + + text data bss dec hex filename + 388 0 0 388 184 swphy.o + +After: + +Idx Name Size VMA LMA File off Algn + 0 .text 000000fc 00000000 00000000 00000034 2**2 + 5 .rodata 00000028 00000000 00000000 00000138 2**2 + + text data bss dec hex filename + 324 0 0 324 144 swphy.o + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/swphy.c | 143 ++++++++++++++++++++++++++---------------------- + 1 file changed, 78 insertions(+), 65 deletions(-) + +--- a/drivers/net/phy/swphy.c ++++ b/drivers/net/phy/swphy.c +@@ -20,6 +20,72 @@ + + #include "swphy.h" + ++struct swmii_regs { ++ u16 bmcr; ++ u16 bmsr; ++ u16 lpa; ++ u16 lpagb; ++}; ++ ++enum { ++ SWMII_SPEED_10 = 0, ++ SWMII_SPEED_100, ++ SWMII_SPEED_1000, ++ SWMII_DUPLEX_HALF = 0, ++ SWMII_DUPLEX_FULL, ++}; ++ ++/* ++ * These two tables get bitwise-anded together to produce the final result. ++ * This means the speed table must contain both duplex settings, and the ++ * duplex table must contain all speed settings. ++ */ ++static const struct swmii_regs speed[] = { ++ [SWMII_SPEED_10] = { ++ .bmcr = BMCR_FULLDPLX, ++ .lpa = LPA_10FULL | LPA_10HALF, ++ }, ++ [SWMII_SPEED_100] = { ++ .bmcr = BMCR_FULLDPLX | BMCR_SPEED100, ++ .bmsr = BMSR_100FULL | BMSR_100HALF, ++ .lpa = LPA_100FULL | LPA_100HALF, ++ }, ++ [SWMII_SPEED_1000] = { ++ .bmcr = BMCR_FULLDPLX | BMCR_SPEED1000, ++ .bmsr = BMSR_ESTATEN, ++ .lpagb = LPA_1000FULL | LPA_1000HALF, ++ }, ++}; ++ ++static const struct swmii_regs duplex[] = { ++ [SWMII_DUPLEX_HALF] = { ++ .bmcr = ~BMCR_FULLDPLX, ++ .bmsr = BMSR_ESTATEN | BMSR_100HALF, ++ .lpa = LPA_10HALF | LPA_100HALF, ++ .lpagb = LPA_1000HALF, ++ }, ++ [SWMII_DUPLEX_FULL] = { ++ .bmcr = ~0, ++ .bmsr = BMSR_ESTATEN | BMSR_100FULL, ++ .lpa = LPA_10FULL | LPA_100FULL, ++ .lpagb = LPA_1000FULL, ++ }, ++}; ++ ++static int swphy_decode_speed(int speed) ++{ ++ switch (speed) { ++ case 1000: ++ return SWMII_SPEED_1000; ++ case 100: ++ return SWMII_SPEED_100; ++ case 10: ++ return SWMII_SPEED_10; ++ default: ++ return -EINVAL; ++ } ++} ++ + /** + * swphy_update_regs - update MII register array with fixed phy state + * @regs: array of 32 registers to update +@@ -30,81 +96,28 @@ + */ + int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state) + { ++ int speed_index, duplex_index; + u16 bmsr = BMSR_ANEGCAPABLE; + u16 bmcr = 0; + u16 lpagb = 0; + u16 lpa = 0; + +- if (state->duplex) { +- switch (state->speed) { +- case 1000: +- bmsr |= BMSR_ESTATEN; +- break; +- case 100: +- bmsr |= BMSR_100FULL; +- break; +- case 10: +- bmsr |= BMSR_10FULL; +- break; +- default: +- break; +- } +- } else { +- switch (state->speed) { +- case 1000: +- bmsr |= BMSR_ESTATEN; +- break; +- case 100: +- bmsr |= BMSR_100HALF; +- break; +- case 10: +- bmsr |= BMSR_10HALF; +- break; +- default: +- break; +- } ++ speed_index = swphy_decode_speed(state->speed); ++ if (speed_index < 0) { ++ pr_warn("swphy: unknown speed\n"); ++ return -EINVAL; + } + ++ duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF; ++ ++ bmsr |= speed[speed_index].bmsr & duplex[duplex_index].bmsr; ++ + if (state->link) { + bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE; + +- if (state->duplex) { +- bmcr |= BMCR_FULLDPLX; +- +- switch (state->speed) { +- case 1000: +- bmcr |= BMCR_SPEED1000; +- lpagb |= LPA_1000FULL; +- break; +- case 100: +- bmcr |= BMCR_SPEED100; +- lpa |= LPA_100FULL; +- break; +- case 10: +- lpa |= LPA_10FULL; +- break; +- default: +- pr_warn("swphy: unknown speed\n"); +- return -EINVAL; +- } +- } else { +- switch (state->speed) { +- case 1000: +- bmcr |= BMCR_SPEED1000; +- lpagb |= LPA_1000HALF; +- break; +- case 100: +- bmcr |= BMCR_SPEED100; +- lpa |= LPA_100HALF; +- break; +- case 10: +- lpa |= LPA_10HALF; +- break; +- default: +- pr_warn("swphy: unknown speed\n"); +- return -EINVAL; +- } +- } ++ bmcr |= speed[speed_index].bmcr & duplex[duplex_index].bmcr; ++ lpa |= speed[speed_index].lpa & duplex[duplex_index].lpa; ++ lpagb |= speed[speed_index].lpagb & duplex[duplex_index].lpagb; + + if (state->pause) + lpa |= LPA_PAUSE_CAP; diff --git a/target/linux/mvebu/patches-4.4/122-phy-separate-swphy-state-validation-from-register-ge.patch b/target/linux/mvebu/patches-4.4/122-phy-separate-swphy-state-validation-from-register-ge.patch new file mode 100644 index 0000000000..4b332cc8b7 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/122-phy-separate-swphy-state-validation-from-register-ge.patch @@ -0,0 +1,138 @@ +From e07630ad84c7dc145863f079f108154fb7c975e7 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sun, 20 Sep 2015 11:12:15 +0100 +Subject: [PATCH 711/744] phy: separate swphy state validation from register + generation + +Separate out the generation of MII registers from the state validation. +This allows us to simplify the error handing in fixed_phy() by allowing +earlier error detection. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/fixed_phy.c | 15 +++++++-------- + drivers/net/phy/swphy.c | 33 ++++++++++++++++++++++++++------- + drivers/net/phy/swphy.h | 3 ++- + 3 files changed, 35 insertions(+), 16 deletions(-) + +--- a/drivers/net/phy/fixed_phy.c ++++ b/drivers/net/phy/fixed_phy.c +@@ -49,12 +49,12 @@ static struct fixed_mdio_bus platform_fm + .phys = LIST_HEAD_INIT(platform_fmb.phys), + }; + +-static int fixed_phy_update_regs(struct fixed_phy *fp) ++static void fixed_phy_update_regs(struct fixed_phy *fp) + { + if (gpio_is_valid(fp->link_gpio)) + fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio); + +- return swphy_update_regs(fp->regs, &fp->status); ++ swphy_update_regs(fp->regs, &fp->status); + } + + static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +@@ -161,6 +161,10 @@ int fixed_phy_add(unsigned int irq, int + struct fixed_mdio_bus *fmb = &platform_fmb; + struct fixed_phy *fp; + ++ ret = swphy_validate_state(status); ++ if (ret < 0) ++ return ret; ++ + fp = kzalloc(sizeof(*fp), GFP_KERNEL); + if (!fp) + return -ENOMEM; +@@ -180,17 +184,12 @@ int fixed_phy_add(unsigned int irq, int + goto err_regs; + } + +- ret = fixed_phy_update_regs(fp); +- if (ret) +- goto err_gpio; ++ fixed_phy_update_regs(fp); + + list_add_tail(&fp->node, &fmb->phys); + + return 0; + +-err_gpio: +- if (gpio_is_valid(fp->link_gpio)) +- gpio_free(fp->link_gpio); + err_regs: + kfree(fp); + return ret; +--- a/drivers/net/phy/swphy.c ++++ b/drivers/net/phy/swphy.c +@@ -87,6 +87,29 @@ static int swphy_decode_speed(int speed) + } + + /** ++ * swphy_validate_state - validate the software phy status ++ * @state: software phy status ++ * ++ * This checks that we can represent the state stored in @state can be ++ * represented in the emulated MII registers. Returns 0 if it can, ++ * otherwise returns -EINVAL. ++ */ ++int swphy_validate_state(const struct fixed_phy_status *state) ++{ ++ int err; ++ ++ if (state->link) { ++ err = swphy_decode_speed(state->speed); ++ if (err < 0) { ++ pr_warn("swphy: unknown speed\n"); ++ return -EINVAL; ++ } ++ } ++ return 0; ++} ++EXPORT_SYMBOL_GPL(swphy_validate_state); ++ ++/** + * swphy_update_regs - update MII register array with fixed phy state + * @regs: array of 32 registers to update + * @state: fixed phy status +@@ -94,7 +117,7 @@ static int swphy_decode_speed(int speed) + * Update the array of MII registers with the fixed phy link, speed, + * duplex and pause mode settings. + */ +-int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state) ++void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state) + { + int speed_index, duplex_index; + u16 bmsr = BMSR_ANEGCAPABLE; +@@ -103,10 +126,8 @@ int swphy_update_regs(u16 *regs, const s + u16 lpa = 0; + + speed_index = swphy_decode_speed(state->speed); +- if (speed_index < 0) { +- pr_warn("swphy: unknown speed\n"); +- return -EINVAL; +- } ++ if (WARN_ON(speed_index < 0)) ++ return; + + duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF; + +@@ -133,7 +154,5 @@ int swphy_update_regs(u16 *regs, const s + regs[MII_BMCR] = bmcr; + regs[MII_LPA] = lpa; + regs[MII_STAT1000] = lpagb; +- +- return 0; + } + EXPORT_SYMBOL_GPL(swphy_update_regs); +--- a/drivers/net/phy/swphy.h ++++ b/drivers/net/phy/swphy.h +@@ -3,6 +3,7 @@ + + struct fixed_phy_status; + +-int swphy_update_regs(u16 *regs, const struct fixed_phy_status *state); ++int swphy_validate_state(const struct fixed_phy_status *state); ++void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state); + + #endif diff --git a/target/linux/mvebu/patches-4.4/123-phy-generate-swphy-registers-on-the-fly.patch b/target/linux/mvebu/patches-4.4/123-phy-generate-swphy-registers-on-the-fly.patch new file mode 100644 index 0000000000..218b902e7d --- /dev/null +++ b/target/linux/mvebu/patches-4.4/123-phy-generate-swphy-registers-on-the-fly.patch @@ -0,0 +1,204 @@ +From e0f33a88243329da1aa5a90fe10ab25c9fb0a091 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sun, 20 Sep 2015 11:28:39 +0100 +Subject: [PATCH 712/744] phy: generate swphy registers on the fly + +Generate software phy registers as and when requested, rather than +duplicating the state in fixed_phy. This allows us to eliminate +the duplicate storage of of the same data, which is only different +in format. + +As fixed_phy_update_regs() no longer updates register state, rename +it to fixed_phy_update(). + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/fixed_phy.c | 31 +++++------------------------- + drivers/net/phy/swphy.c | 47 ++++++++++++++++++++++++++++++++------------- + drivers/net/phy/swphy.h | 2 +- + 3 files changed, 40 insertions(+), 40 deletions(-) + +--- a/drivers/net/phy/fixed_phy.c ++++ b/drivers/net/phy/fixed_phy.c +@@ -26,8 +26,6 @@ + + #include "swphy.h" + +-#define MII_REGS_NUM 29 +- + struct fixed_mdio_bus { + int irqs[PHY_MAX_ADDR]; + struct mii_bus *mii_bus; +@@ -36,7 +34,6 @@ struct fixed_mdio_bus { + + struct fixed_phy { + int addr; +- u16 regs[MII_REGS_NUM]; + struct phy_device *phydev; + struct fixed_phy_status status; + int (*link_update)(struct net_device *, struct fixed_phy_status *); +@@ -49,12 +46,10 @@ static struct fixed_mdio_bus platform_fm + .phys = LIST_HEAD_INIT(platform_fmb.phys), + }; + +-static void fixed_phy_update_regs(struct fixed_phy *fp) ++static void fixed_phy_update(struct fixed_phy *fp) + { + if (gpio_is_valid(fp->link_gpio)) + fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio); +- +- swphy_update_regs(fp->regs, &fp->status); + } + + static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +@@ -62,29 +57,15 @@ static int fixed_mdio_read(struct mii_bu + struct fixed_mdio_bus *fmb = bus->priv; + struct fixed_phy *fp; + +- if (reg_num >= MII_REGS_NUM) +- return -1; +- +- /* We do not support emulating Clause 45 over Clause 22 register reads +- * return an error instead of bogus data. +- */ +- switch (reg_num) { +- case MII_MMD_CTRL: +- case MII_MMD_DATA: +- return -1; +- default: +- break; +- } +- + list_for_each_entry(fp, &fmb->phys, node) { + if (fp->addr == phy_addr) { + /* Issue callback if user registered it. */ + if (fp->link_update) { + fp->link_update(fp->phydev->attached_dev, + &fp->status); +- fixed_phy_update_regs(fp); ++ fixed_phy_update(fp); + } +- return fp->regs[reg_num]; ++ return swphy_read_reg(reg_num, &fp->status); + } + } + +@@ -144,7 +125,7 @@ int fixed_phy_update_state(struct phy_de + _UPD(pause); + _UPD(asym_pause); + #undef _UPD +- fixed_phy_update_regs(fp); ++ fixed_phy_update(fp); + return 0; + } + } +@@ -169,8 +150,6 @@ int fixed_phy_add(unsigned int irq, int + if (!fp) + return -ENOMEM; + +- memset(fp->regs, 0xFF, sizeof(fp->regs[0]) * MII_REGS_NUM); +- + fmb->irqs[phy_addr] = irq; + + fp->addr = phy_addr; +@@ -184,7 +163,7 @@ int fixed_phy_add(unsigned int irq, int + goto err_regs; + } + +- fixed_phy_update_regs(fp); ++ fixed_phy_update(fp); + + list_add_tail(&fp->node, &fmb->phys); + +--- a/drivers/net/phy/swphy.c ++++ b/drivers/net/phy/swphy.c +@@ -20,6 +20,8 @@ + + #include "swphy.h" + ++#define MII_REGS_NUM 29 ++ + struct swmii_regs { + u16 bmcr; + u16 bmsr; +@@ -110,14 +112,13 @@ int swphy_validate_state(const struct fi + EXPORT_SYMBOL_GPL(swphy_validate_state); + + /** +- * swphy_update_regs - update MII register array with fixed phy state +- * @regs: array of 32 registers to update ++ * swphy_read_reg - return a MII register from the fixed phy state ++ * @reg: MII register + * @state: fixed phy status + * +- * Update the array of MII registers with the fixed phy link, speed, +- * duplex and pause mode settings. ++ * Return the MII @reg register generated from the fixed phy state @state. + */ +-void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state) ++int swphy_read_reg(int reg, const struct fixed_phy_status *state) + { + int speed_index, duplex_index; + u16 bmsr = BMSR_ANEGCAPABLE; +@@ -125,9 +126,12 @@ void swphy_update_regs(u16 *regs, const + u16 lpagb = 0; + u16 lpa = 0; + ++ if (reg > MII_REGS_NUM) ++ return -1; ++ + speed_index = swphy_decode_speed(state->speed); + if (WARN_ON(speed_index < 0)) +- return; ++ return 0; + + duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF; + +@@ -147,12 +151,29 @@ void swphy_update_regs(u16 *regs, const + lpa |= LPA_PAUSE_ASYM; + } + +- regs[MII_PHYSID1] = 0; +- regs[MII_PHYSID2] = 0; ++ switch (reg) { ++ case MII_BMCR: ++ return bmcr; ++ case MII_BMSR: ++ return bmsr; ++ case MII_PHYSID1: ++ case MII_PHYSID2: ++ return 0; ++ case MII_LPA: ++ return lpa; ++ case MII_STAT1000: ++ return lpagb; ++ ++ /* ++ * We do not support emulating Clause 45 over Clause 22 register ++ * reads. Return an error instead of bogus data. ++ */ ++ case MII_MMD_CTRL: ++ case MII_MMD_DATA: ++ return -1; + +- regs[MII_BMSR] = bmsr; +- regs[MII_BMCR] = bmcr; +- regs[MII_LPA] = lpa; +- regs[MII_STAT1000] = lpagb; ++ default: ++ return 0xffff; ++ } + } +-EXPORT_SYMBOL_GPL(swphy_update_regs); ++EXPORT_SYMBOL_GPL(swphy_read_reg); +--- a/drivers/net/phy/swphy.h ++++ b/drivers/net/phy/swphy.h +@@ -4,6 +4,6 @@ + struct fixed_phy_status; + + int swphy_validate_state(const struct fixed_phy_status *state); +-void swphy_update_regs(u16 *regs, const struct fixed_phy_status *state); ++int swphy_read_reg(int reg, const struct fixed_phy_status *state); + + #endif diff --git a/target/linux/mvebu/patches-4.4/124-phy-improve-safety-of-fixed-phy-MII-register-reading.patch b/target/linux/mvebu/patches-4.4/124-phy-improve-safety-of-fixed-phy-MII-register-reading.patch new file mode 100644 index 0000000000..5167b07101 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/124-phy-improve-safety-of-fixed-phy-MII-register-reading.patch @@ -0,0 +1,92 @@ +From c36739c3cfd277a4cc9820a29dd0f4b7fbac795b Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sun, 20 Sep 2015 18:31:36 +0100 +Subject: [PATCH 713/744] phy: improve safety of fixed-phy MII register reading + +There is no prevention of a concurrent call to both fixed_mdio_read() +and fixed_phy_update_state(), which can result in the state being +modified while it's being inspected. Fix this by using a seqcount +to detect modifications, and memcpy()ing the state. + +We remain slightly naughty here, calling link_update() and updating +the link status within the read-side loop - which would need rework +of the design to change. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/fixed_phy.c | 28 +++++++++++++++++++++------- + 1 file changed, 21 insertions(+), 7 deletions(-) + +--- a/drivers/net/phy/fixed_phy.c ++++ b/drivers/net/phy/fixed_phy.c +@@ -23,6 +23,7 @@ + #include <linux/slab.h> + #include <linux/of.h> + #include <linux/gpio.h> ++#include <linux/seqlock.h> + + #include "swphy.h" + +@@ -35,6 +36,7 @@ struct fixed_mdio_bus { + struct fixed_phy { + int addr; + struct phy_device *phydev; ++ seqcount_t seqcount; + struct fixed_phy_status status; + int (*link_update)(struct net_device *, struct fixed_phy_status *); + struct list_head node; +@@ -59,13 +61,21 @@ static int fixed_mdio_read(struct mii_bu + + list_for_each_entry(fp, &fmb->phys, node) { + if (fp->addr == phy_addr) { +- /* Issue callback if user registered it. */ +- if (fp->link_update) { +- fp->link_update(fp->phydev->attached_dev, +- &fp->status); +- fixed_phy_update(fp); +- } +- return swphy_read_reg(reg_num, &fp->status); ++ struct fixed_phy_status state; ++ int s; ++ ++ do { ++ s = read_seqcount_begin(&fp->seqcount); ++ /* Issue callback if user registered it. */ ++ if (fp->link_update) { ++ fp->link_update(fp->phydev->attached_dev, ++ &fp->status); ++ fixed_phy_update(fp); ++ } ++ state = fp->status; ++ } while (read_seqcount_retry(&fp->seqcount, s)); ++ ++ return swphy_read_reg(reg_num, &state); + } + } + +@@ -117,6 +127,7 @@ int fixed_phy_update_state(struct phy_de + + list_for_each_entry(fp, &fmb->phys, node) { + if (fp->addr == phydev->addr) { ++ write_seqcount_begin(&fp->seqcount); + #define _UPD(x) if (changed->x) \ + fp->status.x = status->x + _UPD(link); +@@ -126,6 +137,7 @@ int fixed_phy_update_state(struct phy_de + _UPD(asym_pause); + #undef _UPD + fixed_phy_update(fp); ++ write_seqcount_end(&fp->seqcount); + return 0; + } + } +@@ -150,6 +162,8 @@ int fixed_phy_add(unsigned int irq, int + if (!fp) + return -ENOMEM; + ++ seqcount_init(&fp->seqcount); ++ + fmb->irqs[phy_addr] = irq; + + fp->addr = phy_addr; diff --git a/target/linux/mvebu/patches-4.4/125-phy-provide-a-hook-for-link-up-link-down-events.patch b/target/linux/mvebu/patches-4.4/125-phy-provide-a-hook-for-link-up-link-down-events.patch new file mode 100644 index 0000000000..b00968961f --- /dev/null +++ b/target/linux/mvebu/patches-4.4/125-phy-provide-a-hook-for-link-up-link-down-events.patch @@ -0,0 +1,183 @@ +From d8b4e728f598d3c8a9b219d4679d5de350caa082 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Fri, 18 Sep 2015 14:42:16 +0100 +Subject: [PATCH 714/744] phy: provide a hook for link up/link down events + +Sometimes, we need to do additional work between the PHY coming up and +marking the carrier present - for example, we may need to wait for the +PHY to MAC link to finish negotiation. This changes phylib to provide +a notification function pointer which avoids the built-in +netif_carrier_on() and netif_carrier_off() functions. + +Standard ->adjust_link functionality is provided by hooking a helper +into the new ->phy_link_change method. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/phy.c | 42 ++++++++++++++++++++++-------------------- + drivers/net/phy/phy_device.c | 14 ++++++++++++++ + include/linux/phy.h | 1 + + 3 files changed, 37 insertions(+), 20 deletions(-) + +--- a/drivers/net/phy/phy.c ++++ b/drivers/net/phy/phy.c +@@ -849,6 +849,16 @@ void phy_start(struct phy_device *phydev + } + EXPORT_SYMBOL(phy_start); + ++static void phy_link_up(struct phy_device *phydev) ++{ ++ phydev->phy_link_change(phydev, true, true); ++} ++ ++static void phy_link_down(struct phy_device *phydev, bool do_carrier) ++{ ++ phydev->phy_link_change(phydev, false, do_carrier); ++} ++ + /** + * phy_state_machine - Handle the state machine + * @work: work_struct that describes the work to be done +@@ -890,8 +900,7 @@ void phy_state_machine(struct work_struc + /* If the link is down, give up on negotiation for now */ + if (!phydev->link) { + phydev->state = PHY_NOLINK; +- netif_carrier_off(phydev->attached_dev); +- phydev->adjust_link(phydev->attached_dev); ++ phy_link_down(phydev, true); + break; + } + +@@ -903,9 +912,7 @@ void phy_state_machine(struct work_struc + /* If AN is done, we're running */ + if (err > 0) { + phydev->state = PHY_RUNNING; +- netif_carrier_on(phydev->attached_dev); +- phydev->adjust_link(phydev->attached_dev); +- ++ phy_link_up(phydev); + } else if (0 == phydev->link_timeout--) + needs_aneg = true; + break; +@@ -930,8 +937,7 @@ void phy_state_machine(struct work_struc + } + } + phydev->state = PHY_RUNNING; +- netif_carrier_on(phydev->attached_dev); +- phydev->adjust_link(phydev->attached_dev); ++ phy_link_up(phydev); + } + break; + case PHY_FORCING: +@@ -941,13 +947,12 @@ void phy_state_machine(struct work_struc + + if (phydev->link) { + phydev->state = PHY_RUNNING; +- netif_carrier_on(phydev->attached_dev); ++ phy_link_up(phydev); + } else { + if (0 == phydev->link_timeout--) + needs_aneg = true; ++ phy_link_down(phydev, false); + } +- +- phydev->adjust_link(phydev->attached_dev); + break; + case PHY_RUNNING: + /* Only register a CHANGE if we are polling or ignoring +@@ -970,14 +975,12 @@ void phy_state_machine(struct work_struc + + if (phydev->link) { + phydev->state = PHY_RUNNING; +- netif_carrier_on(phydev->attached_dev); ++ phy_link_up(phydev); + } else { + phydev->state = PHY_NOLINK; +- netif_carrier_off(phydev->attached_dev); ++ phy_link_down(phydev, true); + } + +- phydev->adjust_link(phydev->attached_dev); +- + if (phy_interrupt_is_valid(phydev)) + err = phy_config_interrupt(phydev, + PHY_INTERRUPT_ENABLED); +@@ -985,8 +988,7 @@ void phy_state_machine(struct work_struc + case PHY_HALTED: + if (phydev->link) { + phydev->link = 0; +- netif_carrier_off(phydev->attached_dev); +- phydev->adjust_link(phydev->attached_dev); ++ phy_link_down(phydev, true); + do_suspend = true; + } + break; +@@ -1006,11 +1008,11 @@ void phy_state_machine(struct work_struc + + if (phydev->link) { + phydev->state = PHY_RUNNING; +- netif_carrier_on(phydev->attached_dev); ++ phy_link_up(phydev); + } else { + phydev->state = PHY_NOLINK; ++ phy_link_down(phydev, false); + } +- phydev->adjust_link(phydev->attached_dev); + } else { + phydev->state = PHY_AN; + phydev->link_timeout = PHY_AN_TIMEOUT; +@@ -1022,11 +1024,11 @@ void phy_state_machine(struct work_struc + + if (phydev->link) { + phydev->state = PHY_RUNNING; +- netif_carrier_on(phydev->attached_dev); ++ phy_link_up(phydev); + } else { + phydev->state = PHY_NOLINK; ++ phy_link_down(phydev, false); + } +- phydev->adjust_link(phydev->attached_dev); + } + break; + } +--- a/drivers/net/phy/phy_device.c ++++ b/drivers/net/phy/phy_device.c +@@ -441,6 +441,19 @@ struct phy_device *phy_find_first(struct + } + EXPORT_SYMBOL(phy_find_first); + ++static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier) ++{ ++ struct net_device *netdev = phydev->attached_dev; ++ ++ if (do_carrier) { ++ if (up) ++ netif_carrier_on(netdev); ++ else ++ netif_carrier_off(netdev); ++ } ++ phydev->adjust_link(netdev); ++} ++ + /** + * phy_prepare_link - prepares the PHY layer to monitor link status + * @phydev: target phy_device struct +@@ -659,6 +672,7 @@ int phy_attach_direct(struct net_device + goto error; + } + ++ phydev->phy_link_change = phy_link_change; + phydev->attached_dev = dev; + dev->phydev = phydev; + +--- a/include/linux/phy.h ++++ b/include/linux/phy.h +@@ -433,6 +433,7 @@ struct phy_device { + + u8 mdix; + ++ void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier); + void (*adjust_link)(struct net_device *dev); + }; + #define to_phy_device(d) container_of(d, struct phy_device, dev) diff --git a/target/linux/mvebu/patches-4.4/126-phy-marvell-88E1512-add-flow-control-support.patch b/target/linux/mvebu/patches-4.4/126-phy-marvell-88E1512-add-flow-control-support.patch new file mode 100644 index 0000000000..e731c4ddf5 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/126-phy-marvell-88E1512-add-flow-control-support.patch @@ -0,0 +1,26 @@ +From 5eed0bf3bc3e69b20a13d8ffcdf97cb720391637 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 00:34:08 +0100 +Subject: [PATCH 735/744] phy: marvell: 88E1512: add flow control support + +The Marvell PHYs support pause frame advertisments, so we should not be +masking their support off. Add the necessary flag to the Marvell PHY +to allow any MAC level pause frame support to be advertised. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/marvell.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/net/phy/marvell.c ++++ b/drivers/net/phy/marvell.c +@@ -1142,7 +1142,7 @@ static struct phy_driver marvell_drivers + .phy_id = MARVELL_PHY_ID_88E1510, + .phy_id_mask = MARVELL_PHY_ID_MASK, + .name = "Marvell 88E1510", +- .features = PHY_GBIT_FEATURES, ++ .features = PHY_GBIT_FEATURES | SUPPORTED_Pause, + .flags = PHY_HAS_INTERRUPT, + .config_aneg = &m88e1510_config_aneg, + .read_status = &marvell_read_status, diff --git a/target/linux/mvebu/patches-4.4/127-phy-export-phy_start_machine-for-phylink.patch b/target/linux/mvebu/patches-4.4/127-phy-export-phy_start_machine-for-phylink.patch new file mode 100644 index 0000000000..eb73933f3d --- /dev/null +++ b/target/linux/mvebu/patches-4.4/127-phy-export-phy_start_machine-for-phylink.patch @@ -0,0 +1,25 @@ +From f2a9687b39cda3fb67ecd5eaa88e3545e78c982c Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Fri, 16 Oct 2015 12:18:41 +0100 +Subject: [PATCH 715/744] phy: export phy_start_machine() for phylink + +phylink will need phy_start_machine exported, so lets export it as a +GPL symbol. Documentation/networking/phy.txt indicates that this +should be a PHY API function. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/phy.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/drivers/net/phy/phy.c ++++ b/drivers/net/phy/phy.c +@@ -568,6 +568,7 @@ void phy_start_machine(struct phy_device + { + queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ); + } ++EXPORT_SYMBOL_GPL(phy_start_machine); + + /** + * phy_stop_machine - stop the PHY state machine tracking diff --git a/target/linux/mvebu/patches-4.4/128-phy-export-phy_speed_to_str-for-phylink.patch b/target/linux/mvebu/patches-4.4/128-phy-export-phy_speed_to_str-for-phylink.patch new file mode 100644 index 0000000000..c43607029c --- /dev/null +++ b/target/linux/mvebu/patches-4.4/128-phy-export-phy_speed_to_str-for-phylink.patch @@ -0,0 +1,44 @@ +From 5c77cc2ffd5deb4762d9551409472f2441297fe7 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sun, 18 Oct 2015 19:51:10 +0100 +Subject: [PATCH 716/744] phy: export phy_speed_to_str() for phylink + +phylink would like to reuse phy_speed_to_str() to convert the speed +to a string. Add a prototype and export this helper function. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/phy.c | 3 ++- + include/linux/phy.h | 1 + + 2 files changed, 3 insertions(+), 1 deletion(-) + +--- a/drivers/net/phy/phy.c ++++ b/drivers/net/phy/phy.c +@@ -38,7 +38,7 @@ + + #include <asm/irq.h> + +-static const char *phy_speed_to_str(int speed) ++const char *phy_speed_to_str(int speed) + { + switch (speed) { + case SPEED_10: +@@ -57,6 +57,7 @@ static const char *phy_speed_to_str(int + return "Unsupported (update phy.c)"; + } + } ++EXPORT_SYMBOL_GPL(phy_speed_to_str); + + #define PHY_STATE_STR(_state) \ + case PHY_##_state: \ +--- a/include/linux/phy.h ++++ b/include/linux/phy.h +@@ -812,6 +812,7 @@ int phy_ethtool_gset(struct phy_device * + int phy_ethtool_ioctl(struct phy_device *phydev, void *useraddr); + int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd); + int phy_start_interrupts(struct phy_device *phydev); ++const char *phy_speed_to_str(int speed); + void phy_print_status(struct phy_device *phydev); + void phy_device_free(struct phy_device *phydev); + int phy_set_max_speed(struct phy_device *phydev, u32 max_speed); diff --git a/target/linux/mvebu/patches-4.4/129-phy-add-I2C-mdio-bus.patch b/target/linux/mvebu/patches-4.4/129-phy-add-I2C-mdio-bus.patch new file mode 100644 index 0000000000..13f8ecfcd4 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/129-phy-add-I2C-mdio-bus.patch @@ -0,0 +1,163 @@ +From 7f36ac946bfbd4090b8b94be3661b41ac73e21f4 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Fri, 25 Sep 2015 17:43:52 +0100 +Subject: [PATCH 717/744] phy: add I2C mdio bus + +Add an I2C MDIO bus bridge library, to allow phylib to access PHYs which +are connected to an I2C bus instead of the more conventional MDIO bus. +Such PHYs can be found in SFP adapters and SFF modules. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/Kconfig | 10 ++++++ + drivers/net/phy/Makefile | 1 + + drivers/net/phy/mdio-i2c.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++ + drivers/net/phy/mdio-i2c.h | 19 ++++++++++ + 4 files changed, 120 insertions(+) + create mode 100644 drivers/net/phy/mdio-i2c.c + create mode 100644 drivers/net/phy/mdio-i2c.h + +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -233,6 +233,16 @@ config MDIO_GPIO + To compile this driver as a module, choose M here: the module + will be called mdio-gpio. + ++config MDIO_I2C ++ tristate ++ depends on I2C ++ help ++ Support I2C based PHYs. This provides a MDIO bus bridged ++ to I2C to allow PHYs connected in I2C mode to be accessed ++ using the existing infrastructure. ++ ++ This is library mode. ++ + config MDIO_OCTEON + tristate "Support for MDIO buses on Octeon and ThunderX SOCs" + depends on 64BIT +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -41,6 +41,7 @@ obj-$(CONFIG_SWCONFIG_B53) += b53/ + obj-$(CONFIG_FIXED_PHY) += fixed_phy.o + obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o + obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o ++obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o + obj-$(CONFIG_NATIONAL_PHY) += national.o + obj-$(CONFIG_DP83640_PHY) += dp83640.o + obj-$(CONFIG_DP83848_PHY) += dp83848.o +--- /dev/null ++++ b/drivers/net/phy/mdio-i2c.c +@@ -0,0 +1,90 @@ ++/* ++ * MDIO I2C bridge ++ * ++ * Copyright (C) 2015 Russell King ++ * ++ * 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. ++ */ ++#include <linux/i2c.h> ++#include <linux/phy.h> ++ ++#include "mdio-i2c.h" ++ ++static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg) ++{ ++ struct i2c_adapter *i2c = bus->priv; ++ struct i2c_msg msgs[2]; ++ u8 data[2], dev_addr = reg; ++ int bus_addr, ret; ++ ++ bus_addr = 0x40 + phy_id; ++ if (bus_addr == 0x50 || bus_addr == 0x51) ++ return 0xffff; ++ ++ msgs[0].addr = bus_addr; ++ msgs[0].flags = 0; ++ msgs[0].len = 1; ++ msgs[0].buf = &dev_addr; ++ msgs[1].addr = bus_addr; ++ msgs[1].flags = I2C_M_RD; ++ msgs[1].len = sizeof(data); ++ msgs[1].buf = data; ++ ++ ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); ++ if (ret != ARRAY_SIZE(msgs)) ++ return 0xffff; ++ ++ return data[0] << 8 | data[1]; ++} ++ ++static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val) ++{ ++ struct i2c_adapter *i2c = bus->priv; ++ struct i2c_msg msg; ++ int bus_addr, ret; ++ u8 data[3]; ++ ++ bus_addr = 0x40 + phy_id; ++ if (bus_addr == 0x50 || bus_addr == 0x51) ++ return 0; ++ ++ data[0] = reg; ++ data[1] = val >> 8; ++ data[2] = val; ++ ++ msg.addr = bus_addr; ++ msg.flags = 0; ++ msg.len = 3; ++ msg.buf = data; ++ ++ ret = i2c_transfer(i2c, &msg, 1); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c) ++{ ++ struct mii_bus *mii; ++ ++ if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) ++ return ERR_PTR(-EINVAL); ++ ++ mii = mdiobus_alloc(); ++ if (!mii) ++ return ERR_PTR(-ENOMEM); ++ ++ snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent)); ++ mii->parent = parent; ++ mii->read = i2c_mii_read; ++ mii->write = i2c_mii_write; ++ mii->priv = i2c; ++ ++ return mii; ++} ++EXPORT_SYMBOL_GPL(mdio_i2c_alloc); ++ ++MODULE_AUTHOR("Russell King"); ++MODULE_DESCRIPTION("MDIO I2C bridge library"); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/drivers/net/phy/mdio-i2c.h +@@ -0,0 +1,19 @@ ++/* ++ * MDIO I2C bridge ++ * ++ * Copyright (C) 2015 Russell King ++ * ++ * 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. ++ */ ++#ifndef MDIO_I2C_H ++#define MDIO_I2C_H ++ ++struct device; ++struct i2c_adapter; ++struct mii_bus; ++ ++struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c); ++ ++#endif diff --git a/target/linux/mvebu/patches-4.4/130-phylink-add-phylink-infrastructure.patch b/target/linux/mvebu/patches-4.4/130-phylink-add-phylink-infrastructure.patch new file mode 100644 index 0000000000..0d340c8278 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/130-phylink-add-phylink-infrastructure.patch @@ -0,0 +1,1005 @@ +From c6de6de7d3df13822872ac756eebe868d236297a Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Tue, 22 Sep 2015 20:52:18 +0100 +Subject: [PATCH 718/744] phylink: add phylink infrastructure + +The link between the ethernet MAC and its PHY has become more complex +as the interface evolves. This is especially true with serdes links, +where the part of the PHY is effectively integrated into the MAC. + +Serdes links can be connected to a variety of devices, including SFF +modules soldered down onto the board with the MAC, a SFP cage with +a hotpluggable SFP module which may contain a PHY or directly modulate +the serdes signals onto optical media with or without a PHY, or even +a classical PHY connection. + +Moreover, the negotiation information on serdes links comes in two +varieties - SGMII mode, where the PHY provides its speed/duplex/flow +control information to the MAC, and 1000base-X mode where both ends +exchange their abilities and each resolve the link capabilities. + +This means we need a more flexible means to support these arrangements, +particularly with the hotpluggable nature of SFP, where the PHY can +be attached or detached after the network device has been brought up. + +Ethtool information can come from multiple sources: +- we may have a PHY operating in either SGMII or 1000base-X mode, in + which case we take ethtool/mii data directly from the PHY. +- we may have a optical SFP module without a PHY, with the MAC + operating in 1000base-X mode - the ethtool/mii data needs to come + from the MAC. +- we may have a copper SFP module with a PHY whic can't be accessed, + which means we need to take ethtool/mii data from the MAC. + +Phylink aims to solve this by providing an intermediary between the +MAC and PHY, providing a safe way for PHYs to be hotplugged, and +allowing a SFP driver to reconfigure the serdes connection. + +Phylink also takes over support of fixed link connections, where +the speed/duplex/flow control are fixed, but link status may be +controlled by a GPIO signal. By avoiding the fixed-phy implementation, +phylink can provide a faster response to link events: fixed-phy has +to wait for phylib to operate its state machine, which can take +several seconds. In comparison, phylink takes milliseconds. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/Kconfig | 10 + + drivers/net/phy/Makefile | 1 + + drivers/net/phy/phy_device.c | 1 + + drivers/net/phy/phylink.c | 816 +++++++++++++++++++++++++++++++++++++++++++ + include/linux/phy.h | 2 + + include/linux/phylink.h | 70 ++++ + 6 files changed, 900 insertions(+) + create mode 100644 drivers/net/phy/phylink.c + create mode 100644 include/linux/phylink.h + +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -10,6 +10,16 @@ menuconfig PHYLIB + devices. This option provides infrastructure for + managing PHY devices. + ++config PHYLINK ++ tristate ++ depends on NETDEVICES ++ select PHYLIB ++ select SWPHY ++ help ++ PHYlink models the link between the PHY and MAC, allowing fixed ++ configuration links, PHYs, and Serdes links with MAC level ++ autonegotiation modes. ++ + if PHYLIB + + config MDIO_BOARDINFO +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -5,6 +5,7 @@ libphy-$(CONFIG_SWPHY) += swphy.o + + obj-$(CONFIG_MDIO_BOARDINFO) += mdio-boardinfo.o + ++obj-$(CONFIG_PHYLINK) += phylink.o + obj-$(CONFIG_PHYLIB) += libphy.o + obj-$(CONFIG_SWCONFIG) += swconfig.o + obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o +--- a/drivers/net/phy/phy_device.c ++++ b/drivers/net/phy/phy_device.c +@@ -754,6 +754,7 @@ void phy_detach(struct phy_device *phyde + phydev->attached_dev->phydev = NULL; + phydev->attached_dev = NULL; + phy_suspend(phydev); ++ phydev->phylink = NULL; + + /* If the device had no specific driver before (i.e. - it + * was using the generic driver), we unbind the device +--- /dev/null ++++ b/drivers/net/phy/phylink.c +@@ -0,0 +1,816 @@ ++/* ++ * phylink models the MAC to optional PHY connection, supporting ++ * technologies such as SFP cages where the PHY is hot-pluggable. ++ * ++ * Copyright (C) 2015 Russell King ++ * ++ * 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. ++ */ ++#include <linux/ethtool.h> ++#include <linux/export.h> ++#include <linux/gpio/consumer.h> ++#include <linux/netdevice.h> ++#include <linux/of.h> ++#include <linux/of_mdio.h> ++#include <linux/phy.h> ++#include <linux/phy_fixed.h> ++#include <linux/phylink.h> ++#include <linux/spinlock.h> ++#include <linux/workqueue.h> ++ ++#include "swphy.h" ++ ++#define SUPPORTED_INTERFACES \ ++ (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \ ++ SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane) ++#define ADVERTISED_INTERFACES \ ++ (ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \ ++ ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane) ++ ++enum { ++ PHYLINK_DISABLE_STOPPED, ++}; ++ ++struct phylink { ++ struct net_device *netdev; ++ const struct phylink_mac_ops *ops; ++ struct mutex config_mutex; ++ ++ unsigned long phylink_disable_state; /* bitmask of disables */ ++ struct phy_device *phydev; ++ phy_interface_t link_interface; /* PHY_INTERFACE_xxx */ ++ u8 link_an_mode; /* MLO_AN_xxx */ ++ u8 link_port; /* The current non-phy ethtool port */ ++ u32 link_port_support; /* SUPPORTED_xxx ethtool for ports */ ++ ++ /* The link configuration settings */ ++ struct phylink_link_state link_config; ++ struct gpio_desc *link_gpio; ++ ++ struct mutex state_mutex; /* may be taken within config_mutex */ ++ struct phylink_link_state phy_state; ++ struct work_struct resolve; ++ ++ bool mac_link_up; ++}; ++ ++static const char *phylink_an_mode_str(unsigned int mode) ++{ ++ static const char *modestr[] = { ++ [MLO_AN_PHY] = "phy", ++ [MLO_AN_FIXED] = "fixed", ++ [MLO_AN_SGMII] = "SGMII", ++ [MLO_AN_8023Z] = "802.3z", ++ }; ++ ++ return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown"; ++} ++ ++static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np) ++{ ++ struct device_node *fixed_node; ++ int ret, len; ++ ++ fixed_node = of_get_child_by_name(np, "fixed-link"); ++ if (fixed_node) { ++ struct gpio_desc *desc; ++ u32 speed; ++ ++ ret = of_property_read_u32(fixed_node, "speed", &speed); ++ ++ pl->link_an_mode = MLO_AN_FIXED; ++ pl->link_config.link = 1; ++ pl->link_config.an_complete = 1; ++ pl->link_config.speed = speed; ++ pl->link_config.duplex = DUPLEX_HALF; ++ pl->link_config.pause = MLO_PAUSE_NONE; ++ ++ if (of_property_read_bool(fixed_node, "full-duplex")) ++ pl->link_config.duplex = DUPLEX_FULL; ++ if (of_property_read_bool(fixed_node, "pause")) ++ pl->link_config.pause |= MLO_PAUSE_SYM; ++ if (of_property_read_bool(fixed_node, "asym-pause")) ++ pl->link_config.pause |= MLO_PAUSE_ASYM; ++ ++ if (ret == 0) { ++ desc = fwnode_get_named_gpiod(&fixed_node->fwnode, ++ "link-gpios"); ++ ++ if (!IS_ERR(desc)) ++ pl->link_gpio = desc; ++ else if (desc == ERR_PTR(-EPROBE_DEFER)) ++ ret = -EPROBE_DEFER; ++ } ++ of_node_put(fixed_node); ++ } else { ++ const __be32 *fixed_prop; ++ ++ fixed_prop = of_get_property(np, "fixed-link", &len); ++ if (fixed_prop && len == 5 * sizeof(*fixed_prop)) { ++ pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ? ++ DUPLEX_FULL : DUPLEX_HALF; ++ pl->link_config.speed = be32_to_cpu(fixed_prop[2]); ++ pl->link_config.pause = MLO_PAUSE_NONE; ++ if (be32_to_cpu(fixed_prop[3])) ++ pl->link_config.pause |= MLO_PAUSE_SYM; ++ if (be32_to_cpu(fixed_prop[4])) ++ pl->link_config.pause |= MLO_PAUSE_ASYM; ++ ++ pl->link_an_mode = MLO_AN_FIXED; ++ } ++ ret = 0; ++ } ++ ++ if (pl->link_an_mode == MLO_AN_FIXED) { ++ /* Generate the supported/advertising masks */ ++ if (pl->link_config.pause & MLO_PAUSE_SYM) { ++ pl->link_config.supported |= SUPPORTED_Pause; ++ pl->link_config.advertising |= ADVERTISED_Pause; ++ } ++ if (pl->link_config.pause & MLO_PAUSE_ASYM) { ++ pl->link_config.supported |= SUPPORTED_Asym_Pause; ++ pl->link_config.advertising |= ADVERTISED_Asym_Pause; ++ } ++ ++ if (pl->link_config.speed > SPEED_1000 && ++ pl->link_config.duplex != DUPLEX_FULL) ++ netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n", ++ pl->link_config.speed); ++ ++#define S(spd) \ ++ pl->link_config.supported |= pl->link_config.duplex ? \ ++ SUPPORTED_##spd##_Full : SUPPORTED_##spd##_Half ++#define A(spd) \ ++ pl->link_config.advertising |= pl->link_config.duplex ? \ ++ ADVERTISED_##spd##_Full : ADVERTISED_##spd##_Half ++#define C(spd, tech) \ ++ case spd: \ ++ S(spd##tech); \ ++ A(spd##tech); \ ++ break ++ switch (pl->link_config.speed) { ++ C(10, baseT); ++ C(100, baseT); ++ C(1000, baseT); ++#undef S ++#undef A ++#define S(spd) pl->link_config.supported |= SUPPORTED_##spd##_Full ++#define A(spd) pl->link_config.advertising |= ADVERTISED_##spd##_Full ++ C(2500, baseX); ++ C(10000, baseT); ++ } ++#undef S ++#undef A ++#undef C ++ } ++ return ret; ++} ++ ++static int phylink_parse_managed(struct phylink *pl, struct device_node *np) ++{ ++ const char *managed; ++ ++ if (of_property_read_string(np, "managed", &managed) == 0 && ++ strcmp(managed, "in-band-status") == 0) { ++ if (pl->link_an_mode == MLO_AN_FIXED) { ++ netdev_err(pl->netdev, ++ "can't use both fixed-link and in-band-status\n"); ++ return -EINVAL; ++ } ++ pl->link_an_mode = MLO_AN_SGMII; ++ pl->link_config.an_enabled = true; ++ } ++ ++ return 0; ++} ++ ++ ++static int phylink_get_support(struct phylink *pl, unsigned int mode) ++{ ++ struct phylink_link_state state = pl->link_config; ++ int ret; ++ ++ ret = pl->ops->mac_get_support(pl->netdev, mode, &state); ++ if (ret == 0) { ++ pl->link_an_mode = mode; ++ pl->link_config = state; ++ } ++ ++ return ret; ++} ++ ++static void phylink_mac_config(struct phylink *pl, ++ const struct phylink_link_state *state) ++{ ++ pl->ops->mac_config(pl->netdev, pl->link_an_mode, state); ++} ++ ++static void phylink_mac_an_restart(struct phylink *pl) ++{ ++ if (pl->link_config.an_enabled) ++ pl->ops->mac_an_restart(pl->netdev, pl->link_an_mode); ++} ++ ++static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state) ++{ ++ struct net_device *ndev = pl->netdev; ++ ++ state->supported = pl->link_config.supported; ++ state->advertising = pl->link_config.advertising; ++ state->an_enabled = pl->link_config.an_enabled; ++ state->link = 1; ++ state->sync = 1; ++ ++ return pl->ops->mac_link_state(ndev, state); ++} ++ ++/* The fixed state is... fixed except for the link state, ++ * which may be determined by a GPIO. ++ */ ++static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state) ++{ ++ *state = pl->link_config; ++ if (pl->link_gpio) ++ state->link = !!gpiod_get_value(pl->link_gpio); ++} ++ ++extern const char *phy_speed_to_str(int speed); ++ ++static void phylink_resolve(struct work_struct *w) ++{ ++ struct phylink *pl = container_of(w, struct phylink, resolve); ++ struct phylink_link_state link_state; ++ struct net_device *ndev = pl->netdev; ++ ++ mutex_lock(&pl->state_mutex); ++ if (pl->phylink_disable_state) { ++ link_state.link = false; ++ } else { ++ switch (pl->link_an_mode) { ++ case MLO_AN_PHY: ++ link_state = pl->phy_state; ++ break; ++ ++ case MLO_AN_FIXED: ++ phylink_get_fixed_state(pl, &link_state); ++ break; ++ ++ case MLO_AN_SGMII: ++ phylink_get_mac_state(pl, &link_state); ++ if (pl->phydev) ++ link_state.link = link_state.link && ++ pl->phy_state.link; ++ break; ++ ++ case MLO_AN_8023Z: ++ phylink_get_mac_state(pl, &link_state); ++ break; ++ } ++ } ++ ++ if (link_state.link != netif_carrier_ok(ndev)) { ++ if (!link_state.link) { ++ netif_carrier_off(ndev); ++ pl->ops->mac_link_down(ndev, pl->link_an_mode); ++ netdev_info(ndev, "Link is Down\n"); ++ } else { ++ /* If we have a PHY, we need the MAC updated with ++ * the current link parameters (eg, in SGMII mode, ++ * with flow control status.) ++ */ ++ if (pl->phydev) ++ phylink_mac_config(pl, &link_state); ++ ++ pl->ops->mac_link_up(ndev, pl->link_an_mode); ++ ++ netif_carrier_on(ndev); ++ ++ netdev_info(ndev, ++ "Link is Up - %s/%s - flow control %s\n", ++ phy_speed_to_str(link_state.speed), ++ link_state.duplex ? "Full" : "Half", ++ link_state.pause ? "rx/tx" : "off"); ++ } ++ } ++ mutex_unlock(&pl->state_mutex); ++} ++ ++static void phylink_run_resolve(struct phylink *pl) ++{ ++ if (!pl->phylink_disable_state) ++ queue_work(system_power_efficient_wq, &pl->resolve); ++} ++ ++struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, ++ phy_interface_t iface, const struct phylink_mac_ops *ops) ++{ ++ struct phylink *pl; ++ int ret; ++ ++ pl = kzalloc(sizeof(*pl), GFP_KERNEL); ++ if (!pl) ++ return ERR_PTR(-ENOMEM); ++ ++ mutex_init(&pl->state_mutex); ++ mutex_init(&pl->config_mutex); ++ INIT_WORK(&pl->resolve, phylink_resolve); ++ pl->netdev = ndev; ++ pl->link_interface = iface; ++ pl->link_port_support = SUPPORTED_MII; ++ pl->link_port = PORT_MII; ++ pl->ops = ops; ++ __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); ++ ++ ret = phylink_parse_fixedlink(pl, np); ++ if (ret < 0) { ++ kfree(pl); ++ return ERR_PTR(ret); ++ } ++ ++ ret = phylink_parse_managed(pl, np); ++ if (ret < 0) { ++ kfree(pl); ++ return ERR_PTR(ret); ++ } ++ ++ ret = phylink_get_support(pl, pl->link_an_mode); ++ if (ret) { ++ kfree(pl); ++ return ERR_PTR(ret); ++ } ++ ++ return pl; ++} ++EXPORT_SYMBOL_GPL(phylink_create); ++ ++void phylink_destroy(struct phylink *pl) ++{ ++ cancel_work_sync(&pl->resolve); ++ kfree(pl); ++} ++EXPORT_SYMBOL_GPL(phylink_destroy); ++ ++void phylink_phy_change(struct phy_device *phy, bool up, bool do_carrier) ++{ ++ struct phylink *pl = phy->phylink; ++ ++ mutex_lock(&pl->state_mutex); ++ pl->phy_state.speed = phy->speed; ++ pl->phy_state.duplex = phy->duplex; ++ pl->phy_state.pause = MLO_PAUSE_NONE; ++ if (phy->pause) ++ pl->phy_state.pause |= MLO_PAUSE_SYM; ++ if (phy->asym_pause) ++ pl->phy_state.pause |= MLO_PAUSE_ASYM; ++ pl->phy_state.link = up; ++ mutex_unlock(&pl->state_mutex); ++ ++ phylink_run_resolve(pl); ++ ++ netdev_dbg(pl->netdev, "phy link %s\n", up ? "up" : "down"); ++} ++ ++static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) ++{ ++ mutex_lock(&pl->config_mutex); ++ phy->phylink = pl; ++ phy->phy_link_change = phylink_phy_change; ++ ++ netdev_info(pl->netdev, ++ "PHY [%s] driver [%s]\n", dev_name(&phy->dev), ++ phy->drv->name); ++ ++ mutex_lock(&pl->state_mutex); ++ pl->phydev = phy; ++ ++ /* Restrict the phy advertisment to the union of the PHY and ++ * MAC-level advert. ++ */ ++ phy->advertising &= ADVERTISED_INTERFACES | ++ pl->link_config.advertising; ++ mutex_unlock(&pl->state_mutex); ++ ++ phy_start_machine(phy); ++ if (phy->irq > 0) ++ phy_start_interrupts(phy); ++ ++ mutex_unlock(&pl->config_mutex); ++ ++ return 0; ++} ++ ++int phylink_connect_phy(struct phylink *pl, struct phy_device *phy) ++{ ++ int ret; ++ ++ ret = phy_attach_direct(pl->netdev, phy, 0, pl->link_interface); ++ if (ret) ++ return ret; ++ ++ ret = phylink_bringup_phy(pl, phy); ++ if (ret) ++ phy_detach(phy); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_connect_phy); ++ ++int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn) ++{ ++ struct device_node *phy_node; ++ struct phy_device *phy_dev; ++ int ret; ++ ++ /* Fixed links are handled without needing a PHY */ ++ if (pl->link_an_mode == MLO_AN_FIXED) ++ return 0; ++ ++ phy_node = of_parse_phandle(dn, "phy-handle", 0); ++ if (!phy_node) ++ phy_node = of_parse_phandle(dn, "phy", 0); ++ if (!phy_node) ++ phy_node = of_parse_phandle(dn, "phy-device", 0); ++ ++ if (!phy_node) { ++ if (pl->link_an_mode == MLO_AN_PHY) { ++ netdev_err(pl->netdev, "unable to find PHY node\n"); ++ return -ENODEV; ++ } ++ return 0; ++ } ++ ++ phy_dev = of_phy_attach(pl->netdev, phy_node, 0, pl->link_interface); ++ /* We're done with the phy_node handle */ ++ of_node_put(phy_node); ++ ++ if (!phy_dev) ++ return -ENODEV; ++ ++ ret = phylink_bringup_phy(pl, phy_dev); ++ if (ret) ++ phy_detach(phy_dev); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_of_phy_connect); ++ ++void phylink_disconnect_phy(struct phylink *pl) ++{ ++ struct phy_device *phy; ++ ++ mutex_lock(&pl->config_mutex); ++ phy = pl->phydev; ++ ++ mutex_lock(&pl->state_mutex); ++ pl->phydev = NULL; ++ mutex_unlock(&pl->state_mutex); ++ flush_work(&pl->resolve); ++ ++ if (phy) ++ phy_disconnect(phy); ++ ++ mutex_unlock(&pl->config_mutex); ++} ++EXPORT_SYMBOL_GPL(phylink_disconnect_phy); ++ ++void phylink_mac_change(struct phylink *pl, bool up) ++{ ++ phylink_run_resolve(pl); ++ netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down"); ++} ++EXPORT_SYMBOL_GPL(phylink_mac_change); ++ ++void phylink_start(struct phylink *pl) ++{ ++ mutex_lock(&pl->config_mutex); ++ ++ netdev_info(pl->netdev, "configuring for %s link mode\n", ++ phylink_an_mode_str(pl->link_an_mode)); ++ ++ /* Apply the link configuration to the MAC when starting. This allows ++ * a fixed-link to start with the correct parameters, and also ++ * ensures that we set the appropriate advertisment for Serdes links. ++ */ ++ phylink_mac_config(pl, &pl->link_config); ++ ++ clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); ++ phylink_run_resolve(pl); ++ ++ if (pl->phydev) ++ phy_start(pl->phydev); ++ ++ mutex_unlock(&pl->config_mutex); ++} ++EXPORT_SYMBOL_GPL(phylink_start); ++ ++void phylink_stop(struct phylink *pl) ++{ ++ mutex_lock(&pl->config_mutex); ++ ++ if (pl->phydev) ++ phy_stop(pl->phydev); ++ ++ set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); ++ flush_work(&pl->resolve); ++ ++ pl->mac_link_up = false; ++ ++ mutex_unlock(&pl->config_mutex); ++} ++EXPORT_SYMBOL_GPL(phylink_stop); ++ ++static void phylink_get_ethtool(const struct phylink_link_state *state, ++ struct ethtool_cmd *cmd) ++{ ++ cmd->supported &= SUPPORTED_INTERFACES; ++ cmd->supported |= state->supported; ++ cmd->advertising &= ADVERTISED_INTERFACES; ++ cmd->advertising |= state->advertising; ++ ethtool_cmd_speed_set(cmd, state->speed); ++ cmd->duplex = state->duplex; ++ ++ cmd->autoneg = state->an_enabled ? AUTONEG_ENABLE : AUTONEG_DISABLE; ++} ++ ++static int phylink_ethtool_gset(struct phylink *pl, struct ethtool_cmd *cmd) ++{ ++ struct phylink_link_state link_state; ++ int ret; ++ ++ if (pl->phydev) { ++ ret = phy_ethtool_gset(pl->phydev, cmd); ++ if (ret) ++ return ret; ++ ++ cmd->supported &= SUPPORTED_INTERFACES | ++ pl->link_config.supported; ++ } else { ++ cmd->supported = pl->link_port_support; ++ cmd->transceiver = XCVR_EXTERNAL; ++ cmd->port = pl->link_port; ++ } ++ ++ switch (pl->link_an_mode) { ++ case MLO_AN_FIXED: ++ /* We are using fixed settings. Report these as the ++ * current link settings - and note that these also ++ * represent the supported speeds/duplex/pause modes. ++ */ ++ phylink_get_fixed_state(pl, &link_state); ++ phylink_get_ethtool(&link_state, cmd); ++ break; ++ ++ case MLO_AN_SGMII: ++ /* If there is a phy attached, then use the reported ++ * settings from the phy with no modification. ++ */ ++ if (pl->phydev) ++ break; ++ ++ case MLO_AN_8023Z: ++ phylink_get_mac_state(pl, &link_state); ++ ++ /* The MAC is reporting the link results from its own PCS ++ * layer via in-band status. Report these as the current ++ * link settings. ++ */ ++ phylink_get_ethtool(&link_state, cmd); ++ break; ++ } ++ ++ return 0; ++} ++ ++int phylink_ethtool_get_settings(struct phylink *pl, struct ethtool_cmd *cmd) ++{ ++ int ret; ++ ++ mutex_lock(&pl->config_mutex); ++ ret = phylink_ethtool_gset(pl, cmd); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_get_settings); ++ ++static int phylink_ethtool_sset(struct phylink *pl, struct ethtool_cmd *cmd) ++{ ++ u32 supported; ++ int ret; ++ ++ /* Calculate the union of the MAC support and attached phy support */ ++ supported = pl->link_config.supported; ++ if (pl->phydev) ++ supported &= pl->phydev->supported; ++ ++ /* Mask out unsupported advertisments */ ++ cmd->advertising &= supported; ++ ++ /* FIXME: should we reject autoneg if phy/mac does not support it? */ ++ ++ if (cmd->autoneg == AUTONEG_DISABLE) { ++ /* Autonegotiation disabled, validate speed and duplex */ ++ if (cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL) ++ return -EINVAL; ++ ++ /* FIXME: validate speed/duplex against supported */ ++ ++ cmd->advertising &= ~ADVERTISED_Autoneg; ++ } else { ++ /* Autonegotiation enabled, validate advertisment */ ++ /* FIXME: shouldn't we ensure there's some duplex/speeds set */ ++ if (cmd->advertising == 0) ++ return -EINVAL; ++ ++ cmd->advertising |= ADVERTISED_Autoneg; ++ } ++ ++ /* If we have a fixed link (as specified by firmware), refuse ++ * to enable autonegotiation, or change link parameters. ++ */ ++ if (pl->link_an_mode == MLO_AN_FIXED) { ++ if (cmd->autoneg != AUTONEG_DISABLE || ++ ethtool_cmd_speed(cmd) != pl->link_config.speed || ++ cmd->duplex != pl->link_config.duplex) ++ return -EINVAL; ++ } ++ ++ /* If we have a PHY, configure the phy */ ++ if (pl->phydev) { ++ ret = phy_ethtool_sset(pl->phydev, cmd); ++ if (ret) ++ return ret; ++ } ++ ++ mutex_lock(&pl->state_mutex); ++ /* Configure the MAC to match the new settings */ ++ pl->link_config.advertising = cmd->advertising; ++ pl->link_config.speed = cmd->speed; ++ pl->link_config.duplex = cmd->duplex; ++ pl->link_config.an_enabled = cmd->autoneg != AUTONEG_DISABLE; ++ ++ phylink_mac_config(pl, &pl->link_config); ++ phylink_mac_an_restart(pl); ++ mutex_unlock(&pl->state_mutex); ++ ++ return ret; ++} ++ ++int phylink_ethtool_set_settings(struct phylink *pl, struct ethtool_cmd *cmd) ++{ ++ int ret; ++ ++ if (cmd->autoneg != AUTONEG_DISABLE && cmd->autoneg != AUTONEG_ENABLE) ++ return -EINVAL; ++ ++ mutex_lock(&pl->config_mutex); ++ ret = phylink_ethtool_sset(pl, cmd); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings); ++ ++/* This emulates MII registers for a fixed-mode phy operating as per the ++ * passed in state. "aneg" defines if we report negotiation is possible. ++ * ++ * FIXME: should deal with negotiation state too. ++ */ ++static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg, ++ struct phylink_link_state *state, bool aneg) ++{ ++ struct fixed_phy_status fs; ++ int val; ++ ++ fs.link = state->link; ++ fs.speed = state->speed; ++ fs.duplex = state->duplex; ++ fs.pause = state->pause & MLO_PAUSE_SYM; ++ fs.asym_pause = state->pause & MLO_PAUSE_ASYM; ++ ++ val = swphy_read_reg(reg, &fs); ++ if (reg == MII_BMSR) { ++ if (!state->an_complete) ++ val &= ~BMSR_ANEGCOMPLETE; ++ if (!aneg) ++ val &= ~BMSR_ANEGCAPABLE; ++ } ++ return val; ++} ++ ++static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, ++ unsigned int reg) ++{ ++ struct phylink_link_state state; ++ int val = 0xffff; ++ ++ if (pl->phydev && pl->phydev->addr != phy_id) ++ return mdiobus_read(pl->phydev->bus, phy_id, reg); ++ ++ if (!pl->phydev && phy_id != 0) ++ return val; ++ ++ switch (pl->link_an_mode) { ++ case MLO_AN_FIXED: ++ phylink_get_fixed_state(pl, &state); ++ val = phylink_mii_emul_read(pl->netdev, reg, &state, true); ++ break; ++ ++ case MLO_AN_PHY: ++ val = mdiobus_read(pl->phydev->bus, phy_id, reg); ++ break; ++ ++ case MLO_AN_SGMII: ++ if (pl->phydev) { ++ val = mdiobus_read(pl->phydev->bus, ++ pl->phydev->addr, reg); ++ break; ++ } ++ /* No phy, fall through to reading the MAC end */ ++ case MLO_AN_8023Z: ++ val = phylink_get_mac_state(pl, &state); ++ if (val < 0) ++ return val; ++ ++ val = phylink_mii_emul_read(pl->netdev, reg, &state, true); ++ break; ++ } ++ ++ return val & 0xffff; ++} ++ ++static void phylink_mii_write(struct phylink *pl, unsigned int phy_id, ++ unsigned int reg, unsigned int val) ++{ ++ if (pl->phydev && pl->phydev->addr != phy_id) { ++ mdiobus_write(pl->phydev->bus, phy_id, reg, val); ++ return; ++ } ++ ++ if (!pl->phydev && phy_id != 0) ++ return; ++ ++ switch (pl->link_an_mode) { ++ case MLO_AN_FIXED: ++ break; ++ ++ case MLO_AN_PHY: ++ mdiobus_write(pl->phydev->bus, pl->phydev->addr, ++ reg, val); ++ break; ++ ++ case MLO_AN_SGMII: ++ if (pl->phydev) { ++ mdiobus_write(pl->phydev->bus, phy_id, reg, val); ++ break; ++ } ++ /* No phy, fall through to reading the MAC end */ ++ case MLO_AN_8023Z: ++ break; ++ } ++} ++ ++int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd) ++{ ++ struct mii_ioctl_data *mii_data = if_mii(ifr); ++ int val, ret; ++ ++ mutex_lock(&pl->config_mutex); ++ ++ switch (cmd) { ++ case SIOCGMIIPHY: ++ mii_data->phy_id = pl->phydev ? pl->phydev->addr : 0; ++ /* fallthrough */ ++ ++ case SIOCGMIIREG: ++ val = phylink_mii_read(pl, mii_data->phy_id, mii_data->reg_num); ++ if (val < 0) { ++ ret = val; ++ } else { ++ mii_data->val_out = val; ++ ret = 0; ++ } ++ break; ++ ++ case SIOCSMIIREG: ++ phylink_mii_write(pl, mii_data->phy_id, mii_data->reg_num, ++ mii_data->val_in); ++ ret = 0; ++ break; ++ ++ default: ++ ret = -EOPNOTSUPP; ++ if (pl->phydev) ++ ret = phy_mii_ioctl(pl->phydev, ifr, cmd); ++ break; ++ } ++ ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_mii_ioctl); ++ ++MODULE_LICENSE("GPL"); +--- a/include/linux/phy.h ++++ b/include/linux/phy.h +@@ -147,6 +147,7 @@ static inline const char *phy_modes(phy_ + #define MII_ADDR_C45 (1<<30) + + struct device; ++struct phylink; + struct sk_buff; + + /* +@@ -429,6 +430,7 @@ struct phy_device { + + struct mutex lock; + ++ struct phylink *phylink; + struct net_device *attached_dev; + + u8 mdix; +--- /dev/null ++++ b/include/linux/phylink.h +@@ -0,0 +1,70 @@ ++#ifndef NETDEV_PCS_H ++#define NETDEV_PCS_H ++ ++#include <linux/phy.h> ++#include <linux/spinlock.h> ++#include <linux/workqueue.h> ++ ++struct device_node; ++struct ethtool_cmd; ++struct net_device; ++ ++enum { ++ MLO_PAUSE_NONE, ++ MLO_PAUSE_ASYM = BIT(0), ++ MLO_PAUSE_SYM = BIT(1), ++ ++ MLO_AN_PHY = 0, ++ MLO_AN_FIXED, ++ MLO_AN_SGMII, ++ MLO_AN_8023Z, ++}; ++ ++struct phylink_link_state { ++ u32 supported; ++ u32 advertising; ++ u32 lp_advertising; ++ int speed; ++ int duplex; ++ int pause; ++ unsigned int link:1; ++ unsigned int sync:1; ++ unsigned int an_enabled:1; ++ unsigned int an_complete:1; ++}; ++ ++struct phylink_mac_ops { ++ /* Get the ethtool supported mask for the indicated mode */ ++ int (*mac_get_support)(struct net_device *, unsigned int mode, ++ struct phylink_link_state *); ++ ++ /* Read the current link state from the hardware */ ++ int (*mac_link_state)(struct net_device *, struct phylink_link_state *); ++ ++ /* Configure the MAC */ ++ void (*mac_config)(struct net_device *, unsigned int mode, ++ const struct phylink_link_state *); ++ void (*mac_an_restart)(struct net_device *, unsigned int mode); ++ ++ void (*mac_link_down)(struct net_device *, unsigned int mode); ++ void (*mac_link_up)(struct net_device *, unsigned int mode); ++}; ++ ++struct phylink *phylink_create(struct net_device *, struct device_node *, ++ phy_interface_t iface, const struct phylink_mac_ops *ops); ++void phylink_destroy(struct phylink *); ++ ++int phylink_connect_phy(struct phylink *, struct phy_device *); ++int phylink_of_phy_connect(struct phylink *, struct device_node *); ++void phylink_disconnect_phy(struct phylink *); ++ ++void phylink_mac_change(struct phylink *, bool up); ++ ++void phylink_start(struct phylink *); ++void phylink_stop(struct phylink *); ++ ++int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *); ++int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *); ++int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); ++ ++#endif diff --git a/target/linux/mvebu/patches-4.4/131-phylink-add-hooks-for-SFP-support.patch b/target/linux/mvebu/patches-4.4/131-phylink-add-hooks-for-SFP-support.patch new file mode 100644 index 0000000000..64a1c1ff1d --- /dev/null +++ b/target/linux/mvebu/patches-4.4/131-phylink-add-hooks-for-SFP-support.patch @@ -0,0 +1,156 @@ +From 0a0c4b3dd4f34df4532f254a5940b520015d766f Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 24 Sep 2015 11:01:13 +0100 +Subject: [PATCH 719/744] phylink: add hooks for SFP support + +Add support to phylink for SFP, which needs to control and configure +the ethernet MAC link state. Specifically, SFP needs to: + +1. set the negotiation mode between SGMII and 1000base-X +2. attach and detach the module PHY +3. prevent the link coming up when errors are reported + +In the absence of a PHY, we also need to set the ethtool port type +according to the module plugged in. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/phylink.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++ + include/linux/phylink.h | 6 ++++ + 2 files changed, 88 insertions(+) + +--- a/drivers/net/phy/phylink.c ++++ b/drivers/net/phy/phylink.c +@@ -11,6 +11,7 @@ + #include <linux/ethtool.h> + #include <linux/export.h> + #include <linux/gpio/consumer.h> ++#include <linux/list.h> + #include <linux/netdevice.h> + #include <linux/of.h> + #include <linux/of_mdio.h> +@@ -29,11 +30,16 @@ + (ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \ + ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane) + ++static LIST_HEAD(phylinks); ++static DEFINE_MUTEX(phylink_mutex); ++ + enum { + PHYLINK_DISABLE_STOPPED, ++ PHYLINK_DISABLE_LINK, + }; + + struct phylink { ++ struct list_head node; + struct net_device *netdev; + const struct phylink_mac_ops *ops; + struct mutex config_mutex; +@@ -341,12 +347,20 @@ struct phylink *phylink_create(struct ne + return ERR_PTR(ret); + } + ++ mutex_lock(&phylink_mutex); ++ list_add_tail(&pl->node, &phylinks); ++ mutex_unlock(&phylink_mutex); ++ + return pl; + } + EXPORT_SYMBOL_GPL(phylink_create); + + void phylink_destroy(struct phylink *pl) + { ++ mutex_lock(&phylink_mutex); ++ list_del(&pl->node); ++ mutex_unlock(&phylink_mutex); ++ + cancel_work_sync(&pl->resolve); + kfree(pl); + } +@@ -813,4 +827,72 @@ int phylink_mii_ioctl(struct phylink *pl + } + EXPORT_SYMBOL_GPL(phylink_mii_ioctl); + ++ ++ ++void phylink_disable(struct phylink *pl) ++{ ++ set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); ++ flush_work(&pl->resolve); ++ ++ netif_carrier_off(pl->netdev); ++} ++EXPORT_SYMBOL_GPL(phylink_disable); ++ ++void phylink_enable(struct phylink *pl) ++{ ++ clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); ++ phylink_run_resolve(pl); ++} ++EXPORT_SYMBOL_GPL(phylink_enable); ++ ++void phylink_set_link_port(struct phylink *pl, u32 support, u8 port) ++{ ++ WARN_ON(support & ~SUPPORTED_INTERFACES); ++ ++ mutex_lock(&pl->config_mutex); ++ pl->link_port_support = support; ++ pl->link_port = port; ++ mutex_unlock(&pl->config_mutex); ++} ++EXPORT_SYMBOL_GPL(phylink_set_link_port); ++ ++int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode) ++{ ++ int ret = 0; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->link_an_mode != mode) { ++ ret = phylink_get_support(pl, mode); ++ if (ret == 0) { ++ if (!test_bit(PHYLINK_DISABLE_STOPPED, ++ &pl->phylink_disable_state)) ++ phylink_mac_config(pl, &pl->link_config); ++ ++ netdev_info(pl->netdev, "switched to %s link mode\n", ++ phylink_an_mode_str(mode)); ++ } ++ } ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_set_link_an_mode); ++ ++struct phylink *phylink_lookup_by_netdev(struct net_device *ndev) ++{ ++ struct phylink *pl, *found = NULL; ++ ++ mutex_lock(&phylink_mutex); ++ list_for_each_entry(pl, &phylinks, node) ++ if (pl->netdev == ndev) { ++ found = pl; ++ break; ++ } ++ ++ mutex_unlock(&phylink_mutex); ++ ++ return found; ++} ++EXPORT_SYMBOL_GPL(phylink_lookup_by_netdev); ++ + MODULE_LICENSE("GPL"); +--- a/include/linux/phylink.h ++++ b/include/linux/phylink.h +@@ -67,4 +67,10 @@ int phylink_ethtool_get_settings(struct + int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *); + int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); + ++void phylink_set_link_port(struct phylink *pl, u32 support, u8 port); ++int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode); ++void phylink_disable(struct phylink *pl); ++void phylink_enable(struct phylink *pl); ++struct phylink *phylink_lookup_by_netdev(struct net_device *ndev); ++ + #endif diff --git a/target/linux/mvebu/patches-4.4/132-sfp-add-phylink-based-SFP-module-support.patch b/target/linux/mvebu/patches-4.4/132-sfp-add-phylink-based-SFP-module-support.patch new file mode 100644 index 0000000000..07b1766b0b --- /dev/null +++ b/target/linux/mvebu/patches-4.4/132-sfp-add-phylink-based-SFP-module-support.patch @@ -0,0 +1,1382 @@ +From bf0a000960234c0e773fadea47240c3cda0cab02 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sat, 12 Sep 2015 18:43:39 +0100 +Subject: [PATCH 720/744] sfp: add phylink based SFP module support + +Add support for SFP hotpluggable modules via phylink. This supports +both copper and optical SFP modules, which require different Serdes +modes in order to properly negotiate the link. + +Optical SFP modules typically require the Serdes link to be talking +1000base-X mode - this is the gigabit ethernet mode defined by the +802.3 standard. + +Copper SFP modules typically integrate a PHY in the module to convert +from Serdes to copper, and the PHY will be configured by the vendor +to either present a 1000base-X Serdes link (for fixed 1000base-T) or +a SGMII Serdes link. However, this is vendor defined, so we instead +detect the PHY, switch the link to SGMII mode, and use traditional +PHY based negotiation. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/Kconfig | 5 + + drivers/net/phy/Makefile | 1 + + drivers/net/phy/sfp.c | 986 +++++++++++++++++++++++++++++++++++++++++++++++ + include/linux/sfp.h | 339 ++++++++++++++++ + 4 files changed, 1331 insertions(+) + create mode 100644 drivers/net/phy/sfp.c + create mode 100644 include/linux/sfp.h + +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -225,6 +225,11 @@ config FIXED_PHY + + Currently tested with mpc866ads and mpc8349e-mitx. + ++config SFP ++ tristate "SFP cage support" ++ depends on I2C && PHYLINK ++ select MDIO_I2C ++ + config MDIO_BITBANG + tristate "Support for bitbanged MDIO buses" + help +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -61,3 +61,4 @@ obj-$(CONFIG_MDIO_MOXART) += mdio-moxart + obj-$(CONFIG_MDIO_BCM_UNIMAC) += mdio-bcm-unimac.o + obj-$(CONFIG_MICROCHIP_PHY) += microchip.o + obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o ++obj-$(CONFIG_SFP) += sfp.o +--- /dev/null ++++ b/drivers/net/phy/sfp.c +@@ -0,0 +1,986 @@ ++#include <linux/delay.h> ++#include <linux/gpio.h> ++#include <linux/i2c.h> ++#include <linux/interrupt.h> ++#include <linux/jiffies.h> ++#include <linux/module.h> ++#include <linux/mutex.h> ++#include <linux/netdevice.h> ++#include <linux/of.h> ++#include <linux/of_net.h> ++#include <linux/phylink.h> ++#include <linux/platform_device.h> ++#include <linux/sfp.h> ++#include <linux/slab.h> ++#include <linux/workqueue.h> ++ ++#include "mdio-i2c.h" ++#include "swphy.h" ++ ++enum { ++ GPIO_MODDEF0, ++ GPIO_LOS, ++ GPIO_TX_FAULT, ++ GPIO_TX_DISABLE, ++ GPIO_RATE_SELECT, ++ GPIO_MAX, ++ ++ SFP_F_PRESENT = BIT(GPIO_MODDEF0), ++ SFP_F_LOS = BIT(GPIO_LOS), ++ SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT), ++ SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE), ++ SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT), ++ ++ SFP_E_INSERT = 0, ++ SFP_E_REMOVE, ++ SFP_E_DEV_DOWN, ++ SFP_E_DEV_UP, ++ SFP_E_TX_FAULT, ++ SFP_E_TX_CLEAR, ++ SFP_E_LOS_HIGH, ++ SFP_E_LOS_LOW, ++ SFP_E_TIMEOUT, ++ ++ SFP_MOD_EMPTY = 0, ++ SFP_MOD_PROBE, ++ SFP_MOD_PRESENT, ++ SFP_MOD_ERROR, ++ ++ SFP_DEV_DOWN = 0, ++ SFP_DEV_UP, ++ ++ SFP_S_DOWN = 0, ++ SFP_S_INIT, ++ SFP_S_WAIT_LOS, ++ SFP_S_LINK_UP, ++ SFP_S_TX_FAULT, ++ SFP_S_REINIT, ++ SFP_S_TX_DISABLE, ++}; ++ ++static const char *gpio_of_names[] = { ++ "moddef0", ++ "los", ++ "tx-fault", ++ "tx-disable", ++ "rate-select", ++}; ++ ++static const enum gpiod_flags gpio_flags[] = { ++ GPIOD_IN, ++ GPIOD_IN, ++ GPIOD_IN, ++ GPIOD_ASIS, ++ GPIOD_ASIS, ++}; ++ ++#define T_INIT_JIFFIES msecs_to_jiffies(300) ++#define T_RESET_US 10 ++#define T_FAULT_RECOVER msecs_to_jiffies(1000) ++ ++/* SFP module presence detection is poor: the three MOD DEF signals are ++ * the same length on the PCB, which means it's possible for MOD DEF 0 to ++ * connect before the I2C bus on MOD DEF 1/2. Try to work around this ++ * design bug by waiting 50ms before probing, and then retry every 250ms. ++ */ ++#define T_PROBE_INIT msecs_to_jiffies(50) ++#define T_PROBE_RETRY msecs_to_jiffies(250) ++ ++/* ++ * SFP modules appear to always have their PHY configured for bus address ++ * 0x56 (which with mdio-i2c, translates to a PHY address of 22). ++ */ ++#define SFP_PHY_ADDR 22 ++ ++/* ++ * Give this long for the PHY to reset. ++ */ ++#define T_PHY_RESET_MS 50 ++ ++static DEFINE_MUTEX(sfp_mutex); ++ ++struct sfp { ++ struct device *dev; ++ struct i2c_adapter *i2c; ++ struct mii_bus *i2c_mii; ++ struct net_device *ndev; ++ struct phylink *phylink; ++ struct phy_device *mod_phy; ++ ++ unsigned int (*get_state)(struct sfp *); ++ void (*set_state)(struct sfp *, unsigned int); ++ int (*read)(struct sfp *, bool, u8, void *, size_t); ++ ++ struct gpio_desc *gpio[GPIO_MAX]; ++ ++ unsigned int state; ++ struct delayed_work poll; ++ struct delayed_work timeout; ++ struct mutex sm_mutex; ++ unsigned char sm_mod_state; ++ unsigned char sm_dev_state; ++ unsigned short sm_state; ++ unsigned int sm_retries; ++ ++ struct sfp_eeprom_id id; ++ ++ struct notifier_block netdev_nb; ++}; ++ ++static unsigned long poll_jiffies; ++ ++static unsigned int sfp_gpio_get_state(struct sfp *sfp) ++{ ++ unsigned int i, state, v; ++ ++ for (i = state = 0; i < GPIO_MAX; i++) { ++ if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) ++ continue; ++ ++ v = gpiod_get_value_cansleep(sfp->gpio[i]); ++ if (v) ++ state |= BIT(i); ++ } ++ ++ return state; ++} ++ ++static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state) ++{ ++ if (state & SFP_F_PRESENT) { ++ /* If the module is present, drive the signals */ ++ if (sfp->gpio[GPIO_TX_DISABLE]) ++ gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE], ++ state & SFP_F_TX_DISABLE); ++ if (state & SFP_F_RATE_SELECT) ++ gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT], ++ state & SFP_F_RATE_SELECT); ++ } else { ++ /* Otherwise, let them float to the pull-ups */ ++ if (sfp->gpio[GPIO_TX_DISABLE]) ++ gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]); ++ if (state & SFP_F_RATE_SELECT) ++ gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]); ++ } ++} ++ ++static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr, ++ void *buf, size_t len) ++{ ++ struct i2c_msg msgs[2]; ++ int ret; ++ ++ msgs[0].addr = bus_addr; ++ msgs[0].flags = 0; ++ msgs[0].len = 1; ++ msgs[0].buf = &dev_addr; ++ msgs[1].addr = bus_addr; ++ msgs[1].flags = I2C_M_RD; ++ msgs[1].len = len; ++ msgs[1].buf = buf; ++ ++ ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); ++ if (ret < 0) ++ return ret; ++ ++ return ret == ARRAY_SIZE(msgs) ? len : 0; ++} ++ ++static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf, ++ size_t len) ++{ ++ return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len); ++} ++ ++static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c) ++{ ++ struct mii_bus *i2c_mii; ++ int ret; ++ ++ if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) ++ return -EINVAL; ++ ++ sfp->i2c = i2c; ++ sfp->read = sfp_i2c_read; ++ ++ i2c_mii = mdio_i2c_alloc(sfp->dev, i2c); ++ if (IS_ERR(i2c_mii)) ++ return PTR_ERR(i2c_mii); ++ ++ i2c_mii->name = "SFP I2C Bus"; ++ i2c_mii->phy_mask = ~0; ++ ++ ret = mdiobus_register(i2c_mii); ++ if (ret < 0) { ++ mdiobus_free(i2c_mii); ++ return ret; ++ } ++ ++ sfp->i2c_mii = i2c_mii; ++ ++ return 0; ++} ++ ++ ++/* Interface */ ++static unsigned int sfp_get_state(struct sfp *sfp) ++{ ++ return sfp->get_state(sfp); ++} ++ ++static void sfp_set_state(struct sfp *sfp, unsigned int state) ++{ ++ sfp->set_state(sfp, state); ++} ++ ++static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) ++{ ++ return sfp->read(sfp, a2, addr, buf, len); ++} ++ ++static unsigned int sfp_check(void *buf, size_t len) ++{ ++ u8 *p, check; ++ ++ for (p = buf, check = 0; len; p++, len--) ++ check += *p; ++ ++ return check; ++} ++ ++/* Helpers */ ++static void sfp_module_tx_disable(struct sfp *sfp) ++{ ++ dev_dbg(sfp->dev, "tx disable %u -> %u\n", ++ sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1); ++ sfp->state |= SFP_F_TX_DISABLE; ++ sfp_set_state(sfp, sfp->state); ++} ++ ++static void sfp_module_tx_enable(struct sfp *sfp) ++{ ++ dev_dbg(sfp->dev, "tx disable %u -> %u\n", ++ sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0); ++ sfp->state &= ~SFP_F_TX_DISABLE; ++ sfp_set_state(sfp, sfp->state); ++} ++ ++static void sfp_module_tx_fault_reset(struct sfp *sfp) ++{ ++ unsigned int state = sfp->state; ++ ++ if (state & SFP_F_TX_DISABLE) ++ return; ++ ++ sfp_set_state(sfp, state | SFP_F_TX_DISABLE); ++ ++ udelay(T_RESET_US); ++ ++ sfp_set_state(sfp, state); ++} ++ ++/* SFP state machine */ ++static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout) ++{ ++ if (timeout) ++ mod_delayed_work(system_power_efficient_wq, &sfp->timeout, ++ timeout); ++ else ++ cancel_delayed_work(&sfp->timeout); ++} ++ ++static void sfp_sm_next(struct sfp *sfp, unsigned int state, ++ unsigned int timeout) ++{ ++ sfp->sm_state = state; ++ sfp_sm_set_timer(sfp, timeout); ++} ++ ++static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout) ++{ ++ sfp->sm_mod_state = state; ++ sfp_sm_set_timer(sfp, timeout); ++} ++ ++static void sfp_sm_phy_detach(struct sfp *sfp) ++{ ++ phy_stop(sfp->mod_phy); ++ if (sfp->phylink) ++ phylink_disconnect_phy(sfp->phylink); ++ phy_device_remove(sfp->mod_phy); ++ phy_device_free(sfp->mod_phy); ++ sfp->mod_phy = NULL; ++} ++ ++static void sfp_sm_probe_phy(struct sfp *sfp) ++{ ++ struct phy_device *phy; ++ int err; ++ ++ msleep(T_PHY_RESET_MS); ++ ++ phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR); ++ if (IS_ERR(phy)) { ++ dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy)); ++ return; ++ } ++ if (!phy) { ++ dev_info(sfp->dev, "no PHY detected\n"); ++ return; ++ } ++ ++ err = phylink_connect_phy(sfp->phylink, phy); ++ if (err) { ++ phy_device_remove(phy); ++ phy_device_free(phy); ++ dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err); ++ return; ++ } ++ ++ sfp->mod_phy = phy; ++ phy_start(phy); ++} ++ ++static void sfp_sm_link_up(struct sfp *sfp) ++{ ++ if (sfp->phylink) ++ phylink_enable(sfp->phylink); ++ ++ sfp_sm_next(sfp, SFP_S_LINK_UP, 0); ++} ++ ++static void sfp_sm_link_down(struct sfp *sfp) ++{ ++ if (sfp->phylink) ++ phylink_disable(sfp->phylink); ++} ++ ++static void sfp_sm_link_check_los(struct sfp *sfp) ++{ ++ unsigned int los = sfp->state & SFP_F_LOS; ++ ++ /* FIXME: what if neither SFP_OPTIONS_LOS_INVERTED nor ++ * SFP_OPTIONS_LOS_NORMAL are set? For now, we assume ++ * the same as SFP_OPTIONS_LOS_NORMAL set. ++ */ ++ if (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED) ++ los ^= SFP_F_LOS; ++ ++ if (los) ++ sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); ++ else ++ sfp_sm_link_up(sfp); ++} ++ ++static void sfp_sm_fault(struct sfp *sfp, bool warn) ++{ ++ if (sfp->sm_retries && !--sfp->sm_retries) { ++ dev_err(sfp->dev, "module persistently indicates fault, disabling\n"); ++ sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0); ++ } else { ++ if (warn) ++ dev_err(sfp->dev, "module transmit fault indicated\n"); ++ ++ sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER); ++ } ++} ++ ++static void sfp_sm_mod_init(struct sfp *sfp) ++{ ++ sfp_module_tx_enable(sfp); ++ ++ /* Wait t_init before indicating that the link is up, provided the ++ * current state indicates no TX_FAULT. If TX_FAULT clears before ++ * this time, that's fine too. ++ */ ++ sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES); ++ sfp->sm_retries = 5; ++ ++ if (sfp->phylink) { ++ /* Setting the serdes link mode is guesswork: there's no ++ * field in the EEPROM which indicates what mode should ++ * be used. ++ * ++ * If it's a gigabit-only fiber module, it probably does ++ * not have a PHY, so switch to 802.3z negotiation mode. ++ * Otherwise, switch to SGMII mode (which is required to ++ * support non-gigabit speeds) and probe for a PHY. ++ */ ++ if (!sfp->id.base.e1000_base_t && ++ !sfp->id.base.e100_base_lx && ++ !sfp->id.base.e100_base_fx) { ++ phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z); ++ } else { ++ phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII); ++ sfp_sm_probe_phy(sfp); ++ } ++ } ++} ++ ++static int sfp_sm_mod_probe(struct sfp *sfp) ++{ ++ /* SFP module inserted - read I2C data */ ++ struct sfp_eeprom_id id; ++ char vendor[17]; ++ char part[17]; ++ char sn[17]; ++ char date[9]; ++ char rev[5]; ++ u8 check; ++ int err; ++ ++ err = sfp_read(sfp, false, 0, &id, sizeof(id)); ++ if (err < 0) { ++ dev_err(sfp->dev, "failed to read EEPROM: %d\n", err); ++ return -EAGAIN; ++ } ++ ++ /* Validate the checksum over the base structure */ ++ check = sfp_check(&id.base, sizeof(id.base) - 1); ++ if (check != id.base.cc_base) { ++ dev_err(sfp->dev, ++ "EEPROM base structure checksum failure: 0x%02x\n", ++ check); ++ return -EINVAL; ++ } ++ ++ check = sfp_check(&id.ext, sizeof(id.ext) - 1); ++ if (check != id.ext.cc_ext) { ++ dev_err(sfp->dev, ++ "EEPROM extended structure checksum failure: 0x%02x\n", ++ check); ++ memset(&id.ext, 0, sizeof(id.ext)); ++ } ++ ++ sfp->id = id; ++ ++ memcpy(vendor, sfp->id.base.vendor_name, 16); ++ vendor[16] = '\0'; ++ memcpy(part, sfp->id.base.vendor_pn, 16); ++ part[16] = '\0'; ++ memcpy(rev, sfp->id.base.vendor_rev, 4); ++ rev[4] = '\0'; ++ memcpy(sn, sfp->id.ext.vendor_sn, 16); ++ sn[16] = '\0'; ++ memcpy(date, sfp->id.ext.datecode, 8); ++ date[8] = '\0'; ++ ++ dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date); ++ ++ /* We only support SFP modules, not the legacy GBIC modules. */ ++ if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP || ++ sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) { ++ dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n", ++ sfp->id.base.phys_id, sfp->id.base.phys_ext_id); ++ return -EINVAL; ++ } ++ ++ /* ++ * What isn't clear from the SFP documentation is whether this ++ * specifies the encoding expected on the TD/RD lines, or whether ++ * the TD/RD lines are always 8b10b encoded, but the transceiver ++ * converts. Eg, think of a copper SFP supporting 1G/100M/10M ++ * ethernet: this requires 8b10b encoding for 1G, 4b5b for 100M, ++ * and manchester for 10M. ++ */ ++ /* 1Gbit ethernet requires 8b10b encoding */ ++ if (sfp->id.base.encoding != SFP_ENCODING_8B10B) { ++ dev_err(sfp->dev, "module does not support 8B10B encoding\n"); ++ return -EINVAL; ++ } ++ ++ if (sfp->phylink) { ++ u32 support; ++ u8 port; ++ ++ if (sfp->id.base.e1000_base_t) { ++ support = SUPPORTED_TP; ++ port = PORT_TP; ++ } else { ++ support = SUPPORTED_FIBRE; ++ port = PORT_FIBRE; ++ } ++ phylink_set_link_port(sfp->phylink, support, port); ++ } ++ ++ return 0; ++} ++ ++static void sfp_sm_mod_remove(struct sfp *sfp) ++{ ++ if (sfp->mod_phy) ++ sfp_sm_phy_detach(sfp); ++ ++ sfp_module_tx_disable(sfp); ++ ++ memset(&sfp->id, 0, sizeof(sfp->id)); ++ ++ dev_info(sfp->dev, "module removed\n"); ++} ++ ++static void sfp_sm_event(struct sfp *sfp, unsigned int event) ++{ ++ mutex_lock(&sfp->sm_mutex); ++ ++ dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n", ++ sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event); ++ ++ /* This state machine tracks the insert/remove state of ++ * the module, and handles probing the on-board EEPROM. ++ */ ++ switch (sfp->sm_mod_state) { ++ default: ++ if (event == SFP_E_INSERT) { ++ sfp_module_tx_disable(sfp); ++ sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT); ++ } ++ break; ++ ++ case SFP_MOD_PROBE: ++ if (event == SFP_E_REMOVE) { ++ sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0); ++ } else if (event == SFP_E_TIMEOUT) { ++ int err = sfp_sm_mod_probe(sfp); ++ ++ if (err == 0) ++ sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0); ++ else if (err == -EAGAIN) ++ sfp_sm_set_timer(sfp, T_PROBE_RETRY); ++ else ++ sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0); ++ } ++ break; ++ ++ case SFP_MOD_PRESENT: ++ case SFP_MOD_ERROR: ++ if (event == SFP_E_REMOVE) { ++ sfp_sm_mod_remove(sfp); ++ sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0); ++ } ++ break; ++ } ++ ++ /* This state machine tracks the netdev up/down state */ ++ switch (sfp->sm_dev_state) { ++ default: ++ if (event == SFP_E_DEV_UP) ++ sfp->sm_dev_state = SFP_DEV_UP; ++ break; ++ ++ case SFP_DEV_UP: ++ if (event == SFP_E_DEV_DOWN) { ++ /* If the module has a PHY, avoid raising TX disable ++ * as this resets the PHY. Otherwise, raise it to ++ * turn the laser off. ++ */ ++ if (!sfp->mod_phy) ++ sfp_module_tx_disable(sfp); ++ sfp->sm_dev_state = SFP_DEV_DOWN; ++ } ++ break; ++ } ++ ++ /* Some events are global */ ++ if (sfp->sm_state != SFP_S_DOWN && ++ (sfp->sm_mod_state != SFP_MOD_PRESENT || ++ sfp->sm_dev_state != SFP_DEV_UP)) { ++ if (sfp->sm_state == SFP_S_LINK_UP && ++ sfp->sm_dev_state == SFP_DEV_UP) ++ sfp_sm_link_down(sfp); ++ if (sfp->mod_phy) ++ sfp_sm_phy_detach(sfp); ++ sfp_sm_next(sfp, SFP_S_DOWN, 0); ++ mutex_unlock(&sfp->sm_mutex); ++ return; ++ } ++ ++ /* The main state machine */ ++ switch (sfp->sm_state) { ++ case SFP_S_DOWN: ++ if (sfp->sm_mod_state == SFP_MOD_PRESENT && ++ sfp->sm_dev_state == SFP_DEV_UP) ++ sfp_sm_mod_init(sfp); ++ break; ++ ++ case SFP_S_INIT: ++ if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) ++ sfp_sm_fault(sfp, true); ++ else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) ++ sfp_sm_link_check_los(sfp); ++ break; ++ ++ case SFP_S_WAIT_LOS: ++ if (event == SFP_E_TX_FAULT) ++ sfp_sm_fault(sfp, true); ++ else if (event == ++ (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ? ++ SFP_E_LOS_HIGH : SFP_E_LOS_LOW)) ++ sfp_sm_link_up(sfp); ++ break; ++ ++ case SFP_S_LINK_UP: ++ if (event == SFP_E_TX_FAULT) { ++ sfp_sm_link_down(sfp); ++ sfp_sm_fault(sfp, true); ++ } else if (event == ++ (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ? ++ SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) { ++ sfp_sm_link_down(sfp); ++ sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); ++ } ++ break; ++ ++ case SFP_S_TX_FAULT: ++ if (event == SFP_E_TIMEOUT) { ++ sfp_module_tx_fault_reset(sfp); ++ sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES); ++ } ++ break; ++ ++ case SFP_S_REINIT: ++ if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) { ++ sfp_sm_fault(sfp, false); ++ } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) { ++ dev_info(sfp->dev, "module transmit fault recovered\n"); ++ sfp_sm_link_check_los(sfp); ++ } ++ break; ++ ++ case SFP_S_TX_DISABLE: ++ break; ++ } ++ ++ dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n", ++ sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state); ++ ++ mutex_unlock(&sfp->sm_mutex); ++} ++ ++#if 0 ++static int sfp_phy_module_info(struct phy_device *phy, ++ struct ethtool_modinfo *modinfo) ++{ ++ struct sfp *sfp = phy->priv; ++ ++ /* locking... and check module is present */ ++ ++ if (sfp->id.ext.sff8472_compliance) { ++ modinfo->type = ETH_MODULE_SFF_8472; ++ modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN; ++ } else { ++ modinfo->type = ETH_MODULE_SFF_8079; ++ modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN; ++ } ++ return 0; ++} ++ ++static int sfp_phy_module_eeprom(struct phy_device *phy, ++ struct ethtool_eeprom *ee, u8 *data) ++{ ++ struct sfp *sfp = phy->priv; ++ unsigned int first, last, len; ++ int ret; ++ ++ if (ee->len == 0) ++ return -EINVAL; ++ ++ first = ee->offset; ++ last = ee->offset + ee->len; ++ if (first < ETH_MODULE_SFF_8079_LEN) { ++ len = last; ++ if (len > ETH_MODULE_SFF_8079_LEN) ++ len = ETH_MODULE_SFF_8079_LEN; ++ len -= first; ++ ++ ret = sfp->read(sfp, false, first, data, len); ++ if (ret < 0) ++ return ret; ++ ++ first += len; ++ data += len; ++ } ++ if (first >= ETH_MODULE_SFF_8079_LEN && last > first) { ++ len = last - first; ++ ++ ret = sfp->read(sfp, true, first, data, len); ++ if (ret < 0) ++ return ret; ++ } ++ return 0; ++} ++#endif ++ ++static void sfp_timeout(struct work_struct *work) ++{ ++ struct sfp *sfp = container_of(work, struct sfp, timeout.work); ++ ++ sfp_sm_event(sfp, SFP_E_TIMEOUT); ++} ++ ++static void sfp_check_state(struct sfp *sfp) ++{ ++ unsigned int state, i, changed; ++ ++ state = sfp_get_state(sfp); ++ changed = state ^ sfp->state; ++ changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT; ++ ++ for (i = 0; i < GPIO_MAX; i++) ++ if (changed & BIT(i)) ++ dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i], ++ !!(sfp->state & BIT(i)), !!(state & BIT(i))); ++ ++ state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); ++ sfp->state = state; ++ ++ if (changed & SFP_F_PRESENT) ++ sfp_sm_event(sfp, state & SFP_F_PRESENT ? ++ SFP_E_INSERT : SFP_E_REMOVE); ++ ++ if (changed & SFP_F_TX_FAULT) ++ sfp_sm_event(sfp, state & SFP_F_TX_FAULT ? ++ SFP_E_TX_FAULT : SFP_E_TX_CLEAR); ++ ++ if (changed & SFP_F_LOS) ++ sfp_sm_event(sfp, state & SFP_F_LOS ? ++ SFP_E_LOS_HIGH : SFP_E_LOS_LOW); ++} ++ ++static irqreturn_t sfp_irq(int irq, void *data) ++{ ++ struct sfp *sfp = data; ++ ++ sfp_check_state(sfp); ++ ++ return IRQ_HANDLED; ++} ++ ++static void sfp_poll(struct work_struct *work) ++{ ++ struct sfp *sfp = container_of(work, struct sfp, poll.work); ++ ++ sfp_check_state(sfp); ++ mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); ++} ++ ++static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void *data) ++{ ++ struct sfp *sfp = container_of(nb, struct sfp, netdev_nb); ++ struct netdev_notifier_info *info = data; ++ struct net_device *ndev = info->dev; ++ ++ if (!sfp->ndev || ndev != sfp->ndev) ++ return NOTIFY_DONE; ++ ++ switch (act) { ++ case NETDEV_UP: ++ sfp_sm_event(sfp, SFP_E_DEV_UP); ++ break; ++ ++ case NETDEV_GOING_DOWN: ++ sfp_sm_event(sfp, SFP_E_DEV_DOWN); ++ break; ++ ++ case NETDEV_UNREGISTER: ++ if (sfp->mod_phy && sfp->phylink) ++ phylink_disconnect_phy(sfp->phylink); ++ sfp->phylink = NULL; ++ dev_put(sfp->ndev); ++ sfp->ndev = NULL; ++ break; ++ } ++ return NOTIFY_OK; ++} ++ ++static struct sfp *sfp_alloc(struct device *dev) ++{ ++ struct sfp *sfp; ++ ++ sfp = kzalloc(sizeof(*sfp), GFP_KERNEL); ++ if (!sfp) ++ return ERR_PTR(-ENOMEM); ++ ++ sfp->dev = dev; ++ ++ mutex_init(&sfp->sm_mutex); ++ INIT_DELAYED_WORK(&sfp->poll, sfp_poll); ++ INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout); ++ ++ sfp->netdev_nb.notifier_call = sfp_netdev_notify; ++ ++ return sfp; ++} ++ ++static void sfp_destroy(struct sfp *sfp) ++{ ++ cancel_delayed_work_sync(&sfp->poll); ++ cancel_delayed_work_sync(&sfp->timeout); ++ if (sfp->i2c_mii) { ++ mdiobus_unregister(sfp->i2c_mii); ++ mdiobus_free(sfp->i2c_mii); ++ } ++ if (sfp->i2c) ++ i2c_put_adapter(sfp->i2c); ++ of_node_put(sfp->dev->of_node); ++ kfree(sfp); ++} ++ ++static void sfp_cleanup(void *data) ++{ ++ struct sfp *sfp = data; ++ ++ sfp_destroy(sfp); ++} ++ ++static int sfp_probe(struct platform_device *pdev) ++{ ++ struct sfp *sfp; ++ bool poll = false; ++ int irq, err, i; ++ ++ sfp = sfp_alloc(&pdev->dev); ++ if (IS_ERR(sfp)) ++ return PTR_ERR(sfp); ++ ++ platform_set_drvdata(pdev, sfp); ++ ++ err = devm_add_action(sfp->dev, sfp_cleanup, sfp); ++ if (err < 0) ++ return err; ++ ++ if (pdev->dev.of_node) { ++ struct device_node *node = pdev->dev.of_node; ++ struct device_node *np; ++ ++ np = of_parse_phandle(node, "i2c-bus", 0); ++ if (np) { ++ struct i2c_adapter *i2c; ++ ++ i2c = of_find_i2c_adapter_by_node(np); ++ of_node_put(np); ++ if (!i2c) ++ return -EPROBE_DEFER; ++ ++ err = sfp_i2c_configure(sfp, i2c); ++ if (err < 0) { ++ i2c_put_adapter(i2c); ++ return err; ++ } ++ } ++ ++ for (i = 0; i < GPIO_MAX; i++) { ++ sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev, ++ gpio_of_names[i], gpio_flags[i]); ++ if (IS_ERR(sfp->gpio[i])) ++ return PTR_ERR(sfp->gpio[i]); ++ } ++ ++ sfp->get_state = sfp_gpio_get_state; ++ sfp->set_state = sfp_gpio_set_state; ++ ++ np = of_parse_phandle(node, "sfp,ethernet", 0); ++ if (!np) { ++ dev_err(sfp->dev, "missing sfp,ethernet property\n"); ++ return -EINVAL; ++ } ++ ++ sfp->ndev = of_find_net_device_by_node(np); ++ if (!sfp->ndev) { ++ dev_err(sfp->dev, "ethernet device not found\n"); ++ return -EPROBE_DEFER; ++ } ++ ++ dev_hold(sfp->ndev); ++ put_device(&sfp->ndev->dev); ++ ++ sfp->phylink = phylink_lookup_by_netdev(sfp->ndev); ++ if (!sfp->phylink) { ++ dev_err(sfp->dev, "ethernet device not found\n"); ++ return -EPROBE_DEFER; ++ } ++ ++ phylink_disable(sfp->phylink); ++ } ++ ++ sfp->state = sfp_get_state(sfp); ++ if (sfp->gpio[GPIO_TX_DISABLE] && ++ gpiod_get_value_cansleep(sfp->gpio[GPIO_TX_DISABLE])) ++ sfp->state |= SFP_F_TX_DISABLE; ++ if (sfp->gpio[GPIO_RATE_SELECT] && ++ gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT])) ++ sfp->state |= SFP_F_RATE_SELECT; ++ sfp_set_state(sfp, sfp->state); ++ sfp_module_tx_disable(sfp); ++ if (sfp->state & SFP_F_PRESENT) ++ sfp_sm_event(sfp, SFP_E_INSERT); ++ ++ for (i = 0; i < GPIO_MAX; i++) { ++ if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) ++ continue; ++ ++ irq = gpiod_to_irq(sfp->gpio[i]); ++ if (!irq) { ++ poll = true; ++ continue; ++ } ++ ++ err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq, ++ IRQF_ONESHOT | ++ IRQF_TRIGGER_RISING | ++ IRQF_TRIGGER_FALLING, ++ dev_name(sfp->dev), sfp); ++ if (err) ++ poll = true; ++ } ++ ++ if (poll) ++ mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); ++ ++ register_netdevice_notifier(&sfp->netdev_nb); ++ ++ return 0; ++} ++ ++static int sfp_remove(struct platform_device *pdev) ++{ ++ struct sfp *sfp = platform_get_drvdata(pdev); ++ ++ unregister_netdevice_notifier(&sfp->netdev_nb); ++ if (sfp->ndev) ++ dev_put(sfp->ndev); ++ ++ return 0; ++} ++ ++static const struct of_device_id sfp_of_match[] = { ++ { .compatible = "sff,sfp", }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, sfp_of_match); ++ ++static struct platform_driver sfp_driver = { ++ .probe = sfp_probe, ++ .remove = sfp_remove, ++ .driver = { ++ .name = "sfp", ++ .of_match_table = sfp_of_match, ++ }, ++}; ++ ++static int sfp_init(void) ++{ ++ poll_jiffies = msecs_to_jiffies(100); ++ ++ return platform_driver_register(&sfp_driver); ++} ++module_init(sfp_init); ++ ++static void sfp_exit(void) ++{ ++ platform_driver_unregister(&sfp_driver); ++} ++module_exit(sfp_exit); ++ ++MODULE_ALIAS("platform:sfp"); ++MODULE_AUTHOR("Russell King"); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/include/linux/sfp.h +@@ -0,0 +1,339 @@ ++#ifndef LINUX_SFP_H ++#define LINUX_SFP_H ++ ++struct __packed sfp_eeprom_base { ++ u8 phys_id; ++ u8 phys_ext_id; ++ u8 connector; ++#if defined __BIG_ENDIAN_BITFIELD ++ u8 e10g_base_er:1; ++ u8 e10g_base_lrm:1; ++ u8 e10g_base_lr:1; ++ u8 e10g_base_sr:1; ++ u8 if_1x_sx:1; ++ u8 if_1x_lx:1; ++ u8 if_1x_copper_active:1; ++ u8 if_1x_copper_passive:1; ++ ++ u8 escon_mmf_1310_led:1; ++ u8 escon_smf_1310_laser:1; ++ u8 sonet_oc192_short_reach:1; ++ u8 sonet_reach_bit1:1; ++ u8 sonet_reach_bit2:1; ++ u8 sonet_oc48_long_reach:1; ++ u8 sonet_oc48_intermediate_reach:1; ++ u8 sonet_oc48_short_reach:1; ++ ++ u8 unallocated_5_7:1; ++ u8 sonet_oc12_smf_long_reach:1; ++ u8 sonet_oc12_smf_intermediate_reach:1; ++ u8 sonet_oc12_short_reach:1; ++ u8 unallocated_5_3:1; ++ u8 sonet_oc3_smf_long_reach:1; ++ u8 sonet_oc3_smf_intermediate_reach:1; ++ u8 sonet_oc3_short_reach:1; ++ ++ u8 e_base_px:1; ++ u8 e_base_bx10:1; ++ u8 e100_base_fx:1; ++ u8 e100_base_lx:1; ++ u8 e1000_base_t:1; ++ u8 e1000_base_cx:1; ++ u8 e1000_base_lx:1; ++ u8 e1000_base_sx:1; ++ ++ u8 fc_ll_v:1; ++ u8 fc_ll_s:1; ++ u8 fc_ll_i:1; ++ u8 fc_ll_l:1; ++ u8 fc_ll_m:1; ++ u8 fc_tech_sa:1; ++ u8 fc_tech_lc:1; ++ u8 fc_tech_electrical_inter_enclosure:1; ++ ++ u8 fc_tech_electrical_intra_enclosure:1; ++ u8 fc_tech_sn:1; ++ u8 fc_tech_sl:1; ++ u8 fc_tech_ll:1; ++ u8 sfp_ct_active:1; ++ u8 sfp_ct_passive:1; ++ u8 unallocated_8_1:1; ++ u8 unallocated_8_0:1; ++ ++ u8 fc_media_tw:1; ++ u8 fc_media_tp:1; ++ u8 fc_media_mi:1; ++ u8 fc_media_tv:1; ++ u8 fc_media_m6:1; ++ u8 fc_media_m5:1; ++ u8 unallocated_9_1:1; ++ u8 fc_media_sm:1; ++ ++ u8 fc_speed_1200:1; ++ u8 fc_speed_800:1; ++ u8 fc_speed_1600:1; ++ u8 fc_speed_400:1; ++ u8 fc_speed_3200:1; ++ u8 fc_speed_200:1; ++ u8 unallocated_10_1:1; ++ u8 fc_speed_100:1; ++#elif defined __LITTLE_ENDIAN_BITFIELD ++ u8 if_1x_copper_passive:1; ++ u8 if_1x_copper_active:1; ++ u8 if_1x_lx:1; ++ u8 if_1x_sx:1; ++ u8 e10g_base_sr:1; ++ u8 e10g_base_lr:1; ++ u8 e10g_base_lrm:1; ++ u8 e10g_base_er:1; ++ ++ u8 sonet_oc3_short_reach:1; ++ u8 sonet_oc3_smf_intermediate_reach:1; ++ u8 sonet_oc3_smf_long_reach:1; ++ u8 unallocated_5_3:1; ++ u8 sonet_oc12_short_reach:1; ++ u8 sonet_oc12_smf_intermediate_reach:1; ++ u8 sonet_oc12_smf_long_reach:1; ++ u8 unallocated_5_7:1; ++ ++ u8 sonet_oc48_short_reach:1; ++ u8 sonet_oc48_intermediate_reach:1; ++ u8 sonet_oc48_long_reach:1; ++ u8 sonet_reach_bit2:1; ++ u8 sonet_reach_bit1:1; ++ u8 sonet_oc192_short_reach:1; ++ u8 escon_smf_1310_laser:1; ++ u8 escon_mmf_1310_led:1; ++ ++ u8 e1000_base_sx:1; ++ u8 e1000_base_lx:1; ++ u8 e1000_base_cx:1; ++ u8 e1000_base_t:1; ++ u8 e100_base_lx:1; ++ u8 e100_base_fx:1; ++ u8 e_base_bx10:1; ++ u8 e_base_px:1; ++ ++ u8 fc_tech_electrical_inter_enclosure:1; ++ u8 fc_tech_lc:1; ++ u8 fc_tech_sa:1; ++ u8 fc_ll_m:1; ++ u8 fc_ll_l:1; ++ u8 fc_ll_i:1; ++ u8 fc_ll_s:1; ++ u8 fc_ll_v:1; ++ ++ u8 unallocated_8_0:1; ++ u8 unallocated_8_1:1; ++ u8 sfp_ct_passive:1; ++ u8 sfp_ct_active:1; ++ u8 fc_tech_ll:1; ++ u8 fc_tech_sl:1; ++ u8 fc_tech_sn:1; ++ u8 fc_tech_electrical_intra_enclosure:1; ++ ++ u8 fc_media_sm:1; ++ u8 unallocated_9_1:1; ++ u8 fc_media_m5:1; ++ u8 fc_media_m6:1; ++ u8 fc_media_tv:1; ++ u8 fc_media_mi:1; ++ u8 fc_media_tp:1; ++ u8 fc_media_tw:1; ++ ++ u8 fc_speed_100:1; ++ u8 unallocated_10_1:1; ++ u8 fc_speed_200:1; ++ u8 fc_speed_3200:1; ++ u8 fc_speed_400:1; ++ u8 fc_speed_1600:1; ++ u8 fc_speed_800:1; ++ u8 fc_speed_1200:1; ++#else ++#error Unknown Endian ++#endif ++ u8 encoding; ++ u8 br_nominal; ++ u8 rate_id; ++ u8 link_len[6]; ++ char vendor_name[16]; ++ u8 reserved36; ++ char vendor_oui[3]; ++ char vendor_pn[16]; ++ char vendor_rev[4]; ++ union { ++ __be16 optical_wavelength; ++ u8 cable_spec; ++ }; ++ u8 reserved62; ++ u8 cc_base; ++}; ++ ++struct __packed sfp_eeprom_ext { ++ __be16 options; ++ u8 br_max; ++ u8 br_min; ++ char vendor_sn[16]; ++ char datecode[8]; ++ u8 diagmon; ++ u8 enhopts; ++ u8 sff8472_compliance; ++ u8 cc_ext; ++}; ++ ++struct __packed sfp_eeprom_id { ++ struct sfp_eeprom_base base; ++ struct sfp_eeprom_ext ext; ++}; ++ ++/* SFP EEPROM registers */ ++enum { ++ SFP_PHYS_ID = 0x00, ++ SFP_PHYS_EXT_ID = 0x01, ++ SFP_CONNECTOR = 0x02, ++ SFP_COMPLIANCE = 0x03, ++ SFP_ENCODING = 0x0b, ++ SFP_BR_NOMINAL = 0x0c, ++ SFP_RATE_ID = 0x0d, ++ SFP_LINK_LEN_SM_KM = 0x0e, ++ SFP_LINK_LEN_SM_100M = 0x0f, ++ SFP_LINK_LEN_50UM_OM2_10M = 0x10, ++ SFP_LINK_LEN_62_5UM_OM1_10M = 0x11, ++ SFP_LINK_LEN_COPPER_1M = 0x12, ++ SFP_LINK_LEN_50UM_OM4_10M = 0x12, ++ SFP_LINK_LEN_50UM_OM3_10M = 0x13, ++ SFP_VENDOR_NAME = 0x14, ++ SFP_VENDOR_OUI = 0x25, ++ SFP_VENDOR_PN = 0x28, ++ SFP_VENDOR_REV = 0x38, ++ SFP_OPTICAL_WAVELENGTH_MSB = 0x3c, ++ SFP_OPTICAL_WAVELENGTH_LSB = 0x3d, ++ SFP_CABLE_SPEC = 0x3c, ++ SFP_CC_BASE = 0x3f, ++ SFP_OPTIONS = 0x40, /* 2 bytes, MSB, LSB */ ++ SFP_BR_MAX = 0x42, ++ SFP_BR_MIN = 0x43, ++ SFP_VENDOR_SN = 0x44, ++ SFP_DATECODE = 0x54, ++ SFP_DIAGMON = 0x5c, ++ SFP_ENHOPTS = 0x5d, ++ SFP_SFF8472_COMPLIANCE = 0x5e, ++ SFP_CC_EXT = 0x5f, ++ ++ SFP_PHYS_ID_SFP = 0x03, ++ SFP_PHYS_EXT_ID_SFP = 0x04, ++ SFP_CONNECTOR_UNSPEC = 0x00, ++ /* codes 01-05 not supportable on SFP, but some modules have single SC */ ++ SFP_CONNECTOR_SC = 0x01, ++ SFP_CONNECTOR_FIBERJACK = 0x06, ++ SFP_CONNECTOR_LC = 0x07, ++ SFP_CONNECTOR_MT_RJ = 0x08, ++ SFP_CONNECTOR_MU = 0x09, ++ SFP_CONNECTOR_SG = 0x0a, ++ SFP_CONNECTOR_OPTICAL_PIGTAIL = 0x0b, ++ SFP_CONNECTOR_HSSDC_II = 0x20, ++ SFP_CONNECTOR_COPPER_PIGTAIL = 0x21, ++ SFP_ENCODING_UNSPEC = 0x00, ++ SFP_ENCODING_8B10B = 0x01, ++ SFP_ENCODING_4B5B = 0x02, ++ SFP_ENCODING_NRZ = 0x03, ++ SFP_ENCODING_MANCHESTER = 0x04, ++ SFP_OPTIONS_HIGH_POWER_LEVEL = BIT(13), ++ SFP_OPTIONS_PAGING_A2 = BIT(12), ++ SFP_OPTIONS_RETIMER = BIT(11), ++ SFP_OPTIONS_COOLED_XCVR = BIT(10), ++ SFP_OPTIONS_POWER_DECL = BIT(9), ++ SFP_OPTIONS_RX_LINEAR_OUT = BIT(8), ++ SFP_OPTIONS_RX_DECISION_THRESH = BIT(7), ++ SFP_OPTIONS_TUNABLE_TX = BIT(6), ++ SFP_OPTIONS_RATE_SELECT = BIT(5), ++ SFP_OPTIONS_TX_DISABLE = BIT(4), ++ SFP_OPTIONS_TX_FAULT = BIT(3), ++ SFP_OPTIONS_LOS_INVERTED = BIT(2), ++ SFP_OPTIONS_LOS_NORMAL = BIT(1), ++ SFP_DIAGMON_DDM = BIT(6), ++ SFP_DIAGMON_INT_CAL = BIT(5), ++ SFP_DIAGMON_EXT_CAL = BIT(4), ++ SFP_DIAGMON_RXPWR_AVG = BIT(3), ++ SFP_DIAGMON_ADDRMODE = BIT(2), ++ SFP_ENHOPTS_ALARMWARN = BIT(7), ++ SFP_ENHOPTS_SOFT_TX_DISABLE = BIT(6), ++ SFP_ENHOPTS_SOFT_TX_FAULT = BIT(5), ++ SFP_ENHOPTS_SOFT_RX_LOS = BIT(4), ++ SFP_ENHOPTS_SOFT_RATE_SELECT = BIT(3), ++ SFP_ENHOPTS_APP_SELECT_SFF8079 = BIT(2), ++ SFP_ENHOPTS_SOFT_RATE_SFF8431 = BIT(1), ++ SFP_SFF8472_COMPLIANCE_NONE = 0x00, ++ SFP_SFF8472_COMPLIANCE_REV9_3 = 0x01, ++ SFP_SFF8472_COMPLIANCE_REV9_5 = 0x02, ++ SFP_SFF8472_COMPLIANCE_REV10_2 = 0x03, ++ SFP_SFF8472_COMPLIANCE_REV10_4 = 0x04, ++ SFP_SFF8472_COMPLIANCE_REV11_0 = 0x05, ++ SFP_SFF8472_COMPLIANCE_REV11_3 = 0x06, ++ SFP_SFF8472_COMPLIANCE_REV11_4 = 0x07, ++ SFP_SFF8472_COMPLIANCE_REV12_0 = 0x08, ++}; ++ ++/* SFP Diagnostics */ ++enum { ++ /* Alarm and warnings stored MSB at lower address then LSB */ ++ SFP_TEMP_HIGH_ALARM = 0x00, ++ SFP_TEMP_LOW_ALARM = 0x02, ++ SFP_TEMP_HIGH_WARN = 0x04, ++ SFP_TEMP_LOW_WARN = 0x06, ++ SFP_VOLT_HIGH_ALARM = 0x08, ++ SFP_VOLT_LOW_ALARM = 0x0a, ++ SFP_VOLT_HIGH_WARN = 0x0c, ++ SFP_VOLT_LOW_WARN = 0x0e, ++ SFP_BIAS_HIGH_ALARM = 0x10, ++ SFP_BIAS_LOW_ALARM = 0x12, ++ SFP_BIAS_HIGH_WARN = 0x14, ++ SFP_BIAS_LOW_WARN = 0x16, ++ SFP_TXPWR_HIGH_ALARM = 0x18, ++ SFP_TXPWR_LOW_ALARM = 0x1a, ++ SFP_TXPWR_HIGH_WARN = 0x1c, ++ SFP_TXPWR_LOW_WARN = 0x1e, ++ SFP_RXPWR_HIGH_ALARM = 0x20, ++ SFP_RXPWR_LOW_ALARM = 0x22, ++ SFP_RXPWR_HIGH_WARN = 0x24, ++ SFP_RXPWR_LOW_WARN = 0x26, ++ SFP_LASER_TEMP_HIGH_ALARM = 0x28, ++ SFP_LASER_TEMP_LOW_ALARM = 0x2a, ++ SFP_LASER_TEMP_HIGH_WARN = 0x2c, ++ SFP_LASER_TEMP_LOW_WARN = 0x2e, ++ SFP_TEC_CUR_HIGH_ALARM = 0x30, ++ SFP_TEC_CUR_LOW_ALARM = 0x32, ++ SFP_TEC_CUR_HIGH_WARN = 0x34, ++ SFP_TEC_CUR_LOW_WARN = 0x36, ++ SFP_CAL_RXPWR4 = 0x38, ++ SFP_CAL_RXPWR3 = 0x3c, ++ SFP_CAL_RXPWR2 = 0x40, ++ SFP_CAL_RXPWR1 = 0x44, ++ SFP_CAL_RXPWR0 = 0x48, ++ SFP_CAL_TXI_SLOPE = 0x4c, ++ SFP_CAL_TXI_OFFSET = 0x4e, ++ SFP_CAL_TXPWR_SLOPE = 0x50, ++ SFP_CAL_TXPWR_OFFSET = 0x52, ++ SFP_CAL_T_SLOPE = 0x54, ++ SFP_CAL_T_OFFSET = 0x56, ++ SFP_CAL_V_SLOPE = 0x58, ++ SFP_CAL_V_OFFSET = 0x5a, ++ SFP_CHKSUM = 0x5f, ++ ++ SFP_TEMP = 0x60, ++ SFP_VCC = 0x62, ++ SFP_TX_BIAS = 0x64, ++ SFP_TX_POWER = 0x66, ++ SFP_RX_POWER = 0x68, ++ SFP_LASER_TEMP = 0x6a, ++ SFP_TEC_CUR = 0x6c, ++ ++ SFP_STATUS = 0x6e, ++ SFP_ALARM = 0x70, ++ ++ SFP_EXT_STATUS = 0x76, ++ SFP_VSL = 0x78, ++ SFP_PAGE = 0x7f, ++}; ++ ++#endif diff --git a/target/linux/mvebu/patches-4.4/133-sfp-display-SFP-module-information.patch b/target/linux/mvebu/patches-4.4/133-sfp-display-SFP-module-information.patch new file mode 100644 index 0000000000..bc039ee199 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/133-sfp-display-SFP-module-information.patch @@ -0,0 +1,283 @@ +From 66c248886538d7ee97ef2fe498061f857d4c906a Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sun, 13 Sep 2015 01:06:31 +0100 +Subject: [PATCH 721/744] sfp: display SFP module information + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/sfp.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 246 insertions(+), 1 deletion(-) + +--- a/drivers/net/phy/sfp.c ++++ b/drivers/net/phy/sfp.c +@@ -248,6 +248,182 @@ static unsigned int sfp_check(void *buf, + return check; + } + ++static const char *sfp_link_len(char *buf, size_t size, unsigned int length, ++ unsigned int multiplier) ++{ ++ if (length == 0) ++ return "unsupported/unspecified"; ++ ++ if (length == 255) { ++ *buf++ = '>'; ++ size -= 1; ++ length -= 1; ++ } ++ ++ length *= multiplier; ++ ++ if (length >= 1000) ++ snprintf(buf, size, "%u.%0*ukm", ++ length / 1000, ++ multiplier > 100 ? 1 : ++ multiplier > 10 ? 2 : 3, ++ length % 1000); ++ else ++ snprintf(buf, size, "%um", length); ++ ++ return buf; ++} ++ ++struct bitfield { ++ unsigned int mask; ++ unsigned int val; ++ const char *str; ++}; ++ ++static const struct bitfield sfp_options[] = { ++ { ++ .mask = SFP_OPTIONS_HIGH_POWER_LEVEL, ++ .val = SFP_OPTIONS_HIGH_POWER_LEVEL, ++ .str = "hpl", ++ }, { ++ .mask = SFP_OPTIONS_PAGING_A2, ++ .val = SFP_OPTIONS_PAGING_A2, ++ .str = "paginga2", ++ }, { ++ .mask = SFP_OPTIONS_RETIMER, ++ .val = SFP_OPTIONS_RETIMER, ++ .str = "retimer", ++ }, { ++ .mask = SFP_OPTIONS_COOLED_XCVR, ++ .val = SFP_OPTIONS_COOLED_XCVR, ++ .str = "cooled", ++ }, { ++ .mask = SFP_OPTIONS_POWER_DECL, ++ .val = SFP_OPTIONS_POWER_DECL, ++ .str = "powerdecl", ++ }, { ++ .mask = SFP_OPTIONS_RX_LINEAR_OUT, ++ .val = SFP_OPTIONS_RX_LINEAR_OUT, ++ .str = "rxlinear", ++ }, { ++ .mask = SFP_OPTIONS_RX_DECISION_THRESH, ++ .val = SFP_OPTIONS_RX_DECISION_THRESH, ++ .str = "rxthresh", ++ }, { ++ .mask = SFP_OPTIONS_TUNABLE_TX, ++ .val = SFP_OPTIONS_TUNABLE_TX, ++ .str = "tunabletx", ++ }, { ++ .mask = SFP_OPTIONS_RATE_SELECT, ++ .val = SFP_OPTIONS_RATE_SELECT, ++ .str = "ratesel", ++ }, { ++ .mask = SFP_OPTIONS_TX_DISABLE, ++ .val = SFP_OPTIONS_TX_DISABLE, ++ .str = "txdisable", ++ }, { ++ .mask = SFP_OPTIONS_TX_FAULT, ++ .val = SFP_OPTIONS_TX_FAULT, ++ .str = "txfault", ++ }, { ++ .mask = SFP_OPTIONS_LOS_INVERTED, ++ .val = SFP_OPTIONS_LOS_INVERTED, ++ .str = "los-", ++ }, { ++ .mask = SFP_OPTIONS_LOS_NORMAL, ++ .val = SFP_OPTIONS_LOS_NORMAL, ++ .str = "los+", ++ }, { } ++}; ++ ++static const struct bitfield diagmon[] = { ++ { ++ .mask = SFP_DIAGMON_DDM, ++ .val = SFP_DIAGMON_DDM, ++ .str = "ddm", ++ }, { ++ .mask = SFP_DIAGMON_INT_CAL, ++ .val = SFP_DIAGMON_INT_CAL, ++ .str = "intcal", ++ }, { ++ .mask = SFP_DIAGMON_EXT_CAL, ++ .val = SFP_DIAGMON_EXT_CAL, ++ .str = "extcal", ++ }, { ++ .mask = SFP_DIAGMON_RXPWR_AVG, ++ .val = SFP_DIAGMON_RXPWR_AVG, ++ .str = "rxpwravg", ++ }, { } ++}; ++ ++static const char *sfp_bitfield(char *out, size_t outsz, const struct bitfield *bits, unsigned int val) ++{ ++ char *p = out; ++ int n; ++ ++ *p = '\0'; ++ while (bits->mask) { ++ if ((val & bits->mask) == bits->val) { ++ n = snprintf(p, outsz, "%s%s", ++ out != p ? ", " : "", ++ bits->str); ++ if (n == outsz) ++ break; ++ p += n; ++ outsz -= n; ++ } ++ bits++; ++ } ++ ++ return out; ++} ++ ++static const char *sfp_connector(unsigned int connector) ++{ ++ switch (connector) { ++ case SFP_CONNECTOR_UNSPEC: ++ return "unknown/unspecified"; ++ case SFP_CONNECTOR_SC: ++ return "SC"; ++ case SFP_CONNECTOR_FIBERJACK: ++ return "Fiberjack"; ++ case SFP_CONNECTOR_LC: ++ return "LC"; ++ case SFP_CONNECTOR_MT_RJ: ++ return "MT-RJ"; ++ case SFP_CONNECTOR_MU: ++ return "MU"; ++ case SFP_CONNECTOR_SG: ++ return "SG"; ++ case SFP_CONNECTOR_OPTICAL_PIGTAIL: ++ return "Optical pigtail"; ++ case SFP_CONNECTOR_HSSDC_II: ++ return "HSSDC II"; ++ case SFP_CONNECTOR_COPPER_PIGTAIL: ++ return "Copper pigtail"; ++ default: ++ return "unknown"; ++ } ++} ++ ++static const char *sfp_encoding(unsigned int encoding) ++{ ++ switch (encoding) { ++ case SFP_ENCODING_UNSPEC: ++ return "unspecified"; ++ case SFP_ENCODING_8B10B: ++ return "8b10b"; ++ case SFP_ENCODING_4B5B: ++ return "4b5b"; ++ case SFP_ENCODING_NRZ: ++ return "NRZ"; ++ case SFP_ENCODING_MANCHESTER: ++ return "MANCHESTER"; ++ default: ++ return "unknown"; ++ } ++} ++ + /* Helpers */ + static void sfp_module_tx_disable(struct sfp *sfp) + { +@@ -426,6 +602,7 @@ static int sfp_sm_mod_probe(struct sfp * + char sn[17]; + char date[9]; + char rev[5]; ++ char options[80]; + u8 check; + int err; + +@@ -462,10 +639,78 @@ static int sfp_sm_mod_probe(struct sfp * + rev[4] = '\0'; + memcpy(sn, sfp->id.ext.vendor_sn, 16); + sn[16] = '\0'; +- memcpy(date, sfp->id.ext.datecode, 8); ++ date[0] = sfp->id.ext.datecode[4]; ++ date[1] = sfp->id.ext.datecode[5]; ++ date[2] = '-'; ++ date[3] = sfp->id.ext.datecode[2]; ++ date[4] = sfp->id.ext.datecode[3]; ++ date[5] = '-'; ++ date[6] = sfp->id.ext.datecode[0]; ++ date[7] = sfp->id.ext.datecode[1]; + date[8] = '\0'; + + dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date); ++ dev_info(sfp->dev, " %s connector, encoding %s, nominal bitrate %u.%uGbps +%u%% -%u%%\n", ++ sfp_connector(sfp->id.base.connector), ++ sfp_encoding(sfp->id.base.encoding), ++ sfp->id.base.br_nominal / 10, ++ sfp->id.base.br_nominal % 10, ++ sfp->id.ext.br_max, sfp->id.ext.br_min); ++ dev_info(sfp->dev, " 1000BaseSX%c 1000BaseLX%c 1000BaseCX%c 1000BaseT%c 100BaseTLX%c 1000BaseFX%c BaseBX10%c BasePX%c\n", ++ sfp->id.base.e1000_base_sx ? '+' : '-', ++ sfp->id.base.e1000_base_lx ? '+' : '-', ++ sfp->id.base.e1000_base_cx ? '+' : '-', ++ sfp->id.base.e1000_base_t ? '+' : '-', ++ sfp->id.base.e100_base_lx ? '+' : '-', ++ sfp->id.base.e100_base_fx ? '+' : '-', ++ sfp->id.base.e_base_bx10 ? '+' : '-', ++ sfp->id.base.e_base_px ? '+' : '-'); ++ ++ if (!sfp->id.base.sfp_ct_passive && !sfp->id.base.sfp_ct_active && ++ !sfp->id.base.e1000_base_t) { ++ char len_9um[16], len_om[16]; ++ ++ dev_info(sfp->dev, " Wavelength %unm, fiber lengths:\n", ++ be16_to_cpup(&sfp->id.base.optical_wavelength)); ++ ++ if (sfp->id.base.link_len[0] == 255) ++ strcpy(len_9um, ">254km"); ++ else if (sfp->id.base.link_len[1] && sfp->id.base.link_len[1] != 255) ++ sprintf(len_9um, "%um", ++ sfp->id.base.link_len[1] * 100); ++ else if (sfp->id.base.link_len[0]) ++ sprintf(len_9um, "%ukm", sfp->id.base.link_len[0]); ++ else if (sfp->id.base.link_len[1] == 255) ++ strcpy(len_9um, ">25.4km"); ++ else ++ strcpy(len_9um, "unsupported"); ++ ++ dev_info(sfp->dev, " 9µm SM : %s\n", len_9um); ++ dev_info(sfp->dev, " 62.5µm MM OM1: %s\n", ++ sfp_link_len(len_om, sizeof(len_om), ++ sfp->id.base.link_len[3], 10)); ++ dev_info(sfp->dev, " 50µm MM OM2: %s\n", ++ sfp_link_len(len_om, sizeof(len_om), ++ sfp->id.base.link_len[2], 10)); ++ dev_info(sfp->dev, " 50µm MM OM3: %s\n", ++ sfp_link_len(len_om, sizeof(len_om), ++ sfp->id.base.link_len[5], 10)); ++ dev_info(sfp->dev, " 50µm MM OM4: %s\n", ++ sfp_link_len(len_om, sizeof(len_om), ++ sfp->id.base.link_len[4], 10)); ++ } else { ++ char len[16]; ++ dev_info(sfp->dev, " Copper length: %s\n", ++ sfp_link_len(len, sizeof(len), ++ sfp->id.base.link_len[4], 1)); ++ } ++ ++ dev_info(sfp->dev, " Options: %s\n", ++ sfp_bitfield(options, sizeof(options), sfp_options, ++ be16_to_cpu(sfp->id.ext.options))); ++ dev_info(sfp->dev, " Diagnostics: %s\n", ++ sfp_bitfield(options, sizeof(options), diagmon, ++ sfp->id.ext.diagmon)); + + /* We only support SFP modules, not the legacy GBIC modules. */ + if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP || diff --git a/target/linux/mvebu/patches-4.4/134-net-mvneta-convert-to-phylink.patch b/target/linux/mvebu/patches-4.4/134-net-mvneta-convert-to-phylink.patch new file mode 100644 index 0000000000..fac283f3ee --- /dev/null +++ b/target/linux/mvebu/patches-4.4/134-net-mvneta-convert-to-phylink.patch @@ -0,0 +1,697 @@ +From e268be0ddc666f4a98db462cbed2a97637e82b5c Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Wed, 16 Sep 2015 21:27:10 +0100 +Subject: [PATCH 722/744] net: mvneta: convert to phylink + +Convert mvneta to use phylink, which models the MAC to PHY link in +a generic, reusable form. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/ethernet/marvell/Kconfig | 2 +- + drivers/net/ethernet/marvell/mvneta.c | 451 +++++++++++++++++----------------- + 2 files changed, 227 insertions(+), 226 deletions(-) + +--- a/drivers/net/ethernet/marvell/Kconfig ++++ b/drivers/net/ethernet/marvell/Kconfig +@@ -58,7 +58,7 @@ config MVNETA + tristate "Marvell Armada 370/38x/XP network interface support" + depends on PLAT_ORION + select MVMDIO +- select FIXED_PHY ++ select PHYLINK + ---help--- + This driver supports the network interface units in the + Marvell ARMADA XP, ARMADA 370 and ARMADA 38x SoC family. +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -28,6 +28,7 @@ + #include <linux/of_mdio.h> + #include <linux/of_net.h> + #include <linux/phy.h> ++#include <linux/phylink.h> + #include <linux/platform_device.h> + #include <linux/skbuff.h> + #include <net/hwbm.h> +@@ -188,6 +189,7 @@ + #define MVNETA_GMAC_CTRL_0 0x2c00 + #define MVNETA_GMAC_MAX_RX_SIZE_SHIFT 2 + #define MVNETA_GMAC_MAX_RX_SIZE_MASK 0x7ffc ++#define MVNETA_GMAC0_PORT_1000BASE_X BIT(1) + #define MVNETA_GMAC0_PORT_ENABLE BIT(0) + #define MVNETA_GMAC_CTRL_2 0x2c08 + #define MVNETA_GMAC2_INBAND_AN_ENABLE BIT(0) +@@ -203,13 +205,19 @@ + #define MVNETA_GMAC_TX_FLOW_CTRL_ENABLE BIT(5) + #define MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE BIT(6) + #define MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE BIT(7) ++#define MVNETA_GMAC_AN_COMPLETE BIT(11) ++#define MVNETA_GMAC_SYNC_OK BIT(14) + #define MVNETA_GMAC_AUTONEG_CONFIG 0x2c0c + #define MVNETA_GMAC_FORCE_LINK_DOWN BIT(0) + #define MVNETA_GMAC_FORCE_LINK_PASS BIT(1) + #define MVNETA_GMAC_INBAND_AN_ENABLE BIT(2) ++#define MVNETA_GMAC_AN_BYPASS_ENABLE BIT(3) ++#define MVNETA_GMAC_INBAND_RESTART_AN BIT(4) + #define MVNETA_GMAC_CONFIG_MII_SPEED BIT(5) + #define MVNETA_GMAC_CONFIG_GMII_SPEED BIT(6) + #define MVNETA_GMAC_AN_SPEED_EN BIT(7) ++#define MVNETA_GMAC_CONFIG_FLOW_CTRL BIT(8) ++#define MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL BIT(9) + #define MVNETA_GMAC_AN_FLOW_CTRL_EN BIT(11) + #define MVNETA_GMAC_CONFIG_FULL_DUPLEX BIT(12) + #define MVNETA_GMAC_AN_DUPLEX_EN BIT(13) +@@ -396,15 +404,9 @@ struct mvneta_port { + u16 tx_ring_size; + u16 rx_ring_size; + +- struct mii_bus *mii_bus; +- struct phy_device *phy_dev; +- phy_interface_t phy_interface; +- struct device_node *phy_node; +- unsigned int link; +- unsigned int duplex; +- unsigned int speed; ++ struct device_node *dn; + unsigned int tx_csum_limit; +- unsigned int use_inband_status:1; ++ struct phylink *phylink; + + struct mvneta_bm *bm_priv; + struct mvneta_bm_pool *pool_long; +@@ -1236,44 +1238,6 @@ static void mvneta_set_other_mcast_table + mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val); + } + +-static void mvneta_set_autoneg(struct mvneta_port *pp, int enable) +-{ +- u32 val; +- +- if (enable) { +- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); +- val &= ~(MVNETA_GMAC_FORCE_LINK_PASS | +- MVNETA_GMAC_FORCE_LINK_DOWN | +- MVNETA_GMAC_AN_FLOW_CTRL_EN); +- val |= MVNETA_GMAC_INBAND_AN_ENABLE | +- MVNETA_GMAC_AN_SPEED_EN | +- MVNETA_GMAC_AN_DUPLEX_EN; +- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); +- +- val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); +- val |= MVNETA_GMAC_1MS_CLOCK_ENABLE; +- mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); +- +- val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); +- val |= MVNETA_GMAC2_INBAND_AN_ENABLE; +- mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); +- } else { +- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); +- val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE | +- MVNETA_GMAC_AN_SPEED_EN | +- MVNETA_GMAC_AN_DUPLEX_EN); +- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); +- +- val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); +- val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE; +- mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); +- +- val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); +- val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE; +- mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); +- } +-} +- + static void mvneta_percpu_unmask_interrupt(void *arg) + { + struct mvneta_port *pp = arg; +@@ -1421,7 +1385,6 @@ static void mvneta_defaults_set(struct m + val &= ~MVNETA_PHY_POLLING_ENABLE; + mvreg_write(pp, MVNETA_UNIT_CONTROL, val); + +- mvneta_set_autoneg(pp, pp->use_inband_status); + mvneta_set_ucast_table(pp, -1); + mvneta_set_special_mcast_table(pp, -1); + mvneta_set_other_mcast_table(pp, -1); +@@ -2614,26 +2577,11 @@ static irqreturn_t mvneta_isr(int irq, v + return IRQ_HANDLED; + } + +-static int mvneta_fixed_link_update(struct mvneta_port *pp, +- struct phy_device *phy) ++static void mvneta_link_change(struct mvneta_port *pp) + { +- struct fixed_phy_status status; +- struct fixed_phy_status changed = {}; + u32 gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS); + +- status.link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); +- if (gmac_stat & MVNETA_GMAC_SPEED_1000) +- status.speed = SPEED_1000; +- else if (gmac_stat & MVNETA_GMAC_SPEED_100) +- status.speed = SPEED_100; +- else +- status.speed = SPEED_10; +- status.duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); +- changed.link = 1; +- changed.speed = 1; +- changed.duplex = 1; +- fixed_phy_update_state(phy, &status, &changed); +- return 0; ++ phylink_mac_change(pp->phylink, !!(gmac_stat & MVNETA_GMAC_LINK_UP)); + } + + /* NAPI handler +@@ -2662,12 +2610,11 @@ static int mvneta_poll(struct napi_struc + u32 cause_misc = mvreg_read(pp, MVNETA_INTR_MISC_CAUSE); + + mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0); +- if (pp->use_inband_status && (cause_misc & +- (MVNETA_CAUSE_PHY_STATUS_CHANGE | +- MVNETA_CAUSE_LINK_CHANGE | +- MVNETA_CAUSE_PSC_SYNC_CHANGE))) { +- mvneta_fixed_link_update(pp, pp->phy_dev); +- } ++ ++ if (cause_misc & (MVNETA_CAUSE_PHY_STATUS_CHANGE | ++ MVNETA_CAUSE_LINK_CHANGE | ++ MVNETA_CAUSE_PSC_SYNC_CHANGE)) ++ mvneta_link_change(pp); + } + + /* Release Tx descriptors */ +@@ -2983,7 +2930,7 @@ static void mvneta_start_dev(struct mvne + MVNETA_CAUSE_LINK_CHANGE | + MVNETA_CAUSE_PSC_SYNC_CHANGE); + +- phy_start(pp->phy_dev); ++ phylink_start(pp->phylink); + netif_tx_start_all_queues(pp->dev); + } + +@@ -2991,7 +2938,7 @@ static void mvneta_stop_dev(struct mvnet + { + unsigned int cpu; + +- phy_stop(pp->phy_dev); ++ phylink_stop(pp->phylink); + + for_each_online_cpu(cpu) { + struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu); +@@ -3161,99 +3108,219 @@ static int mvneta_set_mac_addr(struct ne + return 0; + } + +-static void mvneta_adjust_link(struct net_device *ndev) ++static int mvneta_mac_support(struct net_device *ndev, unsigned int mode, ++ struct phylink_link_state *state) ++{ ++ switch (mode) { ++ case MLO_AN_8023Z: ++ state->supported = SUPPORTED_1000baseT_Full | ++ SUPPORTED_Autoneg | SUPPORTED_Pause; ++ state->advertising = ADVERTISED_1000baseT_Full | ++ ADVERTISED_Autoneg | ADVERTISED_Pause; ++ state->an_enabled = 1; ++ break; ++ ++ case MLO_AN_FIXED: ++ break; ++ ++ default: ++ state->supported = PHY_10BT_FEATURES | ++ PHY_100BT_FEATURES | ++ SUPPORTED_1000baseT_Full | ++ SUPPORTED_Autoneg; ++ state->advertising = ADVERTISED_10baseT_Half | ++ ADVERTISED_10baseT_Full | ++ ADVERTISED_100baseT_Half | ++ ADVERTISED_100baseT_Full | ++ ADVERTISED_1000baseT_Full | ++ ADVERTISED_Autoneg; ++ state->an_enabled = 1; ++ break; ++ } ++ return 0; ++} ++ ++static int mvneta_mac_link_state(struct net_device *ndev, ++ struct phylink_link_state *state) + { + struct mvneta_port *pp = netdev_priv(ndev); +- struct phy_device *phydev = pp->phy_dev; +- int status_change = 0; ++ u32 gmac_stat; + +- if (phydev->link) { +- if ((pp->speed != phydev->speed) || +- (pp->duplex != phydev->duplex)) { +- u32 val; +- +- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); +- val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | +- MVNETA_GMAC_CONFIG_GMII_SPEED | +- MVNETA_GMAC_CONFIG_FULL_DUPLEX); +- +- if (phydev->duplex) +- val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; +- +- if (phydev->speed == SPEED_1000) +- val |= MVNETA_GMAC_CONFIG_GMII_SPEED; +- else if (phydev->speed == SPEED_100) +- val |= MVNETA_GMAC_CONFIG_MII_SPEED; ++ gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS); + +- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); ++ if (gmac_stat & MVNETA_GMAC_SPEED_1000) ++ state->speed = SPEED_1000; ++ else if (gmac_stat & MVNETA_GMAC_SPEED_100) ++ state->speed = SPEED_100; ++ else ++ state->speed = SPEED_10; + +- pp->duplex = phydev->duplex; +- pp->speed = phydev->speed; +- } ++ state->an_complete = !!(gmac_stat & MVNETA_GMAC_AN_COMPLETE); ++ state->sync = !!(gmac_stat & MVNETA_GMAC_SYNC_OK); ++ state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); ++ state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); ++ ++ state->pause = 0; ++ if (gmac_stat & MVNETA_GMAC_RX_FLOW_CTRL_ENABLE) ++ state->pause |= MLO_PAUSE_RX; ++ if (gmac_stat & MVNETA_GMAC_TX_FLOW_CTRL_ENABLE) ++ state->pause |= MLO_PAUSE_TX; ++ ++ return 1; ++} ++ ++static void mvneta_mac_an_restart(struct net_device *ndev, unsigned int mode) ++{ ++ struct mvneta_port *pp = netdev_priv(ndev); ++ ++ if (mode == MLO_AN_8023Z) { ++ u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); ++ ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, ++ gmac_an | MVNETA_GMAC_INBAND_RESTART_AN); ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, ++ gmac_an & ~MVNETA_GMAC_INBAND_RESTART_AN); + } ++} + +- if (phydev->link != pp->link) { +- if (!phydev->link) { +- pp->duplex = -1; +- pp->speed = 0; +- } ++static void mvneta_mac_config(struct net_device *ndev, unsigned int mode, ++ const struct phylink_link_state *state) ++{ ++ struct mvneta_port *pp = netdev_priv(ndev); ++ u32 new_ctrl0, gmac_ctrl0 = mvreg_read(pp, MVNETA_GMAC_CTRL_0); ++ u32 new_ctrl2, gmac_ctrl2 = mvreg_read(pp, MVNETA_GMAC_CTRL_2); ++ u32 new_clk, gmac_clk = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); ++ u32 new_an, gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); ++ ++ new_ctrl0 = gmac_ctrl0 & ~MVNETA_GMAC0_PORT_1000BASE_X; ++ new_ctrl2 = gmac_ctrl2 & ~MVNETA_GMAC2_INBAND_AN_ENABLE; ++ new_clk = gmac_clk & ~MVNETA_GMAC_1MS_CLOCK_ENABLE; ++ new_an = gmac_an & ~(MVNETA_GMAC_INBAND_AN_ENABLE | ++ MVNETA_GMAC_INBAND_RESTART_AN | ++ MVNETA_GMAC_CONFIG_MII_SPEED | ++ MVNETA_GMAC_CONFIG_GMII_SPEED | ++ MVNETA_GMAC_AN_SPEED_EN | ++ MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL | ++ MVNETA_GMAC_CONFIG_FLOW_CTRL | ++ MVNETA_GMAC_AN_FLOW_CTRL_EN | ++ MVNETA_GMAC_CONFIG_FULL_DUPLEX | ++ MVNETA_GMAC_AN_DUPLEX_EN); ++ ++ if (state->advertising & ADVERTISED_Pause) ++ new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL; ++ ++ switch (mode) { ++ case MLO_AN_SGMII: ++ /* SGMII mode receives the state from the PHY */ ++ new_ctrl2 |= MVNETA_GMAC2_INBAND_AN_ENABLE; ++ new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE; ++ new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN | ++ MVNETA_GMAC_FORCE_LINK_PASS)) | ++ MVNETA_GMAC_INBAND_AN_ENABLE | ++ MVNETA_GMAC_AN_SPEED_EN | ++ MVNETA_GMAC_AN_DUPLEX_EN; ++ break; ++ ++ case MLO_AN_8023Z: ++ /* 802.3z negotiation - only 1000base-X */ ++ new_ctrl0 |= MVNETA_GMAC0_PORT_1000BASE_X; ++ new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE; ++ new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN | ++ MVNETA_GMAC_FORCE_LINK_PASS)) | ++ MVNETA_GMAC_INBAND_AN_ENABLE | ++ MVNETA_GMAC_CONFIG_GMII_SPEED | ++ /* The MAC only supports FD mode */ ++ MVNETA_GMAC_CONFIG_FULL_DUPLEX; ++ ++ if (state->an_enabled) ++ new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN; ++ break; + +- pp->link = phydev->link; +- status_change = 1; ++ default: ++ /* Phy or fixed speed */ ++ if (state->duplex) ++ new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; ++ ++ if (state->speed == SPEED_1000) ++ new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED; ++ else if (state->speed == SPEED_100) ++ new_an |= MVNETA_GMAC_CONFIG_MII_SPEED; ++ break; + } + +- if (status_change) { +- if (phydev->link) { +- if (!pp->use_inband_status) { +- u32 val = mvreg_read(pp, +- MVNETA_GMAC_AUTONEG_CONFIG); +- val &= ~MVNETA_GMAC_FORCE_LINK_DOWN; +- val |= MVNETA_GMAC_FORCE_LINK_PASS; +- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, +- val); +- } +- mvneta_port_up(pp); +- } else { +- if (!pp->use_inband_status) { +- u32 val = mvreg_read(pp, +- MVNETA_GMAC_AUTONEG_CONFIG); +- val &= ~MVNETA_GMAC_FORCE_LINK_PASS; +- val |= MVNETA_GMAC_FORCE_LINK_DOWN; +- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, +- val); +- } +- mvneta_port_down(pp); +- } +- phy_print_status(phydev); ++ /* Armada 370 documentation says we can only change the port mode ++ * and in-band enable when the link is down, so force it down ++ * while making these changes. We also do this for GMAC_CTRL2 */ ++ if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X || ++ (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE || ++ (new_an ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) { ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, ++ (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) | ++ MVNETA_GMAC_FORCE_LINK_DOWN); ++ } ++ ++ if (new_ctrl0 != gmac_ctrl0) ++ mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0); ++ if (new_ctrl2 != gmac_ctrl2) ++ mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2); ++ if (new_clk != gmac_clk) ++ mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk); ++ if (new_an != gmac_an) ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an); ++} ++ ++static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode) ++{ ++ struct mvneta_port *pp = netdev_priv(ndev); ++ u32 val; ++ ++ mvneta_port_down(pp); ++ ++ if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) { ++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); ++ val &= ~MVNETA_GMAC_FORCE_LINK_PASS; ++ val |= MVNETA_GMAC_FORCE_LINK_DOWN; ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); + } + } + +-static int mvneta_mdio_probe(struct mvneta_port *pp) ++static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode, ++ struct phy_device *phy) + { +- struct phy_device *phy_dev; ++ struct mvneta_port *pp = netdev_priv(ndev); ++ u32 val; ++ ++ if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) { ++ val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); ++ val &= ~MVNETA_GMAC_FORCE_LINK_DOWN; ++ val |= MVNETA_GMAC_FORCE_LINK_PASS; ++ mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); ++ } + +- phy_dev = of_phy_connect(pp->dev, pp->phy_node, mvneta_adjust_link, 0, +- pp->phy_interface); +- if (!phy_dev) { +- netdev_err(pp->dev, "could not find the PHY\n"); +- return -ENODEV; +- } +- +- phy_dev->supported &= PHY_GBIT_FEATURES; +- phy_dev->advertising = phy_dev->supported; +- +- pp->phy_dev = phy_dev; +- pp->link = 0; +- pp->duplex = 0; +- pp->speed = 0; ++ mvneta_port_up(pp); ++} + +- return 0; ++static const struct phylink_mac_ops mvneta_phylink_ops = { ++ .mac_get_support = mvneta_mac_support, ++ .mac_link_state = mvneta_mac_link_state, ++ .mac_an_restart = mvneta_mac_an_restart, ++ .mac_config = mvneta_mac_config, ++ .mac_link_down = mvneta_mac_link_down, ++ .mac_link_up = mvneta_mac_link_up, ++}; ++ ++static int mvneta_mdio_probe(struct mvneta_port *pp) ++{ ++ int err = phylink_of_phy_connect(pp->phylink, pp->dn); ++ if (err) ++ netdev_err(pp->dev, "could not attach PHY\n"); ++ ++ return err; + } + + static void mvneta_mdio_remove(struct mvneta_port *pp) + { +- phy_disconnect(pp->phy_dev); +- pp->phy_dev = NULL; ++ phylink_disconnect_phy(pp->phylink); + } + + /* Electing a CPU must be done in an atomic way: it should be done +@@ -3501,10 +3568,7 @@ static int mvneta_ioctl(struct net_devic + { + struct mvneta_port *pp = netdev_priv(dev); + +- if (!pp->phy_dev) +- return -ENOTSUPP; +- +- return phy_mii_ioctl(pp->phy_dev, ifr, cmd); ++ return phylink_mii_ioctl(pp->phylink, ifr, cmd); + } + + /* Ethtool methods */ +@@ -3514,54 +3578,15 @@ int mvneta_ethtool_get_settings(struct n + { + struct mvneta_port *pp = netdev_priv(dev); + +- if (!pp->phy_dev) +- return -ENODEV; +- +- return phy_ethtool_gset(pp->phy_dev, cmd); ++ return phylink_ethtool_get_settings(pp->phylink, cmd); + } + + /* Set settings (phy address, speed) for ethtools */ + int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) + { + struct mvneta_port *pp = netdev_priv(dev); +- struct phy_device *phydev = pp->phy_dev; +- +- if (!phydev) +- return -ENODEV; + +- if ((cmd->autoneg == AUTONEG_ENABLE) != pp->use_inband_status) { +- u32 val; +- +- mvneta_set_autoneg(pp, cmd->autoneg == AUTONEG_ENABLE); +- +- if (cmd->autoneg == AUTONEG_DISABLE) { +- val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); +- val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | +- MVNETA_GMAC_CONFIG_GMII_SPEED | +- MVNETA_GMAC_CONFIG_FULL_DUPLEX); +- +- if (phydev->duplex) +- val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; +- +- if (phydev->speed == SPEED_1000) +- val |= MVNETA_GMAC_CONFIG_GMII_SPEED; +- else if (phydev->speed == SPEED_100) +- val |= MVNETA_GMAC_CONFIG_MII_SPEED; +- +- mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); +- } +- +- pp->use_inband_status = (cmd->autoneg == AUTONEG_ENABLE); +- netdev_info(pp->dev, "autoneg status set to %i\n", +- pp->use_inband_status); +- +- if (netif_running(dev)) { +- mvneta_port_down(pp); +- mvneta_port_up(pp); +- } +- } +- +- return phy_ethtool_sset(pp->phy_dev, cmd); ++ return phylink_ethtool_set_settings(pp->phylink, cmd); + } + + /* Set interrupt coalescing for ethtools */ +@@ -3669,7 +3694,8 @@ static void mvneta_ethtool_update_stats( + { + const struct mvneta_statistic *s; + void __iomem *base = pp->base; +- u32 high, low, val; ++ u32 high, low; ++ u64 val; + u64 val64; + int i; + +@@ -3964,14 +3990,13 @@ static int mvneta_probe(struct platform_ + const struct mbus_dram_target_info *dram_target_info; + struct resource *res; + struct device_node *dn = pdev->dev.of_node; +- struct device_node *phy_node; + struct device_node *bm_node; + struct mvneta_port *pp; + struct net_device *dev; ++ struct phylink *phylink; + const char *dt_mac_addr; + char hw_mac_addr[ETH_ALEN]; + const char *mac_from; +- const char *managed; + int tx_csum_limit; + int phy_mode; + int err; +@@ -3987,31 +4012,11 @@ static int mvneta_probe(struct platform_ + goto err_free_netdev; + } + +- phy_node = of_parse_phandle(dn, "phy", 0); +- if (!phy_node) { +- if (!of_phy_is_fixed_link(dn)) { +- dev_err(&pdev->dev, "no PHY specified\n"); +- err = -ENODEV; +- goto err_free_irq; +- } +- +- err = of_phy_register_fixed_link(dn); +- if (err < 0) { +- dev_err(&pdev->dev, "cannot register fixed PHY\n"); +- goto err_free_irq; +- } +- +- /* In the case of a fixed PHY, the DT node associated +- * to the PHY is the Ethernet MAC DT node. +- */ +- phy_node = of_node_get(dn); +- } +- + phy_mode = of_get_phy_mode(dn); + if (phy_mode < 0) { + dev_err(&pdev->dev, "incorrect phy-mode\n"); + err = -EINVAL; +- goto err_put_phy_node; ++ goto err_free_irq; + } + + dev->tx_queue_len = MVNETA_MAX_TXD; +@@ -4022,12 +4027,7 @@ static int mvneta_probe(struct platform_ + + pp = netdev_priv(dev); + spin_lock_init(&pp->lock); +- pp->phy_node = phy_node; +- pp->phy_interface = phy_mode; +- +- err = of_property_read_string(dn, "managed", &managed); +- pp->use_inband_status = (err == 0 && +- strcmp(managed, "in-band-status") == 0); ++ pp->dn = dn; + pp->cpu_notifier.notifier_call = mvneta_percpu_notifier; + + pp->rxq_def = rxq_def; +@@ -4037,7 +4037,7 @@ static int mvneta_probe(struct platform_ + pp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pp->clk)) { + err = PTR_ERR(pp->clk); +- goto err_put_phy_node; ++ goto err_free_irq; + } + + clk_prepare_enable(pp->clk); +@@ -4140,6 +4140,14 @@ static int mvneta_probe(struct platform_ + dev->priv_flags |= IFF_UNICAST_FLT | IFF_LIVE_ADDR_CHANGE; + dev->gso_max_segs = MVNETA_MAX_TSO_SEGS; + ++ phylink = phylink_create(dev, dn, phy_mode, &mvneta_phylink_ops); ++ if (IS_ERR(phylink)) { ++ err = PTR_ERR(phylink); ++ goto err_free_stats; ++ } ++ ++ pp->phylink = phylink; ++ + err = register_netdev(dev); + if (err < 0) { + dev_err(&pdev->dev, "failed to register\n"); +@@ -4151,13 +4159,6 @@ static int mvneta_probe(struct platform_ + + platform_set_drvdata(pdev, pp->dev); + +- if (pp->use_inband_status) { +- struct phy_device *phy = of_phy_find_device(dn); +- +- mvneta_fixed_link_update(pp, phy); +- +- put_device(&phy->dev); +- } + + return 0; + +@@ -4169,13 +4170,13 @@ err_netdev: + 1 << pp->id); + } + err_free_stats: ++ if (pp->phylink) ++ phylink_destroy(pp->phylink); + free_percpu(pp->stats); + err_free_ports: + free_percpu(pp->ports); + err_clk: + clk_disable_unprepare(pp->clk); +-err_put_phy_node: +- of_node_put(phy_node); + err_free_irq: + irq_dispose_mapping(dev->irq); + err_free_netdev: +@@ -4194,7 +4195,7 @@ static int mvneta_remove(struct platform + free_percpu(pp->ports); + free_percpu(pp->stats); + irq_dispose_mapping(dev->irq); +- of_node_put(pp->phy_node); ++ phylink_destroy(pp->phylink); + free_netdev(dev); + + if (pp->bm_priv) { diff --git a/target/linux/mvebu/patches-4.4/135-phy-fixed-phy-remove-fixed_phy_update_state.patch b/target/linux/mvebu/patches-4.4/135-phy-fixed-phy-remove-fixed_phy_update_state.patch new file mode 100644 index 0000000000..58c9aab4ac --- /dev/null +++ b/target/linux/mvebu/patches-4.4/135-phy-fixed-phy-remove-fixed_phy_update_state.patch @@ -0,0 +1,80 @@ +From 9be436bdb67c1f4aa9f33f2477f94e1f58a0ff02 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Fri, 2 Oct 2015 22:46:54 +0100 +Subject: [PATCH 723/744] phy: fixed-phy: remove fixed_phy_update_state() + +mvneta is the only user of fixed_phy_update_state(), which has been +converted to use phylink instead. Remove fixed_phy_update_state(). + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/fixed_phy.c | 31 ------------------------------- + include/linux/phy_fixed.h | 9 --------- + 2 files changed, 40 deletions(-) + +--- a/drivers/net/phy/fixed_phy.c ++++ b/drivers/net/phy/fixed_phy.c +@@ -115,37 +115,6 @@ int fixed_phy_set_link_update(struct phy + } + EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); + +-int fixed_phy_update_state(struct phy_device *phydev, +- const struct fixed_phy_status *status, +- const struct fixed_phy_status *changed) +-{ +- struct fixed_mdio_bus *fmb = &platform_fmb; +- struct fixed_phy *fp; +- +- if (!phydev || phydev->bus != fmb->mii_bus) +- return -EINVAL; +- +- list_for_each_entry(fp, &fmb->phys, node) { +- if (fp->addr == phydev->addr) { +- write_seqcount_begin(&fp->seqcount); +-#define _UPD(x) if (changed->x) \ +- fp->status.x = status->x +- _UPD(link); +- _UPD(speed); +- _UPD(duplex); +- _UPD(pause); +- _UPD(asym_pause); +-#undef _UPD +- fixed_phy_update(fp); +- write_seqcount_end(&fp->seqcount); +- return 0; +- } +- } +- +- return -ENOENT; +-} +-EXPORT_SYMBOL(fixed_phy_update_state); +- + int fixed_phy_add(unsigned int irq, int phy_addr, + struct fixed_phy_status *status, + int link_gpio) +--- a/include/linux/phy_fixed.h ++++ b/include/linux/phy_fixed.h +@@ -23,9 +23,6 @@ extern void fixed_phy_del(int phy_addr); + extern int fixed_phy_set_link_update(struct phy_device *phydev, + int (*link_update)(struct net_device *, + struct fixed_phy_status *)); +-extern int fixed_phy_update_state(struct phy_device *phydev, +- const struct fixed_phy_status *status, +- const struct fixed_phy_status *changed); + #else + static inline int fixed_phy_add(unsigned int irq, int phy_id, + struct fixed_phy_status *status, +@@ -50,12 +47,6 @@ static inline int fixed_phy_set_link_upd + { + return -ENODEV; + } +-static inline int fixed_phy_update_state(struct phy_device *phydev, +- const struct fixed_phy_status *status, +- const struct fixed_phy_status *changed) +-{ +- return -ENODEV; +-} + #endif /* CONFIG_FIXED_PHY */ + + #endif /* __PHY_FIXED_H */ diff --git a/target/linux/mvebu/patches-4.4/136-phylink-add-ethtool-nway_reset-support.patch b/target/linux/mvebu/patches-4.4/136-phylink-add-ethtool-nway_reset-support.patch new file mode 100644 index 0000000000..ade904eebd --- /dev/null +++ b/target/linux/mvebu/patches-4.4/136-phylink-add-ethtool-nway_reset-support.patch @@ -0,0 +1,48 @@ +From d6bd25b692378ec17bdb1023d398c03c45829947 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 20:27:19 +0100 +Subject: [PATCH 724/744] phylink: add ethtool nway_reset support + +Add ethtool nway_reset support to phylink, to allow userspace to +request a re-negotiation of the link. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/phylink.c | 14 ++++++++++++++ + include/linux/phylink.h | 1 + + 2 files changed, 15 insertions(+) + +--- a/drivers/net/phy/phylink.c ++++ b/drivers/net/phy/phylink.c +@@ -687,6 +687,20 @@ int phylink_ethtool_set_settings(struct + } + EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings); + ++int phylink_ethtool_nway_reset(struct phylink *pl) ++{ ++ int ret = 0; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->phydev) ++ ret = genphy_restart_aneg(pl->phydev); ++ phylink_mac_an_restart(pl); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset); ++ + /* This emulates MII registers for a fixed-mode phy operating as per the + * passed in state. "aneg" defines if we report negotiation is possible. + * +--- a/include/linux/phylink.h ++++ b/include/linux/phylink.h +@@ -65,6 +65,7 @@ void phylink_stop(struct phylink *); + + int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *); + int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *); ++int phylink_ethtool_nway_reset(struct phylink *); + int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); + + void phylink_set_link_port(struct phylink *pl, u32 support, u8 port); diff --git a/target/linux/mvebu/patches-4.4/137-net-mvneta-add-nway_reset-support.patch b/target/linux/mvebu/patches-4.4/137-net-mvneta-add-nway_reset-support.patch new file mode 100644 index 0000000000..034b596436 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/137-net-mvneta-add-nway_reset-support.patch @@ -0,0 +1,38 @@ +From 244bee2889d08f876c64c335765a8ea6de0f5381 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 19:40:31 +0100 +Subject: [PATCH 725/744] net: mvneta: add nway_reset support + +Add ethtool nway_reset support to mvneta via phylink, so that userspace +can request the link in whatever mode to be renegotiated via +ethtool -r ethX. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/ethernet/marvell/mvneta.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3589,6 +3589,13 @@ int mvneta_ethtool_set_settings(struct n + return phylink_ethtool_set_settings(pp->phylink, cmd); + } + ++static int mvneta_ethtool_nway_reset(struct net_device *dev) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ ++ return phylink_ethtool_nway_reset(pp->phylink); ++} ++ + /* Set interrupt coalescing for ethtools */ + static int mvneta_ethtool_set_coalesce(struct net_device *dev, + struct ethtool_coalesce *c) +@@ -3853,6 +3860,7 @@ const struct ethtool_ops mvneta_eth_tool + .get_link = ethtool_op_get_link, + .get_settings = mvneta_ethtool_get_settings, + .set_settings = mvneta_ethtool_set_settings, ++ .nway_reset = mvneta_ethtool_nway_reset, + .set_coalesce = mvneta_ethtool_set_coalesce, + .get_coalesce = mvneta_ethtool_get_coalesce, + .get_drvinfo = mvneta_ethtool_get_drvinfo, diff --git a/target/linux/mvebu/patches-4.4/138-phylink-add-flow-control-support.patch b/target/linux/mvebu/patches-4.4/138-phylink-add-flow-control-support.patch new file mode 100644 index 0000000000..95b8a81943 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/138-phylink-add-flow-control-support.patch @@ -0,0 +1,262 @@ +From f566177aa6661e646b83526f24391a568ffd1a75 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 20:32:07 +0100 +Subject: [PATCH 726/744] phylink: add flow control support + +Add flow control support, including ethtool support, to phylink. We +add support to allow ethtool to get and set the current flow control +settings, and the 802.3 specified resolution for the local and remote +link partner abilities. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/phylink.c | 145 +++++++++++++++++++++++++++++++++++++++++----- + include/linux/phylink.h | 8 +++ + 2 files changed, 139 insertions(+), 14 deletions(-) + +--- a/drivers/net/phy/phylink.c ++++ b/drivers/net/phy/phylink.c +@@ -91,10 +91,12 @@ static int phylink_parse_fixedlink(struc + pl->link_config.an_complete = 1; + pl->link_config.speed = speed; + pl->link_config.duplex = DUPLEX_HALF; +- pl->link_config.pause = MLO_PAUSE_NONE; + + if (of_property_read_bool(fixed_node, "full-duplex")) + pl->link_config.duplex = DUPLEX_FULL; ++ ++ /* We treat the "pause" and "asym-pause" terminology as ++ * defining the link partner's ability. */ + if (of_property_read_bool(fixed_node, "pause")) + pl->link_config.pause |= MLO_PAUSE_SYM; + if (of_property_read_bool(fixed_node, "asym-pause")) +@@ -118,7 +120,6 @@ static int phylink_parse_fixedlink(struc + pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ? + DUPLEX_FULL : DUPLEX_HALF; + pl->link_config.speed = be32_to_cpu(fixed_prop[2]); +- pl->link_config.pause = MLO_PAUSE_NONE; + if (be32_to_cpu(fixed_prop[3])) + pl->link_config.pause |= MLO_PAUSE_SYM; + if (be32_to_cpu(fixed_prop[4])) +@@ -130,16 +131,6 @@ static int phylink_parse_fixedlink(struc + } + + if (pl->link_an_mode == MLO_AN_FIXED) { +- /* Generate the supported/advertising masks */ +- if (pl->link_config.pause & MLO_PAUSE_SYM) { +- pl->link_config.supported |= SUPPORTED_Pause; +- pl->link_config.advertising |= ADVERTISED_Pause; +- } +- if (pl->link_config.pause & MLO_PAUSE_ASYM) { +- pl->link_config.supported |= SUPPORTED_Asym_Pause; +- pl->link_config.advertising |= ADVERTISED_Asym_Pause; +- } +- + if (pl->link_config.speed > SPEED_1000 && + pl->link_config.duplex != DUPLEX_FULL) + netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n", +@@ -242,6 +233,56 @@ static void phylink_get_fixed_state(stru + state->link = !!gpiod_get_value(pl->link_gpio); + } + ++/* Flow control is resolved according to our and the link partners ++ * advertisments using the following drawn from the 802.3 specs: ++ * Local device Link partner ++ * Pause AsymDir Pause AsymDir Result ++ * 1 X 1 X TX+RX ++ * 0 1 1 1 RX ++ * 1 1 0 1 TX ++ */ ++static void phylink_resolve_flow(struct phylink *pl, ++ struct phylink_link_state *state) ++{ ++ int new_pause = 0; ++ ++ if (pl->link_config.pause & MLO_PAUSE_AN) { ++ int pause = 0; ++ ++ if (pl->link_config.advertising & ADVERTISED_Pause) ++ pause |= MLO_PAUSE_SYM; ++ if (pl->link_config.advertising & ADVERTISED_Asym_Pause) ++ pause |= MLO_PAUSE_ASYM; ++ ++ pause &= state->pause; ++ ++ if (pause & MLO_PAUSE_SYM) ++ new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX; ++ else if (pause & MLO_PAUSE_ASYM) ++ new_pause = state->pause & MLO_PAUSE_SYM ? ++ MLO_PAUSE_RX : MLO_PAUSE_TX; ++ } else { ++ new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK; ++ } ++ ++ state->pause &= ~MLO_PAUSE_TXRX_MASK; ++ state->pause |= new_pause; ++} ++ ++static const char *phylink_pause_to_str(int pause) ++{ ++ switch (pause & MLO_PAUSE_TXRX_MASK) { ++ case MLO_PAUSE_TX | MLO_PAUSE_RX: ++ return "rx/tx"; ++ case MLO_PAUSE_TX: ++ return "tx"; ++ case MLO_PAUSE_RX: ++ return "rx"; ++ default: ++ return "off"; ++ } ++} ++ + extern const char *phy_speed_to_str(int speed); + + static void phylink_resolve(struct work_struct *w) +@@ -257,6 +298,7 @@ static void phylink_resolve(struct work_ + switch (pl->link_an_mode) { + case MLO_AN_PHY: + link_state = pl->phy_state; ++ phylink_resolve_flow(pl, &link_state); + break; + + case MLO_AN_FIXED: +@@ -265,9 +307,12 @@ static void phylink_resolve(struct work_ + + case MLO_AN_SGMII: + phylink_get_mac_state(pl, &link_state); +- if (pl->phydev) ++ if (pl->phydev) { + link_state.link = link_state.link && + pl->phy_state.link; ++ link_state.pause |= pl->phy_state.pause; ++ phylink_resolve_flow(pl, &link_state); ++ } + break; + + case MLO_AN_8023Z: +@@ -297,7 +342,7 @@ static void phylink_resolve(struct work_ + "Link is Up - %s/%s - flow control %s\n", + phy_speed_to_str(link_state.speed), + link_state.duplex ? "Full" : "Half", +- link_state.pause ? "rx/tx" : "off"); ++ phylink_pause_to_str(link_state.pause)); + } + } + mutex_unlock(&pl->state_mutex); +@@ -326,6 +371,7 @@ struct phylink *phylink_create(struct ne + pl->link_interface = iface; + pl->link_port_support = SUPPORTED_MII; + pl->link_port = PORT_MII; ++ pl->link_config.pause = MLO_PAUSE_AN; + pl->ops = ops; + __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + +@@ -507,6 +553,7 @@ void phylink_start(struct phylink *pl) + * a fixed-link to start with the correct parameters, and also + * ensures that we set the appropriate advertisment for Serdes links. + */ ++ phylink_resolve_flow(pl, &pl->link_config); + phylink_mac_config(pl, &pl->link_config); + + clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); +@@ -701,6 +748,76 @@ int phylink_ethtool_nway_reset(struct ph + } + EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset); + ++void phylink_ethtool_get_pauseparam(struct phylink *pl, ++ struct ethtool_pauseparam *pause) ++{ ++ mutex_lock(&pl->config_mutex); ++ ++ pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN); ++ pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX); ++ pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX); ++ ++ mutex_unlock(&pl->config_mutex); ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam); ++ ++static int __phylink_ethtool_set_pauseparam(struct phylink *pl, ++ struct ethtool_pauseparam *pause) ++{ ++ struct phylink_link_state *config = &pl->link_config; ++ ++ if (!(config->supported & (SUPPORTED_Pause | SUPPORTED_Asym_Pause))) ++ return -EOPNOTSUPP; ++ ++ if (!(config->supported & SUPPORTED_Asym_Pause) && ++ !pause->autoneg && pause->rx_pause != pause->tx_pause) ++ return -EINVAL; ++ ++ config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK); ++ ++ if (pause->autoneg) ++ config->pause |= MLO_PAUSE_AN; ++ if (pause->rx_pause) ++ config->pause |= MLO_PAUSE_RX; ++ if (pause->tx_pause) ++ config->pause |= MLO_PAUSE_TX; ++ ++ switch (pl->link_an_mode) { ++ case MLO_AN_PHY: ++ /* Silently mark the carrier down, and then trigger a resolve */ ++ netif_carrier_off(pl->netdev); ++ phylink_run_resolve(pl); ++ break; ++ ++ case MLO_AN_FIXED: ++ /* Should we allow fixed links to change against the config? */ ++ phylink_resolve_flow(pl, config); ++ phylink_mac_config(pl, config); ++ break; ++ ++ case MLO_AN_SGMII: ++ case MLO_AN_8023Z: ++ phylink_mac_config(pl, config); ++ phylink_mac_an_restart(pl); ++ break; ++ } ++ ++ return 0; ++} ++ ++int phylink_ethtool_set_pauseparam(struct phylink *pl, ++ struct ethtool_pauseparam *pause) ++{ ++ int ret; ++ ++ mutex_lock(&pl->config_mutex); ++ ret = __phylink_ethtool_set_pauseparam(pl, pause); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); ++ + /* This emulates MII registers for a fixed-mode phy operating as per the + * passed in state. "aneg" defines if we report negotiation is possible. + * +--- a/include/linux/phylink.h ++++ b/include/linux/phylink.h +@@ -13,6 +13,10 @@ enum { + MLO_PAUSE_NONE, + MLO_PAUSE_ASYM = BIT(0), + MLO_PAUSE_SYM = BIT(1), ++ MLO_PAUSE_RX = BIT(2), ++ MLO_PAUSE_TX = BIT(3), ++ MLO_PAUSE_TXRX_MASK = MLO_PAUSE_TX | MLO_PAUSE_RX, ++ MLO_PAUSE_AN = BIT(4), + + MLO_AN_PHY = 0, + MLO_AN_FIXED, +@@ -66,6 +70,10 @@ void phylink_stop(struct phylink *); + int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *); + int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *); + int phylink_ethtool_nway_reset(struct phylink *); ++void phylink_ethtool_get_pauseparam(struct phylink *, ++ struct ethtool_pauseparam *); ++int phylink_ethtool_set_pauseparam(struct phylink *, ++ struct ethtool_pauseparam *); + int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); + + void phylink_set_link_port(struct phylink *pl, u32 support, u8 port); diff --git a/target/linux/mvebu/patches-4.4/139-net-mvneta-add-flow-control-support-via-phylink.patch b/target/linux/mvebu/patches-4.4/139-net-mvneta-add-flow-control-support-via-phylink.patch new file mode 100644 index 0000000000..75cd46e3da --- /dev/null +++ b/target/linux/mvebu/patches-4.4/139-net-mvneta-add-flow-control-support-via-phylink.patch @@ -0,0 +1,66 @@ +From 7bd34822b9922beb22a6384d9190646105d259d8 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 17:41:44 +0100 +Subject: [PATCH 727/744] net: mvneta: add flow control support via phylink + +Add flow control support to mvneta, including the ethtool hooks. This +uses the phylink code to calculate the result of autonegotiation where +a phy is attached, and to handle the ethtool settings. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/ethernet/marvell/mvneta.c | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3208,6 +3208,8 @@ static void mvneta_mac_config(struct net + + if (state->advertising & ADVERTISED_Pause) + new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL; ++ if (state->pause & MLO_PAUSE_TXRX_MASK) ++ new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL; + + switch (mode) { + case MLO_AN_SGMII: +@@ -3232,7 +3234,7 @@ static void mvneta_mac_config(struct net + /* The MAC only supports FD mode */ + MVNETA_GMAC_CONFIG_FULL_DUPLEX; + +- if (state->an_enabled) ++ if (state->pause & MLO_PAUSE_AN && state->an_enabled) + new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN; + break; + +@@ -3685,6 +3687,22 @@ static int mvneta_ethtool_set_ringparam( + return 0; + } + ++static void mvneta_ethtool_get_pauseparam(struct net_device *dev, ++ struct ethtool_pauseparam *pause) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ ++ phylink_ethtool_get_pauseparam(pp->phylink, pause); ++} ++ ++static int mvneta_ethtool_set_pauseparam(struct net_device *dev, ++ struct ethtool_pauseparam *pause) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ ++ return phylink_ethtool_set_pauseparam(pp->phylink, pause); ++} ++ + static void mvneta_ethtool_get_strings(struct net_device *netdev, u32 sset, + u8 *data) + { +@@ -3866,6 +3884,8 @@ const struct ethtool_ops mvneta_eth_tool + .get_drvinfo = mvneta_ethtool_get_drvinfo, + .get_ringparam = mvneta_ethtool_get_ringparam, + .set_ringparam = mvneta_ethtool_set_ringparam, ++ .get_pauseparam = mvneta_ethtool_get_pauseparam, ++ .set_pauseparam = mvneta_ethtool_set_pauseparam, + .get_strings = mvneta_ethtool_get_strings, + .get_ethtool_stats = mvneta_ethtool_get_stats, + .get_sset_count = mvneta_ethtool_get_sset_count, diff --git a/target/linux/mvebu/patches-4.4/140-net-mvneta-enable-flow-control-for-PHY-connections.patch b/target/linux/mvebu/patches-4.4/140-net-mvneta-enable-flow-control-for-PHY-connections.patch new file mode 100644 index 0000000000..e10574c753 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/140-net-mvneta-enable-flow-control-for-PHY-connections.patch @@ -0,0 +1,32 @@ +From 62f8a12044265df11531750a240e516a5f1ff433 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 00:34:08 +0100 +Subject: [PATCH 728/744] net: mvneta: enable flow control for PHY connections + +Enable flow control support for PHY connections by indicating our +support via the ethtool capabilities. phylink takes care of the +appropriate handling. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/ethernet/marvell/mvneta.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3127,12 +3127,14 @@ static int mvneta_mac_support(struct net + state->supported = PHY_10BT_FEATURES | + PHY_100BT_FEATURES | + SUPPORTED_1000baseT_Full | ++ SUPPORTED_Pause | + SUPPORTED_Autoneg; + state->advertising = ADVERTISED_10baseT_Half | + ADVERTISED_10baseT_Full | + ADVERTISED_100baseT_Half | + ADVERTISED_100baseT_Full | + ADVERTISED_1000baseT_Full | ++ ADVERTISED_Pause | + ADVERTISED_Autoneg; + state->an_enabled = 1; + break; diff --git a/target/linux/mvebu/patches-4.4/141-net-mvneta-enable-flow-control-for-fixed-connections.patch b/target/linux/mvebu/patches-4.4/141-net-mvneta-enable-flow-control-for-fixed-connections.patch new file mode 100644 index 0000000000..16ffab3d46 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/141-net-mvneta-enable-flow-control-for-fixed-connections.patch @@ -0,0 +1,53 @@ +From 4c3e2dc08a11fb1273ca62467f1d06e59866bad3 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@armlinux.org.uk> +Date: Tue, 12 Jul 2016 00:04:13 +0100 +Subject: [PATCH 729/744] net: mvneta: enable flow control for fixed + connections + +Allow symetric flow control to be enabled for fixed link connections as +well as other types of connections by setting the supported and +advertised capability bits. + +Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> +--- + drivers/net/ethernet/marvell/mvneta.c | 11 +++++++---- + 1 file changed, 7 insertions(+), 4 deletions(-) + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3114,9 +3114,9 @@ static int mvneta_mac_support(struct net + switch (mode) { + case MLO_AN_8023Z: + state->supported = SUPPORTED_1000baseT_Full | +- SUPPORTED_Autoneg | SUPPORTED_Pause; ++ SUPPORTED_Autoneg; + state->advertising = ADVERTISED_1000baseT_Full | +- ADVERTISED_Autoneg | ADVERTISED_Pause; ++ ADVERTISED_Autoneg; + state->an_enabled = 1; + break; + +@@ -3127,18 +3127,21 @@ static int mvneta_mac_support(struct net + state->supported = PHY_10BT_FEATURES | + PHY_100BT_FEATURES | + SUPPORTED_1000baseT_Full | +- SUPPORTED_Pause | + SUPPORTED_Autoneg; + state->advertising = ADVERTISED_10baseT_Half | + ADVERTISED_10baseT_Full | + ADVERTISED_100baseT_Half | + ADVERTISED_100baseT_Full | + ADVERTISED_1000baseT_Full | +- ADVERTISED_Pause | + ADVERTISED_Autoneg; + state->an_enabled = 1; + break; + } ++ ++ /* All modes support flow control */ ++ state->supported |= SUPPORTED_Pause; ++ state->advertising |= ADVERTISED_Pause; ++ + return 0; + } + diff --git a/target/linux/mvebu/patches-4.4/142-phylink-add-EEE-support.patch b/target/linux/mvebu/patches-4.4/142-phylink-add-EEE-support.patch new file mode 100644 index 0000000000..b06ec76b98 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/142-phylink-add-EEE-support.patch @@ -0,0 +1,111 @@ +From ffba226d73a2be262fff12d30aecf76d107b2ace Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 21:19:53 +0100 +Subject: [PATCH 730/744] phylink: add EEE support + +Add EEE hooks to phylink to allow the phylib EEE functions for the +connected phy to be safely accessed. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/phylink.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++- + include/linux/phylink.h | 7 +++++- + 2 files changed, 63 insertions(+), 2 deletions(-) + +--- a/drivers/net/phy/phylink.c ++++ b/drivers/net/phy/phylink.c +@@ -334,7 +334,8 @@ static void phylink_resolve(struct work_ + if (pl->phydev) + phylink_mac_config(pl, &link_state); + +- pl->ops->mac_link_up(ndev, pl->link_an_mode); ++ pl->ops->mac_link_up(ndev, pl->link_an_mode, ++ pl->phydev); + + netif_carrier_on(ndev); + +@@ -818,6 +819,61 @@ int phylink_ethtool_set_pauseparam(struc + } + EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); + ++int phylink_init_eee(struct phylink *pl, bool clk_stop_enable) ++{ ++ int ret = -EPROTONOSUPPORT; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->phydev) ++ ret = phy_init_eee(pl->phydev, clk_stop_enable); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_init_eee); ++ ++int phylink_get_eee_err(struct phylink *pl) ++{ ++ int ret = 0; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->phydev) ++ ret = phy_get_eee_err(pl->phydev); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_get_eee_err); ++ ++int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee) ++{ ++ int ret = -EOPNOTSUPP; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->phydev) ++ ret = phy_ethtool_get_eee(pl->phydev, eee); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee); ++ ++int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee) ++{ ++ int ret = -EOPNOTSUPP; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->phydev) { ++ ret = phy_ethtool_set_eee(pl->phydev, eee); ++ if (ret == 0 && eee->eee_enabled) ++ phy_start_aneg(pl->phydev); ++ } ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee); ++ + /* This emulates MII registers for a fixed-mode phy operating as per the + * passed in state. "aneg" defines if we report negotiation is possible. + * +--- a/include/linux/phylink.h ++++ b/include/linux/phylink.h +@@ -51,7 +51,8 @@ struct phylink_mac_ops { + void (*mac_an_restart)(struct net_device *, unsigned int mode); + + void (*mac_link_down)(struct net_device *, unsigned int mode); +- void (*mac_link_up)(struct net_device *, unsigned int mode); ++ void (*mac_link_up)(struct net_device *, unsigned int mode, ++ struct phy_device *); + }; + + struct phylink *phylink_create(struct net_device *, struct device_node *, +@@ -74,6 +75,10 @@ void phylink_ethtool_get_pauseparam(stru + struct ethtool_pauseparam *); + int phylink_ethtool_set_pauseparam(struct phylink *, + struct ethtool_pauseparam *); ++int phylink_init_eee(struct phylink *, bool); ++int phylink_get_eee_err(struct phylink *); ++int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *); ++int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *); + int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); + + void phylink_set_link_port(struct phylink *pl, u32 support, u8 port); diff --git a/target/linux/mvebu/patches-4.4/143-net-mvneta-add-EEE-support.patch b/target/linux/mvebu/patches-4.4/143-net-mvneta-add-EEE-support.patch new file mode 100644 index 0000000000..b9043561d0 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/143-net-mvneta-add-EEE-support.patch @@ -0,0 +1,182 @@ +From b7dacf514e41d6efff0ccc170f660cc6dc2aeae2 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Tue, 29 Sep 2015 15:17:39 +0100 +Subject: [PATCH 731/744] net: mvneta: add EEE support + +Add EEE support to mvneta. This allows us to enable the low power idle +support at MAC level if there is a PHY attached through phylink which +supports LPI. The appropriate ethtool support is provided to allow the +feature to be controlled, including ethtool statistics for EEE wakeup +errors. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/ethernet/marvell/mvneta.c | 87 +++++++++++++++++++++++++++++++++++ + 1 file changed, 87 insertions(+) + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -243,6 +243,12 @@ + #define MVNETA_TXQ_TOKEN_SIZE_REG(q) (0x3e40 + ((q) << 2)) + #define MVNETA_TXQ_TOKEN_SIZE_MAX 0x7fffffff + ++#define MVNETA_LPI_CTRL_0 0x2cc0 ++#define MVNETA_LPI_CTRL_1 0x2cc4 ++#define MVNETA_LPI_REQUEST_ENABLE BIT(0) ++#define MVNETA_LPI_CTRL_2 0x2cc8 ++#define MVNETA_LPI_STATUS 0x2ccc ++ + #define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK 0xff + + /* Descriptor ring Macros */ +@@ -316,6 +322,11 @@ + #define MVNETA_RX_GET_BM_POOL_ID(rxd) \ + (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT) + ++enum { ++ ETHTOOL_STAT_EEE_WAKEUP, ++ ETHTOOL_MAX_STATS, ++}; ++ + struct mvneta_statistic { + unsigned short offset; + unsigned short type; +@@ -324,6 +335,7 @@ struct mvneta_statistic { + + #define T_REG_32 32 + #define T_REG_64 64 ++#define T_SW 1 + + static const struct mvneta_statistic mvneta_statistics[] = { + { 0x3000, T_REG_64, "good_octets_received", }, +@@ -358,6 +370,7 @@ static const struct mvneta_statistic mvn + { 0x304c, T_REG_32, "broadcast_frames_sent", }, + { 0x3054, T_REG_32, "fc_sent", }, + { 0x300c, T_REG_32, "internal_mac_transmit_err", }, ++ { ETHTOOL_STAT_EEE_WAKEUP, T_SW, "eee_wakeup_errors", }, + }; + + struct mvneta_pcpu_stats { +@@ -413,6 +426,10 @@ struct mvneta_port { + struct mvneta_bm_pool *pool_short; + int bm_win_id; + ++ bool eee_enabled; ++ bool eee_active; ++ bool tx_lpi_enabled; ++ + u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)]; + + u32 indir[MVNETA_RSS_LU_TABLE_SIZE]; +@@ -3276,6 +3293,18 @@ static void mvneta_mac_config(struct net + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an); + } + ++static void mvneta_set_eee(struct mvneta_port *pp, bool enable) ++{ ++ u32 lpi_ctl1; ++ ++ lpi_ctl1 = mvreg_read(pp, MVNETA_LPI_CTRL_1); ++ if (enable) ++ lpi_ctl1 |= MVNETA_LPI_REQUEST_ENABLE; ++ else ++ lpi_ctl1 &= ~MVNETA_LPI_REQUEST_ENABLE; ++ mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi_ctl1); ++} ++ + static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode) + { + struct mvneta_port *pp = netdev_priv(ndev); +@@ -3289,6 +3318,9 @@ static void mvneta_mac_link_down(struct + val |= MVNETA_GMAC_FORCE_LINK_DOWN; + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); + } ++ ++ pp->eee_active = false; ++ mvneta_set_eee(pp, false); + } + + static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode, +@@ -3305,6 +3337,11 @@ static void mvneta_mac_link_up(struct ne + } + + mvneta_port_up(pp); ++ ++ if (phy && pp->eee_enabled) { ++ pp->eee_active = phy_init_eee(phy, 0) >= 0; ++ mvneta_set_eee(pp, pp->eee_active && pp->tx_lpi_enabled); ++ } + } + + static const struct phylink_mac_ops mvneta_phylink_ops = { +@@ -3744,6 +3781,13 @@ static void mvneta_ethtool_update_stats( + val64 = (u64)high << 32 | low; + pp->ethtool_stats[i] += val64; + break; ++ case T_SW: ++ switch (s->offset) { ++ case ETHTOOL_STAT_EEE_WAKEUP: ++ val = phylink_get_eee_err(pp->phylink); ++ break; ++ } ++ break; + } + } + } +@@ -3867,6 +3911,47 @@ static int mvneta_ethtool_get_rxfh(struc + return 0; + } + ++static int mvneta_ethtool_get_eee(struct net_device *dev, ++ struct ethtool_eee *eee) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ u32 lpi_ctl0; ++ ++ lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0); ++ ++ eee->eee_enabled = pp->eee_enabled; ++ eee->eee_active = pp->eee_active; ++ eee->tx_lpi_enabled = pp->tx_lpi_enabled; ++ eee->tx_lpi_timer = (lpi_ctl0) >> 8; // * scale; ++ ++ return phylink_ethtool_get_eee(pp->phylink, eee); ++} ++ ++static int mvneta_ethtool_set_eee(struct net_device *dev, ++ struct ethtool_eee *eee) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ u32 lpi_ctl0; ++ ++ /* The Armada 37x documents do not give limits for this other than ++ * it being an 8-bit register. */ ++ if (eee->tx_lpi_enabled && ++ (eee->tx_lpi_timer < 0 || eee->tx_lpi_timer > 255)) ++ return -EINVAL; ++ ++ lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0); ++ lpi_ctl0 &= ~(0xff << 8); ++ lpi_ctl0 |= eee->tx_lpi_timer << 8; ++ mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi_ctl0); ++ ++ pp->eee_enabled = eee->eee_enabled; ++ pp->tx_lpi_enabled = eee->tx_lpi_enabled; ++ ++ mvneta_set_eee(pp, eee->tx_lpi_enabled && eee->eee_enabled); ++ ++ return phylink_ethtool_set_eee(pp->phylink, eee); ++} ++ + static const struct net_device_ops mvneta_netdev_ops = { + .ndo_open = mvneta_open, + .ndo_stop = mvneta_stop, +@@ -3898,6 +3983,8 @@ const struct ethtool_ops mvneta_eth_tool + .get_rxnfc = mvneta_ethtool_get_rxnfc, + .get_rxfh = mvneta_ethtool_get_rxfh, + .set_rxfh = mvneta_ethtool_set_rxfh, ++ .get_eee = mvneta_ethtool_get_eee, ++ .set_eee = mvneta_ethtool_set_eee, + }; + + /* Initialize hw */ diff --git a/target/linux/mvebu/patches-4.4/144-phylink-add-module-EEPROM-support.patch b/target/linux/mvebu/patches-4.4/144-phylink-add-module-EEPROM-support.patch new file mode 100644 index 0000000000..f57f70dd9e --- /dev/null +++ b/target/linux/mvebu/patches-4.4/144-phylink-add-module-EEPROM-support.patch @@ -0,0 +1,137 @@ +From 5419ccb638aa5c353ea88815e98953d9fc02e6ca Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 23:10:05 +0100 +Subject: [PATCH 732/744] phylink: add module EEPROM support + +Add support for reading module EEPROMs through phylink. + +Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/phylink.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++ + include/linux/phylink.h | 12 +++++++++ + 2 files changed, 78 insertions(+) + +--- a/drivers/net/phy/phylink.c ++++ b/drivers/net/phy/phylink.c +@@ -60,6 +60,9 @@ struct phylink { + struct work_struct resolve; + + bool mac_link_up; ++ ++ const struct phylink_module_ops *module_ops; ++ void *module_data; + }; + + static const char *phylink_an_mode_str(unsigned int mode) +@@ -819,6 +822,36 @@ int phylink_ethtool_set_pauseparam(struc + } + EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); + ++int phylink_ethtool_get_module_info(struct phylink *pl, ++ struct ethtool_modinfo *modinfo) ++{ ++ int ret = -EOPNOTSUPP; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->module_ops) ++ ret = pl->module_ops->get_module_info(pl->module_data, ++ modinfo); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_info); ++ ++int phylink_ethtool_get_module_eeprom(struct phylink *pl, ++ struct ethtool_eeprom *ee, u8 *buf) ++{ ++ int ret = -EOPNOTSUPP; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->module_ops) ++ ret = pl->module_ops->get_module_eeprom(pl->module_data, ee, ++ buf); ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_eeprom); ++ + int phylink_init_eee(struct phylink *pl, bool clk_stop_enable) + { + int ret = -EPROTONOSUPPORT; +@@ -1016,6 +1049,39 @@ EXPORT_SYMBOL_GPL(phylink_mii_ioctl); + + + ++int phylink_register_module(struct phylink *pl, void *data, ++ const struct phylink_module_ops *ops) ++{ ++ int ret = -EBUSY; ++ ++ mutex_lock(&pl->config_mutex); ++ if (!pl->module_ops) { ++ pl->module_ops = ops; ++ pl->module_data = data; ++ ret = 0; ++ } ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_register_module); ++ ++int phylink_unregister_module(struct phylink *pl, void *data) ++{ ++ int ret = -EINVAL; ++ ++ mutex_lock(&pl->config_mutex); ++ if (pl->module_data == data) { ++ pl->module_ops = NULL; ++ pl->module_data = NULL; ++ ret = 0; ++ } ++ mutex_unlock(&pl->config_mutex); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(phylink_unregister_module); ++ + void phylink_disable(struct phylink *pl) + { + set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); +--- a/include/linux/phylink.h ++++ b/include/linux/phylink.h +@@ -55,6 +55,11 @@ struct phylink_mac_ops { + struct phy_device *); + }; + ++struct phylink_module_ops { ++ int (*get_module_info)(void *, struct ethtool_modinfo *); ++ int (*get_module_eeprom)(void *, struct ethtool_eeprom *, u8 *); ++}; ++ + struct phylink *phylink_create(struct net_device *, struct device_node *, + phy_interface_t iface, const struct phylink_mac_ops *ops); + void phylink_destroy(struct phylink *); +@@ -75,12 +80,19 @@ void phylink_ethtool_get_pauseparam(stru + struct ethtool_pauseparam *); + int phylink_ethtool_set_pauseparam(struct phylink *, + struct ethtool_pauseparam *); ++int phylink_ethtool_get_module_info(struct phylink *, struct ethtool_modinfo *); ++int phylink_ethtool_get_module_eeprom(struct phylink *, ++ struct ethtool_eeprom *, u8 *); + int phylink_init_eee(struct phylink *, bool); + int phylink_get_eee_err(struct phylink *); + int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *); + int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *); + int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); + ++int phylink_register_module(struct phylink *, void *, ++ const struct phylink_module_ops *); ++int phylink_unregister_module(struct phylink *, void *); ++ + void phylink_set_link_port(struct phylink *pl, u32 support, u8 port); + int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode); + void phylink_disable(struct phylink *pl); diff --git a/target/linux/mvebu/patches-4.4/145-net-mvneta-add-module-EEPROM-reading-support.patch b/target/linux/mvebu/patches-4.4/145-net-mvneta-add-module-EEPROM-reading-support.patch new file mode 100644 index 0000000000..b3f9039081 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/145-net-mvneta-add-module-EEPROM-reading-support.patch @@ -0,0 +1,44 @@ +From 665e1fe77dedcfc6b5669214ebfd252c803290d4 Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 1 Oct 2015 23:32:39 +0100 +Subject: [PATCH 733/744] net: mvneta: add module EEPROM reading support + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/ethernet/marvell/mvneta.c | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3911,6 +3911,22 @@ static int mvneta_ethtool_get_rxfh(struc + return 0; + } + ++static int mvneta_ethtool_get_module_info(struct net_device *dev, ++ struct ethtool_modinfo *modinfo) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ ++ return phylink_ethtool_get_module_info(pp->phylink, modinfo); ++} ++ ++static int mvneta_ethtool_get_module_eeprom(struct net_device *dev, ++ struct ethtool_eeprom *ee, u8 *buf) ++{ ++ struct mvneta_port *pp = netdev_priv(dev); ++ ++ return phylink_ethtool_get_module_eeprom(pp->phylink, ee, buf); ++} ++ + static int mvneta_ethtool_get_eee(struct net_device *dev, + struct ethtool_eee *eee) + { +@@ -3983,6 +3999,8 @@ const struct ethtool_ops mvneta_eth_tool + .get_rxnfc = mvneta_ethtool_get_rxnfc, + .get_rxfh = mvneta_ethtool_get_rxfh, + .set_rxfh = mvneta_ethtool_set_rxfh, ++ .get_module_info = mvneta_ethtool_get_module_info, ++ .get_module_eeprom = mvneta_ethtool_get_module_eeprom, + .get_eee = mvneta_ethtool_get_eee, + .set_eee = mvneta_ethtool_set_eee, + }; diff --git a/target/linux/mvebu/patches-4.4/146-sfp-phylink-hook-up-eeprom-functions.patch b/target/linux/mvebu/patches-4.4/146-sfp-phylink-hook-up-eeprom-functions.patch new file mode 100644 index 0000000000..f37e652b8a --- /dev/null +++ b/target/linux/mvebu/patches-4.4/146-sfp-phylink-hook-up-eeprom-functions.patch @@ -0,0 +1,68 @@ +From a7091ef24223ed39b39c6b73b77c55c8a607f34a Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Thu, 8 Oct 2015 23:49:47 +0100 +Subject: [PATCH 734/744] sfp/phylink: hook up eeprom functions + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + drivers/net/phy/sfp.c | 19 +++++++++++-------- + 1 file changed, 11 insertions(+), 8 deletions(-) + +--- a/drivers/net/phy/sfp.c ++++ b/drivers/net/phy/sfp.c +@@ -901,11 +901,9 @@ static void sfp_sm_event(struct sfp *sfp + mutex_unlock(&sfp->sm_mutex); + } + +-#if 0 +-static int sfp_phy_module_info(struct phy_device *phy, +- struct ethtool_modinfo *modinfo) ++static int sfp_module_info(void *priv, struct ethtool_modinfo *modinfo) + { +- struct sfp *sfp = phy->priv; ++ struct sfp *sfp = priv; + + /* locking... and check module is present */ + +@@ -919,10 +917,9 @@ static int sfp_phy_module_info(struct ph + return 0; + } + +-static int sfp_phy_module_eeprom(struct phy_device *phy, +- struct ethtool_eeprom *ee, u8 *data) ++static int sfp_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data) + { +- struct sfp *sfp = phy->priv; ++ struct sfp *sfp = priv; + unsigned int first, last, len; + int ret; + +@@ -953,7 +950,11 @@ static int sfp_phy_module_eeprom(struct + } + return 0; + } +-#endif ++ ++static const struct phylink_module_ops sfp_module_ops = { ++ .get_module_info = sfp_module_info, ++ .get_module_eeprom = sfp_module_eeprom, ++}; + + static void sfp_timeout(struct work_struct *work) + { +@@ -1029,6 +1030,7 @@ static int sfp_netdev_notify(struct noti + case NETDEV_UNREGISTER: + if (sfp->mod_phy && sfp->phylink) + phylink_disconnect_phy(sfp->phylink); ++ phylink_unregister_module(sfp->phylink, sfp); + sfp->phylink = NULL; + dev_put(sfp->ndev); + sfp->ndev = NULL; +@@ -1145,6 +1147,7 @@ static int sfp_probe(struct platform_dev + } + + phylink_disable(sfp->phylink); ++ phylink_register_module(sfp->phylink, sfp, &sfp_module_ops); + } + + sfp->state = sfp_get_state(sfp); diff --git a/target/linux/mvebu/patches-4.4/147-net-mvneta-add-BQL-support.patch b/target/linux/mvebu/patches-4.4/147-net-mvneta-add-BQL-support.patch new file mode 100644 index 0000000000..7bd2593537 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/147-net-mvneta-add-BQL-support.patch @@ -0,0 +1,83 @@ +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -1695,8 +1695,10 @@ static struct mvneta_tx_queue *mvneta_tx + + /* Free tx queue skbuffs */ + static void mvneta_txq_bufs_free(struct mvneta_port *pp, +- struct mvneta_tx_queue *txq, int num) ++ struct mvneta_tx_queue *txq, int num, ++ struct netdev_queue *nq) + { ++ unsigned int bytes_compl = 0, pkts_compl = 0; + int i; + + for (i = 0; i < num; i++) { +@@ -1704,6 +1706,11 @@ static void mvneta_txq_bufs_free(struct + txq->txq_get_index; + struct sk_buff *skb = txq->tx_skb[txq->txq_get_index]; + ++ if (skb) { ++ bytes_compl += skb->len; ++ pkts_compl++; ++ } ++ + mvneta_txq_inc_get(txq); + + if (!IS_TSO_HEADER(txq, tx_desc->buf_phys_addr)) +@@ -1714,6 +1721,8 @@ static void mvneta_txq_bufs_free(struct + continue; + dev_kfree_skb_any(skb); + } ++ ++ netdev_tx_completed_queue(nq, pkts_compl, bytes_compl); + } + + /* Handle end of transmission */ +@@ -1727,7 +1736,7 @@ static void mvneta_txq_done(struct mvnet + if (!tx_done) + return; + +- mvneta_txq_bufs_free(pp, txq, tx_done); ++ mvneta_txq_bufs_free(pp, txq, tx_done, nq); + + txq->count -= tx_done; + +@@ -2334,6 +2343,8 @@ out: + struct mvneta_pcpu_stats *stats = this_cpu_ptr(pp->stats); + struct netdev_queue *nq = netdev_get_tx_queue(dev, txq_id); + ++ netdev_tx_sent_queue(nq, len); ++ + txq->count += frags; + mvneta_txq_pend_desc_add(pp, txq, frags); + +@@ -2358,9 +2369,10 @@ static void mvneta_txq_done_force(struct + struct mvneta_tx_queue *txq) + + { ++ struct netdev_queue *nq = netdev_get_tx_queue(pp->dev, txq->id); + int tx_done = txq->count; + +- mvneta_txq_bufs_free(pp, txq, tx_done); ++ mvneta_txq_bufs_free(pp, txq, tx_done, nq); + + /* reset txq */ + txq->count = 0; +@@ -2841,6 +2853,8 @@ static int mvneta_txq_init(struct mvneta + static void mvneta_txq_deinit(struct mvneta_port *pp, + struct mvneta_tx_queue *txq) + { ++ struct netdev_queue *nq = netdev_get_tx_queue(pp->dev, txq->id); ++ + kfree(txq->tx_skb); + + if (txq->tso_hdrs) +@@ -2852,6 +2866,8 @@ static void mvneta_txq_deinit(struct mvn + txq->size * MVNETA_DESC_ALIGNED_SIZE, + txq->descs, txq->descs_phys); + ++ netdev_tx_reset_queue(nq); ++ + txq->descs = NULL; + txq->last_desc = 0; + txq->next_desc_to_proc = 0; diff --git a/target/linux/mvebu/patches-4.4/202-gpio_mvebu_add_limited_pwm_support.patch b/target/linux/mvebu/patches-4.4/202-gpio_mvebu_add_limited_pwm_support.patch new file mode 100644 index 0000000000..b09c89f31a --- /dev/null +++ b/target/linux/mvebu/patches-4.4/202-gpio_mvebu_add_limited_pwm_support.patch @@ -0,0 +1,433 @@ +Armada 370/XP devices can 'blink' gpio lines with a configurable on +and off period. This can be modelled as a PWM. + +However, there are only two sets of PWM configuration registers for +all the gpio lines. This driver simply allows a single gpio line per +gpio chip of 32 lines to be used as a PWM. Attempts to use more return +EBUSY. + +Due to the interleaving of registers it is not simple to separate the +PWM driver from the gpio driver. Thus the gpio driver has been +extended with a PWM driver. + +Signed-off-by: Andrew Lunn <andrew@lunn.ch> +--- + drivers/gpio/Kconfig | 5 ++ + drivers/gpio/Makefile | 1 + + drivers/gpio/gpio-mvebu-pwm.c | 202 ++++++++++++++++++++++++++++++++++++++++++ + drivers/gpio/gpio-mvebu.c | 37 +++----- + drivers/gpio/gpio-mvebu.h | 79 +++++++++++++++++ + 5 files changed, 299 insertions(+), 25 deletions(-) + create mode 100644 drivers/gpio/gpio-mvebu-pwm.c + create mode 100644 drivers/gpio/gpio-mvebu.h + +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -295,6 +295,11 @@ config GPIO_MVEBU + depends on OF + select GENERIC_IRQ_CHIP + ++config GPIO_MVEBU_PWM ++ def_bool y ++ depends on GPIO_MVEBU ++ depends on PWM ++ + config GPIO_MXC + def_bool y + depends on ARCH_MXC +--- a/drivers/gpio/Makefile ++++ b/drivers/gpio/Makefile +@@ -67,6 +67,7 @@ obj-$(CONFIG_GPIO_MPC5200) += gpio-mpc52 + obj-$(CONFIG_GPIO_MPC8XXX) += gpio-mpc8xxx.o + obj-$(CONFIG_GPIO_MSIC) += gpio-msic.o + obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o ++obj-$(CONFIG_GPIO_MVEBU_PWM) += gpio-mvebu-pwm.o + obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o + obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o + obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o +--- /dev/null ++++ b/drivers/gpio/gpio-mvebu-pwm.c +@@ -0,0 +1,202 @@ ++#include <linux/err.h> ++#include <linux/module.h> ++#include <linux/gpio.h> ++#include <linux/pwm.h> ++#include <linux/clk.h> ++#include <linux/platform_device.h> ++#include "gpio-mvebu.h" ++#include "gpiolib.h" ++ ++static void __iomem *mvebu_gpioreg_blink_select(struct mvebu_gpio_chip *mvchip) ++{ ++ return mvchip->membase + GPIO_BLINK_CNT_SELECT; ++} ++ ++static inline struct mvebu_pwm *to_mvebu_pwm(struct pwm_chip *chip) ++{ ++ return container_of(chip, struct mvebu_pwm, chip); ++} ++ ++static inline struct mvebu_gpio_chip *to_mvchip(struct mvebu_pwm *pwm) ++{ ++ return container_of(pwm, struct mvebu_gpio_chip, pwm); ++} ++ ++static int mvebu_pwm_request(struct pwm_chip *chip, struct pwm_device *pwmd) ++{ ++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip); ++ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm); ++ struct gpio_desc *desc = gpio_to_desc(pwmd->pwm); ++ unsigned long flags; ++ int ret = 0; ++ ++ spin_lock_irqsave(&pwm->lock, flags); ++ if (pwm->used) { ++ ret = -EBUSY; ++ } else { ++ if (!desc) { ++ ret = -ENODEV; ++ goto out; ++ } ++ ret = gpiod_request(desc, "mvebu-pwm"); ++ if (ret) ++ goto out; ++ ++ ret = gpiod_direction_output(desc, 0); ++ if (ret) { ++ gpiod_free(desc); ++ goto out; ++ } ++ ++ pwm->pin = pwmd->pwm - mvchip->chip.base; ++ pwm->used = true; ++ } ++ ++out: ++ spin_unlock_irqrestore(&pwm->lock, flags); ++ return ret; ++} ++ ++static void mvebu_pwm_free(struct pwm_chip *chip, struct pwm_device *pwmd) ++{ ++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip); ++ struct gpio_desc *desc = gpio_to_desc(pwmd->pwm); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&pwm->lock, flags); ++ gpiod_free(desc); ++ pwm->used = false; ++ spin_unlock_irqrestore(&pwm->lock, flags); ++} ++ ++static int mvebu_pwm_config(struct pwm_chip *chip, struct pwm_device *pwmd, ++ int duty_ns, int period_ns) ++{ ++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip); ++ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm); ++ unsigned int on, off; ++ unsigned long long val; ++ u32 u; ++ ++ val = (unsigned long long) pwm->clk_rate * duty_ns; ++ do_div(val, NSEC_PER_SEC); ++ if (val > UINT_MAX) ++ return -EINVAL; ++ if (val) ++ on = val; ++ else ++ on = 1; ++ ++ val = (unsigned long long) pwm->clk_rate * (period_ns - duty_ns); ++ do_div(val, NSEC_PER_SEC); ++ if (val > UINT_MAX) ++ return -EINVAL; ++ if (val) ++ off = val; ++ else ++ off = 1; ++ ++ u = readl_relaxed(mvebu_gpioreg_blink_select(mvchip)); ++ u &= ~(1 << pwm->pin); ++ u |= (pwm->id << pwm->pin); ++ writel_relaxed(u, mvebu_gpioreg_blink_select(mvchip)); ++ ++ writel_relaxed(on, pwm->membase + BLINK_ON_DURATION); ++ writel_relaxed(off, pwm->membase + BLINK_OFF_DURATION); ++ ++ return 0; ++} ++ ++static int mvebu_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwmd) ++{ ++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip); ++ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm); ++ ++ mvebu_gpio_blink(&mvchip->chip, pwm->pin, 1); ++ ++ return 0; ++} ++ ++static void mvebu_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwmd) ++{ ++ struct mvebu_pwm *pwm = to_mvebu_pwm(chip); ++ struct mvebu_gpio_chip *mvchip = to_mvchip(pwm); ++ ++ mvebu_gpio_blink(&mvchip->chip, pwm->pin, 0); ++} ++ ++static const struct pwm_ops mvebu_pwm_ops = { ++ .request = mvebu_pwm_request, ++ .free = mvebu_pwm_free, ++ .config = mvebu_pwm_config, ++ .enable = mvebu_pwm_enable, ++ .disable = mvebu_pwm_disable, ++ .owner = THIS_MODULE, ++}; ++ ++void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip) ++{ ++ struct mvebu_pwm *pwm = &mvchip->pwm; ++ ++ pwm->blink_select = readl_relaxed(mvebu_gpioreg_blink_select(mvchip)); ++ pwm->blink_on_duration = ++ readl_relaxed(pwm->membase + BLINK_ON_DURATION); ++ pwm->blink_off_duration = ++ readl_relaxed(pwm->membase + BLINK_OFF_DURATION); ++} ++ ++void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip) ++{ ++ struct mvebu_pwm *pwm = &mvchip->pwm; ++ ++ writel_relaxed(pwm->blink_select, mvebu_gpioreg_blink_select(mvchip)); ++ writel_relaxed(pwm->blink_on_duration, ++ pwm->membase + BLINK_ON_DURATION); ++ writel_relaxed(pwm->blink_off_duration, ++ pwm->membase + BLINK_OFF_DURATION); ++} ++ ++/* ++ * Armada 370/XP has simple PWM support for gpio lines. Other SoCs ++ * don't have this hardware. So if we don't have the necessary ++ * resource, it is not an error. ++ */ ++int mvebu_pwm_probe(struct platform_device *pdev, ++ struct mvebu_gpio_chip *mvchip, ++ int id) ++{ ++ struct device *dev = &pdev->dev; ++ struct mvebu_pwm *pwm = &mvchip->pwm; ++ struct resource *res; ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwm"); ++ if (!res) ++ return 0; ++ ++ mvchip->pwm.membase = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(mvchip->pwm.membase)) ++ return PTR_ERR(mvchip->percpu_membase); ++ ++ if (id < 0 || id > 1) ++ return -EINVAL; ++ pwm->id = id; ++ ++ if (IS_ERR(mvchip->clk)) ++ return PTR_ERR(mvchip->clk); ++ ++ pwm->clk_rate = clk_get_rate(mvchip->clk); ++ if (!pwm->clk_rate) { ++ dev_err(dev, "failed to get clock rate\n"); ++ return -EINVAL; ++ } ++ ++ pwm->chip.dev = dev; ++ pwm->chip.ops = &mvebu_pwm_ops; ++ pwm->chip.base = mvchip->chip.base; ++ pwm->chip.npwm = mvchip->chip.ngpio; ++ pwm->chip.can_sleep = false; ++ ++ spin_lock_init(&pwm->lock); ++ ++ return pwmchip_add(&pwm->chip); ++} +--- a/drivers/gpio/gpio-mvebu.c ++++ b/drivers/gpio/gpio-mvebu.c +@@ -42,10 +42,11 @@ + #include <linux/io.h> + #include <linux/of_irq.h> + #include <linux/of_device.h> ++#include <linux/pwm.h> + #include <linux/clk.h> + #include <linux/pinctrl/consumer.h> + #include <linux/irqchip/chained_irq.h> +- ++#include "gpio-mvebu.h" + /* + * GPIO unit register offsets. + */ +@@ -75,24 +76,6 @@ + + #define MVEBU_MAX_GPIO_PER_BANK 32 + +-struct mvebu_gpio_chip { +- struct gpio_chip chip; +- spinlock_t lock; +- void __iomem *membase; +- void __iomem *percpu_membase; +- int irqbase; +- struct irq_domain *domain; +- int soc_variant; +- +- /* Used to preserve GPIO registers across suspend/resume */ +- u32 out_reg; +- u32 io_conf_reg; +- u32 blink_en_reg; +- u32 in_pol_reg; +- u32 edge_mask_regs[4]; +- u32 level_mask_regs[4]; +-}; +- + /* + * Functions returning addresses of individual registers for a given + * GPIO controller. +@@ -218,7 +201,7 @@ static int mvebu_gpio_get(struct gpio_ch + return (u >> pin) & 1; + } + +-static void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value) ++void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value) + { + struct mvebu_gpio_chip *mvchip = + container_of(chip, struct mvebu_gpio_chip, chip); +@@ -607,6 +590,8 @@ static int mvebu_gpio_suspend(struct pla + BUG(); + } + ++ mvebu_pwm_suspend(mvchip); ++ + return 0; + } + +@@ -650,6 +635,8 @@ static int mvebu_gpio_resume(struct plat + BUG(); + } + ++ mvebu_pwm_resume(mvchip); ++ + return 0; + } + +@@ -661,7 +648,6 @@ static int mvebu_gpio_probe(struct platf + struct resource *res; + struct irq_chip_generic *gc; + struct irq_chip_type *ct; +- struct clk *clk; + unsigned int ngpios; + int soc_variant; + int i, cpu, id; +@@ -691,10 +677,10 @@ static int mvebu_gpio_probe(struct platf + return id; + } + +- clk = devm_clk_get(&pdev->dev, NULL); ++ mvchip->clk = devm_clk_get(&pdev->dev, NULL); + /* Not all SoCs require a clock.*/ +- if (!IS_ERR(clk)) +- clk_prepare_enable(clk); ++ if (!IS_ERR(mvchip->clk)) ++ clk_prepare_enable(mvchip->clk); + + mvchip->soc_variant = soc_variant; + mvchip->chip.label = dev_name(&pdev->dev); +@@ -828,7 +814,8 @@ static int mvebu_gpio_probe(struct platf + goto err_generic_chip; + } + +- return 0; ++ /* Armada 370/XP has simple PWM support for gpio lines */ ++ return mvebu_pwm_probe(pdev, mvchip, id); + + err_generic_chip: + irq_remove_generic_chip(gc, IRQ_MSK(ngpios), IRQ_NOREQUEST, +--- /dev/null ++++ b/drivers/gpio/gpio-mvebu.h +@@ -0,0 +1,79 @@ ++/* ++ * Interface between MVEBU GPIO driver and PWM driver for GPIO pins ++ * ++ * Copyright (C) 2015, Andrew Lunn <andrew@lunn.ch> ++ * ++ * 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. ++ */ ++ ++#ifndef MVEBU_GPIO_PWM_H ++#define MVEBU_GPIO_PWM_H ++ ++#define BLINK_ON_DURATION 0x0 ++#define BLINK_OFF_DURATION 0x4 ++#define GPIO_BLINK_CNT_SELECT 0x0020 ++ ++struct mvebu_pwm { ++ void __iomem *membase; ++ unsigned long clk_rate; ++ bool used; ++ unsigned pin; ++ struct pwm_chip chip; ++ int id; ++ spinlock_t lock; ++ ++ /* Used to preserve GPIO/PWM registers across suspend / ++ * resume */ ++ u32 blink_select; ++ u32 blink_on_duration; ++ u32 blink_off_duration; ++}; ++ ++struct mvebu_gpio_chip { ++ struct gpio_chip chip; ++ spinlock_t lock; ++ void __iomem *membase; ++ void __iomem *percpu_membase; ++ int irqbase; ++ struct irq_domain *domain; ++ int soc_variant; ++ struct clk *clk; ++#ifdef CONFIG_PWM ++ struct mvebu_pwm pwm; ++#endif ++ /* Used to preserve GPIO registers across suspend/resume */ ++ u32 out_reg; ++ u32 io_conf_reg; ++ u32 blink_en_reg; ++ u32 in_pol_reg; ++ u32 edge_mask_regs[4]; ++ u32 level_mask_regs[4]; ++}; ++ ++void mvebu_gpio_blink(struct gpio_chip *chip, unsigned pin, int value); ++ ++#ifdef CONFIG_PWM ++int mvebu_pwm_probe(struct platform_device *pdev, ++ struct mvebu_gpio_chip *mvchip, ++ int id); ++void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip); ++void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip); ++#else ++int mvebu_pwm_probe(struct platform_device *pdev, ++ struct mvebu_gpio_chip *mvchip, ++ int id) ++{ ++ return 0; ++} ++ ++void mvebu_pwm_suspend(struct mvebu_gpio_chip *mvchip) ++{ ++} ++ ++void mvebu_pwm_resume(struct mvebu_gpio_chip *mvchip) ++{ ++} ++#endif ++#endif diff --git a/target/linux/mvebu/patches-4.4/203-dt_bindings_extend_mvebu_gpio_documentation_with_pwm.patch b/target/linux/mvebu/patches-4.4/203-dt_bindings_extend_mvebu_gpio_documentation_with_pwm.patch new file mode 100644 index 0000000000..48f93944bf --- /dev/null +++ b/target/linux/mvebu/patches-4.4/203-dt_bindings_extend_mvebu_gpio_documentation_with_pwm.patch @@ -0,0 +1,52 @@ +Document the optional parameters needed for PWM operation of gpio +lines. + +Signed-off-by: Andrew Lunn <andrew@lunn.ch> +--- + .../devicetree/bindings/gpio/gpio-mvebu.txt | 31 ++++++++++++++++++++++ + 1 file changed, 31 insertions(+) + +--- a/Documentation/devicetree/bindings/gpio/gpio-mvebu.txt ++++ b/Documentation/devicetree/bindings/gpio/gpio-mvebu.txt +@@ -38,6 +38,23 @@ Required properties: + - #gpio-cells: Should be two. The first cell is the pin number. The + second cell is reserved for flags, unused at the moment. + ++Optional properties: ++ ++In order to use the gpio lines in PWM mode, some additional optional ++properties are required. Only Armada 370 and XP supports these ++properties. ++ ++- reg: an additional register set is needed, for the GPIO Blink ++ Counter on/off registers. ++ ++- reg-names: Must contain an entry "pwm" corresponding to the ++ additional register range needed for pwm operation. ++ ++- #pwm-cells: Should be two. The first cell is the pin number. The ++ second cell is reserved for flags, unused at the moment. ++ ++- clocks: Must be a phandle to the clock for the gpio controller. ++ + Example: + + gpio0: gpio@d0018100 { +@@ -51,3 +68,17 @@ Example: + #interrupt-cells = <2>; + interrupts = <16>, <17>, <18>, <19>; + }; ++ ++ gpio1: gpio@18140 { ++ compatible = "marvell,orion-gpio"; ++ reg = <0x18140 0x40>, <0x181c8 0x08>; ++ reg-names = "gpio", "pwm"; ++ ngpios = <17>; ++ gpio-controller; ++ #gpio-cells = <2>; ++ #pwm-cells = <2>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ interrupts = <87>, <88>, <89>; ++ clocks = <&coreclk 0>; ++ }; diff --git a/target/linux/mvebu/patches-4.4/204-mvebu_xp_add_pwm_properties_to_dtsi_files.patch b/target/linux/mvebu/patches-4.4/204-mvebu_xp_add_pwm_properties_to_dtsi_files.patch new file mode 100644 index 0000000000..69bec6c2a0 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/204-mvebu_xp_add_pwm_properties_to_dtsi_files.patch @@ -0,0 +1,149 @@ +Add properties to the gpio nodes to allow them to be also used +as pwm lines. + +Signed-off-by: Andrew Lunn <andrew@lunn.ch> +--- + arch/arm/boot/dts/armada-370.dtsi | 10 ++++++++-- + arch/arm/boot/dts/armada-xp-mv78230.dtsi | 10 ++++++++-- + arch/arm/boot/dts/armada-xp-mv78260.dtsi | 8 ++++++-- + arch/arm/boot/dts/armada-xp-mv78460.dtsi | 10 ++++++++-- + 4 files changed, 30 insertions(+), 8 deletions(-) + +--- a/arch/arm/boot/dts/armada-370.dtsi ++++ b/arch/arm/boot/dts/armada-370.dtsi +@@ -162,24 +162,30 @@ + + gpio0: gpio@18100 { + compatible = "marvell,orion-gpio"; +- reg = <0x18100 0x40>; ++ reg = <0x18100 0x40>, <0x181c0 0x08>; ++ reg-names = "gpio", "pwm"; + ngpios = <32>; + gpio-controller; + #gpio-cells = <2>; ++ #pwm-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <82>, <83>, <84>, <85>; ++ clocks = <&coreclk 0>; + }; + + gpio1: gpio@18140 { + compatible = "marvell,orion-gpio"; +- reg = <0x18140 0x40>; ++ reg = <0x18140 0x40>, <0x181c8 0x08>; ++ reg-names = "gpio", "pwm"; + ngpios = <32>; + gpio-controller; + #gpio-cells = <2>; ++ #pwm-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <87>, <88>, <89>, <90>; ++ clocks = <&coreclk 0>; + }; + + gpio2: gpio@18180 { +--- a/arch/arm/boot/dts/armada-xp-mv78230.dtsi ++++ b/arch/arm/boot/dts/armada-xp-mv78230.dtsi +@@ -203,24 +203,30 @@ + internal-regs { + gpio0: gpio@18100 { + compatible = "marvell,orion-gpio"; +- reg = <0x18100 0x40>; ++ reg = <0x18100 0x40>, <0x181c0 0x08>; ++ reg-names = "gpio", "pwm"; + ngpios = <32>; + gpio-controller; + #gpio-cells = <2>; ++ #pwm-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <82>, <83>, <84>, <85>; ++ clocks = <&coreclk 0>; + }; + + gpio1: gpio@18140 { + compatible = "marvell,orion-gpio"; +- reg = <0x18140 0x40>; ++ reg = <0x18140 0x40>, <0x181c8 0x08>; ++ reg-names = "gpio", "pwm"; + ngpios = <17>; + gpio-controller; + #gpio-cells = <2>; ++ #pwm-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <87>, <88>, <89>; ++ clocks = <&coreclk 0>; + }; + }; + }; +--- a/arch/arm/boot/dts/armada-xp-mv78260.dtsi ++++ b/arch/arm/boot/dts/armada-xp-mv78260.dtsi +@@ -286,24 +286,28 @@ + internal-regs { + gpio0: gpio@18100 { + compatible = "marvell,orion-gpio"; +- reg = <0x18100 0x40>; ++ reg = <0x18100 0x40>, <0x181c0 0x08>; ++ reg-names = "gpio", "pwm"; + ngpios = <32>; + gpio-controller; + #gpio-cells = <2>; ++ #pwm-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <82>, <83>, <84>, <85>; ++ clocks = <&coreclk 0>; + }; + + gpio1: gpio@18140 { + compatible = "marvell,orion-gpio"; +- reg = <0x18140 0x40>; ++ reg = <0x18140 0x40>, <0x181c8 0x08>; + ngpios = <32>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <87>, <88>, <89>, <90>; ++ clocks = <&coreclk 0>; + }; + + gpio2: gpio@18180 { +--- a/arch/arm/boot/dts/armada-xp-mv78460.dtsi ++++ b/arch/arm/boot/dts/armada-xp-mv78460.dtsi +@@ -324,24 +324,30 @@ + internal-regs { + gpio0: gpio@18100 { + compatible = "marvell,orion-gpio"; +- reg = <0x18100 0x40>; ++ reg = <0x18100 0x40>, <0x181c0 0x08>; ++ reg-names = "gpio", "pwm"; + ngpios = <32>; + gpio-controller; + #gpio-cells = <2>; ++ #pwm-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <82>, <83>, <84>, <85>; ++ clocks = <&coreclk 0>; + }; + + gpio1: gpio@18140 { + compatible = "marvell,orion-gpio"; +- reg = <0x18140 0x40>; ++ reg = <0x18140 0x40>, <0x181c8 0x08>; ++ reg-names = "gpio", "pwm"; + ngpios = <32>; + gpio-controller; + #gpio-cells = <2>; ++ #pwm-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + interrupts = <87>, <88>, <89>, <90>; ++ clocks = <&coreclk 0>; + }; + + gpio2: gpio@18180 { diff --git a/target/linux/mvebu/patches-4.4/205-arm_mvebu_enable_pwm_in_defconfig.patch b/target/linux/mvebu/patches-4.4/205-arm_mvebu_enable_pwm_in_defconfig.patch new file mode 100644 index 0000000000..b52c60ff9f --- /dev/null +++ b/target/linux/mvebu/patches-4.4/205-arm_mvebu_enable_pwm_in_defconfig.patch @@ -0,0 +1,18 @@ +Now that the gpio driver also supports PWM operation, enable +the PWM framework in mvebu_v7_defconfig. + +Signed-off-by: Andrew Lunn <andrew@lunn.ch> +--- + arch/arm/configs/mvebu_v7_defconfig | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/arm/configs/mvebu_v7_defconfig ++++ b/arch/arm/configs/mvebu_v7_defconfig +@@ -131,6 +131,7 @@ CONFIG_DMADEVICES=y + CONFIG_MV_XOR=y + # CONFIG_IOMMU_SUPPORT is not set + CONFIG_MEMORY=y ++CONFIG_PWM=y + CONFIG_EXT4_FS=y + CONFIG_ISO9660_FS=y + CONFIG_JOLIET=y diff --git a/target/linux/mvebu/patches-4.4/206-mvebu_wrt1900ac_use_pwm-fan_rather_than_gpio-fan.patch b/target/linux/mvebu/patches-4.4/206-mvebu_wrt1900ac_use_pwm-fan_rather_than_gpio-fan.patch new file mode 100644 index 0000000000..bff58e9b75 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/206-mvebu_wrt1900ac_use_pwm-fan_rather_than_gpio-fan.patch @@ -0,0 +1,28 @@ +The mvebu gpio driver can also perform PWM on some pins. Us the +pwm-fan driver to control the fan of the WRT1900AC, giving us fine +grain control over its speed and hence noise. + +Signed-off-by: Andrew Lunn <andrew@lunn.ch> +--- + arch/arm/boot/dts/armada-xp-wrt1900ac.dts | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +--- a/arch/arm/boot/dts/armada-xp-linksys-mamba.dts ++++ b/arch/arm/boot/dts/armada-xp-linksys-mamba.dts +@@ -428,13 +428,11 @@ + }; + }; + +- gpio_fan { ++ pwm_fan { + /* SUNON HA4010V4-0000-C99 */ +- compatible = "gpio-fan"; +- gpios = <&gpio0 24 0>; + +- gpio-fan,speed-map = <0 0 +- 4500 1>; ++ compatible = "pwm-fan"; ++ pwms = <&gpio0 24 4000 0>; + }; + + dsa@0 { diff --git a/target/linux/mvebu/patches-4.4/207-armada-385-rd-mtd-partitions.patch b/target/linux/mvebu/patches-4.4/207-armada-385-rd-mtd-partitions.patch new file mode 100644 index 0000000000..fc94d9af50 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/207-armada-385-rd-mtd-partitions.patch @@ -0,0 +1,19 @@ +--- a/arch/arm/boot/dts/armada-388-rd.dts ++++ b/arch/arm/boot/dts/armada-388-rd.dts +@@ -79,6 +79,16 @@ + compatible = "st,m25p128", "jedec,spi-nor"; + reg = <0>; /* Chip select 0 */ + spi-max-frequency = <108000000>; ++ ++ partition@0 { ++ label = "uboot"; ++ reg = <0 0x400000>; ++ }; ++ ++ partition@1 { ++ label = "firmware"; ++ reg = <0x400000 0xc00000>; ++ }; + }; + }; + diff --git a/target/linux/mvebu/patches-4.4/208-ARM-mvebu-385-ap-Add-partitions.patch b/target/linux/mvebu/patches-4.4/208-ARM-mvebu-385-ap-Add-partitions.patch new file mode 100644 index 0000000000..d2aaeef4aa --- /dev/null +++ b/target/linux/mvebu/patches-4.4/208-ARM-mvebu-385-ap-Add-partitions.patch @@ -0,0 +1,34 @@ +From 9861f93a59142a3131870df2521eb2deb73026d7 Mon Sep 17 00:00:00 2001 +From: Maxime Ripard <maxime.ripard@free-electrons.com> +Date: Tue, 13 Jan 2015 11:14:09 +0100 +Subject: [PATCH 2/2] ARM: mvebu: 385-ap: Add partitions + +Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> +--- + arch/arm/boot/dts/armada-385-db-ap.dts | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/arch/arm/boot/dts/armada-385-db-ap.dts ++++ b/arch/arm/boot/dts/armada-385-db-ap.dts +@@ -184,6 +184,21 @@ + marvell,nand-keep-config; + marvell,nand-enable-arbiter; + nand-on-flash-bbt; ++ ++ mtd0@00000000 { ++ label = "u-boot"; ++ reg = <0x00000000 0x00800000>; ++ }; ++ ++ mtd1@00800000 { ++ label = "kernel"; ++ reg = <0x00800000 0x00800000>; ++ }; ++ ++ mtd2@01000000 { ++ label = "ubi"; ++ reg = <0x01000000 0x3f000000>; ++ }; + }; + + usb3@f0000 { diff --git a/target/linux/mvebu/patches-4.4/209-clearfog_switch_node.patch b/target/linux/mvebu/patches-4.4/209-clearfog_switch_node.patch new file mode 100644 index 0000000000..55494d093b --- /dev/null +++ b/target/linux/mvebu/patches-4.4/209-clearfog_switch_node.patch @@ -0,0 +1,21 @@ +--- a/arch/arm/boot/dts/armada-388-clearfog.dts ++++ b/arch/arm/boot/dts/armada-388-clearfog.dts +@@ -430,6 +430,18 @@ + }; + }; + ++ mvsw61xx { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "marvell,88e6176"; ++ status = "okay"; ++ reg = <0x4>; ++ is-indirect; ++ ++ mii-bus = <&mdio>; ++ cpu-port-0 = <5>; ++ }; ++ + gpio-keys { + compatible = "gpio-keys"; + pinctrl-0 = <&rear_button_pins>; diff --git a/target/linux/mvebu/patches-4.4/210-ARM-dts-armada388-clearfog-add-SFP-module-support.patch b/target/linux/mvebu/patches-4.4/210-ARM-dts-armada388-clearfog-add-SFP-module-support.patch new file mode 100644 index 0000000000..9efcff905d --- /dev/null +++ b/target/linux/mvebu/patches-4.4/210-ARM-dts-armada388-clearfog-add-SFP-module-support.patch @@ -0,0 +1,84 @@ +From 63ff73593c2f5d3fc1cba479321d192caaca48aa Mon Sep 17 00:00:00 2001 +From: Russell King <rmk+kernel@arm.linux.org.uk> +Date: Sat, 12 Sep 2015 18:43:39 +0100 +Subject: [PATCH 738/744] ARM: dts: armada388-clearfog: add SFP module support + +Add SFP module support for Clearfog using the SFP phylink support. + +Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> +--- + arch/arm/boot/dts/armada-388-clearfog.dts | 44 ++++++++----------------------- + 1 file changed, 11 insertions(+), 33 deletions(-) + +--- a/arch/arm/boot/dts/armada-388-clearfog.dts ++++ b/arch/arm/boot/dts/armada-388-clearfog.dts +@@ -90,16 +90,12 @@ + }; + + ethernet@34000 { ++ managed = "in-band-status"; + phy-mode = "sgmii"; + buffer-manager = <&bm>; + bm,pool-long = <3>; + bm,pool-short = <1>; + status = "okay"; +- +- fixed-link { +- speed = <1000>; +- full-duplex; +- }; + }; + + i2c@11000 { +@@ -183,34 +179,6 @@ + output-low; + line-name = "m.2 devslp"; + }; +- sfp_los { +- /* SFP loss of signal */ +- gpio-hog; +- gpios = <12 GPIO_ACTIVE_HIGH>; +- input; +- line-name = "sfp-los"; +- }; +- sfp_tx_fault { +- /* SFP laser fault */ +- gpio-hog; +- gpios = <13 GPIO_ACTIVE_HIGH>; +- input; +- line-name = "sfp-tx-fault"; +- }; +- sfp_tx_disable { +- /* SFP transmit disable */ +- gpio-hog; +- gpios = <14 GPIO_ACTIVE_HIGH>; +- output-low; +- line-name = "sfp-tx-disable"; +- }; +- sfp_mod_def0 { +- /* SFP module present */ +- gpio-hog; +- gpios = <15 GPIO_ACTIVE_LOW>; +- input; +- line-name = "sfp-mod-def0"; +- }; + }; + + /* The MCP3021 is 100kHz clock only */ +@@ -374,6 +342,16 @@ + }; + }; + ++ sfp: sfp { ++ compatible = "sff,sfp"; ++ i2c-bus = <&i2c1>; ++ los-gpio = <&expander0 12 GPIO_ACTIVE_HIGH>; ++ moddef0-gpio = <&expander0 15 GPIO_ACTIVE_LOW>; ++ sfp,ethernet = <ð2>; ++ tx-disable-gpio = <&expander0 14 GPIO_ACTIVE_HIGH>; ++ tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>; ++ }; ++ + dsa@0 { + compatible = "marvell,dsa"; + dsa,ethernet = <ð1>; diff --git a/target/linux/mvebu/patches-4.4/300-reprobe_sfp_phy.patch b/target/linux/mvebu/patches-4.4/300-reprobe_sfp_phy.patch new file mode 100644 index 0000000000..42614effb8 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/300-reprobe_sfp_phy.patch @@ -0,0 +1,96 @@ +From 28baa5e2635285b178326b301f534ed95c65dd01 Mon Sep 17 00:00:00 2001 +From: Jonas Gorski <jonas.gorski@gmail.com> +Date: Thu, 29 Sep 2016 11:44:39 +0200 +Subject: [PATCH] sfp: retry phy probe if unsuccessful + +Some phys seem to take longer than 50 ms to come out of reset, so retry +until we find a phy. + +Signed-off-by: Jonas Gorski <jonas.gorski@gmail.com> +--- + drivers/net/phy/sfp.c | 38 +++++++++++++++++++++++++------------- + 1 file changed, 25 insertions(+), 13 deletions(-) + +--- a/drivers/net/phy/sfp.c ++++ b/drivers/net/phy/sfp.c +@@ -488,7 +488,7 @@ static void sfp_sm_phy_detach(struct sfp + sfp->mod_phy = NULL; + } + +-static void sfp_sm_probe_phy(struct sfp *sfp) ++static int sfp_sm_probe_phy(struct sfp *sfp) + { + struct phy_device *phy; + int err; +@@ -498,11 +498,11 @@ static void sfp_sm_probe_phy(struct sfp + phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR); + if (IS_ERR(phy)) { + dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy)); +- return; ++ return PTR_ERR(phy); + } + if (!phy) { +- dev_info(sfp->dev, "no PHY detected\n"); +- return; ++ dev_dbg(sfp->dev, "no PHY detected\n"); ++ return -EAGAIN; + } + + err = phylink_connect_phy(sfp->phylink, phy); +@@ -510,11 +510,13 @@ static void sfp_sm_probe_phy(struct sfp + phy_device_remove(phy); + phy_device_free(phy); + dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err); +- return; ++ return err; + } + + sfp->mod_phy = phy; + phy_start(phy); ++ ++ return 0; + } + + static void sfp_sm_link_up(struct sfp *sfp) +@@ -565,13 +567,6 @@ static void sfp_sm_mod_init(struct sfp * + { + sfp_module_tx_enable(sfp); + +- /* Wait t_init before indicating that the link is up, provided the +- * current state indicates no TX_FAULT. If TX_FAULT clears before +- * this time, that's fine too. +- */ +- sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES); +- sfp->sm_retries = 5; +- + if (sfp->phylink) { + /* Setting the serdes link mode is guesswork: there's no + * field in the EEPROM which indicates what mode should +@@ -587,9 +582,26 @@ static void sfp_sm_mod_init(struct sfp * + !sfp->id.base.e100_base_fx) { + phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z); + } else { ++ int ret; ++ + phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII); +- sfp_sm_probe_phy(sfp); ++ ++ ret = sfp_sm_probe_phy(sfp); ++ if (ret) { ++ if (ret == -EAGAIN) ++ sfp_sm_set_timer(sfp, T_PROBE_RETRY); ++ else ++ sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0); ++ return; ++ } + } ++ ++ /* Wait t_init before indicating that the link is up, provided the ++ * current state indicates no TX_FAULT. If TX_FAULT clears before ++ * this time, that's fine too. ++ */ ++ sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES); ++ sfp->sm_retries = 5; + } + } + diff --git a/target/linux/mvebu/patches-4.4/400-mvneta-tx-queue-workaround.patch b/target/linux/mvebu/patches-4.4/400-mvneta-tx-queue-workaround.patch new file mode 100644 index 0000000000..5dba311d93 --- /dev/null +++ b/target/linux/mvebu/patches-4.4/400-mvneta-tx-queue-workaround.patch @@ -0,0 +1,36 @@ +The hardware queue scheduling is apparently configured with fixed +priorities, which creates a nasty fairness issue where traffic from one +CPU can starve traffic from all other CPUs. + +Work around this issue by forcing all tx packets to go through one CPU, +until this issue is fixed properly. + +Signed-off-by: Felix Fietkau <nbd@nbd.name> +--- +--- a/drivers/net/ethernet/marvell/mvneta.c ++++ b/drivers/net/ethernet/marvell/mvneta.c +@@ -3984,6 +3984,16 @@ static int mvneta_ethtool_set_eee(struct + return phylink_ethtool_set_eee(pp->phylink, eee); + } + ++static u16 mvneta_select_queue(struct net_device *dev, struct sk_buff *skb, ++ void *accel_priv, ++ select_queue_fallback_t fallback) ++{ ++ /* XXX: hardware queue scheduling is broken, ++ * use only one queue until it is fixed */ ++ return 0; ++} ++ ++ + static const struct net_device_ops mvneta_netdev_ops = { + .ndo_open = mvneta_open, + .ndo_stop = mvneta_stop, +@@ -3994,6 +4004,7 @@ static const struct net_device_ops mvnet + .ndo_fix_features = mvneta_fix_features, + .ndo_get_stats64 = mvneta_get_stats64, + .ndo_do_ioctl = mvneta_ioctl, ++ .ndo_select_queue = mvneta_select_queue, + }; + + const struct ethtool_ops mvneta_eth_tool_ops = { |