From a139f9c9e9a8afd94ac0881a4d006eea2146e190 Mon Sep 17 00:00:00 2001 From: CN_SZTL Date: Sat, 1 Feb 2020 01:49:51 +0800 Subject: [PATCH] target: add support for p2w-r619ac --- package/firmware/ipq-wifi/Makefile | 4 +- .../ipq40xx/base-files/etc/board.d/01_leds | 5 + .../ipq40xx/base-files/etc/board.d/02_network | 7 + .../etc/hotplug.d/firmware/11-ath10k-caldata | 8 + .../base-files/lib/upgrade/platform.sh | 4 + .../arm/boot/dts/qcom-ipq4019-r619ac-128m.dts | 18 + .../arch/arm/boot/dts/qcom-ipq4019-r619ac.dts | 12 + .../arm/boot/dts/qcom-ipq4019-r619ac-128m.dts | 18 + .../arch/arm/boot/dts/qcom-ipq4019-r619ac.dts | 12 + target/linux/ipq40xx/image/Makefile | 29 + ...oc-qcom-spm-add-SCM-probe-dependency.patch | 27 + ...4019-use-v2-of-the-kpss-bringup-mech.patch | 97 + ...-ipq40xx-Fix-booting-secondary-cores.patch | 38 + ...4019-add-cpu-operating-points-for-cp.patch | 114 + ...RM-dts-qcom-add-gpio-ranges-property.patch | 70 + ...86-ipq40xx-fix-high-resolution-timer.patch | 29 + .../182-crypto-qce-fix-ctr-blocksize.patch | 11 + .../183-crypto-qce-update-iv.patch | 68 + ...e-allow-building-only-hashes-ciphers.patch | 91 + .../400-sdhci-msm-fix-clock.patch | 11 + .../700-net-add-qualcomm-mdio.patch | 225 ++ .../705-net-add-qualcomm-ar40xx-phy.patch | 2492 +++++++++++++++++ ...essedma-fixup-ethernet-driver-rx-bug.patch | 144 + ...xq-to-be-adaptive-of-cpus-and-netdev.patch | 223 ++ ...xq-to-be-adaptive-of-cpus-and-netdev.patch | 205 ++ .../716-essedma-reduce-write-reg.patch | 54 + ...xx-vqmmc-regulator-driver-to-KConfig.patch | 26 + ...4019-Add-SDHCI-VQMMC-LDO-regulator-n.patch | 33 + .../853-add-sdhci-msm-node-to-dts.patch | 41 + .../854-add-sdhci-reset-implementation.patch | 57 + ...essedma-disable-default-vlan-tagging.patch | 82 + ....11-encapusaltion-offloading-support.patch | 313 +++ .../998-ignore-stats-debug-info.patch | 19 + .../patches-4.14/999-fix-m25p-shutdown.patch | 34 + 34 files changed, 4620 insertions(+), 1 deletion(-) create mode 100644 target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4019-r619ac-128m.dts create mode 100644 target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4019-r619ac.dts create mode 100644 target/linux/ipq40xx/files-4.19/arch/arm/boot/dts/qcom-ipq4019-r619ac-128m.dts create mode 100644 target/linux/ipq40xx/files-4.19/arch/arm/boot/dts/qcom-ipq4019-r619ac.dts create mode 100644 target/linux/ipq40xx/patches-4.14/070-v4.20-soc-qcom-spm-add-SCM-probe-dependency.patch create mode 100644 target/linux/ipq40xx/patches-4.14/071-01-v4.20-ARM-dts-qcom-ipq4019-use-v2-of-the-kpss-bringup-mech.patch create mode 100644 target/linux/ipq40xx/patches-4.14/071-02-ipq40xx-Fix-booting-secondary-cores.patch create mode 100644 target/linux/ipq40xx/patches-4.14/072-v4.20-ARM-dts-qcom-ipq4019-add-cpu-operating-points-for-cp.patch create mode 100644 target/linux/ipq40xx/patches-4.14/080-ARM-dts-qcom-add-gpio-ranges-property.patch create mode 100644 target/linux/ipq40xx/patches-4.14/086-ipq40xx-fix-high-resolution-timer.patch create mode 100644 target/linux/ipq40xx/patches-4.14/182-crypto-qce-fix-ctr-blocksize.patch create mode 100644 target/linux/ipq40xx/patches-4.14/183-crypto-qce-update-iv.patch create mode 100644 target/linux/ipq40xx/patches-4.14/184-crypto-qce-allow-building-only-hashes-ciphers.patch create mode 100644 target/linux/ipq40xx/patches-4.14/400-sdhci-msm-fix-clock.patch create mode 100644 target/linux/ipq40xx/patches-4.14/700-net-add-qualcomm-mdio.patch create mode 100644 target/linux/ipq40xx/patches-4.14/705-net-add-qualcomm-ar40xx-phy.patch create mode 100644 target/linux/ipq40xx/patches-4.14/713-0001-essedma-fixup-ethernet-driver-rx-bug.patch create mode 100644 target/linux/ipq40xx/patches-4.14/713-0002-essedma-refine-txq-to-be-adaptive-of-cpus-and-netdev.patch create mode 100644 target/linux/ipq40xx/patches-4.14/715-essedma-refine-txq-to-be-adaptive-of-cpus-and-netdev.patch create mode 100644 target/linux/ipq40xx/patches-4.14/716-essedma-reduce-write-reg.patch create mode 100644 target/linux/ipq40xx/patches-4.14/851-add-ipq40xx-vqmmc-regulator-driver-to-KConfig.patch create mode 100644 target/linux/ipq40xx/patches-4.14/852-ARM-dts-qcom-ipq4019-Add-SDHCI-VQMMC-LDO-regulator-n.patch create mode 100644 target/linux/ipq40xx/patches-4.14/853-add-sdhci-msm-node-to-dts.patch create mode 100644 target/linux/ipq40xx/patches-4.14/854-add-sdhci-reset-implementation.patch create mode 100644 target/linux/ipq40xx/patches-4.14/901-essedma-disable-default-vlan-tagging.patch create mode 100644 target/linux/ipq40xx/patches-4.14/950-add-tx-hw802.11-encapusaltion-offloading-support.patch create mode 100644 target/linux/ipq40xx/patches-4.14/998-ignore-stats-debug-info.patch create mode 100644 target/linux/ipq40xx/patches-4.14/999-fix-m25p-shutdown.patch diff --git a/package/firmware/ipq-wifi/Makefile b/package/firmware/ipq-wifi/Makefile index 610dee18e0..56c1f2e9c6 100644 --- a/package/firmware/ipq-wifi/Makefile +++ b/package/firmware/ipq-wifi/Makefile @@ -31,7 +31,8 @@ ALLWIFIBOARDS:= \ engenius_emd1 \ ezviz_cs-w3-wd1200g-eup \ linksys_ea8300 \ - qxwlan_e2600ac + qxwlan_e2600ac \ + p2w_r619ac ALLWIFIPACKAGES:=$(foreach BOARD,$(ALLWIFIBOARDS),ipq-wifi-$(BOARD)) @@ -99,5 +100,6 @@ $(eval $(call generate-ipq-wifi-package,engenius_emd1,EnGenius EMD1)) $(eval $(call generate-ipq-wifi-package,ezviz_cs-w3-wd1200g-eup,EZVIZ CS-W3-WD1200G EUP)) $(eval $(call generate-ipq-wifi-package,linksys_ea8300,Linksys EA8300)) $(eval $(call generate-ipq-wifi-package,qxwlan_e2600ac,Qxwlan E2600AC)) +$(eval $(call generate-ipq-wifi-package,p2w_r619ac,board-p2w_r619ac.qca4019,P&W R619AC)) $(foreach PACKAGE,$(ALLWIFIPACKAGES),$(eval $(call BuildPackage,$(PACKAGE)))) diff --git a/target/linux/ipq40xx/base-files/etc/board.d/01_leds b/target/linux/ipq40xx/base-files/etc/board.d/01_leds index 0326bab382..3de6b6ce5c 100755 --- a/target/linux/ipq40xx/base-files/etc/board.d/01_leds +++ b/target/linux/ipq40xx/base-files/etc/board.d/01_leds @@ -14,6 +14,11 @@ case "$board" in alfa-network,ap120c-ac) ucidef_set_led_netdev "wan" "WAN" "${boardname}:amber:wan" "eth1" ;; +p2w,r619ac |\ +p2w,r619ac-128m) + ucidef_set_led_wlan "wlan2g" "WLAN2G" "r619ac:blue:wlan2g" "phy0tpt" + ucidef_set_led_wlan "wlan5g" "WLAN5G" "r619ac:blue:wlan5g" "phy1tpt" + ;; asus,rt-ac58u) ucidef_set_led_netdev "wan" "WAN" "${boardname}:blue:wan" "eth1" ucidef_set_led_switch "lan" "LAN" "${boardname}:blue:lan" "switch0" "0x1e" diff --git a/target/linux/ipq40xx/base-files/etc/board.d/02_network b/target/linux/ipq40xx/base-files/etc/board.d/02_network index 4b636fc701..07a00bb3a9 100755 --- a/target/linux/ipq40xx/base-files/etc/board.d/02_network +++ b/target/linux/ipq40xx/base-files/etc/board.d/02_network @@ -29,6 +29,8 @@ ipq40xx_setup_interfaces() ucidef_set_interfaces_lan_wan "eth1" "eth0" ;; asus,rt-ac58u|\ + p2w,r619ac|\ + p2w,r619ac-128m|\ zyxel,nbg6617) ucidef_set_interfaces_lan_wan "eth0" "eth1" ucidef_add_switch "switch0" \ @@ -102,6 +104,11 @@ ipq40xx_setup_macs() lan_mac=$(mtd_get_mac_binary_ubi Factory 0x5006) label_mac=$wan_mac ;; + p2w,r619ac|\ + p2w,r619ac-128m) + wan_mac=$(cat /sys/class/net/eth0/address) + lan_mac=$(macaddr_add "$wan_mac" 1) + ;; cilab,meshpoint-one) label_mac=$(mtd_get_mac_binary "ART" 0x1006) ;; diff --git a/target/linux/ipq40xx/base-files/etc/hotplug.d/firmware/11-ath10k-caldata b/target/linux/ipq40xx/base-files/etc/hotplug.d/firmware/11-ath10k-caldata index 957bee12ab..36d08377e6 100644 --- a/target/linux/ipq40xx/base-files/etc/hotplug.d/firmware/11-ath10k-caldata +++ b/target/linux/ipq40xx/base-files/etc/hotplug.d/firmware/11-ath10k-caldata @@ -39,6 +39,10 @@ case "$FIRMWARE" in openmesh,a62) caldata_extract "0:ART" 0x9000 0x2f20 ;; + p2w,r619ac |\ + p2w,r619ac-128m) + ath10kcal_extract "ART" 36864 12064 + ;; esac ;; "ath10k/pre-cal-ahb-a000000.wifi.bin") @@ -49,6 +53,8 @@ case "$FIRMWARE" in ezviz,cs-w3-wd1200g-eup |\ glinet,gl-b1300 |\ linksys,ea6350v3 |\ + p2w,r619ac |\ + p2w,r619ac-128m |\ qcom,ap-dk01.1-c1) caldata_extract "ART" 0x1000 0x2f20 ;; @@ -128,6 +134,8 @@ case "$FIRMWARE" in ezviz,cs-w3-wd1200g-eup |\ glinet,gl-b1300 |\ linksys,ea6350v3 |\ + p2w,r619ac |\ + p2w,r619ac-128m |\ qcom,ap-dk01.1-c1) caldata_extract "ART" 0x5000 0x2f20 ;; diff --git a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh index cdf7a2b8f2..725d7a9fc5 100644 --- a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh +++ b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh @@ -67,6 +67,10 @@ platform_do_upgrade() { fi nand_do_upgrade "$1" ;; + p2w,r619ac|\ + p2w,r619ac-128m) + nand_do_upgrade "$1" + ;; asus,map-ac2200) CI_KERNPART="linux" nand_do_upgrade "$1" diff --git a/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4019-r619ac-128m.dts b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4019-r619ac-128m.dts new file mode 100644 index 0000000000..14176a2c16 --- /dev/null +++ b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4019-r619ac-128m.dts @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT + +#include "qcom-ipq4019-r619ac.dtsi" + +/ { + model = "P&W R619AC 128M"; + compatible = "p2w,r619ac-128m"; + + chosen { + bootargs-append = " root=/dev/ubiblock0_1 rootfstype=squashfs"; + }; +}; + +&rootfs_part1 { + reg = <0x0 0x8000000>; +}; + +/delete-node/ &rootfs_part2; \ No newline at end of file diff --git a/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4019-r619ac.dts b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4019-r619ac.dts new file mode 100644 index 0000000000..e2adfc3552 --- /dev/null +++ b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4019-r619ac.dts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT + +#include "qcom-ipq4019-r619ac.dtsi" + +/ { + model = "P&W R619AC"; + compatible = "p2w,r619ac"; + + chosen { + bootargs-append = " root=/dev/ubiblock0_1 rootfstype=squashfs"; + }; +}; \ No newline at end of file diff --git a/target/linux/ipq40xx/files-4.19/arch/arm/boot/dts/qcom-ipq4019-r619ac-128m.dts b/target/linux/ipq40xx/files-4.19/arch/arm/boot/dts/qcom-ipq4019-r619ac-128m.dts new file mode 100644 index 0000000000..b51378a7ed --- /dev/null +++ b/target/linux/ipq40xx/files-4.19/arch/arm/boot/dts/qcom-ipq4019-r619ac-128m.dts @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT + +#include "qcom-ipq4019-r619ac.dtsi" + +/ { + model = "P&W R619AC 128M"; + compatible = "p2w,r619ac-128m"; + + chosen { + bootargs-append = " root=/dev/ubiblock0_1 rootfstype=squashfs"; + }; +}; + +&rootfs_part1 { + reg = <0x0 0x8000000>; +}; + +/delete-node/ &rootfs_part2; diff --git a/target/linux/ipq40xx/files-4.19/arch/arm/boot/dts/qcom-ipq4019-r619ac.dts b/target/linux/ipq40xx/files-4.19/arch/arm/boot/dts/qcom-ipq4019-r619ac.dts new file mode 100644 index 0000000000..31010dd179 --- /dev/null +++ b/target/linux/ipq40xx/files-4.19/arch/arm/boot/dts/qcom-ipq4019-r619ac.dts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT + +#include "qcom-ipq4019-r619ac.dtsi" + +/ { + model = "P&W R619AC"; + compatible = "p2w,r619ac"; + + chosen { + bootargs-append = " root=/dev/ubiblock0_1 rootfstype=squashfs"; + }; +}; diff --git a/target/linux/ipq40xx/image/Makefile b/target/linux/ipq40xx/image/Makefile index bbe4df9df4..6ee169d22b 100644 --- a/target/linux/ipq40xx/image/Makefile +++ b/target/linux/ipq40xx/image/Makefile @@ -484,6 +484,35 @@ define Device/qcom_ap-dk01.1-c1 endef TARGET_DEVICES += qcom_ap-dk01.1-c1 +define Device/p2w_r619ac + $(call Device/FitzImage) + $(call Device/UbiFit) + DEVICE_VENDOR := P&W + DEVICE_MODEL := R619AC + DEVICE_DTS := qcom-ipq4019-r619ac + DEVICE_DTS_CONFIG := config@10 + BLOCKSIZE := 128k + PAGESIZE := 2048 + IMAGES += nand-factory.bin + IMAGE/nand-factory.bin := append-ubi | qsdk-ipq-factory-nand + DEVICE_PACKAGES := ipq-wifi-p2w_r619ac +endef +TARGET_DEVICES += p2w_r619ac + +define Device/p2w_r619ac-128m + $(call Device/FitzImage) + $(call Device/UbiFit) + DEVICE_VENDOR := P&W + DEVICE_MODEL := R619AC + DEVICE_VARIANT := 128M + DEVICE_DTS := qcom-ipq4019-r619ac-128m + DEVICE_DTS_CONFIG := config@10 + BLOCKSIZE := 128k + PAGESIZE := 2048 + DEVICE_PACKAGES := ipq-wifi-p2w_r619ac +endef +TARGET_DEVICES += p2w_r619ac-128m + define Device/qcom_ap-dk04.1-c1 $(call Device/FitImage) $(call Device/UbiFit) diff --git a/target/linux/ipq40xx/patches-4.14/070-v4.20-soc-qcom-spm-add-SCM-probe-dependency.patch b/target/linux/ipq40xx/patches-4.14/070-v4.20-soc-qcom-spm-add-SCM-probe-dependency.patch new file mode 100644 index 0000000000..d0b520f389 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/070-v4.20-soc-qcom-spm-add-SCM-probe-dependency.patch @@ -0,0 +1,27 @@ +From 61a3bd10082b0e861b4e1bc451a92e20181a52f5 Mon Sep 17 00:00:00 2001 +From: Felix Fietkau +Date: Mon, 23 Jul 2018 16:17:35 +0200 +Subject: [PATCH] soc: qcom: spm: add SCM probe dependency + +Check for SCM availability before attempting to use SPM. SPM probe will +fail otherwise. + +Signed-off-by: Felix Fietkau +Signed-off-by: John Crispin +Signed-off-by: Andy Gross +--- + drivers/soc/qcom/spm.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/drivers/soc/qcom/spm.c ++++ b/drivers/soc/qcom/spm.c +@@ -219,6 +219,9 @@ static int __init qcom_cpuidle_init(stru + cpumask_t mask; + bool use_scm_power_down = false; + ++ if (!qcom_scm_is_available()) ++ return -EPROBE_DEFER; ++ + for (i = 0; ; i++) { + state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i); + if (!state_node) diff --git a/target/linux/ipq40xx/patches-4.14/071-01-v4.20-ARM-dts-qcom-ipq4019-use-v2-of-the-kpss-bringup-mech.patch b/target/linux/ipq40xx/patches-4.14/071-01-v4.20-ARM-dts-qcom-ipq4019-use-v2-of-the-kpss-bringup-mech.patch new file mode 100644 index 0000000000..b1d69ca0ee --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/071-01-v4.20-ARM-dts-qcom-ipq4019-use-v2-of-the-kpss-bringup-mech.patch @@ -0,0 +1,97 @@ +From 233c77d4f1d12e4337fba1146d5197f4c0f9107d Mon Sep 17 00:00:00 2001 +From: Matthew McClintock +Date: Wed, 25 Jul 2018 10:37:45 +0200 +Subject: [PATCH] ARM: dts: qcom: ipq4019: use v2 of the kpss bringup mechanism + +v1 was the incorrect choice here and sometimes the board +would not come up properly. + +Signed-off-by: Matthew McClintock +Signed-off-by: Christian Lamparter +Signed-off-by: John Crispin +Signed-off-by: Andy Gross +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 25 +++++++++++++++++-------- + 1 file changed, 17 insertions(+), 8 deletions(-) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -52,7 +52,8 @@ + cpu@0 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; +- enable-method = "qcom,kpss-acc-v1"; ++ enable-method = "qcom,kpss-acc-v2"; ++ next-level-cache = <&L2>; + qcom,acc = <&acc0>; + qcom,saw = <&saw0>; + reg = <0x0>; +@@ -71,7 +72,8 @@ + cpu@1 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; +- enable-method = "qcom,kpss-acc-v1"; ++ enable-method = "qcom,kpss-acc-v2"; ++ next-level-cache = <&L2>; + qcom,acc = <&acc1>; + qcom,saw = <&saw1>; + reg = <0x1>; +@@ -90,7 +92,8 @@ + cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; +- enable-method = "qcom,kpss-acc-v1"; ++ enable-method = "qcom,kpss-acc-v2"; ++ next-level-cache = <&L2>; + qcom,acc = <&acc2>; + qcom,saw = <&saw2>; + reg = <0x2>; +@@ -109,7 +112,8 @@ + cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a7"; +- enable-method = "qcom,kpss-acc-v1"; ++ enable-method = "qcom,kpss-acc-v2"; ++ next-level-cache = <&L2>; + qcom,acc = <&acc3>; + qcom,saw = <&saw3>; + reg = <0x3>; +@@ -124,6 +128,11 @@ + >; + clock-latency = <256000>; + }; ++ ++ L2: l2-cache { ++ compatible = "cache"; ++ cache-level = <2>; ++ }; + }; + + pmu { +@@ -292,22 +301,22 @@ + }; + + acc0: clock-controller@b088000 { +- compatible = "qcom,kpss-acc-v1"; ++ compatible = "qcom,kpss-acc-v2"; + reg = <0x0b088000 0x1000>, <0xb008000 0x1000>; + }; + + acc1: clock-controller@b098000 { +- compatible = "qcom,kpss-acc-v1"; ++ compatible = "qcom,kpss-acc-v2"; + reg = <0x0b098000 0x1000>, <0xb008000 0x1000>; + }; + + acc2: clock-controller@b0a8000 { +- compatible = "qcom,kpss-acc-v1"; ++ compatible = "qcom,kpss-acc-v2"; + reg = <0x0b0a8000 0x1000>, <0xb008000 0x1000>; + }; + + acc3: clock-controller@b0b8000 { +- compatible = "qcom,kpss-acc-v1"; ++ compatible = "qcom,kpss-acc-v2"; + reg = <0x0b0b8000 0x1000>, <0xb008000 0x1000>; + }; + diff --git a/target/linux/ipq40xx/patches-4.14/071-02-ipq40xx-Fix-booting-secondary-cores.patch b/target/linux/ipq40xx/patches-4.14/071-02-ipq40xx-Fix-booting-secondary-cores.patch new file mode 100644 index 0000000000..d37a8cb6ba --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/071-02-ipq40xx-Fix-booting-secondary-cores.patch @@ -0,0 +1,38 @@ +From 8a4540321e8bcf7a5b485c332a2e78f3501c78ed Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 29 Nov 2018 22:29:36 +0100 +Subject: [PATCH] ipq40xx: Fix booting secondary cores + +Add the second part of old 071-qcom-ipq4019-use-v2-of-the-kpss-bringup-mechanism.patch +We dont modify the patch itself as its upstream and this change is not. + +Originally added by Mantas Pucka Mantas Pucka + +Signed-off-by: Robert Marko +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -132,6 +132,7 @@ + L2: l2-cache { + compatible = "cache"; + cache-level = <2>; ++ qcom,saw = <&saw_l2>; + }; + }; + +@@ -344,6 +345,12 @@ + regulator; + }; + ++ saw_l2: regulator@b012000 { ++ compatible = "qcom,saw2"; ++ reg = <0xb012000 0x1000>; ++ regulator; ++ }; ++ + blsp1_uart1: serial@78af000 { + compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm"; + reg = <0x78af000 0x200>; diff --git a/target/linux/ipq40xx/patches-4.14/072-v4.20-ARM-dts-qcom-ipq4019-add-cpu-operating-points-for-cp.patch b/target/linux/ipq40xx/patches-4.14/072-v4.20-ARM-dts-qcom-ipq4019-add-cpu-operating-points-for-cp.patch new file mode 100644 index 0000000000..991b366eda --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/072-v4.20-ARM-dts-qcom-ipq4019-add-cpu-operating-points-for-cp.patch @@ -0,0 +1,114 @@ +From bcb9ab4c2917e92114d2f4c2b1da97cdf15b471b Mon Sep 17 00:00:00 2001 +From: Matthew McClintock +Date: Wed, 25 Jul 2018 10:37:46 +0200 +Subject: [PATCH] ARM: dts: qcom: ipq4019: add cpu operating points for cpufreq + support + +This adds some operating points for cpu frequeny scaling + +Signed-off-by: Matthew McClintock +Signed-off-by: John Crispin +Signed-off-by: Andy Gross +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 58 ++++++++++++++--------------- + 1 file changed, 30 insertions(+), 28 deletions(-) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -59,14 +59,8 @@ + reg = <0x0>; + clocks = <&gcc GCC_APPS_CLK_SRC>; + clock-frequency = <0>; +- operating-points = < +- /* kHz uV (fixed) */ +- 48000 1100000 +- 200000 1100000 +- 500000 1100000 +- 716000 1100000 +- >; + clock-latency = <256000>; ++ operating-points-v2 = <&cpu0_opp_table>; + }; + + cpu@1 { +@@ -79,14 +73,8 @@ + reg = <0x1>; + clocks = <&gcc GCC_APPS_CLK_SRC>; + clock-frequency = <0>; +- operating-points = < +- /* kHz uV (fixed) */ +- 48000 1100000 +- 200000 1100000 +- 500000 1100000 +- 666000 1100000 +- >; + clock-latency = <256000>; ++ operating-points-v2 = <&cpu0_opp_table>; + }; + + cpu@2 { +@@ -99,14 +87,8 @@ + reg = <0x2>; + clocks = <&gcc GCC_APPS_CLK_SRC>; + clock-frequency = <0>; +- operating-points = < +- /* kHz uV (fixed) */ +- 48000 1100000 +- 200000 1100000 +- 500000 1100000 +- 666000 1100000 +- >; + clock-latency = <256000>; ++ operating-points-v2 = <&cpu0_opp_table>; + }; + + cpu@3 { +@@ -119,14 +101,8 @@ + reg = <0x3>; + clocks = <&gcc GCC_APPS_CLK_SRC>; + clock-frequency = <0>; +- operating-points = < +- /* kHz uV (fixed) */ +- 48000 1100000 +- 200000 1100000 +- 500000 1100000 +- 666000 1100000 +- >; + clock-latency = <256000>; ++ operating-points-v2 = <&cpu0_opp_table>; + }; + + L2: l2-cache { +@@ -136,6 +112,32 @@ + }; + }; + ++ cpu0_opp_table: opp_table0 { ++ compatible = "operating-points-v2"; ++ opp-shared; ++ ++ opp-48000000 { ++ opp-hz = /bits/ 64 <48000000>; ++ opp-microvolt = <1100000>; ++ clock-latency-ns = <256000>; ++ }; ++ opp-200000000 { ++ opp-hz = /bits/ 64 <200000000>; ++ opp-microvolt = <1100000>; ++ clock-latency-ns = <256000>; ++ }; ++ opp-500000000 { ++ opp-hz = /bits/ 64 <500000000>; ++ opp-microvolt = <1100000>; ++ clock-latency-ns = <256000>; ++ }; ++ opp-716000000 { ++ opp-hz = /bits/ 64 <716000000>; ++ opp-microvolt = <1100000>; ++ clock-latency-ns = <256000>; ++ }; ++ }; ++ + pmu { + compatible = "arm,cortex-a7-pmu"; + interrupts = +X-Patchwork-Id: 917856 +Message-Id: <0ae3376606a89bcdf3fe753a5c967f7103699e09.1526935804.git.chunkeey@gmail.com> +To: linux-gpio@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org +Cc: Bjorn Andersson , + Linus Walleij , + Stephen Boyd , David Brown , + Rob Herring , Mark Rutland , + Andy Gross , + Sven Eckelmann +Date: Mon, 21 May 2018 22:57:38 +0200 +From: Christian Lamparter +List-Id: + +This patch adds the gpio-ranges property to almost all of +the Qualcomm ARM platforms that utilize the pinctrl-msm +framework. + +The gpio-ranges property is part of the gpiolib subsystem. +As a result, the binding text is available in section +"2.1 gpio- and pin-controller interaction" of +Documentation/devicetree/bindings/gpio/gpio.txt + +For more information please see the patch titled: +"pinctrl: msm: fix gpio-hog related boot issues" from +this series. + +Reported-by: Sven Eckelmann +Tested-by: Sven Eckelmann [ipq4019] +Reviewed-by: Bjorn Andersson +Signed-off-by: Christian Lamparter +Reviewed-by: Linus Walleij +--- +To help with git bisect, the DT update patch has been intentionally +placed after the "pinctrl: msm: fix gpio-hog related boot issues". +Otherwise - if the order was reveresed - and bisect decides to split +between these two patches, the gpiochip_add_pin_ranges() function +will be executed twice with the same parameters for the same pinctrl. +--- + arch/arm/boot/dts/qcom-apq8064.dtsi | 1 + + arch/arm/boot/dts/qcom-apq8084.dtsi | 1 + + arch/arm/boot/dts/qcom-ipq4019.dtsi | 1 + + arch/arm/boot/dts/qcom-ipq8064.dtsi | 1 + + arch/arm/boot/dts/qcom-mdm9615.dtsi | 1 + + arch/arm/boot/dts/qcom-msm8660.dtsi | 1 + + arch/arm/boot/dts/qcom-msm8960.dtsi | 1 + + arch/arm/boot/dts/qcom-msm8974.dtsi | 1 + + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 3 ++- + arch/arm64/boot/dts/qcom/msm8916.dtsi | 1 + + arch/arm64/boot/dts/qcom/msm8992.dtsi | 1 + + arch/arm64/boot/dts/qcom/msm8994.dtsi | 1 + + arch/arm64/boot/dts/qcom/msm8996.dtsi | 1 + + 13 files changed, 14 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -206,6 +206,7 @@ + compatible = "qcom,ipq4019-pinctrl"; + reg = <0x01000000 0x300000>; + gpio-controller; ++ gpio-ranges = <&tlmm 0 0 100>; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; diff --git a/target/linux/ipq40xx/patches-4.14/086-ipq40xx-fix-high-resolution-timer.patch b/target/linux/ipq40xx/patches-4.14/086-ipq40xx-fix-high-resolution-timer.patch new file mode 100644 index 0000000000..b45182271a --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/086-ipq40xx-fix-high-resolution-timer.patch @@ -0,0 +1,29 @@ +From 09f145f417a5d64d6b8d4476699dfb0eccc6c784 Mon Sep 17 00:00:00 2001 +From: Abhishek Sahu +Date: Tue, 7 May 2019 10:14:05 +0300 +Subject: [PATCH] ipq40xx: fix high resolution timer + +Cherry-picked from CAF QSDK repo. +Original commit message: +The kernel is failing in switching the timer for high resolution +mode and clock source operates in 10ms resolution. The always-on +property needs to be given for timer device tree node to make +clock source working in 1ns resolution. + +Change-Id: I7c00b3c74d97c2a30ac9f05e18b511a0550fd459 +Signed-off-by: Abhishek Sahu +Signed-off-by: Pavel Kubelun +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -171,6 +171,7 @@ + <1 4 0xf08>, + <1 1 0xf08>; + clock-frequency = <48000000>; ++ always-on; + }; + + soc { diff --git a/target/linux/ipq40xx/patches-4.14/182-crypto-qce-fix-ctr-blocksize.patch b/target/linux/ipq40xx/patches-4.14/182-crypto-qce-fix-ctr-blocksize.patch new file mode 100644 index 0000000000..832d679c7c --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/182-crypto-qce-fix-ctr-blocksize.patch @@ -0,0 +1,11 @@ +--- a/drivers/crypto/qce/ablkcipher.c 2019-10-21 17:18:47.311684603 -0300 ++++ b/drivers/crypto/qce/ablkcipher.c 2019-10-21 17:20:32.080765945 -0300 +@@ -292,7 +292,7 @@ + .name = "ctr(aes)", + .drv_name = "ctr-aes-qce", + .blocksize = AES_BLOCK_SIZE, +- .ivsize = AES_BLOCK_SIZE, ++ .ivsize = 1, + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + }, diff --git a/target/linux/ipq40xx/patches-4.14/183-crypto-qce-update-iv.patch b/target/linux/ipq40xx/patches-4.14/183-crypto-qce-update-iv.patch new file mode 100644 index 0000000000..a8e979ed3e --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/183-crypto-qce-update-iv.patch @@ -0,0 +1,68 @@ +diff --git a/drivers/crypto/qce/ablkcipher.c b/drivers/crypto/qce/ablkcipher.c +index 7a98bf5cc967..b935ce0acc1c 100644 +--- a/drivers/crypto/qce/ablkcipher.c ++++ b/drivers/crypto/qce/ablkcipher.c +@@ -14,6 +14,20 @@ + + static LIST_HEAD(ablkcipher_algs); + ++static void qce_update_ctr_iv(u8 *iv, unsigned int ivsize, u32 add) ++{ ++ __be32 *a = (__be32 *)(iv + ivsize); ++ u32 b; ++ ++ for (; ivsize >= 4; ivsize -= 4) { ++ b = be32_to_cpu(*--a) + add; ++ *a = cpu_to_be32(b); ++ if (b >= add) ++ return; ++ add = 1; ++ } ++} ++ + static void qce_ablkcipher_done(void *data) + { + struct crypto_async_request *async_req = data; +@@ -39,6 +53,18 @@ static void qce_ablkcipher_done(void *data) + dma_unmap_sg(qce->dev, rctx->src_sg, rctx->src_nents, dir_src); + dma_unmap_sg(qce->dev, rctx->dst_sg, rctx->dst_nents, dir_dst); + ++ if (IS_CBC(rctx->flags)) { ++ if (IS_ENCRYPT(rctx->flags)) ++ sg_pcopy_to_buffer(rctx->dst_sg, rctx->dst_nents, ++ rctx->iv, rctx->ivsize, ++ rctx->cryptlen - rctx->ivsize); ++ else ++ memcpy(rctx->iv, rctx->saved_iv, rctx->ivsize); ++ } else if (IS_CTR(rctx->flags) && IS_AES(rctx->flags)) { ++ qce_update_ctr_iv(rctx->iv, rctx->ivsize, ++ DIV_ROUND_UP(rctx->cryptlen, AES_BLOCK_SIZE)); ++ } ++ + sg_free_table(&rctx->dst_tbl); + + error = qce_check_status(qce, &status); +@@ -131,6 +157,11 @@ qce_ablkcipher_async_req_handle(struct crypto_async_request *async_req) + + qce_dma_issue_pending(&qce->dma); + ++ if (IS_CBC(rctx->flags) && IS_DECRYPT(rctx->flags)) ++ sg_pcopy_to_buffer(rctx->src_sg, rctx->src_nents, ++ rctx->saved_iv, rctx->ivsize, ++ rctx->cryptlen - rctx->ivsize); ++ + ret = qce_start(async_req, tmpl->crypto_alg_type, req->nbytes, 0); + if (ret) + goto error_terminate; +diff --git a/drivers/crypto/qce/cipher.h b/drivers/crypto/qce/cipher.h +index 5cab8f0706a8..a919022e28df 100644 +--- a/drivers/crypto/qce/cipher.h ++++ b/drivers/crypto/qce/cipher.h +@@ -43,6 +43,7 @@ struct qce_cipher_reqctx { + struct sg_table src_tbl; + struct scatterlist *src_sg; + unsigned int cryptlen; ++ u8 saved_iv[QCE_MAX_IV_SIZE]; + }; + + static inline struct qce_alg_template *to_cipher_tmpl(struct crypto_tfm *tfm) diff --git a/target/linux/ipq40xx/patches-4.14/184-crypto-qce-allow-building-only-hashes-ciphers.patch b/target/linux/ipq40xx/patches-4.14/184-crypto-qce-allow-building-only-hashes-ciphers.patch new file mode 100644 index 0000000000..4b8a25604c --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/184-crypto-qce-allow-building-only-hashes-ciphers.patch @@ -0,0 +1,91 @@ +From 2f23be905522cc67505daaf4d94c0292dbddd315 Mon Sep 17 00:00:00 2001 +From: Eneas U de Queiroz +Date: Mon, 28 Oct 2019 15:17:19 -0300 +Subject: [PATCH] crypto: qce - allow building only hashes/ciphers + +Signed-off-by: Eneas U de Queiroz + +diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig +index 1fb622f2a87d..0dc4bbcfc092 100644 +--- a/drivers/crypto/Kconfig ++++ b/drivers/crypto/Kconfig +@@ -580,5 +580,13 @@ config CRYPTO_DEV_QCE + tristate "Qualcomm crypto engine accelerator" + depends on (ARCH_QCOM || COMPILE_TEST) && HAS_DMA && HAS_IOMEM ++ help ++ This driver supports Qualcomm crypto engine accelerator ++ hardware. To compile this driver as a module, choose M here. The ++ module will be called qcrypto. ++ ++config CRYPTO_DEV_QCE_BLKCIPHER ++ bool ++ depends on CRYPTO_DEV_QCE + select CRYPTO_AES + select CRYPTO_LIB_DES + select CRYPTO_ECB +@@ -580,10 +588,29 @@ config CRYPTO_DEV_QCE + select CRYPTO_XTS + select CRYPTO_CTR + select CRYPTO_BLKCIPHER +- help +- This driver supports Qualcomm crypto engine accelerator +- hardware. To compile this driver as a module, choose M here. The +- module will be called qcrypto. ++ ++config CRYPTO_DEV_QCE_SHA ++ bool ++ depends on CRYPTO_DEV_QCE ++ ++choice ++ prompt "Algorithms enabled for QCE acceleration" ++ default CRYPTO_DEV_QCE_ENABLE_ALL ++ depends on CRYPTO_DEV_QCE ++ ++ config CRYPTO_DEV_QCE_ENABLE_ALL ++ bool "All supported algorithms" ++ select CRYPTO_DEV_QCE_BLKCIPHER ++ select CRYPTO_DEV_QCE_SHA ++ ++ config CRYPTO_DEV_QCE_ENABLE_BLKCIPHER ++ bool "Block ciphers only" ++ select CRYPTO_DEV_QCE_BLKCIPHER ++ ++ config CRYPTO_DEV_QCE_ENABLE_SHA ++ bool "Hash/HMAC only" ++ select CRYPTO_DEV_QCE_SHA ++endchoice + + config CRYPTO_DEV_QCOM_RNG + tristate "Qualcomm Random Number Generator Driver" +diff --git a/drivers/crypto/qce/Makefile b/drivers/crypto/qce/Makefile +index 19a7f899acff..f6a411c255b8 100644 +--- a/drivers/crypto/qce/Makefile ++++ b/drivers/crypto/qce/Makefile +@@ -2,6 +2,7 @@ + obj-$(CONFIG_CRYPTO_DEV_QCE) += qcrypto.o + qcrypto-objs := core.o \ + common.o \ +- dma.o \ +- sha.o \ +- ablkcipher.o ++ dma.o ++ ++qcrypto-$(CONFIG_CRYPTO_DEV_QCE_SHA) += sha.o ++qcrypto-$(CONFIG_CRYPTO_DEV_QCE_BLKCIPHER) += ablkcipher.o +diff --git a/drivers/crypto/qce/core.c b/drivers/crypto/qce/core.c +index 08d4ce3bfddf..3428746f1869 100644 +--- a/drivers/crypto/qce/core.c ++++ b/drivers/crypto/qce/core.c +@@ -22,8 +22,12 @@ + #define QCE_QUEUE_LENGTH 1 + + static const struct qce_algo_ops *qce_ops[] = { ++#ifdef CONFIG_CRYPTO_DEV_QCE_BLKCIPHER + &ablkcipher_ops, ++#endif ++#ifdef CONFIG_CRYPTO_DEV_QCE_SHA + &ahash_ops, ++#endif + }; + + static void qce_unregister_algs(struct qce_device *qce) diff --git a/target/linux/ipq40xx/patches-4.14/400-sdhci-msm-fix-clock.patch b/target/linux/ipq40xx/patches-4.14/400-sdhci-msm-fix-clock.patch new file mode 100644 index 0000000000..e2b4bcfc03 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/400-sdhci-msm-fix-clock.patch @@ -0,0 +1,11 @@ +--- a/drivers/mmc/host/sdhci-msm.c ++++ b/drivers/mmc/host/sdhci-msm.c +@@ -1681,7 +1681,7 @@ MODULE_DEVICE_TABLE(of, sdhci_msm_dt_mat + + static const struct sdhci_ops sdhci_msm_ops = { + .reset = sdhci_reset, +- .set_clock = sdhci_msm_set_clock, ++ .set_clock = sdhci_set_clock, + .get_min_clock = sdhci_msm_get_min_clock, + .get_max_clock = sdhci_msm_get_max_clock, + .set_bus_width = sdhci_set_bus_width, diff --git a/target/linux/ipq40xx/patches-4.14/700-net-add-qualcomm-mdio.patch b/target/linux/ipq40xx/patches-4.14/700-net-add-qualcomm-mdio.patch new file mode 100644 index 0000000000..847e2e8701 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/700-net-add-qualcomm-mdio.patch @@ -0,0 +1,225 @@ +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -524,6 +524,13 @@ config XILINX_GMII2RGMII + the Reduced Gigabit Media Independent Interface(RGMII) between + Ethernet physical media devices and the Gigabit Ethernet controller. + ++config MDIO_IPQ40XX ++ tristate "Qualcomm Atheros ipq40xx MDIO interface" ++ depends on HAS_IOMEM && OF ++ ---help--- ++ This driver supports the MDIO interface found in Qualcomm ++ Atheros ipq40xx Soc chip. ++ + endif # PHYLIB + + config MICREL_KS8995MA +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -48,6 +48,7 @@ obj-$(CONFIG_MDIO_CAVIUM) += mdio-cavium + obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o + obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o + obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o ++obj-$(CONFIG_MDIO_IPQ40XX) += mdio-ipq40xx.o + obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o + obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o + obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o +--- /dev/null ++++ b/drivers/net/phy/mdio-ipq40xx.c +@@ -0,0 +1,196 @@ ++/* ++ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. ++ * ++ * Permission to use, copy, modify, and/or distribute this software for ++ * any purpose with or without fee is hereby granted, provided that the ++ * above copyright notice and this permission notice appear in all copies. ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT ++ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MDIO_CTRL_0_REG 0x40 ++#define MDIO_CTRL_1_REG 0x44 ++#define MDIO_CTRL_2_REG 0x48 ++#define MDIO_CTRL_3_REG 0x4c ++#define MDIO_CTRL_4_REG 0x50 ++#define MDIO_CTRL_4_ACCESS_BUSY BIT(16) ++#define MDIO_CTRL_4_ACCESS_START BIT(8) ++#define MDIO_CTRL_4_ACCESS_CODE_READ 0 ++#define MDIO_CTRL_4_ACCESS_CODE_WRITE 1 ++#define CTRL_0_REG_DEFAULT_VALUE 0x150FF ++ ++#define IPQ40XX_MDIO_RETRY 1000 ++#define IPQ40XX_MDIO_DELAY 10 ++ ++struct ipq40xx_mdio_data { ++ struct mii_bus *mii_bus; ++ void __iomem *membase; ++ struct device *dev; ++}; ++ ++static int ipq40xx_mdio_wait_busy(struct ipq40xx_mdio_data *am) ++{ ++ int i; ++ ++ for (i = 0; i < IPQ40XX_MDIO_RETRY; i++) { ++ unsigned int busy; ++ ++ busy = readl(am->membase + MDIO_CTRL_4_REG) & ++ MDIO_CTRL_4_ACCESS_BUSY; ++ if (!busy) ++ return 0; ++ ++ /* BUSY might take to be cleard by 15~20 times of loop */ ++ udelay(IPQ40XX_MDIO_DELAY); ++ } ++ ++ dev_err(am->dev, "%s: MDIO operation timed out\n", am->mii_bus->name); ++ ++ return -ETIMEDOUT; ++} ++ ++static int ipq40xx_mdio_read(struct mii_bus *bus, int mii_id, int regnum) ++{ ++ struct ipq40xx_mdio_data *am = bus->priv; ++ int value = 0; ++ unsigned int cmd = 0; ++ ++ lockdep_assert_held(&bus->mdio_lock); ++ ++ if (ipq40xx_mdio_wait_busy(am)) ++ return -ETIMEDOUT; ++ ++ /* issue the phy address and reg */ ++ writel((mii_id << 8) | regnum, am->membase + MDIO_CTRL_1_REG); ++ ++ cmd = MDIO_CTRL_4_ACCESS_START|MDIO_CTRL_4_ACCESS_CODE_READ; ++ ++ /* issue read command */ ++ writel(cmd, am->membase + MDIO_CTRL_4_REG); ++ ++ /* Wait read complete */ ++ if (ipq40xx_mdio_wait_busy(am)) ++ return -ETIMEDOUT; ++ ++ /* Read data */ ++ value = readl(am->membase + MDIO_CTRL_3_REG); ++ ++ return value; ++} ++ ++static int ipq40xx_mdio_write(struct mii_bus *bus, int mii_id, int regnum, ++ u16 value) ++{ ++ struct ipq40xx_mdio_data *am = bus->priv; ++ unsigned int cmd = 0; ++ ++ lockdep_assert_held(&bus->mdio_lock); ++ ++ if (ipq40xx_mdio_wait_busy(am)) ++ return -ETIMEDOUT; ++ ++ /* issue the phy address and reg */ ++ writel((mii_id << 8) | regnum, am->membase + MDIO_CTRL_1_REG); ++ ++ /* issue write data */ ++ writel(value, am->membase + MDIO_CTRL_2_REG); ++ ++ cmd = MDIO_CTRL_4_ACCESS_START|MDIO_CTRL_4_ACCESS_CODE_WRITE; ++ /* issue write command */ ++ writel(cmd, am->membase + MDIO_CTRL_4_REG); ++ ++ /* Wait write complete */ ++ if (ipq40xx_mdio_wait_busy(am)) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++static int ipq40xx_mdio_probe(struct platform_device *pdev) ++{ ++ struct ipq40xx_mdio_data *am; ++ struct resource *res; ++ int i; ++ ++ am = devm_kzalloc(&pdev->dev, sizeof(*am), GFP_KERNEL); ++ if (!am) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_err(&pdev->dev, "no iomem resource found\n"); ++ return -ENXIO; ++ } ++ ++ am->membase = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(am->membase)) { ++ dev_err(&pdev->dev, "unable to ioremap registers\n"); ++ return PTR_ERR(am->membase); ++ } ++ ++ am->mii_bus = devm_mdiobus_alloc(&pdev->dev); ++ if (!am->mii_bus) ++ return -ENOMEM; ++ ++ writel(CTRL_0_REG_DEFAULT_VALUE, am->membase + MDIO_CTRL_0_REG); ++ ++ am->mii_bus->name = "ipq40xx_mdio"; ++ am->mii_bus->read = ipq40xx_mdio_read; ++ am->mii_bus->write = ipq40xx_mdio_write; ++ am->mii_bus->priv = am; ++ am->mii_bus->parent = &pdev->dev; ++ snprintf(am->mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(&pdev->dev)); ++ ++ am->dev = &pdev->dev; ++ platform_set_drvdata(pdev, am); ++ ++ return of_mdiobus_register(am->mii_bus, pdev->dev.of_node); ++} ++ ++static int ipq40xx_mdio_remove(struct platform_device *pdev) ++{ ++ struct ipq40xx_mdio_data *am = platform_get_drvdata(pdev); ++ ++ mdiobus_unregister(am->mii_bus); ++ ++ return 0; ++} ++ ++static const struct of_device_id ipq40xx_mdio_dt_ids[] = { ++ { .compatible = "qcom,ipq4019-mdio" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, ipq40xx_mdio_dt_ids); ++ ++static struct platform_driver ipq40xx_mdio_driver = { ++ .probe = ipq40xx_mdio_probe, ++ .remove = ipq40xx_mdio_remove, ++ .driver = { ++ .name = "ipq40xx-mdio", ++ .of_match_table = ipq40xx_mdio_dt_ids, ++ }, ++}; ++ ++module_platform_driver(ipq40xx_mdio_driver); ++ ++#define DRV_VERSION "1.0" ++ ++MODULE_DESCRIPTION("IPQ40XX MDIO interface driver"); ++MODULE_AUTHOR("Qualcomm Atheros"); ++MODULE_VERSION(DRV_VERSION); ++MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/ipq40xx/patches-4.14/705-net-add-qualcomm-ar40xx-phy.patch b/target/linux/ipq40xx/patches-4.14/705-net-add-qualcomm-ar40xx-phy.patch new file mode 100644 index 0000000000..bc75aab088 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/705-net-add-qualcomm-ar40xx-phy.patch @@ -0,0 +1,2492 @@ +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -531,6 +531,13 @@ config MDIO_IPQ40XX + This driver supports the MDIO interface found in Qualcomm + Atheros ipq40xx Soc chip. + ++config AR40XX_PHY ++ tristate "Driver for Qualcomm Atheros IPQ40XX switches" ++ depends on HAS_IOMEM && OF ++ select SWCONFIG ++ ---help--- ++ This is the driver for Qualcomm Atheros IPQ40XX ESS switches. ++ + endif # PHYLIB + + config MICREL_KS8995MA +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -62,6 +62,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m) + + obj-$(CONFIG_AMD_PHY) += amd.o + obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o ++obj-$(CONFIG_AR40XX_PHY) += ar40xx.o + obj-$(CONFIG_AX88796B_PHY) += ax88796b.o + obj-$(CONFIG_AT803X_PHY) += at803x.o + obj-$(CONFIG_BCM63XX_PHY) += bcm63xx.o +--- /dev/null ++++ b/drivers/net/phy/ar40xx.c +@@ -0,0 +1,2122 @@ ++/* ++ * Copyright (c) 2016, The Linux Foundation. All rights reserved. ++ * ++ * Permission to use, copy, modify, and/or distribute this software for ++ * any purpose with or without fee is hereby granted, provided that the ++ * above copyright notice and this permission notice appear in all copies. ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT ++ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "ar40xx.h" ++ ++static struct ar40xx_priv *ar40xx_priv; ++ ++#define MIB_DESC(_s , _o, _n) \ ++ { \ ++ .size = (_s), \ ++ .offset = (_o), \ ++ .name = (_n), \ ++ } ++ ++static const struct ar40xx_mib_desc ar40xx_mibs[] = { ++ MIB_DESC(1, AR40XX_STATS_RXBROAD, "RxBroad"), ++ MIB_DESC(1, AR40XX_STATS_RXPAUSE, "RxPause"), ++ MIB_DESC(1, AR40XX_STATS_RXMULTI, "RxMulti"), ++ MIB_DESC(1, AR40XX_STATS_RXFCSERR, "RxFcsErr"), ++ MIB_DESC(1, AR40XX_STATS_RXALIGNERR, "RxAlignErr"), ++ MIB_DESC(1, AR40XX_STATS_RXRUNT, "RxRunt"), ++ MIB_DESC(1, AR40XX_STATS_RXFRAGMENT, "RxFragment"), ++ MIB_DESC(1, AR40XX_STATS_RX64BYTE, "Rx64Byte"), ++ MIB_DESC(1, AR40XX_STATS_RX128BYTE, "Rx128Byte"), ++ MIB_DESC(1, AR40XX_STATS_RX256BYTE, "Rx256Byte"), ++ MIB_DESC(1, AR40XX_STATS_RX512BYTE, "Rx512Byte"), ++ MIB_DESC(1, AR40XX_STATS_RX1024BYTE, "Rx1024Byte"), ++ MIB_DESC(1, AR40XX_STATS_RX1518BYTE, "Rx1518Byte"), ++ MIB_DESC(1, AR40XX_STATS_RXMAXBYTE, "RxMaxByte"), ++ MIB_DESC(1, AR40XX_STATS_RXTOOLONG, "RxTooLong"), ++ MIB_DESC(2, AR40XX_STATS_RXGOODBYTE, "RxGoodByte"), ++ MIB_DESC(2, AR40XX_STATS_RXBADBYTE, "RxBadByte"), ++ MIB_DESC(1, AR40XX_STATS_RXOVERFLOW, "RxOverFlow"), ++ MIB_DESC(1, AR40XX_STATS_FILTERED, "Filtered"), ++ MIB_DESC(1, AR40XX_STATS_TXBROAD, "TxBroad"), ++ MIB_DESC(1, AR40XX_STATS_TXPAUSE, "TxPause"), ++ MIB_DESC(1, AR40XX_STATS_TXMULTI, "TxMulti"), ++ MIB_DESC(1, AR40XX_STATS_TXUNDERRUN, "TxUnderRun"), ++ MIB_DESC(1, AR40XX_STATS_TX64BYTE, "Tx64Byte"), ++ MIB_DESC(1, AR40XX_STATS_TX128BYTE, "Tx128Byte"), ++ MIB_DESC(1, AR40XX_STATS_TX256BYTE, "Tx256Byte"), ++ MIB_DESC(1, AR40XX_STATS_TX512BYTE, "Tx512Byte"), ++ MIB_DESC(1, AR40XX_STATS_TX1024BYTE, "Tx1024Byte"), ++ MIB_DESC(1, AR40XX_STATS_TX1518BYTE, "Tx1518Byte"), ++ MIB_DESC(1, AR40XX_STATS_TXMAXBYTE, "TxMaxByte"), ++ MIB_DESC(1, AR40XX_STATS_TXOVERSIZE, "TxOverSize"), ++ MIB_DESC(2, AR40XX_STATS_TXBYTE, "TxByte"), ++ MIB_DESC(1, AR40XX_STATS_TXCOLLISION, "TxCollision"), ++ MIB_DESC(1, AR40XX_STATS_TXABORTCOL, "TxAbortCol"), ++ MIB_DESC(1, AR40XX_STATS_TXMULTICOL, "TxMultiCol"), ++ MIB_DESC(1, AR40XX_STATS_TXSINGLECOL, "TxSingleCol"), ++ MIB_DESC(1, AR40XX_STATS_TXEXCDEFER, "TxExcDefer"), ++ MIB_DESC(1, AR40XX_STATS_TXDEFER, "TxDefer"), ++ MIB_DESC(1, AR40XX_STATS_TXLATECOL, "TxLateCol"), ++}; ++ ++static u32 ++ar40xx_read(struct ar40xx_priv *priv, int reg) ++{ ++ return readl(priv->hw_addr + reg); ++} ++ ++static u32 ++ar40xx_psgmii_read(struct ar40xx_priv *priv, int reg) ++{ ++ return readl(priv->psgmii_hw_addr + reg); ++} ++ ++static void ++ar40xx_write(struct ar40xx_priv *priv, int reg, u32 val) ++{ ++ writel(val, priv->hw_addr + reg); ++} ++ ++static u32 ++ar40xx_rmw(struct ar40xx_priv *priv, int reg, u32 mask, u32 val) ++{ ++ u32 ret; ++ ++ ret = ar40xx_read(priv, reg); ++ ret &= ~mask; ++ ret |= val; ++ ar40xx_write(priv, reg, ret); ++ return ret; ++} ++ ++static void ++ar40xx_psgmii_write(struct ar40xx_priv *priv, int reg, u32 val) ++{ ++ writel(val, priv->psgmii_hw_addr + reg); ++} ++ ++static void ++ar40xx_phy_dbg_write(struct ar40xx_priv *priv, int phy_addr, ++ u16 dbg_addr, u16 dbg_data) ++{ ++ struct mii_bus *bus = priv->mii_bus; ++ ++ mutex_lock(&bus->mdio_lock); ++ bus->write(bus, phy_addr, AR40XX_MII_ATH_DBG_ADDR, dbg_addr); ++ bus->write(bus, phy_addr, AR40XX_MII_ATH_DBG_DATA, dbg_data); ++ mutex_unlock(&bus->mdio_lock); ++} ++ ++static void ++ar40xx_phy_dbg_read(struct ar40xx_priv *priv, int phy_addr, ++ u16 dbg_addr, u16 *dbg_data) ++{ ++ struct mii_bus *bus = priv->mii_bus; ++ ++ mutex_lock(&bus->mdio_lock); ++ bus->write(bus, phy_addr, AR40XX_MII_ATH_DBG_ADDR, dbg_addr); ++ *dbg_data = bus->read(bus, phy_addr, AR40XX_MII_ATH_DBG_DATA); ++ mutex_unlock(&bus->mdio_lock); ++} ++ ++static void ++ar40xx_phy_mmd_write(struct ar40xx_priv *priv, u32 phy_id, ++ u16 mmd_num, u16 reg_id, u16 reg_val) ++{ ++ struct mii_bus *bus = priv->mii_bus; ++ ++ mutex_lock(&bus->mdio_lock); ++ bus->write(bus, phy_id, ++ AR40XX_MII_ATH_MMD_ADDR, mmd_num); ++ bus->write(bus, phy_id, ++ AR40XX_MII_ATH_MMD_DATA, reg_id); ++ bus->write(bus, phy_id, ++ AR40XX_MII_ATH_MMD_ADDR, ++ 0x4000 | mmd_num); ++ bus->write(bus, phy_id, ++ AR40XX_MII_ATH_MMD_DATA, reg_val); ++ mutex_unlock(&bus->mdio_lock); ++} ++ ++static u16 ++ar40xx_phy_mmd_read(struct ar40xx_priv *priv, u32 phy_id, ++ u16 mmd_num, u16 reg_id) ++{ ++ u16 value; ++ struct mii_bus *bus = priv->mii_bus; ++ ++ mutex_lock(&bus->mdio_lock); ++ bus->write(bus, phy_id, ++ AR40XX_MII_ATH_MMD_ADDR, mmd_num); ++ bus->write(bus, phy_id, ++ AR40XX_MII_ATH_MMD_DATA, reg_id); ++ bus->write(bus, phy_id, ++ AR40XX_MII_ATH_MMD_ADDR, ++ 0x4000 | mmd_num); ++ value = bus->read(bus, phy_id, AR40XX_MII_ATH_MMD_DATA); ++ mutex_unlock(&bus->mdio_lock); ++ return value; ++} ++ ++/* Start of swconfig support */ ++ ++static void ++ar40xx_phy_poll_reset(struct ar40xx_priv *priv) ++{ ++ u32 i, in_reset, retries = 500; ++ struct mii_bus *bus = priv->mii_bus; ++ ++ /* Assume RESET was recently issued to some or all of the phys */ ++ in_reset = GENMASK(AR40XX_NUM_PHYS - 1, 0); ++ ++ while (retries--) { ++ /* 1ms should be plenty of time. ++ * 802.3 spec allows for a max wait time of 500ms ++ */ ++ usleep_range(1000, 2000); ++ ++ for (i = 0; i < AR40XX_NUM_PHYS; i++) { ++ int val; ++ ++ /* skip devices which have completed reset */ ++ if (!(in_reset & BIT(i))) ++ continue; ++ ++ val = mdiobus_read(bus, i, MII_BMCR); ++ if (val < 0) ++ continue; ++ ++ /* mark when phy is no longer in reset state */ ++ if (!(val & BMCR_RESET)) ++ in_reset &= ~BIT(i); ++ } ++ ++ if (!in_reset) ++ return; ++ } ++ ++ dev_warn(&bus->dev, "Failed to reset all phys! (in_reset: 0x%x)\n", ++ in_reset); ++} ++ ++static void ++ar40xx_phy_init(struct ar40xx_priv *priv) ++{ ++ int i; ++ struct mii_bus *bus; ++ u16 val; ++ ++ bus = priv->mii_bus; ++ for (i = 0; i < AR40XX_NUM_PORTS - 1; i++) { ++ ar40xx_phy_dbg_read(priv, i, AR40XX_PHY_DEBUG_0, &val); ++ val &= ~AR40XX_PHY_MANU_CTRL_EN; ++ ar40xx_phy_dbg_write(priv, i, AR40XX_PHY_DEBUG_0, val); ++ mdiobus_write(bus, i, ++ MII_ADVERTISE, ADVERTISE_ALL | ++ ADVERTISE_PAUSE_CAP | ++ ADVERTISE_PAUSE_ASYM); ++ mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); ++ mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); ++ } ++ ++ ar40xx_phy_poll_reset(priv); ++} ++ ++static void ++ar40xx_port_phy_linkdown(struct ar40xx_priv *priv) ++{ ++ struct mii_bus *bus; ++ int i; ++ u16 val; ++ ++ bus = priv->mii_bus; ++ for (i = 0; i < AR40XX_NUM_PORTS - 1; i++) { ++ mdiobus_write(bus, i, MII_CTRL1000, 0); ++ mdiobus_write(bus, i, MII_ADVERTISE, 0); ++ mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); ++ ar40xx_phy_dbg_read(priv, i, AR40XX_PHY_DEBUG_0, &val); ++ val |= AR40XX_PHY_MANU_CTRL_EN; ++ ar40xx_phy_dbg_write(priv, i, AR40XX_PHY_DEBUG_0, val); ++ /* disable transmit */ ++ ar40xx_phy_dbg_read(priv, i, AR40XX_PHY_DEBUG_2, &val); ++ val &= 0xf00f; ++ ar40xx_phy_dbg_write(priv, i, AR40XX_PHY_DEBUG_2, val); ++ } ++} ++ ++static void ++ar40xx_set_mirror_regs(struct ar40xx_priv *priv) ++{ ++ int port; ++ ++ /* reset all mirror registers */ ++ ar40xx_rmw(priv, AR40XX_REG_FWD_CTRL0, ++ AR40XX_FWD_CTRL0_MIRROR_PORT, ++ (0xF << AR40XX_FWD_CTRL0_MIRROR_PORT_S)); ++ for (port = 0; port < AR40XX_NUM_PORTS; port++) { ++ ar40xx_rmw(priv, AR40XX_REG_PORT_LOOKUP(port), ++ AR40XX_PORT_LOOKUP_ING_MIRROR_EN, 0); ++ ++ ar40xx_rmw(priv, AR40XX_REG_PORT_HOL_CTRL1(port), ++ AR40XX_PORT_HOL_CTRL1_EG_MIRROR_EN, 0); ++ } ++ ++ /* now enable mirroring if necessary */ ++ if (priv->source_port >= AR40XX_NUM_PORTS || ++ priv->monitor_port >= AR40XX_NUM_PORTS || ++ priv->source_port == priv->monitor_port) { ++ return; ++ } ++ ++ ar40xx_rmw(priv, AR40XX_REG_FWD_CTRL0, ++ AR40XX_FWD_CTRL0_MIRROR_PORT, ++ (priv->monitor_port << AR40XX_FWD_CTRL0_MIRROR_PORT_S)); ++ ++ if (priv->mirror_rx) ++ ar40xx_rmw(priv, AR40XX_REG_PORT_LOOKUP(priv->source_port), 0, ++ AR40XX_PORT_LOOKUP_ING_MIRROR_EN); ++ ++ if (priv->mirror_tx) ++ ar40xx_rmw(priv, AR40XX_REG_PORT_HOL_CTRL1(priv->source_port), ++ 0, AR40XX_PORT_HOL_CTRL1_EG_MIRROR_EN); ++} ++ ++static int ++ar40xx_sw_get_ports(struct switch_dev *dev, struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ u8 ports = priv->vlan_table[val->port_vlan]; ++ int i; ++ ++ val->len = 0; ++ for (i = 0; i < dev->ports; i++) { ++ struct switch_port *p; ++ ++ if (!(ports & BIT(i))) ++ continue; ++ ++ p = &val->value.ports[val->len++]; ++ p->id = i; ++ if ((priv->vlan_tagged & BIT(i)) || ++ (priv->pvid[i] != val->port_vlan)) ++ p->flags = BIT(SWITCH_PORT_FLAG_TAGGED); ++ else ++ p->flags = 0; ++ } ++ return 0; ++} ++ ++static int ++ar40xx_sw_set_ports(struct switch_dev *dev, struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ u8 *vt = &priv->vlan_table[val->port_vlan]; ++ int i; ++ ++ *vt = 0; ++ for (i = 0; i < val->len; i++) { ++ struct switch_port *p = &val->value.ports[i]; ++ ++ if (p->flags & BIT(SWITCH_PORT_FLAG_TAGGED)) { ++ if (val->port_vlan == priv->pvid[p->id]) ++ priv->vlan_tagged |= BIT(p->id); ++ } else { ++ priv->vlan_tagged &= ~BIT(p->id); ++ priv->pvid[p->id] = val->port_vlan; ++ } ++ ++ *vt |= BIT(p->id); ++ } ++ return 0; ++} ++ ++static int ++ar40xx_reg_wait(struct ar40xx_priv *priv, u32 reg, u32 mask, u32 val, ++ unsigned timeout) ++{ ++ int i; ++ ++ for (i = 0; i < timeout; i++) { ++ u32 t; ++ ++ t = ar40xx_read(priv, reg); ++ if ((t & mask) == val) ++ return 0; ++ ++ usleep_range(1000, 2000); ++ } ++ ++ return -ETIMEDOUT; ++} ++ ++static int ++ar40xx_mib_op(struct ar40xx_priv *priv, u32 op) ++{ ++ int ret; ++ ++ lockdep_assert_held(&priv->mib_lock); ++ ++ /* Capture the hardware statistics for all ports */ ++ ar40xx_rmw(priv, AR40XX_REG_MIB_FUNC, ++ AR40XX_MIB_FUNC, (op << AR40XX_MIB_FUNC_S)); ++ ++ /* Wait for the capturing to complete. */ ++ ret = ar40xx_reg_wait(priv, AR40XX_REG_MIB_FUNC, ++ AR40XX_MIB_BUSY, 0, 10); ++ ++ return ret; ++} ++ ++static void ++ar40xx_mib_fetch_port_stat(struct ar40xx_priv *priv, int port, bool flush) ++{ ++ unsigned int base; ++ u64 *mib_stats; ++ int i; ++ u32 num_mibs = ARRAY_SIZE(ar40xx_mibs); ++ ++ WARN_ON(port >= priv->dev.ports); ++ ++ lockdep_assert_held(&priv->mib_lock); ++ ++ base = AR40XX_REG_PORT_STATS_START + ++ AR40XX_REG_PORT_STATS_LEN * port; ++ ++ mib_stats = &priv->mib_stats[port * num_mibs]; ++ if (flush) { ++ u32 len; ++ ++ len = num_mibs * sizeof(*mib_stats); ++ memset(mib_stats, 0, len); ++ return; ++ } ++ for (i = 0; i < num_mibs; i++) { ++ const struct ar40xx_mib_desc *mib; ++ u64 t; ++ ++ mib = &ar40xx_mibs[i]; ++ t = ar40xx_read(priv, base + mib->offset); ++ if (mib->size == 2) { ++ u64 hi; ++ ++ hi = ar40xx_read(priv, base + mib->offset + 4); ++ t |= hi << 32; ++ } ++ ++ mib_stats[i] += t; ++ } ++} ++ ++static int ++ar40xx_mib_capture(struct ar40xx_priv *priv) ++{ ++ return ar40xx_mib_op(priv, AR40XX_MIB_FUNC_CAPTURE); ++} ++ ++static int ++ar40xx_mib_flush(struct ar40xx_priv *priv) ++{ ++ return ar40xx_mib_op(priv, AR40XX_MIB_FUNC_FLUSH); ++} ++ ++static int ++ar40xx_sw_set_reset_mibs(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ unsigned int len; ++ int ret; ++ u32 num_mibs = ARRAY_SIZE(ar40xx_mibs); ++ ++ mutex_lock(&priv->mib_lock); ++ ++ len = priv->dev.ports * num_mibs * sizeof(*priv->mib_stats); ++ memset(priv->mib_stats, 0, len); ++ ret = ar40xx_mib_flush(priv); ++ ++ mutex_unlock(&priv->mib_lock); ++ return ret; ++} ++ ++static int ++ar40xx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ priv->vlan = !!val->value.i; ++ return 0; ++} ++ ++static int ++ar40xx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ val->value.i = priv->vlan; ++ return 0; ++} ++ ++static int ++ar40xx_sw_set_mirror_rx_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ priv->mirror_rx = !!val->value.i; ++ ar40xx_set_mirror_regs(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ return 0; ++} ++ ++static int ++ar40xx_sw_get_mirror_rx_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ val->value.i = priv->mirror_rx; ++ mutex_unlock(&priv->reg_mutex); ++ return 0; ++} ++ ++static int ++ar40xx_sw_set_mirror_tx_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ priv->mirror_tx = !!val->value.i; ++ ar40xx_set_mirror_regs(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ return 0; ++} ++ ++static int ++ar40xx_sw_get_mirror_tx_enable(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ val->value.i = priv->mirror_tx; ++ mutex_unlock(&priv->reg_mutex); ++ return 0; ++} ++ ++static int ++ar40xx_sw_set_mirror_monitor_port(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ priv->monitor_port = val->value.i; ++ ar40xx_set_mirror_regs(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ return 0; ++} ++ ++static int ++ar40xx_sw_get_mirror_monitor_port(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ val->value.i = priv->monitor_port; ++ mutex_unlock(&priv->reg_mutex); ++ return 0; ++} ++ ++static int ++ar40xx_sw_set_mirror_source_port(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ priv->source_port = val->value.i; ++ ar40xx_set_mirror_regs(priv); ++ mutex_unlock(&priv->reg_mutex); ++ ++ return 0; ++} ++ ++static int ++ar40xx_sw_get_mirror_source_port(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ mutex_lock(&priv->reg_mutex); ++ val->value.i = priv->source_port; ++ mutex_unlock(&priv->reg_mutex); ++ return 0; ++} ++ ++static int ++ar40xx_sw_set_linkdown(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ if (val->value.i == 1) ++ ar40xx_port_phy_linkdown(priv); ++ else ++ ar40xx_phy_init(priv); ++ ++ return 0; ++} ++ ++static int ++ar40xx_sw_set_port_reset_mib(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ int port; ++ int ret; ++ ++ port = val->port_vlan; ++ if (port >= dev->ports) ++ return -EINVAL; ++ ++ mutex_lock(&priv->mib_lock); ++ ret = ar40xx_mib_capture(priv); ++ if (ret) ++ goto unlock; ++ ++ ar40xx_mib_fetch_port_stat(priv, port, true); ++ ++unlock: ++ mutex_unlock(&priv->mib_lock); ++ return ret; ++} ++ ++static int ++ar40xx_sw_get_port_mib(struct switch_dev *dev, ++ const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ u64 *mib_stats; ++ int port; ++ int ret; ++ char *buf = priv->buf; ++ int i, len = 0; ++ u32 num_mibs = ARRAY_SIZE(ar40xx_mibs); ++ ++ port = val->port_vlan; ++ if (port >= dev->ports) ++ return -EINVAL; ++ ++ mutex_lock(&priv->mib_lock); ++ ret = ar40xx_mib_capture(priv); ++ if (ret) ++ goto unlock; ++ ++ ar40xx_mib_fetch_port_stat(priv, port, false); ++ ++ len += snprintf(buf + len, sizeof(priv->buf) - len, ++ "Port %d MIB counters\n", ++ port); ++ ++ mib_stats = &priv->mib_stats[port * num_mibs]; ++ for (i = 0; i < num_mibs; i++) ++ len += snprintf(buf + len, sizeof(priv->buf) - len, ++ "%-12s: %llu\n", ++ ar40xx_mibs[i].name, ++ mib_stats[i]); ++ ++ val->value.s = buf; ++ val->len = len; ++ ++unlock: ++ mutex_unlock(&priv->mib_lock); ++ return ret; ++} ++ ++static int ++ar40xx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ priv->vlan_id[val->port_vlan] = val->value.i; ++ return 0; ++} ++ ++static int ++ar40xx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, ++ struct switch_val *val) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ val->value.i = priv->vlan_id[val->port_vlan]; ++ return 0; ++} ++ ++static int ++ar40xx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ *vlan = priv->pvid[port]; ++ return 0; ++} ++ ++static int ++ar40xx_sw_set_pvid(struct switch_dev *dev, int port, int vlan) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ /* make sure no invalid PVIDs get set */ ++ if (vlan >= dev->vlans) ++ return -EINVAL; ++ ++ priv->pvid[port] = vlan; ++ return 0; ++} ++ ++static void ++ar40xx_read_port_link(struct ar40xx_priv *priv, int port, ++ struct switch_port_link *link) ++{ ++ u32 status; ++ u32 speed; ++ ++ memset(link, 0, sizeof(*link)); ++ ++ status = ar40xx_read(priv, AR40XX_REG_PORT_STATUS(port)); ++ ++ link->aneg = !!(status & AR40XX_PORT_AUTO_LINK_EN); ++ if (link->aneg || (port != AR40XX_PORT_CPU)) ++ link->link = !!(status & AR40XX_PORT_STATUS_LINK_UP); ++ else ++ link->link = true; ++ ++ if (!link->link) ++ return; ++ ++ link->duplex = !!(status & AR40XX_PORT_DUPLEX); ++ link->tx_flow = !!(status & AR40XX_PORT_STATUS_TXFLOW); ++ link->rx_flow = !!(status & AR40XX_PORT_STATUS_RXFLOW); ++ ++ speed = (status & AR40XX_PORT_SPEED) >> ++ AR40XX_PORT_STATUS_SPEED_S; ++ ++ switch (speed) { ++ case AR40XX_PORT_SPEED_10M: ++ link->speed = SWITCH_PORT_SPEED_10; ++ break; ++ case AR40XX_PORT_SPEED_100M: ++ link->speed = SWITCH_PORT_SPEED_100; ++ break; ++ case AR40XX_PORT_SPEED_1000M: ++ link->speed = SWITCH_PORT_SPEED_1000; ++ break; ++ default: ++ link->speed = SWITCH_PORT_SPEED_UNKNOWN; ++ break; ++ } ++} ++ ++static int ++ar40xx_sw_get_port_link(struct switch_dev *dev, int port, ++ struct switch_port_link *link) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ ++ ar40xx_read_port_link(priv, port, link); ++ return 0; ++} ++ ++static int ++ar40xx_sw_get_port_stats(struct switch_dev *dev, int port, ++ struct switch_port_stats *stats) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ u32 num_mibs = ARRAY_SIZE(ar40xx_mibs); ++ u64 *mib_stats; ++ int ret = 0; ++ static int last_port = AR40XX_NUM_PORTS - 1; ++ ++ mutex_lock(&priv->mib_lock); ++ ++ /* Avoid unnecessary capture */ ++ if (last_port >= port) { ++ ret = ar40xx_mib_capture(priv); ++ if (ret) ++ goto unlock; ++ } ++ last_port = port; ++ ++ ar40xx_mib_fetch_port_stat(priv, port, false); ++ ++ mib_stats = &priv->mib_stats[port * num_mibs]; ++ /* 15: TXBYTE, 31: RXGOODBYTE */ ++ stats->tx_bytes = mib_stats[15]; ++ stats->rx_bytes = mib_stats[31]; ++unlock: ++ mutex_unlock(&priv->mib_lock); ++ return ret; ++} ++ ++static const struct switch_attr ar40xx_sw_attr_globals[] = { ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_vlan", ++ .description = "Enable VLAN mode", ++ .set = ar40xx_sw_set_vlan, ++ .get = ar40xx_sw_get_vlan, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_NOVAL, ++ .name = "reset_mibs", ++ .description = "Reset all MIB counters", ++ .set = ar40xx_sw_set_reset_mibs, ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_mirror_rx", ++ .description = "Enable mirroring of RX packets", ++ .set = ar40xx_sw_set_mirror_rx_enable, ++ .get = ar40xx_sw_get_mirror_rx_enable, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "enable_mirror_tx", ++ .description = "Enable mirroring of TX packets", ++ .set = ar40xx_sw_set_mirror_tx_enable, ++ .get = ar40xx_sw_get_mirror_tx_enable, ++ .max = 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "mirror_monitor_port", ++ .description = "Mirror monitor port", ++ .set = ar40xx_sw_set_mirror_monitor_port, ++ .get = ar40xx_sw_get_mirror_monitor_port, ++ .max = AR40XX_NUM_PORTS - 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "mirror_source_port", ++ .description = "Mirror source port", ++ .set = ar40xx_sw_set_mirror_source_port, ++ .get = ar40xx_sw_get_mirror_source_port, ++ .max = AR40XX_NUM_PORTS - 1 ++ }, ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "linkdown", ++ .description = "Link down all the PHYs", ++ .set = ar40xx_sw_set_linkdown, ++ .max = 1 ++ }, ++}; ++ ++static const struct switch_attr ar40xx_sw_attr_port[] = { ++ { ++ .type = SWITCH_TYPE_NOVAL, ++ .name = "reset_mib", ++ .description = "Reset single port MIB counters", ++ .set = ar40xx_sw_set_port_reset_mib, ++ }, ++ { ++ .type = SWITCH_TYPE_STRING, ++ .name = "mib", ++ .description = "Get port's MIB counters", ++ .set = NULL, ++ .get = ar40xx_sw_get_port_mib, ++ }, ++}; ++ ++const struct switch_attr ar40xx_sw_attr_vlan[] = { ++ { ++ .type = SWITCH_TYPE_INT, ++ .name = "vid", ++ .description = "VLAN ID (0-4094)", ++ .set = ar40xx_sw_set_vid, ++ .get = ar40xx_sw_get_vid, ++ .max = 4094, ++ }, ++}; ++ ++/* End of swconfig support */ ++ ++static int ++ar40xx_wait_bit(struct ar40xx_priv *priv, int reg, u32 mask, u32 val) ++{ ++ int timeout = 20; ++ u32 t; ++ ++ while (1) { ++ t = ar40xx_read(priv, reg); ++ if ((t & mask) == val) ++ return 0; ++ ++ if (timeout-- <= 0) ++ break; ++ ++ usleep_range(10, 20); ++ } ++ ++ pr_err("ar40xx: timeout for reg %08x: %08x & %08x != %08x\n", ++ (unsigned int)reg, t, mask, val); ++ return -ETIMEDOUT; ++} ++ ++static int ++ar40xx_atu_flush(struct ar40xx_priv *priv) ++{ ++ int ret; ++ ++ ret = ar40xx_wait_bit(priv, AR40XX_REG_ATU_FUNC, ++ AR40XX_ATU_FUNC_BUSY, 0); ++ if (!ret) ++ ar40xx_write(priv, AR40XX_REG_ATU_FUNC, ++ AR40XX_ATU_FUNC_OP_FLUSH | ++ AR40XX_ATU_FUNC_BUSY); ++ ++ return ret; ++} ++ ++static void ++ar40xx_ess_reset(struct ar40xx_priv *priv) ++{ ++ reset_control_assert(priv->ess_rst); ++ mdelay(10); ++ reset_control_deassert(priv->ess_rst); ++ /* Waiting for all inner tables init done. ++ * It cost 5~10ms. ++ */ ++ mdelay(10); ++ ++ pr_info("ESS reset ok!\n"); ++} ++ ++/* Start of psgmii self test */ ++ ++static void ++ar40xx_malibu_psgmii_ess_reset(struct ar40xx_priv *priv) ++{ ++ u32 n; ++ struct mii_bus *bus = priv->mii_bus; ++ /* reset phy psgmii */ ++ /* fix phy psgmii RX 20bit */ ++ mdiobus_write(bus, 5, 0x0, 0x005b); ++ /* reset phy psgmii */ ++ mdiobus_write(bus, 5, 0x0, 0x001b); ++ /* release reset phy psgmii */ ++ mdiobus_write(bus, 5, 0x0, 0x005b); ++ ++ for (n = 0; n < AR40XX_PSGMII_CALB_NUM; n++) { ++ u16 status; ++ ++ status = ar40xx_phy_mmd_read(priv, 5, 1, 0x28); ++ if (status & BIT(0)) ++ break; ++ /* Polling interval to check PSGMII PLL in malibu is ready ++ * the worst time is 8.67ms ++ * for 25MHz reference clock ++ * [512+(128+2048)*49]*80ns+100us ++ */ ++ mdelay(2); ++ } ++ ++ /*check malibu psgmii calibration done end..*/ ++ ++ /*freeze phy psgmii RX CDR*/ ++ mdiobus_write(bus, 5, 0x1a, 0x2230); ++ ++ ar40xx_ess_reset(priv); ++ ++ /*check psgmii calibration done start*/ ++ for (n = 0; n < AR40XX_PSGMII_CALB_NUM; n++) { ++ u32 status; ++ ++ status = ar40xx_psgmii_read(priv, 0xa0); ++ if (status & BIT(0)) ++ break; ++ /* Polling interval to check PSGMII PLL in ESS is ready */ ++ mdelay(2); ++ } ++ ++ /* check dakota psgmii calibration done end..*/ ++ ++ /* relesae phy psgmii RX CDR */ ++ mdiobus_write(bus, 5, 0x1a, 0x3230); ++ /* release phy psgmii RX 20bit */ ++ mdiobus_write(bus, 5, 0x0, 0x005f); ++} ++ ++static void ++ar40xx_psgmii_single_phy_testing(struct ar40xx_priv *priv, int phy) ++{ ++ int j; ++ u32 tx_ok, tx_error; ++ u32 rx_ok, rx_error; ++ u32 tx_ok_high16; ++ u32 rx_ok_high16; ++ u32 tx_all_ok, rx_all_ok; ++ struct mii_bus *bus = priv->mii_bus; ++ ++ mdiobus_write(bus, phy, 0x0, 0x9000); ++ mdiobus_write(bus, phy, 0x0, 0x4140); ++ ++ for (j = 0; j < AR40XX_PSGMII_CALB_NUM; j++) { ++ u16 status; ++ ++ status = mdiobus_read(bus, phy, 0x11); ++ if (status & AR40XX_PHY_SPEC_STATUS_LINK) ++ break; ++ /* the polling interval to check if the PHY link up or not ++ * maxwait_timer: 750 ms +/-10 ms ++ * minwait_timer : 1 us +/- 0.1us ++ * time resides in minwait_timer ~ maxwait_timer ++ * see IEEE 802.3 section 40.4.5.2 ++ */ ++ mdelay(8); ++ } ++ ++ /* enable check */ ++ ar40xx_phy_mmd_write(priv, phy, 7, 0x8029, 0x0000); ++ ar40xx_phy_mmd_write(priv, phy, 7, 0x8029, 0x0003); ++ ++ /* start traffic */ ++ ar40xx_phy_mmd_write(priv, phy, 7, 0x8020, 0xa000); ++ /* wait for all traffic end ++ * 4096(pkt num)*1524(size)*8ns(125MHz)=49.9ms ++ */ ++ mdelay(50); ++ ++ /* check counter */ ++ tx_ok = ar40xx_phy_mmd_read(priv, phy, 7, 0x802e); ++ tx_ok_high16 = ar40xx_phy_mmd_read(priv, phy, 7, 0x802d); ++ tx_error = ar40xx_phy_mmd_read(priv, phy, 7, 0x802f); ++ rx_ok = ar40xx_phy_mmd_read(priv, phy, 7, 0x802b); ++ rx_ok_high16 = ar40xx_phy_mmd_read(priv, phy, 7, 0x802a); ++ rx_error = ar40xx_phy_mmd_read(priv, phy, 7, 0x802c); ++ tx_all_ok = tx_ok + (tx_ok_high16 << 16); ++ rx_all_ok = rx_ok + (rx_ok_high16 << 16); ++ if (tx_all_ok == 0x1000 && tx_error == 0) { ++ /* success */ ++ priv->phy_t_status &= (~BIT(phy)); ++ } else { ++ pr_info("PHY %d single test PSGMII issue happen!\n", phy); ++ priv->phy_t_status |= BIT(phy); ++ } ++ ++ mdiobus_write(bus, phy, 0x0, 0x1840); ++} ++ ++static void ++ar40xx_psgmii_all_phy_testing(struct ar40xx_priv *priv) ++{ ++ int phy, j; ++ struct mii_bus *bus = priv->mii_bus; ++ ++ mdiobus_write(bus, 0x1f, 0x0, 0x9000); ++ mdiobus_write(bus, 0x1f, 0x0, 0x4140); ++ ++ for (j = 0; j < AR40XX_PSGMII_CALB_NUM; j++) { ++ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) { ++ u16 status; ++ ++ status = mdiobus_read(bus, phy, 0x11); ++ if (!(status & BIT(10))) ++ break; ++ } ++ ++ if (phy >= (AR40XX_NUM_PORTS - 1)) ++ break; ++ /* The polling interva to check if the PHY link up or not */ ++ mdelay(8); ++ } ++ /* enable check */ ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8029, 0x0000); ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8029, 0x0003); ++ ++ /* start traffic */ ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8020, 0xa000); ++ /* wait for all traffic end ++ * 4096(pkt num)*1524(size)*8ns(125MHz)=49.9ms ++ */ ++ mdelay(50); ++ ++ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) { ++ u32 tx_ok, tx_error; ++ u32 rx_ok, rx_error; ++ u32 tx_ok_high16; ++ u32 rx_ok_high16; ++ u32 tx_all_ok, rx_all_ok; ++ ++ /* check counter */ ++ tx_ok = ar40xx_phy_mmd_read(priv, phy, 7, 0x802e); ++ tx_ok_high16 = ar40xx_phy_mmd_read(priv, phy, 7, 0x802d); ++ tx_error = ar40xx_phy_mmd_read(priv, phy, 7, 0x802f); ++ rx_ok = ar40xx_phy_mmd_read(priv, phy, 7, 0x802b); ++ rx_ok_high16 = ar40xx_phy_mmd_read(priv, phy, 7, 0x802a); ++ rx_error = ar40xx_phy_mmd_read(priv, phy, 7, 0x802c); ++ tx_all_ok = tx_ok + (tx_ok_high16<<16); ++ rx_all_ok = rx_ok + (rx_ok_high16<<16); ++ if (tx_all_ok == 0x1000 && tx_error == 0) { ++ /* success */ ++ priv->phy_t_status &= ~BIT(phy + 8); ++ } else { ++ pr_info("PHY%d test see issue!\n", phy); ++ priv->phy_t_status |= BIT(phy + 8); ++ } ++ } ++ ++ pr_debug("PHY all test 0x%x \r\n", priv->phy_t_status); ++} ++ ++void ++ar40xx_psgmii_self_test(struct ar40xx_priv *priv) ++{ ++ u32 i, phy; ++ struct mii_bus *bus = priv->mii_bus; ++ ++ ar40xx_malibu_psgmii_ess_reset(priv); ++ ++ /* switch to access MII reg for copper */ ++ mdiobus_write(bus, 4, 0x1f, 0x8500); ++ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) { ++ /*enable phy mdio broadcast write*/ ++ ar40xx_phy_mmd_write(priv, phy, 7, 0x8028, 0x801f); ++ } ++ /* force no link by power down */ ++ mdiobus_write(bus, 0x1f, 0x0, 0x1840); ++ /*packet number*/ ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8021, 0x1000); ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8062, 0x05e0); ++ ++ /*fix mdi status */ ++ mdiobus_write(bus, 0x1f, 0x10, 0x6800); ++ for (i = 0; i < AR40XX_PSGMII_CALB_NUM; i++) { ++ priv->phy_t_status = 0; ++ ++ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) { ++ ar40xx_rmw(priv, AR40XX_REG_PORT_LOOKUP(phy + 1), ++ AR40XX_PORT_LOOKUP_LOOPBACK, ++ AR40XX_PORT_LOOKUP_LOOPBACK); ++ } ++ ++ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) ++ ar40xx_psgmii_single_phy_testing(priv, phy); ++ ++ ar40xx_psgmii_all_phy_testing(priv); ++ ++ if (priv->phy_t_status) ++ ar40xx_malibu_psgmii_ess_reset(priv); ++ else ++ break; ++ } ++ ++ if (i >= AR40XX_PSGMII_CALB_NUM) ++ pr_info("PSGMII cannot recover\n"); ++ else ++ pr_debug("PSGMII recovered after %d times reset\n", i); ++ ++ /* configuration recover */ ++ /* packet number */ ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8021, 0x0); ++ /* disable check */ ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8029, 0x0); ++ /* disable traffic */ ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8020, 0x0); ++} ++ ++void ++ar40xx_psgmii_self_test_clean(struct ar40xx_priv *priv) ++{ ++ int phy; ++ struct mii_bus *bus = priv->mii_bus; ++ ++ /* disable phy internal loopback */ ++ mdiobus_write(bus, 0x1f, 0x10, 0x6860); ++ mdiobus_write(bus, 0x1f, 0x0, 0x9040); ++ ar40xx_phy_mmd_write(priv, 0x1f, 7, 0x8076, 0x0670); /* 1000_LED_n */ ++ ++ for (phy = 0; phy < AR40XX_NUM_PORTS - 1; phy++) { ++ /* disable mac loop back */ ++ ar40xx_rmw(priv, AR40XX_REG_PORT_LOOKUP(phy + 1), ++ AR40XX_PORT_LOOKUP_LOOPBACK, 0); ++ /* disable phy mdio broadcast write */ ++ ar40xx_phy_mmd_write(priv, phy, 7, 0x8028, 0x001f); ++ } ++ ++ /* clear fdb entry */ ++ ar40xx_atu_flush(priv); ++} ++ ++/* End of psgmii self test */ ++ ++static void ++ar40xx_mac_mode_init(struct ar40xx_priv *priv, u32 mode) ++{ ++ if (mode == PORT_WRAPPER_PSGMII) { ++ ar40xx_psgmii_write(priv, AR40XX_PSGMII_MODE_CONTROL, 0x2200); ++ ar40xx_psgmii_write(priv, AR40XX_PSGMIIPHY_TX_CONTROL, 0x8380); ++ } ++} ++ ++static ++int ar40xx_cpuport_setup(struct ar40xx_priv *priv) ++{ ++ u32 t; ++ ++ t = AR40XX_PORT_STATUS_TXFLOW | ++ AR40XX_PORT_STATUS_RXFLOW | ++ AR40XX_PORT_TXHALF_FLOW | ++ AR40XX_PORT_DUPLEX | ++ AR40XX_PORT_SPEED_1000M; ++ ar40xx_write(priv, AR40XX_REG_PORT_STATUS(0), t); ++ usleep_range(10, 20); ++ ++ t |= AR40XX_PORT_TX_EN | ++ AR40XX_PORT_RX_EN; ++ ar40xx_write(priv, AR40XX_REG_PORT_STATUS(0), t); ++ ++ return 0; ++} ++ ++static void ++ar40xx_init_port(struct ar40xx_priv *priv, int port) ++{ ++ u32 t; ++ ++ ar40xx_rmw(priv, AR40XX_REG_PORT_STATUS(port), ++ AR40XX_PORT_AUTO_LINK_EN, 0); ++ ++ ar40xx_write(priv, AR40XX_REG_PORT_HEADER(port), 0); ++ ++ ar40xx_write(priv, AR40XX_REG_PORT_VLAN0(port), 0); ++ ++ t = AR40XX_PORT_VLAN1_OUT_MODE_UNTOUCH << AR40XX_PORT_VLAN1_OUT_MODE_S; ++ ar40xx_write(priv, AR40XX_REG_PORT_VLAN1(port), t); ++ ++ t = AR40XX_PORT_LOOKUP_LEARN; ++ t |= AR40XX_PORT_STATE_FORWARD << AR40XX_PORT_LOOKUP_STATE_S; ++ ar40xx_write(priv, AR40XX_REG_PORT_LOOKUP(port), t); ++} ++ ++void ++ar40xx_init_globals(struct ar40xx_priv *priv) ++{ ++ u32 t; ++ ++ /* enable CPU port and disable mirror port */ ++ t = AR40XX_FWD_CTRL0_CPU_PORT_EN | ++ AR40XX_FWD_CTRL0_MIRROR_PORT; ++ ar40xx_write(priv, AR40XX_REG_FWD_CTRL0, t); ++ ++ /* forward multicast and broadcast frames to CPU */ ++ t = (AR40XX_PORTS_ALL << AR40XX_FWD_CTRL1_UC_FLOOD_S) | ++ (AR40XX_PORTS_ALL << AR40XX_FWD_CTRL1_MC_FLOOD_S) | ++ (AR40XX_PORTS_ALL << AR40XX_FWD_CTRL1_BC_FLOOD_S); ++ ar40xx_write(priv, AR40XX_REG_FWD_CTRL1, t); ++ ++ /* enable jumbo frames */ ++ ar40xx_rmw(priv, AR40XX_REG_MAX_FRAME_SIZE, ++ AR40XX_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2); ++ ++ /* Enable MIB counters */ ++ ar40xx_rmw(priv, AR40XX_REG_MODULE_EN, 0, ++ AR40XX_MODULE_EN_MIB); ++ ++ /* Disable AZ */ ++ ar40xx_write(priv, AR40XX_REG_EEE_CTRL, 0); ++ ++ /* set flowctrl thershold for cpu port */ ++ t = (AR40XX_PORT0_FC_THRESH_ON_DFLT << 16) | ++ AR40XX_PORT0_FC_THRESH_OFF_DFLT; ++ ar40xx_write(priv, AR40XX_REG_PORT_FLOWCTRL_THRESH(0), t); ++} ++ ++static void ++ar40xx_malibu_init(struct ar40xx_priv *priv) ++{ ++ int i; ++ struct mii_bus *bus; ++ u16 val; ++ ++ bus = priv->mii_bus; ++ ++ /* war to enable AZ transmitting ability */ ++ ar40xx_phy_mmd_write(priv, AR40XX_PSGMII_ID, 1, ++ AR40XX_MALIBU_PSGMII_MODE_CTRL, ++ AR40XX_MALIBU_PHY_PSGMII_MODE_CTRL_ADJUST_VAL); ++ for (i = 0; i < AR40XX_NUM_PORTS - 1; i++) { ++ /* change malibu control_dac */ ++ val = ar40xx_phy_mmd_read(priv, i, 7, ++ AR40XX_MALIBU_PHY_MMD7_DAC_CTRL); ++ val &= ~AR40XX_MALIBU_DAC_CTRL_MASK; ++ val |= AR40XX_MALIBU_DAC_CTRL_VALUE; ++ ar40xx_phy_mmd_write(priv, i, 7, ++ AR40XX_MALIBU_PHY_MMD7_DAC_CTRL, val); ++ if (i == AR40XX_MALIBU_PHY_LAST_ADDR) { ++ /* to avoid goes into hibernation */ ++ val = ar40xx_phy_mmd_read(priv, i, 3, ++ AR40XX_MALIBU_PHY_RLP_CTRL); ++ val &= (~(1<<1)); ++ ar40xx_phy_mmd_write(priv, i, 3, ++ AR40XX_MALIBU_PHY_RLP_CTRL, val); ++ } ++ } ++ ++ /* adjust psgmii serdes tx amp */ ++ mdiobus_write(bus, AR40XX_PSGMII_ID, AR40XX_PSGMII_TX_DRIVER_1_CTRL, ++ AR40XX_MALIBU_PHY_PSGMII_REDUCE_SERDES_TX_AMP); ++} ++ ++static int ++ar40xx_hw_init(struct ar40xx_priv *priv) ++{ ++ u32 i; ++ ++ ar40xx_ess_reset(priv); ++ ++ if (priv->mii_bus) ++ ar40xx_malibu_init(priv); ++ else ++ return -1; ++ ++ ar40xx_psgmii_self_test(priv); ++ ar40xx_psgmii_self_test_clean(priv); ++ ++ ar40xx_mac_mode_init(priv, priv->mac_mode); ++ ++ for (i = 0; i < priv->dev.ports; i++) ++ ar40xx_init_port(priv, i); ++ ++ ar40xx_init_globals(priv); ++ ++ return 0; ++} ++ ++/* Start of qm error WAR */ ++ ++static ++int ar40xx_force_1g_full(struct ar40xx_priv *priv, u32 port_id) ++{ ++ u32 reg; ++ ++ if (port_id < 0 || port_id > 6) ++ return -1; ++ ++ reg = AR40XX_REG_PORT_STATUS(port_id); ++ return ar40xx_rmw(priv, reg, AR40XX_PORT_SPEED, ++ (AR40XX_PORT_SPEED_1000M | AR40XX_PORT_DUPLEX)); ++} ++ ++static ++int ar40xx_get_qm_status(struct ar40xx_priv *priv, ++ u32 port_id, u32 *qm_buffer_err) ++{ ++ u32 reg; ++ u32 qm_val; ++ ++ if (port_id < 1 || port_id > 5) { ++ *qm_buffer_err = 0; ++ return -1; ++ } ++ ++ if (port_id < 4) { ++ reg = AR40XX_REG_QM_PORT0_3_QNUM; ++ ar40xx_write(priv, AR40XX_REG_QM_DEBUG_ADDR, reg); ++ qm_val = ar40xx_read(priv, AR40XX_REG_QM_DEBUG_VALUE); ++ /* every 8 bits for each port */ ++ *qm_buffer_err = (qm_val >> (port_id * 8)) & 0xFF; ++ } else { ++ reg = AR40XX_REG_QM_PORT4_6_QNUM; ++ ar40xx_write(priv, AR40XX_REG_QM_DEBUG_ADDR, reg); ++ qm_val = ar40xx_read(priv, AR40XX_REG_QM_DEBUG_VALUE); ++ /* every 8 bits for each port */ ++ *qm_buffer_err = (qm_val >> ((port_id-4) * 8)) & 0xFF; ++ } ++ ++ return 0; ++} ++ ++static void ++ar40xx_sw_mac_polling_task(struct ar40xx_priv *priv) ++{ ++ static int task_count; ++ u32 i; ++ u32 reg, value; ++ u32 link, speed, duplex; ++ u32 qm_buffer_err; ++ u16 port_phy_status[AR40XX_NUM_PORTS]; ++ static u32 qm_err_cnt[AR40XX_NUM_PORTS] = {0, 0, 0, 0, 0, 0}; ++ static u32 link_cnt[AR40XX_NUM_PORTS] = {0, 0, 0, 0, 0, 0}; ++ struct mii_bus *bus = NULL; ++ ++ if (!priv || !priv->mii_bus) ++ return; ++ ++ bus = priv->mii_bus; ++ ++ ++task_count; ++ ++ for (i = 1; i < AR40XX_NUM_PORTS; ++i) { ++ port_phy_status[i] = ++ mdiobus_read(bus, i-1, AR40XX_PHY_SPEC_STATUS); ++ speed = link = duplex = port_phy_status[i]; ++ speed &= AR40XX_PHY_SPEC_STATUS_SPEED; ++ speed >>= 14; ++ link &= AR40XX_PHY_SPEC_STATUS_LINK; ++ link >>= 10; ++ duplex &= AR40XX_PHY_SPEC_STATUS_DUPLEX; ++ duplex >>= 13; ++ ++ if (link != priv->ar40xx_port_old_link[i]) { ++ ++link_cnt[i]; ++ /* Up --> Down */ ++ if ((priv->ar40xx_port_old_link[i] == ++ AR40XX_PORT_LINK_UP) && ++ (link == AR40XX_PORT_LINK_DOWN)) { ++ /* LINK_EN disable(MAC force mode)*/ ++ reg = AR40XX_REG_PORT_STATUS(i); ++ ar40xx_rmw(priv, reg, ++ AR40XX_PORT_AUTO_LINK_EN, 0); ++ ++ /* Check queue buffer */ ++ qm_err_cnt[i] = 0; ++ ar40xx_get_qm_status(priv, i, &qm_buffer_err); ++ if (qm_buffer_err) { ++ priv->ar40xx_port_qm_buf[i] = ++ AR40XX_QM_NOT_EMPTY; ++ } else { ++ u16 phy_val = 0; ++ ++ priv->ar40xx_port_qm_buf[i] = ++ AR40XX_QM_EMPTY; ++ ar40xx_force_1g_full(priv, i); ++ /* Ref:QCA8337 Datasheet,Clearing ++ * MENU_CTRL_EN prevents phy to ++ * stuck in 100BT mode when ++ * bringing up the link ++ */ ++ ar40xx_phy_dbg_read(priv, i-1, ++ AR40XX_PHY_DEBUG_0, ++ &phy_val); ++ phy_val &= (~AR40XX_PHY_MANU_CTRL_EN); ++ ar40xx_phy_dbg_write(priv, i-1, ++ AR40XX_PHY_DEBUG_0, ++ phy_val); ++ } ++ priv->ar40xx_port_old_link[i] = link; ++ } else if ((priv->ar40xx_port_old_link[i] == ++ AR40XX_PORT_LINK_DOWN) && ++ (link == AR40XX_PORT_LINK_UP)) { ++ /* Down --> Up */ ++ if (priv->port_link_up[i] < 1) { ++ ++priv->port_link_up[i]; ++ } else { ++ /* Change port status */ ++ reg = AR40XX_REG_PORT_STATUS(i); ++ value = ar40xx_read(priv, reg); ++ priv->port_link_up[i] = 0; ++ ++ value &= ~(AR40XX_PORT_DUPLEX | ++ AR40XX_PORT_SPEED); ++ value |= speed | (duplex ? BIT(6) : 0); ++ ar40xx_write(priv, reg, value); ++ /* clock switch need such time ++ * to avoid glitch ++ */ ++ usleep_range(100, 200); ++ ++ value |= AR40XX_PORT_AUTO_LINK_EN; ++ ar40xx_write(priv, reg, value); ++ /* HW need such time to make sure link ++ * stable before enable MAC ++ */ ++ usleep_range(100, 200); ++ ++ if (speed == AR40XX_PORT_SPEED_100M) { ++ u16 phy_val = 0; ++ /* Enable @100M, if down to 10M ++ * clock will change smoothly ++ */ ++ ar40xx_phy_dbg_read(priv, i-1, ++ 0, ++ &phy_val); ++ phy_val |= ++ AR40XX_PHY_MANU_CTRL_EN; ++ ar40xx_phy_dbg_write(priv, i-1, ++ 0, ++ phy_val); ++ } ++ priv->ar40xx_port_old_link[i] = link; ++ } ++ } ++ } ++ ++ if (priv->ar40xx_port_qm_buf[i] == AR40XX_QM_NOT_EMPTY) { ++ /* Check QM */ ++ ar40xx_get_qm_status(priv, i, &qm_buffer_err); ++ if (qm_buffer_err) { ++ ++qm_err_cnt[i]; ++ } else { ++ priv->ar40xx_port_qm_buf[i] = ++ AR40XX_QM_EMPTY; ++ qm_err_cnt[i] = 0; ++ ar40xx_force_1g_full(priv, i); ++ } ++ } ++ } ++} ++ ++static void ++ar40xx_qm_err_check_work_task(struct work_struct *work) ++{ ++ struct ar40xx_priv *priv = container_of(work, struct ar40xx_priv, ++ qm_dwork.work); ++ ++ mutex_lock(&priv->qm_lock); ++ ++ ar40xx_sw_mac_polling_task(priv); ++ ++ mutex_unlock(&priv->qm_lock); ++ ++ schedule_delayed_work(&priv->qm_dwork, ++ msecs_to_jiffies(AR40XX_QM_WORK_DELAY)); ++} ++ ++static int ++ar40xx_qm_err_check_work_start(struct ar40xx_priv *priv) ++{ ++ mutex_init(&priv->qm_lock); ++ ++ INIT_DELAYED_WORK(&priv->qm_dwork, ar40xx_qm_err_check_work_task); ++ ++ schedule_delayed_work(&priv->qm_dwork, ++ msecs_to_jiffies(AR40XX_QM_WORK_DELAY)); ++ ++ return 0; ++} ++ ++/* End of qm error WAR */ ++ ++static int ++ar40xx_vlan_init(struct ar40xx_priv *priv) ++{ ++ int port; ++ unsigned long bmp; ++ ++ /* By default Enable VLAN */ ++ priv->vlan = 1; ++ priv->vlan_table[AR40XX_LAN_VLAN] = priv->cpu_bmp | priv->lan_bmp; ++ priv->vlan_table[AR40XX_WAN_VLAN] = priv->cpu_bmp | priv->wan_bmp; ++ priv->vlan_tagged = priv->cpu_bmp; ++ bmp = priv->lan_bmp; ++ for_each_set_bit(port, &bmp, AR40XX_NUM_PORTS) ++ priv->pvid[port] = AR40XX_LAN_VLAN; ++ ++ bmp = priv->wan_bmp; ++ for_each_set_bit(port, &bmp, AR40XX_NUM_PORTS) ++ priv->pvid[port] = AR40XX_WAN_VLAN; ++ ++ return 0; ++} ++ ++static void ++ar40xx_mib_work_func(struct work_struct *work) ++{ ++ struct ar40xx_priv *priv; ++ int err; ++ ++ priv = container_of(work, struct ar40xx_priv, mib_work.work); ++ ++ mutex_lock(&priv->mib_lock); ++ ++ err = ar40xx_mib_capture(priv); ++ if (err) ++ goto next_port; ++ ++ ar40xx_mib_fetch_port_stat(priv, priv->mib_next_port, false); ++ ++next_port: ++ priv->mib_next_port++; ++ if (priv->mib_next_port >= priv->dev.ports) ++ priv->mib_next_port = 0; ++ ++ mutex_unlock(&priv->mib_lock); ++ ++ schedule_delayed_work(&priv->mib_work, ++ msecs_to_jiffies(AR40XX_MIB_WORK_DELAY)); ++} ++ ++static void ++ar40xx_setup_port(struct ar40xx_priv *priv, int port, u32 members) ++{ ++ u32 t; ++ u32 egress, ingress; ++ u32 pvid = priv->vlan_id[priv->pvid[port]]; ++ ++ if (priv->vlan) { ++ egress = AR40XX_PORT_VLAN1_OUT_MODE_UNMOD; ++ ingress = AR40XX_IN_SECURE; ++ } else { ++ egress = AR40XX_PORT_VLAN1_OUT_MODE_UNTOUCH; ++ ingress = AR40XX_IN_PORT_ONLY; ++ } ++ ++ t = pvid << AR40XX_PORT_VLAN0_DEF_SVID_S; ++ t |= pvid << AR40XX_PORT_VLAN0_DEF_CVID_S; ++ ar40xx_write(priv, AR40XX_REG_PORT_VLAN0(port), t); ++ ++ t = AR40XX_PORT_VLAN1_PORT_VLAN_PROP; ++ t |= egress << AR40XX_PORT_VLAN1_OUT_MODE_S; ++ ar40xx_write(priv, AR40XX_REG_PORT_VLAN1(port), t); ++ ++ t = members; ++ t |= AR40XX_PORT_LOOKUP_LEARN; ++ t |= ingress << AR40XX_PORT_LOOKUP_IN_MODE_S; ++ t |= AR40XX_PORT_STATE_FORWARD << AR40XX_PORT_LOOKUP_STATE_S; ++ ar40xx_write(priv, AR40XX_REG_PORT_LOOKUP(port), t); ++} ++ ++static void ++ar40xx_vtu_op(struct ar40xx_priv *priv, u32 op, u32 val) ++{ ++ if (ar40xx_wait_bit(priv, AR40XX_REG_VTU_FUNC1, ++ AR40XX_VTU_FUNC1_BUSY, 0)) ++ return; ++ ++ if ((op & AR40XX_VTU_FUNC1_OP) == AR40XX_VTU_FUNC1_OP_LOAD) ++ ar40xx_write(priv, AR40XX_REG_VTU_FUNC0, val); ++ ++ op |= AR40XX_VTU_FUNC1_BUSY; ++ ar40xx_write(priv, AR40XX_REG_VTU_FUNC1, op); ++} ++ ++static void ++ar40xx_vtu_load_vlan(struct ar40xx_priv *priv, u32 vid, u32 port_mask) ++{ ++ u32 op; ++ u32 val; ++ int i; ++ ++ op = AR40XX_VTU_FUNC1_OP_LOAD | (vid << AR40XX_VTU_FUNC1_VID_S); ++ val = AR40XX_VTU_FUNC0_VALID | AR40XX_VTU_FUNC0_IVL; ++ for (i = 0; i < AR40XX_NUM_PORTS; i++) { ++ u32 mode; ++ ++ if ((port_mask & BIT(i)) == 0) ++ mode = AR40XX_VTU_FUNC0_EG_MODE_NOT; ++ else if (priv->vlan == 0) ++ mode = AR40XX_VTU_FUNC0_EG_MODE_KEEP; ++ else if ((priv->vlan_tagged & BIT(i)) || ++ (priv->vlan_id[priv->pvid[i]] != vid)) ++ mode = AR40XX_VTU_FUNC0_EG_MODE_TAG; ++ else ++ mode = AR40XX_VTU_FUNC0_EG_MODE_UNTAG; ++ ++ val |= mode << AR40XX_VTU_FUNC0_EG_MODE_S(i); ++ } ++ ar40xx_vtu_op(priv, op, val); ++} ++ ++static void ++ar40xx_vtu_flush(struct ar40xx_priv *priv) ++{ ++ ar40xx_vtu_op(priv, AR40XX_VTU_FUNC1_OP_FLUSH, 0); ++} ++ ++static int ++ar40xx_sw_hw_apply(struct switch_dev *dev) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ u8 portmask[AR40XX_NUM_PORTS]; ++ int i, j; ++ ++ mutex_lock(&priv->reg_mutex); ++ /* flush all vlan entries */ ++ ar40xx_vtu_flush(priv); ++ ++ memset(portmask, 0, sizeof(portmask)); ++ if (priv->vlan) { ++ for (j = 0; j < AR40XX_MAX_VLANS; j++) { ++ u8 vp = priv->vlan_table[j]; ++ ++ if (!vp) ++ continue; ++ ++ for (i = 0; i < dev->ports; i++) { ++ u8 mask = BIT(i); ++ ++ if (vp & mask) ++ portmask[i] |= vp & ~mask; ++ } ++ ++ ar40xx_vtu_load_vlan(priv, priv->vlan_id[j], ++ priv->vlan_table[j]); ++ } ++ } else { ++ /* 8021q vlan disabled */ ++ for (i = 0; i < dev->ports; i++) { ++ if (i == AR40XX_PORT_CPU) ++ continue; ++ ++ portmask[i] = BIT(AR40XX_PORT_CPU); ++ portmask[AR40XX_PORT_CPU] |= BIT(i); ++ } ++ } ++ ++ /* update the port destination mask registers and tag settings */ ++ for (i = 0; i < dev->ports; i++) ++ ar40xx_setup_port(priv, i, portmask[i]); ++ ++ ar40xx_set_mirror_regs(priv); ++ ++ mutex_unlock(&priv->reg_mutex); ++ return 0; ++} ++ ++static int ++ar40xx_sw_reset_switch(struct switch_dev *dev) ++{ ++ struct ar40xx_priv *priv = swdev_to_ar40xx(dev); ++ int i, rv; ++ ++ mutex_lock(&priv->reg_mutex); ++ memset(&priv->vlan, 0, sizeof(struct ar40xx_priv) - ++ offsetof(struct ar40xx_priv, vlan)); ++ ++ for (i = 0; i < AR40XX_MAX_VLANS; i++) ++ priv->vlan_id[i] = i; ++ ++ ar40xx_vlan_init(priv); ++ ++ priv->mirror_rx = false; ++ priv->mirror_tx = false; ++ priv->source_port = 0; ++ priv->monitor_port = 0; ++ ++ mutex_unlock(&priv->reg_mutex); ++ ++ rv = ar40xx_sw_hw_apply(dev); ++ return rv; ++} ++ ++static int ++ar40xx_start(struct ar40xx_priv *priv) ++{ ++ int ret; ++ ++ ret = ar40xx_hw_init(priv); ++ if (ret) ++ return ret; ++ ++ ret = ar40xx_sw_reset_switch(&priv->dev); ++ if (ret) ++ return ret; ++ ++ /* at last, setup cpu port */ ++ ret = ar40xx_cpuport_setup(priv); ++ if (ret) ++ return ret; ++ ++ schedule_delayed_work(&priv->mib_work, ++ msecs_to_jiffies(AR40XX_MIB_WORK_DELAY)); ++ ++ ar40xx_qm_err_check_work_start(priv); ++ ++ return 0; ++} ++ ++static const struct switch_dev_ops ar40xx_sw_ops = { ++ .attr_global = { ++ .attr = ar40xx_sw_attr_globals, ++ .n_attr = ARRAY_SIZE(ar40xx_sw_attr_globals), ++ }, ++ .attr_port = { ++ .attr = ar40xx_sw_attr_port, ++ .n_attr = ARRAY_SIZE(ar40xx_sw_attr_port), ++ }, ++ .attr_vlan = { ++ .attr = ar40xx_sw_attr_vlan, ++ .n_attr = ARRAY_SIZE(ar40xx_sw_attr_vlan), ++ }, ++ .get_port_pvid = ar40xx_sw_get_pvid, ++ .set_port_pvid = ar40xx_sw_set_pvid, ++ .get_vlan_ports = ar40xx_sw_get_ports, ++ .set_vlan_ports = ar40xx_sw_set_ports, ++ .apply_config = ar40xx_sw_hw_apply, ++ .reset_switch = ar40xx_sw_reset_switch, ++ .get_port_link = ar40xx_sw_get_port_link, ++ .get_port_stats = ar40xx_sw_get_port_stats, ++}; ++ ++/* Start of phy driver support */ ++ ++static const u32 ar40xx_phy_ids[] = { ++ 0x004dd0b1, ++ 0x004dd0b2, /* AR40xx */ ++}; ++ ++static bool ++ar40xx_phy_match(u32 phy_id) ++{ ++ int i; ++ ++ for (i = 0; i < ARRAY_SIZE(ar40xx_phy_ids); i++) ++ if (phy_id == ar40xx_phy_ids[i]) ++ return true; ++ ++ return false; ++} ++ ++static bool ++is_ar40xx_phy(struct mii_bus *bus) ++{ ++ unsigned i; ++ ++ for (i = 0; i < 4; i++) { ++ u32 phy_id; ++ ++ phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16; ++ phy_id |= mdiobus_read(bus, i, MII_PHYSID2); ++ if (!ar40xx_phy_match(phy_id)) ++ return false; ++ } ++ ++ return true; ++} ++ ++static int ++ar40xx_phy_probe(struct phy_device *phydev) ++{ ++ if (!is_ar40xx_phy(phydev->mdio.bus)) ++ return -ENODEV; ++ ++ ar40xx_priv->mii_bus = phydev->mdio.bus; ++ phydev->priv = ar40xx_priv; ++ if (phydev->mdio.addr == 0) ++ ar40xx_priv->phy = phydev; ++ ++ phydev->supported |= SUPPORTED_1000baseT_Full; ++ phydev->advertising |= ADVERTISED_1000baseT_Full; ++ return 0; ++} ++ ++static void ++ar40xx_phy_remove(struct phy_device *phydev) ++{ ++ ar40xx_priv->mii_bus = NULL; ++ phydev->priv = NULL; ++} ++ ++static int ++ar40xx_phy_config_init(struct phy_device *phydev) ++{ ++ return 0; ++} ++ ++static int ++ar40xx_phy_read_status(struct phy_device *phydev) ++{ ++ if (phydev->mdio.addr != 0) ++ return genphy_read_status(phydev); ++ ++ return 0; ++} ++ ++static int ++ar40xx_phy_config_aneg(struct phy_device *phydev) ++{ ++ if (phydev->mdio.addr == 0) ++ return 0; ++ ++ return genphy_config_aneg(phydev); ++} ++ ++static struct phy_driver ar40xx_phy_driver = { ++ .phy_id = 0x004d0000, ++ .name = "QCA Malibu", ++ .phy_id_mask = 0xffff0000, ++ .features = PHY_BASIC_FEATURES, ++ .probe = ar40xx_phy_probe, ++ .remove = ar40xx_phy_remove, ++ .config_init = ar40xx_phy_config_init, ++ .config_aneg = ar40xx_phy_config_aneg, ++ .read_status = ar40xx_phy_read_status, ++}; ++ ++static uint16_t ar40xx_gpio_get_phy(unsigned int offset) ++{ ++ return offset / 4; ++} ++ ++static uint16_t ar40xx_gpio_get_reg(unsigned int offset) ++{ ++ return 0x8074 + offset % 4; ++} ++ ++static void ar40xx_gpio_set(struct gpio_chip *gc, unsigned int offset, ++ int value) ++{ ++ struct ar40xx_priv *priv = gpiochip_get_data(gc); ++ ++ ar40xx_phy_mmd_write(priv, ar40xx_gpio_get_phy(offset), 0x7, ++ ar40xx_gpio_get_reg(offset), ++ value ? 0xA000 : 0x8000); ++} ++ ++static int ar40xx_gpio_get(struct gpio_chip *gc, unsigned offset) ++{ ++ struct ar40xx_priv *priv = gpiochip_get_data(gc); ++ ++ return ar40xx_phy_mmd_read(priv, ar40xx_gpio_get_phy(offset), 0x7, ++ ar40xx_gpio_get_reg(offset)) == 0xA000; ++} ++ ++static int ar40xx_gpio_get_dir(struct gpio_chip *gc, unsigned offset) ++{ ++ return 0; /* only out direction */ ++} ++ ++static int ar40xx_gpio_dir_out(struct gpio_chip *gc, unsigned offset, ++ int value) ++{ ++ /* ++ * the direction out value is used to set the initial value. ++ * support of this function is required by leds-gpio.c ++ */ ++ ar40xx_gpio_set(gc, offset, value); ++ return 0; ++} ++ ++static void ar40xx_register_gpio(struct device *pdev, ++ struct ar40xx_priv *priv, ++ struct device_node *switch_node) ++{ ++ struct gpio_chip *gc; ++ int err; ++ ++ gc = devm_kzalloc(pdev, sizeof(*gc), GFP_KERNEL); ++ if (!gc) ++ return; ++ ++ gc->label = "ar40xx_gpio", ++ gc->base = -1, ++ gc->ngpio = 5 /* mmd 0 - 4 */ * 4 /* 0x8074 - 0x8077 */, ++ gc->parent = pdev; ++ gc->owner = THIS_MODULE; ++ ++ gc->get_direction = ar40xx_gpio_get_dir; ++ gc->direction_output = ar40xx_gpio_dir_out; ++ gc->get = ar40xx_gpio_get; ++ gc->set = ar40xx_gpio_set; ++ gc->can_sleep = true; ++ gc->label = priv->dev.name; ++ gc->of_node = switch_node; ++ ++ err = devm_gpiochip_add_data(pdev, gc, priv); ++ if (err != 0) ++ dev_err(pdev, "Failed to register gpio %d.\n", err); ++} ++ ++/* End of phy driver support */ ++ ++/* Platform driver probe function */ ++ ++static int ar40xx_probe(struct platform_device *pdev) ++{ ++ struct device_node *switch_node; ++ struct device_node *psgmii_node; ++ const __be32 *mac_mode; ++ struct clk *ess_clk; ++ struct switch_dev *swdev; ++ struct ar40xx_priv *priv; ++ u32 len; ++ u32 num_mibs; ++ struct resource psgmii_base = {0}; ++ struct resource switch_base = {0}; ++ int ret; ++ ++ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, priv); ++ ar40xx_priv = priv; ++ ++ switch_node = of_node_get(pdev->dev.of_node); ++ if (of_address_to_resource(switch_node, 0, &switch_base) != 0) ++ return -EIO; ++ ++ priv->hw_addr = devm_ioremap_resource(&pdev->dev, &switch_base); ++ if (IS_ERR(priv->hw_addr)) { ++ dev_err(&pdev->dev, "Failed to ioremap switch_base!\n"); ++ return PTR_ERR(priv->hw_addr); ++ } ++ ++ /*psgmii dts get*/ ++ psgmii_node = of_find_node_by_name(NULL, "ess-psgmii"); ++ if (!psgmii_node) { ++ dev_err(&pdev->dev, "Failed to find ess-psgmii node!\n"); ++ return -EINVAL; ++ } ++ ++ if (of_address_to_resource(psgmii_node, 0, &psgmii_base) != 0) ++ return -EIO; ++ ++ priv->psgmii_hw_addr = devm_ioremap_resource(&pdev->dev, &psgmii_base); ++ if (IS_ERR(priv->psgmii_hw_addr)) { ++ dev_err(&pdev->dev, "psgmii ioremap fail!\n"); ++ return PTR_ERR(priv->psgmii_hw_addr); ++ } ++ ++ mac_mode = of_get_property(switch_node, "switch_mac_mode", &len); ++ if (!mac_mode) { ++ dev_err(&pdev->dev, "Failed to read switch_mac_mode\n"); ++ return -EINVAL; ++ } ++ priv->mac_mode = be32_to_cpup(mac_mode); ++ ++ ess_clk = of_clk_get_by_name(switch_node, "ess_clk"); ++ if (ess_clk) ++ clk_prepare_enable(ess_clk); ++ ++ priv->ess_rst = devm_reset_control_get(&pdev->dev, "ess_rst"); ++ if (IS_ERR(priv->ess_rst)) { ++ dev_err(&pdev->dev, "Failed to get ess_rst control!\n"); ++ return PTR_ERR(priv->ess_rst); ++ } ++ ++ if (of_property_read_u32(switch_node, "switch_cpu_bmp", ++ &priv->cpu_bmp) || ++ of_property_read_u32(switch_node, "switch_lan_bmp", ++ &priv->lan_bmp) || ++ of_property_read_u32(switch_node, "switch_wan_bmp", ++ &priv->wan_bmp)) { ++ dev_err(&pdev->dev, "Failed to read port properties\n"); ++ return -EIO; ++ } ++ ++ ret = phy_driver_register(&ar40xx_phy_driver, THIS_MODULE); ++ if (ret) { ++ dev_err(&pdev->dev, "Failed to register ar40xx phy driver!\n"); ++ return -EIO; ++ } ++ ++ mutex_init(&priv->reg_mutex); ++ mutex_init(&priv->mib_lock); ++ INIT_DELAYED_WORK(&priv->mib_work, ar40xx_mib_work_func); ++ ++ /* register switch */ ++ swdev = &priv->dev; ++ ++ swdev->alias = dev_name(&priv->mii_bus->dev); ++ ++ swdev->cpu_port = AR40XX_PORT_CPU; ++ swdev->name = "QCA AR40xx"; ++ swdev->vlans = AR40XX_MAX_VLANS; ++ swdev->ports = AR40XX_NUM_PORTS; ++ swdev->ops = &ar40xx_sw_ops; ++ ret = register_switch(swdev, NULL); ++ if (ret) ++ goto err_unregister_phy; ++ ++ num_mibs = ARRAY_SIZE(ar40xx_mibs); ++ len = priv->dev.ports * num_mibs * ++ sizeof(*priv->mib_stats); ++ priv->mib_stats = devm_kzalloc(&pdev->dev, len, GFP_KERNEL); ++ if (!priv->mib_stats) { ++ ret = -ENOMEM; ++ goto err_unregister_switch; ++ } ++ ++ ar40xx_start(priv); ++ ++ if (of_property_read_bool(switch_node, "gpio-controller")) ++ ar40xx_register_gpio(&pdev->dev, ar40xx_priv, switch_node); ++ ++ return 0; ++ ++err_unregister_switch: ++ unregister_switch(&priv->dev); ++err_unregister_phy: ++ phy_driver_unregister(&ar40xx_phy_driver); ++ platform_set_drvdata(pdev, NULL); ++ return ret; ++} ++ ++static int ar40xx_remove(struct platform_device *pdev) ++{ ++ struct ar40xx_priv *priv = platform_get_drvdata(pdev); ++ ++ cancel_delayed_work_sync(&priv->qm_dwork); ++ cancel_delayed_work_sync(&priv->mib_work); ++ ++ unregister_switch(&priv->dev); ++ ++ phy_driver_unregister(&ar40xx_phy_driver); ++ ++ return 0; ++} ++ ++static const struct of_device_id ar40xx_of_mtable[] = { ++ {.compatible = "qcom,ess-switch" }, ++ {} ++}; ++ ++struct platform_driver ar40xx_drv = { ++ .probe = ar40xx_probe, ++ .remove = ar40xx_remove, ++ .driver = { ++ .name = "ar40xx", ++ .of_match_table = ar40xx_of_mtable, ++ }, ++}; ++ ++module_platform_driver(ar40xx_drv); ++ ++MODULE_DESCRIPTION("IPQ40XX ESS driver"); ++MODULE_LICENSE("Dual BSD/GPL"); +--- /dev/null ++++ b/drivers/net/phy/ar40xx.h +@@ -0,0 +1,337 @@ ++/* ++ * Copyright (c) 2016, The Linux Foundation. All rights reserved. ++ * ++ * Permission to use, copy, modify, and/or distribute this software for ++ * any purpose with or without fee is hereby granted, provided that the ++ * above copyright notice and this permission notice appear in all copies. ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT ++ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++ #ifndef __AR40XX_H ++#define __AR40XX_H ++ ++#define AR40XX_MAX_VLANS 128 ++#define AR40XX_NUM_PORTS 6 ++#define AR40XX_NUM_PHYS 5 ++ ++#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) ++ ++struct ar40xx_priv { ++ struct switch_dev dev; ++ ++ u8 __iomem *hw_addr; ++ u8 __iomem *psgmii_hw_addr; ++ u32 mac_mode; ++ struct reset_control *ess_rst; ++ u32 cpu_bmp; ++ u32 lan_bmp; ++ u32 wan_bmp; ++ ++ struct mii_bus *mii_bus; ++ struct phy_device *phy; ++ ++ /* mutex for qm task */ ++ struct mutex qm_lock; ++ struct delayed_work qm_dwork; ++ u32 port_link_up[AR40XX_NUM_PORTS]; ++ u32 ar40xx_port_old_link[AR40XX_NUM_PORTS]; ++ u32 ar40xx_port_qm_buf[AR40XX_NUM_PORTS]; ++ ++ u32 phy_t_status; ++ ++ /* mutex for switch reg access */ ++ struct mutex reg_mutex; ++ ++ /* mutex for mib task */ ++ struct mutex mib_lock; ++ struct delayed_work mib_work; ++ int mib_next_port; ++ u64 *mib_stats; ++ ++ char buf[2048]; ++ ++ /* all fields below will be cleared on reset */ ++ bool vlan; ++ u16 vlan_id[AR40XX_MAX_VLANS]; ++ u8 vlan_table[AR40XX_MAX_VLANS]; ++ u8 vlan_tagged; ++ u16 pvid[AR40XX_NUM_PORTS]; ++ ++ /* mirror */ ++ bool mirror_rx; ++ bool mirror_tx; ++ int source_port; ++ int monitor_port; ++}; ++ ++#define AR40XX_PORT_LINK_UP 1 ++#define AR40XX_PORT_LINK_DOWN 0 ++#define AR40XX_QM_NOT_EMPTY 1 ++#define AR40XX_QM_EMPTY 0 ++ ++#define AR40XX_LAN_VLAN 1 ++#define AR40XX_WAN_VLAN 2 ++ ++enum ar40xx_port_wrapper_cfg { ++ PORT_WRAPPER_PSGMII = 0, ++}; ++ ++struct ar40xx_mib_desc { ++ u32 size; ++ u32 offset; ++ const char *name; ++}; ++ ++#define AR40XX_PORT_CPU 0 ++ ++#define AR40XX_PSGMII_MODE_CONTROL 0x1b4 ++#define AR40XX_PSGMII_ATHR_CSCO_MODE_25M BIT(0) ++ ++#define AR40XX_PSGMIIPHY_TX_CONTROL 0x288 ++ ++#define AR40XX_MII_ATH_MMD_ADDR 0x0d ++#define AR40XX_MII_ATH_MMD_DATA 0x0e ++#define AR40XX_MII_ATH_DBG_ADDR 0x1d ++#define AR40XX_MII_ATH_DBG_DATA 0x1e ++ ++#define AR40XX_STATS_RXBROAD 0x00 ++#define AR40XX_STATS_RXPAUSE 0x04 ++#define AR40XX_STATS_RXMULTI 0x08 ++#define AR40XX_STATS_RXFCSERR 0x0c ++#define AR40XX_STATS_RXALIGNERR 0x10 ++#define AR40XX_STATS_RXRUNT 0x14 ++#define AR40XX_STATS_RXFRAGMENT 0x18 ++#define AR40XX_STATS_RX64BYTE 0x1c ++#define AR40XX_STATS_RX128BYTE 0x20 ++#define AR40XX_STATS_RX256BYTE 0x24 ++#define AR40XX_STATS_RX512BYTE 0x28 ++#define AR40XX_STATS_RX1024BYTE 0x2c ++#define AR40XX_STATS_RX1518BYTE 0x30 ++#define AR40XX_STATS_RXMAXBYTE 0x34 ++#define AR40XX_STATS_RXTOOLONG 0x38 ++#define AR40XX_STATS_RXGOODBYTE 0x3c ++#define AR40XX_STATS_RXBADBYTE 0x44 ++#define AR40XX_STATS_RXOVERFLOW 0x4c ++#define AR40XX_STATS_FILTERED 0x50 ++#define AR40XX_STATS_TXBROAD 0x54 ++#define AR40XX_STATS_TXPAUSE 0x58 ++#define AR40XX_STATS_TXMULTI 0x5c ++#define AR40XX_STATS_TXUNDERRUN 0x60 ++#define AR40XX_STATS_TX64BYTE 0x64 ++#define AR40XX_STATS_TX128BYTE 0x68 ++#define AR40XX_STATS_TX256BYTE 0x6c ++#define AR40XX_STATS_TX512BYTE 0x70 ++#define AR40XX_STATS_TX1024BYTE 0x74 ++#define AR40XX_STATS_TX1518BYTE 0x78 ++#define AR40XX_STATS_TXMAXBYTE 0x7c ++#define AR40XX_STATS_TXOVERSIZE 0x80 ++#define AR40XX_STATS_TXBYTE 0x84 ++#define AR40XX_STATS_TXCOLLISION 0x8c ++#define AR40XX_STATS_TXABORTCOL 0x90 ++#define AR40XX_STATS_TXMULTICOL 0x94 ++#define AR40XX_STATS_TXSINGLECOL 0x98 ++#define AR40XX_STATS_TXEXCDEFER 0x9c ++#define AR40XX_STATS_TXDEFER 0xa0 ++#define AR40XX_STATS_TXLATECOL 0xa4 ++ ++#define AR40XX_REG_MODULE_EN 0x030 ++#define AR40XX_MODULE_EN_MIB BIT(0) ++ ++#define AR40XX_REG_MIB_FUNC 0x034 ++#define AR40XX_MIB_BUSY BIT(17) ++#define AR40XX_MIB_CPU_KEEP BIT(20) ++#define AR40XX_MIB_FUNC BITS(24, 3) ++#define AR40XX_MIB_FUNC_S 24 ++#define AR40XX_MIB_FUNC_NO_OP 0x0 ++#define AR40XX_MIB_FUNC_FLUSH 0x1 ++ ++#define AR40XX_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) ++#define AR40XX_PORT_SPEED BITS(0, 2) ++#define AR40XX_PORT_STATUS_SPEED_S 0 ++#define AR40XX_PORT_TX_EN BIT(2) ++#define AR40XX_PORT_RX_EN BIT(3) ++#define AR40XX_PORT_STATUS_TXFLOW BIT(4) ++#define AR40XX_PORT_STATUS_RXFLOW BIT(5) ++#define AR40XX_PORT_DUPLEX BIT(6) ++#define AR40XX_PORT_TXHALF_FLOW BIT(7) ++#define AR40XX_PORT_STATUS_LINK_UP BIT(8) ++#define AR40XX_PORT_AUTO_LINK_EN BIT(9) ++#define AR40XX_PORT_STATUS_FLOW_CONTROL BIT(12) ++ ++#define AR40XX_REG_MAX_FRAME_SIZE 0x078 ++#define AR40XX_MAX_FRAME_SIZE_MTU BITS(0, 14) ++ ++#define AR40XX_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) ++ ++#define AR40XX_REG_EEE_CTRL 0x100 ++#define AR40XX_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2) ++ ++#define AR40XX_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) ++#define AR40XX_PORT_VLAN0_DEF_SVID BITS(0, 12) ++#define AR40XX_PORT_VLAN0_DEF_SVID_S 0 ++#define AR40XX_PORT_VLAN0_DEF_CVID BITS(16, 12) ++#define AR40XX_PORT_VLAN0_DEF_CVID_S 16 ++ ++#define AR40XX_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) ++#define AR40XX_PORT_VLAN1_PORT_VLAN_PROP BIT(6) ++#define AR40XX_PORT_VLAN1_OUT_MODE BITS(12, 2) ++#define AR40XX_PORT_VLAN1_OUT_MODE_S 12 ++#define AR40XX_PORT_VLAN1_OUT_MODE_UNMOD 0 ++#define AR40XX_PORT_VLAN1_OUT_MODE_UNTAG 1 ++#define AR40XX_PORT_VLAN1_OUT_MODE_TAG 2 ++#define AR40XX_PORT_VLAN1_OUT_MODE_UNTOUCH 3 ++ ++#define AR40XX_REG_VTU_FUNC0 0x0610 ++#define AR40XX_VTU_FUNC0_EG_MODE BITS(4, 14) ++#define AR40XX_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) ++#define AR40XX_VTU_FUNC0_EG_MODE_KEEP 0 ++#define AR40XX_VTU_FUNC0_EG_MODE_UNTAG 1 ++#define AR40XX_VTU_FUNC0_EG_MODE_TAG 2 ++#define AR40XX_VTU_FUNC0_EG_MODE_NOT 3 ++#define AR40XX_VTU_FUNC0_IVL BIT(19) ++#define AR40XX_VTU_FUNC0_VALID BIT(20) ++ ++#define AR40XX_REG_VTU_FUNC1 0x0614 ++#define AR40XX_VTU_FUNC1_OP BITS(0, 3) ++#define AR40XX_VTU_FUNC1_OP_NOOP 0 ++#define AR40XX_VTU_FUNC1_OP_FLUSH 1 ++#define AR40XX_VTU_FUNC1_OP_LOAD 2 ++#define AR40XX_VTU_FUNC1_OP_PURGE 3 ++#define AR40XX_VTU_FUNC1_OP_REMOVE_PORT 4 ++#define AR40XX_VTU_FUNC1_OP_GET_NEXT 5 ++#define AR40XX7_VTU_FUNC1_OP_GET_ONE 6 ++#define AR40XX_VTU_FUNC1_FULL BIT(4) ++#define AR40XX_VTU_FUNC1_PORT BIT(8, 4) ++#define AR40XX_VTU_FUNC1_PORT_S 8 ++#define AR40XX_VTU_FUNC1_VID BIT(16, 12) ++#define AR40XX_VTU_FUNC1_VID_S 16 ++#define AR40XX_VTU_FUNC1_BUSY BIT(31) ++ ++#define AR40XX_REG_FWD_CTRL0 0x620 ++#define AR40XX_FWD_CTRL0_CPU_PORT_EN BIT(10) ++#define AR40XX_FWD_CTRL0_MIRROR_PORT BITS(4, 4) ++#define AR40XX_FWD_CTRL0_MIRROR_PORT_S 4 ++ ++#define AR40XX_REG_FWD_CTRL1 0x624 ++#define AR40XX_FWD_CTRL1_UC_FLOOD BITS(0, 7) ++#define AR40XX_FWD_CTRL1_UC_FLOOD_S 0 ++#define AR40XX_FWD_CTRL1_MC_FLOOD BITS(8, 7) ++#define AR40XX_FWD_CTRL1_MC_FLOOD_S 8 ++#define AR40XX_FWD_CTRL1_BC_FLOOD BITS(16, 7) ++#define AR40XX_FWD_CTRL1_BC_FLOOD_S 16 ++#define AR40XX_FWD_CTRL1_IGMP BITS(24, 7) ++#define AR40XX_FWD_CTRL1_IGMP_S 24 ++ ++#define AR40XX_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) ++#define AR40XX_PORT_LOOKUP_MEMBER BITS(0, 7) ++#define AR40XX_PORT_LOOKUP_IN_MODE BITS(8, 2) ++#define AR40XX_PORT_LOOKUP_IN_MODE_S 8 ++#define AR40XX_PORT_LOOKUP_STATE BITS(16, 3) ++#define AR40XX_PORT_LOOKUP_STATE_S 16 ++#define AR40XX_PORT_LOOKUP_LEARN BIT(20) ++#define AR40XX_PORT_LOOKUP_LOOPBACK BIT(21) ++#define AR40XX_PORT_LOOKUP_ING_MIRROR_EN BIT(25) ++ ++#define AR40XX_REG_ATU_FUNC 0x60c ++#define AR40XX_ATU_FUNC_OP BITS(0, 4) ++#define AR40XX_ATU_FUNC_OP_NOOP 0x0 ++#define AR40XX_ATU_FUNC_OP_FLUSH 0x1 ++#define AR40XX_ATU_FUNC_OP_LOAD 0x2 ++#define AR40XX_ATU_FUNC_OP_PURGE 0x3 ++#define AR40XX_ATU_FUNC_OP_FLUSH_LOCKED 0x4 ++#define AR40XX_ATU_FUNC_OP_FLUSH_UNICAST 0x5 ++#define AR40XX_ATU_FUNC_OP_GET_NEXT 0x6 ++#define AR40XX_ATU_FUNC_OP_SEARCH_MAC 0x7 ++#define AR40XX_ATU_FUNC_OP_CHANGE_TRUNK 0x8 ++#define AR40XX_ATU_FUNC_BUSY BIT(31) ++ ++#define AR40XX_REG_QM_DEBUG_ADDR 0x820 ++#define AR40XX_REG_QM_DEBUG_VALUE 0x824 ++#define AR40XX_REG_QM_PORT0_3_QNUM 0x1d ++#define AR40XX_REG_QM_PORT4_6_QNUM 0x1e ++ ++#define AR40XX_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8) ++#define AR40XX_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16) ++ ++#define AR40XX_REG_PORT_FLOWCTRL_THRESH(_i) (0x9b0 + (_i) * 0x4) ++#define AR40XX_PORT0_FC_THRESH_ON_DFLT 0x60 ++#define AR40XX_PORT0_FC_THRESH_OFF_DFLT 0x90 ++ ++#define AR40XX_PHY_DEBUG_0 0 ++#define AR40XX_PHY_MANU_CTRL_EN BIT(12) ++ ++#define AR40XX_PHY_DEBUG_2 2 ++ ++#define AR40XX_PHY_SPEC_STATUS 0x11 ++#define AR40XX_PHY_SPEC_STATUS_LINK BIT(10) ++#define AR40XX_PHY_SPEC_STATUS_DUPLEX BIT(13) ++#define AR40XX_PHY_SPEC_STATUS_SPEED BITS(14, 2) ++ ++/* port forwarding state */ ++enum { ++ AR40XX_PORT_STATE_DISABLED = 0, ++ AR40XX_PORT_STATE_BLOCK = 1, ++ AR40XX_PORT_STATE_LISTEN = 2, ++ AR40XX_PORT_STATE_LEARN = 3, ++ AR40XX_PORT_STATE_FORWARD = 4 ++}; ++ ++/* ingress 802.1q mode */ ++enum { ++ AR40XX_IN_PORT_ONLY = 0, ++ AR40XX_IN_PORT_FALLBACK = 1, ++ AR40XX_IN_VLAN_ONLY = 2, ++ AR40XX_IN_SECURE = 3 ++}; ++ ++/* egress 802.1q mode */ ++enum { ++ AR40XX_OUT_KEEP = 0, ++ AR40XX_OUT_STRIP_VLAN = 1, ++ AR40XX_OUT_ADD_VLAN = 2 ++}; ++ ++/* port speed */ ++enum { ++ AR40XX_PORT_SPEED_10M = 0, ++ AR40XX_PORT_SPEED_100M = 1, ++ AR40XX_PORT_SPEED_1000M = 2, ++ AR40XX_PORT_SPEED_ERR = 3, ++}; ++ ++#define AR40XX_MIB_WORK_DELAY 2000 /* msecs */ ++ ++#define AR40XX_QM_WORK_DELAY 100 ++ ++#define AR40XX_MIB_FUNC_CAPTURE 0x3 ++ ++#define AR40XX_REG_PORT_STATS_START 0x1000 ++#define AR40XX_REG_PORT_STATS_LEN 0x100 ++ ++#define AR40XX_PORTS_ALL 0x3f ++ ++#define AR40XX_PSGMII_ID 5 ++#define AR40XX_PSGMII_CALB_NUM 100 ++#define AR40XX_MALIBU_PSGMII_MODE_CTRL 0x6d ++#define AR40XX_MALIBU_PHY_PSGMII_MODE_CTRL_ADJUST_VAL 0x220c ++#define AR40XX_MALIBU_PHY_MMD7_DAC_CTRL 0x801a ++#define AR40XX_MALIBU_DAC_CTRL_MASK 0x380 ++#define AR40XX_MALIBU_DAC_CTRL_VALUE 0x280 ++#define AR40XX_MALIBU_PHY_RLP_CTRL 0x805a ++#define AR40XX_PSGMII_TX_DRIVER_1_CTRL 0xb ++#define AR40XX_MALIBU_PHY_PSGMII_REDUCE_SERDES_TX_AMP 0x8a ++#define AR40XX_MALIBU_PHY_LAST_ADDR 4 ++ ++static inline struct ar40xx_priv * ++swdev_to_ar40xx(struct switch_dev *swdev) ++{ ++ return container_of(swdev, struct ar40xx_priv, dev); ++} ++ ++#endif diff --git a/target/linux/ipq40xx/patches-4.14/713-0001-essedma-fixup-ethernet-driver-rx-bug.patch b/target/linux/ipq40xx/patches-4.14/713-0001-essedma-fixup-ethernet-driver-rx-bug.patch new file mode 100644 index 0000000000..75267b688d --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/713-0001-essedma-fixup-ethernet-driver-rx-bug.patch @@ -0,0 +1,144 @@ +From 0bcfbe3c613d6ed8044404bc1cc3c29ff961d89c Mon Sep 17 00:00:00 2001 +From: Chen Minqiang +Date: Thu, 15 Mar 2018 04:59:57 +0800 +Subject: [PATCH 1/2] essedma: fixup ethernet driver rx bug + + - modify the error rx ring full conditions + - in rare cases, out of memory allocation failure causes the receive queues stop + we use the timer to re-alloc rx rings under these circumstances + +Signed-off-by: Chen Minqiang +--- + drivers/net/ethernet/qualcomm/essedma/edma.c | 51 ++++++++++++++++++++++-- + drivers/net/ethernet/qualcomm/essedma/edma.h | 3 ++ + drivers/net/ethernet/qualcomm/essedma/edma_axi.c | 8 ++++ + 3 files changed, 58 insertions(+), 4 deletions(-) + +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma.c b/drivers/net/ethernet/qualcomm/essedma/edma.c +index fecc0ba..3f1da93 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.c +@@ -530,6 +530,47 @@ static int edma_rx_complete_paged(struct sk_buff *skb, u16 num_rfds, u16 length, + return sw_next_to_clean; + } + ++static int edma_rfd_desc_unused(struct edma_rfd_desc_ring *erdr) ++{ ++ if (erdr->sw_next_to_clean > erdr->sw_next_to_fill) ++ return erdr->sw_next_to_clean - erdr->sw_next_to_fill - 1; ++ return erdr->count + erdr->sw_next_to_clean - erdr->sw_next_to_fill - 1; ++} ++ ++void edma_rx_realloc(unsigned long data) ++{ ++ struct edma_per_cpu_queues_info *edma_percpu_info = (struct edma_per_cpu_queues_info *)data; ++ struct edma_common_info *edma_cinfo = edma_percpu_info->edma_cinfo; ++ s32 status = edma_percpu_info->rx_realloc_status; ++ ++ while (status) { ++ int queue_id; ++ int ret_count; ++ struct edma_rfd_desc_ring *erdr; ++ ++ queue_id = ffs(status) - 1; ++ erdr = edma_cinfo->rfd_ring[queue_id]; ++ ret_count = edma_alloc_rx_buf(edma_cinfo, erdr, edma_rfd_desc_unused(erdr), queue_id); ++ if (ret_count == 0) { ++ edma_percpu_info->rx_realloc_status &= ~(1 << queue_id); ++ } ++ status &= ~(1 << queue_id); ++ } ++ ++ if (edma_percpu_info->rx_realloc_status) { ++ mod_timer(&edma_percpu_info->rx_realloc_timer, jiffies + HZ); ++ } ++} ++ ++static inline void edma_realloc_timer_start(struct napi_struct *napi, int queue_id) ++{ ++ struct edma_per_cpu_queues_info *edma_percpu_info = container_of(napi, ++ struct edma_per_cpu_queues_info, napi); ++ ++ edma_percpu_info->rx_realloc_status |= (1 << queue_id); ++ mod_timer(&edma_percpu_info->rx_realloc_timer, jiffies + 5 * HZ); /* restart alloc in 5 secs */ ++} ++ + /* + * edma_rx_complete() + * Main api called from the poll function to process rx packets. +@@ -754,10 +795,12 @@ static void edma_rx_complete(struct edma_common_info *edma_cinfo, + erdr->sw_next_to_clean = sw_next_to_clean; + + /* Refill here in case refill threshold wasn't reached */ +- if (likely(cleaned_count)) { +- ret_count = edma_alloc_rx_buf(edma_cinfo, erdr, cleaned_count, queue_id); +- if (ret_count) ++ if (edma_rfd_desc_unused(erdr)) { ++ ret_count = edma_alloc_rx_buf(edma_cinfo, erdr, edma_rfd_desc_unused(erdr), queue_id); ++ if (ret_count) { + dev_dbg(&pdev->dev, "Not all buffers was reallocated"); ++ edma_realloc_timer_start(napi, queue_id); ++ } + edma_write_reg(EDMA_REG_RX_SW_CONS_IDX_Q(queue_id), + erdr->sw_next_to_clean); + } +@@ -1801,7 +1844,7 @@ int edma_configure(struct edma_common_info *edma_cinfo) + /* Allocate the RX buffer */ + for (i = 0, j = 0; i < edma_cinfo->num_rx_queues; i++) { + struct edma_rfd_desc_ring *ring = edma_cinfo->rfd_ring[j]; +- ret_count = edma_alloc_rx_buf(edma_cinfo, ring, ring->count, j); ++ ret_count = edma_alloc_rx_buf(edma_cinfo, ring, edma_rfd_desc_unused(ring), j); + if (ret_count) { + dev_dbg(&edma_cinfo->pdev->dev, "not all rx buffers allocated\n"); + } +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma.h b/drivers/net/ethernet/qualcomm/essedma/edma.h +index 5d6dc73..29c8379 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma.h ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.h +@@ -304,6 +304,8 @@ struct edma_per_cpu_queues_info { + u32 tx_start; /* tx queue start */ + u32 rx_start; /* rx queue start */ + struct edma_common_info *edma_cinfo; /* edma common info */ ++ u32 rx_realloc_status; ++ struct timer_list rx_realloc_timer; + }; + + /* edma specific common info */ +@@ -448,6 +450,7 @@ void edma_change_tx_coalesce(int usecs); + void edma_change_rx_coalesce(int usecs); + void edma_get_tx_rx_coalesce(u32 *reg_val); + void edma_clear_irq_status(void); ++void edma_rx_realloc(unsigned long data); + void ess_set_port_status_speed(struct edma_common_info *edma_cinfo, + struct phy_device *phydev, uint8_t port_id); + #endif /* _EDMA_H_ */ +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma_axi.c b/drivers/net/ethernet/qualcomm/essedma/edma_axi.c +index 81fc1e1..d9f8b52 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma_axi.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma_axi.c +@@ -1131,6 +1131,11 @@ static int edma_axi_probe(struct platform_device *pdev) + edma_cinfo->edma_percpu_info[i].rx_status = 0; + edma_cinfo->edma_percpu_info[i].edma_cinfo = edma_cinfo; + ++ edma_cinfo->edma_percpu_info[i].rx_realloc_status = 0; ++ init_timer(&edma_cinfo->edma_percpu_info[i].rx_realloc_timer); ++ edma_cinfo->edma_percpu_info[i].rx_realloc_timer.function = edma_rx_realloc; ++ edma_cinfo->edma_percpu_info[i].rx_realloc_timer.data = (unsigned long)&edma_cinfo->edma_percpu_info[i]; ++ + /* Request irq per core */ + for (j = edma_cinfo->edma_percpu_info[i].tx_start; + j < tx_start[i] + 4; j++) { +@@ -1259,7 +1264,10 @@ err_configure: + err_rmap_add_fail: + edma_free_irqs(adapter[0]); + for (i = 0; i < CONFIG_NR_CPUS; i++) ++ { + napi_disable(&edma_cinfo->edma_percpu_info[i].napi); ++ del_timer_sync(&edma_cinfo->edma_percpu_info[i].rx_realloc_timer); ++ } + err_reset: + err_unregister_sysctl_tbl: + err_rmap_alloc_fail: +-- +2.7.4 + diff --git a/target/linux/ipq40xx/patches-4.14/713-0002-essedma-refine-txq-to-be-adaptive-of-cpus-and-netdev.patch b/target/linux/ipq40xx/patches-4.14/713-0002-essedma-refine-txq-to-be-adaptive-of-cpus-and-netdev.patch new file mode 100644 index 0000000000..6c6ef84abc --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/713-0002-essedma-refine-txq-to-be-adaptive-of-cpus-and-netdev.patch @@ -0,0 +1,223 @@ +From 7be0cb35513b07bf74d93d052d57b12e2c654b43 Mon Sep 17 00:00:00 2001 +From: Chen Minqiang +Date: Thu, 15 Mar 2018 05:04:37 +0800 +Subject: [PATCH 2/2] essedma: refine txq to be adaptive of cpus and netdev + + - use 4 queue for each cpu if only 1 netdev + - use all 16 txqueue if only 1 netdev + +Signed-off-by: Chen Minqiang +--- + drivers/net/ethernet/qualcomm/essedma/edma.c | 22 +++++-------- + drivers/net/ethernet/qualcomm/essedma/edma.h | 5 +-- + drivers/net/ethernet/qualcomm/essedma/edma_axi.c | 40 ++++++++++++++---------- + 3 files changed, 35 insertions(+), 32 deletions(-) + +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma.c b/drivers/net/ethernet/qualcomm/essedma/edma.c +index 3f1da93..05f9ce9 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.c +@@ -22,14 +22,6 @@ extern struct net_device *edma_netdev[EDMA_MAX_PORTID_SUPPORTED]; + bool edma_stp_rstp; + u16 edma_ath_eth_type; + +-/* edma_skb_priority_offset() +- * get edma skb priority +- */ +-static unsigned int edma_skb_priority_offset(struct sk_buff *skb) +-{ +- return (skb->priority >> 2) & 1; +-} +- + /* edma_alloc_tx_ring() + * Allocate Tx descriptors ring + */ +@@ -1042,13 +1034,14 @@ static inline u16 edma_tpd_available(struct edma_common_info *edma_cinfo, + /* edma_tx_queue_get() + * Get the starting number of the queue + */ +-static inline int edma_tx_queue_get(struct edma_adapter *adapter, ++static inline int edma_tx_queue_get(struct edma_common_info *edma_cinfo, struct edma_adapter *adapter, + struct sk_buff *skb, int txq_id) + { + /* skb->priority is used as an index to skb priority table + * and based on packet priority, correspong queue is assigned. ++ * FIXME we just simple use jiffies for time base balance + */ +- return adapter->tx_start_offset[txq_id] + edma_skb_priority_offset(skb); ++ return adapter->tx_start_offset[txq_id] + (jiffies % edma_cinfo->num_txq_per_core_netdev); + } + + /* edma_tx_update_hw_idx() +@@ -1417,8 +1410,9 @@ netdev_tx_t edma_xmit(struct sk_buff *skb, + } + + /* this will be one of the 4 TX queues exposed to linux kernel */ +- txq_id = skb_get_queue_mapping(skb); +- queue_id = edma_tx_queue_get(adapter, skb, txq_id); ++ /* XXX what if num_online_cpus() > EDMA_CPU_CORES_SUPPORTED */ ++ txq_id = smp_processor_id() % EDMA_CPU_CORES_SUPPORTED; ++ queue_id = edma_tx_queue_get(edma_cinfo, adapter, skb, txq_id); + etdr = edma_cinfo->tpd_ring[queue_id]; + nq = netdev_get_tx_queue(net_dev, txq_id); + +@@ -1899,8 +1893,8 @@ void edma_free_irqs(struct edma_adapter *adapter) + int i, j; + int k = ((edma_cinfo->num_rx_queues == 4) ? 1 : 2); + +- for (i = 0; i < CONFIG_NR_CPUS; i++) { +- for (j = edma_cinfo->edma_percpu_info[i].tx_start; j < (edma_cinfo->edma_percpu_info[i].tx_start + 4); j++) ++ for (i = 0; i < num_online_cpus() && i < EDMA_CPU_CORES_SUPPORTED; i++) { ++ for (j = edma_cinfo->edma_percpu_info[i].tx_start; j < (edma_cinfo->edma_percpu_info[i].tx_start + edma_cinfo->num_txq_per_core); j++) + free_irq(edma_cinfo->tx_irq[j], &edma_cinfo->edma_percpu_info[i]); + + for (j = edma_cinfo->edma_percpu_info[i].rx_start; j < (edma_cinfo->edma_percpu_info[i].rx_start + k); j++) +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma.h b/drivers/net/ethernet/qualcomm/essedma/edma.h +index 29c8379..2ba43e0 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma.h ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.h +@@ -325,6 +325,7 @@ struct edma_common_info { + u32 from_cpu; /* from CPU TPD field */ + u32 num_rxq_per_core; /* Rx queues per core */ + u32 num_txq_per_core; /* Tx queues per core */ ++ u32 num_txq_per_core_netdev; /* Tx queues per core per netdev */ + u16 tx_ring_count; /* Tx ring count */ + u16 rx_ring_count; /* Rx ring*/ + u16 rx_head_buffer_len; /* rx buffer length */ +@@ -332,7 +333,7 @@ struct edma_common_info { + u32 page_mode; /* Jumbo frame supported flag */ + u32 fraglist_mode; /* fraglist supported flag */ + struct edma_hw hw; /* edma hw specific structure */ +- struct edma_per_cpu_queues_info edma_percpu_info[CONFIG_NR_CPUS]; /* per cpu information */ ++ struct edma_per_cpu_queues_info edma_percpu_info[EDMA_CPU_CORES_SUPPORTED]; /* per cpu information */ + spinlock_t stats_lock; /* protect edma stats area for updation */ + + bool is_single_phy; +@@ -401,7 +402,7 @@ struct edma_adapter { + u32 link_state; /* phy link state */ + u32 phy_mdio_addr; /* PHY device address on MII interface */ + u32 poll_required; /* check if link polling is required */ +- u32 tx_start_offset[CONFIG_NR_CPUS]; /* tx queue start */ ++ u32 tx_start_offset[EDMA_CPU_CORES_SUPPORTED]; /* tx queue start */ + u32 default_vlan_tag; /* vlan tag */ + u32 dp_bitmap; + uint8_t phy_id[MII_BUS_ID_SIZE + 3]; +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma_axi.c b/drivers/net/ethernet/qualcomm/essedma/edma_axi.c +index d9f8b52..5824680 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma_axi.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma_axi.c +@@ -721,11 +721,7 @@ static int edma_axi_probe(struct platform_device *pdev) + int i, j, k, err = 0; + int portid_bmp; + int idx = 0, idx_mac = 0; +- +- if (CONFIG_NR_CPUS != EDMA_CPU_CORES_SUPPORTED) { +- dev_err(&pdev->dev, "Invalid CPU Cores\n"); +- return -EINVAL; +- } ++ int netdev_group = 2; + + if ((num_rxq != 4) && (num_rxq != 8)) { + dev_err(&pdev->dev, "Invalid RX queue, edma probe failed\n"); +@@ -749,7 +745,7 @@ static int edma_axi_probe(struct platform_device *pdev) + /* Initialize the netdev array before allocation + * to avoid double free + */ +- for (i = 0 ; i < edma_cinfo->num_gmac ; i++) ++ for (i = 0 ; i < EDMA_MAX_PORTID_SUPPORTED; i++) + edma_netdev[i] = NULL; + + for (i = 0 ; i < edma_cinfo->num_gmac ; i++) { +@@ -770,8 +766,11 @@ static int edma_axi_probe(struct platform_device *pdev) + + /* Fill ring details */ + edma_cinfo->num_tx_queues = EDMA_MAX_TRANSMIT_QUEUE; +- edma_cinfo->num_txq_per_core = (EDMA_MAX_TRANSMIT_QUEUE / 4); ++ edma_cinfo->num_txq_per_core = (EDMA_MAX_TRANSMIT_QUEUE / num_online_cpus()); ++ edma_cinfo->num_txq_per_core_netdev = (EDMA_MAX_TRANSMIT_QUEUE / netdev_group / num_online_cpus()); + edma_cinfo->tx_ring_count = EDMA_TX_RING_SIZE; ++ if (edma_cinfo->num_txq_per_core == 0) ++ edma_cinfo->num_txq_per_core = 1; + + /* Update num rx queues based on module parameter */ + edma_cinfo->num_rx_queues = num_rxq; +@@ -941,6 +940,13 @@ static int edma_axi_probe(struct platform_device *pdev) + idx_mac++; + } + ++ if (edma_cinfo->num_gmac == 1) { ++ netdev_group = 1; ++ edma_cinfo->num_txq_per_core_netdev = (EDMA_MAX_TRANSMIT_QUEUE / netdev_group / num_online_cpus()); ++ } ++ if (edma_cinfo->num_txq_per_core_netdev == 0) ++ edma_cinfo->num_txq_per_core_netdev = 1; ++ + /* Populate the adapter structure register the netdevice */ + for (i = 0; i < edma_cinfo->num_gmac; i++) { + int k, m; +@@ -948,17 +954,16 @@ static int edma_axi_probe(struct platform_device *pdev) + adapter[i] = netdev_priv(edma_netdev[i]); + adapter[i]->netdev = edma_netdev[i]; + adapter[i]->pdev = pdev; +- for (j = 0; j < CONFIG_NR_CPUS; j++) { +- m = i % 2; +- adapter[i]->tx_start_offset[j] = +- ((j << EDMA_TX_CPU_START_SHIFT) + (m << 1)); ++ for (j = 0; j < num_online_cpus() && j < EDMA_CPU_CORES_SUPPORTED; j++) { ++ m = i % netdev_group; ++ adapter[i]->tx_start_offset[j] = j * edma_cinfo->num_txq_per_core + m * edma_cinfo->num_txq_per_core_netdev; + /* Share the queues with available net-devices. + * For instance , with 5 net-devices + * eth0/eth2/eth4 will share q0,q1,q4,q5,q8,q9,q12,q13 + * and eth1/eth3 will get the remaining. + */ + for (k = adapter[i]->tx_start_offset[j]; k < +- (adapter[i]->tx_start_offset[j] + 2); k++) { ++ (adapter[i]->tx_start_offset[j] + edma_cinfo->num_txq_per_core_netdev); k++) { + if (edma_fill_netdev(edma_cinfo, k, i, j)) { + pr_err("Netdev overflow Error\n"); + goto err_register; +@@ -1111,9 +1116,12 @@ static int edma_axi_probe(struct platform_device *pdev) + /* populate per_core_info, do a napi_Add, request 16 TX irqs, + * 8 RX irqs, do a napi enable + */ +- for (i = 0; i < CONFIG_NR_CPUS; i++) { ++ for (i = 0; i < num_online_cpus() && i < EDMA_MAX_TRANSMIT_QUEUE; i++) { + u8 rx_start; + ++ tx_mask[i] = (0xFFFF >> (16 - edma_cinfo->num_txq_per_core)) << (i * edma_cinfo->num_txq_per_core); ++ tx_start[i] = i * edma_cinfo->num_txq_per_core; ++ + edma_cinfo->edma_percpu_info[i].napi.state = 0; + + netif_napi_add(edma_netdev[0], +@@ -1138,7 +1146,7 @@ static int edma_axi_probe(struct platform_device *pdev) + + /* Request irq per core */ + for (j = edma_cinfo->edma_percpu_info[i].tx_start; +- j < tx_start[i] + 4; j++) { ++ j < tx_start[i] + edma_cinfo->num_txq_per_core; j++) { + sprintf(&edma_tx_irq[j][0], "edma_eth_tx%d", j); + err = request_irq(edma_cinfo->tx_irq[j], + edma_interrupt, +@@ -1263,7 +1271,7 @@ err_configure: + #endif + err_rmap_add_fail: + edma_free_irqs(adapter[0]); +- for (i = 0; i < CONFIG_NR_CPUS; i++) ++ for (i = 0; i < num_online_cpus() && i < EDMA_CPU_CORES_SUPPORTED; i++) + { + napi_disable(&edma_cinfo->edma_percpu_info[i].napi); + del_timer_sync(&edma_cinfo->edma_percpu_info[i].rx_realloc_timer); +@@ -1314,7 +1322,7 @@ static int edma_axi_remove(struct platform_device *pdev) + unregister_netdev(edma_netdev[i]); + + edma_stop_rx_tx(hw); +- for (i = 0; i < CONFIG_NR_CPUS; i++) ++ for (i = 0; i < num_online_cpus() && i < EDMA_CPU_CORES_SUPPORTED; i++) + napi_disable(&edma_cinfo->edma_percpu_info[i].napi); + + edma_irq_disable(edma_cinfo); +-- +2.7.4 + diff --git a/target/linux/ipq40xx/patches-4.14/715-essedma-refine-txq-to-be-adaptive-of-cpus-and-netdev.patch b/target/linux/ipq40xx/patches-4.14/715-essedma-refine-txq-to-be-adaptive-of-cpus-and-netdev.patch new file mode 100644 index 0000000000..d8d09c1425 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/715-essedma-refine-txq-to-be-adaptive-of-cpus-and-netdev.patch @@ -0,0 +1,205 @@ +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma.c b/drivers/net/ethernet/qualcomm/essedma/edma.c +index 724f355..7a16236 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.c +@@ -22,14 +22,6 @@ extern struct net_device *edma_netdev[EDMA_MAX_PORTID_SUPPORTED]; + bool edma_stp_rstp; + u16 edma_ath_eth_type; + +-/* edma_skb_priority_offset() +- * get edma skb priority +- */ +-static unsigned int edma_skb_priority_offset(struct sk_buff *skb) +-{ +- return (skb->priority >> 2) & 1; +-} +- + /* edma_alloc_tx_ring() + * Allocate Tx descriptors ring + */ +@@ -1014,13 +1006,14 @@ static inline u16 edma_tpd_available(struct edma_common_info *edma_cinfo, + /* edma_tx_queue_get() + * Get the starting number of the queue + */ +-static inline int edma_tx_queue_get(struct edma_adapter *adapter, ++static inline int edma_tx_queue_get(struct edma_common_info *edma_cinfo, struct edma_adapter *adapter, + struct sk_buff *skb, int txq_id) + { + /* skb->priority is used as an index to skb priority table + * and based on packet priority, correspong queue is assigned. ++ * FIXME we just simple use jiffies for time base balance + */ +- return adapter->tx_start_offset[txq_id] + edma_skb_priority_offset(skb); ++ return adapter->tx_start_offset[txq_id] + (smp_processor_id() % edma_cinfo->num_txq_per_core_netdev); + } + + /* edma_tx_update_hw_idx() +@@ -1389,8 +1382,9 @@ netdev_tx_t edma_xmit(struct sk_buff *skb, + } + + /* this will be one of the 4 TX queues exposed to linux kernel */ +- txq_id = skb_get_queue_mapping(skb); +- queue_id = edma_tx_queue_get(adapter, skb, txq_id); ++ /* XXX what if num_online_cpus() > EDMA_CPU_CORES_SUPPORTED */ ++ txq_id = ((jiffies >> 5) % (EDMA_CPU_CORES_SUPPORTED - 1) + smp_processor_id() + 1) % EDMA_CPU_CORES_SUPPORTED; ++ queue_id = edma_tx_queue_get(edma_cinfo, adapter, skb, txq_id); + etdr = edma_cinfo->tpd_ring[queue_id]; + nq = netdev_get_tx_queue(net_dev, txq_id); + +@@ -1871,8 +1865,8 @@ void edma_free_irqs(struct edma_adapter *adapter) + int i, j; + int k = ((edma_cinfo->num_rx_queues == 4) ? 1 : 2); + +- for (i = 0; i < CONFIG_NR_CPUS; i++) { +- for (j = edma_cinfo->edma_percpu_info[i].tx_start; j < (edma_cinfo->edma_percpu_info[i].tx_start + 4); j++) ++ for (i = 0; i < num_online_cpus() && i < EDMA_CPU_CORES_SUPPORTED; i++) { ++ for (j = edma_cinfo->edma_percpu_info[i].tx_start; j < (edma_cinfo->edma_percpu_info[i].tx_start + edma_cinfo->num_txq_per_core); j++) + free_irq(edma_cinfo->tx_irq[j], &edma_cinfo->edma_percpu_info[i]); + + for (j = edma_cinfo->edma_percpu_info[i].rx_start; j < (edma_cinfo->edma_percpu_info[i].rx_start + k); j++) +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma.h b/drivers/net/ethernet/qualcomm/essedma/edma.h +index 015e5f5..abb0bd5 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma.h ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.h +@@ -324,6 +324,7 @@ struct edma_common_info { + u32 from_cpu; /* from CPU TPD field */ + u32 num_rxq_per_core; /* Rx queues per core */ + u32 num_txq_per_core; /* Tx queues per core */ ++ u32 num_txq_per_core_netdev; /* Tx queues per core per netdev */ + u16 tx_ring_count; /* Tx ring count */ + u16 rx_ring_count; /* Rx ring*/ + u16 rx_head_buffer_len; /* rx buffer length */ +@@ -331,7 +332,7 @@ struct edma_common_info { + u32 page_mode; /* Jumbo frame supported flag */ + u32 fraglist_mode; /* fraglist supported flag */ + struct edma_hw hw; /* edma hw specific structure */ +- struct edma_per_cpu_queues_info edma_percpu_info[CONFIG_NR_CPUS]; /* per cpu information */ ++ struct edma_per_cpu_queues_info edma_percpu_info[EDMA_CPU_CORES_SUPPORTED]; /* per cpu information */ + spinlock_t stats_lock; /* protect edma stats area for updation */ + struct timer_list edma_stats_timer; + bool is_single_phy; +@@ -401,7 +402,7 @@ struct edma_adapter { + u32 link_state; /* phy link state */ + u32 phy_mdio_addr; /* PHY device address on MII interface */ + u32 poll_required; /* check if link polling is required */ +- u32 tx_start_offset[CONFIG_NR_CPUS]; /* tx queue start */ ++ u32 tx_start_offset[EDMA_CPU_CORES_SUPPORTED]; /* tx queue start */ + u32 default_vlan_tag; /* vlan tag */ + u32 dp_bitmap; + uint8_t phy_id[MII_BUS_ID_SIZE + 3]; +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma_axi.c b/drivers/net/ethernet/qualcomm/essedma/edma_axi.c +index d53c63b..2d4770c 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma_axi.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma_axi.c +@@ -719,11 +719,7 @@ static int edma_axi_probe(struct platform_device *pdev) + int i, j, k, err = 0; + int portid_bmp; + int idx = 0, idx_mac = 0; +- +- if (CONFIG_NR_CPUS != EDMA_CPU_CORES_SUPPORTED) { +- dev_err(&pdev->dev, "Invalid CPU Cores\n"); +- return -EINVAL; +- } ++ int netdev_group = 2; + + if ((num_rxq != 4) && (num_rxq != 8)) { + dev_err(&pdev->dev, "Invalid RX queue, edma probe failed\n"); +@@ -747,7 +743,7 @@ static int edma_axi_probe(struct platform_device *pdev) + /* Initialize the netdev array before allocation + * to avoid double free + */ +- for (i = 0 ; i < edma_cinfo->num_gmac ; i++) ++ for (i = 0 ; i < EDMA_MAX_PORTID_SUPPORTED; i++) + edma_netdev[i] = NULL; + + for (i = 0 ; i < edma_cinfo->num_gmac ; i++) { +@@ -768,8 +764,11 @@ static int edma_axi_probe(struct platform_device *pdev) + + /* Fill ring details */ + edma_cinfo->num_tx_queues = EDMA_MAX_TRANSMIT_QUEUE; +- edma_cinfo->num_txq_per_core = (EDMA_MAX_TRANSMIT_QUEUE / 4); ++ edma_cinfo->num_txq_per_core = (EDMA_MAX_TRANSMIT_QUEUE / num_online_cpus()); ++ edma_cinfo->num_txq_per_core_netdev = (EDMA_MAX_TRANSMIT_QUEUE / netdev_group / num_online_cpus()); + edma_cinfo->tx_ring_count = EDMA_TX_RING_SIZE; ++ if (edma_cinfo->num_txq_per_core == 0) ++ edma_cinfo->num_txq_per_core = 1; + + /* Update num rx queues based on module parameter */ + edma_cinfo->num_rx_queues = num_rxq; +@@ -939,6 +938,13 @@ static int edma_axi_probe(struct platform_device *pdev) + idx_mac++; + } + ++ if (edma_cinfo->num_gmac == 1) { ++ netdev_group = 1; ++ edma_cinfo->num_txq_per_core_netdev = (EDMA_MAX_TRANSMIT_QUEUE / netdev_group / num_online_cpus()); ++ } ++ if (edma_cinfo->num_txq_per_core_netdev == 0) ++ edma_cinfo->num_txq_per_core_netdev = 1; ++ + /* Populate the adapter structure register the netdevice */ + for (i = 0; i < edma_cinfo->num_gmac; i++) { + int k, m; +@@ -946,17 +952,16 @@ static int edma_axi_probe(struct platform_device *pdev) + adapter[i] = netdev_priv(edma_netdev[i]); + adapter[i]->netdev = edma_netdev[i]; + adapter[i]->pdev = pdev; +- for (j = 0; j < CONFIG_NR_CPUS; j++) { +- m = i % 2; +- adapter[i]->tx_start_offset[j] = +- ((j << EDMA_TX_CPU_START_SHIFT) + (m << 1)); ++ for (j = 0; j < num_online_cpus() && j < EDMA_CPU_CORES_SUPPORTED; j++) { ++ m = i % netdev_group; ++ adapter[i]->tx_start_offset[j] = j * edma_cinfo->num_txq_per_core + m * edma_cinfo->num_txq_per_core_netdev; + /* Share the queues with available net-devices. + * For instance , with 5 net-devices + * eth0/eth2/eth4 will share q0,q1,q4,q5,q8,q9,q12,q13 + * and eth1/eth3 will get the remaining. + */ + for (k = adapter[i]->tx_start_offset[j]; k < +- (adapter[i]->tx_start_offset[j] + 2); k++) { ++ (adapter[i]->tx_start_offset[j] + edma_cinfo->num_txq_per_core_netdev); k++) { + if (edma_fill_netdev(edma_cinfo, k, i, j)) { + pr_err("Netdev overflow Error\n"); + goto err_register; +@@ -1109,9 +1114,12 @@ static int edma_axi_probe(struct platform_device *pdev) + /* populate per_core_info, do a napi_Add, request 16 TX irqs, + * 8 RX irqs, do a napi enable + */ +- for (i = 0; i < CONFIG_NR_CPUS; i++) { ++ for (i = 0; i < num_online_cpus() && i < EDMA_CPU_CORES_SUPPORTED; i++) { + u8 rx_start; + ++ tx_mask[i] = (0xFFFF >> (16 - edma_cinfo->num_txq_per_core)) << (i * edma_cinfo->num_txq_per_core); ++ tx_start[i] = i * edma_cinfo->num_txq_per_core; ++ + edma_cinfo->edma_percpu_info[i].napi.state = 0; + + netif_napi_add(edma_netdev[0], +@@ -1131,7 +1139,7 @@ static int edma_axi_probe(struct platform_device *pdev) + + /* Request irq per core */ + for (j = edma_cinfo->edma_percpu_info[i].tx_start; +- j < tx_start[i] + 4; j++) { ++ j < tx_start[i] + edma_cinfo->num_txq_per_core; j++) { + sprintf(&edma_tx_irq[j][0], "edma_eth_tx%d", j); + err = request_irq(edma_cinfo->tx_irq[j], + edma_interrupt, +@@ -1253,7 +1261,7 @@ static int edma_axi_probe(struct platform_device *pdev) + #endif + err_rmap_add_fail: + edma_free_irqs(adapter[0]); +- for (i = 0; i < CONFIG_NR_CPUS; i++) ++ for (i = 0; i < num_online_cpus() && i < EDMA_CPU_CORES_SUPPORTED; i++) + napi_disable(&edma_cinfo->edma_percpu_info[i].napi); + err_reset: + err_unregister_sysctl_tbl: +@@ -1301,7 +1309,7 @@ static int edma_axi_remove(struct platform_device *pdev) + unregister_netdev(edma_netdev[i]); + + edma_stop_rx_tx(hw); +- for (i = 0; i < CONFIG_NR_CPUS; i++) ++ for (i = 0; i < num_online_cpus() && i < EDMA_CPU_CORES_SUPPORTED; i++) + napi_disable(&edma_cinfo->edma_percpu_info[i].napi); + + edma_irq_disable(edma_cinfo); diff --git a/target/linux/ipq40xx/patches-4.14/716-essedma-reduce-write-reg.patch b/target/linux/ipq40xx/patches-4.14/716-essedma-reduce-write-reg.patch new file mode 100644 index 0000000000..58b87467d6 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/716-essedma-reduce-write-reg.patch @@ -0,0 +1,54 @@ +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma.c b/drivers/net/ethernet/qualcomm/essedma/edma.c +index fc274c8..e9d12a4 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.c +@@ -2075,15 +2075,13 @@ int edma_poll(struct napi_struct *napi, int budget) + int i, work_done = 0; + u16 rx_pending_fill; + +- /* Store the Rx/Tx status by ANDing it with +- * appropriate CPU RX?TX mask ++ /* Store the Tx status by ANDing it with ++ * appropriate CPU TX mask + */ +- edma_read_reg(EDMA_REG_RX_ISR, ®_data); +- edma_percpu_info->rx_status |= reg_data & edma_percpu_info->rx_mask; +- shadow_rx_status = edma_percpu_info->rx_status; + edma_read_reg(EDMA_REG_TX_ISR, ®_data); + edma_percpu_info->tx_status |= reg_data & edma_percpu_info->tx_mask; + shadow_tx_status = edma_percpu_info->tx_status; ++ edma_write_reg(EDMA_REG_TX_ISR, shadow_tx_status); + + /* Every core will have a start, which will be computed + * in probe and stored in edma_percpu_info->tx_start variable. +@@ -2098,6 +2096,14 @@ int edma_poll(struct napi_struct *napi, int budget) + edma_percpu_info->tx_status &= ~(1 << queue_id); + } + ++ /* Store the Rx status by ANDing it with ++ * appropriate CPU RX mask ++ */ ++ edma_read_reg(EDMA_REG_RX_ISR, ®_data); ++ edma_percpu_info->rx_status |= reg_data & edma_percpu_info->rx_mask; ++ shadow_rx_status = edma_percpu_info->rx_status; ++ edma_write_reg(EDMA_REG_RX_ISR, shadow_rx_status); ++ + /* Every core will have a start, which will be computed + * in probe and stored in edma_percpu_info->tx_start variable. + * We will shift the status bit by tx_start to obtain +@@ -2122,15 +2128,6 @@ int edma_poll(struct napi_struct *napi, int budget) + } + } + +- /* Clear the status register, to avoid the interrupts to +- * reoccur.This clearing of interrupt status register is +- * done here as writing to status register only takes place +- * once the producer/consumer index has been updated to +- * reflect that the packet transmission/reception went fine. +- */ +- edma_write_reg(EDMA_REG_RX_ISR, shadow_rx_status); +- edma_write_reg(EDMA_REG_TX_ISR, shadow_tx_status); +- + /* If budget not fully consumed, exit the polling mode */ + if (likely(work_done < budget)) { + napi_complete(napi); diff --git a/target/linux/ipq40xx/patches-4.14/851-add-ipq40xx-vqmmc-regulator-driver-to-KConfig.patch b/target/linux/ipq40xx/patches-4.14/851-add-ipq40xx-vqmmc-regulator-driver-to-KConfig.patch new file mode 100644 index 0000000000..3b8ac4944a --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/851-add-ipq40xx-vqmmc-regulator-driver-to-KConfig.patch @@ -0,0 +1,26 @@ +--- a/drivers/regulator/Kconfig ++++ b/drivers/regulator/Kconfig +@@ -334,6 +334,13 @@ config REGULATOR_HI655X + This driver provides support for the voltage regulators of the + Hisilicon Hi655x PMIC device. + ++config REGULATOR_VQMMC_IPQ4019 ++ tristate "IPQ4019 VQMMC SD LDO regulator support" ++ depends on ARCH_QCOM ++ help ++ This driver provides support for the VQMMC LDO I/0 ++ voltage regulator of the IPQ4019 SD/EMMC controller. ++ + config REGULATOR_ISL9305 + tristate "Intersil ISL9305 regulator" + depends on I2C +--- a/drivers/regulator/Makefile ++++ b/drivers/regulator/Makefile +@@ -43,6 +43,7 @@ obj-$(CONFIG_REGULATOR_GPIO) += gpio-reg + obj-$(CONFIG_REGULATOR_HI6421) += hi6421-regulator.o + obj-$(CONFIG_REGULATOR_HI6421V530) += hi6421v530-regulator.o + obj-$(CONFIG_REGULATOR_HI655X) += hi655x-regulator.o ++obj-$(CONFIG_REGULATOR_VQMMC_IPQ4019) += ipq4019-vqmmc-regulator.o + obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o + obj-$(CONFIG_REGULATOR_ISL9305) += isl9305.o + obj-$(CONFIG_REGULATOR_LM363X) += lm363x-regulator.o diff --git a/target/linux/ipq40xx/patches-4.14/852-ARM-dts-qcom-ipq4019-Add-SDHCI-VQMMC-LDO-regulator-n.patch b/target/linux/ipq40xx/patches-4.14/852-ARM-dts-qcom-ipq4019-Add-SDHCI-VQMMC-LDO-regulator-n.patch new file mode 100644 index 0000000000..fdb3f8bbcc --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/852-ARM-dts-qcom-ipq4019-Add-SDHCI-VQMMC-LDO-regulator-n.patch @@ -0,0 +1,33 @@ +From beae4078c07d3cdc90473a2b35eb0d2b4f3c922c Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 14 Sep 2019 23:13:17 +0200 +Subject: [PATCH] ARM: dts: qcom: ipq4019: Add SDHCI VQMMC LDO regulator node + +IPQ4019 has a built in SD/eMMC controller which depends on +VQMMC LDO regulator working. +Since we have a driver for it lets add the appropriate node for it. + +Signed-off-by: Robert Marko +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -216,6 +216,16 @@ + interrupts = ; + }; + ++ vqmmc: regulator@1948000 { ++ compatible = "qcom,ipq4019-vqmmc-regulator"; ++ reg = <0x01948000 0x4>; ++ regulator-name = "vqmmc"; ++ regulator-min-microvolt = <1500000>; ++ regulator-max-microvolt = <3000000>; ++ regulator-always-on; ++ status = "disabled"; ++ }; ++ + blsp_dma: dma@7884000 { + compatible = "qcom,bam-v1.7.0"; + reg = <0x07884000 0x23000>; diff --git a/target/linux/ipq40xx/patches-4.14/853-add-sdhci-msm-node-to-dts.patch b/target/linux/ipq40xx/patches-4.14/853-add-sdhci-msm-node-to-dts.patch new file mode 100644 index 0000000000..4450d48501 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/853-add-sdhci-msm-node-to-dts.patch @@ -0,0 +1,41 @@ +Subject: [PATCH] ARM: dts: qcom: ipq4019: Add SDHCI controller node +Date: Thu, 15 Aug 2019 19:28:23 +0200 +Message-Id: <20190815172823.12028-1-robimarko@gmail.com> +X-Mailer: git-send-email 2.21.0 +MIME-Version: 1.0 +Sender: linux-arm-msm-owner@vger.kernel.org +Precedence: bulk +List-ID: +X-Mailing-List: linux-arm-msm@vger.kernel.org +X-Virus-Scanned: ClamAV using ClamSMTP + +IPQ4019 has a built in SD/eMMC controller which is supported by the +SDHCI MSM driver, by the "qcom,sdhci-msm-v4" binding. +So lets add the appropriate node for it. + +Signed-off-by: Robert Marko +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -226,6 +226,18 @@ + status = "disabled"; + }; + ++ sdhci: sdhci@7824900 { ++ compatible = "qcom,sdhci-msm-v4"; ++ reg = <0x7824900 0x11c>, <0x7824000 0x800>; ++ interrupts = , ; ++ interrupt-names = "hc_irq", "pwr_irq"; ++ bus-width = <8>; ++ clocks = <&gcc GCC_SDCC1_APPS_CLK>, <&gcc GCC_SDCC1_AHB_CLK>, ++ <&gcc GCC_DCD_XO_CLK>; ++ clock-names = "core", "iface", "xo"; ++ status = "disabled"; ++ }; ++ + blsp_dma: dma@7884000 { + compatible = "qcom,bam-v1.7.0"; + reg = <0x07884000 0x23000>; diff --git a/target/linux/ipq40xx/patches-4.14/854-add-sdhci-reset-implementation.patch b/target/linux/ipq40xx/patches-4.14/854-add-sdhci-reset-implementation.patch new file mode 100644 index 0000000000..636b0cef82 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/854-add-sdhci-reset-implementation.patch @@ -0,0 +1,57 @@ +From: Georgi Djakov +Date: Mon, 28 Nov 2016 19:39:20 +0200 +Subject: [PATCH v2] mmc: sdhci-msm: Add sdhci_reset() implementation + +Signed-off-by: Georgi Djakov +--- + +drivers/mmc/host/sdhci-msm.c | 29 ++++++++++++++++++++++++++++- +1 file changed, 28 insertions(+), 1 deletion(-) + +diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c +index 32879b845b75..157ae07f9309 100644 +--- a/drivers/mmc/host/sdhci-msm.c ++++ b/drivers/mmc/host/sdhci-msm.c +@@ -1117,6 +1117,33 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) + __sdhci_msm_set_clock(host, clock); + } + ++void sdhci_msm_reset(struct sdhci_host *host, u8 mask) ++{ ++ unsigned long timeout = 100; ++ ++ sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET); ++ ++ if (mask & SDHCI_RESET_ALL) { ++ host->clock = 0; ++ ++ /* ++ * SDHCI_RESET_ALL triggers the PWR IRQ ++ * and we need to handle it here. ++ */ ++ sdhci_msm_voltage_switch(host); ++ } ++ ++ while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { ++ if (timeout == 0) { ++ pr_err("%s: Reset 0x%x never completed.\n", ++ mmc_hostname(host->mmc), (int)mask); ++ return; ++ } ++ timeout--; ++ mdelay(1); ++ } ++} ++ + static void sdhci_msm_write_w(struct sdhci_host *host, u16 val, int reg) + { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); +@@ -1148,7 +1174,7 @@ static const struct of_device_id sdhci_msm_dt_match[] = { + MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match); + + static const struct sdhci_ops sdhci_msm_ops = { +- .reset = sdhci_reset, ++ .reset = sdhci_msm_reset, + .set_clock = sdhci_set_clock, + .get_min_clock = sdhci_msm_get_min_clock, + .get_max_clock = sdhci_msm_get_max_clock, diff --git a/target/linux/ipq40xx/patches-4.14/901-essedma-disable-default-vlan-tagging.patch b/target/linux/ipq40xx/patches-4.14/901-essedma-disable-default-vlan-tagging.patch new file mode 100644 index 0000000000..ec339c4795 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/901-essedma-disable-default-vlan-tagging.patch @@ -0,0 +1,82 @@ +From 42b508d91b7f51b054f383e3aa42089ccab9300d Mon Sep 17 00:00:00 2001 +From: Chen Minqiang +Date: Thu, 15 Mar 2018 05:33:46 +0800 +Subject: [PATCH] essedma: disable default vlan tagging + +The essedma driver has its own unique take on VLAN management +and its configuration. In the original SDK, each VLAN is +assigned one virtual ethernet netdev. + +However, this is non-standard. So, this patch does away +with the default_vlan_tag property the driver is using +and therefore forces the user to use the kernel's vlan +feature. + +This patch also removes the "qcom,poll_required = <1>;" from +the essedma node. + +Signed-off-by: Christian Lamparter +Signed-off-by: Chen Minqiang +--- + arch/arm/boot/dts/qcom-ipq4019.dtsi | 5 ++--- + drivers/net/ethernet/qualcomm/essedma/edma.c | 14 +++++--------- + 2 files changed, 7 insertions(+), 12 deletions(-) + +diff --git a/arch/arm/boot/dts/qcom-ipq4019.dtsi b/arch/arm/boot/dts/qcom-ipq4019.dtsi +index 3c4617f..7c3af8e 100644 +--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi +@@ -598,8 +598,7 @@ + qcom,page-mode = <0>; + qcom,rx_head_buf_size = <1540>; + qcom,mdio_supported; +- qcom,poll_required = <1>; +- qcom,num_gmac = <2>; ++ qcom,num_gmac = <1>; + interrupts = <0 65 IRQ_TYPE_EDGE_RISING + 0 66 IRQ_TYPE_EDGE_RISING + 0 67 IRQ_TYPE_EDGE_RISING +@@ -637,7 +636,7 @@ + + gmac0: gmac0 { + local-mac-address = [00 00 00 00 00 00]; +- vlan_tag = <1 0x1f>; ++ vlan_tag = <1 0x3f>; + }; + + gmac1: gmac1 { +diff --git a/drivers/net/ethernet/qualcomm/essedma/edma.c b/drivers/net/ethernet/qualcomm/essedma/edma.c +index 05f9ce9..a3c0d66 100644 +--- a/drivers/net/ethernet/qualcomm/essedma/edma.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.c +@@ -750,13 +750,11 @@ static void edma_rx_complete(struct edma_common_info *edma_cinfo, + edma_receive_checksum(rd, skb); + + /* Process VLAN HW acceleration indication provided by HW */ +- if (unlikely(adapter->default_vlan_tag != rd->rrd4)) { +- vlan = rd->rrd4; +- if (likely(rd->rrd7 & EDMA_RRD_CVLAN)) +- __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan); +- else if (rd->rrd1 & EDMA_RRD_SVLAN) +- __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021AD), vlan); +- } ++ vlan = rd->rrd4; ++ if (likely(rd->rrd7 & EDMA_RRD_CVLAN)) ++ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan); ++ else if (rd->rrd1 & EDMA_RRD_SVLAN) ++ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021AD), vlan); + + /* Update rx statistics */ + adapter->stats.rx_packets++; +@@ -1434,8 +1432,6 @@ netdev_tx_t edma_xmit(struct sk_buff *skb, + if (!adapter->edma_cinfo->is_single_phy) { + if (unlikely(skb_vlan_tag_present(skb))) + flags_transmit |= EDMA_VLAN_TX_TAG_INSERT_FLAG; +- else if (adapter->default_vlan_tag) +- flags_transmit |= EDMA_VLAN_TX_TAG_INSERT_DEFAULT_FLAG; + } + + /* Check and mark checksum offload */ +-- +2.7.4 + diff --git a/target/linux/ipq40xx/patches-4.14/950-add-tx-hw802.11-encapusaltion-offloading-support.patch b/target/linux/ipq40xx/patches-4.14/950-add-tx-hw802.11-encapusaltion-offloading-support.patch new file mode 100644 index 0000000000..6ee0d779f7 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/950-add-tx-hw802.11-encapusaltion-offloading-support.patch @@ -0,0 +1,313 @@ +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index 4f76ba5d78a9..eef0d931dd4e 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -38,18 +38,21 @@ + static bool uart_print; + static bool skip_otp; + static bool rawmode; ++static bool ethernetmode; + + module_param_named(debug_mask, ath10k_debug_mask, uint, 0644); + module_param_named(cryptmode, ath10k_cryptmode_param, uint, 0644); + module_param(uart_print, bool, 0644); + module_param(skip_otp, bool, 0644); + module_param(rawmode, bool, 0644); ++module_param(ethernetmode, bool, 0644); + + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); + MODULE_PARM_DESC(cryptmode, "Crypto mode: 0-hardware, 1-software"); + MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); ++MODULE_PARM_DESC(ethernetmode, "Use ethernet frame datapath"); + + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { +@@ -2390,6 +2393,15 @@ + + /* peer stats are enabled by default */ + set_bit(ATH10K_FLAG_PEER_STATS, &ar->dev_flags); ++ ++ if (ethernetmode && rawmode) { ++ ath10k_err(ar, "ethernet and raw mode cannot co-exist\n"); ++ status = -EINVAL; ++ goto err; ++ } ++ ++ ar->ethernetmode = ethernetmode; ++ + + status = ath10k_core_probe_fw(ar); + if (status) { +diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h +index af68eb5d0776..251fb57cbe8f 100644 +--- a/drivers/net/wireless/ath/ath10k/core.h ++++ b/drivers/net/wireless/ath/ath10k/core.h +@@ -117,6 +117,7 @@ + ATH10K_SKB_F_DELIVER_CAB = BIT(2), + ATH10K_SKB_F_MGMT = BIT(3), + ATH10K_SKB_F_QOS = BIT(4), ++ ATH10K_SKB_F_HW_80211_ENCAP = BIT(6), + }; + + struct ath10k_skb_cb { +@@ -1000,6 +1001,8 @@ + u32 ampdu_reference; + + void *ce_priv; ++ ++ bool ethernetmode; + + /* must be last */ + u8 drv_priv[0] __aligned(sizeof(void *)); +diff --git a/drivers/net/wireless/ath/ath10k/htt_tx.c b/drivers/net/wireless/ath/ath10k/htt_tx.c +index a182c0944cc7..0ca0705fe69a 100644 +--- a/drivers/net/wireless/ath/ath10k/htt_tx.c ++++ b/drivers/net/wireless/ath/ath10k/htt_tx.c +@@ -812,6 +812,10 @@ static u8 ath10k_htt_tx_get_tid(struct sk_buff *skb, bool is_eth) + struct ieee80211_hdr *hdr = (void *)skb->data; + struct ath10k_skb_cb *cb = ATH10K_SKB_CB(skb); + ++ /* Firmware takes care of tid classification for ethernet format */ ++ if (cb->flags & ATH10K_SKB_F_HW_80211_ENCAP) ++ return skb->priority % IEEE80211_QOS_CTL_TID_MASK; ++ + if (!is_eth && ieee80211_is_mgmt(hdr->frame_control)) + return HTT_DATA_TX_EXT_TID_MGMT; + else if (cb->flags & ATH10K_SKB_F_QOS) +@@ -933,15 +937,17 @@ int ath10k_htt_tx(struct ath10k_htt *htt, enum ath10k_hw_txrx_mode txmode, + txbuf_paddr = htt->txbuf.paddr + + (sizeof(struct ath10k_htt_txbuf) * msdu_id); + +- if ((ieee80211_is_action(hdr->frame_control) || +- ieee80211_is_deauth(hdr->frame_control) || +- ieee80211_is_disassoc(hdr->frame_control)) && +- ieee80211_has_protected(hdr->frame_control)) { +- skb_put(msdu, IEEE80211_CCMP_MIC_LEN); +- } else if (!(skb_cb->flags & ATH10K_SKB_F_NO_HWCRYPT) && +- txmode == ATH10K_HW_TXRX_RAW && +- ieee80211_has_protected(hdr->frame_control)) { +- skb_put(msdu, IEEE80211_CCMP_MIC_LEN); ++ if (!(info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP)) { ++ if ((ieee80211_is_action(hdr->frame_control) || ++ ieee80211_is_deauth(hdr->frame_control) || ++ ieee80211_is_disassoc(hdr->frame_control)) && ++ ieee80211_has_protected(hdr->frame_control)) { ++ skb_put(msdu, IEEE80211_CCMP_MIC_LEN); ++ } else if (!(skb_cb->flags & ATH10K_SKB_F_NO_HWCRYPT) && ++ txmode == ATH10K_HW_TXRX_RAW && ++ ieee80211_has_protected(hdr->frame_control)) { ++ skb_put(msdu, IEEE80211_CCMP_MIC_LEN); ++ } + } + + skb_cb->paddr = dma_map_single(dev, msdu->data, msdu->len, +diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c +index 83cc8778ca1e..0ea0d0be74a1 100644 +--- a/drivers/net/wireless/ath/ath10k/mac.c ++++ b/drivers/net/wireless/ath/ath10k/mac.c +@@ -3363,10 +3363,14 @@ + struct sk_buff *skb) + { + const struct ieee80211_hdr *hdr = (void *)skb->data; ++ struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb); + __le16 fc = hdr->frame_control; + + if (!vif || vif->type == NL80211_IFTYPE_MONITOR) + return ATH10K_HW_TXRX_RAW; ++ ++ if (tx_info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP) ++ return ATH10K_HW_TXRX_ETHERNET; + + if (ieee80211_is_mgmt(fc)) + return ATH10K_HW_TXRX_MGMT; +@@ -3516,6 +3520,15 @@ + struct ath10k_skb_cb *cb = ATH10K_SKB_CB(skb); + + cb->flags = 0; ++ cb->vif = vif; ++ cb->txq = txq; ++ cb->airtime_est = airtime; ++ ++ if (info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP) { ++ cb->flags |= ATH10K_SKB_F_HW_80211_ENCAP; ++ return; ++ } ++ + if (!ath10k_tx_h_use_hwcrypto(vif, skb)) + cb->flags |= ATH10K_SKB_F_NO_HWCRYPT; + +@@ -3525,8 +3538,6 @@ + if (ieee80211_is_data_qos(hdr->frame_control)) + cb->flags |= ATH10K_SKB_F_QOS; + +- cb->vif = vif; +- cb->txq = txq; + } + + bool ath10k_mac_tx_frm_has_freq(struct ath10k *ar) +@@ -3631,7 +3642,10 @@ + { + struct ieee80211_hw *hw = ar->hw; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); +- int ret; ++ int ret;. ++ ++ if (info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP) ++ goto skip_encap; + + /* We should disable CCK RATE due to P2P */ + if (info->flags & IEEE80211_TX_CTL_NO_CCK_RATE) +@@ -3655,6 +3669,7 @@ + } + } + ++ skip_encap: + if (!noque_offchan && info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) { + if (!ath10k_mac_tx_frm_has_freq(ar)) { + ath10k_dbg(ar, ATH10K_DBG_MAC, "mac queued offchannel skb %pK len %d\n", +@@ -3704,6 +3719,7 @@ + int ret; + unsigned long time_left; + bool tmp_peer_created = false; ++ struct ieee80211_tx_info *info; + + /* FW requirement: We must create a peer before FW will send out + * an offchannel frame. Otherwise the frame will be stuck and +@@ -3723,8 +3739,15 @@ + ath10k_dbg(ar, ATH10K_DBG_MAC, "mac offchannel skb %pK len %d\n", + skb, skb->len); + +- hdr = (struct ieee80211_hdr *)skb->data; +- peer_addr = ieee80211_get_DA(hdr); ++ info = IEEE80211_SKB_CB(skb); ++ ++ if (info->control.flags & IEEE80211_TX_CTRL_HW_80211_ENCAP) { ++ peer_addr = skb->data; ++ } else { ++ hdr = (struct ieee80211_hdr *)skb->data; ++ peer_addr = ieee80211_get_DA(hdr); ++ } ++ + + spin_lock_bh(&ar->data_lock); + vdev_id = ar->scan.vdev_id; +@@ -4198,7 +4221,7 @@ + struct ieee80211_vif *vif = info->control.vif; + struct ieee80211_sta *sta = control->sta; + struct ieee80211_txq *txq = NULL; +- struct ieee80211_hdr *hdr = (void *)skb->data; ++ struct ieee80211_hdr *hdr; + enum ath10k_hw_txrx_mode txmode; + enum ath10k_mac_tx_path txpath; + bool is_htt; +@@ -4227,14 +4250,20 @@ + return; + } + +- ret = ath10k_htt_tx_mgmt_inc_pending(htt, is_mgmt, is_presp); +- if (ret) { +- ath10k_dbg(ar, ATH10K_DBG_MAC, "failed to increase tx mgmt pending count: %d, dropping\n", +- ret); +- ath10k_htt_tx_dec_pending(htt); +- spin_unlock_bh(&ar->htt.tx_lock); +- ieee80211_free_txskb(ar->hw, skb); +- return; ++ if (is_mgmt) { ++ hdr = (struct ieee80211_hdr *)skb->data; ++ is_presp = ieee80211_is_probe_resp(hdr->frame_control); ++ ++ ret = ath10k_htt_tx_mgmt_inc_pending(htt, is_mgmt, ++ is_presp); ++ if (ret) { ++ ath10k_dbg(ar, ATH10K_DBG_MAC, "failed to increase tx mgmt pending count: %d, dropping\n", ++ ret); ++ ath10k_htt_tx_dec_pending(htt); ++ spin_unlock_bh(&ar->htt.tx_lock); ++ ieee80211_free_txskb(ar->hw, skb); ++ return; ++ } + } + spin_unlock_bh(&ar->htt.tx_lock); + } +@@ -4929,10 +4958,12 @@ + static int ath10k_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) + { ++ struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif); + struct ath10k *ar = hw->priv; + struct ath10k_vif *arvif = (void *)vif->drv_priv; + struct ath10k_peer *peer; + enum wmi_sta_powersave_param param; ++ int hw_encap = 0; + int ret = 0; + u32 value; + int bit; +@@ -5023,6 +5054,21 @@ + WARN_ON(1); + break; + } ++ ++ switch (vif->type) { ++ case NL80211_IFTYPE_STATION: ++ case NL80211_IFTYPE_AP_VLAN: ++ if (wdev->netdev->ieee80211_ptr->use_4addr) ++ break; ++ /* fall through */ ++ case NL80211_IFTYPE_AP: ++ hw_encap = 1; ++ break; ++ default: ++ break; ++ } ++ ++ ieee80211_set_hw_80211_encap(vif, ar->ethernetmode & hw_encap); + + /* Using vdev_id as queue number will make it very easy to do per-vif + * tx queue locking. This shouldn't wrap due to interface combinations +@@ -8208,6 +8254,8 @@ + ieee80211_hw_set(ar->hw, QUEUE_CONTROL); + ieee80211_hw_set(ar->hw, SUPPORTS_TX_FRAG); + ieee80211_hw_set(ar->hw, REPORTS_LOW_ACK); ++ if (ar->ethernetmode) ++ ieee80211_hw_set(ar->hw, SUPPORTS_80211_ENCAP); + + if (!test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags)) + ieee80211_hw_set(ar->hw, SW_CRYPTO_CONTROL); +diff --git a/drivers/net/wireless/ath/ath10k/txrx.c b/drivers/net/wireless/ath/ath10k/txrx.c +index 39abf8b12903..1d8a6c2571a1 100644 +--- a/drivers/net/wireless/ath/ath10k/txrx.c ++++ b/drivers/net/wireless/ath/ath10k/txrx.c +@@ -60,6 +60,8 @@ int ath10k_txrx_tx_unref(struct ath10k_htt *htt, + struct ath10k_skb_cb *skb_cb; + struct ath10k_txq *artxq; + struct sk_buff *msdu; ++ struct ieee80211_vif *vif; ++ u8 flags; + + ath10k_dbg(ar, ATH10K_DBG_HTT, + "htt tx completion msdu_id %u status %d\n", +@@ -88,6 +90,9 @@ int ath10k_txrx_tx_unref(struct ath10k_htt *htt, + artxq->num_fw_queued--; + } + ++ flags = skb_cb->flags; ++ vif = skb_cb->vif; ++ + ath10k_htt_tx_free_msdu_id(htt, tx_done->msdu_id); + ath10k_htt_tx_dec_pending(htt); + if (htt->num_pending_tx == 0) +@@ -119,7 +124,11 @@ int ath10k_txrx_tx_unref(struct ath10k_htt *htt, + (info->flags & IEEE80211_TX_CTL_NO_ACK)) + info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED; + +- ieee80211_tx_status(htt->ar->hw, msdu); ++ if (flags & ATH10K_SKB_F_HW_80211_ENCAP) ++ ieee80211_tx_status_8023(htt->ar->hw, vif, msdu); ++ else ++ ieee80211_tx_status(htt->ar->hw, msdu); ++ + /* we do not own the msdu anymore */ + + return 0; diff --git a/target/linux/ipq40xx/patches-4.14/998-ignore-stats-debug-info.patch b/target/linux/ipq40xx/patches-4.14/998-ignore-stats-debug-info.patch new file mode 100644 index 0000000000..6d967d8d33 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/998-ignore-stats-debug-info.patch @@ -0,0 +1,19 @@ +diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c +index 10a4467..eb8fb94 100644 +--- a/drivers/base/firmware_class.c ++++ b/drivers/base/firmware_class.c +@@ -1218,10 +1218,10 @@ + if (ret) { + if (!(opt_flags & FW_OPT_NO_WARN)) + dev_warn(device, +- "Direct firmware load for %s failed with error %d\n", +- name, ret); ++ "Direct firmware load for %s \n", ++ name); + if (opt_flags & FW_OPT_USERHELPER) { +- dev_warn(device, "Falling back to user helper\n"); ++ dev_warn(device, "Load Wireless ART files\n"); + ret = fw_load_from_user_helper(fw, name, device, + opt_flags); + } + diff --git a/target/linux/ipq40xx/patches-4.14/999-fix-m25p-shutdown.patch b/target/linux/ipq40xx/patches-4.14/999-fix-m25p-shutdown.patch new file mode 100644 index 0000000000..0b825d61e0 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/999-fix-m25p-shutdown.patch @@ -0,0 +1,34 @@ +diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c +index 24e1ea3..b1ff69a 100644 +--- a/drivers/mtd/devices/m25p80.c ++++ b/drivers/mtd/devices/m25p80.c +@@ -313,6 +313,21 @@ static int m25p_remove(struct spi_device *spi) + return mtd_device_unregister(&flash->spi_nor.mtd); + } + ++static void m25p_shutdown(struct spi_device *spi) ++{ ++ struct m25p *flash = spi_get_drvdata(spi); ++ ++ if ((&flash->spi_nor)->addr_width > 3) { ++ printk(KERN_INFO "m25p80: exit 4-byte address mode\n"); ++ flash->command[0] = SPINOR_OP_EX4B; // exit 4-byte address mode: 0xe9 ++ spi_write(flash->spi, flash->command, 1); ++ flash->command[0] = 0x66; // enable reset ++ spi_write(flash->spi, flash->command, 1); ++ flash->command[0] = 0x99; // reset ++ spi_write(flash->spi, flash->command, 1); ++ } ++} ++ + /* + * Do NOT add to this array without reading the following: + * +@@ -387,6 +402,7 @@ static struct spi_driver m25p80_driver = { + .id_table = m25p_ids, + .probe = m25p_probe, + .remove = m25p_remove, ++ .shutdown = m25p_shutdown, + + /* REVISIT: many of these chips have deep power-down modes, which + * should clearly be entered on suspend() to minimize power use.