gl-infra-builder-FUjr/patches-19.x/0026-target-add-some-gl-inet-model-support.patch
2021-05-27 17:58:57 +08:00

17843 lines
482 KiB
Diff

From 4596c8b4b08588aae4b32e73256aaf471e50211a Mon Sep 17 00:00:00 2001
From: Jianhui Zhao <jianhui.zhao@gl-inet.com>
Date: Tue, 27 Apr 2021 10:37:37 +0800
Subject: [PATCH 25/26] target: add some gl-inet model support
Signed-off-by: Jianhui Zhao <jianhui.zhao@gl-inet.com>
---
package/boot/uboot-envtools/files/ipq40xx | 1 +
package/boot/uboot-envtools/files/mvebu | 4 +
.../ath79/base-files/etc/board.d/01_leds | 49 +-
.../ath79/base-files/etc/board.d/02_network | 32 +-
.../etc/hotplug.d/firmware/11-ath10k-caldata | 19 +-
.../ath79/base-files/lib/upgrade/platform.sh | 65 +-
target/linux/ath79/dts/ar9330.dtsi | 34 +-
target/linux/ath79/dts/ar9330_glinet.dtsi | 218 +
.../ath79/dts/ar9330_glinet_gl-ar150.dts | 36 +-
.../linux/ath79/dts/ar9330_glinet_gl-mifi.dts | 153 +
.../ath79/dts/ar9330_glinet_gl-usb150.dts | 123 +
.../dts/qca9531_glinet_gl-ar300m-lite.dts | 2 +-
.../dts/qca9531_glinet_gl-ar300m-nand.dts | 5 +-
.../dts/qca9531_glinet_gl-ar300m-nor.dts | 5 +
.../ath79/dts/qca9531_glinet_gl-ar300m.dtsi | 36 +-
...l-x750.dts => qca9531_glinet_gl-ar750.dts} | 42 +-
.../dts/qca9531_glinet_gl-e750-nor-nand.dts | 25 +
.../ath79/dts/qca9531_glinet_gl-e750-nor.dts | 18 +
.../ath79/dts/qca9531_glinet_gl-e750.dtsi | 134 +
.../dts/qca9531_glinet_gl-x300b-nor-nand.dts | 25 +
.../ath79/dts/qca9531_glinet_gl-x300b-nor.dts | 18 +
.../ath79/dts/qca9531_glinet_gl-x300b.dtsi | 172 +
.../dts/qca9531_glinet_gl-x750-nor-nand.dts | 25 +
.../ath79/dts/qca9531_glinet_gl-x750-nor.dts | 18 +
.../ath79/dts/qca9531_glinet_gl-x750.dtsi | 180 +
.../ath79/dts/qca9531_glinet_gl-xe300-iot.dts | 29 +
.../dts/qca9531_glinet_gl-xe300-nor-nand.dts | 25 +
.../ath79/dts/qca9531_glinet_gl-xe300-nor.dts | 18 +
.../ath79/dts/qca9531_glinet_gl-xe300.dtsi | 178 +
target/linux/ath79/dts/qca953x.dtsi | 7 +-
.../dts/qca9563_glinet_gl-ar750s-nor-nand.dts | 25 +
.../dts/qca9563_glinet_gl-ar750s-nor.dts | 18 +
...750s.dts => qca9563_glinet_gl-ar750s.dtsi} | 49 +-
.../dts/qca9563_glinet_gl-x1200-nor-nand.dts | 25 +
.../ath79/dts/qca9563_glinet_gl-x1200-nor.dts | 18 +
.../ath79/dts/qca9563_glinet_gl-x1200.dtsi | 203 +
.../net/ethernet/atheros/ag71xx/Makefile | 1 +
.../ethernet/atheros/ag71xx/ag71xx_ar7240.c | 1344 +++++
target/linux/ath79/image/Makefile | 2 +-
target/linux/ath79/image/generic.mk | 19 +-
target/linux/ath79/image/nand.mk | 214 +-
target/linux/ath79/nand/config-default | 13 +-
.../409-mtd-support-glinet-spinand.patch | 3008 +++++++++++
.../821-fix-glinet-rs485-auto-txrx.patch | 81 +
.../911-ath79-eth-support-ifname.patch | 20 +
.../931-fix-led-netdev-trigger-by-wwanx.patch | 26 +
.../932-fix-ar7240-switch-reset.patch | 16 +
.../932-fix-ar8337-switch-reset.patch | 67 +
.../ipq40xx/base-files/etc/board.d/01_leds | 1 +
.../ipq40xx/base-files/etc/board.d/02_network | 1 +
.../etc/hotplug.d/firmware/11-ath10k-caldata | 4 +
.../base-files/lib/upgrade/platform.sh | 1 +
target/linux/ipq40xx/config-4.14 | 8 +
.../arm/boot/dts/qcom-ipq4018-gl-b1300th.dts | 282 ++
.../arm/boot/dts/qcom-ipq4029-gl-s1300.dts | 428 ++
target/linux/ipq40xx/image/Makefile | 25 +
.../409-mtd-support-glinet-spinand.patch | 3008 +++++++++++
.../901-arm-boot-add-dts-files.patch | 4 +-
target/linux/mvebu/Makefile | 2 +-
.../mvebu/base-files/etc/board.d/02_network | 6 +-
.../base-files/lib/preinit/79_move_config | 2 +-
.../base-files/lib/preinit/81_linksys_syscfg | 10 +-
.../mvebu/base-files/lib/upgrade/linksys.sh | 2 +-
.../mvebu/base-files/lib/upgrade/platform.sh | 33 +-
.../mvebu/base-files/lib/upgrade/sdcard.sh | 47 +-
target/linux/mvebu/config-4.14 | 15 +-
.../dts/marvell/armada-gl-mv1000-emmc.dts | 194 +
.../boot/dts/marvell/armada-gl-mv1000.dts | 204 +
target/linux/mvebu/image/Makefile | 15 +
.../linux/mvebu/image/gen_mvebu_sdcard_img.sh | 6 +
.../mvebu/image/generic-arm64-emmc.bootscript | 12 +
target/linux/mvebu/image/gl-mv1000.mk | 29 +
...-arm64-add-keys-configure-for-mv1000.patch | 106 +
.../patches-4.14/535-arm64-del-mmc-led.patch | 19 +
...-driver-adaptas-rtl8192eu-for-mv1000.patch | 239 +
.../540-arm64-add-udc-driver.patch | 4376 +++++++++++++++++
.../541-arm64-usb-device-dts.patch | 25 +
.../542-arm64-usb-gadget-ether-fix-mac.patch | 51 +
.../patches-4.14/550_support_EC20_4g.patch | 241 +
.../ramips/base-files/etc/board.d/01_leds | 3 +
.../ramips/base-files/etc/board.d/02_network | 4 +
target/linux/ramips/base-files/lib/ramips.sh | 6 +
target/linux/ramips/dts/GL-MT1300.dts | 136 +
target/linux/ramips/dts/microuter-N300.dts | 108 +
target/linux/ramips/image/mt7621.mk | 11 +
target/linux/ramips/image/mt76x8.mk | 9 +
86 files changed, 16369 insertions(+), 149 deletions(-)
create mode 100644 target/linux/ath79/dts/ar9330_glinet.dtsi
create mode 100644 target/linux/ath79/dts/ar9330_glinet_gl-mifi.dts
create mode 100644 target/linux/ath79/dts/ar9330_glinet_gl-usb150.dts
rename target/linux/ath79/dts/{qca9531_glinet_gl-x750.dts => qca9531_glinet_gl-ar750.dts} (82%)
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-e750-nor-nand.dts
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-e750-nor.dts
create mode 100755 target/linux/ath79/dts/qca9531_glinet_gl-e750.dtsi
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-x300b-nor-nand.dts
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-x300b-nor.dts
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-x300b.dtsi
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-x750-nor-nand.dts
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-x750-nor.dts
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-x750.dtsi
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-xe300-iot.dts
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-xe300-nor-nand.dts
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-xe300-nor.dts
create mode 100644 target/linux/ath79/dts/qca9531_glinet_gl-xe300.dtsi
create mode 100644 target/linux/ath79/dts/qca9563_glinet_gl-ar750s-nor-nand.dts
create mode 100644 target/linux/ath79/dts/qca9563_glinet_gl-ar750s-nor.dts
rename target/linux/ath79/dts/{qca9563_glinet_gl-ar750s.dts => qca9563_glinet_gl-ar750s.dtsi} (77%)
create mode 100644 target/linux/ath79/dts/qca9563_glinet_gl-x1200-nor-nand.dts
create mode 100644 target/linux/ath79/dts/qca9563_glinet_gl-x1200-nor.dts
create mode 100644 target/linux/ath79/dts/qca9563_glinet_gl-x1200.dtsi
create mode 100755 target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
create mode 100644 target/linux/ath79/patches-4.14/409-mtd-support-glinet-spinand.patch
create mode 100644 target/linux/ath79/patches-4.14/821-fix-glinet-rs485-auto-txrx.patch
create mode 100644 target/linux/ath79/patches-4.14/911-ath79-eth-support-ifname.patch
create mode 100644 target/linux/ath79/patches-4.14/931-fix-led-netdev-trigger-by-wwanx.patch
create mode 100644 target/linux/ath79/patches-4.14/932-fix-ar7240-switch-reset.patch
create mode 100644 target/linux/ath79/patches-4.14/932-fix-ar8337-switch-reset.patch
create mode 100644 target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4018-gl-b1300th.dts
create mode 100755 target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4029-gl-s1300.dts
create mode 100644 target/linux/ipq40xx/patches-4.14/409-mtd-support-glinet-spinand.patch
create mode 100644 target/linux/mvebu/files-4.14/arch/arm64/boot/dts/marvell/armada-gl-mv1000-emmc.dts
create mode 100644 target/linux/mvebu/files-4.14/arch/arm64/boot/dts/marvell/armada-gl-mv1000.dts
create mode 100644 target/linux/mvebu/image/generic-arm64-emmc.bootscript
create mode 100644 target/linux/mvebu/image/gl-mv1000.mk
create mode 100644 target/linux/mvebu/patches-4.14/534-arm64-add-keys-configure-for-mv1000.patch
create mode 100644 target/linux/mvebu/patches-4.14/535-arm64-del-mmc-led.patch
create mode 100644 target/linux/mvebu/patches-4.14/536-arm64-usb2-driver-adaptas-rtl8192eu-for-mv1000.patch
create mode 100644 target/linux/mvebu/patches-4.14/540-arm64-add-udc-driver.patch
create mode 100644 target/linux/mvebu/patches-4.14/541-arm64-usb-device-dts.patch
create mode 100644 target/linux/mvebu/patches-4.14/542-arm64-usb-gadget-ether-fix-mac.patch
create mode 100644 target/linux/mvebu/patches-4.14/550_support_EC20_4g.patch
create mode 100644 target/linux/ramips/dts/GL-MT1300.dts
create mode 100644 target/linux/ramips/dts/microuter-N300.dts
diff --git a/package/boot/uboot-envtools/files/ipq40xx b/package/boot/uboot-envtools/files/ipq40xx
index 7bcad00b01..223a4e9f2c 100644
--- a/package/boot/uboot-envtools/files/ipq40xx
+++ b/package/boot/uboot-envtools/files/ipq40xx
@@ -32,6 +32,7 @@ ubootenv_mtdinfo () {
case "$board" in
alfa-network,ap120c-ac |\
+glinet,gl-b1300th |\
glinet,gl-b1300 |\
openmesh,a42 |\
openmesh,a62)
diff --git a/package/boot/uboot-envtools/files/mvebu b/package/boot/uboot-envtools/files/mvebu
index 03c8b393d6..ce43f19478 100644
--- a/package/boot/uboot-envtools/files/mvebu
+++ b/package/boot/uboot-envtools/files/mvebu
@@ -35,6 +35,10 @@ globalscale,espressobin-v7-emmc)
marvell,armada8040-mcbin)
ubootenv_add_uci_config "/dev/mtd0" "0x3f0000" "0x10000" "0x10000" "1"
;;
+glinet,gl-mv1000|\
+gl-mv1000)
+ ubootenv_add_uci_config "/dev/mtd1" "0x0" "0x8000" "0x8000" "1"
+ ;;
linksys,caiman|\
linksys,cobra|\
linksys,shelby)
diff --git a/target/linux/ath79/base-files/etc/board.d/01_leds b/target/linux/ath79/base-files/etc/board.d/01_leds
index dd0f91affa..47911f22ab 100755
--- a/target/linux/ath79/base-files/etc/board.d/01_leds
+++ b/target/linux/ath79/base-files/etc/board.d/01_leds
@@ -74,16 +74,57 @@ etactica,eg200)
ucidef_set_led_oneshot "modbus" "Modbus" "$boardname:red:modbus" "100" "33"
ucidef_set_led_default "etactica" "etactica" "$boardname:red:etactica" "ignore"
;;
+glinet,gl-ar150)
+ ucidef_set_led_wlan "wlan" "WLAN" "gl-ar150:orange:wlan" "phy0tpt"
+ ;;
+glinet,gl-usb150)
+ ucidef_set_led_wlan "wlan" "WLAN" "gl-usb150:green:wlan" "phy0tpt"
+ ucidef_set_led_default "power" "POWER" "gl-usb150:green:power" "1"
+ ;;
+glinet,gl-mifi)
+ ucidef_set_led_wlan "wlan" "WLAN" "gl-mifi:green:wlan" "phy0tpt"
+ ucidef_set_led_netdev "wan" "WAN" "gl-mifi:green:wan" "eth0"
+ ucidef_set_led_switch "lan" "LAN" "gl-mifi:green:lan" "switch0" "0x2"
+ ucidef_set_led_netdev "3gnet" "3GNET" "gl-mifi:green:net" "3g-wan"
+ ;;
glinet,gl-ar300m-nand|\
glinet,gl-ar300m-nor)
- ucidef_set_led_netdev "lan" "LAN" "gl-ar300m:green:lan" "eth0"
+ ucidef_set_led_netdev "lan" "LAN" "gl-ar300m:green:lan" "eth1"
;;
glinet,gl-ar300m-lite)
ucidef_set_led_netdev "lan" "LAN" "gl-ar300m-lite:green:lan" "eth0"
;;
-glinet,gl-x750)
- ucidef_set_led_netdev "wan" "WAN" "$boardname:green:wan" "eth1"
- ;;
+glinet,gl-x300b|\
+glinet,gl-x300b-nor|\
+glinet,gl-x300b-nor-nand)
+ ucidef_set_led_netdev "wlan2g" "WLAN2G" "gl-x300b:green:wlan2g" "wlan0"
+ ucidef_set_led_netdev "wan" "WAN" "gl-x300b:green:wan" "eth0"
+ ucidef_set_led_netdev "3gnet" "3GNET" "gl-x300b:green:lte" "3g-wan"
+ ;;
+glinet,gl-x750|\
+glinet,gl-x750-nor|\
+glinet,gl-x750-nor-nand)
+ ucidef_set_led_netdev "wan" "WAN" "gl-x750:green:wan" "eth0"
+ ucidef_set_led_netdev "3gnet" "LTE" "gl-x750:green:lte" "wwan0"
+ ;;
+glinet,gl-x1200|\
+glinet,gl-x1200-nor|\
+glinet,gl-x1200-nor-nand)
+ ucidef_set_led_wlan "wlan2g" "WLAN2G" "gl-x1200:green:wlan2g" "phy1tpt"
+ ucidef_set_led_wlan "wlan5g" "WLAN5G" "gl-x1200:green:wlan5g" "phy0tpt"
+ ;;
+glinet,gl-xe300|\
+glinet,gl-xe300-nor|\
+glinet,gl-xe300-iot|\
+glinet,gl-xe300-nor-nand)
+ ucidef_set_led_switch "lan" "LAN" "gl-xe300:green:lan" "switch0" "0x10"
+ ucidef_set_led_netdev "wan" "WAN" "gl-xe300:green:wan" "eth1"
+ ucidef_set_led_netdev "3gnet" "LTE" "gl-xe300:green:lte" "wwan0"
+ ;;
+glinet,gl-ar750)
+ ucidef_set_led_wlan "wlan2g" "WLAN2G" "gl-ar750:white:wlan2g" "phy1tpt"
+ ucidef_set_led_wlan "wlan5g" "WLAN5G" "gl-ar750:white:wlan5g" "phy0tpt"
+ ;;
netgear,wnr612-v2|\
on,n150r)
ucidef_set_led_netdev "wan" "WAN" "netgear:green:wan" "eth0"
diff --git a/target/linux/ath79/base-files/etc/board.d/02_network b/target/linux/ath79/base-files/etc/board.d/02_network
index 5dda551caa..378e51b66c 100755
--- a/target/linux/ath79/base-files/etc/board.d/02_network
+++ b/target/linux/ath79/base-files/etc/board.d/02_network
@@ -16,7 +16,11 @@ ath79_setup_interfaces()
devolo,dvl1750i|\
devolo,dvl1750x|\
engenius,ecb1750|\
+ glinet,gl-usb150|\
glinet,gl-ar300m-lite|\
+ glinet,gl-e750|\
+ glinet,gl-e750-nor|\
+ glinet,gl-e750-nor-nand|\
netgear,ex6400|\
netgear,ex7300|\
ocedo,koala|\
@@ -113,6 +117,11 @@ ath79_setup_interfaces()
ucidef_add_switch "switch0" \
"0@eth0" "1:lan:1" "3:lan:4" "4:lan:3" "5:lan:2" "2:wan"
;;
+ glinet,gl-ar150|\
+ glinet,gl-mifi|\
+ glinet,gl-x300b|\
+ glinet,gl-x300b-nor|\
+ glinet,gl-x300b-nor-nand|\
comfast,cf-e110n-v2|\
comfast,cf-e120a-v3|\
tplink,cpe220-v3|\
@@ -120,6 +129,12 @@ ath79_setup_interfaces()
ubnt,routerstation)
ucidef_set_interfaces_lan_wan "eth1" "eth0"
;;
+ glinet,gl-x1200|\
+ glinet,gl-x1200-nor|\
+ glinet,gl-x1200-nor-nand)
+ ucidef_add_switch "switch0" \
+ "0@eth0" "1:lan" "2:lan" "3:lan" "4:lan" "5:wan"
+ ;;
devolo,dvl1200e|\
devolo,dvl1750e|\
ocedo,ursus)
@@ -155,10 +170,23 @@ ath79_setup_interfaces()
etactica,eg200)
ucidef_set_interface_lan "eth0" "dhcp"
;;
- glinet,gl-ar750s)
+ glinet,gl-ar750s|\
+ glinet,gl-ar750s-nor|\
+ glinet,gl-ar750s-nor-nand)
ucidef_add_switch "switch0" \
"0@eth0" "2:lan:2" "3:lan:1" "1:wan"
;;
+ glinet,gl-xe300|\
+ glinet,gl-xe300-nor|\
+ glinet,gl-xe300-iot|\
+ glinet,gl-xe300-nor-nand)
+ ucidef_set_interfaces_lan_wan "eth0" "eth1"
+ ;;
+ glinet,gl-ar750)
+ ucidef_set_interfaces_lan_wan "eth1.1" "eth0"
+ ucidef_add_switch "switch0" \
+ "0@eth1" "1:lan" "2:lan"
+ ;;
iodata,etg3-r|\
iodata,wn-ac1167dgr|\
iodata,wn-ac1600dgr|\
@@ -287,7 +315,7 @@ ath79_setup_interfaces()
"0@eth0" "3:lan:1" "4:lan:2"
;;
*)
- ucidef_set_interfaces_lan_wan "eth0" "eth1"
+ ucidef_set_interfaces_lan_wan "eth1" "eth0"
;;
esac
}
diff --git a/target/linux/ath79/base-files/etc/hotplug.d/firmware/11-ath10k-caldata b/target/linux/ath79/base-files/etc/hotplug.d/firmware/11-ath10k-caldata
index d93e6dcd71..fc58dc28a2 100644
--- a/target/linux/ath79/base-files/etc/hotplug.d/firmware/11-ath10k-caldata
+++ b/target/linux/ath79/base-files/etc/hotplug.d/firmware/11-ath10k-caldata
@@ -117,13 +117,18 @@ case "$FIRMWARE" in
ath10kcal_extract "art" 20480 2116
ath10kcal_patch_mac $(macaddr_add $(cat /sys/class/net/eth0/address) +1)
;;
- glinet,gl-ar750s)
+ glinet,gl-ar750|\
+ glinet,gl-ar750s|\
+ glinet,gl-ar750s-nor|\
+ glinet,gl-ar750s-nor-nand)
ath10kcal_extract "art" 20480 2116
ath10kcal_patch_mac $(macaddr_add $(mtd_get_mac_binary art 0) +1)
;;
- glinet,gl-x750)
+ glinet,gl-x750|\
+ glinet,gl-x750-nor|\
+ glinet,gl-x750-nor-nand)
ath10kcal_extract "art" 20480 2116
- ath10kcal_patch_mac $(macaddr_add $(mtd_get_mac_binary art 0) +2)
+ ath10kcal_patch_mac $(macaddr_add $(mtd_get_mac_binary art 0) +3)
;;
nec,wg800hp)
ath10kcal_extract "art" 20480 2116
@@ -192,6 +197,14 @@ case "$FIRMWARE" in
ath10kcal_extract "caldata" 20480 12064
ath10kcal_patch_mac $(mtd_get_mac_binary caldata 12)
;;
+ glinet,gl-x1200|\
+ glinet,gl-x1200-nor|\
+ glinet,gl-x1200-nor-nand)
+ ath10kcal_extract "art" 20480 12064
+ ln -sf /lib/firmware/ath10k/pre-cal-pci-0000\:00\:00.0.bin \
+ /lib/firmware/ath10k/QCA9888/hw2.0/board.bin
+ #ath10kcal_patch_mac $(macaddr_add $(cat /sys/class/net/eth0/address) +1)
+ ;;
phicomm,k2t)
ath10kcal_extract "art" 20480 12064
ath10kcal_patch_mac_crc $(k2t_get_mac "5g_mac")
diff --git a/target/linux/ath79/base-files/lib/upgrade/platform.sh b/target/linux/ath79/base-files/lib/upgrade/platform.sh
index f3e19a5694..3ff2f861bf 100644
--- a/target/linux/ath79/base-files/lib/upgrade/platform.sh
+++ b/target/linux/ath79/base-files/lib/upgrade/platform.sh
@@ -3,7 +3,19 @@
#
PART_NAME=firmware
-REQUIRE_IMAGE_METADATA=1
+REQUIRE_IMAGE_METADATA=0
+
+glinet_led_indicator()
+{
+ while true;do
+ for i in 1 0;do
+ for led in $@;do
+ echo $i > /sys/class/leds/$led/brightness;
+ sleep 0.1;
+ done
+ done
+ done
+}
redboot_fis_do_upgrade() {
local append
@@ -32,8 +44,44 @@ redboot_fis_do_upgrade() {
fi
}
+nand_check_support_device()
+{
+ local model=""
+ json_load "$(cat /tmp/sysupgrade.meta)" || return 1
+ json_select supported_devices || {
+ #glinet openwrt 18.06 device
+ model=`awk -F': ' '/machine/ {print tolower($NF)}' /proc/cpuinfo |cut -d ' ' -f2`
+ nand_do_platform_check "$model" "$1"
+ return $?
+ }
+ json_get_keys dev_keys
+ for k in $dev_keys; do
+ json_get_var dev "$k"
+ model=${dev/,/_}
+ nand_do_platform_check "$model" "$1" && return 0
+ done
+ return 1
+}
+
platform_check_image() {
- return 0
+ local board=$(board_name)
+
+ case "$board" in
+ glinet,gl-ar300m-nand|\
+ glinet,gl-ar750s-nor-nand|\
+ glinet,gl-e750-nor-nand|\
+ glinet,gl-x1200-nor-nand|\
+ glinet,gl-x300b-nor-nand|\
+ glinet,gl-x750-nor-nand|\
+ glinet,gl-xe300-iot|\
+ glinet,gl-xe300-nor-nand)
+ nand_check_support_device "$1"
+ return $?
+ ;;
+ *)
+ return 0
+ ;;
+ esac
}
platform_do_upgrade() {
@@ -47,6 +95,19 @@ platform_do_upgrade() {
ubnt,routerstation-pro)
redboot_fis_do_upgrade "$1" kernel
;;
+ glinet,gl-ar300m-nand|\
+ glinet,gl-ar750s-nor-nand|\
+ glinet,gl-e750-nor-nand|\
+ glinet,gl-x1200-nor-nand|\
+ glinet,gl-x300b-nor-nand|\
+ glinet,gl-x750-nor-nand)
+ nand_do_upgrade "$1"
+ ;;
+ glinet,gl-xe300-iot|\
+ glinet,gl-xe300-nor-nand)
+ glinet_led_indicator gl-xe300:green:wan gl-xe300:green:lan gl-xe300:green:wlan gl-xe300:green:lte &
+ nand_do_upgrade "$1"
+ ;;
*)
default_do_upgrade "$1"
;;
diff --git a/target/linux/ath79/dts/ar9330.dtsi b/target/linux/ath79/dts/ar9330.dtsi
index 6ccb30c9a1..4e2342d7c6 100644
--- a/target/linux/ath79/dts/ar9330.dtsi
+++ b/target/linux/ath79/dts/ar9330.dtsi
@@ -102,19 +102,19 @@
};
};
- usb: usb@1b000000 {
- compatible = "chipidea,usb2";
- reg = <0x1b000000 0x200>;
+ usb: usb@1b000000 {
+ compatible = "chipidea,usb2";
+ reg = <0x1b000000 0x200>;
- interrupts = <3>;
- resets = <&rst 5>;
- reset-names = "usb-host";
+ interrupts = <3>;
+ resets = <&rst 5>;
+ reset-names = "usb-host";
- phy-names = "usb-phy";
- phys = <&usb_phy>;
+ phy-names = "usb-phy";
+ phys = <&usb_phy>;
- status = "disabled";
- };
+ status = "disabled";
+ };
spi: spi@1f000000 {
compatible = "qca,ar7100-spi";
@@ -144,16 +144,16 @@
};
};
- usb_phy: usb-phy {
- compatible = "qca,ar7200-usb-phy";
+ usb_phy: usb-phy {
+ compatible = "qca,ar7200-usb-phy";
- reset-names = "usb-phy", "usb-suspend-override";
- resets = <&rst 4>, <&rst 3>;
+ reset-names = "usb-phy", "usb-suspend-override";
+ resets = <&rst 4>, <&rst 3>;
- #phy-cells = <0>;
+ #phy-cells = <0>;
- status = "disabled";
- };
+ status = "disabled";
+ };
};
&cpuintc {
diff --git a/target/linux/ath79/dts/ar9330_glinet.dtsi b/target/linux/ath79/dts/ar9330_glinet.dtsi
new file mode 100644
index 0000000000..77387bf37c
--- /dev/null
+++ b/target/linux/ath79/dts/ar9330_glinet.dtsi
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+#include <dt-bindings/clock/ath79-clk.h>
+#include "ath79.dtsi"
+
+/ {
+ compatible = "qca,ar9330";
+
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu@0 {
+ device_type = "cpu";
+ compatible = "mips,mips24Kc";
+ clocks = <&pll ATH79_CLK_CPU>;
+ reg = <0>;
+ };
+ };
+
+ chosen {
+ bootargs = "console=ttyATH0,115200";
+ };
+
+ ahb {
+ apb {
+ ddr_ctrl: memory-controller@18000000 {
+ compatible = "qca,ar7240-ddr-controller";
+ reg = <0x18000000 0x100>;
+
+ #qca,ddr-wb-channel-cells = <1>;
+ };
+
+ uart: uart@18020000 {
+ compatible = "qca,ar9330-uart";
+ reg = <0x18020000 0x14>;
+
+ interrupts = <3>;
+
+ clocks = <&pll ATH79_CLK_REF>;
+ clock-names = "uart";
+
+ status = "disabled";
+ };
+
+ gpio: gpio@18040000 {
+ compatible = "qca,ar7100-gpio";
+ reg = <0x18040000 0x34>;
+ interrupts = <2>;
+
+ ngpios = <30>;
+
+ gpio-controller;
+ #gpio-cells = <2>;
+
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+
+ pinmux: pinmux@18040028 {
+ compatible = "pinctrl-single";
+ reg = <0x18040028 0x8>;
+
+ pinctrl-single,bit-per-mux;
+ pinctrl-single,register-width = <32>;
+ pinctrl-single,function-mask = <0x1>;
+ #pinctrl-cells = <2>;
+
+ jtag_disable_pins: pinmux_jtag_disable_pins {
+ pinctrl-single,bits = <0x0 0x1 0x1>;
+ };
+
+ switch_led_disable_pins: pinmux_switch_led_disable_pins {
+ pinctrl-single,bits = <0x0 0x0 0xf8>;
+ };
+ };
+
+ pll: pll-controller@18050000 {
+ compatible = "qca,ar9330-pll";
+ reg = <0x18050000 0x100>;
+
+ #clock-cells = <1>;
+ };
+
+ wdt: wdt@18060008 {
+ compatible = "qca,ar7130-wdt";
+ reg = <0x18060008 0x8>;
+
+ interrupts = <4>;
+
+ clocks = <&pll ATH79_CLK_AHB>;
+ clock-names = "wdt";
+ };
+
+ rst: reset-controller@1806001c {
+ compatible = "qca,ar7100-reset";
+ reg = <0x1806001c 0x4>;
+
+ #reset-cells = <1>;
+ };
+ };
+
+ usb: usb@1b000000 {
+ compatible = "generic-ehci";
+ reg = <0x1b000000 0x200>;
+
+ interrupts = <3>;
+ resets = <&rst 5>;
+ reset-names = "usb-host";
+ dr_mode = "host";
+
+ has-transaction-translator;
+ caps-offset = <0x100>;
+
+ phy-names = "usb-phy";
+ phys = <&usb_phy>;
+
+ status = "disabled";
+ };
+
+ spi: spi@1f000000 {
+ compatible = "qca,ar7100-spi";
+ reg = <0x1f000000 0x10>;
+
+ clocks = <&pll ATH79_CLK_AHB>;
+ clock-names = "ahb";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ status = "disabled";
+ };
+
+ gmac: gmac@18070000 {
+ compatible = "qca,ar9330-gmac";
+ reg = <0x18070000 0x4>;
+ };
+
+ wmac: wmac@18100000 {
+ compatible = "qca,ar9330-wmac";
+ reg = <0x18100000 0x20000>;
+
+ interrupts = <2>;
+
+ status = "disabled";
+ };
+ };
+
+ usb_phy: usb-phy {
+ compatible = "qca,ar7200-usb-phy";
+
+ reset-names = "usb-phy", "usb-suspend-override";
+ resets = <&rst 4>, <&rst 3>;
+
+ #phy-cells = <0>;
+
+ status = "disabled";
+ };
+};
+
+&cpuintc {
+ qca,ddr-wb-channel-interrupts = <2>, <3>;
+ qca,ddr-wb-channels = <&ddr_ctrl 3>, <&ddr_ctrl 2>;
+};
+
+&eth0 {
+ compatible = "qca,ar9330-eth", "syscon";
+
+ pll-data = <0x00110000 0x00001099 0x00991099>;
+
+ resets = <&rst 9>;
+ reset-names = "mac";
+ phy-mode = "mii";
+ phy-handle = <&swphy4>;
+};
+
+&mdio1 {
+ status = "okay";
+ compatible = "qca,ar9330-mdio";
+
+ resets = <&rst 23>;
+ reset-names = "mdio";
+ builtin-switch;
+
+ builtin_switch: switch0@1f {
+ compatible = "qca,ar8216-builtin";
+ reg = <0x1f>;
+ resets = <&rst 8>;
+ reset-names = "switch";
+
+ mdio-bus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ swphy4: ethernet-phy@4 {
+ reg = <4>;
+ phy-mode = "mii";
+ };
+ };
+ };
+};
+
+&eth1 {
+ compatible = "qca,ar9330-eth", "syscon";
+
+ pll-data = <0x00110000 0x00001099 0x00991099>;
+ phy-mode = "gmii";
+
+ resets = <&rst 13>;
+ reset-names = "mac";
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+};
diff --git a/target/linux/ath79/dts/ar9330_glinet_gl-ar150.dts b/target/linux/ath79/dts/ar9330_glinet_gl-ar150.dts
index 30c9c77c27..2b15feeb03 100644
--- a/target/linux/ath79/dts/ar9330_glinet_gl-ar150.dts
+++ b/target/linux/ath79/dts/ar9330_glinet_gl-ar150.dts
@@ -4,7 +4,7 @@
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
-#include "ar9330.dtsi"
+#include "ar9330_glinet.dtsi"
/ {
model = "GL.iNet GL-AR150";
@@ -12,17 +12,13 @@
aliases {
serial0 = &uart;
- led-boot = &led_power;
- led-failsafe = &led_power;
- led-running = &led_power;
- led-upgrade = &led_power;
- };
+ };
leds {
compatible = "gpio-leds";
wlan {
- label = "gl-ar150:orange:wlan";
+ label = "gl-ar150:red:wlan";
gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "phy0tpt";
};
@@ -50,7 +46,7 @@
};
auto {
- label = "auto";
+ label = "BTN_8";
linux,code = <BTN_8>;
gpios = <&gpio 8 GPIO_ACTIVE_HIGH>;
};
@@ -61,6 +57,15 @@
gpios = <&gpio 11 GPIO_ACTIVE_HIGH>;
};
};
+ gpio-export {
+ compatible = "gpio-export";
+ gpio_usb_power {
+ gpio-export,name = "usb_power";
+ gpio-export,output = <1>;
+ gpios = <&gpio 6 GPIO_ACTIVE_HIGH>;
+ };
+
+ };
};
&uart {
@@ -72,9 +77,18 @@
status = "okay";
};
+&usb {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ hub_port: port@1 {
+ reg = <1>;
+ #trigger-source-cells = <0>;
+ };
+};
&usb_phy {
status = "okay";
- gpios = <&gpio 6 GPIO_ACTIVE_HIGH>;
};
&spi {
@@ -121,13 +135,15 @@
status = "okay";
mtd-mac-address = <&art 0x0>;
+
+ ifname = "eth0";
};
&eth1 {
status = "okay";
mtd-mac-address = <&art 0x0>;
-
+ ifname = "eth1";
gmac-config {
device = <&gmac>;
diff --git a/target/linux/ath79/dts/ar9330_glinet_gl-mifi.dts b/target/linux/ath79/dts/ar9330_glinet_gl-mifi.dts
new file mode 100644
index 0000000000..b68614afe6
--- /dev/null
+++ b/target/linux/ath79/dts/ar9330_glinet_gl-mifi.dts
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+#include "ar9330_glinet.dtsi"
+
+/ {
+ model = "GL.iNet GL-MIFI";
+ compatible = "glinet,gl-mifi", "qca,ar9330";
+
+ aliases {
+ serial0 = &uart;
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ 3gcontrol {
+ label = "gl-mifi:green:3gcontrol";
+ gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
+ };
+
+ wan {
+ label = "gl-mifi:green:wan";
+ gpios = <&gpio 27 GPIO_ACTIVE_HIGH>;
+ };
+
+ lan {
+ label = "gl-mifi:green:lan";
+ gpios = <&gpio 16 GPIO_ACTIVE_HIGH>;
+ };
+
+ net {
+ label = "gl-mifi:green:net";
+ gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+ };
+
+ wlan {
+ label = "gl-mifi:green:wlan";
+ gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
+ linux,default-trigger = "phy0tpt";
+ };
+
+ };
+
+ keys {
+ compatible = "gpio-keys-polled";
+ poll-interval = <100>;
+
+ reset {
+ label = "reset";
+ linux,code = <KEY_RESTART>;
+ gpios = <&gpio 11 GPIO_ACTIVE_HIGH>;
+ };
+ };
+ gpio-export {
+ compatible = "gpio-export";
+ gpio_usb_power {
+ gpio-export,name = "usb_power";
+ gpio-export,output = <0>;
+ gpios = <&gpio 6 GPIO_ACTIVE_LOW>;
+ };
+
+ };
+};
+
+&uart {
+ status = "okay";
+};
+
+
+&usb {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ hub_port: port@1 {
+ reg = <1>;
+ #trigger-source-cells = <0>;
+ };
+};
+&usb_phy {
+ status = "okay";
+};
+
+&spi {
+ num-chipselects = <1>;
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ spi-max-frequency = <104000000>;
+ reg = <0>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x000000 0x040000>;
+ read-only;
+ };
+
+ partition@1 {
+ label = "u-boot-env";
+ reg = <0x040000 0x010000>;
+ };
+
+ partition@2 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x050000 0xfa0000>;
+ };
+
+ art: partition@3 {
+ label = "art";
+ reg = <0xff0000 0x010000>;
+ read-only;
+ };
+ };
+ };
+};
+
+&eth0 {
+ status = "okay";
+
+ mtd-mac-address = <&art 0x0>;
+ ifname = "eth0";
+};
+
+&eth1 {
+ status = "okay";
+
+ mtd-mac-address = <&art 0x0>;
+ ifname = "eth1";
+
+ gmac-config {
+ device = <&gmac>;
+
+ switch-phy-addr-swap = <0>;
+ switch-phy-swap = <0>;
+ };
+};
+
+&wmac {
+ status = "okay";
+ mtd-cal-data = <&art 0x1000>;
+ mtd-mac-address = <&art 0x0>;
+};
diff --git a/target/linux/ath79/dts/ar9330_glinet_gl-usb150.dts b/target/linux/ath79/dts/ar9330_glinet_gl-usb150.dts
new file mode 100644
index 0000000000..51610fefa3
--- /dev/null
+++ b/target/linux/ath79/dts/ar9330_glinet_gl-usb150.dts
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+#include "ar9330.dtsi"
+
+/ {
+ model = "GL.iNet GL-USB150";
+ compatible = "glinet,gl-usb150", "qca,ar9330";
+
+ aliases {
+ serial0 = &uart;
+ led-boot = &led_power;
+ led-failsafe = &led_power;
+ led-running = &led_power;
+ led-upgrade = &led_power;
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ wlan {
+ label = "gl-usb150:orange:wlan";
+ gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+ linux,default-trigger = "phy0tpt";
+ };
+
+ led_power: power {
+ label = "gl-usb150:green:power";
+ gpios = <&gpio 13 GPIO_ACTIVE_HIGH>;
+ default-state = "on";
+ };
+ };
+
+ keys {
+ compatible = "gpio-keys-polled";
+ poll-interval = <100>;
+
+ reset {
+ label = "reset";
+ linux,code = <KEY_RESTART>;
+ gpios = <&gpio 11 GPIO_ACTIVE_HIGH>;
+ };
+ };
+ gpio-export {
+ compatible = "gpio-export";
+ gpio_usb_power {
+ gpio-export,name = "gpio7";
+ gpio-export,output = <0>;
+ gpios = <&gpio 7 GPIO_ACTIVE_LOW>;
+ };
+ };
+};
+
+&uart {
+ status = "okay";
+};
+
+&spi {
+ num-chipselects = <1>;
+ status = "okay";
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ spi-max-frequency = <104000000>;
+ reg = <0>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x000000 0x040000>;
+ read-only;
+ };
+
+ partition@1 {
+ label = "u-boot-env";
+ reg = <0x040000 0x010000>;
+ };
+
+ partition@2 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x050000 0xfa0000>;
+ };
+
+ art: partition@3 {
+ label = "art";
+ reg = <0xff0000 0x010000>;
+ read-only;
+ };
+ };
+ };
+};
+
+&eth0 {
+ status = "okay";
+
+ mtd-mac-address = <&art 0x0>;
+
+ gmac-config {
+ device = <&gmac>;
+
+ switch-phy-addr-swap = <0>;
+ switch-phy-swap = <0>;
+ };
+};
+
+&eth1 {
+ status = "okay";
+ compatible = "syscon", "simple-mfd";
+};
+
+&wmac {
+ status = "okay";
+ mtd-cal-data = <&art 0x1000>;
+ mtd-mac-address = <&art 0x0>;
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-lite.dts b/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-lite.dts
index b2cb5bc261..148d017540 100644
--- a/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-lite.dts
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-lite.dts
@@ -23,4 +23,4 @@
&led_wlan {
label = "gl-ar300m-lite:green:wlan";
-};
\ No newline at end of file
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-nand.dts b/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-nand.dts
index 26c30f2b72..c53f6d61f8 100644
--- a/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-nand.dts
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-nand.dts
@@ -8,10 +8,11 @@
};
&spi {
- num-cs = <1>;
+ num-cs = <2>;
+ cs-gpios = <0>, <0>;
flash@1 {
- compatible = "spinand,mt29f";
+ compatible = "spinand,glinet";
reg = <1>;
spi-max-frequency = <25000000>;
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-nor.dts b/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-nor.dts
index 22e5ae4e74..5ebc352f93 100644
--- a/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-nor.dts
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-ar300m-nor.dts
@@ -6,3 +6,8 @@
compatible = "glinet,gl-ar300m-nor", "qca,qca9531";
model = "GL.iNet GL-AR300M (NOR)";
};
+
+&firmware_nor {
+ compatible = "denx,uimage";
+ label = "firmware";
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-ar300m.dtsi b/target/linux/ath79/dts/qca9531_glinet_gl-ar300m.dtsi
index 8b127ed1d8..3471d34eac 100644
--- a/target/linux/ath79/dts/qca9531_glinet_gl-ar300m.dtsi
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-ar300m.dtsi
@@ -6,12 +6,6 @@
#include "qca953x.dtsi"
/ {
- aliases {
- led-boot = &led_status;
- led-failsafe = &led_status;
- led-running = &led_status;
- led-upgrade = &led_status;
- };
keys {
compatible = "gpio-keys-polled";
@@ -26,13 +20,13 @@
};
button1 {
- label = "button right";
+ label = "left";
linux,code = <BTN_0>;
gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
};
button3 {
- label = "button left";
+ label = "right";
linux,code = <BTN_1>;
gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
};
@@ -44,8 +38,9 @@
// Colors from non-Lite versions
led_status: status {
- label = "gl-ar300m:green:status";
+ label = "gl-ar300m:green:system";
gpios = <&gpio 12 GPIO_ACTIVE_LOW>;
+ default-state = "on";
};
led_lan: lan {
@@ -54,11 +49,21 @@
};
led_wlan: wlan {
- label = "gl-ar300m:red:wlan";
+ label = "gl-ar300m:green:wlan";
gpios = <&gpio 14 GPIO_ACTIVE_LOW>;
linux,default-trigger = "phy0tpt";
};
};
+
+ gpio-export {
+ compatible = "gpio-export";
+
+ gpio_usb_power {
+ gpio-export,name = "usb_power";
+ gpio-export,output = <1>;
+ gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
+ };
+ };
};
&pcie0 {
@@ -85,18 +90,17 @@
read-only;
};
- partition@1 {
+ partition@40000 {
label = "u-boot-env";
reg = <0x040000 0x010000>;
};
- partition@2 {
- compatible = "denx,uimage";
- label = "firmware";
+ firmware_nor: partition@50000 {
+ label = "reserved";
reg = <0x050000 0xfa0000>;
};
- art: partition@3 {
+ art: partition@ff0000 {
label = "art";
reg = <0xff0000 0x010000>;
read-only;
@@ -123,11 +127,13 @@
status = "okay";
mtd-mac-address = <&art 0x0>;
phy-handle = <&swphy4>;
+ ifname = "eth0";
};
&eth1 {
mtd-mac-address = <&art 0x0>;
mtd-mac-address-increment = <1>;
+ ifname = "eth1";
};
&wmac {
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-x750.dts b/target/linux/ath79/dts/qca9531_glinet_gl-ar750.dts
similarity index 82%
rename from target/linux/ath79/dts/qca9531_glinet_gl-x750.dts
rename to target/linux/ath79/dts/qca9531_glinet_gl-ar750.dts
index ddaf7709b7..8a7e60823a 100644
--- a/target/linux/ath79/dts/qca9531_glinet_gl-x750.dts
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-ar750.dts
@@ -7,54 +7,51 @@
#include "qca953x.dtsi"
/ {
- compatible = "glinet,gl-x750", "qca,qca9531";
- model = "GL.iNet GL-X750";
+ compatible = "glinet,gl-ar750", "qca,qca9531";
+ model = "GL.iNet GL-AR750";
keys {
compatible = "gpio-keys-polled";
-
poll-interval = <20>;
pinctrl-names = "default";
pinctrl-0 = <&jtag_disable_pins>;
- button0 {
+ reset {
label = "reset";
linux,code = <KEY_RESTART>;
gpios = <&gpio 3 GPIO_ACTIVE_LOW>;
};
+
+ mode {
+ label = "sw1";
+ linux,code = <BTN_0>;
+ linux,input-type = <EV_SW>;
+ gpios = <&gpio 0 GPIO_ACTIVE_LOW>;
+ };
};
leds {
compatible = "gpio-leds";
- power {
- label = "gl-x750:green:power";
+ power: power {
+ label = "gl-ar750:white:power";
gpios = <&gpio 12 GPIO_ACTIVE_LOW>;
- default-state = "on";
+ default-state = "keep";
};
wlan2g {
- label = "gl-x750:green:wlan2g";
- gpios = <&gpio 4 GPIO_ACTIVE_LOW>;
+ label = "gl-ar750:white:wlan2g";
+ gpios = <&gpio 14 GPIO_ACTIVE_LOW>;
linux,default-trigger = "phy1tpt";
};
wlan5g {
- label = "gl-x750:green:wlan5g";
+ label = "gl-ar750:white:wlan5g";
gpios = <&gpio 13 GPIO_ACTIVE_LOW>;
linux,default-trigger = "phy0tpt";
};
-
- wan {
- label = "gl-x750:green:wan";
- gpios = <&gpio 14 GPIO_ACTIVE_LOW>;
- };
-
- 4g {
- label = "gl-x750:green:4g";
- gpios = <&gpio 15 GPIO_ACTIVE_LOW>;
- };
};
+
};
&pcie0 {
@@ -110,7 +107,6 @@
reg = <0x050000 0x010000>;
read-only;
};
-
partition@60000 {
compatible = "denx,uimage";
label = "firmware";
@@ -118,17 +114,21 @@
};
};
};
+
+
};
&eth0 {
status = "okay";
mtd-mac-address = <&art 0x0>;
phy-handle = <&swphy4>;
+ ifname = "eth0";
};
&eth1 {
mtd-mac-address = <&art 0x0>;
mtd-mac-address-increment = <1>;
+ ifname = "eth1";
};
&wmac {
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-e750-nor-nand.dts b/target/linux/ath79/dts/qca9531_glinet_gl-e750-nor-nand.dts
new file mode 100644
index 0000000000..4af6c273f0
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-e750-nor-nand.dts
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-e750.dtsi"
+
+/ {
+ compatible = "glinet,gl-e750-nor-nand", "qca,qca9531";
+ model = "GL.iNet GL-E750 (NOR/NAND)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ label = "kernel";
+ reg = <0x060000 0x200000>;
+ };
+ parition@260000 {
+ label = "nor_reserved";
+ reg = <0x260000 0xbc0000>;
+ };
+};
+
+&nand_ubi {
+ label = "ubi";
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-e750-nor.dts b/target/linux/ath79/dts/qca9531_glinet_gl-e750-nor.dts
new file mode 100644
index 0000000000..eb0e732a65
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-e750-nor.dts
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-e750.dtsi"
+
+/ {
+ compatible = "glinet,gl-e750-nor", "qca,qca9531";
+ model = "GL.iNet GL-E750 (NOR)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x060000 0xfa0000>;
+ };
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-e750.dtsi b/target/linux/ath79/dts/qca9531_glinet_gl-e750.dtsi
new file mode 100755
index 0000000000..8aa0c05082
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-e750.dtsi
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+#include "qca953x.dtsi"
+
+/ {
+ compatible = "glinet,gl-e750", "qca,qca9531";
+ model = "GL.iNet GL-E750";
+
+ aliases {
+ label-mac-device = &eth0;
+ };
+
+ keys {
+ compatible = "gpio-keys";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&jtag_disable_pins>;
+
+ reset {
+ label = "reset";
+ linux,code = <KEY_RESTART>;
+ gpios = <&gpio 3 GPIO_ACTIVE_LOW>;
+ };
+
+ switch {
+ label = "right";
+ linux,code = <BTN_0>;
+ gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ gpio-export {
+ compatible = "gpio-export";
+
+ gpio_lte_power {
+ gpio-export,name = "lte_power";
+ gpio-export,output = <1>;
+ gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+ };
+ };
+};
+
+&pcie0 {
+ status = "okay";
+};
+
+&uart {
+ status = "okay";
+};
+
+&usb0 {
+ status = "okay";
+};
+
+&usb_phy {
+ status = "okay";
+};
+
+&spi {
+ status = "okay";
+ num-cs = <2>;
+ cs-gpios = <0>, <0>;
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <25000000>;
+
+ nor_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x0 0x40000>;
+ read-only;
+ };
+
+ partition@40000 {
+ label = "u-boot-env";
+ reg = <0x40000 0x10000>;
+ };
+
+ art: partition@50000 {
+ label = "art";
+ reg = <0x50000 0x10000>;
+ read-only;
+ };
+ };
+ };
+
+ flash@1 {
+ compatible = "spinand,glinet";
+ reg = <1>;
+ spi-max-frequency = <25000000>;
+
+ nand_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ nand_ubi: partition@0 {
+ label = "nand_ubi";
+ reg = <0x0 0x8000000>;
+ };
+ };
+ };
+};
+
+&bootargs {
+ bootargs="";
+};
+
+&eth0 {
+ status = "okay";
+
+ mtd-mac-address = <&art 0x0>;
+ phy-handle = <&swphy4>;
+};
+
+&eth1 {
+ compatible = "syscon", "simple-mfd";
+};
+
+&wmac {
+ status = "okay";
+
+ mtd-cal-data = <&art 0x1000>;
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-x300b-nor-nand.dts b/target/linux/ath79/dts/qca9531_glinet_gl-x300b-nor-nand.dts
new file mode 100644
index 0000000000..b50ed14f26
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-x300b-nor-nand.dts
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-x300b.dtsi"
+
+/ {
+ compatible = "glinet,gl-x300b-nor-nand", "qca,qca9531";
+ model = "GL.iNet GL-X300B (NOR/NAND)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ label = "kernel";
+ reg = <0x060000 0x200000>;
+ };
+ parition@260000 {
+ label = "nor_reserved";
+ reg = <0x260000 0xbc0000>;
+ };
+};
+
+&nand_ubi {
+ label = "ubi";
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-x300b-nor.dts b/target/linux/ath79/dts/qca9531_glinet_gl-x300b-nor.dts
new file mode 100644
index 0000000000..566df3c238
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-x300b-nor.dts
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-x300b.dtsi"
+
+/ {
+ compatible = "glinet,gl-x300b-nor", "qca,qca9531";
+ model = "GL.iNet GL-X300B (NOR)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x060000 0xfa0000>;
+ };
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-x300b.dtsi b/target/linux/ath79/dts/qca9531_glinet_gl-x300b.dtsi
new file mode 100644
index 0000000000..e3d27835df
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-x300b.dtsi
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+#include "qca953x.dtsi"
+
+/ {
+ compatible = "glinet,gl-x300b", "qca,qca9531";
+ model = "GL.iNet GL-X300B";
+
+ keys {
+ compatible = "gpio-keys-polled";
+
+ poll-interval = <20>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&jtag_disable_pins>;
+
+ button0 {
+ label = "reset";
+ linux,code = <KEY_RESTART>;
+ gpios = <&gpio 3 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ wlan2g {
+ label = "gl-x300b:green:wlan2g";
+ gpios = <&gpio 4 GPIO_ACTIVE_LOW>;
+ linux,default-trigger = "phy0tpt";
+ };
+
+ wan {
+ label = "gl-x300b:green:wan";
+ gpios = <&gpio 13 GPIO_ACTIVE_LOW>;
+ };
+
+ lte {
+ label = "gl-x300b:green:lte";
+ gpios = <&gpio 15 GPIO_ACTIVE_LOW>;
+ };
+ };
+ gpio-export {
+ compatible = "gpio-export";
+
+ gpio_lte_power {
+ gpio-export,name = "gpio0";
+ gpio-export,output = <0>;
+ gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+ };
+
+ gpio_rs485tx_en {
+ gpio-export,name = "gpio1";
+ gpio-export,output = <0>;
+ gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ gpio_ble_rst {
+ gpio-export,name = "gpio16";
+ gpio-export,output = <0>;
+ gpios = <&gpio 16 GPIO_ACTIVE_HIGH>;
+ };
+
+ };
+ watchdog {
+ compatible = "hw_wdt";
+ dog_en_gpio= <12>;
+ feed_dog_gpio=<2>;
+ feed_dog_interval=<100000000>;
+ };
+
+};
+
+
+&uart {
+ status = "okay";
+ rs485_pin=<1>;
+};
+
+&usb0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ hub_port: port@1 {
+ reg = <1>;
+ #trigger-source-cells = <0>;
+ };
+};
+
+&usb_phy {
+ status = "okay";
+};
+
+&spi {
+ status = "okay";
+ num-cs = <2>;
+ cs-gpios = <0>, <0>;
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <25000000>;
+
+ nor_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x000000 0x040000>;
+ read-only;
+ };
+
+ partition@40000 {
+ label = "u-boot-env";
+ reg = <0x040000 0x010000>;
+ };
+
+ art: partition@50000 {
+ label = "art";
+ reg = <0x050000 0x010000>;
+ read-only;
+ };
+ };
+ };
+
+ flash_nand: flash@1 {
+ compatible = "spinand,glinet";
+ reg = <1>;
+ spi-max-frequency = <25000000>;
+
+ nand_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ nand_ubi: partition@0 {
+ label = "nand_ubi";
+ reg = <0x000000 0x8000000>;
+ };
+ };
+ };
+
+};
+/*
+&bootargs {
+ bootargs="";
+};*/
+
+&eth0 {
+ status = "okay";
+ mtd-mac-address = <&art 0x0>;
+ phy-handle = <&swphy4>;
+ ifname = "eth0";
+};
+
+&eth1 {
+ mtd-mac-address = <&art 0x0>;
+ mtd-mac-address-increment = <1>;
+ ifname = "eth1";
+};
+
+&wmac {
+ status = "okay";
+ mtd-cal-data = <&art 0x1000>;
+ mtd-mac-address = <&art 0x1002>;
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-x750-nor-nand.dts b/target/linux/ath79/dts/qca9531_glinet_gl-x750-nor-nand.dts
new file mode 100644
index 0000000000..1926ff8626
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-x750-nor-nand.dts
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-x750.dtsi"
+
+/ {
+ compatible = "glinet,gl-x750-nor-nand", "qca,qca9531";
+ model = "GL.iNet GL-X750 (NOR/NAND)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ label = "kernel";
+ reg = <0x060000 0x200000>;
+ };
+ parition@260000 {
+ label = "nor_reserved";
+ reg = <0x260000 0xbc0000>;
+ };
+};
+
+&nand_ubi {
+ label = "ubi";
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-x750-nor.dts b/target/linux/ath79/dts/qca9531_glinet_gl-x750-nor.dts
new file mode 100644
index 0000000000..14be3e13a5
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-x750-nor.dts
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-x750.dtsi"
+
+/ {
+ compatible = "glinet,gl-x750-nor", "qca,qca9531";
+ model = "GL.iNet GL-X750 (NOR)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x060000 0xfa0000>;
+ };
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-x750.dtsi b/target/linux/ath79/dts/qca9531_glinet_gl-x750.dtsi
new file mode 100644
index 0000000000..1517c7ce3c
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-x750.dtsi
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+#include "qca953x.dtsi"
+
+/ {
+ compatible = "glinet,gl-x750", "qca,qca9531";
+ model = "GL.iNet GL-X750";
+
+ keys {
+ compatible = "gpio-keys-polled";
+
+ poll-interval = <20>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&jtag_disable_pins>;
+
+ button0 {
+ label = "reset";
+ linux,code = <KEY_RESTART>;
+ gpios = <&gpio 3 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ power {
+ label = "gl-x750:green:power";
+ gpios = <&gpio 12 GPIO_ACTIVE_LOW>;
+ default-state = "on";
+ };
+
+ wlan2g {
+ label = "gl-x750:green:wlan2g";
+ gpios = <&gpio 4 GPIO_ACTIVE_LOW>;
+ linux,default-trigger = "phy1tpt";
+ };
+
+ wlan5g {
+ label = "gl-x750:green:wlan5g";
+ gpios = <&gpio 13 GPIO_ACTIVE_LOW>;
+ linux,default-trigger = "phy0tpt";
+ };
+
+ wan {
+ label = "gl-x750:green:wan";
+ gpios = <&gpio 14 GPIO_ACTIVE_LOW>;
+ };
+
+ lte {
+ label = "gl-x750:green:lte";
+ gpios = <&gpio 15 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ gpio-export {
+ compatible = "gpio-export";
+
+ gpio_pci_power {
+ gpio-export,name = "pci_power";
+ gpio-export,output = <0>;
+ gpios = <&gpio 0 GPIO_ACTIVE_LOW>;
+ };
+
+ gpio_usb_power {
+ gpio-export,name = "usb_power";
+ gpio-export,output = <0>;
+ gpios = <&gpio 2 GPIO_ACTIVE_LOW>;
+ };
+
+ gpio_ble_reset {
+ //set name as gpio1 to compat 1806 gpio name
+ gpio-export,name = "gpio1";
+ gpio-export,output = <0>;
+ gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
+ };
+ };
+};
+
+&pcie0 {
+ status = "okay";
+};
+
+&uart {
+ status = "okay";
+};
+
+&usb0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ hub_port: port@1 {
+ reg = <1>;
+ #trigger-source-cells = <0>;
+ };
+};
+
+&usb_phy {
+ status = "okay";
+};
+
+&spi {
+ status = "okay";
+ num-cs = <2>;
+ cs-gpios = <0>, <0>;
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <25000000>;
+
+ nor_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x000000 0x040000>;
+ read-only;
+ };
+
+ partition@40000 {
+ label = "u-boot-env";
+ reg = <0x040000 0x010000>;
+ };
+
+ art: partition@50000 {
+ label = "art";
+ reg = <0x050000 0x010000>;
+ read-only;
+ };
+ };
+ };
+
+ flash_nand: flash@1 {
+ compatible = "spinand,glinet";
+ reg = <1>;
+ spi-max-frequency = <25000000>;
+
+ nand_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ nand_ubi: partition@0 {
+ label = "nand_ubi";
+ reg = <0x000000 0x8000000>;
+ };
+ };
+ };
+
+};
+
+&bootargs {
+ bootargs="";
+};
+
+&eth0 {
+ status = "okay";
+ mtd-mac-address = <&art 0x0>;
+ phy-handle = <&swphy4>;
+ ifname = "eth0";
+};
+
+&eth1 {
+ mtd-mac-address = <&art 0x0>;
+ mtd-mac-address-increment = <1>;
+ ifname = "eth1";
+};
+
+&wmac {
+ status = "okay";
+ mtd-cal-data = <&art 0x1000>;
+ mtd-mac-address = <&art 0x1002>;
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-xe300-iot.dts b/target/linux/ath79/dts/qca9531_glinet_gl-xe300-iot.dts
new file mode 100644
index 0000000000..3d830fb545
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-xe300-iot.dts
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-xe300.dtsi"
+
+/ {
+ compatible = "glinet,gl-xe300-iot", "qca,qca9531";
+ model = "GL.iNet GL-XE300 (NOR/NAND IOT)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ label = "kernel";
+ reg = <0x060000 0x400000>;
+ };
+ parition@260000 {
+ label = "nor_reserved";
+ reg = <0x460000 0xba0000>;
+ };
+};
+
+&nand_ubi {
+ label = "ubi";
+};
+
+&bootargs {
+ bootargs="";
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-xe300-nor-nand.dts b/target/linux/ath79/dts/qca9531_glinet_gl-xe300-nor-nand.dts
new file mode 100644
index 0000000000..4e92f309e5
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-xe300-nor-nand.dts
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-xe300.dtsi"
+
+/ {
+ compatible = "glinet,gl-xe300-nor-nand", "qca,qca9531";
+ model = "GL.iNet GL-XE300 (NOR/NAND)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ label = "kernel";
+ reg = <0x060000 0x400000>;
+ };
+ parition@260000 {
+ label = "nor_reserved";
+ reg = <0x460000 0xba0000>;
+ };
+};
+
+&nand_ubi {
+ label = "ubi";
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-xe300-nor.dts b/target/linux/ath79/dts/qca9531_glinet_gl-xe300-nor.dts
new file mode 100644
index 0000000000..9b67f49368
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-xe300-nor.dts
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9531_glinet_gl-xe300.dtsi"
+
+/ {
+ compatible = "glinet,gl-xe300-nor", "qca,qca9531";
+ model = "GL.iNet GL-XE300 (NOR)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x060000 0xfa0000>;
+ };
+};
diff --git a/target/linux/ath79/dts/qca9531_glinet_gl-xe300.dtsi b/target/linux/ath79/dts/qca9531_glinet_gl-xe300.dtsi
new file mode 100644
index 0000000000..9c8f7e0a51
--- /dev/null
+++ b/target/linux/ath79/dts/qca9531_glinet_gl-xe300.dtsi
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+#include "qca953x.dtsi"
+
+/ {
+ compatible = "glinet,gl-xe300", "qca,qca9531";
+ model = "GL.iNet GL-XE300";
+
+ keys {
+ compatible = "gpio-keys-polled";
+
+ poll-interval = <20>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&jtag_disable_pins>;
+
+ button0 {
+ label = "reset";
+ linux,code = <KEY_RESTART>;
+ gpios = <&gpio 3 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ wan {
+ label = "gl-xe300:green:wan";
+ gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
+ };
+
+ lan {
+ label = "gl-xe300:green:lan";
+ gpios = <&gpio 10 GPIO_ACTIVE_LOW>;
+ };
+
+ wlan {
+ label = "gl-xe300:green:wlan";
+ gpios = <&gpio 12 GPIO_ACTIVE_LOW>;
+ linux,default-trigger = "phy0tpt";
+ };
+
+ lte {
+ label = "gl-xe300:green:lte";
+ gpios = <&gpio 13 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ gpio-export {
+ compatible = "gpio-export";
+
+ gpio_lte_power {
+ gpio-export,name = "lte_power";
+ gpio-export,output = <1>;
+ gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+ };
+
+ gpio_sd_detect {
+ gpio-export,name = "sd_detect";
+ gpio-export,output = <0>;
+ gpios = <&gpio 17 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ i2c: i2c {
+ compatible = "i2c-gpio";
+
+ sda-gpios = <&gpio 14 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
+ scl-gpios = <&gpio 16 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ rtc@32 {
+ compatible = "rtc-sd2068";
+ reg = <0x32>;
+ };
+
+ };
+};
+
+&pcie0 {
+ status = "okay";
+};
+
+&uart {
+ status = "okay";
+};
+
+&usb0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "okay";
+
+ hub_port: port@1 {
+ reg = <1>;
+ #trigger-source-cells = <0>;
+ };
+};
+
+&usb_phy {
+ status = "okay";
+};
+
+&spi {
+ status = "okay";
+ num-cs = <2>;
+ cs-gpios = <0>, <0>;
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <25000000>;
+
+ nor_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x000000 0x040000>;
+ read-only;
+ };
+
+ partition@40000 {
+ label = "u-boot-env";
+ reg = <0x040000 0x010000>;
+ };
+
+ art: partition@50000 {
+ label = "art";
+ reg = <0x050000 0x010000>;
+ read-only;
+ };
+ };
+ };
+
+ flash_nand: flash@1 {
+ compatible = "spinand,glinet";
+ reg = <1>;
+ spi-max-frequency = <25000000>;
+
+ nand_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ nand_ubi: partition@0 {
+ label = "nand_ubi";
+ reg = <0x000000 0x8000000>;
+ };
+ };
+ };
+
+};
+
+&eth0 {
+ status = "okay";
+ mtd-mac-address = <&art 0x0>;
+ phy-handle = <&swphy4>;
+ ifname = "eth1";
+};
+
+&eth1 {
+ mtd-mac-address = <&art 0x0>;
+ mtd-mac-address-increment = <1>;
+ ifname = "eth0";
+};
+
+&wmac {
+ status = "okay";
+ mtd-cal-data = <&art 0x1000>;
+ mtd-mac-address = <&art 0x1002>;
+};
diff --git a/target/linux/ath79/dts/qca953x.dtsi b/target/linux/ath79/dts/qca953x.dtsi
index 7a7d178ff4..977cfa905a 100644
--- a/target/linux/ath79/dts/qca953x.dtsi
+++ b/target/linux/ath79/dts/qca953x.dtsi
@@ -8,7 +8,7 @@
#address-cells = <1>;
#size-cells = <1>;
- chosen {
+ bootargs: chosen {
bootargs = "console=ttyS0,115200n8";
};
@@ -245,14 +245,13 @@
builtin-switch;
builtin_switch: switch0@1f {
- compatible = "qca,ar8229";
+ compatible = "qca,ar8229-builtin";
reg = <0x1f>;
resets = <&rst 8>;
reset-names = "switch";
phy-mode = "gmii";
- qca,phy4-mii-enable;
- qca,mib-poll-interval = <500>;
+ phy4-mii-enable;
mdio-bus {
#address-cells = <1>;
diff --git a/target/linux/ath79/dts/qca9563_glinet_gl-ar750s-nor-nand.dts b/target/linux/ath79/dts/qca9563_glinet_gl-ar750s-nor-nand.dts
new file mode 100644
index 0000000000..fab54fb51b
--- /dev/null
+++ b/target/linux/ath79/dts/qca9563_glinet_gl-ar750s-nor-nand.dts
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9563_glinet_gl-ar750s.dtsi"
+
+/ {
+ compatible = "glinet,gl-ar750s-nor-nand", "qca,qca9563";
+ model = "GL.iNet GL-AR750S (NOR/NAND)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ label = "kernel";
+ reg = <0x060000 0x200000>;
+ };
+ parition@260000 {
+ label = "nor_reserved";
+ reg = <0x260000 0xbc0000>;
+ };
+};
+
+&nand_ubi {
+ label = "ubi";
+};
diff --git a/target/linux/ath79/dts/qca9563_glinet_gl-ar750s-nor.dts b/target/linux/ath79/dts/qca9563_glinet_gl-ar750s-nor.dts
new file mode 100644
index 0000000000..271cef516e
--- /dev/null
+++ b/target/linux/ath79/dts/qca9563_glinet_gl-ar750s-nor.dts
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9563_glinet_gl-ar750s.dtsi"
+
+/ {
+ compatible = "glinet,gl-ar750s-nor", "qca,qca9563";
+ model = "GL.iNet GL-AR750S (NOR)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x060000 0xfa0000>;
+ };
+};
diff --git a/target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dts b/target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dtsi
similarity index 77%
rename from target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dts
rename to target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dtsi
index 03922bcd1f..2ccfed59ab 100644
--- a/target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dts
+++ b/target/linux/ath79/dts/qca9563_glinet_gl-ar750s.dtsi
@@ -10,13 +10,6 @@
compatible = "glinet,gl-ar750s", "qca,qca9563";
model = "GL.iNet GL-AR750S";
- aliases {
- led-boot = &power;
- led-failsafe = &power;
- led-running = &power;
- led-upgrade = &power;
- };
-
keys {
compatible = "gpio-keys-polled";
poll-interval = <20>;
@@ -30,7 +23,7 @@
};
mode {
- label = "mode";
+ label = "right";
linux,code = <BTN_0>;
linux,input-type = <EV_SW>;
gpios = <&gpio 8 GPIO_ACTIVE_LOW>;
@@ -41,19 +34,19 @@
compatible = "gpio-leds";
power: power {
- label = "gl-ar750s:green:power";
+ label = "gl-ar750s:white:power";
gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
default-state = "keep";
};
wlan2g {
- label = "gl-ar750s:green:wlan2g";
+ label = "gl-ar750s:white:wlan2g";
gpios = <&gpio 19 GPIO_ACTIVE_LOW>;
linux,default-trigger = "phy1tpt";
};
wlan5g {
- label = "gl-ar750s:green:wlan5g";
+ label = "gl-ar750s:white:wlan5g";
gpios = <&gpio 20 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "phy0tpt";
};
@@ -71,18 +64,29 @@
gpio = <&gpio 7 GPIO_ACTIVE_HIGH>;
enable-active-high;
};
+
+ gpio-export {
+ compatible = "gpio-export";
+
+ gpio_usb_power {
+ gpio-export,name = "usb_power";
+ gpio-export,output = <1>;
+ gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
+ };
+ };
};
&spi {
status = "okay";
- num-cs = <0>;
+ num-cs = <2>;
+ cs-gpios = <0>, <0>;
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <25000000>;
- partitions {
+ nor_partitions: partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
@@ -103,11 +107,22 @@
reg = <0x050000 0x010000>;
read-only;
};
+ };
+ };
+
+ flash_nand: flash@1 {
+ compatible = "spinand,glinet";
+ reg = <1>;
+ spi-max-frequency = <25000000>;
+
+ nand_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
- partition@60000 {
- compatible = "denx,uimage";
- label = "firmware";
- reg = <0x060000 0xfa0000>;
+ nand_ubi: partition@0 {
+ label = "nand_ubi";
+ reg = <0x000000 0x8000000>;
};
};
};
diff --git a/target/linux/ath79/dts/qca9563_glinet_gl-x1200-nor-nand.dts b/target/linux/ath79/dts/qca9563_glinet_gl-x1200-nor-nand.dts
new file mode 100644
index 0000000000..c8a99de877
--- /dev/null
+++ b/target/linux/ath79/dts/qca9563_glinet_gl-x1200-nor-nand.dts
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9563_glinet_gl-x1200.dtsi"
+
+/ {
+ compatible = "glinet,gl-x1200-nor-nand", "qca,qca9563";
+ model = "GL.iNet GL-X1200 (NOR/NAND)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ label = "kernel";
+ reg = <0x060000 0x200000>;
+ };
+ parition@260000 {
+ label = "nor_reserved";
+ reg = <0x260000 0xbc0000>;
+ };
+};
+
+&nand_ubi {
+ label = "ubi";
+};
diff --git a/target/linux/ath79/dts/qca9563_glinet_gl-x1200-nor.dts b/target/linux/ath79/dts/qca9563_glinet_gl-x1200-nor.dts
new file mode 100644
index 0000000000..ed2572c7a7
--- /dev/null
+++ b/target/linux/ath79/dts/qca9563_glinet_gl-x1200-nor.dts
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+
+/dts-v1/;
+
+#include "qca9563_glinet_gl-x1200.dtsi"
+
+/ {
+ compatible = "glinet,gl-x1200-nor", "qca,qca9563";
+ model = "GL.iNet GL-X1200 (NOR)";
+};
+
+&nor_partitions {
+ partition@60000 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x060000 0xfa0000>;
+ };
+};
diff --git a/target/linux/ath79/dts/qca9563_glinet_gl-x1200.dtsi b/target/linux/ath79/dts/qca9563_glinet_gl-x1200.dtsi
new file mode 100644
index 0000000000..a5efbf1957
--- /dev/null
+++ b/target/linux/ath79/dts/qca9563_glinet_gl-x1200.dtsi
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+#include "qca956x.dtsi"
+
+/ {
+ compatible = "glinet,gl-x1200", "qca,qca9563";
+ model = "GL.iNet GL-X1200";
+
+ aliases {
+ led-boot = &power;
+ led-failsafe = &power;
+ led-running = &power;
+ led-upgrade = &power;
+ };
+
+ keys {
+ compatible = "gpio-keys-polled";
+ poll-interval = <20>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&jtag_disable_pins>;
+
+ reset {
+ label = "reset";
+ linux,code = <KEY_RESTART>;
+ gpios = <&gpio 2 GPIO_ACTIVE_LOW>;
+ };
+
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ power: power {
+ label = "gl-x1200:green:power";
+ gpios = <&gpio 8 GPIO_ACTIVE_LOW>;
+ default-state = "keep";
+ };
+
+ wlan2g {
+ label = "gl-x1200:green:wlan2g";
+ gpios = <&gpio 19 GPIO_ACTIVE_LOW>;
+ linux,default-trigger = "phy1tpt";
+ };
+
+ wlan5g {
+ label = "gl-x1200:green:wlan5g";
+ gpios = <&gpio 20 GPIO_ACTIVE_HIGH>;
+ linux,default-trigger = "phy0tpt";
+ };
+ };
+ gpio-export {
+ compatible = "gpio-export";
+
+ gpio_usb_power {
+ gpio-export,name = "gpio7";
+ gpio-export,output = <1>;
+ gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
+ };
+
+ gpio_lte1_power {
+ gpio-export,name = "gpio5";
+ gpio-export,output = <0>;
+ gpios = <&gpio 5 GPIO_ACTIVE_LOW>;
+ };
+
+ gpio_lte2_power {
+ gpio-export,name = "gpio15";
+ gpio-export,output = <0>;
+ gpios = <&gpio 15 GPIO_ACTIVE_LOW>;
+ };
+
+ gpio_watchdog_en {
+ gpio-export,name = "gpio16";
+ gpio-export,output = <1>;
+ gpios = <&gpio 16 GPIO_ACTIVE_HIGH>;
+ };
+
+ gpio_feed_dog {
+ gpio-export,name = "gpio14";
+ gpio-export,output = <1>;
+ gpios = <&gpio 14 GPIO_ACTIVE_HIGH>;
+ };
+ };
+
+ usb_vbus: regulator-usb-vbus {
+ compatible = "regulator-fixed";
+
+ regulator-name = "USB_VBUS";
+
+ regulator-min-microvolt = <5000000>;
+ regulator-max-microvolt = <5000000>;
+ regulator-always-on;
+
+ };
+};
+
+&spi {
+ status = "okay";
+ num-cs = <2>;
+ cs-gpios = <0>, <0>;
+
+ flash@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <25000000>;
+
+ nor_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x000000 0x040000>;
+ read-only;
+ };
+
+ partition@40000 {
+ label = "u-boot-env";
+ reg = <0x040000 0x010000>;
+ };
+
+ art: partition@50000 {
+ label = "art";
+ reg = <0x050000 0x010000>;
+ read-only;
+ };
+ };
+ };
+
+ flash_nand: flash@1 {
+ compatible = "spinand,glinet";
+ reg = <1>;
+ spi-max-frequency = <25000000>;
+
+ nand_partitions: partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ nand_ubi: partition@0 {
+ label = "nand_ubi";
+ reg = <0x000000 0x8000000>;
+ };
+ };
+ };
+};
+
+&pcie {
+ status = "okay";
+};
+
+&uart {
+ status = "okay";
+};
+
+&usb0 {
+ status = "okay";
+ vbus-supply = <&usb_vbus>;
+};
+
+&usb_phy0 {
+ status = "okay";
+};
+
+&usb1 {
+ status = "okay";
+};
+
+&usb_phy1 {
+ status = "okay";
+};
+
+&mdio0 {
+ status = "okay";
+ phy-mask = <0>;
+
+ phy0: ethernet-phy@0 {
+ reg = <0>;
+ phy-mode = "sgmii";
+ qca,ar8327-initvals = <
+ 0x04 0x00080080 /* PORT0 PAD MODE CTRL */
+ 0x7c 0x0000007e /* PORT0_STATUS */
+ >;
+ };
+};
+
+&eth0 {
+ status = "okay";
+
+ mtd-mac-address = <&art 0x0>;
+ phy-handle = <&phy0>;
+};
+
+&wmac {
+ status = "okay";
+ mtd-cal-data = <&art 0x1000>;
+ mtd-mac-address = <&art 0x1002>;
+};
diff --git a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/Makefile b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/Makefile
index 87add0d208..74da2aaec4 100644
--- a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/Makefile
+++ b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/Makefile
@@ -9,5 +9,6 @@ ag71xx-y += ag71xx_phy.o
ag71xx-$(CONFIG_AG71XX_DEBUG_FS) += ag71xx_debugfs.o
+obj-$(CONFIG_AG71XX) += ag71xx_ar7240.o
obj-$(CONFIG_AG71XX) += ag71xx_mdio.o
obj-$(CONFIG_AG71XX) += ag71xx.o
diff --git a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
new file mode 100755
index 0000000000..77f7670c2b
--- /dev/null
+++ b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
@@ -0,0 +1,1344 @@
+/*
+ * Driver for the built-in ethernet switch of the Atheros AR7240 SoC
+ * Copyright (c) 2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2010 Felix Fietkau <nbd@nbd.name>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/mii.h>
+#include <linux/bitops.h>
+#include <linux/switch.h>
+#include "ag71xx.h"
+
+#define BITM(_count) (BIT(_count) - 1)
+#define BITS(_shift, _count) (BITM(_count) << _shift)
+
+#define AR7240_REG_MASK_CTRL 0x00
+#define AR7240_MASK_CTRL_REVISION_M BITM(8)
+#define AR7240_MASK_CTRL_VERSION_M BITM(8)
+#define AR7240_MASK_CTRL_VERSION_S 8
+#define AR7240_MASK_CTRL_VERSION_AR7240 0x01
+#define AR7240_MASK_CTRL_VERSION_AR934X 0x02
+#define AR7240_MASK_CTRL_SOFT_RESET BIT(31)
+
+#define AR7240_REG_MAC_ADDR0 0x20
+#define AR7240_REG_MAC_ADDR1 0x24
+
+#define AR7240_REG_FLOOD_MASK 0x2c
+#define AR7240_FLOOD_MASK_BROAD_TO_CPU BIT(26)
+
+#define AR7240_REG_GLOBAL_CTRL 0x30
+#define AR7240_GLOBAL_CTRL_MTU_M BITM(11)
+#define AR9340_GLOBAL_CTRL_MTU_M BITM(14)
+
+#define AR7240_REG_VTU 0x0040
+#define AR7240_VTU_OP BITM(3)
+#define AR7240_VTU_OP_NOOP 0x0
+#define AR7240_VTU_OP_FLUSH 0x1
+#define AR7240_VTU_OP_LOAD 0x2
+#define AR7240_VTU_OP_PURGE 0x3
+#define AR7240_VTU_OP_REMOVE_PORT 0x4
+#define AR7240_VTU_ACTIVE BIT(3)
+#define AR7240_VTU_FULL BIT(4)
+#define AR7240_VTU_PORT BITS(8, 4)
+#define AR7240_VTU_PORT_S 8
+#define AR7240_VTU_VID BITS(16, 12)
+#define AR7240_VTU_VID_S 16
+#define AR7240_VTU_PRIO BITS(28, 3)
+#define AR7240_VTU_PRIO_S 28
+#define AR7240_VTU_PRIO_EN BIT(31)
+
+#define AR7240_REG_VTU_DATA 0x0044
+#define AR7240_VTUDATA_MEMBER BITS(0, 10)
+#define AR7240_VTUDATA_VALID BIT(11)
+
+#define AR7240_REG_ATU 0x50
+#define AR7240_ATU_FLUSH_ALL 0x1
+
+#define AR7240_REG_AT_CTRL 0x5c
+#define AR7240_AT_CTRL_AGE_TIME BITS(0, 15)
+#define AR7240_AT_CTRL_AGE_EN BIT(17)
+#define AR7240_AT_CTRL_LEARN_CHANGE BIT(18)
+#define AR7240_AT_CTRL_RESERVED BIT(19)
+#define AR7240_AT_CTRL_ARP_EN BIT(20)
+
+#define AR7240_REG_TAG_PRIORITY 0x70
+
+#define AR7240_REG_SERVICE_TAG 0x74
+#define AR7240_SERVICE_TAG_M BITM(16)
+
+#define AR7240_REG_CPU_PORT 0x78
+#define AR7240_MIRROR_PORT_S 4
+#define AR7240_MIRROR_PORT_M BITM(4)
+#define AR7240_CPU_PORT_EN BIT(8)
+
+#define AR7240_REG_MIB_FUNCTION0 0x80
+#define AR7240_MIB_TIMER_M BITM(16)
+#define AR7240_MIB_AT_HALF_EN BIT(16)
+#define AR7240_MIB_BUSY BIT(17)
+#define AR7240_MIB_FUNC_S 24
+#define AR7240_MIB_FUNC_M BITM(3)
+#define AR7240_MIB_FUNC_NO_OP 0x0
+#define AR7240_MIB_FUNC_FLUSH 0x1
+#define AR7240_MIB_FUNC_CAPTURE 0x3
+
+#define AR7240_REG_MDIO_CTRL 0x98
+#define AR7240_MDIO_CTRL_DATA_M BITM(16)
+#define AR7240_MDIO_CTRL_REG_ADDR_S 16
+#define AR7240_MDIO_CTRL_PHY_ADDR_S 21
+#define AR7240_MDIO_CTRL_CMD_WRITE 0
+#define AR7240_MDIO_CTRL_CMD_READ BIT(27)
+#define AR7240_MDIO_CTRL_MASTER_EN BIT(30)
+#define AR7240_MDIO_CTRL_BUSY BIT(31)
+
+#define AR7240_REG_PORT_BASE(_port) (0x100 + (_port) * 0x100)
+
+#define AR7240_REG_PORT_STATUS(_port) (AR7240_REG_PORT_BASE((_port)) + 0x00)
+#define AR7240_PORT_STATUS_SPEED_S 0
+#define AR7240_PORT_STATUS_SPEED_M BITM(2)
+#define AR7240_PORT_STATUS_SPEED_10 0
+#define AR7240_PORT_STATUS_SPEED_100 1
+#define AR7240_PORT_STATUS_SPEED_1000 2
+#define AR7240_PORT_STATUS_TXMAC BIT(2)
+#define AR7240_PORT_STATUS_RXMAC BIT(3)
+#define AR7240_PORT_STATUS_TXFLOW BIT(4)
+#define AR7240_PORT_STATUS_RXFLOW BIT(5)
+#define AR7240_PORT_STATUS_DUPLEX BIT(6)
+#define AR7240_PORT_STATUS_LINK_UP BIT(8)
+#define AR7240_PORT_STATUS_LINK_AUTO BIT(9)
+#define AR7240_PORT_STATUS_LINK_PAUSE BIT(10)
+
+#define AR7240_REG_PORT_CTRL(_port) (AR7240_REG_PORT_BASE((_port)) + 0x04)
+#define AR7240_PORT_CTRL_STATE_M BITM(3)
+#define AR7240_PORT_CTRL_STATE_DISABLED 0
+#define AR7240_PORT_CTRL_STATE_BLOCK 1
+#define AR7240_PORT_CTRL_STATE_LISTEN 2
+#define AR7240_PORT_CTRL_STATE_LEARN 3
+#define AR7240_PORT_CTRL_STATE_FORWARD 4
+#define AR7240_PORT_CTRL_LEARN_LOCK BIT(7)
+#define AR7240_PORT_CTRL_VLAN_MODE_S 8
+#define AR7240_PORT_CTRL_VLAN_MODE_KEEP 0
+#define AR7240_PORT_CTRL_VLAN_MODE_STRIP 1
+#define AR7240_PORT_CTRL_VLAN_MODE_ADD 2
+#define AR7240_PORT_CTRL_VLAN_MODE_DOUBLE_TAG 3
+#define AR7240_PORT_CTRL_IGMP_SNOOP BIT(10)
+#define AR7240_PORT_CTRL_HEADER BIT(11)
+#define AR7240_PORT_CTRL_MAC_LOOP BIT(12)
+#define AR7240_PORT_CTRL_SINGLE_VLAN BIT(13)
+#define AR7240_PORT_CTRL_LEARN BIT(14)
+#define AR7240_PORT_CTRL_DOUBLE_TAG BIT(15)
+#define AR7240_PORT_CTRL_MIRROR_TX BIT(16)
+#define AR7240_PORT_CTRL_MIRROR_RX BIT(17)
+
+#define AR7240_REG_PORT_VLAN(_port) (AR7240_REG_PORT_BASE((_port)) + 0x08)
+
+#define AR7240_PORT_VLAN_DEFAULT_ID_S 0
+#define AR7240_PORT_VLAN_DEST_PORTS_S 16
+#define AR7240_PORT_VLAN_MODE_S 30
+#define AR7240_PORT_VLAN_MODE_PORT_ONLY 0
+#define AR7240_PORT_VLAN_MODE_PORT_FALLBACK 1
+#define AR7240_PORT_VLAN_MODE_VLAN_ONLY 2
+#define AR7240_PORT_VLAN_MODE_SECURE 3
+
+
+#define AR7240_REG_STATS_BASE(_port) (0x20000 + (_port) * 0x100)
+
+#define AR7240_STATS_RXBROAD 0x00
+#define AR7240_STATS_RXPAUSE 0x04
+#define AR7240_STATS_RXMULTI 0x08
+#define AR7240_STATS_RXFCSERR 0x0c
+#define AR7240_STATS_RXALIGNERR 0x10
+#define AR7240_STATS_RXRUNT 0x14
+#define AR7240_STATS_RXFRAGMENT 0x18
+#define AR7240_STATS_RX64BYTE 0x1c
+#define AR7240_STATS_RX128BYTE 0x20
+#define AR7240_STATS_RX256BYTE 0x24
+#define AR7240_STATS_RX512BYTE 0x28
+#define AR7240_STATS_RX1024BYTE 0x2c
+#define AR7240_STATS_RX1518BYTE 0x30
+#define AR7240_STATS_RXMAXBYTE 0x34
+#define AR7240_STATS_RXTOOLONG 0x38
+#define AR7240_STATS_RXGOODBYTE 0x3c
+#define AR7240_STATS_RXBADBYTE 0x44
+#define AR7240_STATS_RXOVERFLOW 0x4c
+#define AR7240_STATS_FILTERED 0x50
+#define AR7240_STATS_TXBROAD 0x54
+#define AR7240_STATS_TXPAUSE 0x58
+#define AR7240_STATS_TXMULTI 0x5c
+#define AR7240_STATS_TXUNDERRUN 0x60
+#define AR7240_STATS_TX64BYTE 0x64
+#define AR7240_STATS_TX128BYTE 0x68
+#define AR7240_STATS_TX256BYTE 0x6c
+#define AR7240_STATS_TX512BYTE 0x70
+#define AR7240_STATS_TX1024BYTE 0x74
+#define AR7240_STATS_TX1518BYTE 0x78
+#define AR7240_STATS_TXMAXBYTE 0x7c
+#define AR7240_STATS_TXOVERSIZE 0x80
+#define AR7240_STATS_TXBYTE 0x84
+#define AR7240_STATS_TXCOLLISION 0x8c
+#define AR7240_STATS_TXABORTCOL 0x90
+#define AR7240_STATS_TXMULTICOL 0x94
+#define AR7240_STATS_TXSINGLECOL 0x98
+#define AR7240_STATS_TXEXCDEFER 0x9c
+#define AR7240_STATS_TXDEFER 0xa0
+#define AR7240_STATS_TXLATECOL 0xa4
+
+#define AR7240_PORT_CPU 0
+#define AR7240_NUM_PORTS 6
+#define AR7240_NUM_PHYS 5
+
+#define AR7240_PHY_ID1 0x004d
+#define AR7240_PHY_ID2 0xd041
+
+#define AR934X_PHY_ID1 0x004d
+#define AR934X_PHY_ID2 0xd042
+
+#define AR7240_MAX_VLANS 16
+
+#define AR934X_REG_OPER_MODE0 0x04
+#define AR934X_OPER_MODE0_MAC_GMII_EN BIT(6)
+#define AR934X_OPER_MODE0_PHY_MII_EN BIT(10)
+
+#define AR934X_REG_OPER_MODE1 0x08
+#define AR934X_REG_OPER_MODE1_PHY4_MII_EN BIT(28)
+
+#define AR934X_REG_FLOOD_MASK 0x2c
+#define AR934X_FLOOD_MASK_MC_DP(_p) BIT(16 + (_p))
+#define AR934X_FLOOD_MASK_BC_DP(_p) BIT(25 + (_p))
+
+#define AR934X_REG_QM_CTRL 0x3c
+#define AR934X_QM_CTRL_ARP_EN BIT(15)
+
+#define AR934X_REG_AT_CTRL 0x5c
+#define AR934X_AT_CTRL_AGE_TIME BITS(0, 15)
+#define AR934X_AT_CTRL_AGE_EN BIT(17)
+#define AR934X_AT_CTRL_LEARN_CHANGE BIT(18)
+
+#define AR934X_MIB_ENABLE BIT(30)
+
+#define AR934X_REG_PORT_BASE(_port) (0x100 + (_port) * 0x100)
+
+#define AR934X_REG_PORT_VLAN1(_port) (AR934X_REG_PORT_BASE((_port)) + 0x08)
+#define AR934X_PORT_VLAN1_DEFAULT_SVID_S 0
+#define AR934X_PORT_VLAN1_FORCE_DEFAULT_VID_EN BIT(12)
+#define AR934X_PORT_VLAN1_PORT_TLS_MODE BIT(13)
+#define AR934X_PORT_VLAN1_PORT_VLAN_PROP_EN BIT(14)
+#define AR934X_PORT_VLAN1_PORT_CLONE_EN BIT(15)
+#define AR934X_PORT_VLAN1_DEFAULT_CVID_S 16
+#define AR934X_PORT_VLAN1_FORCE_PORT_VLAN_EN BIT(28)
+#define AR934X_PORT_VLAN1_ING_PORT_PRI_S 29
+
+#define AR934X_REG_PORT_VLAN2(_port) (AR934X_REG_PORT_BASE((_port)) + 0x0c)
+#define AR934X_PORT_VLAN2_PORT_VID_MEM_S 16
+#define AR934X_PORT_VLAN2_8021Q_MODE_S 30
+#define AR934X_PORT_VLAN2_8021Q_MODE_PORT_ONLY 0
+#define AR934X_PORT_VLAN2_8021Q_MODE_PORT_FALLBACK 1
+#define AR934X_PORT_VLAN2_8021Q_MODE_VLAN_ONLY 2
+#define AR934X_PORT_VLAN2_8021Q_MODE_SECURE 3
+
+#define sw_to_ar7240(_dev) container_of(_dev, struct ar7240sw, swdev)
+
+struct ar7240sw_port_stat {
+ unsigned long rx_broadcast;
+ unsigned long rx_pause;
+ unsigned long rx_multicast;
+ unsigned long rx_fcs_error;
+ unsigned long rx_align_error;
+ unsigned long rx_runt;
+ unsigned long rx_fragments;
+ unsigned long rx_64byte;
+ unsigned long rx_128byte;
+ unsigned long rx_256byte;
+ unsigned long rx_512byte;
+ unsigned long rx_1024byte;
+ unsigned long rx_1518byte;
+ unsigned long rx_maxbyte;
+ unsigned long rx_toolong;
+ unsigned long rx_good_byte;
+ unsigned long rx_bad_byte;
+ unsigned long rx_overflow;
+ unsigned long filtered;
+
+ unsigned long tx_broadcast;
+ unsigned long tx_pause;
+ unsigned long tx_multicast;
+ unsigned long tx_underrun;
+ unsigned long tx_64byte;
+ unsigned long tx_128byte;
+ unsigned long tx_256byte;
+ unsigned long tx_512byte;
+ unsigned long tx_1024byte;
+ unsigned long tx_1518byte;
+ unsigned long tx_maxbyte;
+ unsigned long tx_oversize;
+ unsigned long tx_byte;
+ unsigned long tx_collision;
+ unsigned long tx_abortcol;
+ unsigned long tx_multicol;
+ unsigned long tx_singlecol;
+ unsigned long tx_excdefer;
+ unsigned long tx_defer;
+ unsigned long tx_xlatecol;
+};
+
+struct ar7240sw {
+ struct mii_bus *mii_bus;
+ struct mii_bus *switch_mii_bus;
+ struct device_node *of_node;
+ struct device_node *mdio_node;
+ struct switch_dev swdev;
+ int num_ports;
+ u8 ver;
+ bool vlan;
+ u16 vlan_id[AR7240_MAX_VLANS];
+ u8 vlan_table[AR7240_MAX_VLANS];
+ u8 vlan_tagged;
+ u16 pvid[AR7240_NUM_PORTS];
+ char buf[80];
+
+ rwlock_t stats_lock;
+ struct ar7240sw_port_stat port_stats[AR7240_NUM_PORTS];
+};
+
+struct ar7240sw_hw_stat {
+ char string[ETH_GSTRING_LEN];
+ int sizeof_stat;
+ int reg;
+};
+
+static DEFINE_MUTEX(reg_mutex);
+
+static inline int sw_is_ar7240(struct ar7240sw *as)
+{
+ return as->ver == AR7240_MASK_CTRL_VERSION_AR7240;
+}
+
+static inline int sw_is_ar934x(struct ar7240sw *as)
+{
+ return as->ver == AR7240_MASK_CTRL_VERSION_AR934X;
+}
+
+static inline u32 ar7240sw_port_mask(struct ar7240sw *as, int port)
+{
+ return BIT(port);
+}
+
+static inline u32 ar7240sw_port_mask_all(struct ar7240sw *as)
+{
+ return BIT(as->swdev.ports) - 1;
+}
+
+static inline u32 ar7240sw_port_mask_but(struct ar7240sw *as, int port)
+{
+ return ar7240sw_port_mask_all(as) & ~BIT(port);
+}
+
+static inline u16 mk_phy_addr(u32 reg)
+{
+ return 0x17 & ((reg >> 4) | 0x10);
+}
+
+static inline u16 mk_phy_reg(u32 reg)
+{
+ return (reg << 1) & 0x1e;
+}
+
+static inline u16 mk_high_addr(u32 reg)
+{
+ return (reg >> 7) & 0x1ff;
+}
+
+static u32 __ar7240sw_reg_read(struct mii_bus *mii, u32 reg)
+{
+ unsigned long flags;
+ u16 phy_addr;
+ u16 phy_reg;
+ u32 hi, lo;
+
+ reg = (reg & 0xfffffffc) >> 2;
+ phy_addr = mk_phy_addr(reg);
+ phy_reg = mk_phy_reg(reg);
+
+ local_irq_save(flags);
+ mutex_lock(&mii->mdio_lock);
+ mii->write(mii, 0x1f, 0x10, mk_high_addr(reg));
+ lo = (u32) mii->read(mii, phy_addr, phy_reg);
+ hi = (u32) mii->read(mii, phy_addr, phy_reg + 1);
+ mutex_unlock(&mii->mdio_lock);
+ local_irq_restore(flags);
+
+ return (hi << 16) | lo;
+}
+
+static void __ar7240sw_reg_write(struct mii_bus *mii, u32 reg, u32 val)
+{
+ unsigned long flags;
+ u16 phy_addr;
+ u16 phy_reg;
+
+ reg = (reg & 0xfffffffc) >> 2;
+ phy_addr = mk_phy_addr(reg);
+ phy_reg = mk_phy_reg(reg);
+
+ local_irq_save(flags);
+ mutex_lock(&mii->mdio_lock);
+ mii->write(mii, 0x1f, 0x10, mk_high_addr(reg));
+ mii->write(mii, phy_addr, phy_reg + 1, (val >> 16));
+ mii->write(mii, phy_addr, phy_reg, (val & 0xffff));
+ mutex_unlock(&mii->mdio_lock);
+ local_irq_restore(flags);
+}
+
+static u32 ar7240sw_reg_read(struct mii_bus *mii, u32 reg_addr)
+{
+ u32 ret;
+
+ mutex_lock(&reg_mutex);
+ ret = __ar7240sw_reg_read(mii, reg_addr);
+ mutex_unlock(&reg_mutex);
+
+ return ret;
+}
+
+static void ar7240sw_reg_write(struct mii_bus *mii, u32 reg_addr, u32 reg_val)
+{
+ mutex_lock(&reg_mutex);
+ __ar7240sw_reg_write(mii, reg_addr, reg_val);
+ mutex_unlock(&reg_mutex);
+}
+
+static u32 ar7240sw_reg_rmw(struct mii_bus *mii, u32 reg, u32 mask, u32 val)
+{
+ u32 t;
+
+ mutex_lock(&reg_mutex);
+ t = __ar7240sw_reg_read(mii, reg);
+ t &= ~mask;
+ t |= val;
+ __ar7240sw_reg_write(mii, reg, t);
+ mutex_unlock(&reg_mutex);
+
+ return t;
+}
+
+static void ar7240sw_reg_set(struct mii_bus *mii, u32 reg, u32 val)
+{
+ u32 t;
+
+ mutex_lock(&reg_mutex);
+ t = __ar7240sw_reg_read(mii, reg);
+ t |= val;
+ __ar7240sw_reg_write(mii, reg, t);
+ mutex_unlock(&reg_mutex);
+}
+
+static int __ar7240sw_reg_wait(struct mii_bus *mii, u32 reg, u32 mask, u32 val,
+ unsigned timeout)
+{
+ int i;
+
+ for (i = 0; i < timeout; i++) {
+ u32 t;
+
+ t = __ar7240sw_reg_read(mii, reg);
+ if ((t & mask) == val)
+ return 0;
+
+ usleep_range(1000, 2000);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int ar7240sw_reg_wait(struct mii_bus *mii, u32 reg, u32 mask, u32 val,
+ unsigned timeout)
+{
+ int ret;
+
+ mutex_lock(&reg_mutex);
+ ret = __ar7240sw_reg_wait(mii, reg, mask, val, timeout);
+ mutex_unlock(&reg_mutex);
+ return ret;
+}
+
+int ar7240sw_phy_read(struct mii_bus *bus, int phy_addr, int reg_addr)
+{
+ u32 t, val = 0xffff;
+ int err;
+ struct ar7240sw *as = bus->priv;
+ struct mii_bus *mii = as->mii_bus;
+
+ if (phy_addr >= AR7240_NUM_PHYS)
+ return 0xffff;
+
+ mutex_lock(&reg_mutex);
+ t = (reg_addr << AR7240_MDIO_CTRL_REG_ADDR_S) |
+ (phy_addr << AR7240_MDIO_CTRL_PHY_ADDR_S) |
+ AR7240_MDIO_CTRL_MASTER_EN |
+ AR7240_MDIO_CTRL_BUSY |
+ AR7240_MDIO_CTRL_CMD_READ;
+
+ __ar7240sw_reg_write(mii, AR7240_REG_MDIO_CTRL, t);
+ err = __ar7240sw_reg_wait(mii, AR7240_REG_MDIO_CTRL,
+ AR7240_MDIO_CTRL_BUSY, 0, 5);
+ if (!err)
+ val = __ar7240sw_reg_read(mii, AR7240_REG_MDIO_CTRL);
+ mutex_unlock(&reg_mutex);
+
+ return val & AR7240_MDIO_CTRL_DATA_M;
+}
+
+int ar7240sw_phy_write(struct mii_bus *bus, int phy_addr, int reg_addr,
+ u16 reg_val)
+{
+ u32 t;
+ int ret;
+ struct ar7240sw *as = bus->priv;
+ struct mii_bus *mii = as->mii_bus;
+
+ if (phy_addr >= AR7240_NUM_PHYS)
+ return -EINVAL;
+
+ mutex_lock(&reg_mutex);
+ t = (phy_addr << AR7240_MDIO_CTRL_PHY_ADDR_S) |
+ (reg_addr << AR7240_MDIO_CTRL_REG_ADDR_S) |
+ AR7240_MDIO_CTRL_MASTER_EN |
+ AR7240_MDIO_CTRL_BUSY |
+ AR7240_MDIO_CTRL_CMD_WRITE |
+ reg_val;
+
+ __ar7240sw_reg_write(mii, AR7240_REG_MDIO_CTRL, t);
+ ret = __ar7240sw_reg_wait(mii, AR7240_REG_MDIO_CTRL,
+ AR7240_MDIO_CTRL_BUSY, 0, 5);
+ mutex_unlock(&reg_mutex);
+
+ return ret;
+}
+
+static int ar7240sw_capture_stats(struct ar7240sw *as)
+{
+ struct mii_bus *mii = as->mii_bus;
+ int port;
+ int ret;
+
+ write_lock(&as->stats_lock);
+
+ /* Capture the hardware statistics for all ports */
+ ar7240sw_reg_rmw(mii, AR7240_REG_MIB_FUNCTION0,
+ (AR7240_MIB_FUNC_M << AR7240_MIB_FUNC_S),
+ (AR7240_MIB_FUNC_CAPTURE << AR7240_MIB_FUNC_S));
+
+ /* Wait for the capturing to complete. */
+ ret = ar7240sw_reg_wait(mii, AR7240_REG_MIB_FUNCTION0,
+ AR7240_MIB_BUSY, 0, 10);
+
+ if (ret)
+ goto unlock;
+
+ for (port = 0; port < AR7240_NUM_PORTS; port++) {
+ unsigned int base;
+ struct ar7240sw_port_stat *stats;
+
+ base = AR7240_REG_STATS_BASE(port);
+ stats = &as->port_stats[port];
+
+#define READ_STAT(_r) ar7240sw_reg_read(mii, base + AR7240_STATS_ ## _r)
+
+ stats->rx_good_byte += READ_STAT(RXGOODBYTE);
+ stats->tx_byte += READ_STAT(TXBYTE);
+
+#undef READ_STAT
+ }
+
+ ret = 0;
+
+unlock:
+ write_unlock(&as->stats_lock);
+ return ret;
+}
+
+static void ar7240sw_disable_port(struct ar7240sw *as, unsigned port)
+{
+ ar7240sw_reg_write(as->mii_bus, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_STATE_DISABLED);
+}
+
+static void ar7240sw_setup(struct ar7240sw *as)
+{
+ struct mii_bus *mii = as->mii_bus;
+
+ /* Enable CPU port, and disable mirror port */
+ ar7240sw_reg_write(mii, AR7240_REG_CPU_PORT,
+ AR7240_CPU_PORT_EN |
+ (15 << AR7240_MIRROR_PORT_S));
+
+ /* Setup TAG priority mapping */
+ ar7240sw_reg_write(mii, AR7240_REG_TAG_PRIORITY, 0xfa50);
+
+ if (sw_is_ar934x(as)) {
+ /* Enable aging, MAC replacing */
+ ar7240sw_reg_write(mii, AR934X_REG_AT_CTRL,
+ 0x2b /* 5 min age time */ |
+ AR934X_AT_CTRL_AGE_EN |
+ AR934X_AT_CTRL_LEARN_CHANGE);
+ /* Enable ARP frame acknowledge */
+ ar7240sw_reg_set(mii, AR934X_REG_QM_CTRL,
+ AR934X_QM_CTRL_ARP_EN);
+ /* Enable Broadcast/Multicast frames transmitted to the CPU */
+ ar7240sw_reg_set(mii, AR934X_REG_FLOOD_MASK,
+ AR934X_FLOOD_MASK_BC_DP(0) |
+ AR934X_FLOOD_MASK_MC_DP(0));
+
+ /* setup MTU */
+ ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL,
+ AR9340_GLOBAL_CTRL_MTU_M,
+ AR9340_GLOBAL_CTRL_MTU_M);
+
+ /* Enable MIB counters */
+ ar7240sw_reg_set(mii, AR7240_REG_MIB_FUNCTION0,
+ AR934X_MIB_ENABLE);
+
+ } else {
+ /* Enable ARP frame acknowledge, aging, MAC replacing */
+ ar7240sw_reg_write(mii, AR7240_REG_AT_CTRL,
+ AR7240_AT_CTRL_RESERVED |
+ 0x2b /* 5 min age time */ |
+ AR7240_AT_CTRL_AGE_EN |
+ AR7240_AT_CTRL_ARP_EN |
+ AR7240_AT_CTRL_LEARN_CHANGE);
+ /* Enable Broadcast frames transmitted to the CPU */
+ ar7240sw_reg_set(mii, AR7240_REG_FLOOD_MASK,
+ AR7240_FLOOD_MASK_BROAD_TO_CPU);
+
+ /* setup MTU */
+ ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL,
+ AR7240_GLOBAL_CTRL_MTU_M,
+ AR7240_GLOBAL_CTRL_MTU_M);
+ }
+
+ /* setup Service TAG */
+ ar7240sw_reg_rmw(mii, AR7240_REG_SERVICE_TAG, AR7240_SERVICE_TAG_M, 0);
+}
+
+/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */
+static int
+ar7240sw_phy_poll_reset(struct mii_bus *bus)
+{
+ const unsigned int sleep_msecs = 20;
+ int ret, elapsed, i;
+
+ for (elapsed = sleep_msecs; elapsed <= 600;
+ elapsed += sleep_msecs) {
+ msleep(sleep_msecs);
+ for (i = 0; i < AR7240_NUM_PHYS; i++) {
+ ret = ar7240sw_phy_read(bus, i, MII_BMCR);
+ if (ret < 0)
+ return ret;
+ if (ret & BMCR_RESET)
+ break;
+ if (i == AR7240_NUM_PHYS - 1) {
+ usleep_range(1000, 2000);
+ return 0;
+ }
+ }
+ }
+ return -ETIMEDOUT;
+}
+
+static int ar7240sw_reset(struct ar7240sw *as)
+{
+ struct mii_bus *mii = as->mii_bus;
+ struct mii_bus *swmii = as->switch_mii_bus;
+ int ret;
+ int i;
+
+ /* Set all ports to disabled state. */
+ for (i = 0; i < AR7240_NUM_PORTS; i++)
+ ar7240sw_disable_port(as, i);
+
+ /* Wait for transmit queues to drain. */
+ usleep_range(2000, 3000);
+
+ /* Reset the switch. */
+ ar7240sw_reg_write(mii, AR7240_REG_MASK_CTRL,
+ AR7240_MASK_CTRL_SOFT_RESET);
+
+ ret = ar7240sw_reg_wait(mii, AR7240_REG_MASK_CTRL,
+ AR7240_MASK_CTRL_SOFT_RESET, 0, 1000);
+
+ /* setup PHYs */
+ for (i = 0; i < AR7240_NUM_PHYS; i++) {
+ ar7240sw_phy_write(swmii, i, MII_ADVERTISE,
+ ADVERTISE_ALL | ADVERTISE_PAUSE_CAP |
+ ADVERTISE_PAUSE_ASYM);
+ ar7240sw_phy_write(swmii, i, MII_BMCR,
+ BMCR_RESET | BMCR_ANENABLE);
+ }
+ ret = ar7240sw_phy_poll_reset(swmii);
+ if (ret)
+ return ret;
+
+ ar7240sw_setup(as);
+ return ret;
+}
+
+static void ar7240sw_setup_port(struct ar7240sw *as, unsigned port, u8 portmask)
+{
+ struct mii_bus *mii = as->mii_bus;
+ u32 ctrl;
+ u32 vid, mode;
+
+ ctrl = AR7240_PORT_CTRL_STATE_FORWARD | AR7240_PORT_CTRL_LEARN |
+ AR7240_PORT_CTRL_SINGLE_VLAN;
+
+ if (port == AR7240_PORT_CPU) {
+ ar7240sw_reg_write(mii, AR7240_REG_PORT_STATUS(port),
+ AR7240_PORT_STATUS_SPEED_1000 |
+ AR7240_PORT_STATUS_TXFLOW |
+ AR7240_PORT_STATUS_RXFLOW |
+ AR7240_PORT_STATUS_TXMAC |
+ AR7240_PORT_STATUS_RXMAC |
+ AR7240_PORT_STATUS_DUPLEX);
+ } else {
+ ar7240sw_reg_write(mii, AR7240_REG_PORT_STATUS(port),
+ AR7240_PORT_STATUS_LINK_AUTO);
+ }
+
+ /* Set the default VID for this port */
+ if (as->vlan) {
+ vid = as->vlan_id[as->pvid[port]];
+ mode = AR7240_PORT_VLAN_MODE_SECURE;
+ } else {
+ vid = port;
+ mode = AR7240_PORT_VLAN_MODE_PORT_ONLY;
+ }
+
+ if (as->vlan) {
+ if (as->vlan_tagged & BIT(port))
+ ctrl |= AR7240_PORT_CTRL_VLAN_MODE_ADD <<
+ AR7240_PORT_CTRL_VLAN_MODE_S;
+ else
+ ctrl |= AR7240_PORT_CTRL_VLAN_MODE_STRIP <<
+ AR7240_PORT_CTRL_VLAN_MODE_S;
+ } else {
+ ctrl |= AR7240_PORT_CTRL_VLAN_MODE_KEEP <<
+ AR7240_PORT_CTRL_VLAN_MODE_S;
+ }
+
+ if (!portmask) {
+ if (port == AR7240_PORT_CPU)
+ portmask = ar7240sw_port_mask_but(as, AR7240_PORT_CPU);
+ else
+ portmask = ar7240sw_port_mask(as, AR7240_PORT_CPU);
+ }
+
+ /* preserve mirror rx&tx flags */
+ ctrl |= ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port)) &
+ (AR7240_PORT_CTRL_MIRROR_RX | AR7240_PORT_CTRL_MIRROR_TX);
+
+ /* allow the port to talk to all other ports, but exclude its
+ * own ID to prevent frames from being reflected back to the
+ * port that they came from */
+ portmask &= ar7240sw_port_mask_but(as, port);
+
+ ar7240sw_reg_write(mii, AR7240_REG_PORT_CTRL(port), ctrl);
+ if (sw_is_ar934x(as)) {
+ u32 vlan1, vlan2;
+
+ vlan1 = (vid << AR934X_PORT_VLAN1_DEFAULT_CVID_S);
+ vlan2 = (portmask << AR934X_PORT_VLAN2_PORT_VID_MEM_S) |
+ (mode << AR934X_PORT_VLAN2_8021Q_MODE_S);
+ ar7240sw_reg_write(mii, AR934X_REG_PORT_VLAN1(port), vlan1);
+ ar7240sw_reg_write(mii, AR934X_REG_PORT_VLAN2(port), vlan2);
+ } else {
+ u32 vlan;
+
+ vlan = vid | (mode << AR7240_PORT_VLAN_MODE_S) |
+ (portmask << AR7240_PORT_VLAN_DEST_PORTS_S);
+
+ ar7240sw_reg_write(mii, AR7240_REG_PORT_VLAN(port), vlan);
+ }
+}
+
+static int
+ar7240_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ as->vlan_id[val->port_vlan] = val->value.i;
+ return 0;
+}
+
+static int
+ar7240_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ val->value.i = as->vlan_id[val->port_vlan];
+ return 0;
+}
+
+static int
+ar7240_set_pvid(struct switch_dev *dev, int port, int vlan)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+
+ /* make sure no invalid PVIDs get set */
+
+ if (vlan >= dev->vlans)
+ return -EINVAL;
+
+ as->pvid[port] = vlan;
+ return 0;
+}
+
+static int
+ar7240_get_pvid(struct switch_dev *dev, int port, int *vlan)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ *vlan = as->pvid[port];
+ return 0;
+}
+
+static int
+ar7240_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ u8 ports = as->vlan_table[val->port_vlan];
+ int i;
+
+ val->len = 0;
+ for (i = 0; i < as->swdev.ports; i++) {
+ struct switch_port *p;
+
+ if (!(ports & (1 << i)))
+ continue;
+
+ p = &val->value.ports[val->len++];
+ p->id = i;
+ if (as->vlan_tagged & (1 << i))
+ p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
+ else
+ p->flags = 0;
+ }
+ return 0;
+}
+
+static int
+ar7240_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ u8 *vt = &as->vlan_table[val->port_vlan];
+ int i, j;
+
+ *vt = 0;
+ for (i = 0; i < val->len; i++) {
+ struct switch_port *p = &val->value.ports[i];
+
+ if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
+ as->vlan_tagged |= (1 << p->id);
+ else {
+ as->vlan_tagged &= ~(1 << p->id);
+ as->pvid[p->id] = val->port_vlan;
+
+ /* make sure that an untagged port does not
+ * appear in other vlans */
+ for (j = 0; j < AR7240_MAX_VLANS; j++) {
+ if (j == val->port_vlan)
+ continue;
+ as->vlan_table[j] &= ~(1 << p->id);
+ }
+ }
+
+ *vt |= 1 << p->id;
+ }
+ return 0;
+}
+
+static int
+ar7240_set_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ as->vlan = !!val->value.i;
+ return 0;
+}
+
+static int
+ar7240_get_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ val->value.i = as->vlan;
+ return 0;
+}
+
+static void
+ar7240_vtu_op(struct ar7240sw *as, u32 op, u32 val)
+{
+ struct mii_bus *mii = as->mii_bus;
+
+ if (ar7240sw_reg_wait(mii, AR7240_REG_VTU, AR7240_VTU_ACTIVE, 0, 5))
+ return;
+
+ if ((op & AR7240_VTU_OP) == AR7240_VTU_OP_LOAD) {
+ val &= AR7240_VTUDATA_MEMBER;
+ val |= AR7240_VTUDATA_VALID;
+ ar7240sw_reg_write(mii, AR7240_REG_VTU_DATA, val);
+ }
+ op |= AR7240_VTU_ACTIVE;
+ ar7240sw_reg_write(mii, AR7240_REG_VTU, op);
+}
+
+static int
+ar7240_hw_apply(struct switch_dev *dev)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ u8 portmask[AR7240_NUM_PORTS];
+ int i, j;
+
+ /* flush all vlan translation unit entries */
+ ar7240_vtu_op(as, AR7240_VTU_OP_FLUSH, 0);
+
+ memset(portmask, 0, sizeof(portmask));
+ if (as->vlan) {
+ /* calculate the port destination masks and load vlans
+ * into the vlan translation unit */
+ for (j = 0; j < AR7240_MAX_VLANS; j++) {
+ u8 vp = as->vlan_table[j];
+
+ if (!vp)
+ continue;
+
+ for (i = 0; i < as->swdev.ports; i++) {
+ u8 mask = (1 << i);
+ if (vp & mask)
+ portmask[i] |= vp & ~mask;
+ }
+
+ ar7240_vtu_op(as,
+ AR7240_VTU_OP_LOAD |
+ (as->vlan_id[j] << AR7240_VTU_VID_S),
+ as->vlan_table[j]);
+ }
+ } else {
+ /* vlan disabled:
+ * isolate all ports, but connect them to the cpu port */
+ for (i = 0; i < as->swdev.ports; i++) {
+ if (i == AR7240_PORT_CPU)
+ continue;
+
+ portmask[i] = 1 << AR7240_PORT_CPU;
+ portmask[AR7240_PORT_CPU] |= (1 << i);
+ }
+ }
+
+ /* update the port destination mask registers and tag settings */
+ for (i = 0; i < as->swdev.ports; i++)
+ ar7240sw_setup_port(as, i, portmask[i]);
+
+ return 0;
+}
+
+static int
+ar7240_reset_switch(struct switch_dev *dev)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ ar7240sw_reset(as);
+ return 0;
+}
+
+static int
+ar7240_get_port_link(struct switch_dev *dev, int port,
+ struct switch_port_link *link)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+ u32 status;
+
+ if (port >= AR7240_NUM_PORTS)
+ return -EINVAL;
+
+ status = ar7240sw_reg_read(mii, AR7240_REG_PORT_STATUS(port));
+ link->aneg = !!(status & AR7240_PORT_STATUS_LINK_AUTO);
+ if (link->aneg) {
+ link->link = !!(status & AR7240_PORT_STATUS_LINK_UP);
+ if (!link->link)
+ return 0;
+ } else {
+ link->link = true;
+ }
+
+ link->duplex = !!(status & AR7240_PORT_STATUS_DUPLEX);
+ link->tx_flow = !!(status & AR7240_PORT_STATUS_TXFLOW);
+ link->rx_flow = !!(status & AR7240_PORT_STATUS_RXFLOW);
+ switch (status & AR7240_PORT_STATUS_SPEED_M) {
+ case AR7240_PORT_STATUS_SPEED_10:
+ link->speed = SWITCH_PORT_SPEED_10;
+ break;
+ case AR7240_PORT_STATUS_SPEED_100:
+ link->speed = SWITCH_PORT_SPEED_100;
+ break;
+ case AR7240_PORT_STATUS_SPEED_1000:
+ link->speed = SWITCH_PORT_SPEED_1000;
+ break;
+ }
+
+ return 0;
+}
+
+static int
+ar7240_get_port_stats(struct switch_dev *dev, int port,
+ struct switch_port_stats *stats)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+
+ if (port >= AR7240_NUM_PORTS)
+ return -EINVAL;
+
+ ar7240sw_capture_stats(as);
+
+ read_lock(&as->stats_lock);
+ stats->rx_bytes = as->port_stats[port].rx_good_byte;
+ stats->tx_bytes = as->port_stats[port].tx_byte;
+ read_unlock(&as->stats_lock);
+
+ return 0;
+}
+
+static int
+ar7240_set_mirror_monitor_port(struct switch_dev *dev,
+ const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ int port = val->value.i;
+
+ if (port > 15)
+ return -EINVAL;
+
+ ar7240sw_reg_rmw(mii, AR7240_REG_CPU_PORT,
+ AR7240_MIRROR_PORT_M << AR7240_MIRROR_PORT_S,
+ port << AR7240_MIRROR_PORT_S);
+
+ return 0;
+}
+
+static int
+ar7240_get_mirror_monitor_port(struct switch_dev *dev,
+ const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ u32 ret;
+
+ ret = ar7240sw_reg_read(mii, AR7240_REG_CPU_PORT);
+ val->value.i = (ret >> AR7240_MIRROR_PORT_S) & AR7240_MIRROR_PORT_M;
+
+ return 0;
+}
+
+static int
+ar7240_set_mirror_rx(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ int port = val->port_vlan;
+
+ if (port >= dev->ports)
+ return -EINVAL;
+
+ if (val && val->value.i == 1)
+ ar7240sw_reg_set(mii, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_MIRROR_RX);
+ else
+ ar7240sw_reg_rmw(mii, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_MIRROR_RX, 0);
+
+ return 0;
+}
+
+static int
+ar7240_get_mirror_rx(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ u32 ctrl;
+
+ int port = val->port_vlan;
+
+ if (port >= dev->ports)
+ return -EINVAL;
+
+ ctrl = ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port));
+
+ if ((ctrl & AR7240_PORT_CTRL_MIRROR_RX) == AR7240_PORT_CTRL_MIRROR_RX)
+ val->value.i = 1;
+ else
+ val->value.i = 0;
+
+ return 0;
+}
+
+static int
+ar7240_set_mirror_tx(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ int port = val->port_vlan;
+
+ if (port >= dev->ports)
+ return -EINVAL;
+
+ if (val && val->value.i == 1)
+ ar7240sw_reg_set(mii, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_MIRROR_TX);
+ else
+ ar7240sw_reg_rmw(mii, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_MIRROR_TX, 0);
+
+ return 0;
+}
+
+static int
+ar7240_get_mirror_tx(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ u32 ctrl;
+
+ int port = val->port_vlan;
+
+ if (port >= dev->ports)
+ return -EINVAL;
+
+ ctrl = ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port));
+
+ if ((ctrl & AR7240_PORT_CTRL_MIRROR_TX) == AR7240_PORT_CTRL_MIRROR_TX)
+ val->value.i = 1;
+ else
+ val->value.i = 0;
+
+ return 0;
+}
+
+static struct switch_attr ar7240_globals[] = {
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "enable_vlan",
+ .description = "Enable VLAN mode",
+ .set = ar7240_set_vlan,
+ .get = ar7240_get_vlan,
+ .max = 1
+ },
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "mirror_monitor_port",
+ .description = "Mirror monitor port",
+ .set = ar7240_set_mirror_monitor_port,
+ .get = ar7240_get_mirror_monitor_port,
+ .max = 15
+ },
+};
+
+static struct switch_attr ar7240_port[] = {
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "enable_mirror_rx",
+ .description = "Enable mirroring of RX packets",
+ .set = ar7240_set_mirror_rx,
+ .get = ar7240_get_mirror_rx,
+ .max = 1
+ },
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "enable_mirror_tx",
+ .description = "Enable mirroring of TX packets",
+ .set = ar7240_set_mirror_tx,
+ .get = ar7240_get_mirror_tx,
+ .max = 1
+ },
+};
+
+static struct switch_attr ar7240_vlan[] = {
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "vid",
+ .description = "VLAN ID",
+ .set = ar7240_set_vid,
+ .get = ar7240_get_vid,
+ .max = 4094,
+ },
+};
+
+static const struct switch_dev_ops ar7240_ops = {
+ .attr_global = {
+ .attr = ar7240_globals,
+ .n_attr = ARRAY_SIZE(ar7240_globals),
+ },
+ .attr_port = {
+ .attr = ar7240_port,
+ .n_attr = ARRAY_SIZE(ar7240_port),
+ },
+ .attr_vlan = {
+ .attr = ar7240_vlan,
+ .n_attr = ARRAY_SIZE(ar7240_vlan),
+ },
+ .get_port_pvid = ar7240_get_pvid,
+ .set_port_pvid = ar7240_set_pvid,
+ .get_vlan_ports = ar7240_get_ports,
+ .set_vlan_ports = ar7240_set_ports,
+ .apply_config = ar7240_hw_apply,
+ .reset_switch = ar7240_reset_switch,
+ .get_port_link = ar7240_get_port_link,
+ .get_port_stats = ar7240_get_port_stats,
+};
+
+static int
+ag71xx_ar7240_probe(struct mdio_device *mdiodev)
+{
+ struct mii_bus *mii = mdiodev->bus;
+ struct ar7240sw *as;
+ struct switch_dev *swdev;
+ struct reset_control *switch_reset;
+ u32 ctrl;
+ int phy_if_mode, err, i;
+
+ as = devm_kzalloc(&mdiodev->dev, sizeof(*as), GFP_KERNEL);
+ if (!as)
+ return -ENOMEM;
+
+ as->mii_bus = mii;
+ as->of_node = mdiodev->dev.of_node;
+ as->mdio_node = of_get_child_by_name(as->of_node, "mdio-bus");
+
+ swdev = &as->swdev;
+
+ switch_reset = devm_reset_control_get_optional(&mdiodev->dev, "switch");
+ if (switch_reset) {
+ reset_control_assert(switch_reset);
+ msleep(50);
+ reset_control_deassert(switch_reset);
+ msleep(200);
+ }
+
+ ctrl = ar7240sw_reg_read(mii, AR7240_REG_MASK_CTRL);
+ as->ver = (ctrl >> AR7240_MASK_CTRL_VERSION_S) &
+ AR7240_MASK_CTRL_VERSION_M;
+
+ if (sw_is_ar7240(as)) {
+ swdev->name = "AR7240/AR9330 built-in switch";
+ swdev->ports = AR7240_NUM_PORTS - 1;
+ } else if (sw_is_ar934x(as)) {
+ swdev->name = "AR934X built-in switch";
+ phy_if_mode = of_get_phy_mode(as->of_node);
+
+ if (phy_if_mode == PHY_INTERFACE_MODE_GMII) {
+ ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE0,
+ AR934X_OPER_MODE0_MAC_GMII_EN);
+ } else if (phy_if_mode == PHY_INTERFACE_MODE_MII) {
+ ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE0,
+ AR934X_OPER_MODE0_PHY_MII_EN);
+ } else {
+ pr_err("%s: invalid PHY interface mode\n",
+ dev_name(&mdiodev->dev));
+ return -EINVAL;
+ }
+
+ if (of_property_read_bool(as->of_node, "phy4-mii-enable")) {
+ ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE1,
+ AR934X_REG_OPER_MODE1_PHY4_MII_EN);
+ swdev->ports = AR7240_NUM_PORTS - 1;
+ } else {
+ swdev->ports = AR7240_NUM_PORTS;
+ }
+ } else {
+ pr_err("%s: unsupported chip, ctrl=%08x\n",
+ dev_name(&mdiodev->dev), ctrl);
+ return -EINVAL;
+ }
+
+ swdev->cpu_port = AR7240_PORT_CPU;
+ swdev->vlans = AR7240_MAX_VLANS;
+ swdev->ops = &ar7240_ops;
+ swdev->alias = dev_name(&mdiodev->dev);
+
+ if ((err = register_switch(&as->swdev, NULL)) < 0)
+ return err;
+
+ pr_info("%s: Found an %s\n", dev_name(&mdiodev->dev), swdev->name);
+
+ as->switch_mii_bus = devm_mdiobus_alloc(&mdiodev->dev);
+ as->switch_mii_bus->name = "ar7240sw_mdio";
+ as->switch_mii_bus->read = ar7240sw_phy_read;
+ as->switch_mii_bus->write = ar7240sw_phy_write;
+ as->switch_mii_bus->priv = as;
+ as->switch_mii_bus->parent = &mdiodev->dev;
+ snprintf(as->switch_mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(&mdiodev->dev));
+
+ if(as->mdio_node) {
+ err = of_mdiobus_register(as->switch_mii_bus, as->mdio_node);
+ if (err)
+ return err;
+ }
+
+ /* initialize defaults */
+ for (i = 0; i < AR7240_MAX_VLANS; i++)
+ as->vlan_id[i] = i;
+
+ as->vlan_table[0] = ar7240sw_port_mask_all(as);
+ ar7240sw_reset(as);
+ ar7240_hw_apply(&as->swdev);
+ rwlock_init(&as->stats_lock);
+ dev_set_drvdata(&mdiodev->dev, as);
+ return 0;
+}
+
+static void
+ag71xx_ar7240_remove(struct mdio_device *mdiodev)
+{
+ struct ar7240sw *as = dev_get_drvdata(&mdiodev->dev);
+ if(as->mdio_node)
+ mdiobus_unregister(as->switch_mii_bus);
+ unregister_switch(&as->swdev);
+}
+
+static const struct of_device_id ag71xx_sw_of_match[] = {
+ { .compatible = "qca,ar8216-builtin" },
+ { .compatible = "qca,ar8229-builtin" },
+ { /* sentinel */ },
+};
+
+static struct mdio_driver ag71xx_sw_driver = {
+ .probe = ag71xx_ar7240_probe,
+ .remove = ag71xx_ar7240_remove,
+ .mdiodrv.driver = {
+ .name = "ag71xx-switch",
+ .of_match_table = ag71xx_sw_of_match,
+ },
+};
+
+mdio_module_driver(ag71xx_sw_driver);
+MODULE_LICENSE("GPL");
diff --git a/target/linux/ath79/image/Makefile b/target/linux/ath79/image/Makefile
index 734f27e689..10dbcaa24a 100644
--- a/target/linux/ath79/image/Makefile
+++ b/target/linux/ath79/image/Makefile
@@ -63,7 +63,7 @@ define Device/Default
SUPPORTED_DEVICES := $(subst _,$(comma),$(1))
IMAGES := sysupgrade.bin
IMAGE/sysupgrade.bin = append-kernel | pad-to $$$$(BLOCKSIZE) | \
- append-rootfs | pad-rootfs | append-metadata | check-size $$$$(IMAGE_SIZE)
+ append-rootfs | pad-rootfs | append-gl-metadata | check-size $$$$(IMAGE_SIZE)
endef
ifeq ($(SUBTARGET),generic)
diff --git a/target/linux/ath79/image/generic.mk b/target/linux/ath79/image/generic.mk
index 55053be34f..ea59faa196 100644
--- a/target/linux/ath79/image/generic.mk
+++ b/target/linux/ath79/image/generic.mk
@@ -377,7 +377,7 @@ define Device/glinet_gl-ar150
IMAGE_SIZE := 16000k
SUPPORTED_DEVICES += gl-ar150
endef
-TARGET_DEVICES += glinet_gl-ar150
+#TARGET_DEVICES += glinet_gl-ar150
define Device/glinet_gl-ar300m-common-nor
ATH_SOC := qca9531
@@ -398,13 +398,13 @@ define Device/glinet_gl-ar300m-nor
endef
#TARGET_DEVICES += glinet_gl-ar300m-nor
-define Device/glinet_gl-ar750s
- ATH_SOC := qca9563
- DEVICE_TITLE := GL.iNet GL-AR750S
- DEVICE_PACKAGES := kmod-usb2 kmod-ath10k-ct ath10k-firmware-qca9887-ct block-mount
- IMAGE_SIZE := 16000k
- SUPPORTED_DEVICES += gl-ar750s
-endef
+#define Device/glinet_gl-ar750s
+# ATH_SOC := qca9563
+# DEVICE_TITLE := GL.iNet GL-AR750S
+# DEVICE_PACKAGES := kmod-usb2 kmod-ath10k-ct ath10k-firmware-qca9887-ct block-mount
+# IMAGE_SIZE := 16000k
+# SUPPORTED_DEVICES += gl-ar750s
+#endef
#TARGET_DEVICES += glinet_gl-ar750s
define Device/glinet_gl-x750
@@ -412,8 +412,9 @@ define Device/glinet_gl-x750
DEVICE_TITLE := GL.iNet GL-X750
DEVICE_PACKAGES := kmod-usb-core kmod-usb2 kmod-ath10k-ct ath10k-firmware-qca9887-ct
IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-x750
endef
-TARGET_DEVICES += glinet_gl-x750
+#TARGET_DEVICES += glinet_gl-x750
define Device/iodata_etg3-r
ATH_SOC := ar9342
diff --git a/target/linux/ath79/image/nand.mk b/target/linux/ath79/image/nand.mk
index 0b5c05f8cb..ffb2c27560 100644
--- a/target/linux/ath79/image/nand.mk
+++ b/target/linux/ath79/image/nand.mk
@@ -1,3 +1,38 @@
+define Device/glinet_gl-ar150
+ ATH_SOC := ar9330
+ DEVICE_TITLE := GL.iNet GL-AR150
+ DEVICE_PACKAGES := kmod-usb2 block-mount
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-ar150 glinet,gl-ar150
+endef
+TARGET_DEVICES += glinet_gl-ar150
+
+define Device/glinet_gl-usb150
+ ATH_SOC := ar9330
+ DEVICE_TITLE := GL.iNet GL-USB150
+ DEVICE_PACKAGES := kmod-usb2 block-mount
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-usb150 glinet,gl-usb150
+endef
+TARGET_DEVICES += glinet_gl-usb150
+
+define Device/glinet_gl-mifi
+ ATH_SOC := ar9330
+ DEVICE_TITLE := GL.iNet GL-MIFI
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-mifi glinet,gl-mifi
+endef
+TARGET_DEVICES += glinet_gl-mifi
+
+define Device/glinet_gl-ar300m-nor
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-AR300M (NOR)
+ DEVICE_PACKAGES := kmod-usb2 block-mount
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-ar300m glinet,gl-ar300m
+endef
+TARGET_DEVICES += glinet_gl-ar300m-nor
+
define Device/glinet_gl-ar300m-nand
ATH_SOC := qca9531
DEVICE_TITLE := GL-AR300M (NAND)
@@ -5,9 +40,178 @@ define Device/glinet_gl-ar300m-nand
KERNEL_SIZE := 2048k
BLOCKSIZE := 128k
PAGESIZE := 2048
- VID_HDR_OFFSET := 512
- IMAGES += factory.ubi
- IMAGE/sysupgrade.bin := sysupgrade-tar
- IMAGE/factory.ubi := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi
+ VID_HDR_OFFSET := 2048
+ IMAGES := factory.img sysupgrade.tar
+ IMAGE/sysupgrade.tar := sysupgrade-tar-compat-1806 | append-gl-metadata
+ IMAGE/factory.img := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi | append-gl-metadata
+ SUPPORTED_DEVICES += gl-ar300m glinet,gl-ar300m
+endef
+TARGET_DEVICES += glinet_gl-ar300m-nand
+
+define Device/glinet_gl-ar750
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-AR750
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k ath10k-firmware-qca9887 block-mount PCI_SUPPORT
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-ar750 glinet,gl-ar750
+endef
+TARGET_DEVICES += glinet_gl-ar750
+
+define Device/glinet_gl-ar750s-nor
+ ATH_SOC := qca9563
+ DEVICE_TITLE := GL.iNet GL-AR750S (NOR)
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k ath10k-firmware-qca9887 block-mount
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-ar750s glinet,gl-ar750s
+endef
+TARGET_DEVICES += glinet_gl-ar750s-nor
+
+define Device/glinet_gl-ar750s-nor-nand
+ ATH_SOC := qca9563
+ DEVICE_TITLE := GL.iNet GL-AR750S (NOR/NAND)
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k ath10k-firmware-qca9887 block-mount PCI_SUPPORT
+ KERNEL_SIZE := 2048k
+ BLOCKSIZE := 128k
+ PAGESIZE := 2048
+ VID_HDR_OFFSET := 2048
+ IMAGES := factory.img sysupgrade.tar
+ IMAGE/sysupgrade.tar := sysupgrade-tar | append-gl-metadata
+ IMAGE/factory.img := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi | append-gl-metadata
+ SUPPORTED_DEVICES += gl-ar750s glinet,gl-ar750s
+endef
+TARGET_DEVICES += glinet_gl-ar750s-nor-nand
+
+define Device/glinet_gl-e750-nor
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-E750 (NOR)
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k ath10k-firmware-qca9887 block-mount
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-e750 glinet,gl-e750
+endef
+TARGET_DEVICES += glinet_gl-e750-nor
+
+define Device/glinet_gl-e750-nor-nand
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-E750 (NOR/NAND)
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k ath10k-firmware-qca9887 block-mount PCI_SUPPORT
+ KERNEL_SIZE := 2048k
+ BLOCKSIZE := 128k
+ PAGESIZE := 2048
+ VID_HDR_OFFSET := 2048
+ IMAGES := factory.img sysupgrade.tar
+ IMAGE/sysupgrade.tar := sysupgrade-tar-compat-1806 | append-gl-metadata
+ IMAGE/factory.img := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi | append-gl-metadata
+ SUPPORTED_DEVICES += gl-e750 glinet,gl-e750
+endef
+TARGET_DEVICES += glinet_gl-e750-nor-nand
+
+define Device/glinet_gl-x750-nor
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-X750 (NOR)
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k ath10k-firmware-qca9887 block-mount
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-x750 glinet,gl-x750
+endef
+TARGET_DEVICES += glinet_gl-x750-nor
+
+define Device/glinet_gl-x750-nor-nand
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-X750 (NOR/NAND)
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k ath10k-firmware-qca9887 block-mount PCI_SUPPORT
+ KERNEL_SIZE := 2048k
+ BLOCKSIZE := 128k
+ PAGESIZE := 2048
+ VID_HDR_OFFSET := 2048
+ IMAGES := factory.img sysupgrade.tar
+ IMAGE/sysupgrade.tar := sysupgrade-tar | append-gl-metadata
+ IMAGE/factory.img := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi | append-gl-metadata
+ SUPPORTED_DEVICES += gl-x750 glinet,gl-x750
+endef
+TARGET_DEVICES += glinet_gl-x750-nor-nand
+
+define Device/glinet_gl-xe300-nor
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-XE300 (NOR)
+ DEVICE_PACKAGES := kmod-usb2 block-mount kmod-usb-serial-ch341
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-xe300 glinet,gl-xe300
+endef
+TARGET_DEVICES += glinet_gl-xe300-nor
+
+define Device/glinet_gl-xe300-nor-nand
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-XE300 (NOR/NAND)
+ DEVICE_PACKAGES := kmod-usb2 block-mount kmod-usb-serial-ch341
+ KERNEL_SIZE := 2048k
+ BLOCKSIZE := 128k
+ PAGESIZE := 2048
+ VID_HDR_OFFSET := 2048
+ IMAGES := factory.img sysupgrade.tar
+ IMAGE/sysupgrade.tar := sysupgrade-tar-compat-1806 | append-gl-metadata
+ IMAGE/factory.img := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi | append-gl-metadata
+ SUPPORTED_DEVICES += gl-xe300 glinet,gl-xe300
+endef
+TARGET_DEVICES += glinet_gl-xe300-nor-nand
+
+define Device/glinet_gl-xe300-iot
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-XE300 (NOR/NAND IOT)
+ DEVICE_PACKAGES := kmod-usb2 block-mount kmod-usb-serial-ch341
+ KERNEL_SIZE := 2048k
+ BLOCKSIZE := 128k
+ PAGESIZE := 2048
+ VID_HDR_OFFSET := 2048
+ IMAGES := factory.img sysupgrade.tar
+ IMAGE/sysupgrade.tar := sysupgrade-tar-compat-1806 | append-gl-metadata
+ IMAGE/factory.img := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi | append-gl-metadata
+ SUPPORTED_DEVICES += gl-xe300 glinet,gl-xe300
+endef
+TARGET_DEVICES += glinet_gl-xe300-iot
+
+define Device/glinet_gl-x300b-nor
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-X300B (NOR)
+ DEVICE_PACKAGES := kmod-usb2 block-mount
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-x300b glinet,gl-x300b
+endef
+TARGET_DEVICES += glinet_gl-x300b-nor
+
+define Device/glinet_gl-x300b-nor-nand
+ ATH_SOC := qca9531
+ DEVICE_TITLE := GL.iNet GL-X300B (NOR/NAND)
+ DEVICE_PACKAGES := kmod-usb2 block-mount
+ KERNEL_SIZE := 2048k
+ BLOCKSIZE := 128k
+ PAGESIZE := 2048
+ VID_HDR_OFFSET := 2048
+ IMAGES := factory.img sysupgrade.tar
+ IMAGE/sysupgrade.tar := sysupgrade-tar-compat-1806 | append-gl-metadata
+ IMAGE/factory.img := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi | append-gl-metadata
+ SUPPORTED_DEVICES += gl-x300b glinet,gl-x300b
+endef
+TARGET_DEVICES += glinet_gl-x300b-nor-nand
+
+define Device/glinet_gl-x1200-nor
+ ATH_SOC := qca9563
+ DEVICE_TITLE := GL.iNet GL-X1200 (NOR)
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k-ct ath10k-firmware-qca9887-ct-htt block-mount
+ IMAGE_SIZE := 16000k
+ SUPPORTED_DEVICES += gl-x1200 glinet,gl-x1200
+endef
+TARGET_DEVICES += glinet_gl-x1200-nor
+
+define Device/glinet_gl-x1200-nor-nand
+ ATH_SOC := qca9563
+ DEVICE_TITLE := GL.iNet GL-X1200 (NOR/NAND)
+ DEVICE_PACKAGES := kmod-usb2 kmod-ath10k-ct ath10k-firmware-qca9887-ct-htt block-mount PCI_SUPPORT
+ KERNEL_SIZE := 2048k
+ BLOCKSIZE := 128k
+ PAGESIZE := 2048
+ VID_HDR_OFFSET := 2048
+ IMAGES := factory.img sysupgrade.tar
+ IMAGE/sysupgrade.tar := sysupgrade-tar-compat-1806 | append-gl-metadata
+ IMAGE/factory.img := append-kernel | pad-to $$$$(KERNEL_SIZE) | append-ubi | append-gl-metadata
+ SUPPORTED_DEVICES += gl-x1200 glinet,gl-x1200
endef
-#TARGET_DEVICES += glinet_gl-ar300m-nand
+TARGET_DEVICES += glinet_gl-x1200-nor-nand
diff --git a/target/linux/ath79/nand/config-default b/target/linux/ath79/nand/config-default
index 738c29c9b1..d7921d11e3 100644
--- a/target/linux/ath79/nand/config-default
+++ b/target/linux/ath79/nand/config-default
@@ -7,8 +7,19 @@ CONFIG_MTD_SPINAND_MT29F=y
CONFIG_MTD_SPINAND_ONDIEECC=y
CONFIG_MTD_UBI=y
CONFIG_MTD_UBI_BEB_LIMIT=20
-# CONFIG_MTD_UBI_BLOCK is not set
+CONFIG_MTD_UBI_BLOCK=y
# CONFIG_MTD_UBI_FASTMAP is not set
# CONFIG_MTD_UBI_GLUEBI is not set
CONFIG_MTD_UBI_WL_THRESHOLD=4096
# CONFIG_UBIFS_FS is not set
+CONFIG_UBIFS_FS=y
+CONFIG_UBIFS_FS_ADVANCED_COMPR=y
+CONFIG_UBIFS_FS_LZO=y
+CONFIG_UBIFS_FS_ZLIB=y
+CONFIG_MTD_NAND_SPI_NAND=y
+CONFIG_PCI=y
+CONFIG_PCI_AR71XX=y
+CONFIG_PCI_AR724X=y
+CONFIG_PCI_DISABLE_COMMON_QUIRKS=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_HW_HAS_PCI=y
diff --git a/target/linux/ath79/patches-4.14/409-mtd-support-glinet-spinand.patch b/target/linux/ath79/patches-4.14/409-mtd-support-glinet-spinand.patch
new file mode 100644
index 0000000000..a47dd117d7
--- /dev/null
+++ b/target/linux/ath79/patches-4.14/409-mtd-support-glinet-spinand.patch
@@ -0,0 +1,3008 @@
+--- a/drivers/mtd/nand/Kconfig
++++ b/drivers/mtd/nand/Kconfig
+@@ -563,4 +563,12 @@ config MTD_NAND_MTK
+ Enables support for NAND controller on MTK SoCs.
+ This controller is found on mt27xx, mt81xx, mt65xx SoCs.
+
++config MTD_NAND_SPI_NAND
++ tristate "SPI Nand flash support"
++ default n
++ depends on MTD_NAND
++ help
++ Enables the driver for SPI NAND flash controller on Qualcomm-Atheros System on Chips
++ This controller is used on families AR71xx and AR9xxx.
++
+ endif # MTD_NAND
+--- a/drivers/mtd/nand/Makefile
++++ b/drivers/mtd/nand/Makefile
+@@ -60,6 +60,7 @@ obj-$(CONFIG_MTD_NAND_HISI504) +
+ obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/
+ obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o
+ obj-$(CONFIG_MTD_NAND_MTK) += mtk_nand.o mtk_ecc.o
++obj-$(CONFIG_MTD_NAND_SPI_NAND) += spinand/
+
+ nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o
+ nand-objs += nand_amd.o
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/Kconfig
+@@ -0,0 +1,7 @@
++menuconfig MTD_SPI_NAND
++ tristate "SPI NAND device Support"
++ select MTD_NAND_CORE
++ help
++ This is the framework for the SPI NAND device drivers.
++
++source "drivers/mtd/nand/spi/controllers/Kconfig"
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/Makefile
+@@ -0,0 +1 @@
++obj-$(CONFIG_MTD_NAND_SPI_NAND) += generic-spinand-controller.o core.o bbt.o nand_core.o micron.o etron.o gigadevice.o paragon.o mxic.o
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/bbt.c
+@@ -0,0 +1,79 @@
++/*
++ * Copyright (c) 2017 Free Electrons
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * Authors:
++ * Boris Brezillon <boris.brezillon@free-electrons.com>
++ * Peter Pan <peterpandong@micron.com>
++ */
++
++#define pr_fmt(fmt) "nand-bbt: " fmt
++
++#include <linux/mtd/rawnand.h>
++#include <linux/slab.h>
++#include "spinand.h"
++
++int nanddev_bbt_init(struct nand_device *nand)
++{
++ unsigned int nwords = nanddev_neraseblocks(nand);
++
++ nand->bbt.cache = kzalloc(nwords, GFP_KERNEL);
++ if (!nand->bbt.cache)
++ return -ENOMEM;
++ memset(nand->bbt.cache,0,nwords);
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_init);
++
++void nanddev_bbt_cleanup(struct nand_device *nand)
++{
++ kfree(nand->bbt.cache);
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_cleanup);
++
++int nanddev_bbt_update(struct nand_device *nand)
++{
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_update);
++
++int nanddev_bbt_get_block_status(const struct nand_device *nand,
++ unsigned int entry)
++{
++ unsigned char *pos = nand->bbt.cache + entry;
++ unsigned long status;
++
++ if (entry >= nanddev_neraseblocks(nand)){
++ return -ERANGE;
++ }
++
++ status = pos[0];
++
++
++ return status & 0xff;
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_get_block_status);
++
++int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry,
++ enum nand_bbt_block_status status)
++{
++ unsigned char *pos = nand->bbt.cache + entry;;
++
++ if (entry >= nanddev_neraseblocks(nand)){
++ return -ERANGE;
++ }
++
++ pos[0] = status & 0xff;
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_set_block_status);
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/core.c
+@@ -0,0 +1,921 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#define pr_fmt(fmt) "spi-nand: " fmt
++
++#include <linux/kernel.h>
++#include <linux/device.h>
++#include <linux/module.h>
++#include <linux/jiffies.h>
++#include "spinand.h"
++#include <linux/slab.h>
++#include <linux/of.h>
++static inline void spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ if (!spinand->manufacturer.manu->ops->adjust_cache_op)
++ return;
++
++ spinand->manufacturer.manu->ops->adjust_cache_op(spinand, req, op);
++}
++
++static inline int spinand_exec_op(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ return spinand->controller.controller->ops->exec_op(spinand, op);
++}
++
++static inline void spinand_op_init(struct spinand_op *op)
++{
++ memset(op, 0, sizeof(struct spinand_op));
++ op->addr_nbits = 1;
++ op->data_nbits = 1;
++}
++
++static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
++{
++ struct spinand_op op;
++ int ret;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_GET_FEATURE;
++ op.n_addr = 1;
++ op.addr[0] = reg;
++ op.n_rx = 1;
++ op.rx_buf = val;
++
++ ret = spinand_exec_op(spinand, &op);
++ if (ret < 0)
++ pr_err("failed to read register %d (err = %d)\n", reg, ret);
++
++ return ret;
++}
++
++static int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val)
++{
++ struct spinand_op op;
++ int ret;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_SET_FEATURE;
++ op.n_addr = 1;
++ op.addr[0] = reg;
++ op.n_tx = 1;
++ op.tx_buf = &val;
++
++ ret = spinand_exec_op(spinand, &op);
++ if (ret < 0)
++ pr_err("failed to write register %d (err = %d)\n", reg, ret);
++
++ return ret;
++}
++
++static int spinand_get_cfg(struct spinand_device *spinand, u8 *cfg)
++{
++ return spinand_read_reg_op(spinand, REG_CFG, cfg);
++}
++
++static int spinand_set_cfg(struct spinand_device *spinand, u8 cfg)
++{
++ return spinand_write_reg_op(spinand, REG_CFG, cfg);
++}
++
++static int spinand_read_status(struct spinand_device *spinand, u8 *status)
++{
++ return spinand_read_reg_op(spinand, REG_STATUS, status);
++}
++
++static void spinand_disable_ecc(struct spinand_device *spinand)
++{
++ u8 cfg = 0;
++
++ spinand_get_cfg(spinand, &cfg);
++
++ if ((cfg & CFG_ECC_MASK) == CFG_ECC_ENABLE) {
++ cfg &= ~CFG_ECC_ENABLE;
++ spinand_set_cfg(spinand, cfg);
++ }
++}
++
++static void spinand_enable_ecc(struct spinand_device *spinand)
++{
++ u8 cfg = 0;
++
++ spinand_get_cfg(spinand, &cfg);
++
++ if ((cfg & CFG_ECC_MASK) != CFG_ECC_ENABLE) {
++ cfg |= CFG_ECC_ENABLE;
++ spinand_set_cfg(spinand, cfg);
++ }
++}
++static int spinand_write_enable_op(struct spinand_device *spinand)
++{
++ struct spinand_op op;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_WR_ENABLE;
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_load_page_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = &spinand->base;
++ unsigned int row = nanddev_pos_to_offs(nand, &req->pos);
++ struct spinand_op op;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_PAGE_READ;
++ op.n_addr = 3;
++ unsigned int page = row /nand->memorg.pagesize;
++ unsigned int block = page /nand->memorg.pages_per_eraseblock;
++ op.addr[0] = block >> 10;
++ op.addr[1] = block >> 2;
++ op.addr[2] = ((block & 0x3)<<6)|(page & 0x3f);
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_get_address_bits(u8 opcode)
++{
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 4;
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ return 2;
++ default:
++ return 1;
++ }
++}
++
++static int spinand_get_data_bits(u8 opcode)
++{
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_PROG_LOAD_X4:
++ case SPINAND_CMD_PROG_LOAD_RDM_DATA_X4:
++ return 4;
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ return 2;
++ default:
++ return 1;
++ }
++}
++
++static int spinand_read_from_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = &spinand->base;
++ struct nand_page_io_req adjreq = *req;
++ struct spinand_op op;
++ u16 column = 0;
++ int ret;
++
++ spinand_op_init(&op);
++ op.cmd = spinand->read_cache_op;
++ op.n_addr =3;
++ op.addr_nbits = spinand_get_address_bits(spinand->read_cache_op);
++ if (req->datalen) {
++ adjreq.datalen = nanddev_page_size(nand);
++ adjreq.dataoffs = 0;
++ adjreq.databuf.in = spinand->buf;
++ op.rx_buf = spinand->buf;
++ op.n_rx = adjreq.datalen;
++ }
++
++ if (req->ooblen) {
++ adjreq.ooblen = nanddev_per_page_oobsize(nand);
++ adjreq.ooboffs = 0;
++ adjreq.oobbuf.in = spinand->oobbuf;
++ op.n_rx =nanddev_per_page_oobsize(nand);
++ if (!op.rx_buf) {
++ op.rx_buf = spinand->oobbuf;
++ column = nanddev_page_size(nand);
++ }
++ }
++
++ op.addr[0] =0 ;
++ op.addr[1] = column>>8;
++ op.addr[2] = column;
++ op.data_nbits = spinand_get_data_bits(spinand->read_cache_op);
++ spinand_adjust_cache_op(spinand, &adjreq, &op);
++
++ ret = spinand_exec_op(spinand, &op);
++ if (ret)
++ return ret;
++
++ if (req->datalen)
++ memcpy(req->databuf.in, spinand->buf + req->dataoffs,
++ req->datalen);
++
++ if (req->ooblen)
++ memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs,
++ req->ooblen);
++
++ return 0;
++}
++
++static int spinand_write_to_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = &spinand->base;
++ struct nand_page_io_req adjreq = *req;
++ struct spinand_op op;
++ u16 column = 0;
++
++ spinand_op_init(&op);
++ op.cmd = spinand->write_cache_op;
++ op.n_addr = 2;
++
++ memset(spinand->buf, 0xff,
++ nanddev_page_size(nand) +
++ nanddev_per_page_oobsize(nand));
++
++ if (req->datalen) {
++ memcpy(spinand->buf + req->dataoffs, req->databuf.out,
++ req->datalen);
++ adjreq.dataoffs = 0;
++ adjreq.datalen = nanddev_page_size(nand);
++ adjreq.databuf.out = spinand->buf;
++ op.tx_buf = spinand->buf;
++ op.n_tx = adjreq.datalen;
++ }
++
++ if (req->ooblen) {
++ memcpy(spinand->oobbuf + req->ooboffs, req->oobbuf.out,
++ req->ooblen);
++ memset(spinand->oobbuf,0x00,2);
++ adjreq.ooblen = nanddev_per_page_oobsize(nand);
++ adjreq.ooboffs = 0;
++ op.n_tx = nanddev_page_size(nand)+adjreq.ooblen;
++
++ if (!op.tx_buf) {
++ printk("oob write \n");
++ op.tx_buf = spinand->buf;
++ //column = nanddev_page_size(nand);
++ }
++ }
++
++ op.addr[0] = column >> 8;
++ op.addr[1] = column;
++
++ op.addr_nbits = spinand_get_address_bits(spinand->write_cache_op);
++ op.data_nbits = spinand_get_data_bits(spinand->write_cache_op);
++ spinand_adjust_cache_op(spinand, &adjreq, &op);
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_program_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int row = nanddev_pos_to_offs(nand, &req->pos);
++ struct spinand_op op;
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_PROG_EXC;
++ op.n_addr = 3;
++ unsigned int page = row /nand->memorg.pagesize;
++ unsigned int block = page /nand->memorg.pages_per_eraseblock;
++ op.addr[0] = block >> 10;
++ op.addr[1] = block >> 2;
++ op.addr[2] = ((block & 0x3)<<6)|(page & 0x3f);
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_erase_op(struct spinand_device *spinand,
++ const struct nand_pos *pos)
++{
++ struct nand_device *nand = &spinand->base;
++ unsigned int row = nanddev_pos_to_offs(nand, pos);
++ struct spinand_op op;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_BLK_ERASE;
++ op.n_addr = 3;
++ unsigned int page = row /nand->memorg.pagesize;
++ unsigned int block = page /nand->memorg.pages_per_eraseblock;
++ op.addr[0] = block >> 10;
++ op.addr[1] = block >> 2;
++ op.addr[2] = ((block & 0x3)<<6)|(page & 0x3f);
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_wait(struct spinand_device *spinand, u8 *s)
++{
++ unsigned long timeo = jiffies + msecs_to_jiffies(400);
++ u8 status;
++
++ do {
++ spinand_read_status(spinand, &status);
++ if ((status & STATUS_OIP_MASK) == STATUS_READY)
++ goto out;
++ } while (time_before(jiffies, timeo));
++
++ /*
++ * Extra read, just in case the STATUS_READY bit has changed
++ * since our last check
++ */
++ spinand_read_status(spinand, &status);
++out:
++ if (s)
++ *s = status;
++
++ return (status & STATUS_OIP_MASK) == STATUS_READY ? 0 : -ETIMEDOUT;
++}
++
++static int spinand_read_id_op(struct spinand_device *spinand, u8 *buf,char option)
++{
++ struct spinand_op op;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_READ_ID;
++ op.n_rx = SPINAND_MAX_ID_LEN;
++ op.rx_buf = buf;
++
++ if(option){
++ op.n_addr =1;
++ op.addr[0] =0;
++ }
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_reset_op(struct spinand_device *spinand)
++{
++ struct spinand_op op;
++ int ret;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_RESET;
++
++ ret = spinand_exec_op(spinand, &op);
++ if (ret < 0) {
++ pr_err("failed to reset the NAND (err = %d)\n", ret);
++ goto out;
++ }
++
++ ret = spinand_wait(spinand, NULL);
++
++out:
++ return ret;
++}
++
++static int spinand_lock_block(struct spinand_device *spinand, u8 lock)
++{
++ return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock);
++}
++
++static int spinand_read_page(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ int ret;
++
++ spinand_load_page_op(spinand, req);
++
++ ret = spinand_wait(spinand, NULL);
++ if (ret < 0) {
++ pr_err("failed to load page @%llx (err = %d)\n",
++ nanddev_pos_to_offs(nand, &req->pos), ret);
++ return ret;
++ }
++
++ spinand_read_from_cache_op(spinand, req);
++
++ return 0;
++}
++
++static int spinand_write_page(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ u8 status;
++ int ret = 0;
++
++ spinand_write_enable_op(spinand);
++ spinand_write_to_cache_op(spinand, req);
++ spinand_program_op(spinand, req);
++
++ ret = spinand_wait(spinand, &status);
++ if (!ret && (status & STATUS_P_FAIL_MASK) == STATUS_P_FAIL)
++ ret = -EIO;
++
++ if (ret < 0)
++ pr_err("failed to program page @%llx (err = %d)\n",
++ nanddev_pos_to_offs(nand, &req->pos), ret);
++
++ return ret;
++}
++
++static int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
++ struct mtd_oob_ops *ops)
++{
++ struct spinand_device *spinand = mtd_to_spinand(mtd);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct nand_io_iter iter;
++ int ret;
++
++ mutex_lock(&spinand->lock);
++ nanddev_io_for_each_page(nand, from, ops, &iter) {
++ ret = spinand_read_page(spinand, &iter.req);
++ if (ret)
++ break;
++
++ ops->retlen += iter.req.datalen;
++ ops->oobretlen += iter.req.datalen;
++ }
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
++ struct mtd_oob_ops *ops)
++{
++ struct spinand_device *spinand = mtd_to_spinand(mtd);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct nand_io_iter iter;
++ int ret = 0;
++ mutex_lock(&spinand->lock);
++ nanddev_io_for_each_page(nand, to, ops, &iter) {
++ ret = spinand_write_page(spinand, &iter.req);
++ if (ret)
++ return ret;
++
++ ops->retlen += iter.req.datalen;
++ ops->oobretlen += iter.req.ooblen;
++ }
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos)
++{
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ struct nand_page_io_req req = {
++ .pos = *pos,
++ .ooblen = 2,
++ .ooboffs = 0,
++ .oobbuf.in = spinand->oobbuf,
++ };
++
++ memset(spinand->oobbuf, 0x00, 2);
++ spinand_read_page(spinand, &req);
++ if (spinand->oobbuf[0] != 0xff || spinand->oobbuf[1] != 0xff)
++ return true;
++
++ return false;
++}
++
++static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ struct nand_pos pos;
++ int ret;
++ nanddev_offs_to_pos(nand, offs, &pos);
++ mutex_lock(&spinand->lock);
++ ret = spinand_isbad(nand, &pos);
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos)
++{
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ struct nand_page_io_req req = {
++ .pos = *pos,
++ .ooboffs = 0,
++ .ooblen = 2,
++ .oobbuf.out = spinand->oobbuf,
++ };
++
++ /* Erase block before marking it bad. */
++ spinand_write_enable_op(spinand);
++ spinand_erase_op(spinand, pos);
++ u8 status;
++ spinand_wait(spinand, &status);
++
++ memset(spinand->oobbuf, 0x00, 2);
++ return spinand_write_page(spinand, &req);
++}
++
++
++static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ struct nand_pos pos;
++ int ret;
++ nanddev_offs_to_pos(nand, offs, &pos);
++ /*bad block mark the first page*/
++ pos.page=0;
++
++ mutex_lock(&spinand->lock);
++ ret = nanddev_markbad(nand, &pos);
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static int spinand_erase(struct nand_device *nand, const struct nand_pos *pos)
++{
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ u8 status;
++ int ret;
++
++ spinand_write_enable_op(spinand);
++ spinand_erase_op(spinand, pos);
++
++ ret = spinand_wait(spinand, &status);
++
++ if (!ret && (status & STATUS_E_FAIL_MASK) == STATUS_E_FAIL)
++ ret = -EIO;
++
++ if (ret)
++ pr_err("failed to erase block %d (err = %d)\n",
++ pos->eraseblock, ret);
++
++ return ret;
++}
++
++static int spinand_mtd_erase(struct mtd_info *mtd,
++ struct erase_info *einfo)
++{
++ struct spinand_device *spinand = mtd_to_spinand(mtd);
++ int ret;
++// printk("erase block\n");
++ mutex_lock(&spinand->lock);
++ ret = nanddev_mtd_erase(mtd, einfo);
++ mutex_unlock(&spinand->lock);
++
++ //if (!ret)
++ // mtd_erase_callback(einfo);
++
++ return ret;
++}
++
++static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
++{
++ struct spinand_device *spinand = mtd_to_spinand(mtd);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct nand_pos pos;
++ int ret;
++
++ nanddev_offs_to_pos(nand, offs, &pos);
++ mutex_lock(&spinand->lock);
++ ret = nanddev_isreserved(nand, &pos);
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static void spinand_set_rd_wr_op(struct spinand_device *spinand)
++{
++ u32 controller_cap = spinand->controller.controller->caps;
++ u32 rw_mode = spinand->rw_mode;
++
++ if ((controller_cap & SPINAND_CAP_RD_QUAD) &&
++ (rw_mode & SPINAND_RD_QUAD))
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_QUAD_IO;
++ else if ((controller_cap & SPINAND_CAP_RD_X4) &&
++ (rw_mode & SPINAND_RD_X4))
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_X4;
++ else if ((controller_cap & SPINAND_CAP_RD_DUAL) &&
++ (rw_mode & SPINAND_RD_DUAL))
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_DUAL_IO;
++ else if ((controller_cap & SPINAND_CAP_RD_X2) &&
++ (rw_mode & SPINAND_RD_X2))
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_X2;
++ else
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_FAST;
++
++ if ((controller_cap & SPINAND_CAP_WR_X4) &&
++ (rw_mode & SPINAND_WR_X4))
++ spinand->write_cache_op = SPINAND_CMD_PROG_LOAD_X4;
++ else
++ spinand->write_cache_op = SPINAND_CMD_PROG_LOAD;
++}
++
++static const struct nand_ops spinand_ops = {
++ .erase = spinand_erase,
++ .markbad = spinand_markbad,
++ .isbad = spinand_isbad,
++};
++
++static const struct spinand_manufacturer *spinand_manufacturers[] = {
++ &micron_spinand_manufacturer,
++ &etron_spinand_manufacturer,
++ &giga_spinand_manufacturer,
++ &paragon_spinand_manufacturer,
++ &mxic_spinand_manufacturer,
++};
++
++
++static int spinand_manufacturer_detect(struct spinand_device *spinand)
++{
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(spinand_manufacturers); i++) {
++ if (spinand_manufacturers[i]->ops->detect(spinand)) {
++ spinand->manufacturer.manu = spinand_manufacturers[i];
++
++ return 0;
++ }
++ }
++
++ return -ENODEV;
++}
++
++static int spinand_manufacturer_init(struct spinand_device *spinand)
++{
++ if (spinand->manufacturer.manu->ops->init)
++ return spinand->manufacturer.manu->ops->init(spinand);
++
++ return 0;
++}
++
++static void spinand_manufacturer_cleanup(struct spinand_device *spinand)
++{
++ /* Release manufacturer private data */
++ if (spinand->manufacturer.manu->ops->cleanup)
++ return spinand->manufacturer.manu->ops->cleanup(spinand);
++}
++static int spinand_detect(struct spinand_device *spinand)
++{
++ struct nand_device *nand = &spinand->base;
++ int ret=-1;
++ char option=0;
++
++ spinand_reset_op(spinand);
++ spinand->id.len = SPINAND_MAX_ID_LEN;
++ for(option=0;option<2;option++){
++ spinand_read_id_op(spinand, spinand->id.data,option);
++ ret = spinand_manufacturer_detect(spinand);
++ if(ret==0)
++ break;
++ }
++ if (ret) {
++ pr_err("unknown raw ID %*phN\n",
++ SPINAND_MAX_ID_LEN, spinand->id.data);
++ return ret;
++ }
++
++ pr_info("%s SPI NAND was found.\n", spinand->manufacturer.manu->name);
++ pr_info("%d MiB, block size: %d KiB, page size: %d, OOB size: %d\n",
++ (int)(nanddev_size(nand) >> 20),
++ nanddev_eraseblock_size(nand) >> 10,
++ nanddev_page_size(nand), nanddev_per_page_oobsize(nand));
++ return 0;
++}
++/**
++ * devm_spinand_alloc - [SPI NAND Interface] allocate SPI NAND device instance
++ * @dev: pointer to device model structure
++ */
++struct spinand_device *devm_spinand_alloc(struct device *dev)
++{
++ struct spinand_device *spinand;
++ struct mtd_info *mtd;
++
++ spinand = devm_kzalloc(dev, sizeof(*spinand), GFP_KERNEL);
++ if (!spinand)
++ return ERR_PTR(-ENOMEM);
++
++ spinand_set_of_node(spinand, dev->of_node);
++ mutex_init(&spinand->lock);
++ mtd = spinand_to_mtd(spinand);
++ mtd->dev.parent = dev;
++
++ return spinand;
++}
++EXPORT_SYMBOL_GPL(devm_spinand_alloc);
++static int spinand_read(struct mtd_info *mtd, loff_t from, size_t len,size_t *retlen, u_char *buf)
++{
++ int ret;
++ struct mtd_oob_ops ops = {
++ .len = len,
++ .datbuf = buf,
++ };
++ ret = mtd->_read_oob(mtd, from, &ops);
++ *retlen = ops.retlen;
++
++ if (unlikely(ret < 0))
++ return ret;
++ if (mtd->ecc_strength == 0)
++ return 0; /* device lacks ecc */
++ return ret >= mtd->bitflip_threshold ? -EUCLEAN : 0;
++}
++
++static int spinand_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,const u_char *buf)
++{
++ struct mtd_oob_ops ops = {
++ .len = len,
++ .datbuf = (u8 *)buf,
++ };
++ int ret;
++
++ ret = mtd->_write_oob(mtd, to, &ops);
++ *retlen = ops.retlen;
++ return ret;
++
++}
++
++int spinand_bbt_create(struct nand_device *nand )
++{
++ unsigned int block=0;
++ unsigned int entry=0;
++ int status=NAND_BBT_BLOCK_STATUS_UNKNOWN;
++ int ret = 0;
++ struct nand_pos pos;
++ struct mtd_info *mtd = nanddev_to_mtd(nand);
++ if (nanddev_bbt_is_initialized(nand)) {
++ for(block=0;block < nand->memorg.eraseblocks_per_lun;block++){
++ pos.eraseblock=block;
++ pos.lun=0;
++ pos.page=0;
++ pos.plane=0;
++ pos.target=0;
++ entry = nanddev_bbt_pos_to_entry(nand, &pos);
++ if(nand->ops->isbad(nand, &pos)){
++ printk("found bad block %llx\n",nanddev_pos_to_offs(nand,&pos));
++ ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_FACTORY_BAD);
++ ret = nanddev_bbt_update(nand);
++ mtd->ecc_stats.badblocks++;
++ }
++ else{
++ nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_GOOD);
++ }
++ }
++
++ }
++ return 0;
++
++}
++int write_test(struct mtd_info *mtd,loff_t to,size_t len)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ size_t retlen;
++ unsigned char *buf;
++ int i=0;
++
++ buf = kzalloc(nanddev_page_size(nand) +
++ nanddev_per_page_oobsize(nand),
++ GFP_KERNEL);
++ for(i=0;i<len;i++){
++ buf[i]=i%16;
++ }
++ spinand_write(mtd,to,len,&retlen,buf);
++ kfree(buf);
++ return 0;
++}
++int erase_test(struct mtd_info *mtd,uint64_t from,uint64_t len)
++{
++ struct erase_info einfo={
++ .mtd=mtd,
++ .addr=from,
++ .len=len,
++ .callback = NULL,
++ };
++
++ spinand_mtd_erase(mtd,&einfo);
++ return 0;
++}
++int read_test(struct mtd_info *mtd,loff_t from,size_t len)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ size_t retlen;
++ unsigned char *buf;
++ int i=0;
++ char en=16;
++ buf = kzalloc(nanddev_page_size(nand) +
++ nanddev_per_page_oobsize(nand),
++ GFP_KERNEL);
++ spinand_read(mtd,from,len,&retlen,buf);
++ for(i=0;i<len;i=i+en){
++ if(en==16){
++ printk("%2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X\n",\
++ buf[i],buf[i+1],buf[i+2],buf[i+3],buf[i+4],buf[i+5],buf[i+6],buf[i+7],buf[i+8],buf[i+9],\
++ buf[i+10],buf[i|11],buf[i+12],buf[i+13],buf[i+14],buf[i+15]);
++ }
++ else{
++ printk("%2X %2X %2X %2X %2X %2X %2X %2X\n",\
++ buf[i],buf[i+1],buf[i+2],buf[i+3],buf[i+4],buf[i+5],buf[i+6],buf[i+7]);
++ }
++ if(i==2032){
++ i=i+8;
++ en=8;
++ }
++ }
++ kfree(buf);
++ return 0;
++}
++
++int mark_bad_test(struct mtd_info *mtd,loff_t offs)
++{
++ return spinand_mtd_block_markbad(mtd,offs);
++}
++/**
++ * spinand_init - [SPI NAND Interface] initialize the SPI NAND device
++ * @spinand: SPI NAND device structure
++ */
++int spinand_init(struct spinand_device *spinand, struct module *owner)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ int ret;
++
++ ret = spinand_detect(spinand);
++ if (ret) {
++ pr_err("Failed to detect a SPI NAND (err = %d).\n", ret);
++ return ret;
++ }
++
++ ret = nanddev_init(nand, &spinand_ops, owner);
++ if (ret)
++ return ret;
++
++ spinand_set_rd_wr_op(spinand);
++
++ /*
++ * Use kzalloc() instead of devm_kzalloc() here, because some drivers
++ * may use this buffer for DMA access.
++ * Memory allocated by devm_ does not guarantee DMA-safe alignment.
++ */
++ spinand->buf = kzalloc(nanddev_page_size(nand) +
++ nanddev_per_page_oobsize(nand),
++ GFP_KERNEL);
++ if (!spinand->buf)
++ return -ENOMEM;
++
++ spinand->oobbuf = spinand->buf + nanddev_page_size(nand);
++
++ ret = spinand_manufacturer_init(spinand);
++ if (ret) {
++ pr_err("Init of SPI NAND failed (err = %d).\n", ret);
++ goto err_free_buf;
++ }
++
++ /*
++ * Right now, we don't support ECC, so let the whole oob
++ * area is available for user.
++ */
++ mtd->_read_oob = spinand_mtd_read;
++ mtd->_write_oob = spinand_mtd_write;
++ mtd->_block_isbad = spinand_mtd_block_isbad;
++ mtd->_block_markbad = spinand_mtd_block_markbad;
++ mtd->_block_isreserved = spinand_mtd_block_isreserved;
++ mtd->_erase = spinand_mtd_erase;
++ mtd->_read = spinand_read;
++ mtd->_write = spinand_write;
++
++ /* After power up, all blocks are locked, so unlock it here. */
++ spinand_lock_block(spinand, BL_ALL_UNLOCKED);
++ /* Right now, we don't support ECC, so disable on-die ECC */
++ //spinand_disable_ecc(spinand);
++ spinand_enable_ecc(spinand);
++
++ return 0;
++
++err_free_buf:
++ kfree(spinand->buf);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(spinand_init);
++/**
++ * spinand_cleanup - clean SPI NAND device
++ * @spinand: SPI NAND device structure
++ */
++void spinand_cleanup(struct spinand_device *spinand)
++{
++ struct nand_device *nand = &spinand->base;
++
++ spinand_manufacturer_cleanup(spinand);
++ kfree(spinand->buf);
++ nanddev_cleanup(nand);
++}
++EXPORT_SYMBOL_GPL(spinand_cleanup);
++
++MODULE_DESCRIPTION("SPI NAND framework");
++MODULE_AUTHOR("Peter Pan<peterpandong@micron.com>");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/etron.c
+@@ -0,0 +1,156 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_ETRON 0xD5
++
++struct etron_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define ETRON_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct etron_spinand_info etron_spinand_table[] = {
++ ETRON_SPI_NAND_INFO("ETNORxxxx", 0x11,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int etron_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 2;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * etron_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool etron_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct etron_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(etron_spinand_table); i++) {
++ item = (struct etron_spinand_info *)etron_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * etron_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool etron_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if (id[0] != SPINAND_MFR_ETRON)
++ return false;
++
++ return etron_spinand_scan_id_table(spinand, id[1]);
++}
++
++/**
++ * etron_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void etron_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++
++ /*
++ * No need to specify the plane number if there's only one plane per
++ * LUN.
++ */
++ /*if (nand->memorg.planes_per_lun < 2)
++ return;*/
++
++ /* The plane number is passed in MSB just above the column address */
++ //shift = fls(nand->memorg.pagesize);
++ /*op->n_addr= 2;
++ op->addr[0] = op->addr[1];
++ op->addr[1] = op->addr[2];
++ op->addr[2] = 0;*/
++ op->dummy_bytes = etron_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops etron_spinand_manuf_ops = {
++ .detect = etron_spinand_detect,
++ .adjust_cache_op = etron_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer etron_spinand_manufacturer = {
++ .id = SPINAND_MFR_ETRON,
++ .name = "Etron",
++ .ops = &etron_spinand_manuf_ops,
++};
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/generic-spinand-controller.c
+@@ -0,0 +1,187 @@
++/*
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/spi/spi.h>
++#include "spinand.h"
++#include <linux/mtd/mtd.h>
++
++struct gen_spinand_controller {
++ struct spinand_controller ctrl;
++ struct spi_device *spi;
++};
++
++#define to_gen_spinand_controller(c) \
++ container_of(c, struct gen_spinand_controller, ctrl)
++
++/*
++ * gen_spinand_controller_exec_op - to process a command to send to the
++ * SPI NAND by generic SPI bus
++ * @spinand: SPI NAND device structure
++ * @op: SPI NAND operation descriptor
++ */
++static int gen_spinand_controller_exec_op(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ struct spi_message message;
++ struct spi_transfer x[3];
++ struct spinand_controller *spinand_controller;
++ struct gen_spinand_controller *controller;
++
++ spinand_controller = spinand->controller.controller;
++ controller = to_gen_spinand_controller(spinand_controller);
++ spi_message_init(&message);
++ memset(x, 0, sizeof(x));
++ x[0].len = 1;
++ x[0].tx_nbits = 1;
++ x[0].tx_buf = &op->cmd;
++ spi_message_add_tail(&x[0], &message);
++
++ if (op->n_addr + op->dummy_bytes) {
++ x[1].len = op->n_addr + op->dummy_bytes;
++ x[1].tx_nbits = op->addr_nbits;
++ x[1].tx_buf = op->addr;
++ //printk("cmd:%2X,naddr:%d,[%2X][%2X][%2X]\n",op->cmd,op->n_addr,op->addr[0],op->addr[1],op->addr[2]);
++ spi_message_add_tail(&x[1], &message);
++ }
++
++ if (op->n_tx) {
++ x[2].len = op->n_tx;
++ x[2].tx_nbits = op->data_nbits;
++ x[2].tx_buf = op->tx_buf;
++ spi_message_add_tail(&x[2], &message);
++ } else if (op->n_rx) {
++ x[2].len = op->n_rx;
++ x[2].rx_nbits = op->data_nbits;
++ x[2].rx_buf = op->rx_buf;
++ spi_message_add_tail(&x[2], &message);
++ }
++
++ return spi_sync(controller->spi, &message);
++}
++
++static struct spinand_controller_ops gen_spinand_controller_ops = {
++ .exec_op = gen_spinand_controller_exec_op,
++};
++extern int read_test(struct mtd_info *mtd,loff_t from,size_t len);
++extern int erase_test(struct mtd_info *mtd,uint64_t from,uint64_t len);
++extern int write_test(struct mtd_info *mtd,loff_t to,size_t len);
++extern int spinand_bbt_create(struct nand_device *nand );
++extern int mark_bad_test(struct mtd_info *mtd,loff_t offs);
++static int gen_spinand_controller_probe(struct spi_device *spi)
++{
++ struct spinand_device *spinand;
++ struct gen_spinand_controller *controller;
++ struct spinand_controller *spinand_controller;
++ struct device *dev = &spi->dev;
++ u16 mode = spi->mode;
++ int ret;
++
++ spinand = devm_spinand_alloc(dev);
++ if (IS_ERR(spinand)) {
++ ret = PTR_ERR(spinand);
++ goto out;
++ }
++
++ controller = devm_kzalloc(dev, sizeof(*controller), GFP_KERNEL);
++ if (!controller) {
++ ret = -ENOMEM;
++ goto out;
++ }
++
++ controller->spi = spi;
++ spinand_controller = &controller->ctrl;
++ spinand_controller->ops = &gen_spinand_controller_ops;
++ spinand_controller->caps = SPINAND_CAP_RD_X1 | SPINAND_CAP_WR_X1;
++
++ if ((mode & SPI_RX_QUAD) && (mode & SPI_TX_QUAD))
++ spinand_controller->caps |= SPINAND_CAP_RD_QUAD;
++
++ if ((mode & SPI_RX_DUAL) && (mode & SPI_TX_DUAL))
++ spinand_controller->caps |= SPINAND_CAP_RD_DUAL;
++
++ if (mode & SPI_RX_QUAD)
++ spinand_controller->caps |= SPINAND_CAP_RD_X4;
++
++ if (mode & SPI_RX_DUAL)
++ spinand_controller->caps |= SPINAND_CAP_RD_X2;
++
++ if (mode & SPI_TX_QUAD)
++ spinand_controller->caps |= SPINAND_CAP_WR_QUAD |
++ SPINAND_CAP_WR_X4;
++
++ if (mode & SPI_TX_DUAL)
++ spinand_controller->caps |= SPINAND_CAP_WR_DUAL |
++ SPINAND_CAP_WR_X2;
++
++ spinand->controller.controller = spinand_controller;
++ spi_set_drvdata(spi, spinand);
++
++ ret = spinand_init(spinand, THIS_MODULE);
++ if (ret)
++ goto out;
++
++ ret = mtd_device_register(spinand_to_mtd(spinand), NULL, 0);
++ struct nand_device *nand =spinand_to_nand(spinand);
++ spinand_bbt_create(nand);
++ //mark_bad_test(spinand_to_mtd(spinand),0x00);
++ /*
++ int i=0,status=0;
++ unsigned int entry=0;
++ struct nand_pos pos;
++ for(i=0;i<1024;i++){
++
++ erase_test(spinand_to_mtd(spinand),i*0x20000,0x20000);
++ }*/
++ //erase_test(spinand_to_mtd(spinand),0x00,0x20000);
++ //write_test(spinand_to_mtd(spinand),0x000,2048);
++ //read_test(spinand_to_mtd(spinand),0x000,2048);
++ //mark_bad_test(spinand_to_mtd(spinand),0);
++out:
++ return ret;
++}
++
++static int gen_spinand_controller_remove(struct spi_device *spi)
++{
++ struct spinand_device *spinand = spi_get_drvdata(spi);
++ int ret;
++
++ ret = mtd_device_unregister(spinand_to_mtd(spinand));
++ if (ret)
++ return ret;
++
++ spinand_cleanup(spinand);
++
++ return 0;
++}
++
++static const struct of_device_id spinand_glinet_dt[] = {
++ { .compatible = "spinand,glinet", },
++ {}
++};
++
++static struct spi_driver gen_spinand_controller_driver = {
++ .driver = {
++ .name = "generic-spinand-controller",
++ .of_match_table = spinand_glinet_dt,
++ .owner = THIS_MODULE,
++ },
++ .probe = gen_spinand_controller_probe,
++ .remove = gen_spinand_controller_remove,
++};
++module_spi_driver(gen_spinand_controller_driver);
++
++MODULE_DESCRIPTION("Generic SPI NAND controller");
++MODULE_AUTHOR("Peter Pan <peterpandong@micron.com>");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/gigadevice.c
+@@ -0,0 +1,161 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_GIGA 0xC8
++
++struct giga_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define GIGA_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct giga_spinand_info giga_spinand_table[] = {
++ GIGA_SPI_NAND_INFO("GD5F1GQ4UF", 0xB1,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++ GIGA_SPI_NAND_INFO("GD5F1GQ4UE", 0xD1,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++ GIGA_SPI_NAND_INFO("GD5F1GQ5UE", 0x51,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int giga_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 2;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * giga_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool giga_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct giga_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(giga_spinand_table); i++) {
++ item = (struct giga_spinand_info *)giga_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * giga_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool giga_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if ((id[0] != SPINAND_MFR_GIGA) && (id[1] != SPINAND_MFR_GIGA))
++ return false;
++
++ /*if(id[1] == SPINAND_MFR_GIGA){
++ return giga_spinand_scan_id_table(spinand, id[2]);
++ }*/
++
++ return giga_spinand_scan_id_table(spinand, id[1]);
++}
++
++/**
++ * giga_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void giga_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++
++ //for GD5F1GQ4XE and GD5F1GQ5XE,the dummy byte position at byte3 when read from cache
++ if(((spinand->id.data[1] == 0xd1) || (spinand->id.data[1] == 0x51)) && (op->cmd == spinand->read_cache_op)){
++ op->n_addr= 2;
++ op->addr[0] = op->addr[1];
++ op->addr[1] = op->addr[2];
++ op->addr[2] = 0;
++ }
++ op->dummy_bytes = giga_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops giga_spinand_manuf_ops = {
++ .detect = giga_spinand_detect,
++ .adjust_cache_op = giga_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer giga_spinand_manufacturer = {
++ .id = SPINAND_MFR_GIGA,
++ .name = "Giga",
++ .ops = &giga_spinand_manuf_ops,
++};
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/micron.c
+@@ -0,0 +1,153 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_MICRON 0x2C
++
++struct micron_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define MICRON_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct micron_spinand_info micron_spinand_table[] = {
++ MICRON_SPI_NAND_INFO("MT29F2G01ABAGD", 0x24,
++ NAND_MEMORG(1, 2048, 128, 64, 2048, 2, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int micron_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 2;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * micron_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool micron_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct micron_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(micron_spinand_table); i++) {
++ item = (struct micron_spinand_info *)micron_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * micron_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool micron_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if (id[1] != SPINAND_MFR_MICRON)
++ return false;
++
++ return micron_spinand_scan_id_table(spinand, id[2]);
++}
++
++/**
++ * micron_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void micron_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++
++ /*
++ * No need to specify the plane number if there's only one plane per
++ * LUN.
++ */
++ if (nand->memorg.planes_per_lun < 2)
++ return;
++
++ /* The plane number is passed in MSB just above the column address */
++ shift = fls(nand->memorg.pagesize);
++ op->addr[(16 - shift) / 8] |= req->pos.plane << (shift % 8);
++ op->dummy_bytes = micron_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = {
++ .detect = micron_spinand_detect,
++ .adjust_cache_op = micron_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer micron_spinand_manufacturer = {
++ .id = SPINAND_MFR_MICRON,
++ .name = "Micron",
++ .ops = &micron_spinand_manuf_ops,
++};
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/nand_core.c
+@@ -0,0 +1,213 @@
++/*
++ * Copyright (c) 2017 Free Electrons
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * Authors:
++ * Boris Brezillon <boris.brezillon@free-electrons.com>
++ * Peter Pan <peterpandong@micron.com>
++ */
++
++#define pr_fmt(fmt) "nand: " fmt
++
++#include <linux/mtd/rawnand.h>
++#include "spinand.h"
++
++bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos)
++{
++#if 1
++ if (nanddev_bbt_is_initialized(nand)) {
++ unsigned int entry=0;
++ int status=0;
++
++ entry = nanddev_bbt_pos_to_entry(nand, pos);
++ status = nanddev_bbt_get_block_status(nand, entry);
++ /* Lazy block status retrieval */
++ if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) {
++ if (nand->ops->isbad(nand, pos))
++ status = NAND_BBT_BLOCK_FACTORY_BAD;
++ else
++ status = NAND_BBT_BLOCK_GOOD;
++
++ nanddev_bbt_set_block_status(nand, entry, status);
++ }
++ //printk("status %llx,%x\n",nanddev_pos_to_offs(nand, pos),status);
++ if (status == NAND_BBT_BLOCK_WORN ||
++ status == NAND_BBT_BLOCK_FACTORY_BAD)
++ return true;
++
++ return false;
++ }
++#endif
++ return nand->ops->isbad(nand, pos);
++}
++EXPORT_SYMBOL_GPL(nanddev_isbad);
++
++/**
++ * nanddev_markbad - Write a bad block marker to a block
++ * @nand: NAND device
++ * @block: block to mark bad
++ *
++ * Mark a block bad. This function is updating the BBT if available and
++ * calls the low-level markbad hook (nand->ops->markbad()) if
++ * NAND_BBT_NO_OOB_BBM is not set.
++ *
++ * Return: 0 in case of success, a negative error code otherwise.
++ */
++int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos)
++{
++ struct mtd_info *mtd = nanddev_to_mtd(nand);
++ unsigned int entry;
++ int ret = 0;
++ if (nanddev_isbad(nand, pos))
++ return 0;
++
++ ret = nand->ops->markbad(nand, pos);
++ if (ret)
++ pr_warn("failed to write BBM to block @%llx (err = %d)\n",
++ nanddev_pos_to_offs(nand, pos), ret);
++
++ if (!nanddev_bbt_is_initialized(nand))
++ goto out;
++
++ entry = nanddev_bbt_pos_to_entry(nand, pos);
++ ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN);
++ if (ret)
++ goto out;
++
++ ret = nanddev_bbt_update(nand);
++
++out:
++ if (!ret)
++ mtd->ecc_stats.badblocks++;
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(nanddev_markbad);
++
++bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos)
++{
++ unsigned int entry;
++ int status;
++
++ if (!nanddev_bbt_is_initialized(nand))
++ return false;
++
++ /* Return info from the table */
++ entry = nanddev_bbt_pos_to_entry(nand, pos);
++ status = nanddev_bbt_get_block_status(nand, entry);
++ return status == NAND_BBT_BLOCK_RESERVED;
++}
++EXPORT_SYMBOL_GPL(nanddev_isreserved);
++
++/**
++ * nanddev_erase - Erase a NAND portion
++ * @nand: NAND device
++ * @block: eraseblock to erase
++ *
++ * Erase @block block if it's not bad.
++ *
++ * Return: 0 in case of success, a negative error code otherwise.
++ */
++
++int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
++{
++ if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) {
++ //pr_warn("attempt to erase a bad/reserved block @%llx\n",
++ // nanddev_pos_to_offs(nand, pos));
++ return -EIO;
++ }
++
++ return nand->ops->erase(nand, pos);
++}
++EXPORT_SYMBOL_GPL(nanddev_erase);
++
++int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct nand_pos pos, last;
++ int ret;
++
++ nanddev_offs_to_pos(nand, einfo->addr, &pos);
++ nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last);
++ while (nanddev_pos_cmp(&pos, &last) <= 0) {
++ ret = nanddev_erase(nand, &pos);
++ if (ret) {
++ einfo->fail_addr = nanddev_pos_to_offs(nand, &pos);
++ einfo->state = MTD_ERASE_FAILED;
++ //printk("erase failed ....\n");
++ return ret;
++ }
++
++ nanddev_pos_next_eraseblock(nand, &pos);
++ }
++
++ einfo->state = MTD_ERASE_DONE;
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nanddev_mtd_erase);
++
++/**
++ * nanddev_init - Initialize a NAND device
++ * @nand: NAND device
++ * @memorg: NAND memory organization descriptor
++ * @ops: NAND device operations
++ *
++ * Initialize a NAND device object. Consistency checks are done on @memorg and
++ * @ops.
++ *
++ * Return: 0 in case of success, a negative error code otherwise.
++ */
++int nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
++ struct module *owner)
++{
++ struct mtd_info *mtd = nanddev_to_mtd(nand);
++ struct nand_memory_organization *memorg = nanddev_get_memorg(nand);
++
++ if (!nand || !ops)
++ return -EINVAL;
++
++ if (!ops->erase || !ops->markbad || !ops->isbad)
++ return -EINVAL;
++
++ if (!memorg->bits_per_cell || !memorg->pagesize ||
++ !memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun ||
++ !memorg->planes_per_lun || !memorg->luns_per_target ||
++ !memorg->ntargets)
++ return -EINVAL;
++
++ nand->rowconv.eraseblock_addr_shift = fls(memorg->pagesize);
++ nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun) +
++ nand->rowconv.eraseblock_addr_shift;
++
++ nand->ops = ops;
++
++ mtd->type = memorg->bits_per_cell == 1 ?
++ MTD_NANDFLASH : MTD_MLCNANDFLASH;
++ mtd->flags = MTD_CAP_NANDFLASH;
++ mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock;
++ mtd->writesize = memorg->pagesize;
++ mtd->oobsize = memorg->oobsize;
++ mtd->writebufsize = memorg->pagesize;
++ mtd->size = nanddev_size(nand);
++ mtd->owner = owner;
++
++ return nanddev_bbt_init(nand);
++}
++EXPORT_SYMBOL_GPL(nanddev_init);
++
++void nanddev_cleanup(struct nand_device *nand)
++{
++ if (nanddev_bbt_is_initialized(nand))
++ nanddev_bbt_cleanup(nand);
++}
++EXPORT_SYMBOL_GPL(nanddev_cleanup);
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/spinand.h
+@@ -0,0 +1,765 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++#ifndef __LINUX_MTD_SPINAND_H
++#define __LINUX_MTD_SPINAND_H
++
++#include <linux/mutex.h>
++#include <linux/bitops.h>
++#include <linux/device.h>
++#include <linux/mtd/mtd.h>
++#include <linux/mtd/rawnand.h>
++#include <linux/of.h>
++
++/**
++ * Standard SPI NAND flash commands
++ */
++#define SPINAND_CMD_RESET 0xff
++#define SPINAND_CMD_GET_FEATURE 0x0f
++#define SPINAND_CMD_SET_FEATURE 0x1f
++#define SPINAND_CMD_PAGE_READ 0x13
++#define SPINAND_CMD_READ_FROM_CACHE 0x03
++#define SPINAND_CMD_READ_FROM_CACHE_FAST 0x0b
++#define SPINAND_CMD_READ_FROM_CACHE_X2 0x3b
++#define SPINAND_CMD_READ_FROM_CACHE_DUAL_IO 0xbb
++#define SPINAND_CMD_READ_FROM_CACHE_X4 0x6b
++#define SPINAND_CMD_READ_FROM_CACHE_QUAD_IO 0xeb
++#define SPINAND_CMD_BLK_ERASE 0xd8
++#define SPINAND_CMD_PROG_EXC 0x10
++#define SPINAND_CMD_PROG_LOAD 0x02
++#define SPINAND_CMD_PROG_LOAD_RDM_DATA 0x84
++#define SPINAND_CMD_PROG_LOAD_X4 0x32
++#define SPINAND_CMD_PROG_LOAD_RDM_DATA_X4 0x34
++#define SPINAND_CMD_READ_ID 0x9f
++#define SPINAND_CMD_WR_DISABLE 0x04
++#define SPINAND_CMD_WR_ENABLE 0x06
++
++/* feature register */
++#define REG_BLOCK_LOCK 0xa0
++#define REG_CFG 0xb0
++#define REG_STATUS 0xc0
++
++/* status register */
++#define STATUS_OIP_MASK BIT(0)
++#define STATUS_CRBSY_MASK BIT(7)
++#define STATUS_READY 0
++#define STATUS_BUSY BIT(0)
++
++#define STATUS_E_FAIL_MASK BIT(2)
++#define STATUS_E_FAIL BIT(2)
++
++#define STATUS_P_FAIL_MASK BIT(3)
++#define STATUS_P_FAIL BIT(3)
++
++/* configuration register */
++#define CFG_ECC_MASK BIT(4)
++#define CFG_ECC_ENABLE BIT(4)
++
++/* block lock register */
++#define BL_ALL_UNLOCKED 0X00
++
++struct spinand_op;
++struct spinand_device;
++struct nand_device;
++
++/**
++ * struct nand_memory_organization - memory organization structure
++ * @bits_per_cell: number of bits per NAND cell
++ * @pagesize: page size
++ * @oobsize: OOB area size
++ * @pages_per_eraseblock: number of pages per eraseblock
++ * @eraseblocks_per_die: number of eraseblocks per die
++ * @ndies: number of dies
++ */
++struct nand_memory_organization {
++ unsigned int bits_per_cell;
++ unsigned int pagesize;
++ unsigned int oobsize;
++ unsigned int pages_per_eraseblock;
++ unsigned int eraseblocks_per_lun;
++ unsigned int planes_per_lun;
++ unsigned int luns_per_target;
++ unsigned int ntargets;
++};
++
++#define NAND_MEMORG(bpc, ps, os, ppe, epl, ppl, lpt, nt) \
++ { \
++ .bits_per_cell = (bpc), \
++ .pagesize = (ps), \
++ .oobsize = (os), \
++ .pages_per_eraseblock = (ppe), \
++ .eraseblocks_per_lun = (epl), \
++ .planes_per_lun = (ppl), \
++ .luns_per_target = (lpt), \
++ .ntargets = (nt), \
++ }
++
++/**
++ * struct nand_bbt - bad block table structure
++ * @cache: in memory BBT cache
++ */
++struct nand_bbt {
++ unsigned char *cache;
++};
++
++struct nand_row_converter {
++ unsigned int lun_addr_shift;
++ unsigned int eraseblock_addr_shift;
++};
++
++struct nand_pos {
++ unsigned int target;
++ unsigned int lun;
++ unsigned int plane;
++ unsigned int eraseblock;
++ unsigned int page;
++};
++
++struct nand_page_io_req {
++ struct nand_pos pos;
++ unsigned int dataoffs;
++ unsigned int datalen;
++ union {
++ const void *out;
++ void *in;
++ } databuf;
++ unsigned int ooboffs;
++ unsigned int ooblen;
++ union {
++ const void *out;
++ void *in;
++ } oobbuf;
++};
++/**
++ * struct nand_ops - NAND operations
++ * @erase: erase a specific block
++ * @markbad: mark a specific block bad
++ */
++struct nand_ops {
++ int (*erase)(struct nand_device *nand, const struct nand_pos *pos);
++ int (*markbad)(struct nand_device *nand, const struct nand_pos *pos);
++ bool (*isbad)(struct nand_device *nand, const struct nand_pos *pos);
++};
++
++struct nand_ecc_req {
++ unsigned int strength;
++ unsigned int step_size;
++};
++
++#define NAND_ECCREQ(str, stp) { .strength = (str), .step_size = (stp) }
++
++struct nand_device{
++ struct mtd_info mtd;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ struct nand_row_converter rowconv;
++ struct nand_bbt bbt;
++ const struct nand_ops *ops;
++};
++
++#define SPINAND_MAX_ID_LEN 4
++
++/**
++ * struct spinand_id - SPI NAND id structure
++ * @data: buffer containing the id bytes. Currently 4 bytes large, but can
++ * be extended if required.
++ * @len: ID length
++ */
++struct spinand_id {
++ u8 data[SPINAND_MAX_ID_LEN];
++ int len;
++};
++
++/**
++ * struct spinand_controller_ops - SPI NAND controller operations
++ * @exec_op: executute SPI NAND operation
++ */
++struct spinand_controller_ops {
++ int (*exec_op)(struct spinand_device *spinand,
++ struct spinand_op *op);
++};
++
++
++/**
++ * struct manufacurer_ops - SPI NAND manufacturer specified operations
++ * @detect: detect SPI NAND device, should bot be NULL.
++ * ->detect() implementation for manufacturer A never sends
++ * any manufacturer specific SPI command to a SPI NAND from
++ * manufacturer B, so the proper way is to decode the raw id
++ * data in spinand->id.data first, if manufacture ID dismatch,
++ * return directly and let others to detect.
++ * @init: initialize SPI NAND device.
++ * @cleanup: clean SPI NAND device footprint.
++ * @prepare_op: prepara read/write operation.
++ */
++struct spinand_manufacturer_ops {
++ bool (*detect)(struct spinand_device *spinand);
++ int (*init)(struct spinand_device *spinand);
++ void (*cleanup)(struct spinand_device *spinand);
++ void (*adjust_cache_op)(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op);
++};
++
++/**
++ * struct spinand_manufacturer - SPI NAND manufacturer instance
++ * @id: manufacturer ID
++ * @name: manufacturer name
++ * @ops: point to manufacturer operations
++ */
++struct spinand_manufacturer {
++ u8 id;
++ char *name;
++ const struct spinand_manufacturer_ops *ops;
++};
++
++extern const struct spinand_manufacturer micron_spinand_manufacturer;
++extern const struct spinand_manufacturer etron_spinand_manufacturer;
++extern const struct spinand_manufacturer paragon_spinand_manufacturer;
++extern const struct spinand_manufacturer giga_spinand_manufacturer;
++extern const struct spinand_manufacturer mxic_spinand_manufacturer;
++
++#define SPINAND_CAP_RD_X1 BIT(0)
++#define SPINAND_CAP_RD_X2 BIT(1)
++#define SPINAND_CAP_RD_X4 BIT(2)
++#define SPINAND_CAP_RD_DUAL BIT(3)
++#define SPINAND_CAP_RD_QUAD BIT(4)
++#define SPINAND_CAP_WR_X1 BIT(5)
++#define SPINAND_CAP_WR_X2 BIT(6)
++#define SPINAND_CAP_WR_X4 BIT(7)
++#define SPINAND_CAP_WR_DUAL BIT(8)
++#define SPINAND_CAP_WR_QUAD BIT(9)
++
++/**
++ * struct spinand_controller - SPI NAND controller instance
++ * @ops: point to controller operations
++ * @caps: controller capabilities
++ */
++struct spinand_controller {
++ struct spinand_controller_ops *ops;
++ u32 caps;
++};
++
++/**
++ * struct spinand_device - SPI NAND device instance
++ * @base: NAND device instance
++ * @bbp: internal bad block pattern descriptor
++ * @lock: protection lock
++ * @id: ID structure
++ * @read_cache_op: Opcode of read from cache
++ * @write_cache_op: Opcode of program load
++ * @buf: buffer for read/write data
++ * @oobbuf: buffer for read/write oob
++ * @rw_mode: read/write mode of SPI NAND device
++ * @controller: SPI NAND controller instance
++ * @manufacturer: SPI NAND manufacturer instance, describe
++ * manufacturer related objects
++ */
++struct spinand_device {
++ struct nand_device base;
++ struct mutex lock;
++ struct spinand_id id;
++ u8 read_cache_op;
++ u8 write_cache_op;
++ u8 *buf;
++ u8 *oobbuf;
++ u32 rw_mode;
++ struct {
++ struct spinand_controller *controller;
++ void *priv;
++ } controller;
++ struct {
++ const struct spinand_manufacturer *manu;
++ void *priv;
++ } manufacturer;
++};
++
++/**
++ * struct nand_io_iter - NAND I/O iterator
++ * @req: current I/O request
++ * @oobbytes_per_page: maximun oob bytes per page
++ * @dataleft: remaining number of data bytes to read/write
++ * @oobleft: remaining number of OOB bytes to read/write
++ */
++struct nand_io_iter {
++ struct nand_page_io_req req;
++ unsigned int oobbytes_per_page;
++ unsigned int dataleft;
++ unsigned int oobleft;
++};
++
++/**
++ * mtd_to_nanddev - Get the NAND device attached to the MTD instance
++ * @mtd: MTD instance
++ *
++ * Return: the NAND device embedding @mtd.
++ */
++static inline struct nand_device *mtd_to_nanddev(struct mtd_info *mtd)
++{
++ return container_of(mtd, struct nand_device, mtd);
++}
++/**
++ * nanddev_to_mtd - Get the MTD device attached to a NAND device
++ * @nand: NAND device
++ *
++ * Return: the MTD device embedded in @nand.
++ */
++static inline struct mtd_info *nanddev_to_mtd(struct nand_device *nand)
++{
++ return &nand->mtd;
++}
++
++/**
++ * mtd_to_spinand - Get the SPI NAND device attached to the MTD instance
++ * @mtd: MTD instance
++ *
++ * Returns the SPI NAND device attached to @mtd.
++ */
++static inline struct spinand_device *mtd_to_spinand(struct mtd_info *mtd)
++{
++ return container_of(mtd_to_nanddev(mtd), struct spinand_device, base);
++}
++
++/**
++ * spinand_to_mtd - Get the MTD device attached to the SPI NAND device
++ * @spinand: SPI NAND device
++ *
++ * Returns the MTD device attached to @spinand.
++ */
++static inline struct mtd_info *spinand_to_mtd(struct spinand_device *spinand)
++{
++ return nanddev_to_mtd(&spinand->base);
++}
++
++/**
++ * nand_to_spinand - Get the SPI NAND device embedding an NAND object
++ * @nand: NAND object
++ *
++ * Returns the SPI NAND device embedding @nand.
++ */
++static inline struct spinand_device *nand_to_spinand(struct nand_device *nand)
++{
++ return container_of(nand, struct spinand_device, base);
++}
++
++/**
++ * spinand_to_nand - Get the NAND device embedded in a SPI NAND object
++ * @spinand: SPI NAND device
++ *
++ * Returns the NAND device embedded in @spinand.
++ */
++static inline struct nand_device *
++spinand_to_nand(struct spinand_device *spinand)
++{
++ return &spinand->base;
++}
++
++/**
++ * nanddev_set_of_node - Attach a DT node to a NAND device
++ * @nand: NAND device
++ * @np: DT node
++ *
++ * Attach a DT node to a NAND device.
++ */
++static inline void nanddev_set_of_node(struct nand_device *nand,
++ struct device_node *np)
++{
++ mtd_set_of_node(&nand->mtd, np);
++}
++
++/**
++ * spinand_set_of_node - Attach a DT node to a SPI NAND device
++ * @spinand: SPI NAND device
++ * @np: DT node
++ *
++ * Attach a DT node to a SPI NAND device.
++ */
++static inline void spinand_set_of_node(struct spinand_device *spinand,
++ struct device_node *np)
++{
++ nanddev_set_of_node(&spinand->base, np);
++}
++
++#define SPINAND_MAX_ADDR_LEN 4
++
++/**
++ * struct spinand_op - SPI NAND operation description
++ * @cmd: opcode to send
++ * @n_addr: address bytes
++ * @addr_nbits: number of bit used to transfer address
++ * @dummy_types: dummy bytes followed address
++ * @addr: address or dummy bytes buffer
++ * @n_tx: size of tx_buf
++ * @tx_buf: data to be written
++ * @n_rx: size of rx_buf
++ * @rx_buf: data to be read
++ * @data_nbits: number of bit used to transfer data
++ */
++struct spinand_op {
++ u8 cmd;
++ u8 n_addr;
++ u8 addr_nbits;
++ u8 dummy_bytes;
++ u8 addr[SPINAND_MAX_ADDR_LEN];
++ u32 n_tx;
++ const u8 *tx_buf;
++ u32 n_rx;
++ u8 *rx_buf;
++ u8 data_nbits;
++};
++/**
++ * nanddev_neraseblocks - Get the total number of erasablocks
++ * @nand: NAND device
++ *
++ * Return: the number of eraseblocks exposed by @nand.
++ */
++static inline unsigned int nanddev_neraseblocks(const struct nand_device *nand)
++{
++ return (u64)nand->memorg.luns_per_target *
++ nand->memorg.eraseblocks_per_lun *
++ nand->memorg.ntargets;
++}
++
++/* BBT related functions */
++enum nand_bbt_block_status {
++ NAND_BBT_BLOCK_STATUS_UNKNOWN,
++ NAND_BBT_BLOCK_GOOD,
++ NAND_BBT_BLOCK_WORN,
++ NAND_BBT_BLOCK_RESERVED,
++ NAND_BBT_BLOCK_FACTORY_BAD,
++ NAND_BBT_BLOCK_NUM_STATUS,
++};
++int nanddev_bbt_init(struct nand_device *nand);
++void nanddev_bbt_cleanup(struct nand_device *nand);
++int nanddev_bbt_update(struct nand_device *nand);
++int nanddev_bbt_get_block_status(const struct nand_device *nand,
++ unsigned int entry);
++int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry,
++ enum nand_bbt_block_status status);
++
++/* SPI NAND supported OP mode */
++#define SPINAND_RD_X1 BIT(0)
++#define SPINAND_RD_X2 BIT(1)
++#define SPINAND_RD_X4 BIT(2)
++#define SPINAND_RD_DUAL BIT(3)
++#define SPINAND_RD_QUAD BIT(4)
++#define SPINAND_WR_X1 BIT(5)
++#define SPINAND_WR_X2 BIT(6)
++#define SPINAND_WR_X4 BIT(7)
++#define SPINAND_WR_DUAL BIT(8)
++#define SPINAND_WR_QUAD BIT(9)
++
++#define SPINAND_RD_COMMON (SPINAND_RD_X1 | SPINAND_RD_X2 | \
++ SPINAND_RD_X4 | SPINAND_RD_DUAL | \
++ SPINAND_RD_QUAD)
++#define SPINAND_WR_COMMON (SPINAND_WR_X1 | SPINAND_WR_X4)
++#define SPINAND_RW_COMMON (SPINAND_RD_COMMON | SPINAND_WR_COMMON)
++
++struct spinand_device *devm_spinand_alloc(struct device *dev);
++int spinand_init(struct spinand_device *spinand, struct module *owner);
++void spinand_cleanup(struct spinand_device *spinand);
++
++/**
++ * nanddev_page_size - Get NAND page size
++ * @nand: NAND device
++ *
++ * Return: the page size.
++ */
++static inline size_t nanddev_page_size(const struct nand_device *nand)
++{
++ return nand->memorg.pagesize;
++}
++
++/**
++ * nanddev_per_page_oobsize - Get NAND OOB size
++ * @nand: NAND device
++ *
++ * Return: the OOB size.
++ */
++static inline unsigned int
++nanddev_per_page_oobsize(const struct nand_device *nand)
++{
++ return nand->memorg.oobsize;
++}
++
++/**
++ * nanddev_pages_per_eraseblock - Get the number of pages per eraseblock
++ * @nand: NAND device
++ *
++ * Return: the number of pages per eraseblock.
++ */
++static inline unsigned int
++nanddev_pages_per_eraseblock(const struct nand_device *nand)
++{
++ return nand->memorg.pages_per_eraseblock;
++}
++
++/**
++ * nanddev_per_page_oobsize - Get NAND erase block size
++ * @nand: NAND device
++ *
++ * Return: the eraseblock size.
++ */
++static inline size_t nanddev_eraseblock_size(const struct nand_device *nand)
++{
++ return nand->memorg.pagesize * nand->memorg.pages_per_eraseblock;
++}
++
++static inline u64 nanddev_target_size(const struct nand_device *nand)
++{
++ return (u64)nand->memorg.luns_per_target *
++ nand->memorg.eraseblocks_per_lun *
++ nand->memorg.pages_per_eraseblock *
++ nand->memorg.pagesize;
++}
++
++/**
++ * nanddev_ntarget - Get the total of targets
++ * @nand: NAND device
++ *
++ * Return: the number of dies exposed by @nand.
++ */
++static inline unsigned int nanddev_ntargets(const struct nand_device *nand)
++{
++ return nand->memorg.ntargets;
++}
++
++/**
++ * nanddev_size - Get NAND size
++ * @nand: NAND device
++ *
++ * Return: the total size exposed of @nand.
++ */
++static inline u64 nanddev_size(const struct nand_device *nand)
++{
++ return nanddev_target_size(nand) * nanddev_ntargets(nand);
++}
++
++/**
++ * nanddev_get_memorg - Extract memory organization info from a NAND device
++ * @nand: NAND device
++ *
++ * This can be used by the upper layer to fill the memorg info before calling
++ * nanddev_init().
++ *
++ * Return: the memorg object embedded in the NAND device.
++ */
++static inline struct nand_memory_organization *
++nanddev_get_memorg(struct nand_device *nand)
++{
++ return &nand->memorg;
++}
++
++
++static inline unsigned int nanddev_pos_to_row(struct nand_device *nand,
++ const struct nand_pos *pos)
++{
++ return (pos->lun << nand->rowconv.lun_addr_shift) |
++ (pos->eraseblock << nand->rowconv.eraseblock_addr_shift) |
++ pos->page;
++}
++
++
++static inline unsigned int nanddev_offs_to_pos(struct nand_device *nand,
++ loff_t offs,
++ struct nand_pos *pos)
++{
++ unsigned int pageoffs;
++ u64 tmp = offs;
++
++ pageoffs = do_div(tmp, nand->memorg.pagesize);
++ pos->page = do_div(tmp, nand->memorg.pages_per_eraseblock);
++ pos->eraseblock = do_div(tmp, nand->memorg.eraseblocks_per_lun);
++ pos->plane = pos->eraseblock % nand->memorg.planes_per_lun;
++ pos->lun = do_div(tmp, nand->memorg.luns_per_target);
++ pos->target = tmp;
++
++ return pageoffs;
++}
++
++static inline int nanddev_pos_cmp(const struct nand_pos *a,
++ const struct nand_pos *b)
++{
++ if (a->target != b->target)
++ return a->target < b->target ? -1 : 1;
++
++ if (a->lun != b->lun)
++ return a->lun < b->lun ? -1 : 1;
++
++ if (a->eraseblock != b->eraseblock)
++ return a->eraseblock < b->eraseblock ? -1 : 1;
++
++ if (a->page != b->page)
++ return a->page < b->page ? -1 : 1;
++
++ return 0;
++}
++
++static inline void nanddev_pos_next_target(struct nand_device *nand,
++ struct nand_pos *pos)
++{
++ pos->page = 0;
++ pos->plane = 0;
++ pos->eraseblock = 0;
++ pos->lun = 0;
++ pos->target++;
++}
++
++static inline void nanddev_pos_next_lun(struct nand_device *nand,
++ struct nand_pos *pos)
++{
++ if (pos->lun >= nand->memorg.luns_per_target - 1)
++ return nanddev_pos_next_target(nand, pos);
++
++ pos->lun++;
++ pos->page = 0;
++ pos->plane = 0;
++ pos->eraseblock = 0;
++}
++
++static inline void nanddev_pos_next_eraseblock(struct nand_device *nand,
++ struct nand_pos *pos)
++{
++ if (pos->eraseblock >= nand->memorg.eraseblocks_per_lun - 1)
++ return nanddev_pos_next_lun(nand, pos);
++
++ pos->eraseblock++;
++ pos->page = 0;
++ pos->plane = pos->eraseblock % nand->memorg.planes_per_lun;
++}
++
++static inline loff_t nanddev_pos_to_offs(struct nand_device *nand,
++ const struct nand_pos *pos)
++{
++ unsigned int npages;
++
++ npages = pos->page +
++ ((pos->eraseblock +
++ (pos->lun +
++ (pos->target * nand->memorg.luns_per_target)) *
++ nand->memorg.eraseblocks_per_lun) *
++ nand->memorg.pages_per_eraseblock);
++
++ return (loff_t)npages * nand->memorg.pagesize;
++}
++
++static inline void nanddev_pos_next_page(struct nand_device *nand,
++ struct nand_pos *pos)
++{
++ if (pos->page >= nand->memorg.pages_per_eraseblock - 1)
++ return nanddev_pos_next_eraseblock(nand, pos);
++
++ pos->page++;
++}
++
++/**
++ * nand_io_iter_init - Initialize a NAND I/O iterator
++ * @nand: NAND device
++ * @offs: absolute offset
++ * @req: MTD request
++ * @iter: page iterator
++ */
++static inline void nanddev_io_iter_init(struct nand_device *nand,
++ loff_t offs, struct mtd_oob_ops *req,
++ struct nand_io_iter *iter)
++{
++ struct mtd_info *mtd = nanddev_to_mtd(nand);
++
++ iter->req.dataoffs = nanddev_offs_to_pos(nand, offs, &iter->req.pos);
++ iter->req.ooboffs = req->ooboffs;
++ iter->oobbytes_per_page = mtd_oobavail(mtd, req);
++ iter->dataleft = req->len;
++ iter->oobleft = req->ooblen;
++ iter->req.databuf.in = req->datbuf;
++ iter->req.datalen = min_t(unsigned int,
++ nand->memorg.pagesize - iter->req.dataoffs,
++ iter->dataleft);
++ iter->req.oobbuf.in = req->oobbuf;
++ iter->req.ooblen = min_t(unsigned int,
++ iter->oobbytes_per_page - iter->req.ooboffs,
++ iter->oobleft);
++}
++
++/**
++ * nand_io_iter_next_page - Move to the next page
++ * @nand: NAND device
++ * @iter: page iterator
++ */
++static inline void nanddev_io_iter_next_page(struct nand_device *nand,
++ struct nand_io_iter *iter)
++{
++ nanddev_pos_next_page(nand, &iter->req.pos);
++ iter->dataleft -= iter->req.datalen;
++ iter->req.databuf.in += iter->req.datalen;
++ iter->oobleft -= iter->req.ooblen;
++ iter->req.oobbuf.in += iter->req.ooblen;
++ iter->req.dataoffs = 0;
++ iter->req.ooboffs = 0;
++ iter->req.datalen = min_t(unsigned int, nand->memorg.pagesize,
++ iter->dataleft);
++ iter->req.ooblen = min_t(unsigned int, iter->oobbytes_per_page,
++ iter->oobleft);
++}
++
++/**
++ * nand_io_iter_end - Should end iteration or not
++ * @nand: NAND device
++ * @iter: page iterator
++ */
++static inline bool nanddev_io_iter_end(struct nand_device *nand,
++ const struct nand_io_iter *iter)
++{
++ if (iter->dataleft || iter->oobleft)
++ return false;
++
++ return true;
++}
++
++/**
++ * nand_io_for_each_page - Iterate over all NAND pages contained in an MTD I/O
++ * request
++ * @nand: NAND device
++ * @start: start address to read/write
++ * @req: MTD I/O request
++ * @iter: page iterator
++ */
++#define nanddev_io_for_each_page(nand, start, req, iter) \
++ for (nanddev_io_iter_init(nand, start, req, iter); \
++ !nanddev_io_iter_end(nand, iter); \
++ nanddev_io_iter_next_page(nand, iter))
++
++static inline unsigned int nanddev_bbt_pos_to_entry(struct nand_device *nand,
++ const struct nand_pos *pos)
++{
++ return pos->eraseblock;
++}
++
++static inline bool nanddev_bbt_is_initialized(struct nand_device *nand)
++{
++ return !!nand->bbt.cache;
++}
++
++int nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
++ struct module *owner);
++void nanddev_cleanup(struct nand_device *nand);
++bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos);
++bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos);
++int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos);
++int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos);
++
++/* MTD -> NAND helper functions. */
++int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo);
++
++
++#endif /* __LINUX_MTD_SPINAND_H */
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/paragon.c
+@@ -0,0 +1,152 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_PARAGON 0xA1
++#define SPINAND_MFR_XTX 0x0B
++
++struct paragon_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define PARAGON_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct paragon_spinand_info paragon_spinand_table[] = {
++ PARAGON_SPI_NAND_INFO("PARAGONxxxx", 0xe1,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++ PARAGON_SPI_NAND_INFO("XT26G01xxxx", 0xf1,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int paragon_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 2;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * paragon_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool paragon_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct paragon_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(paragon_spinand_table); i++) {
++ item = (struct paragon_spinand_info *)paragon_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * paragon_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool paragon_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if ( (id[1] != SPINAND_MFR_PARAGON) && (id[1] != SPINAND_MFR_XTX) )
++ return false;
++
++ return paragon_spinand_scan_id_table(spinand, id[2]);
++}
++
++/**
++ * paragon_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void paragon_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++
++ op->n_addr= 2;
++ op->addr[0] = op->addr[1];
++ op->addr[1] = op->addr[2];
++ op->addr[2] = 0;
++ op->dummy_bytes = paragon_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops paragon_spinand_manuf_ops = {
++ .detect = paragon_spinand_detect,
++ .adjust_cache_op = paragon_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer paragon_spinand_manufacturer = {
++ .id = SPINAND_MFR_PARAGON,
++ .name = "Paragon(XTX)",
++ .ops = &paragon_spinand_manuf_ops,
++};
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/mxic.c
+@@ -0,0 +1,152 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_MXIC 0xC2
++
++struct mxic_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define MXIC_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct mxic_spinand_info mxic_spinand_table[] = {
++ MXIC_SPI_NAND_INFO("MX35LF1G24AD", 0x14,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++
++ MXIC_SPI_NAND_INFO("MX35LF1GE4AB", 0x12,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int mxic_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * mxic_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool mxic_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct mxic_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(mxic_spinand_table); i++) {
++ item = (struct mxic_spinand_info *)mxic_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * mxic_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool mxic_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if (id[1] != SPINAND_MFR_MXIC)
++ return false;
++
++ return mxic_spinand_scan_id_table(spinand, id[2]);
++}
++
++/**
++ * mxic_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void mxic_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++ if(op->cmd == spinand->read_cache_op){//read from cache only 2 bytes address
++ op->n_addr= 2;
++ op->addr[0] = op->addr[1];
++ op->addr[1] = op->addr[2];
++ op->addr[2] = 0;
++ }
++
++ /* The plane number is passed in MSB just above the column address */
++ op->dummy_bytes = mxic_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops mxic_spinand_manuf_ops = {
++ .detect = mxic_spinand_detect,
++ .adjust_cache_op = mxic_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer mxic_spinand_manufacturer = {
++ .id = SPINAND_MFR_MXIC,
++ .name = "Mxic",
++ .ops = &mxic_spinand_manuf_ops,
++};
diff --git a/target/linux/ath79/patches-4.14/821-fix-glinet-rs485-auto-txrx.patch b/target/linux/ath79/patches-4.14/821-fix-glinet-rs485-auto-txrx.patch
new file mode 100644
index 0000000000..ada92a4ebb
--- /dev/null
+++ b/target/linux/ath79/patches-4.14/821-fix-glinet-rs485-auto-txrx.patch
@@ -0,0 +1,81 @@
+Index: b/drivers/tty/serial/8250/8250.h
+===================================================================
+--- a/drivers/tty/serial/8250/8250.h 2021-01-18 19:25:59.000000000 +0800
++++ b/drivers/tty/serial/8250/8250.h 2021-01-18 19:25:59.000000000 +0800
+@@ -15,6 +15,8 @@
+ #include <linux/serial_reg.h>
+ #include <linux/dmaengine.h>
+
++
++extern unsigned int rs485txen_gpio;
+ struct uart_8250_dma {
+ int (*tx_dma)(struct uart_8250_port *p);
+ int (*rx_dma)(struct uart_8250_port *p);
+Index: b/drivers/tty/serial/8250/8250_port.c
+===================================================================
+--- a/drivers/tty/serial/8250/8250_port.c 2021-01-18 19:25:59.000000000 +0800
++++ b/drivers/tty/serial/8250/8250_port.c 2021-01-18 19:25:59.000000000 +0800
+@@ -38,6 +38,7 @@
+ #include <linux/uaccess.h>
+ #include <linux/pm_runtime.h>
+ #include <linux/ktime.h>
++#include <linux/gpio/consumer.h>
+
+ #include <asm/io.h>
+ #include <asm/irq.h>
+@@ -1502,12 +1503,22 @@ static void __stop_tx_rs485(struct uart_
+ }
+ }
+
++
++static unsigned int serial8250_tx_empty(struct uart_port *port);
+ static inline void __do_stop_tx(struct uart_8250_port *p)
+ {
+ if (p->ier & UART_IER_THRI) {
+ p->ier &= ~UART_IER_THRI;
+ serial_out(p, UART_IER, p->ier);
+ serial8250_rpm_put_tx(p);
++ if(0xff != rs485txen_gpio){
++
++ while(!serial8250_tx_empty(&(p->port))){
++
++ ;
++ }
++ gpiod_set_value(gpio_to_desc(rs485txen_gpio),0);
++ }
+ }
+ }
+
+@@ -1553,6 +1564,9 @@ static void serial8250_stop_tx(struct ua
+
+ static inline void __start_tx(struct uart_port *port)
+ {
++ if(0xff != rs485txen_gpio){
++ gpiod_set_value(gpio_to_desc(rs485txen_gpio),1);
++ }
+ struct uart_8250_port *up = up_to_u8250p(port);
+
+ if (up->dma && !up->dma->tx_dma(up))
+Index: b/drivers/tty/serial/8250/8250_of.c
+===================================================================
+--- a/drivers/tty/serial/8250/8250_of.c 2021-01-18 19:25:59.000000000 +0800
++++ b/drivers/tty/serial/8250/8250_of.c 2021-01-18 19:26:53.000000000 +0800
+@@ -193,6 +193,7 @@ err_pmruntime:
+ /*
+ * Try to register a serial port
+ */
++unsigned int rs485txen_gpio = 0xff;
+ static const struct of_device_id of_platform_serial_table[];
+ static int of_platform_serial_probe(struct platform_device *ofdev)
+ {
+@@ -237,6 +238,10 @@ static int of_platform_serial_probe(stru
+ &port8250.overrun_backoff_time_ms) != 0)
+ port8250.overrun_backoff_time_ms = 0;
+
++ if(!of_property_read_u32(ofdev->dev.of_node,"rs485_pin",&rs485txen_gpio)){
++ pr_info("Serial port to 485 enable,rs485txen_gpio = gpio%d \n",rs485txen_gpio);
++ }
++
+ ret = serial8250_register_8250_port(&port8250);
+ if (ret < 0)
+ goto err_dispose;
diff --git a/target/linux/ath79/patches-4.14/911-ath79-eth-support-ifname.patch b/target/linux/ath79/patches-4.14/911-ath79-eth-support-ifname.patch
new file mode 100644
index 0000000000..1495bb550a
--- /dev/null
+++ b/target/linux/ath79/patches-4.14/911-ath79-eth-support-ifname.patch
@@ -0,0 +1,20 @@
+--- a/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c
++++ b/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c
+@@ -1303,6 +1303,7 @@ static int ag71xx_probe(struct platform_
+ struct resource *res;
+ struct ag71xx *ag;
+ const void *mac_addr;
++ const char *ifname = NULL;
+ u32 max_frame_len;
+ int tx_size, err;
+
+@@ -1514,6 +1515,9 @@ static int ag71xx_probe(struct platform_
+
+ platform_set_drvdata(pdev, dev);
+
++ if(!of_property_read_string(np, "ifname",&ifname))
++ memcpy(dev->name,ifname,strlen(ifname)+1);
++
+ err = register_netdev(dev);
+ if (err) {
+ dev_err(&pdev->dev, "unable to register net device\n");
diff --git a/target/linux/ath79/patches-4.14/931-fix-led-netdev-trigger-by-wwanx.patch b/target/linux/ath79/patches-4.14/931-fix-led-netdev-trigger-by-wwanx.patch
new file mode 100644
index 0000000000..a9ba1e3769
--- /dev/null
+++ b/target/linux/ath79/patches-4.14/931-fix-led-netdev-trigger-by-wwanx.patch
@@ -0,0 +1,26 @@
+Index: linux-4.14.209/drivers/leds/trigger/ledtrig-netdev.c
+===================================================================
+--- linux-4.14.209.orig/drivers/leds/trigger/ledtrig-netdev.c
++++ linux-4.14.209/drivers/leds/trigger/ledtrig-netdev.c
+@@ -111,6 +111,7 @@ static ssize_t device_name_store(struct
+ {
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_netdev_data *trigger_data = led_cdev->trigger_data;
++ unsigned int flags;
+
+ if (size >= IFNAMSIZ)
+ return -EINVAL;
+@@ -133,9 +134,11 @@ static ssize_t device_name_store(struct
+ dev_get_by_name(&init_net, trigger_data->device_name);
+
+ clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
+- if (trigger_data->net_dev != NULL)
+- if (netif_carrier_ok(trigger_data->net_dev))
++ if (trigger_data->net_dev != NULL){
++ flags = dev_get_flags(trigger_data->net_dev);
++ if (flags & IFF_LOWER_UP)
+ set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ }
+
+ trigger_data->last_activity = 0;
+
diff --git a/target/linux/ath79/patches-4.14/932-fix-ar7240-switch-reset.patch b/target/linux/ath79/patches-4.14/932-fix-ar7240-switch-reset.patch
new file mode 100644
index 0000000000..d1c1f41f9e
--- /dev/null
+++ b/target/linux/ath79/patches-4.14/932-fix-ar7240-switch-reset.patch
@@ -0,0 +1,16 @@
+Index: linux-4.14.209/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
+===================================================================
+--- linux-4.14.209.orig/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
++++ linux-4.14.209/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
+@@ -955,6 +955,11 @@ ar7240_reset_switch(struct switch_dev *d
+ {
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ ar7240sw_reset(as);
++
++ /* ar7240 reapply hardware settings*/
++ if (sw_is_ar7240(as))
++ ar7240_hw_apply(dev);
++
+ return 0;
+ }
+
diff --git a/target/linux/ath79/patches-4.14/932-fix-ar8337-switch-reset.patch b/target/linux/ath79/patches-4.14/932-fix-ar8337-switch-reset.patch
new file mode 100644
index 0000000000..d44d1e62c3
--- /dev/null
+++ b/target/linux/ath79/patches-4.14/932-fix-ar8337-switch-reset.patch
@@ -0,0 +1,67 @@
+Index: linux-4.14.209/drivers/net/phy/ar8216.c
+===================================================================
+--- linux-4.14.209.orig/drivers/net/phy/ar8216.c
++++ linux-4.14.209/drivers/net/phy/ar8216.c
+@@ -1445,6 +1445,36 @@ ar8xxx_sw_reset_switch(struct switch_dev
+ return chip->sw_hw_apply(dev);
+ }
+
++ar8337_sw_reset_switch(struct switch_dev *dev)
++{
++ struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
++ const struct ar8xxx_chip *chip = priv->chip;
++ int i;
++
++ mutex_lock(&priv->reg_mutex);
++
++ for (i = 0; i < dev->vlans; i++)
++ priv->vlan_id[i] = i;
++
++ /* Configure all ports */
++ for (i = 0; i < dev->ports; i++)
++ chip->init_port(priv, i);
++
++ priv->mirror_rx = false;
++ priv->mirror_tx = false;
++ priv->source_port = 0;
++ priv->monitor_port = 0;
++ priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME;
++
++ chip->init_globals(priv);
++ chip->atu_flush(priv);
++ chip->hw_init(priv);
++
++ mutex_unlock(&priv->reg_mutex);
++
++ return chip->sw_hw_apply(dev);
++}
++
+ int
+ ar8xxx_sw_set_reset_mibs(struct switch_dev *dev,
+ const struct switch_attr *attr,
+Index: linux-4.14.209/drivers/net/phy/ar8216.h
+===================================================================
+--- linux-4.14.209.orig/drivers/net/phy/ar8216.h
++++ linux-4.14.209/drivers/net/phy/ar8216.h
+@@ -612,6 +612,8 @@ ar8xxx_sw_hw_apply(struct switch_dev *de
+ int
+ ar8xxx_sw_reset_switch(struct switch_dev *dev);
+ int
++ar8337_sw_reset_switch(struct switch_dev *dev);
++int
+ ar8xxx_sw_get_port_link(struct switch_dev *dev, int port,
+ struct switch_port_link *link);
+ int
+Index: linux-4.14.209/drivers/net/phy/ar8327.c
+===================================================================
+--- linux-4.14.209.orig/drivers/net/phy/ar8327.c
++++ linux-4.14.209/drivers/net/phy/ar8327.c
+@@ -1469,7 +1469,7 @@ static const struct switch_dev_ops ar832
+ .get_vlan_ports = ar8327_sw_get_ports,
+ .set_vlan_ports = ar8327_sw_set_ports,
+ .apply_config = ar8327_sw_hw_apply,
+- .reset_switch = ar8xxx_sw_reset_switch,
++ .reset_switch = ar8337_sw_reset_switch,
+ .get_port_link = ar8xxx_sw_get_port_link,
+ .get_port_stats = ar8xxx_sw_get_port_stats,
+ };
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 9cd51e5de0..428dc4007b 100755
--- a/target/linux/ipq40xx/base-files/etc/board.d/01_leds
+++ b/target/linux/ipq40xx/base-files/etc/board.d/01_leds
@@ -26,6 +26,7 @@ avm,fritzbox-4040)
ucidef_set_led_switch "lan" "LAN" "fritz4040:green:lan" "switch0" "0x1e"
;;
avm,fritzbox-7530 |\
+glinet,gl-s1300 |\
glinet,gl-b1300)
ucidef_set_led_wlan "wlan" "WLAN" "${boardname}:green:wlan" "phy0tpt"
;;
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 01825b8bac..bb78f5403c 100755
--- a/target/linux/ipq40xx/base-files/etc/board.d/02_network
+++ b/target/linux/ipq40xx/base-files/etc/board.d/02_network
@@ -51,6 +51,7 @@ ipq40xx_setup_interfaces()
compex,wpj428)
ucidef_set_interface_lan "eth0 eth1"
;;
+ glinet,gl-b1300th |\
glinet,gl-b1300)
ucidef_set_interfaces_lan_wan "eth0" "eth1"
ucidef_add_switch "switch0" \
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 b0035ce8a3..3e17d1547e 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
@@ -132,8 +132,10 @@ case "$FIRMWARE" in
case "$board" in
8dev,jalapeno |\
alfa-network,ap120c-ac |\
+ glinet,gl-b1300th |\
glinet,gl-b1300 |\
linksys,ea6350v3 |\
+ glinet,gl-s1300 |\
qcom,ap-dk01.1-c1)
ath10kcal_extract "ART" 4096 12064
;;
@@ -194,8 +196,10 @@ case "$FIRMWARE" in
case "$board" in
8dev,jalapeno |\
alfa-network,ap120c-ac |\
+ glinet,gl-b1300th |\
glinet,gl-b1300 |\
linksys,ea6350v3 |\
+ glinet,gl-s1300 |\
qcom,ap-dk01.1-c1)
ath10kcal_extract "ART" 20480 12064
;;
diff --git a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
index a7b7da1bf3..90e25ee0c4 100644
--- a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
+++ b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh
@@ -51,6 +51,7 @@ platform_do_upgrade() {
avm,fritzbox-7530 |\
avm,fritzrepeater-1200 |\
avm,fritzrepeater-3000 |\
+ glinet,gl-b1300th |\
qxwlan,e2600ac-c2)
nand_do_upgrade "$1"
;;
diff --git a/target/linux/ipq40xx/config-4.14 b/target/linux/ipq40xx/config-4.14
index c72e693206..3b3d138c14 100644
--- a/target/linux/ipq40xx/config-4.14
+++ b/target/linux/ipq40xx/config-4.14
@@ -284,6 +284,13 @@ CONFIG_MFD_SYSCON=y
CONFIG_MIGHT_HAVE_CACHE_L2X0=y
CONFIG_MIGHT_HAVE_PCI=y
CONFIG_MIGRATION=y
+CONFIG_MMC=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_IO_ACCESSORS=y
+CONFIG_MMC_SDHCI_MSM=y
+# CONFIG_MMC_SDHCI_PCI is not set
+CONFIG_MMC_SDHCI_PLTFM=y
+# CONFIG_MMC_TIFM_SD is not set
CONFIG_MODULES_USE_ELF_REL=y
# CONFIG_MSM_GCC_8660 is not set
# CONFIG_MSM_GCC_8916 is not set
@@ -483,3 +490,4 @@ CONFIG_ZBOOT_ROM_BSS=0
CONFIG_ZBOOT_ROM_TEXT=0
CONFIG_ZLIB_DEFLATE=y
CONFIG_ZLIB_INFLATE=y
+CONFIG_MTD_NAND_SPI_NAND=y
diff --git a/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4018-gl-b1300th.dts b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4018-gl-b1300th.dts
new file mode 100644
index 0000000000..509bed031e
--- /dev/null
+++ b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4018-gl-b1300th.dts
@@ -0,0 +1,282 @@
+/* Copyright (c) 2015, 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 "qcom-ipq4019.dtsi"
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/soc/qcom,tcsr.h>
+
+/ {
+ model = "GL.iNet GL-B1300TH";
+ compatible = "glinet,gl-b1300th", "qcom,ipq4019";
+
+ aliases {
+ led-boot = &power;
+ led-failsafe = &power;
+ led-running = &power;
+ led-upgrade = &power;
+ };
+
+ memory {
+ device_type = "memory";
+ reg = <0x80000000 0x10000000>;
+ };
+
+ chosen {
+ bootargs-append = " ubi.mtd=ubi root=/dev/ubiblock0_1 clk_ignore_unused";
+ };
+
+ soc {
+ mdio@90000 {
+ status = "okay";
+ };
+
+ ess-psgmii@98000 {
+ status = "okay";
+ };
+
+ tcsr@1949000 {
+ compatible = "qcom,tcsr";
+ reg = <0x1949000 0x100>;
+ qcom,wifi_glb_cfg = <TCSR_WIFI_GLB_CFG>;
+ };
+
+ tcsr@194b000 {
+ /* select hostmode */
+ compatible = "qcom,tcsr";
+ reg = <0x194b000 0x100>;
+ qcom,usb-hsphy-mode-select = <TCSR_USB_HSPHY_HOST_MODE>;
+ status = "okay";
+ };
+
+ ess_tcsr@1953000 {
+ compatible = "qcom,tcsr";
+ reg = <0x1953000 0x1000>;
+ qcom,ess-interface-select = <TCSR_ESS_PSGMII>;
+ };
+
+ tcsr@1957000 {
+ compatible = "qcom,tcsr";
+ reg = <0x1957000 0x100>;
+ qcom,wifi_noc_memtype_m0_m2 = <TCSR_WIFI_NOC_MEMTYPE_M0_M2>;
+ };
+
+ usb2@60f8800 {
+ status = "okay";
+ };
+
+ usb3@8af8800 {
+ status = "okay";
+ };
+
+ crypto@8e3a000 {
+ status = "okay";
+ };
+
+ watchdog@b017000 {
+ status = "okay";
+ };
+
+ ess-switch@c000000 {
+ status = "okay";
+ switch_lan_bmp = <0x18>;
+ switch_wan_bmp = <0x20>;
+ };
+
+ edma@c080000 {
+ status = "okay";
+ };
+ };
+
+ keys {
+ compatible = "gpio-keys";
+
+ wps {
+ label = "wps";
+ gpios = <&tlmm 5 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_WPS_BUTTON>;
+ };
+
+ reset {
+ label = "reset";
+ gpios = <&tlmm 63 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_RESTART>;
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ power: power {
+ label = "gl-b1300th:green:power";
+ gpios = <&tlmm 4 GPIO_ACTIVE_HIGH>;
+ default-state = "on";
+ };
+
+ mesh {
+ label = "gl-b1300th:green:mesh";
+ gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>;
+ };
+
+ wlan {
+ label = "gl-b1300th:green:wlan";
+ gpios = <&tlmm 2 GPIO_ACTIVE_HIGH>;
+ };
+ };
+};
+
+&blsp_dma {
+ status = "okay";
+};
+
+&cryptobam {
+ status = "okay";
+};
+
+&blsp1_spi1 {
+ pinctrl-0 = <&spi_0_pins>;
+ pinctrl-names = "default";
+ cs-gpios = <&tlmm 54 GPIO_ACTIVE_HIGH>, <&tlmm 59 GPIO_ACTIVE_HIGH>;
+ status = "okay";
+
+ m25p80@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0>;
+ compatible = "jedec,spi-nor";
+ spi-max-frequency = <24000000>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition0@0 {
+ label = "SBL1";
+ reg = <0x00000000 0x00040000>;
+ read-only;
+ };
+ partition1@40000 {
+ label = "MIBIB";
+ reg = <0x00040000 0x00020000>;
+ read-only;
+ };
+ partition2@60000 {
+ label = "QSEE";
+ reg = <0x00060000 0x00060000>;
+ read-only;
+ };
+ partition3@c0000 {
+ label = "CDT";
+ reg = <0x000c0000 0x00010000>;
+ read-only;
+ };
+ partition4@d0000 {
+ label = "DDRPARAMS";
+ reg = <0x000d0000 0x00010000>;
+ read-only;
+ };
+ partition5@e0000 {
+ label = "APPSBLENV"; /* uboot env*/
+ reg = <0x000e0000 0x00010000>;
+ read-only;
+ };
+ partition5@f0000 {
+ label = "APPSBL"; /* uboot */
+ reg = <0x000f0000 0x00080000>;
+ read-only;
+ };
+ partition5@170000 {
+ label = "ART";
+ reg = <0x00170000 0x00010000>;
+ read-only;
+ };
+ };
+ };
+
+ spi-nand@1 {
+ status = "okay";
+
+ compatible = "spinand,glinet";
+ reg = <1>;
+ spi-max-frequency = <24000000>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "ubi";
+ reg = <0x00000000 0x08000000>;
+ };
+ };
+ };
+};
+
+&blsp1_uart1 {
+ pinctrl-0 = <&serial_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+};
+
+&tlmm {
+ serial_pins: serial_pinmux {
+ mux {
+ pins = "gpio60", "gpio61";
+ function = "blsp_uart0";
+ bias-disable;
+ };
+ };
+
+ spi_0_pins: spi_0_pinmux {
+ pin {
+ function = "blsp_spi0";
+ pins = "gpio55", "gpio56", "gpio57";
+ drive-strength = <2>;
+ bias-disable;
+ };
+ pin_cs {
+ function = "gpio";
+ pins = "gpio54", "gpio59";
+ drive-strength = <2>;
+ bias-disable;
+ output-high;
+ };
+ };
+};
+
+&usb2_hs_phy {
+ status = "okay";
+};
+
+&usb3_hs_phy {
+ status = "okay";
+};
+
+&usb3_ss_phy {
+ status = "okay";
+};
+
+&wifi0 {
+ status = "okay";
+ qcom,ath10k-calibration-variant = "GL-B1300";
+};
+
+&wifi1 {
+ status = "okay";
+ qcom,ath10k-calibration-variant = "GL-B1300";
+};
diff --git a/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4029-gl-s1300.dts b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4029-gl-s1300.dts
new file mode 100755
index 0000000000..f4fed89ebc
--- /dev/null
+++ b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4029-gl-s1300.dts
@@ -0,0 +1,428 @@
+/* Copyright (c) 2015, 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 "qcom-ipq4019.dtsi"
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+#include <dt-bindings/soc/qcom,tcsr.h>
+
+/ {
+ model = "GL.iNet GL-S1300";
+ compatible = "glinet,gl-s1300", "qcom,ipq4019";
+
+ aliases {
+ led-boot = &power;
+ led-failsafe = &power;
+ led-running = &power;
+ led-upgrade = &power;
+ sdhc1 = &sdhc_1;
+ };
+
+ memory {
+ device_type = "memory";
+ reg = <0x80000000 0x10000000>;
+ };
+
+ soc {
+ mdio@90000 {
+ status = "okay";
+ };
+
+ ess-psgmii@98000 {
+ status = "okay";
+ };
+
+ tcsr@1949000 {
+ compatible = "qcom,tcsr";
+ reg = <0x1949000 0x100>;
+ qcom,wifi_glb_cfg = <TCSR_WIFI_GLB_CFG>;
+ };
+
+ tcsr@194b000 {
+ /* select hostmode */
+ compatible = "qcom,tcsr";
+ reg = <0x194b000 0x100>;
+ qcom,usb-hsphy-mode-select = <TCSR_USB_HSPHY_HOST_MODE>;
+ status = "okay";
+ };
+
+ ess_tcsr@1953000 {
+ compatible = "qcom,tcsr";
+ reg = <0x1953000 0x1000>;
+ qcom,ess-interface-select = <TCSR_ESS_PSGMII>;
+ };
+
+ tcsr@1957000 {
+ compatible = "qcom,tcsr";
+ reg = <0x1957000 0x100>;
+ qcom,wifi_noc_memtype_m0_m2 = <TCSR_WIFI_NOC_MEMTYPE_M0_M2>;
+ };
+
+ usb2@60f8800 {
+ status = "okay";
+ };
+
+ serial@78af000 {
+ pinctrl-0 = <&serial_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+ };
+ usb3@8af8800 {
+ status = "okay";
+ };
+
+ crypto@8e3a000 {
+ status = "okay";
+ };
+
+ watchdog@b017000 {
+ status = "okay";
+ };
+
+ ess-switch@c000000 {
+ status = "okay";
+ switch_lan_bmp = <0x18>;
+ switch_wan_bmp = <0x20>;
+ };
+
+ edma@c080000 {
+ status = "okay";
+ };
+
+ sdhc_1: sdhci@7824000 {
+ compatible = "qcom,sdhci-msm";
+ reg = <0x7824900 0x11c>, <0x7824000 0x800>;
+ reg-names = "hc_mem", "core_mem";
+ interrupts = <0 123 0>, <0 138 0>;
+ interrupt-names = "hc_irq", "pwr_irq";
+ qcom,bus-width = <8>;
+ qcom,max_clk = <192000000>;
+ clocks = <&gcc 47>,
+ <&gcc 46>;
+ clock-names = "core_clk", "iface_clk";
+ qcom,large-address-bus;
+ qcom,disable-aggressive-pm;
+ status = "disabled";
+ };
+
+ sdhc_1: sdhci@7824000{
+ status = "ok";
+ qcom,bus-speed-mode = "HS200_1p8v", "DDR_1p8v";
+ qcom,clk-rates = <400000 25000000 50000000 100000000 \
+ 192000000 384000000>;
+ pinctrl-0 = <&sd_0_pins>;
+ pinctrl-names = "active", "sleep";
+ cd-gpios = <&tlmm 22 0x1>;
+ sd-ldo-gpios = <&tlmm 33 0x1>;
+ };
+
+ serial@78b0000 {
+ pinctrl-0 = <&serial_1_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+ };
+ };
+
+ gpio-keys {
+ compatible = "gpio-keys";
+
+ wps {
+ label = "wps";
+ gpios = <&tlmm 53 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_WPS_BUTTON>;
+ };
+
+ reset {
+ label = "reset";
+ gpios = <&tlmm 18 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_RESTART>;
+ };
+ };
+
+ gpio-leds {
+ compatible = "gpio-leds";
+
+ power: power {
+ label = "gl-s1300:green:power";
+ gpios = <&tlmm 57 GPIO_ACTIVE_HIGH>;
+ default-state = "on";
+ };
+
+ mesh {
+ label = "gl-s1300:green:mesh";
+ gpios = <&tlmm 59 GPIO_ACTIVE_HIGH>;
+ };
+
+ wlan {
+ label = "gl-s1300:green:wlan";
+ gpios = <&tlmm 60 GPIO_ACTIVE_HIGH>;
+ };
+ };
+};
+
+&blsp_dma {
+ status = "okay";
+};
+
+&cryptobam {
+ status = "okay";
+};
+
+&blsp1_spi1 {
+ pinctrl-0 = <&spi_0_pins>;
+ pinctrl-names = "default";
+ status = "okay";
+ cs-gpios = <&tlmm 12 GPIO_ACTIVE_HIGH>;
+
+ mx25l25635f@0 {
+ compatible = "jedec,spi-nor";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0>;
+ spi-max-frequency = <24000000>;
+
+
+ SBL1@0 {
+ label = "SBL1";
+ reg = <0x0 0x40000>;
+ read-only;
+ };
+
+ MIBIB@40000 {
+ label = "MIBIB";
+ reg = <0x40000 0x20000>;
+ read-only;
+ };
+
+ QSEE@60000 {
+ label = "QSEE";
+ reg = <0x60000 0x60000>;
+ read-only;
+ };
+
+ CDT@c0000 {
+ label = "CDT";
+ reg = <0xc0000 0x10000>;
+ read-only;
+ };
+
+ DDRPARAMS@d0000 {
+ label = "DDRPARAMS";
+ reg = <0xd0000 0x10000>;
+ read-only;
+ };
+
+ APPSBLENV@e0000 {
+ label = "APPSBLENV";
+ reg = <0xe0000 0x10000>;
+ read-only;
+ };
+
+ APPSBL@f0000 {
+ label = "APPSBL";
+ reg = <0xf0000 0x80000>;
+ read-only;
+ };
+
+ ART@170000 {
+ label = "ART";
+ reg = <0x170000 0x10000>;
+ read-only;
+ };
+
+ firmware@180000 {
+ label = "firmware";
+ reg = <0x180000 0xe80000>;
+ };
+ };
+};
+
+&blsp1_spi2 {
+ pinctrl-0 = <&spi_1_pins>;
+ pinctrl-names = "default";
+ cs-gpios = <&tlmm 45 GPIO_ACTIVE_HIGH>;
+ status = "ok";
+
+ spidev1: spi@1 {
+ compatible = "siliconlabs,si3210";
+ reg = <0>;
+ spi-max-frequency = <24000000>;
+ };
+};
+
+&tlmm {
+ serial_pins: serial_pinmux {
+ mux {
+ pins = "gpio16", "gpio17";
+ function = "blsp_uart0";
+ bias-disable;
+ };
+ };
+
+ serial_1_pins: serial1_pinmux {
+ mux {
+ pins = "gpio8", "gpio9",
+ "gpio10", "gpio11";
+ function = "blsp_uart1";
+ bias-disable;
+ };
+ };
+
+ spi_0_pins: spi_0_pinmux {
+ pinmux {
+ function = "blsp_spi0";
+ pins = "gpio13", "gpio14", "gpio15";
+ };
+ pinmux_cs {
+ function = "gpio";
+ pins = "gpio12";
+ };
+ pinconf {
+ pins = "gpio13", "gpio14", "gpio15";
+ drive-strength = <12>;
+ bias-disable;
+ };
+ pinconf_cs {
+ pins = "gpio12";
+ drive-strength = <2>;
+ bias-disable;
+ output-high;
+ };
+ };
+
+ spi_1_pins: spi_1_pinmux {
+ mux {
+ pins = "gpio44", "gpio46", "gpio47";
+ function = "blsp_spi1";
+ bias-disable;
+ };
+ host_int {
+ pins = "gpio42";
+ function = "gpio";
+ input;
+ };
+ cs {
+ pins = "gpio45";
+ function = "gpio";
+ bias-pull-up;
+ };
+ wake {
+ pins = "gpio40";
+ function = "gpio";
+ output-high;
+ };
+ reset {
+ pins = "gpio49";
+ function = "gpio";
+ output-high;
+ };
+ };
+
+ sd_0_pins: sd_0_pinmux {
+ sd0 {
+ pins = "gpio23";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ sd1 {
+ pins = "gpio24";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ sd2 {
+ pins = "gpio25";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ sd3 {
+ pins = "gpio26";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ sdclk {
+ pins = "gpio27";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <7>;
+ vm-enable;
+ };
+ sdcmd {
+ pins = "gpio28";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ sd4 {
+ pins = "gpio29";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ sd5 {
+ pins = "gpio30";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ sd6 {
+ pins = "gpio31";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ sd7 {
+ pins = "gpio32";
+ function = "sdio";
+ pull-res = <2>;
+ drive-type = <1>;
+ vm-enable;
+ };
+ };
+};
+
+&usb2_hs_phy {
+ status = "okay";
+};
+
+&usb3_hs_phy {
+ status = "okay";
+};
+
+&usb3_ss_phy {
+ status = "okay";
+};
+
+&wifi0 {
+ status = "okay";
+ qcom,ath10k-calibration-variant = "GL-S1300";
+};
+
+&wifi1 {
+ status = "okay";
+ qcom,ath10k-calibration-variant = "GL-S1300";
+};
diff --git a/target/linux/ipq40xx/image/Makefile b/target/linux/ipq40xx/image/Makefile
index 98c81726d9..023c71e731 100644
--- a/target/linux/ipq40xx/image/Makefile
+++ b/target/linux/ipq40xx/image/Makefile
@@ -211,6 +211,18 @@ define Device/engenius_ens620ext
endef
TARGET_DEVICES += engenius_ens620ext
+define Device/glinet_gl-b1300th
+ $(call Device/FitImage)
+ $(call Device/UbiFit)
+ DEVICE_DTS := qcom-ipq4018-gl-b1300th
+ BLOCKSIZE := 128k
+ PAGESIZE := 2048
+ DEVICE_TITLE := GL.iNet GL-B1300TH
+ DEVICE_DTS_CONFIG := config@ap.dk01.1-c2
+ IMAGE_SIZE := 131072k
+endef
+TARGET_DEVICES += glinet_gl-b1300th
+
define Device/glinet_gl-b1300
$(call Device/FitImage)
DEVICE_TITLE := GL.iNet GL-B1300
@@ -273,6 +285,19 @@ define Device/linksys_ea8300
endef
TARGET_DEVICES += linksys_ea8300
+define Device/glinet_gl-s1300
+ $(call Device/FitImage)
+ DEVICE_TITLE := GL.iNet GL-S1300
+ BOARD_NAME := gl-s1300
+ DEVICE_DTS := qcom-ipq4029-gl-s1300
+ KERNEL_SIZE := 4096k
+ IMAGE_SIZE := 26624k
+ IMAGES := sysupgrade.bin
+ IMAGE/sysupgrade.bin := append-kernel |append-rootfs | pad-rootfs | append-metadata
+ DEVICE_PACKAGES := ipq-wifi-glinet_gl-s1300 kmod-fs-ext4 kmod-mmc kmod-spi-dev
+endef
+TARGET_DEVICES += glinet_gl-s1300
+
define Device/meraki_mr33
$(call Device/FitImage)
DEVICE_DTS := qcom-ipq4029-mr33
diff --git a/target/linux/ipq40xx/patches-4.14/409-mtd-support-glinet-spinand.patch b/target/linux/ipq40xx/patches-4.14/409-mtd-support-glinet-spinand.patch
new file mode 100644
index 0000000000..a47dd117d7
--- /dev/null
+++ b/target/linux/ipq40xx/patches-4.14/409-mtd-support-glinet-spinand.patch
@@ -0,0 +1,3008 @@
+--- a/drivers/mtd/nand/Kconfig
++++ b/drivers/mtd/nand/Kconfig
+@@ -563,4 +563,12 @@ config MTD_NAND_MTK
+ Enables support for NAND controller on MTK SoCs.
+ This controller is found on mt27xx, mt81xx, mt65xx SoCs.
+
++config MTD_NAND_SPI_NAND
++ tristate "SPI Nand flash support"
++ default n
++ depends on MTD_NAND
++ help
++ Enables the driver for SPI NAND flash controller on Qualcomm-Atheros System on Chips
++ This controller is used on families AR71xx and AR9xxx.
++
+ endif # MTD_NAND
+--- a/drivers/mtd/nand/Makefile
++++ b/drivers/mtd/nand/Makefile
+@@ -60,6 +60,7 @@ obj-$(CONFIG_MTD_NAND_HISI504) +
+ obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/
+ obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o
+ obj-$(CONFIG_MTD_NAND_MTK) += mtk_nand.o mtk_ecc.o
++obj-$(CONFIG_MTD_NAND_SPI_NAND) += spinand/
+
+ nand-objs := nand_base.o nand_bbt.o nand_timings.o nand_ids.o
+ nand-objs += nand_amd.o
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/Kconfig
+@@ -0,0 +1,7 @@
++menuconfig MTD_SPI_NAND
++ tristate "SPI NAND device Support"
++ select MTD_NAND_CORE
++ help
++ This is the framework for the SPI NAND device drivers.
++
++source "drivers/mtd/nand/spi/controllers/Kconfig"
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/Makefile
+@@ -0,0 +1 @@
++obj-$(CONFIG_MTD_NAND_SPI_NAND) += generic-spinand-controller.o core.o bbt.o nand_core.o micron.o etron.o gigadevice.o paragon.o mxic.o
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/bbt.c
+@@ -0,0 +1,79 @@
++/*
++ * Copyright (c) 2017 Free Electrons
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * Authors:
++ * Boris Brezillon <boris.brezillon@free-electrons.com>
++ * Peter Pan <peterpandong@micron.com>
++ */
++
++#define pr_fmt(fmt) "nand-bbt: " fmt
++
++#include <linux/mtd/rawnand.h>
++#include <linux/slab.h>
++#include "spinand.h"
++
++int nanddev_bbt_init(struct nand_device *nand)
++{
++ unsigned int nwords = nanddev_neraseblocks(nand);
++
++ nand->bbt.cache = kzalloc(nwords, GFP_KERNEL);
++ if (!nand->bbt.cache)
++ return -ENOMEM;
++ memset(nand->bbt.cache,0,nwords);
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_init);
++
++void nanddev_bbt_cleanup(struct nand_device *nand)
++{
++ kfree(nand->bbt.cache);
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_cleanup);
++
++int nanddev_bbt_update(struct nand_device *nand)
++{
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_update);
++
++int nanddev_bbt_get_block_status(const struct nand_device *nand,
++ unsigned int entry)
++{
++ unsigned char *pos = nand->bbt.cache + entry;
++ unsigned long status;
++
++ if (entry >= nanddev_neraseblocks(nand)){
++ return -ERANGE;
++ }
++
++ status = pos[0];
++
++
++ return status & 0xff;
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_get_block_status);
++
++int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry,
++ enum nand_bbt_block_status status)
++{
++ unsigned char *pos = nand->bbt.cache + entry;;
++
++ if (entry >= nanddev_neraseblocks(nand)){
++ return -ERANGE;
++ }
++
++ pos[0] = status & 0xff;
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nanddev_bbt_set_block_status);
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/core.c
+@@ -0,0 +1,921 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#define pr_fmt(fmt) "spi-nand: " fmt
++
++#include <linux/kernel.h>
++#include <linux/device.h>
++#include <linux/module.h>
++#include <linux/jiffies.h>
++#include "spinand.h"
++#include <linux/slab.h>
++#include <linux/of.h>
++static inline void spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ if (!spinand->manufacturer.manu->ops->adjust_cache_op)
++ return;
++
++ spinand->manufacturer.manu->ops->adjust_cache_op(spinand, req, op);
++}
++
++static inline int spinand_exec_op(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ return spinand->controller.controller->ops->exec_op(spinand, op);
++}
++
++static inline void spinand_op_init(struct spinand_op *op)
++{
++ memset(op, 0, sizeof(struct spinand_op));
++ op->addr_nbits = 1;
++ op->data_nbits = 1;
++}
++
++static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
++{
++ struct spinand_op op;
++ int ret;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_GET_FEATURE;
++ op.n_addr = 1;
++ op.addr[0] = reg;
++ op.n_rx = 1;
++ op.rx_buf = val;
++
++ ret = spinand_exec_op(spinand, &op);
++ if (ret < 0)
++ pr_err("failed to read register %d (err = %d)\n", reg, ret);
++
++ return ret;
++}
++
++static int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val)
++{
++ struct spinand_op op;
++ int ret;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_SET_FEATURE;
++ op.n_addr = 1;
++ op.addr[0] = reg;
++ op.n_tx = 1;
++ op.tx_buf = &val;
++
++ ret = spinand_exec_op(spinand, &op);
++ if (ret < 0)
++ pr_err("failed to write register %d (err = %d)\n", reg, ret);
++
++ return ret;
++}
++
++static int spinand_get_cfg(struct spinand_device *spinand, u8 *cfg)
++{
++ return spinand_read_reg_op(spinand, REG_CFG, cfg);
++}
++
++static int spinand_set_cfg(struct spinand_device *spinand, u8 cfg)
++{
++ return spinand_write_reg_op(spinand, REG_CFG, cfg);
++}
++
++static int spinand_read_status(struct spinand_device *spinand, u8 *status)
++{
++ return spinand_read_reg_op(spinand, REG_STATUS, status);
++}
++
++static void spinand_disable_ecc(struct spinand_device *spinand)
++{
++ u8 cfg = 0;
++
++ spinand_get_cfg(spinand, &cfg);
++
++ if ((cfg & CFG_ECC_MASK) == CFG_ECC_ENABLE) {
++ cfg &= ~CFG_ECC_ENABLE;
++ spinand_set_cfg(spinand, cfg);
++ }
++}
++
++static void spinand_enable_ecc(struct spinand_device *spinand)
++{
++ u8 cfg = 0;
++
++ spinand_get_cfg(spinand, &cfg);
++
++ if ((cfg & CFG_ECC_MASK) != CFG_ECC_ENABLE) {
++ cfg |= CFG_ECC_ENABLE;
++ spinand_set_cfg(spinand, cfg);
++ }
++}
++static int spinand_write_enable_op(struct spinand_device *spinand)
++{
++ struct spinand_op op;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_WR_ENABLE;
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_load_page_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = &spinand->base;
++ unsigned int row = nanddev_pos_to_offs(nand, &req->pos);
++ struct spinand_op op;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_PAGE_READ;
++ op.n_addr = 3;
++ unsigned int page = row /nand->memorg.pagesize;
++ unsigned int block = page /nand->memorg.pages_per_eraseblock;
++ op.addr[0] = block >> 10;
++ op.addr[1] = block >> 2;
++ op.addr[2] = ((block & 0x3)<<6)|(page & 0x3f);
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_get_address_bits(u8 opcode)
++{
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 4;
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ return 2;
++ default:
++ return 1;
++ }
++}
++
++static int spinand_get_data_bits(u8 opcode)
++{
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_PROG_LOAD_X4:
++ case SPINAND_CMD_PROG_LOAD_RDM_DATA_X4:
++ return 4;
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ return 2;
++ default:
++ return 1;
++ }
++}
++
++static int spinand_read_from_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = &spinand->base;
++ struct nand_page_io_req adjreq = *req;
++ struct spinand_op op;
++ u16 column = 0;
++ int ret;
++
++ spinand_op_init(&op);
++ op.cmd = spinand->read_cache_op;
++ op.n_addr =3;
++ op.addr_nbits = spinand_get_address_bits(spinand->read_cache_op);
++ if (req->datalen) {
++ adjreq.datalen = nanddev_page_size(nand);
++ adjreq.dataoffs = 0;
++ adjreq.databuf.in = spinand->buf;
++ op.rx_buf = spinand->buf;
++ op.n_rx = adjreq.datalen;
++ }
++
++ if (req->ooblen) {
++ adjreq.ooblen = nanddev_per_page_oobsize(nand);
++ adjreq.ooboffs = 0;
++ adjreq.oobbuf.in = spinand->oobbuf;
++ op.n_rx =nanddev_per_page_oobsize(nand);
++ if (!op.rx_buf) {
++ op.rx_buf = spinand->oobbuf;
++ column = nanddev_page_size(nand);
++ }
++ }
++
++ op.addr[0] =0 ;
++ op.addr[1] = column>>8;
++ op.addr[2] = column;
++ op.data_nbits = spinand_get_data_bits(spinand->read_cache_op);
++ spinand_adjust_cache_op(spinand, &adjreq, &op);
++
++ ret = spinand_exec_op(spinand, &op);
++ if (ret)
++ return ret;
++
++ if (req->datalen)
++ memcpy(req->databuf.in, spinand->buf + req->dataoffs,
++ req->datalen);
++
++ if (req->ooblen)
++ memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs,
++ req->ooblen);
++
++ return 0;
++}
++
++static int spinand_write_to_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = &spinand->base;
++ struct nand_page_io_req adjreq = *req;
++ struct spinand_op op;
++ u16 column = 0;
++
++ spinand_op_init(&op);
++ op.cmd = spinand->write_cache_op;
++ op.n_addr = 2;
++
++ memset(spinand->buf, 0xff,
++ nanddev_page_size(nand) +
++ nanddev_per_page_oobsize(nand));
++
++ if (req->datalen) {
++ memcpy(spinand->buf + req->dataoffs, req->databuf.out,
++ req->datalen);
++ adjreq.dataoffs = 0;
++ adjreq.datalen = nanddev_page_size(nand);
++ adjreq.databuf.out = spinand->buf;
++ op.tx_buf = spinand->buf;
++ op.n_tx = adjreq.datalen;
++ }
++
++ if (req->ooblen) {
++ memcpy(spinand->oobbuf + req->ooboffs, req->oobbuf.out,
++ req->ooblen);
++ memset(spinand->oobbuf,0x00,2);
++ adjreq.ooblen = nanddev_per_page_oobsize(nand);
++ adjreq.ooboffs = 0;
++ op.n_tx = nanddev_page_size(nand)+adjreq.ooblen;
++
++ if (!op.tx_buf) {
++ printk("oob write \n");
++ op.tx_buf = spinand->buf;
++ //column = nanddev_page_size(nand);
++ }
++ }
++
++ op.addr[0] = column >> 8;
++ op.addr[1] = column;
++
++ op.addr_nbits = spinand_get_address_bits(spinand->write_cache_op);
++ op.data_nbits = spinand_get_data_bits(spinand->write_cache_op);
++ spinand_adjust_cache_op(spinand, &adjreq, &op);
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_program_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int row = nanddev_pos_to_offs(nand, &req->pos);
++ struct spinand_op op;
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_PROG_EXC;
++ op.n_addr = 3;
++ unsigned int page = row /nand->memorg.pagesize;
++ unsigned int block = page /nand->memorg.pages_per_eraseblock;
++ op.addr[0] = block >> 10;
++ op.addr[1] = block >> 2;
++ op.addr[2] = ((block & 0x3)<<6)|(page & 0x3f);
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_erase_op(struct spinand_device *spinand,
++ const struct nand_pos *pos)
++{
++ struct nand_device *nand = &spinand->base;
++ unsigned int row = nanddev_pos_to_offs(nand, pos);
++ struct spinand_op op;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_BLK_ERASE;
++ op.n_addr = 3;
++ unsigned int page = row /nand->memorg.pagesize;
++ unsigned int block = page /nand->memorg.pages_per_eraseblock;
++ op.addr[0] = block >> 10;
++ op.addr[1] = block >> 2;
++ op.addr[2] = ((block & 0x3)<<6)|(page & 0x3f);
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_wait(struct spinand_device *spinand, u8 *s)
++{
++ unsigned long timeo = jiffies + msecs_to_jiffies(400);
++ u8 status;
++
++ do {
++ spinand_read_status(spinand, &status);
++ if ((status & STATUS_OIP_MASK) == STATUS_READY)
++ goto out;
++ } while (time_before(jiffies, timeo));
++
++ /*
++ * Extra read, just in case the STATUS_READY bit has changed
++ * since our last check
++ */
++ spinand_read_status(spinand, &status);
++out:
++ if (s)
++ *s = status;
++
++ return (status & STATUS_OIP_MASK) == STATUS_READY ? 0 : -ETIMEDOUT;
++}
++
++static int spinand_read_id_op(struct spinand_device *spinand, u8 *buf,char option)
++{
++ struct spinand_op op;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_READ_ID;
++ op.n_rx = SPINAND_MAX_ID_LEN;
++ op.rx_buf = buf;
++
++ if(option){
++ op.n_addr =1;
++ op.addr[0] =0;
++ }
++
++ return spinand_exec_op(spinand, &op);
++}
++
++static int spinand_reset_op(struct spinand_device *spinand)
++{
++ struct spinand_op op;
++ int ret;
++
++ spinand_op_init(&op);
++ op.cmd = SPINAND_CMD_RESET;
++
++ ret = spinand_exec_op(spinand, &op);
++ if (ret < 0) {
++ pr_err("failed to reset the NAND (err = %d)\n", ret);
++ goto out;
++ }
++
++ ret = spinand_wait(spinand, NULL);
++
++out:
++ return ret;
++}
++
++static int spinand_lock_block(struct spinand_device *spinand, u8 lock)
++{
++ return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock);
++}
++
++static int spinand_read_page(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ int ret;
++
++ spinand_load_page_op(spinand, req);
++
++ ret = spinand_wait(spinand, NULL);
++ if (ret < 0) {
++ pr_err("failed to load page @%llx (err = %d)\n",
++ nanddev_pos_to_offs(nand, &req->pos), ret);
++ return ret;
++ }
++
++ spinand_read_from_cache_op(spinand, req);
++
++ return 0;
++}
++
++static int spinand_write_page(struct spinand_device *spinand,
++ const struct nand_page_io_req *req)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ u8 status;
++ int ret = 0;
++
++ spinand_write_enable_op(spinand);
++ spinand_write_to_cache_op(spinand, req);
++ spinand_program_op(spinand, req);
++
++ ret = spinand_wait(spinand, &status);
++ if (!ret && (status & STATUS_P_FAIL_MASK) == STATUS_P_FAIL)
++ ret = -EIO;
++
++ if (ret < 0)
++ pr_err("failed to program page @%llx (err = %d)\n",
++ nanddev_pos_to_offs(nand, &req->pos), ret);
++
++ return ret;
++}
++
++static int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
++ struct mtd_oob_ops *ops)
++{
++ struct spinand_device *spinand = mtd_to_spinand(mtd);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct nand_io_iter iter;
++ int ret;
++
++ mutex_lock(&spinand->lock);
++ nanddev_io_for_each_page(nand, from, ops, &iter) {
++ ret = spinand_read_page(spinand, &iter.req);
++ if (ret)
++ break;
++
++ ops->retlen += iter.req.datalen;
++ ops->oobretlen += iter.req.datalen;
++ }
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
++ struct mtd_oob_ops *ops)
++{
++ struct spinand_device *spinand = mtd_to_spinand(mtd);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct nand_io_iter iter;
++ int ret = 0;
++ mutex_lock(&spinand->lock);
++ nanddev_io_for_each_page(nand, to, ops, &iter) {
++ ret = spinand_write_page(spinand, &iter.req);
++ if (ret)
++ return ret;
++
++ ops->retlen += iter.req.datalen;
++ ops->oobretlen += iter.req.ooblen;
++ }
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos)
++{
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ struct nand_page_io_req req = {
++ .pos = *pos,
++ .ooblen = 2,
++ .ooboffs = 0,
++ .oobbuf.in = spinand->oobbuf,
++ };
++
++ memset(spinand->oobbuf, 0x00, 2);
++ spinand_read_page(spinand, &req);
++ if (spinand->oobbuf[0] != 0xff || spinand->oobbuf[1] != 0xff)
++ return true;
++
++ return false;
++}
++
++static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ struct nand_pos pos;
++ int ret;
++ nanddev_offs_to_pos(nand, offs, &pos);
++ mutex_lock(&spinand->lock);
++ ret = spinand_isbad(nand, &pos);
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos)
++{
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ struct nand_page_io_req req = {
++ .pos = *pos,
++ .ooboffs = 0,
++ .ooblen = 2,
++ .oobbuf.out = spinand->oobbuf,
++ };
++
++ /* Erase block before marking it bad. */
++ spinand_write_enable_op(spinand);
++ spinand_erase_op(spinand, pos);
++ u8 status;
++ spinand_wait(spinand, &status);
++
++ memset(spinand->oobbuf, 0x00, 2);
++ return spinand_write_page(spinand, &req);
++}
++
++
++static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ struct nand_pos pos;
++ int ret;
++ nanddev_offs_to_pos(nand, offs, &pos);
++ /*bad block mark the first page*/
++ pos.page=0;
++
++ mutex_lock(&spinand->lock);
++ ret = nanddev_markbad(nand, &pos);
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static int spinand_erase(struct nand_device *nand, const struct nand_pos *pos)
++{
++ struct spinand_device *spinand = nand_to_spinand(nand);
++ u8 status;
++ int ret;
++
++ spinand_write_enable_op(spinand);
++ spinand_erase_op(spinand, pos);
++
++ ret = spinand_wait(spinand, &status);
++
++ if (!ret && (status & STATUS_E_FAIL_MASK) == STATUS_E_FAIL)
++ ret = -EIO;
++
++ if (ret)
++ pr_err("failed to erase block %d (err = %d)\n",
++ pos->eraseblock, ret);
++
++ return ret;
++}
++
++static int spinand_mtd_erase(struct mtd_info *mtd,
++ struct erase_info *einfo)
++{
++ struct spinand_device *spinand = mtd_to_spinand(mtd);
++ int ret;
++// printk("erase block\n");
++ mutex_lock(&spinand->lock);
++ ret = nanddev_mtd_erase(mtd, einfo);
++ mutex_unlock(&spinand->lock);
++
++ //if (!ret)
++ // mtd_erase_callback(einfo);
++
++ return ret;
++}
++
++static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
++{
++ struct spinand_device *spinand = mtd_to_spinand(mtd);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct nand_pos pos;
++ int ret;
++
++ nanddev_offs_to_pos(nand, offs, &pos);
++ mutex_lock(&spinand->lock);
++ ret = nanddev_isreserved(nand, &pos);
++ mutex_unlock(&spinand->lock);
++
++ return ret;
++}
++
++static void spinand_set_rd_wr_op(struct spinand_device *spinand)
++{
++ u32 controller_cap = spinand->controller.controller->caps;
++ u32 rw_mode = spinand->rw_mode;
++
++ if ((controller_cap & SPINAND_CAP_RD_QUAD) &&
++ (rw_mode & SPINAND_RD_QUAD))
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_QUAD_IO;
++ else if ((controller_cap & SPINAND_CAP_RD_X4) &&
++ (rw_mode & SPINAND_RD_X4))
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_X4;
++ else if ((controller_cap & SPINAND_CAP_RD_DUAL) &&
++ (rw_mode & SPINAND_RD_DUAL))
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_DUAL_IO;
++ else if ((controller_cap & SPINAND_CAP_RD_X2) &&
++ (rw_mode & SPINAND_RD_X2))
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_X2;
++ else
++ spinand->read_cache_op = SPINAND_CMD_READ_FROM_CACHE_FAST;
++
++ if ((controller_cap & SPINAND_CAP_WR_X4) &&
++ (rw_mode & SPINAND_WR_X4))
++ spinand->write_cache_op = SPINAND_CMD_PROG_LOAD_X4;
++ else
++ spinand->write_cache_op = SPINAND_CMD_PROG_LOAD;
++}
++
++static const struct nand_ops spinand_ops = {
++ .erase = spinand_erase,
++ .markbad = spinand_markbad,
++ .isbad = spinand_isbad,
++};
++
++static const struct spinand_manufacturer *spinand_manufacturers[] = {
++ &micron_spinand_manufacturer,
++ &etron_spinand_manufacturer,
++ &giga_spinand_manufacturer,
++ &paragon_spinand_manufacturer,
++ &mxic_spinand_manufacturer,
++};
++
++
++static int spinand_manufacturer_detect(struct spinand_device *spinand)
++{
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(spinand_manufacturers); i++) {
++ if (spinand_manufacturers[i]->ops->detect(spinand)) {
++ spinand->manufacturer.manu = spinand_manufacturers[i];
++
++ return 0;
++ }
++ }
++
++ return -ENODEV;
++}
++
++static int spinand_manufacturer_init(struct spinand_device *spinand)
++{
++ if (spinand->manufacturer.manu->ops->init)
++ return spinand->manufacturer.manu->ops->init(spinand);
++
++ return 0;
++}
++
++static void spinand_manufacturer_cleanup(struct spinand_device *spinand)
++{
++ /* Release manufacturer private data */
++ if (spinand->manufacturer.manu->ops->cleanup)
++ return spinand->manufacturer.manu->ops->cleanup(spinand);
++}
++static int spinand_detect(struct spinand_device *spinand)
++{
++ struct nand_device *nand = &spinand->base;
++ int ret=-1;
++ char option=0;
++
++ spinand_reset_op(spinand);
++ spinand->id.len = SPINAND_MAX_ID_LEN;
++ for(option=0;option<2;option++){
++ spinand_read_id_op(spinand, spinand->id.data,option);
++ ret = spinand_manufacturer_detect(spinand);
++ if(ret==0)
++ break;
++ }
++ if (ret) {
++ pr_err("unknown raw ID %*phN\n",
++ SPINAND_MAX_ID_LEN, spinand->id.data);
++ return ret;
++ }
++
++ pr_info("%s SPI NAND was found.\n", spinand->manufacturer.manu->name);
++ pr_info("%d MiB, block size: %d KiB, page size: %d, OOB size: %d\n",
++ (int)(nanddev_size(nand) >> 20),
++ nanddev_eraseblock_size(nand) >> 10,
++ nanddev_page_size(nand), nanddev_per_page_oobsize(nand));
++ return 0;
++}
++/**
++ * devm_spinand_alloc - [SPI NAND Interface] allocate SPI NAND device instance
++ * @dev: pointer to device model structure
++ */
++struct spinand_device *devm_spinand_alloc(struct device *dev)
++{
++ struct spinand_device *spinand;
++ struct mtd_info *mtd;
++
++ spinand = devm_kzalloc(dev, sizeof(*spinand), GFP_KERNEL);
++ if (!spinand)
++ return ERR_PTR(-ENOMEM);
++
++ spinand_set_of_node(spinand, dev->of_node);
++ mutex_init(&spinand->lock);
++ mtd = spinand_to_mtd(spinand);
++ mtd->dev.parent = dev;
++
++ return spinand;
++}
++EXPORT_SYMBOL_GPL(devm_spinand_alloc);
++static int spinand_read(struct mtd_info *mtd, loff_t from, size_t len,size_t *retlen, u_char *buf)
++{
++ int ret;
++ struct mtd_oob_ops ops = {
++ .len = len,
++ .datbuf = buf,
++ };
++ ret = mtd->_read_oob(mtd, from, &ops);
++ *retlen = ops.retlen;
++
++ if (unlikely(ret < 0))
++ return ret;
++ if (mtd->ecc_strength == 0)
++ return 0; /* device lacks ecc */
++ return ret >= mtd->bitflip_threshold ? -EUCLEAN : 0;
++}
++
++static int spinand_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,const u_char *buf)
++{
++ struct mtd_oob_ops ops = {
++ .len = len,
++ .datbuf = (u8 *)buf,
++ };
++ int ret;
++
++ ret = mtd->_write_oob(mtd, to, &ops);
++ *retlen = ops.retlen;
++ return ret;
++
++}
++
++int spinand_bbt_create(struct nand_device *nand )
++{
++ unsigned int block=0;
++ unsigned int entry=0;
++ int status=NAND_BBT_BLOCK_STATUS_UNKNOWN;
++ int ret = 0;
++ struct nand_pos pos;
++ struct mtd_info *mtd = nanddev_to_mtd(nand);
++ if (nanddev_bbt_is_initialized(nand)) {
++ for(block=0;block < nand->memorg.eraseblocks_per_lun;block++){
++ pos.eraseblock=block;
++ pos.lun=0;
++ pos.page=0;
++ pos.plane=0;
++ pos.target=0;
++ entry = nanddev_bbt_pos_to_entry(nand, &pos);
++ if(nand->ops->isbad(nand, &pos)){
++ printk("found bad block %llx\n",nanddev_pos_to_offs(nand,&pos));
++ ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_FACTORY_BAD);
++ ret = nanddev_bbt_update(nand);
++ mtd->ecc_stats.badblocks++;
++ }
++ else{
++ nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_GOOD);
++ }
++ }
++
++ }
++ return 0;
++
++}
++int write_test(struct mtd_info *mtd,loff_t to,size_t len)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ size_t retlen;
++ unsigned char *buf;
++ int i=0;
++
++ buf = kzalloc(nanddev_page_size(nand) +
++ nanddev_per_page_oobsize(nand),
++ GFP_KERNEL);
++ for(i=0;i<len;i++){
++ buf[i]=i%16;
++ }
++ spinand_write(mtd,to,len,&retlen,buf);
++ kfree(buf);
++ return 0;
++}
++int erase_test(struct mtd_info *mtd,uint64_t from,uint64_t len)
++{
++ struct erase_info einfo={
++ .mtd=mtd,
++ .addr=from,
++ .len=len,
++ .callback = NULL,
++ };
++
++ spinand_mtd_erase(mtd,&einfo);
++ return 0;
++}
++int read_test(struct mtd_info *mtd,loff_t from,size_t len)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ size_t retlen;
++ unsigned char *buf;
++ int i=0;
++ char en=16;
++ buf = kzalloc(nanddev_page_size(nand) +
++ nanddev_per_page_oobsize(nand),
++ GFP_KERNEL);
++ spinand_read(mtd,from,len,&retlen,buf);
++ for(i=0;i<len;i=i+en){
++ if(en==16){
++ printk("%2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X\n",\
++ buf[i],buf[i+1],buf[i+2],buf[i+3],buf[i+4],buf[i+5],buf[i+6],buf[i+7],buf[i+8],buf[i+9],\
++ buf[i+10],buf[i|11],buf[i+12],buf[i+13],buf[i+14],buf[i+15]);
++ }
++ else{
++ printk("%2X %2X %2X %2X %2X %2X %2X %2X\n",\
++ buf[i],buf[i+1],buf[i+2],buf[i+3],buf[i+4],buf[i+5],buf[i+6],buf[i+7]);
++ }
++ if(i==2032){
++ i=i+8;
++ en=8;
++ }
++ }
++ kfree(buf);
++ return 0;
++}
++
++int mark_bad_test(struct mtd_info *mtd,loff_t offs)
++{
++ return spinand_mtd_block_markbad(mtd,offs);
++}
++/**
++ * spinand_init - [SPI NAND Interface] initialize the SPI NAND device
++ * @spinand: SPI NAND device structure
++ */
++int spinand_init(struct spinand_device *spinand, struct module *owner)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ int ret;
++
++ ret = spinand_detect(spinand);
++ if (ret) {
++ pr_err("Failed to detect a SPI NAND (err = %d).\n", ret);
++ return ret;
++ }
++
++ ret = nanddev_init(nand, &spinand_ops, owner);
++ if (ret)
++ return ret;
++
++ spinand_set_rd_wr_op(spinand);
++
++ /*
++ * Use kzalloc() instead of devm_kzalloc() here, because some drivers
++ * may use this buffer for DMA access.
++ * Memory allocated by devm_ does not guarantee DMA-safe alignment.
++ */
++ spinand->buf = kzalloc(nanddev_page_size(nand) +
++ nanddev_per_page_oobsize(nand),
++ GFP_KERNEL);
++ if (!spinand->buf)
++ return -ENOMEM;
++
++ spinand->oobbuf = spinand->buf + nanddev_page_size(nand);
++
++ ret = spinand_manufacturer_init(spinand);
++ if (ret) {
++ pr_err("Init of SPI NAND failed (err = %d).\n", ret);
++ goto err_free_buf;
++ }
++
++ /*
++ * Right now, we don't support ECC, so let the whole oob
++ * area is available for user.
++ */
++ mtd->_read_oob = spinand_mtd_read;
++ mtd->_write_oob = spinand_mtd_write;
++ mtd->_block_isbad = spinand_mtd_block_isbad;
++ mtd->_block_markbad = spinand_mtd_block_markbad;
++ mtd->_block_isreserved = spinand_mtd_block_isreserved;
++ mtd->_erase = spinand_mtd_erase;
++ mtd->_read = spinand_read;
++ mtd->_write = spinand_write;
++
++ /* After power up, all blocks are locked, so unlock it here. */
++ spinand_lock_block(spinand, BL_ALL_UNLOCKED);
++ /* Right now, we don't support ECC, so disable on-die ECC */
++ //spinand_disable_ecc(spinand);
++ spinand_enable_ecc(spinand);
++
++ return 0;
++
++err_free_buf:
++ kfree(spinand->buf);
++ return ret;
++}
++EXPORT_SYMBOL_GPL(spinand_init);
++/**
++ * spinand_cleanup - clean SPI NAND device
++ * @spinand: SPI NAND device structure
++ */
++void spinand_cleanup(struct spinand_device *spinand)
++{
++ struct nand_device *nand = &spinand->base;
++
++ spinand_manufacturer_cleanup(spinand);
++ kfree(spinand->buf);
++ nanddev_cleanup(nand);
++}
++EXPORT_SYMBOL_GPL(spinand_cleanup);
++
++MODULE_DESCRIPTION("SPI NAND framework");
++MODULE_AUTHOR("Peter Pan<peterpandong@micron.com>");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/etron.c
+@@ -0,0 +1,156 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_ETRON 0xD5
++
++struct etron_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define ETRON_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct etron_spinand_info etron_spinand_table[] = {
++ ETRON_SPI_NAND_INFO("ETNORxxxx", 0x11,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int etron_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 2;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * etron_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool etron_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct etron_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(etron_spinand_table); i++) {
++ item = (struct etron_spinand_info *)etron_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * etron_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool etron_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if (id[0] != SPINAND_MFR_ETRON)
++ return false;
++
++ return etron_spinand_scan_id_table(spinand, id[1]);
++}
++
++/**
++ * etron_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void etron_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++
++ /*
++ * No need to specify the plane number if there's only one plane per
++ * LUN.
++ */
++ /*if (nand->memorg.planes_per_lun < 2)
++ return;*/
++
++ /* The plane number is passed in MSB just above the column address */
++ //shift = fls(nand->memorg.pagesize);
++ /*op->n_addr= 2;
++ op->addr[0] = op->addr[1];
++ op->addr[1] = op->addr[2];
++ op->addr[2] = 0;*/
++ op->dummy_bytes = etron_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops etron_spinand_manuf_ops = {
++ .detect = etron_spinand_detect,
++ .adjust_cache_op = etron_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer etron_spinand_manufacturer = {
++ .id = SPINAND_MFR_ETRON,
++ .name = "Etron",
++ .ops = &etron_spinand_manuf_ops,
++};
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/generic-spinand-controller.c
+@@ -0,0 +1,187 @@
++/*
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/spi/spi.h>
++#include "spinand.h"
++#include <linux/mtd/mtd.h>
++
++struct gen_spinand_controller {
++ struct spinand_controller ctrl;
++ struct spi_device *spi;
++};
++
++#define to_gen_spinand_controller(c) \
++ container_of(c, struct gen_spinand_controller, ctrl)
++
++/*
++ * gen_spinand_controller_exec_op - to process a command to send to the
++ * SPI NAND by generic SPI bus
++ * @spinand: SPI NAND device structure
++ * @op: SPI NAND operation descriptor
++ */
++static int gen_spinand_controller_exec_op(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ struct spi_message message;
++ struct spi_transfer x[3];
++ struct spinand_controller *spinand_controller;
++ struct gen_spinand_controller *controller;
++
++ spinand_controller = spinand->controller.controller;
++ controller = to_gen_spinand_controller(spinand_controller);
++ spi_message_init(&message);
++ memset(x, 0, sizeof(x));
++ x[0].len = 1;
++ x[0].tx_nbits = 1;
++ x[0].tx_buf = &op->cmd;
++ spi_message_add_tail(&x[0], &message);
++
++ if (op->n_addr + op->dummy_bytes) {
++ x[1].len = op->n_addr + op->dummy_bytes;
++ x[1].tx_nbits = op->addr_nbits;
++ x[1].tx_buf = op->addr;
++ //printk("cmd:%2X,naddr:%d,[%2X][%2X][%2X]\n",op->cmd,op->n_addr,op->addr[0],op->addr[1],op->addr[2]);
++ spi_message_add_tail(&x[1], &message);
++ }
++
++ if (op->n_tx) {
++ x[2].len = op->n_tx;
++ x[2].tx_nbits = op->data_nbits;
++ x[2].tx_buf = op->tx_buf;
++ spi_message_add_tail(&x[2], &message);
++ } else if (op->n_rx) {
++ x[2].len = op->n_rx;
++ x[2].rx_nbits = op->data_nbits;
++ x[2].rx_buf = op->rx_buf;
++ spi_message_add_tail(&x[2], &message);
++ }
++
++ return spi_sync(controller->spi, &message);
++}
++
++static struct spinand_controller_ops gen_spinand_controller_ops = {
++ .exec_op = gen_spinand_controller_exec_op,
++};
++extern int read_test(struct mtd_info *mtd,loff_t from,size_t len);
++extern int erase_test(struct mtd_info *mtd,uint64_t from,uint64_t len);
++extern int write_test(struct mtd_info *mtd,loff_t to,size_t len);
++extern int spinand_bbt_create(struct nand_device *nand );
++extern int mark_bad_test(struct mtd_info *mtd,loff_t offs);
++static int gen_spinand_controller_probe(struct spi_device *spi)
++{
++ struct spinand_device *spinand;
++ struct gen_spinand_controller *controller;
++ struct spinand_controller *spinand_controller;
++ struct device *dev = &spi->dev;
++ u16 mode = spi->mode;
++ int ret;
++
++ spinand = devm_spinand_alloc(dev);
++ if (IS_ERR(spinand)) {
++ ret = PTR_ERR(spinand);
++ goto out;
++ }
++
++ controller = devm_kzalloc(dev, sizeof(*controller), GFP_KERNEL);
++ if (!controller) {
++ ret = -ENOMEM;
++ goto out;
++ }
++
++ controller->spi = spi;
++ spinand_controller = &controller->ctrl;
++ spinand_controller->ops = &gen_spinand_controller_ops;
++ spinand_controller->caps = SPINAND_CAP_RD_X1 | SPINAND_CAP_WR_X1;
++
++ if ((mode & SPI_RX_QUAD) && (mode & SPI_TX_QUAD))
++ spinand_controller->caps |= SPINAND_CAP_RD_QUAD;
++
++ if ((mode & SPI_RX_DUAL) && (mode & SPI_TX_DUAL))
++ spinand_controller->caps |= SPINAND_CAP_RD_DUAL;
++
++ if (mode & SPI_RX_QUAD)
++ spinand_controller->caps |= SPINAND_CAP_RD_X4;
++
++ if (mode & SPI_RX_DUAL)
++ spinand_controller->caps |= SPINAND_CAP_RD_X2;
++
++ if (mode & SPI_TX_QUAD)
++ spinand_controller->caps |= SPINAND_CAP_WR_QUAD |
++ SPINAND_CAP_WR_X4;
++
++ if (mode & SPI_TX_DUAL)
++ spinand_controller->caps |= SPINAND_CAP_WR_DUAL |
++ SPINAND_CAP_WR_X2;
++
++ spinand->controller.controller = spinand_controller;
++ spi_set_drvdata(spi, spinand);
++
++ ret = spinand_init(spinand, THIS_MODULE);
++ if (ret)
++ goto out;
++
++ ret = mtd_device_register(spinand_to_mtd(spinand), NULL, 0);
++ struct nand_device *nand =spinand_to_nand(spinand);
++ spinand_bbt_create(nand);
++ //mark_bad_test(spinand_to_mtd(spinand),0x00);
++ /*
++ int i=0,status=0;
++ unsigned int entry=0;
++ struct nand_pos pos;
++ for(i=0;i<1024;i++){
++
++ erase_test(spinand_to_mtd(spinand),i*0x20000,0x20000);
++ }*/
++ //erase_test(spinand_to_mtd(spinand),0x00,0x20000);
++ //write_test(spinand_to_mtd(spinand),0x000,2048);
++ //read_test(spinand_to_mtd(spinand),0x000,2048);
++ //mark_bad_test(spinand_to_mtd(spinand),0);
++out:
++ return ret;
++}
++
++static int gen_spinand_controller_remove(struct spi_device *spi)
++{
++ struct spinand_device *spinand = spi_get_drvdata(spi);
++ int ret;
++
++ ret = mtd_device_unregister(spinand_to_mtd(spinand));
++ if (ret)
++ return ret;
++
++ spinand_cleanup(spinand);
++
++ return 0;
++}
++
++static const struct of_device_id spinand_glinet_dt[] = {
++ { .compatible = "spinand,glinet", },
++ {}
++};
++
++static struct spi_driver gen_spinand_controller_driver = {
++ .driver = {
++ .name = "generic-spinand-controller",
++ .of_match_table = spinand_glinet_dt,
++ .owner = THIS_MODULE,
++ },
++ .probe = gen_spinand_controller_probe,
++ .remove = gen_spinand_controller_remove,
++};
++module_spi_driver(gen_spinand_controller_driver);
++
++MODULE_DESCRIPTION("Generic SPI NAND controller");
++MODULE_AUTHOR("Peter Pan <peterpandong@micron.com>");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/gigadevice.c
+@@ -0,0 +1,161 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_GIGA 0xC8
++
++struct giga_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define GIGA_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct giga_spinand_info giga_spinand_table[] = {
++ GIGA_SPI_NAND_INFO("GD5F1GQ4UF", 0xB1,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++ GIGA_SPI_NAND_INFO("GD5F1GQ4UE", 0xD1,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++ GIGA_SPI_NAND_INFO("GD5F1GQ5UE", 0x51,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int giga_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 2;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * giga_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool giga_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct giga_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(giga_spinand_table); i++) {
++ item = (struct giga_spinand_info *)giga_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * giga_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool giga_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if ((id[0] != SPINAND_MFR_GIGA) && (id[1] != SPINAND_MFR_GIGA))
++ return false;
++
++ /*if(id[1] == SPINAND_MFR_GIGA){
++ return giga_spinand_scan_id_table(spinand, id[2]);
++ }*/
++
++ return giga_spinand_scan_id_table(spinand, id[1]);
++}
++
++/**
++ * giga_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void giga_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++
++ //for GD5F1GQ4XE and GD5F1GQ5XE,the dummy byte position at byte3 when read from cache
++ if(((spinand->id.data[1] == 0xd1) || (spinand->id.data[1] == 0x51)) && (op->cmd == spinand->read_cache_op)){
++ op->n_addr= 2;
++ op->addr[0] = op->addr[1];
++ op->addr[1] = op->addr[2];
++ op->addr[2] = 0;
++ }
++ op->dummy_bytes = giga_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops giga_spinand_manuf_ops = {
++ .detect = giga_spinand_detect,
++ .adjust_cache_op = giga_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer giga_spinand_manufacturer = {
++ .id = SPINAND_MFR_GIGA,
++ .name = "Giga",
++ .ops = &giga_spinand_manuf_ops,
++};
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/micron.c
+@@ -0,0 +1,153 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_MICRON 0x2C
++
++struct micron_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define MICRON_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct micron_spinand_info micron_spinand_table[] = {
++ MICRON_SPI_NAND_INFO("MT29F2G01ABAGD", 0x24,
++ NAND_MEMORG(1, 2048, 128, 64, 2048, 2, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int micron_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 2;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * micron_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool micron_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct micron_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(micron_spinand_table); i++) {
++ item = (struct micron_spinand_info *)micron_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * micron_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool micron_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if (id[1] != SPINAND_MFR_MICRON)
++ return false;
++
++ return micron_spinand_scan_id_table(spinand, id[2]);
++}
++
++/**
++ * micron_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void micron_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++
++ /*
++ * No need to specify the plane number if there's only one plane per
++ * LUN.
++ */
++ if (nand->memorg.planes_per_lun < 2)
++ return;
++
++ /* The plane number is passed in MSB just above the column address */
++ shift = fls(nand->memorg.pagesize);
++ op->addr[(16 - shift) / 8] |= req->pos.plane << (shift % 8);
++ op->dummy_bytes = micron_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = {
++ .detect = micron_spinand_detect,
++ .adjust_cache_op = micron_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer micron_spinand_manufacturer = {
++ .id = SPINAND_MFR_MICRON,
++ .name = "Micron",
++ .ops = &micron_spinand_manuf_ops,
++};
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/nand_core.c
+@@ -0,0 +1,213 @@
++/*
++ * Copyright (c) 2017 Free Electrons
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * Authors:
++ * Boris Brezillon <boris.brezillon@free-electrons.com>
++ * Peter Pan <peterpandong@micron.com>
++ */
++
++#define pr_fmt(fmt) "nand: " fmt
++
++#include <linux/mtd/rawnand.h>
++#include "spinand.h"
++
++bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos)
++{
++#if 1
++ if (nanddev_bbt_is_initialized(nand)) {
++ unsigned int entry=0;
++ int status=0;
++
++ entry = nanddev_bbt_pos_to_entry(nand, pos);
++ status = nanddev_bbt_get_block_status(nand, entry);
++ /* Lazy block status retrieval */
++ if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) {
++ if (nand->ops->isbad(nand, pos))
++ status = NAND_BBT_BLOCK_FACTORY_BAD;
++ else
++ status = NAND_BBT_BLOCK_GOOD;
++
++ nanddev_bbt_set_block_status(nand, entry, status);
++ }
++ //printk("status %llx,%x\n",nanddev_pos_to_offs(nand, pos),status);
++ if (status == NAND_BBT_BLOCK_WORN ||
++ status == NAND_BBT_BLOCK_FACTORY_BAD)
++ return true;
++
++ return false;
++ }
++#endif
++ return nand->ops->isbad(nand, pos);
++}
++EXPORT_SYMBOL_GPL(nanddev_isbad);
++
++/**
++ * nanddev_markbad - Write a bad block marker to a block
++ * @nand: NAND device
++ * @block: block to mark bad
++ *
++ * Mark a block bad. This function is updating the BBT if available and
++ * calls the low-level markbad hook (nand->ops->markbad()) if
++ * NAND_BBT_NO_OOB_BBM is not set.
++ *
++ * Return: 0 in case of success, a negative error code otherwise.
++ */
++int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos)
++{
++ struct mtd_info *mtd = nanddev_to_mtd(nand);
++ unsigned int entry;
++ int ret = 0;
++ if (nanddev_isbad(nand, pos))
++ return 0;
++
++ ret = nand->ops->markbad(nand, pos);
++ if (ret)
++ pr_warn("failed to write BBM to block @%llx (err = %d)\n",
++ nanddev_pos_to_offs(nand, pos), ret);
++
++ if (!nanddev_bbt_is_initialized(nand))
++ goto out;
++
++ entry = nanddev_bbt_pos_to_entry(nand, pos);
++ ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN);
++ if (ret)
++ goto out;
++
++ ret = nanddev_bbt_update(nand);
++
++out:
++ if (!ret)
++ mtd->ecc_stats.badblocks++;
++
++ return ret;
++}
++EXPORT_SYMBOL_GPL(nanddev_markbad);
++
++bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos)
++{
++ unsigned int entry;
++ int status;
++
++ if (!nanddev_bbt_is_initialized(nand))
++ return false;
++
++ /* Return info from the table */
++ entry = nanddev_bbt_pos_to_entry(nand, pos);
++ status = nanddev_bbt_get_block_status(nand, entry);
++ return status == NAND_BBT_BLOCK_RESERVED;
++}
++EXPORT_SYMBOL_GPL(nanddev_isreserved);
++
++/**
++ * nanddev_erase - Erase a NAND portion
++ * @nand: NAND device
++ * @block: eraseblock to erase
++ *
++ * Erase @block block if it's not bad.
++ *
++ * Return: 0 in case of success, a negative error code otherwise.
++ */
++
++int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos)
++{
++ if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) {
++ //pr_warn("attempt to erase a bad/reserved block @%llx\n",
++ // nanddev_pos_to_offs(nand, pos));
++ return -EIO;
++ }
++
++ return nand->ops->erase(nand, pos);
++}
++EXPORT_SYMBOL_GPL(nanddev_erase);
++
++int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo)
++{
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct nand_pos pos, last;
++ int ret;
++
++ nanddev_offs_to_pos(nand, einfo->addr, &pos);
++ nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last);
++ while (nanddev_pos_cmp(&pos, &last) <= 0) {
++ ret = nanddev_erase(nand, &pos);
++ if (ret) {
++ einfo->fail_addr = nanddev_pos_to_offs(nand, &pos);
++ einfo->state = MTD_ERASE_FAILED;
++ //printk("erase failed ....\n");
++ return ret;
++ }
++
++ nanddev_pos_next_eraseblock(nand, &pos);
++ }
++
++ einfo->state = MTD_ERASE_DONE;
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(nanddev_mtd_erase);
++
++/**
++ * nanddev_init - Initialize a NAND device
++ * @nand: NAND device
++ * @memorg: NAND memory organization descriptor
++ * @ops: NAND device operations
++ *
++ * Initialize a NAND device object. Consistency checks are done on @memorg and
++ * @ops.
++ *
++ * Return: 0 in case of success, a negative error code otherwise.
++ */
++int nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
++ struct module *owner)
++{
++ struct mtd_info *mtd = nanddev_to_mtd(nand);
++ struct nand_memory_organization *memorg = nanddev_get_memorg(nand);
++
++ if (!nand || !ops)
++ return -EINVAL;
++
++ if (!ops->erase || !ops->markbad || !ops->isbad)
++ return -EINVAL;
++
++ if (!memorg->bits_per_cell || !memorg->pagesize ||
++ !memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun ||
++ !memorg->planes_per_lun || !memorg->luns_per_target ||
++ !memorg->ntargets)
++ return -EINVAL;
++
++ nand->rowconv.eraseblock_addr_shift = fls(memorg->pagesize);
++ nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun) +
++ nand->rowconv.eraseblock_addr_shift;
++
++ nand->ops = ops;
++
++ mtd->type = memorg->bits_per_cell == 1 ?
++ MTD_NANDFLASH : MTD_MLCNANDFLASH;
++ mtd->flags = MTD_CAP_NANDFLASH;
++ mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock;
++ mtd->writesize = memorg->pagesize;
++ mtd->oobsize = memorg->oobsize;
++ mtd->writebufsize = memorg->pagesize;
++ mtd->size = nanddev_size(nand);
++ mtd->owner = owner;
++
++ return nanddev_bbt_init(nand);
++}
++EXPORT_SYMBOL_GPL(nanddev_init);
++
++void nanddev_cleanup(struct nand_device *nand)
++{
++ if (nanddev_bbt_is_initialized(nand))
++ nanddev_bbt_cleanup(nand);
++}
++EXPORT_SYMBOL_GPL(nanddev_cleanup);
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/spinand.h
+@@ -0,0 +1,765 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++#ifndef __LINUX_MTD_SPINAND_H
++#define __LINUX_MTD_SPINAND_H
++
++#include <linux/mutex.h>
++#include <linux/bitops.h>
++#include <linux/device.h>
++#include <linux/mtd/mtd.h>
++#include <linux/mtd/rawnand.h>
++#include <linux/of.h>
++
++/**
++ * Standard SPI NAND flash commands
++ */
++#define SPINAND_CMD_RESET 0xff
++#define SPINAND_CMD_GET_FEATURE 0x0f
++#define SPINAND_CMD_SET_FEATURE 0x1f
++#define SPINAND_CMD_PAGE_READ 0x13
++#define SPINAND_CMD_READ_FROM_CACHE 0x03
++#define SPINAND_CMD_READ_FROM_CACHE_FAST 0x0b
++#define SPINAND_CMD_READ_FROM_CACHE_X2 0x3b
++#define SPINAND_CMD_READ_FROM_CACHE_DUAL_IO 0xbb
++#define SPINAND_CMD_READ_FROM_CACHE_X4 0x6b
++#define SPINAND_CMD_READ_FROM_CACHE_QUAD_IO 0xeb
++#define SPINAND_CMD_BLK_ERASE 0xd8
++#define SPINAND_CMD_PROG_EXC 0x10
++#define SPINAND_CMD_PROG_LOAD 0x02
++#define SPINAND_CMD_PROG_LOAD_RDM_DATA 0x84
++#define SPINAND_CMD_PROG_LOAD_X4 0x32
++#define SPINAND_CMD_PROG_LOAD_RDM_DATA_X4 0x34
++#define SPINAND_CMD_READ_ID 0x9f
++#define SPINAND_CMD_WR_DISABLE 0x04
++#define SPINAND_CMD_WR_ENABLE 0x06
++
++/* feature register */
++#define REG_BLOCK_LOCK 0xa0
++#define REG_CFG 0xb0
++#define REG_STATUS 0xc0
++
++/* status register */
++#define STATUS_OIP_MASK BIT(0)
++#define STATUS_CRBSY_MASK BIT(7)
++#define STATUS_READY 0
++#define STATUS_BUSY BIT(0)
++
++#define STATUS_E_FAIL_MASK BIT(2)
++#define STATUS_E_FAIL BIT(2)
++
++#define STATUS_P_FAIL_MASK BIT(3)
++#define STATUS_P_FAIL BIT(3)
++
++/* configuration register */
++#define CFG_ECC_MASK BIT(4)
++#define CFG_ECC_ENABLE BIT(4)
++
++/* block lock register */
++#define BL_ALL_UNLOCKED 0X00
++
++struct spinand_op;
++struct spinand_device;
++struct nand_device;
++
++/**
++ * struct nand_memory_organization - memory organization structure
++ * @bits_per_cell: number of bits per NAND cell
++ * @pagesize: page size
++ * @oobsize: OOB area size
++ * @pages_per_eraseblock: number of pages per eraseblock
++ * @eraseblocks_per_die: number of eraseblocks per die
++ * @ndies: number of dies
++ */
++struct nand_memory_organization {
++ unsigned int bits_per_cell;
++ unsigned int pagesize;
++ unsigned int oobsize;
++ unsigned int pages_per_eraseblock;
++ unsigned int eraseblocks_per_lun;
++ unsigned int planes_per_lun;
++ unsigned int luns_per_target;
++ unsigned int ntargets;
++};
++
++#define NAND_MEMORG(bpc, ps, os, ppe, epl, ppl, lpt, nt) \
++ { \
++ .bits_per_cell = (bpc), \
++ .pagesize = (ps), \
++ .oobsize = (os), \
++ .pages_per_eraseblock = (ppe), \
++ .eraseblocks_per_lun = (epl), \
++ .planes_per_lun = (ppl), \
++ .luns_per_target = (lpt), \
++ .ntargets = (nt), \
++ }
++
++/**
++ * struct nand_bbt - bad block table structure
++ * @cache: in memory BBT cache
++ */
++struct nand_bbt {
++ unsigned char *cache;
++};
++
++struct nand_row_converter {
++ unsigned int lun_addr_shift;
++ unsigned int eraseblock_addr_shift;
++};
++
++struct nand_pos {
++ unsigned int target;
++ unsigned int lun;
++ unsigned int plane;
++ unsigned int eraseblock;
++ unsigned int page;
++};
++
++struct nand_page_io_req {
++ struct nand_pos pos;
++ unsigned int dataoffs;
++ unsigned int datalen;
++ union {
++ const void *out;
++ void *in;
++ } databuf;
++ unsigned int ooboffs;
++ unsigned int ooblen;
++ union {
++ const void *out;
++ void *in;
++ } oobbuf;
++};
++/**
++ * struct nand_ops - NAND operations
++ * @erase: erase a specific block
++ * @markbad: mark a specific block bad
++ */
++struct nand_ops {
++ int (*erase)(struct nand_device *nand, const struct nand_pos *pos);
++ int (*markbad)(struct nand_device *nand, const struct nand_pos *pos);
++ bool (*isbad)(struct nand_device *nand, const struct nand_pos *pos);
++};
++
++struct nand_ecc_req {
++ unsigned int strength;
++ unsigned int step_size;
++};
++
++#define NAND_ECCREQ(str, stp) { .strength = (str), .step_size = (stp) }
++
++struct nand_device{
++ struct mtd_info mtd;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ struct nand_row_converter rowconv;
++ struct nand_bbt bbt;
++ const struct nand_ops *ops;
++};
++
++#define SPINAND_MAX_ID_LEN 4
++
++/**
++ * struct spinand_id - SPI NAND id structure
++ * @data: buffer containing the id bytes. Currently 4 bytes large, but can
++ * be extended if required.
++ * @len: ID length
++ */
++struct spinand_id {
++ u8 data[SPINAND_MAX_ID_LEN];
++ int len;
++};
++
++/**
++ * struct spinand_controller_ops - SPI NAND controller operations
++ * @exec_op: executute SPI NAND operation
++ */
++struct spinand_controller_ops {
++ int (*exec_op)(struct spinand_device *spinand,
++ struct spinand_op *op);
++};
++
++
++/**
++ * struct manufacurer_ops - SPI NAND manufacturer specified operations
++ * @detect: detect SPI NAND device, should bot be NULL.
++ * ->detect() implementation for manufacturer A never sends
++ * any manufacturer specific SPI command to a SPI NAND from
++ * manufacturer B, so the proper way is to decode the raw id
++ * data in spinand->id.data first, if manufacture ID dismatch,
++ * return directly and let others to detect.
++ * @init: initialize SPI NAND device.
++ * @cleanup: clean SPI NAND device footprint.
++ * @prepare_op: prepara read/write operation.
++ */
++struct spinand_manufacturer_ops {
++ bool (*detect)(struct spinand_device *spinand);
++ int (*init)(struct spinand_device *spinand);
++ void (*cleanup)(struct spinand_device *spinand);
++ void (*adjust_cache_op)(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op);
++};
++
++/**
++ * struct spinand_manufacturer - SPI NAND manufacturer instance
++ * @id: manufacturer ID
++ * @name: manufacturer name
++ * @ops: point to manufacturer operations
++ */
++struct spinand_manufacturer {
++ u8 id;
++ char *name;
++ const struct spinand_manufacturer_ops *ops;
++};
++
++extern const struct spinand_manufacturer micron_spinand_manufacturer;
++extern const struct spinand_manufacturer etron_spinand_manufacturer;
++extern const struct spinand_manufacturer paragon_spinand_manufacturer;
++extern const struct spinand_manufacturer giga_spinand_manufacturer;
++extern const struct spinand_manufacturer mxic_spinand_manufacturer;
++
++#define SPINAND_CAP_RD_X1 BIT(0)
++#define SPINAND_CAP_RD_X2 BIT(1)
++#define SPINAND_CAP_RD_X4 BIT(2)
++#define SPINAND_CAP_RD_DUAL BIT(3)
++#define SPINAND_CAP_RD_QUAD BIT(4)
++#define SPINAND_CAP_WR_X1 BIT(5)
++#define SPINAND_CAP_WR_X2 BIT(6)
++#define SPINAND_CAP_WR_X4 BIT(7)
++#define SPINAND_CAP_WR_DUAL BIT(8)
++#define SPINAND_CAP_WR_QUAD BIT(9)
++
++/**
++ * struct spinand_controller - SPI NAND controller instance
++ * @ops: point to controller operations
++ * @caps: controller capabilities
++ */
++struct spinand_controller {
++ struct spinand_controller_ops *ops;
++ u32 caps;
++};
++
++/**
++ * struct spinand_device - SPI NAND device instance
++ * @base: NAND device instance
++ * @bbp: internal bad block pattern descriptor
++ * @lock: protection lock
++ * @id: ID structure
++ * @read_cache_op: Opcode of read from cache
++ * @write_cache_op: Opcode of program load
++ * @buf: buffer for read/write data
++ * @oobbuf: buffer for read/write oob
++ * @rw_mode: read/write mode of SPI NAND device
++ * @controller: SPI NAND controller instance
++ * @manufacturer: SPI NAND manufacturer instance, describe
++ * manufacturer related objects
++ */
++struct spinand_device {
++ struct nand_device base;
++ struct mutex lock;
++ struct spinand_id id;
++ u8 read_cache_op;
++ u8 write_cache_op;
++ u8 *buf;
++ u8 *oobbuf;
++ u32 rw_mode;
++ struct {
++ struct spinand_controller *controller;
++ void *priv;
++ } controller;
++ struct {
++ const struct spinand_manufacturer *manu;
++ void *priv;
++ } manufacturer;
++};
++
++/**
++ * struct nand_io_iter - NAND I/O iterator
++ * @req: current I/O request
++ * @oobbytes_per_page: maximun oob bytes per page
++ * @dataleft: remaining number of data bytes to read/write
++ * @oobleft: remaining number of OOB bytes to read/write
++ */
++struct nand_io_iter {
++ struct nand_page_io_req req;
++ unsigned int oobbytes_per_page;
++ unsigned int dataleft;
++ unsigned int oobleft;
++};
++
++/**
++ * mtd_to_nanddev - Get the NAND device attached to the MTD instance
++ * @mtd: MTD instance
++ *
++ * Return: the NAND device embedding @mtd.
++ */
++static inline struct nand_device *mtd_to_nanddev(struct mtd_info *mtd)
++{
++ return container_of(mtd, struct nand_device, mtd);
++}
++/**
++ * nanddev_to_mtd - Get the MTD device attached to a NAND device
++ * @nand: NAND device
++ *
++ * Return: the MTD device embedded in @nand.
++ */
++static inline struct mtd_info *nanddev_to_mtd(struct nand_device *nand)
++{
++ return &nand->mtd;
++}
++
++/**
++ * mtd_to_spinand - Get the SPI NAND device attached to the MTD instance
++ * @mtd: MTD instance
++ *
++ * Returns the SPI NAND device attached to @mtd.
++ */
++static inline struct spinand_device *mtd_to_spinand(struct mtd_info *mtd)
++{
++ return container_of(mtd_to_nanddev(mtd), struct spinand_device, base);
++}
++
++/**
++ * spinand_to_mtd - Get the MTD device attached to the SPI NAND device
++ * @spinand: SPI NAND device
++ *
++ * Returns the MTD device attached to @spinand.
++ */
++static inline struct mtd_info *spinand_to_mtd(struct spinand_device *spinand)
++{
++ return nanddev_to_mtd(&spinand->base);
++}
++
++/**
++ * nand_to_spinand - Get the SPI NAND device embedding an NAND object
++ * @nand: NAND object
++ *
++ * Returns the SPI NAND device embedding @nand.
++ */
++static inline struct spinand_device *nand_to_spinand(struct nand_device *nand)
++{
++ return container_of(nand, struct spinand_device, base);
++}
++
++/**
++ * spinand_to_nand - Get the NAND device embedded in a SPI NAND object
++ * @spinand: SPI NAND device
++ *
++ * Returns the NAND device embedded in @spinand.
++ */
++static inline struct nand_device *
++spinand_to_nand(struct spinand_device *spinand)
++{
++ return &spinand->base;
++}
++
++/**
++ * nanddev_set_of_node - Attach a DT node to a NAND device
++ * @nand: NAND device
++ * @np: DT node
++ *
++ * Attach a DT node to a NAND device.
++ */
++static inline void nanddev_set_of_node(struct nand_device *nand,
++ struct device_node *np)
++{
++ mtd_set_of_node(&nand->mtd, np);
++}
++
++/**
++ * spinand_set_of_node - Attach a DT node to a SPI NAND device
++ * @spinand: SPI NAND device
++ * @np: DT node
++ *
++ * Attach a DT node to a SPI NAND device.
++ */
++static inline void spinand_set_of_node(struct spinand_device *spinand,
++ struct device_node *np)
++{
++ nanddev_set_of_node(&spinand->base, np);
++}
++
++#define SPINAND_MAX_ADDR_LEN 4
++
++/**
++ * struct spinand_op - SPI NAND operation description
++ * @cmd: opcode to send
++ * @n_addr: address bytes
++ * @addr_nbits: number of bit used to transfer address
++ * @dummy_types: dummy bytes followed address
++ * @addr: address or dummy bytes buffer
++ * @n_tx: size of tx_buf
++ * @tx_buf: data to be written
++ * @n_rx: size of rx_buf
++ * @rx_buf: data to be read
++ * @data_nbits: number of bit used to transfer data
++ */
++struct spinand_op {
++ u8 cmd;
++ u8 n_addr;
++ u8 addr_nbits;
++ u8 dummy_bytes;
++ u8 addr[SPINAND_MAX_ADDR_LEN];
++ u32 n_tx;
++ const u8 *tx_buf;
++ u32 n_rx;
++ u8 *rx_buf;
++ u8 data_nbits;
++};
++/**
++ * nanddev_neraseblocks - Get the total number of erasablocks
++ * @nand: NAND device
++ *
++ * Return: the number of eraseblocks exposed by @nand.
++ */
++static inline unsigned int nanddev_neraseblocks(const struct nand_device *nand)
++{
++ return (u64)nand->memorg.luns_per_target *
++ nand->memorg.eraseblocks_per_lun *
++ nand->memorg.ntargets;
++}
++
++/* BBT related functions */
++enum nand_bbt_block_status {
++ NAND_BBT_BLOCK_STATUS_UNKNOWN,
++ NAND_BBT_BLOCK_GOOD,
++ NAND_BBT_BLOCK_WORN,
++ NAND_BBT_BLOCK_RESERVED,
++ NAND_BBT_BLOCK_FACTORY_BAD,
++ NAND_BBT_BLOCK_NUM_STATUS,
++};
++int nanddev_bbt_init(struct nand_device *nand);
++void nanddev_bbt_cleanup(struct nand_device *nand);
++int nanddev_bbt_update(struct nand_device *nand);
++int nanddev_bbt_get_block_status(const struct nand_device *nand,
++ unsigned int entry);
++int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry,
++ enum nand_bbt_block_status status);
++
++/* SPI NAND supported OP mode */
++#define SPINAND_RD_X1 BIT(0)
++#define SPINAND_RD_X2 BIT(1)
++#define SPINAND_RD_X4 BIT(2)
++#define SPINAND_RD_DUAL BIT(3)
++#define SPINAND_RD_QUAD BIT(4)
++#define SPINAND_WR_X1 BIT(5)
++#define SPINAND_WR_X2 BIT(6)
++#define SPINAND_WR_X4 BIT(7)
++#define SPINAND_WR_DUAL BIT(8)
++#define SPINAND_WR_QUAD BIT(9)
++
++#define SPINAND_RD_COMMON (SPINAND_RD_X1 | SPINAND_RD_X2 | \
++ SPINAND_RD_X4 | SPINAND_RD_DUAL | \
++ SPINAND_RD_QUAD)
++#define SPINAND_WR_COMMON (SPINAND_WR_X1 | SPINAND_WR_X4)
++#define SPINAND_RW_COMMON (SPINAND_RD_COMMON | SPINAND_WR_COMMON)
++
++struct spinand_device *devm_spinand_alloc(struct device *dev);
++int spinand_init(struct spinand_device *spinand, struct module *owner);
++void spinand_cleanup(struct spinand_device *spinand);
++
++/**
++ * nanddev_page_size - Get NAND page size
++ * @nand: NAND device
++ *
++ * Return: the page size.
++ */
++static inline size_t nanddev_page_size(const struct nand_device *nand)
++{
++ return nand->memorg.pagesize;
++}
++
++/**
++ * nanddev_per_page_oobsize - Get NAND OOB size
++ * @nand: NAND device
++ *
++ * Return: the OOB size.
++ */
++static inline unsigned int
++nanddev_per_page_oobsize(const struct nand_device *nand)
++{
++ return nand->memorg.oobsize;
++}
++
++/**
++ * nanddev_pages_per_eraseblock - Get the number of pages per eraseblock
++ * @nand: NAND device
++ *
++ * Return: the number of pages per eraseblock.
++ */
++static inline unsigned int
++nanddev_pages_per_eraseblock(const struct nand_device *nand)
++{
++ return nand->memorg.pages_per_eraseblock;
++}
++
++/**
++ * nanddev_per_page_oobsize - Get NAND erase block size
++ * @nand: NAND device
++ *
++ * Return: the eraseblock size.
++ */
++static inline size_t nanddev_eraseblock_size(const struct nand_device *nand)
++{
++ return nand->memorg.pagesize * nand->memorg.pages_per_eraseblock;
++}
++
++static inline u64 nanddev_target_size(const struct nand_device *nand)
++{
++ return (u64)nand->memorg.luns_per_target *
++ nand->memorg.eraseblocks_per_lun *
++ nand->memorg.pages_per_eraseblock *
++ nand->memorg.pagesize;
++}
++
++/**
++ * nanddev_ntarget - Get the total of targets
++ * @nand: NAND device
++ *
++ * Return: the number of dies exposed by @nand.
++ */
++static inline unsigned int nanddev_ntargets(const struct nand_device *nand)
++{
++ return nand->memorg.ntargets;
++}
++
++/**
++ * nanddev_size - Get NAND size
++ * @nand: NAND device
++ *
++ * Return: the total size exposed of @nand.
++ */
++static inline u64 nanddev_size(const struct nand_device *nand)
++{
++ return nanddev_target_size(nand) * nanddev_ntargets(nand);
++}
++
++/**
++ * nanddev_get_memorg - Extract memory organization info from a NAND device
++ * @nand: NAND device
++ *
++ * This can be used by the upper layer to fill the memorg info before calling
++ * nanddev_init().
++ *
++ * Return: the memorg object embedded in the NAND device.
++ */
++static inline struct nand_memory_organization *
++nanddev_get_memorg(struct nand_device *nand)
++{
++ return &nand->memorg;
++}
++
++
++static inline unsigned int nanddev_pos_to_row(struct nand_device *nand,
++ const struct nand_pos *pos)
++{
++ return (pos->lun << nand->rowconv.lun_addr_shift) |
++ (pos->eraseblock << nand->rowconv.eraseblock_addr_shift) |
++ pos->page;
++}
++
++
++static inline unsigned int nanddev_offs_to_pos(struct nand_device *nand,
++ loff_t offs,
++ struct nand_pos *pos)
++{
++ unsigned int pageoffs;
++ u64 tmp = offs;
++
++ pageoffs = do_div(tmp, nand->memorg.pagesize);
++ pos->page = do_div(tmp, nand->memorg.pages_per_eraseblock);
++ pos->eraseblock = do_div(tmp, nand->memorg.eraseblocks_per_lun);
++ pos->plane = pos->eraseblock % nand->memorg.planes_per_lun;
++ pos->lun = do_div(tmp, nand->memorg.luns_per_target);
++ pos->target = tmp;
++
++ return pageoffs;
++}
++
++static inline int nanddev_pos_cmp(const struct nand_pos *a,
++ const struct nand_pos *b)
++{
++ if (a->target != b->target)
++ return a->target < b->target ? -1 : 1;
++
++ if (a->lun != b->lun)
++ return a->lun < b->lun ? -1 : 1;
++
++ if (a->eraseblock != b->eraseblock)
++ return a->eraseblock < b->eraseblock ? -1 : 1;
++
++ if (a->page != b->page)
++ return a->page < b->page ? -1 : 1;
++
++ return 0;
++}
++
++static inline void nanddev_pos_next_target(struct nand_device *nand,
++ struct nand_pos *pos)
++{
++ pos->page = 0;
++ pos->plane = 0;
++ pos->eraseblock = 0;
++ pos->lun = 0;
++ pos->target++;
++}
++
++static inline void nanddev_pos_next_lun(struct nand_device *nand,
++ struct nand_pos *pos)
++{
++ if (pos->lun >= nand->memorg.luns_per_target - 1)
++ return nanddev_pos_next_target(nand, pos);
++
++ pos->lun++;
++ pos->page = 0;
++ pos->plane = 0;
++ pos->eraseblock = 0;
++}
++
++static inline void nanddev_pos_next_eraseblock(struct nand_device *nand,
++ struct nand_pos *pos)
++{
++ if (pos->eraseblock >= nand->memorg.eraseblocks_per_lun - 1)
++ return nanddev_pos_next_lun(nand, pos);
++
++ pos->eraseblock++;
++ pos->page = 0;
++ pos->plane = pos->eraseblock % nand->memorg.planes_per_lun;
++}
++
++static inline loff_t nanddev_pos_to_offs(struct nand_device *nand,
++ const struct nand_pos *pos)
++{
++ unsigned int npages;
++
++ npages = pos->page +
++ ((pos->eraseblock +
++ (pos->lun +
++ (pos->target * nand->memorg.luns_per_target)) *
++ nand->memorg.eraseblocks_per_lun) *
++ nand->memorg.pages_per_eraseblock);
++
++ return (loff_t)npages * nand->memorg.pagesize;
++}
++
++static inline void nanddev_pos_next_page(struct nand_device *nand,
++ struct nand_pos *pos)
++{
++ if (pos->page >= nand->memorg.pages_per_eraseblock - 1)
++ return nanddev_pos_next_eraseblock(nand, pos);
++
++ pos->page++;
++}
++
++/**
++ * nand_io_iter_init - Initialize a NAND I/O iterator
++ * @nand: NAND device
++ * @offs: absolute offset
++ * @req: MTD request
++ * @iter: page iterator
++ */
++static inline void nanddev_io_iter_init(struct nand_device *nand,
++ loff_t offs, struct mtd_oob_ops *req,
++ struct nand_io_iter *iter)
++{
++ struct mtd_info *mtd = nanddev_to_mtd(nand);
++
++ iter->req.dataoffs = nanddev_offs_to_pos(nand, offs, &iter->req.pos);
++ iter->req.ooboffs = req->ooboffs;
++ iter->oobbytes_per_page = mtd_oobavail(mtd, req);
++ iter->dataleft = req->len;
++ iter->oobleft = req->ooblen;
++ iter->req.databuf.in = req->datbuf;
++ iter->req.datalen = min_t(unsigned int,
++ nand->memorg.pagesize - iter->req.dataoffs,
++ iter->dataleft);
++ iter->req.oobbuf.in = req->oobbuf;
++ iter->req.ooblen = min_t(unsigned int,
++ iter->oobbytes_per_page - iter->req.ooboffs,
++ iter->oobleft);
++}
++
++/**
++ * nand_io_iter_next_page - Move to the next page
++ * @nand: NAND device
++ * @iter: page iterator
++ */
++static inline void nanddev_io_iter_next_page(struct nand_device *nand,
++ struct nand_io_iter *iter)
++{
++ nanddev_pos_next_page(nand, &iter->req.pos);
++ iter->dataleft -= iter->req.datalen;
++ iter->req.databuf.in += iter->req.datalen;
++ iter->oobleft -= iter->req.ooblen;
++ iter->req.oobbuf.in += iter->req.ooblen;
++ iter->req.dataoffs = 0;
++ iter->req.ooboffs = 0;
++ iter->req.datalen = min_t(unsigned int, nand->memorg.pagesize,
++ iter->dataleft);
++ iter->req.ooblen = min_t(unsigned int, iter->oobbytes_per_page,
++ iter->oobleft);
++}
++
++/**
++ * nand_io_iter_end - Should end iteration or not
++ * @nand: NAND device
++ * @iter: page iterator
++ */
++static inline bool nanddev_io_iter_end(struct nand_device *nand,
++ const struct nand_io_iter *iter)
++{
++ if (iter->dataleft || iter->oobleft)
++ return false;
++
++ return true;
++}
++
++/**
++ * nand_io_for_each_page - Iterate over all NAND pages contained in an MTD I/O
++ * request
++ * @nand: NAND device
++ * @start: start address to read/write
++ * @req: MTD I/O request
++ * @iter: page iterator
++ */
++#define nanddev_io_for_each_page(nand, start, req, iter) \
++ for (nanddev_io_iter_init(nand, start, req, iter); \
++ !nanddev_io_iter_end(nand, iter); \
++ nanddev_io_iter_next_page(nand, iter))
++
++static inline unsigned int nanddev_bbt_pos_to_entry(struct nand_device *nand,
++ const struct nand_pos *pos)
++{
++ return pos->eraseblock;
++}
++
++static inline bool nanddev_bbt_is_initialized(struct nand_device *nand)
++{
++ return !!nand->bbt.cache;
++}
++
++int nanddev_init(struct nand_device *nand, const struct nand_ops *ops,
++ struct module *owner);
++void nanddev_cleanup(struct nand_device *nand);
++bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos);
++bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos);
++int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos);
++int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos);
++
++/* MTD -> NAND helper functions. */
++int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo);
++
++
++#endif /* __LINUX_MTD_SPINAND_H */
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/paragon.c
+@@ -0,0 +1,152 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_PARAGON 0xA1
++#define SPINAND_MFR_XTX 0x0B
++
++struct paragon_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define PARAGON_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct paragon_spinand_info paragon_spinand_table[] = {
++ PARAGON_SPI_NAND_INFO("PARAGONxxxx", 0xe1,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++ PARAGON_SPI_NAND_INFO("XT26G01xxxx", 0xf1,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int paragon_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++
++ case SPINAND_CMD_READ_FROM_CACHE_QUAD_IO:
++ return 2;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * paragon_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool paragon_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct paragon_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(paragon_spinand_table); i++) {
++ item = (struct paragon_spinand_info *)paragon_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * paragon_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool paragon_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if ( (id[1] != SPINAND_MFR_PARAGON) && (id[1] != SPINAND_MFR_XTX) )
++ return false;
++
++ return paragon_spinand_scan_id_table(spinand, id[2]);
++}
++
++/**
++ * paragon_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void paragon_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++
++ op->n_addr= 2;
++ op->addr[0] = op->addr[1];
++ op->addr[1] = op->addr[2];
++ op->addr[2] = 0;
++ op->dummy_bytes = paragon_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops paragon_spinand_manuf_ops = {
++ .detect = paragon_spinand_detect,
++ .adjust_cache_op = paragon_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer paragon_spinand_manufacturer = {
++ .id = SPINAND_MFR_PARAGON,
++ .name = "Paragon(XTX)",
++ .ops = &paragon_spinand_manuf_ops,
++};
+--- /dev/null
++++ b/drivers/mtd/nand/spinand/mxic.c
+@@ -0,0 +1,152 @@
++/*
++ *
++ * Copyright (c) 2016-2017 Micron Technology, Inc.
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#include <linux/device.h>
++#include <linux/kernel.h>
++#include "spinand.h"
++
++#define SPINAND_MFR_MXIC 0xC2
++
++struct mxic_spinand_info {
++ char *name;
++ u8 dev_id;
++ struct nand_memory_organization memorg;
++ struct nand_ecc_req eccreq;
++ unsigned int rw_mode;
++};
++
++#define MXIC_SPI_NAND_INFO(nm, did, mo, er, rwm) \
++ { \
++ .name = (nm), \
++ .dev_id = (did), \
++ .memorg = mo, \
++ .eccreq = er, \
++ .rw_mode = (rwm) \
++ }
++
++static const struct mxic_spinand_info mxic_spinand_table[] = {
++ MXIC_SPI_NAND_INFO("MX35LF1G24AD", 0x14,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++
++ MXIC_SPI_NAND_INFO("MX35LF1GE4AB", 0x12,
++ NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1),
++ NAND_ECCREQ(8, 512),
++ SPINAND_RW_COMMON),
++};
++
++static int mxic_spinand_get_dummy(struct spinand_device *spinand,
++ struct spinand_op *op)
++{
++ u8 opcode = op->cmd;
++
++ switch (opcode) {
++ case SPINAND_CMD_READ_FROM_CACHE:
++ case SPINAND_CMD_READ_FROM_CACHE_FAST:
++ case SPINAND_CMD_READ_FROM_CACHE_X2:
++ case SPINAND_CMD_READ_FROM_CACHE_DUAL_IO:
++ case SPINAND_CMD_READ_FROM_CACHE_X4:
++ case SPINAND_CMD_READ_ID:
++ return 1;
++
++ default:
++ return 0;
++ }
++}
++
++/**
++ * mxic_spinand_scan_id_table - scan SPI NAND info in id table
++ * @spinand: SPI NAND device structure
++ * @id: point to manufacture id and device id
++ * Description:
++ * If found in id table, config device with table information.
++ */
++static bool mxic_spinand_scan_id_table(struct spinand_device *spinand,
++ u8 dev_id)
++{
++ struct mtd_info *mtd = spinand_to_mtd(spinand);
++ struct nand_device *nand = mtd_to_nanddev(mtd);
++ struct mxic_spinand_info *item;
++ unsigned int i;
++
++ for (i = 0; i < ARRAY_SIZE(mxic_spinand_table); i++) {
++ item = (struct mxic_spinand_info *)mxic_spinand_table + i;
++ if (dev_id != item->dev_id)
++ continue;
++
++ nand->memorg = item->memorg;
++ nand->eccreq = item->eccreq;
++ spinand->rw_mode = item->rw_mode;
++
++ return true;
++ }
++
++ return false;
++}
++
++/**
++ * mxic_spinand_detect - initialize device related part in spinand_device
++ * struct if it is Micron device.
++ * @spinand: SPI NAND device structure
++ */
++static bool mxic_spinand_detect(struct spinand_device *spinand)
++{
++ u8 *id = spinand->id.data;
++
++ /*
++ * Micron SPI NAND read ID need a dummy byte,
++ * so the first byte in raw_id is dummy.
++ */
++ if (id[1] != SPINAND_MFR_MXIC)
++ return false;
++
++ return mxic_spinand_scan_id_table(spinand, id[2]);
++}
++
++/**
++ * mxic_spinand_prepare_op - Fix address for cache operation.
++ * @spinand: SPI NAND device structure
++ * @op: pointer to spinand_op struct
++ * @page: page address
++ * @column: column address
++ */
++static void mxic_spinand_adjust_cache_op(struct spinand_device *spinand,
++ const struct nand_page_io_req *req,
++ struct spinand_op *op)
++{
++ struct nand_device *nand = spinand_to_nand(spinand);
++ unsigned int shift;
++ if(op->cmd == spinand->read_cache_op){//read from cache only 2 bytes address
++ op->n_addr= 2;
++ op->addr[0] = op->addr[1];
++ op->addr[1] = op->addr[2];
++ op->addr[2] = 0;
++ }
++
++ /* The plane number is passed in MSB just above the column address */
++ op->dummy_bytes = mxic_spinand_get_dummy(spinand, op);
++}
++
++static const struct spinand_manufacturer_ops mxic_spinand_manuf_ops = {
++ .detect = mxic_spinand_detect,
++ .adjust_cache_op = mxic_spinand_adjust_cache_op,
++};
++
++const struct spinand_manufacturer mxic_spinand_manufacturer = {
++ .id = SPINAND_MFR_MXIC,
++ .name = "Mxic",
++ .ops = &mxic_spinand_manuf_ops,
++};
diff --git a/target/linux/ipq40xx/patches-4.14/901-arm-boot-add-dts-files.patch b/target/linux/ipq40xx/patches-4.14/901-arm-boot-add-dts-files.patch
index f7efd415f1..4ec8b94462 100644
--- a/target/linux/ipq40xx/patches-4.14/901-arm-boot-add-dts-files.patch
+++ b/target/linux/ipq40xx/patches-4.14/901-arm-boot-add-dts-files.patch
@@ -10,7 +10,7 @@ Signed-off-by: John Crispin <john@phrozen.org>
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
-@@ -697,7 +697,31 @@ dtb-$(CONFIG_ARCH_QCOM) += \
+@@ -697,7 +697,33 @@ dtb-$(CONFIG_ARCH_QCOM) += \
qcom-apq8074-dragonboard.dtb \
qcom-apq8084-ifc6540.dtb \
qcom-apq8084-mtp.dtb \
@@ -38,6 +38,8 @@ Signed-off-by: John Crispin <john@phrozen.org>
+ qcom-ipq4019-qxwlan-e2600ac-c2.dtb \
+ qcom-ipq4028-wpj428.dtb \
+ qcom-ipq4029-gl-b1300.dtb \
++ qcom-ipq4018-gl-b1300th.dtb \
++ qcom-ipq4029-gl-s1300.dtb \
+ qcom-ipq4029-mr33.dtb \
qcom-ipq8064-ap148.dtb \
qcom-msm8660-surf.dtb \
diff --git a/target/linux/mvebu/Makefile b/target/linux/mvebu/Makefile
index a920f6db7d..11aa445f35 100644
--- a/target/linux/mvebu/Makefile
+++ b/target/linux/mvebu/Makefile
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
BOARD:=mvebu
BOARDNAME:=Marvell EBU Armada
-FEATURES:=fpu usb pci pcie gpio nand squashfs ramdisk boot-part rootfs-part
+FEATURES:=fpu usb pci pcie gpio nand squashfs ramdisk boot-part rootfs-part usbgadget
SUBTARGETS:=cortexa9 cortexa53 cortexa72
MAINTAINER:=Imre Kaloz <kaloz@openwrt.org>
diff --git a/target/linux/mvebu/base-files/etc/board.d/02_network b/target/linux/mvebu/base-files/etc/board.d/02_network
index b96faa5c4a..34227a26dd 100755
--- a/target/linux/mvebu/base-files/etc/board.d/02_network
+++ b/target/linux/mvebu/base-files/etc/board.d/02_network
@@ -18,8 +18,10 @@ cznic,turris-omnia)
globalscale,espressobin|\
globalscale,espressobin-emmc|\
globalscale,espressobin-v7|\
-globalscale,espressobin-v7-emmc)
- ucidef_set_interfaces_lan_wan "lan0 lan1" "wan"
+globalscale,espressobin-v7-emmc|\
+glinet,gl-mv1000|\
+gl-mv1000)
+ ucidef_set_interfaces_lan_wan "lan0 lan1 usb0" "wan"
;;
linksys,caiman|\
linksys,cobra|\
diff --git a/target/linux/mvebu/base-files/lib/preinit/79_move_config b/target/linux/mvebu/base-files/lib/preinit/79_move_config
index 195be0e137..640fb5cdad 100644
--- a/target/linux/mvebu/base-files/lib/preinit/79_move_config
+++ b/target/linux/mvebu/base-files/lib/preinit/79_move_config
@@ -18,7 +18,7 @@ move_config() {
esac
mkdir -p /boot
mount -o rw,noatime "/dev/$partdev" /boot
- [ -f "/boot/$BACKUP_FILE" ] && mv -f "/boot/$BACKUP_FILE" /
+ [ -f /boot/sysupgrade.tgz ] && mv -f /boot/sysupgrade.tgz /
umount /boot
fi
}
diff --git a/target/linux/mvebu/base-files/lib/preinit/81_linksys_syscfg b/target/linux/mvebu/base-files/lib/preinit/81_linksys_syscfg
index 83448e5ace..b107eacb11 100644
--- a/target/linux/mvebu/base-files/lib/preinit/81_linksys_syscfg
+++ b/target/linux/mvebu/base-files/lib/preinit/81_linksys_syscfg
@@ -4,8 +4,8 @@
#
preinit_mount_syscfg() {
+
. /lib/functions.sh
- . /lib/upgrade/common.sh
case $(board_name) in
linksys,caiman|linksys,cobra|linksys,mamba|linksys,rango|linksys,shelby|linksys,venom)
@@ -22,12 +22,12 @@ preinit_mount_syscfg() {
fi
mkdir /tmp/syscfg
mount -t ubifs ubi1:syscfg /tmp/syscfg
- [ -f "/tmp/syscfg/$BACKUP_FILE" ] && {
+ [ -f /tmp/syscfg/sysupgrade.tgz ] && {
echo "- config restore -"
cd /
- mv "/tmp/syscfg/$BACKUP_FILE" /tmp
- tar xzf "/tmp/$BACKUP_FILE"
- rm -f "/tmp/$BACKUP_FILE"
+ mv /tmp/syscfg/sysupgrade.tgz /tmp
+ tar xzf /tmp/sysupgrade.tgz
+ rm -f /tmp/sysupgrade.tgz
sync
}
;;
diff --git a/target/linux/mvebu/base-files/lib/upgrade/linksys.sh b/target/linux/mvebu/base-files/lib/upgrade/linksys.sh
index ca64a0edf1..3f45d6cac5 100644
--- a/target/linux/mvebu/base-files/lib/upgrade/linksys.sh
+++ b/target/linux/mvebu/base-files/lib/upgrade/linksys.sh
@@ -93,6 +93,6 @@ platform_do_upgrade_linksys() {
}
platform_copy_config_linksys() {
- cp -f "$UPGRADE_BACKUP" "/tmp/syscfg/$BACKUP_FILE"
+ cp -f /tmp/sysupgrade.tgz /tmp/syscfg/sysupgrade.tgz
sync
}
diff --git a/target/linux/mvebu/base-files/lib/upgrade/platform.sh b/target/linux/mvebu/base-files/lib/upgrade/platform.sh
index 58e7d83e4e..0160588ce3 100755
--- a/target/linux/mvebu/base-files/lib/upgrade/platform.sh
+++ b/target/linux/mvebu/base-files/lib/upgrade/platform.sh
@@ -3,6 +3,7 @@
# Copyright (C) 2016 LEDE-Project.org
#
+PART_NAME='firmware'
RAMFS_COPY_BIN='fw_printenv fw_setenv'
RAMFS_COPY_DATA='/etc/fw_env.config /var/lock/fw_printenv.lock'
REQUIRE_IMAGE_METADATA=1
@@ -11,7 +12,7 @@ platform_check_image() {
case "$(board_name)" in
cznic,turris-omnia|globalscale,espressobin|globalscale,espressobin-emmc|globalscale,espressobin-v7|globalscale,espressobin-v7-emmc|\
marvell,armada8040-mcbin|solidrun,clearfog-base-a1|solidrun,clearfog-pro-a1)
- platform_check_image_sdcard "$1"
+ platform_check_image_sdcard "$ARGV"
;;
*)
return 0
@@ -19,17 +20,36 @@ platform_check_image() {
esac
}
+platform_do_upgrade_mv1000(){
+ local firmware=`fw_printenv firmware | awk -F '=' '{print $2}'`
+
+ case "$firmware" in
+ gl-mv1000-emmc)
+ platform_do_upgrade_sdcard "$ARGV"
+ ;;
+ gl-mv1000-emmc-gzip)
+ platform_do_upgrade_sdcard "$ARGV"
+ ;;
+ *)
+ default_do_upgrade "$ARGV"
+ ;;
+ esac
+}
+
platform_do_upgrade() {
case "$(board_name)" in
linksys,caiman|linksys,cobra|linksys,mamba|linksys,rango|linksys,shelby|linksys,venom)
- platform_do_upgrade_linksys "$1"
+ platform_do_upgrade_linksys "$ARGV"
;;
cznic,turris-omnia|globalscale,espressobin|globalscale,espressobin-emmc|globalscale,espressobin-v7|globalscale,espressobin-v7-emmc|\
marvell,armada8040-mcbin|solidrun,clearfog-base-a1|solidrun,clearfog-pro-a1)
- platform_do_upgrade_sdcard "$1"
+ platform_do_upgrade_sdcard "$ARGV"
+ ;;
+ gl-mv1000)
+ platform_do_upgrade_mv1000 "$ARGV"
;;
*)
- default_do_upgrade "$1"
+ default_do_upgrade "$ARGV"
;;
esac
}
@@ -40,7 +60,10 @@ platform_copy_config() {
;;
cznic,turris-omnia|globalscale,espressobin|globalscale,espressobin-emmc|globalscale,espressobin-v7|globalscale,espressobin-v7-emmc|\
marvell,armada8040-mcbin|solidrun,clearfog-base-a1|solidrun,clearfog-pro-a1)
- platform_copy_config_sdcard
+ platform_copy_config_sdcard "$ARGV"
+ ;;
+ gl-mv1000)
+ platform_copy_config_sdcard "$ARGV"
;;
esac
}
diff --git a/target/linux/mvebu/base-files/lib/upgrade/sdcard.sh b/target/linux/mvebu/base-files/lib/upgrade/sdcard.sh
index bada47a1dd..43fc2504fc 100644
--- a/target/linux/mvebu/base-files/lib/upgrade/sdcard.sh
+++ b/target/linux/mvebu/base-files/lib/upgrade/sdcard.sh
@@ -49,7 +49,7 @@ platform_do_upgrade_sdcard() {
sync
- if [ "$UPGRADE_OPT_SAVE_PARTITIONS" = "1" ]; then
+ if [ "$SAVE_PARTITIONS" = "1" ]; then
get_partitions "/dev/$diskdev" bootdisk
#extract the boot sector from the image
@@ -70,24 +70,35 @@ platform_do_upgrade_sdcard() {
# will be missing if it overlaps with the old partition 2
partx -d - "/dev/$diskdev"
partx -a - "/dev/$diskdev"
- else
- #write uboot image
- get_image "$@" | dd of="$diskdev" bs=512 skip=1 seek=1 count=2048 conv=fsync
- #iterate over each partition from the image and write it to the boot disk
- while read part start size; do
- if export_partdevice partdev $part; then
- echo "Writing image to /dev/$partdev..."
- get_image "$@" | dd of="/dev/$partdev" ibs="512" obs=1M skip="$start" count="$size" conv=fsync
- else
- echo "Unable to find partition $part device, skipped."
- fi
- done < /tmp/partmap.image
-
- #copy partition uuid
- echo "Writing new UUID to /dev/$diskdev..."
- get_image "$@" | dd of="/dev/$diskdev" bs=1 skip=440 count=4 seek=440 conv=fsync
+
+ return 0
fi
+ #write uboot image
+ get_image "$@" | dd of="$diskdev" bs=512 skip=1 seek=1 count=2048 conv=fsync
+ #iterate over each partition from the image and write it to the boot disk
+ while read part start size; do
+ if export_partdevice partdev $part; then
+ echo "Writing image to /dev/$partdev..."
+ get_image "$@" | dd of="/dev/$partdev" ibs="512" obs=1M skip="$start" count="$size" conv=fsync
+ else
+ echo "Unable to find partition $part device, skipped."
+ fi
+ done < /tmp/partmap.image
+
+ #copy partition uuid
+ echo "Writing new UUID to /dev/$diskdev..."
+ get_image "$@" | dd of="/dev/$diskdev" bs=1 skip=440 count=4 seek=440 conv=fsync
+
+ case "$board" in
+ cznic,turris-omnia)
+ fw_setenv openwrt_bootargs 'earlyprintk console=ttyS0,115200 root=/dev/mmcblk0p2 rootfstype=auto rootwait'
+ fw_setenv openwrt_mmcload 'setenv bootargs "$openwrt_bootargs cfg80211.freg=$regdomain"; fatload mmc 0 0x01000000 zImage; fatload mmc 0 0x02000000 armada-385-turris-omnia.dtb'
+ fw_setenv factory_mmcload 'setenv bootargs "$bootargs cfg80211.freg=$regdomain"; btrload mmc 0 0x01000000 boot/zImage @; btrload mmc 0 0x02000000 boot/dtb @'
+ fw_setenv mmcboot 'run openwrt_mmcload || run factory_mmcload; bootz 0x01000000 - 0x02000000'
+ ;;
+ esac
+
sleep 1
}
@@ -97,7 +108,7 @@ platform_copy_config_sdcard() {
if export_partdevice partdev 1; then
mkdir -p /boot
[ -f /boot/kernel.img ] || mount -o rw,noatime /dev/$partdev /boot
- cp -af "$UPGRADE_BACKUP" "/boot/$BACKUP_FILE"
+ cp -af "$CONF_TAR" /boot/
sync
umount /boot
fi
diff --git a/target/linux/mvebu/config-4.14 b/target/linux/mvebu/config-4.14
index 7a0caeeb61..1949e55a24 100644
--- a/target/linux/mvebu/config-4.14
+++ b/target/linux/mvebu/config-4.14
@@ -268,7 +268,6 @@ CONFIG_HOTPLUG_CPU=y
CONFIG_HWBM=y
CONFIG_HWMON=y
CONFIG_HW_RANDOM=y
-# CONFIG_HW_RANDOM_OMAP is not set
CONFIG_HZ_FIXED=0
CONFIG_HZ_PERIODIC=y
CONFIG_I2C=y
@@ -277,6 +276,9 @@ CONFIG_I2C_CHARDEV=y
CONFIG_I2C_MV64XXX=y
# CONFIG_I2C_PXA is not set
CONFIG_INITRAMFS_SOURCE=""
+CONFIG_INPUT=y
+CONFIG_INPUT_KEYBOARD=y
+CONFIG_INPUT_POLLDEV=y
CONFIG_IOMMU_HELPER=y
CONFIG_IRQCHIP=y
CONFIG_IRQ_DOMAIN=y
@@ -286,6 +288,7 @@ CONFIG_IRQ_FORCED_THREADING=y
CONFIG_IRQ_WORK=y
# CONFIG_IWMMXT is not set
CONFIG_JBD2=y
+CONFIG_KEYBOARD_GLMV1000=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_PCA963X=y
CONFIG_LEDS_TLC591XX=y
@@ -328,6 +331,8 @@ CONFIG_MTD_NAND_ECC=y
CONFIG_MTD_NAND_PXA3xx=y
CONFIG_MTD_SPI_NOR=y
CONFIG_MTD_SPLIT_FIRMWARE=y
+CONFIG_MTD_SPLIT_LZMA_FW=y
+CONFIG_MTD_SPLIT_UIMAGE_FW=y
CONFIG_MTD_UBI=y
CONFIG_MTD_UBI_BEB_LIMIT=20
CONFIG_MTD_UBI_BLOCK=y
@@ -476,10 +481,18 @@ CONFIG_USB_COMMON=y
CONFIG_USB_EHCI_HCD=y
CONFIG_USB_EHCI_HCD_ORION=y
CONFIG_USB_EHCI_HCD_PLATFORM=y
+CONFIG_USB_GADGET=y
CONFIG_USB_LEDS_TRIGGER_USBPORT=y
+CONFIG_USB_MVEBU_U3D=y
+CONFIG_USB_MV_UDC=y
+CONFIG_USB_NET_DRIVERS=y
+CONFIG_USB_NET_QMI_WWAN=y
CONFIG_USB_PHY=y
+CONFIG_USB_SERIAL_OPTION=y
+CONFIG_USB_SERIAL_WWAN=y
CONFIG_USB_STORAGE=y
CONFIG_USB_SUPPORT=y
+CONFIG_USB_USBNET=y
CONFIG_USB_XHCI_HCD=y
CONFIG_USB_XHCI_MVEBU=y
CONFIG_USB_XHCI_PCI=y
diff --git a/target/linux/mvebu/files-4.14/arch/arm64/boot/dts/marvell/armada-gl-mv1000-emmc.dts b/target/linux/mvebu/files-4.14/arch/arm64/boot/dts/marvell/armada-gl-mv1000-emmc.dts
new file mode 100644
index 0000000000..bc2b1bb4fb
--- /dev/null
+++ b/target/linux/mvebu/files-4.14/arch/arm64/boot/dts/marvell/armada-gl-mv1000-emmc.dts
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Device Tree file for GL.iNet GL-MV1000
+ */
+
+/dts-v1/;
+#include <dt-bindings/gpio/gpio.h>
+#include "armada-372x.dtsi"
+
+/ {
+ model = "GL.inet GL-MV1000";
+ compatible = "gl-mv1000", "marvell,armada3720";
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x00000000 0x00000000 0x00000000 0x20000000>;
+ };
+
+ vcc_sd_reg1: regulator {
+ compatible = "regulator-gpio";
+ regulator-name = "vcc_sd1";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-boot-on;
+
+ gpios = <&gpionb 4 GPIO_ACTIVE_HIGH>;
+ gpios-states = <0>;
+ states = <1800000 0x1
+ 3300000 0x0>;
+ enable-active-high;
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ led@487 {
+ label = "gl-mv1000:green:vpn";
+ gpios = <&gpionb 11 GPIO_ACTIVE_LOW>;
+ default-state = "off";
+ };
+
+ led@488 {
+ label = "gl-mv1000:green:wifi";
+ gpios = <&gpionb 12 GPIO_ACTIVE_LOW>;
+ default-state = "off";
+ };
+
+ led@489 {
+ label = "gl-mv1000:green:power";
+ gpios = <&gpionb 13 GPIO_ACTIVE_LOW>;
+ default-state = "on";
+ };
+ };
+};
+
+&spi0 {
+ status = "okay";
+
+ flash@0 {
+ reg = <0>;
+ compatible = "jedec,spi-nor";
+ spi-max-frequency = <104000000>;
+ m25p,fast-read;
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0 0xf0000>;
+ };
+
+ partition@f0000 {
+ label = "u-boot-env";
+ reg = <0Xf0000 0x8000>;
+ };
+
+ factory: partition@f8000 {
+ label = "factory";
+ reg = <0xf8000 0x8000>;
+ };
+ };
+ };
+};
+
+&sdhci0 {
+ bus-width = <8>;
+ mmc-ddr-1_8v;
+ mmc-hs400-1_8v;
+ non-removable;
+ no-sd;
+ no-sdio;
+ marvell,pad-type = "fixed-1-8v";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&mmc_pins>;
+ status = "okay";
+};
+
+&sdhci1 {
+ wp-inverted;
+ bus-width = <4>;
+ cd-gpios = <&gpionb 17 GPIO_ACTIVE_LOW>;
+ marvell,pad-type = "sd";
+ no-1-8-v;
+ vqmmc-supply = <&vcc_sd_reg1>;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&sdio_pins>;
+ status = "okay";
+};
+
+&usb3 {
+ status = "okay";
+};
+
+&usb2 {
+ status = "okay";
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&mdio {
+ switch0: switch0@1 {
+ compatible = "marvell,mv88e6085";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <1>;
+
+ dsa,member = <0 0>;
+
+ ports: ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ label = "cpu";
+ ethernet = <&eth0>;
+ };
+
+ port@1 {
+ reg = <1>;
+ label = "wan";
+ phy-handle = <&switch0phy0>;
+ };
+
+ port@2 {
+ reg = <2>;
+ label = "lan0";
+ phy-handle = <&switch0phy1>;
+ };
+
+ port@3 {
+ reg = <3>;
+ label = "lan1";
+ phy-handle = <&switch0phy2>;
+ };
+ };
+
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ switch0phy0: switch0phy0@11 {
+ reg = <0x11>;
+ };
+ switch0phy1: switch0phy1@12 {
+ reg = <0x12>;
+ };
+ switch0phy2: switch0phy2@13 {
+ reg = <0x13>;
+ };
+ };
+ };
+};
+
+&eth0 {
+ mtd-mac-address = <&factory 0x0>;
+ phy-mode = "rgmii-id";
+ status = "okay";
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+};
diff --git a/target/linux/mvebu/files-4.14/arch/arm64/boot/dts/marvell/armada-gl-mv1000.dts b/target/linux/mvebu/files-4.14/arch/arm64/boot/dts/marvell/armada-gl-mv1000.dts
new file mode 100644
index 0000000000..9810086182
--- /dev/null
+++ b/target/linux/mvebu/files-4.14/arch/arm64/boot/dts/marvell/armada-gl-mv1000.dts
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Device Tree file for GL.iNet GL-MV1000
+ */
+
+/dts-v1/;
+#include <dt-bindings/gpio/gpio.h>
+#include "armada-372x.dtsi"
+
+/ {
+ model = "GL.inet GL-MV1000";
+ compatible = "gl-mv1000", "marvell,armada3720";
+
+ chosen {
+ stdout-path = "serial0:115200n8";
+ };
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x00000000 0x00000000 0x00000000 0x20000000>;
+ };
+
+ vcc_sd_reg1: regulator {
+ compatible = "regulator-gpio";
+ regulator-name = "vcc_sd1";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-boot-on;
+
+ gpios = <&gpionb 4 GPIO_ACTIVE_HIGH>;
+ gpios-states = <0>;
+ states = <1800000 0x1
+ 3300000 0x0>;
+ enable-active-high;
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ led@487 {
+ label = "gl-mv1000:green:vpn";
+ gpios = <&gpionb 11 GPIO_ACTIVE_LOW>;
+ default-state = "off";
+ };
+
+ led@488 {
+ label = "gl-mv1000:green:wifi";
+ gpios = <&gpionb 12 GPIO_ACTIVE_LOW>;
+ default-state = "off";
+ };
+
+ led@489 {
+ label = "gl-mv1000:green:power";
+ gpios = <&gpionb 13 GPIO_ACTIVE_LOW>;
+ default-state = "on";
+ };
+ };
+};
+
+&spi0 {
+ status = "okay";
+
+ flash@0 {
+ reg = <0>;
+ compatible = "jedec,spi-nor";
+ spi-max-frequency = <104000000>;
+ m25p,fast-read;
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0 0xf0000>;
+ };
+
+ partition@f0000 {
+ label = "u-boot-env";
+ reg = <0Xf0000 0x8000>;
+ };
+
+ factory: partition@f8000 {
+ label = "factory";
+ reg = <0xf8000 0x8000>;
+ };
+
+ partition@100000{
+ label = "dtb";
+ reg = <0X100000 0x10000>;
+ };
+
+ partition@110000 {
+ label = "firmware";
+ reg = <0X110000 0xef0000>;
+ };
+ };
+ };
+};
+
+&sdhci0 {
+ bus-width = <8>;
+ mmc-ddr-1_8v;
+ mmc-hs400-1_8v;
+ non-removable;
+ no-sd;
+ no-sdio;
+ marvell,pad-type = "fixed-1-8v";
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&mmc_pins>;
+ status = "okay";
+};
+
+&sdhci1 {
+ wp-inverted;
+ bus-width = <4>;
+ cd-gpios = <&gpionb 17 GPIO_ACTIVE_LOW>;
+ marvell,pad-type = "sd";
+ no-1-8-v;
+ vqmmc-supply = <&vcc_sd_reg1>;
+
+ pinctrl-names = "default";
+ pinctrl-0 = <&sdio_pins>;
+ status = "okay";
+};
+
+&usb3 {
+ status = "okay";
+};
+
+&usb2 {
+ status = "okay";
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&mdio {
+ switch0: switch0@1 {
+ compatible = "marvell,mv88e6085";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <1>;
+
+ dsa,member = <0 0>;
+
+ ports: ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ label = "cpu";
+ ethernet = <&eth0>;
+ };
+
+ port@1 {
+ reg = <1>;
+ label = "wan";
+ phy-handle = <&switch0phy0>;
+ };
+
+ port@2 {
+ reg = <2>;
+ label = "lan0";
+ phy-handle = <&switch0phy1>;
+ };
+
+ port@3 {
+ reg = <3>;
+ label = "lan1";
+ phy-handle = <&switch0phy2>;
+ };
+ };
+
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ switch0phy0: switch0phy0@11 {
+ reg = <0x11>;
+ };
+ switch0phy1: switch0phy1@12 {
+ reg = <0x12>;
+ };
+ switch0phy2: switch0phy2@13 {
+ reg = <0x13>;
+ };
+ };
+ };
+};
+
+&eth0 {
+ mtd-mac-address = <&factory 0x0>;
+ phy-mode = "rgmii-id";
+ status = "okay";
+
+ fixed-link {
+ speed = <1000>;
+ full-duplex;
+ };
+};
diff --git a/target/linux/mvebu/image/Makefile b/target/linux/mvebu/image/Makefile
index 57e5a30491..edef87a8e4 100644
--- a/target/linux/mvebu/image/Makefile
+++ b/target/linux/mvebu/image/Makefile
@@ -68,6 +68,11 @@ define Build/omnia-medkit-initramfs
--file=$@ -C $(dir $(IMAGE_KERNEL))boot/ .
endef
+define Build/pad-dtb
+ (dd if=$(KDIR)/image-$(DEVICE_DTS).dtb bs=64k conv=sync;dd if=$@) > $@.new
+ mv $@.new $@
+endef
+
define Device/Default
PROFILES := Default
BOARD_NAME = $$(DEVICE_DTS)
@@ -92,6 +97,15 @@ define Device/Default-arm64
KERNEL := kernel-bin
endef
+define Device/Default-arm64-emmc
+ BOOT_SCRIPT := generic-arm64-emmc
+ DTS_DIR := $(DTS_DIR)/marvell
+ IMAGES := emmc.img
+ IMAGE/emmc.img := boot-scr | boot-img-ext4 | sdcard-img-ext4 | append-gl-metadata
+ KERNEL_NAME := Image
+ KERNEL := kernel-bin
+endef
+
define Device/NAND-128K
BLOCKSIZE := 128k
PAGESIZE := 2048
@@ -112,5 +126,6 @@ endef
include cortex-a9.mk
include cortex-a53.mk
include cortex-a72.mk
+include gl-mv1000.mk
$(eval $(call BuildImage))
diff --git a/target/linux/mvebu/image/gen_mvebu_sdcard_img.sh b/target/linux/mvebu/image/gen_mvebu_sdcard_img.sh
index e0230e48b6..22ab20cd69 100755
--- a/target/linux/mvebu/image/gen_mvebu_sdcard_img.sh
+++ b/target/linux/mvebu/image/gen_mvebu_sdcard_img.sh
@@ -51,6 +51,12 @@ while [ "$#" -ge 3 ]; do
shift; shift; shift
done
+model=''
+model=$(echo $OUTFILE | grep "gl-mv1000-emmc")
+[ "$model" != "" ] && {
+ ptgen_args="$ptgen_args -t 83 -p 7093504"
+}
+
head=16
sect=63
diff --git a/target/linux/mvebu/image/generic-arm64-emmc.bootscript b/target/linux/mvebu/image/generic-arm64-emmc.bootscript
new file mode 100644
index 0000000000..4de4d3901f
--- /dev/null
+++ b/target/linux/mvebu/image/generic-arm64-emmc.bootscript
@@ -0,0 +1,12 @@
+setenv bootargs "root=/dev/mmcblk0p2 rw rootwait"
+
+if test -n "${console}"; then
+ setenv bootargs "${bootargs} ${console}"
+fi
+
+setenv mmcdev 0
+
+load mmc ${mmcdev}:1 ${fdt_addr} @DTB@.dtb
+load mmc ${mmcdev}:1 ${kernel_addr} Image
+
+booti ${kernel_addr} - ${fdt_addr}
diff --git a/target/linux/mvebu/image/gl-mv1000.mk b/target/linux/mvebu/image/gl-mv1000.mk
new file mode 100644
index 0000000000..be556504c4
--- /dev/null
+++ b/target/linux/mvebu/image/gl-mv1000.mk
@@ -0,0 +1,29 @@
+ifeq ($(SUBTARGET),cortexa53)
+
+define Device/gl-mv1000
+ KERNEL_NAME := Image
+ KERNEL_LOADADDR := 0x000080000
+ KERNEL := kernel-bin | lzma | uImage lzma | pad-dtb | append-gl-metadata
+ DEVICE_TITLE := GL.iNet GL-MV1000
+ DEVICE_PACKAGES := e2fsprogs ethtool mkf2fs kmod-fs-vfat kmod-usb2 kmod-usb3 kmod-usb-storage
+ BLOCKSIZE := 64k
+ IMAGES := sysupgrade.bin
+ IMAGE_SIZE := 15000k
+ IMAGE/sysupgrade.bin := append-kernel | pad-to $$$$(BLOCKSIZE) | append-rootfs | pad-rootfs | append-metadata | check-size $$$$(IMAGE_SIZE)
+ DEVICE_DTS := armada-gl-mv1000
+ DTS_DIR := $(DTS_DIR)/marvell
+ SUPPORTED_DEVICES := gl-mv1000
+endef
+TARGET_DEVICES += gl-mv1000
+
+define Device/gl-mv1000-emmc
+ $(call Device/Default-arm64-emmc)
+ DEVICE_TITLE := GL.iNet GL-MV1000 EMMC
+ DEVICE_DTS := armada-gl-mv1000-emmc
+ SUPPORTED_DEVICES := gl-mv1000-emmc
+endef
+
+TARGET_DEVICES += gl-mv1000-emmc
+
+endif
+
diff --git a/target/linux/mvebu/patches-4.14/534-arm64-add-keys-configure-for-mv1000.patch b/target/linux/mvebu/patches-4.14/534-arm64-add-keys-configure-for-mv1000.patch
new file mode 100644
index 0000000000..555d5c1e6a
--- /dev/null
+++ b/target/linux/mvebu/patches-4.14/534-arm64-add-keys-configure-for-mv1000.patch
@@ -0,0 +1,106 @@
+--- a/drivers/input/keyboard/keys-glmv1000.c 1969-12-31 16:00:00.000000000 -0800
++++ b/drivers/input/keyboard/keys-glmv1000.c 2019-06-05 05:57:25.645189647 -0700
+@@ -0,0 +1,78 @@
++/*
++ * Keypad device register for GL.inet mv1000.
++ *
++ */
++
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/platform_device.h>
++#include <linux/input.h>
++#include <linux/slab.h>
++#include <linux/device.h>
++#include <linux/gpio_keys.h>
++
++static struct gpio_keys_button gpio_keys[] = {
++ {
++ .desc = "vpn",
++ .type = EV_KEY,
++ .code = KEY_WPS_BUTTON,
++ .debounce_interval = 60,
++ .gpio = 468,
++ .active_low = 0,
++ },
++ {
++ .desc = "reset",
++ .type = EV_KEY,
++ .code = KEY_RESTART,
++ .debounce_interval = 60,
++ .gpio = 490,
++ .active_low = 1,
++ },
++};
++
++static struct gpio_keys_platform_data gpio_keys_info = {
++ .buttons = gpio_keys,
++ .nbuttons = ARRAY_SIZE(gpio_keys),
++ .poll_interval = 20,
++ .rep = 1,
++};
++
++static void key_dev_release(struct device *dev)
++{
++
++}
++
++static struct platform_device keys_gpio = {
++ .name = "gpio-keys-polled",
++ .id = -1,
++ .dev = {
++ .platform_data = &gpio_keys_info,
++ .release = key_dev_release,
++ },
++};
++
++static int __init glmv1000_key_init(void)
++{
++ int ret;
++
++ ret = platform_device_register(&keys_gpio);
++
++ if(ret){
++ pr_err("Error registering mv1000 keys deviecs\n");
++ platform_device_unregister(&keys_gpio);
++ }
++
++ return ret;
++}
++
++static void __exit glmv1000_key_exit(void)
++{
++ platform_device_unregister(&keys_gpio);
++}
++
++module_init(glmv1000_key_init);
++module_exit(glmv1000_key_exit);
++
++MODULE_AUTHOR("li.zhang <li.zhang@gl-inet.com>");
++MODULE_DESCRIPTION("GL.inet MV1000 KEY");
++MODULE_LICENSE("GPL");
+--- a/drivers/input/keyboard/Makefile 2019-06-05 20:11:48.658047482 -0700
++++ b/drivers/input/keyboard/Makefile 2019-06-05 05:57:25.621189647 -0700
+@@ -5,6 +5,7 @@
+
+ # Each configuration option enables a list of files.
+
++obj-$(CONFIG_KEYBOARD_GLMV1000) += keys-glmv1000.o
+ obj-$(CONFIG_KEYBOARD_ADC) += adc-keys.o
+ obj-$(CONFIG_KEYBOARD_ADP5520) += adp5520-keys.o
+ obj-$(CONFIG_KEYBOARD_ADP5588) += adp5588-keys.o
+--- a/drivers/input/keyboard/Kconfig 2019-06-05 20:11:48.658047482 -0700
++++ b/drivers/input/keyboard/Kconfig 2019-06-05 05:57:25.621189647 -0700
+@@ -12,6 +12,12 @@
+
+ if INPUT_KEYBOARD
+
++config KEYBOARD_GLMV1000
++ tristate "GLMV1000 Buttons"
++ select INPUT_POLLDEV
++ help
++ This driver implements support for buttons register for gl-mv1000.
++
+ config KEYBOARD_ADC
+ tristate "ADC Ladder Buttons"
+ depends on IIO
diff --git a/target/linux/mvebu/patches-4.14/535-arm64-del-mmc-led.patch b/target/linux/mvebu/patches-4.14/535-arm64-del-mmc-led.patch
new file mode 100644
index 0000000000..bcc4e5f9f5
--- /dev/null
+++ b/target/linux/mvebu/patches-4.14/535-arm64-del-mmc-led.patch
@@ -0,0 +1,19 @@
+--- a/drivers/mmc/host/sdhci.c 2019-07-08 22:26:53.542096346 +0800
++++ b/drivers/mmc/host/sdhci.c 2019-07-08 22:27:47.426457950 +0800
+@@ -3967,14 +3967,14 @@
+ mmc_hostname(mmc), host->irq, ret);
+ goto untasklet;
+ }
+-
++#if 0
+ ret = sdhci_led_register(host);
+ if (ret) {
+ pr_err("%s: Failed to register LED device: %d\n",
+ mmc_hostname(mmc), ret);
+ goto unirq;
+ }
+-
++#endif
+ mmiowb();
+
+ ret = mmc_add_host(mmc);
diff --git a/target/linux/mvebu/patches-4.14/536-arm64-usb2-driver-adaptas-rtl8192eu-for-mv1000.patch b/target/linux/mvebu/patches-4.14/536-arm64-usb2-driver-adaptas-rtl8192eu-for-mv1000.patch
new file mode 100644
index 0000000000..c138e5622f
--- /dev/null
+++ b/target/linux/mvebu/patches-4.14/536-arm64-usb2-driver-adaptas-rtl8192eu-for-mv1000.patch
@@ -0,0 +1,239 @@
+--- a/drivers/usb/host/ehci-orion.c 2019-06-26 06:08:32.407586970 -0700
++++ b/drivers/usb/host/ehci-orion.c 2019-06-26 05:43:29.571455440 -0700
+@@ -48,16 +48,17 @@
+ #define USB_PHY_TST_GRP_CTRL 0x450
+
+ #define USB_SBUSCFG 0x90
+-
+-/* BAWR = BARD = 3 : Align read/write bursts packets larger than 128 bytes */
+-#define USB_SBUSCFG_BAWR_ALIGN_128B (0x3 << 6)
+-#define USB_SBUSCFG_BARD_ALIGN_128B (0x3 << 3)
+-/* AHBBRST = 3 : Align AHB Burst to INCR16 (64 bytes) */
+-#define USB_SBUSCFG_AHBBRST_INCR16 (0x3 << 0)
+-
+-#define USB_SBUSCFG_DEF_VAL (USB_SBUSCFG_BAWR_ALIGN_128B \
+- | USB_SBUSCFG_BARD_ALIGN_128B \
+- | USB_SBUSCFG_AHBBRST_INCR16)
++#define USB_SBUSCFG_BAWR_OFF 0x6
++#define USB_SBUSCFG_BARD_OFF 0x3
++#define USB_SBUSCFG_AHBBRST_OFF 0x0
++
++#define USB_SBUSCFG_BAWR_ALIGN_128B 0x3
++#define USB_SBUSCFG_BARD_ALIGN_128B 0x3
++#define USB_SBUSCFG_AHBBRST_INCR16 0x3
++
++#define USB_SBUSCFG_DEF_VAL ((USB_SBUSCFG_BAWR_ALIGN_128B << USB_SBUSCFG_BAWR_OFF) \
++ | (USB_SBUSCFG_BARD_ALIGN_128B << USB_SBUSCFG_BARD_OFF) \
++ | (USB_SBUSCFG_AHBBRST_INCR16 << USB_SBUSCFG_AHBBRST_OFF))
+
+ #define DRIVER_DESC "EHCI orion driver"
+
+@@ -66,12 +67,16 @@
+ struct orion_ehci_hcd {
+ struct clk *clk;
+ struct phy *phy;
++ bool reset_on_resume;
+ };
+
+ static const char hcd_name[] = "ehci-orion";
+
+ static struct hc_driver __read_mostly ehci_orion_hc_driver;
+
++static u32 usb_save[(USB_IPG - USB_CAUSE) +
++ (USB_PHY_TST_GRP_CTRL - USB_PHY_PWR_CTRL)];
++
+ /*
+ * Implement Orion USB controller specification guidelines
+ */
+@@ -166,23 +171,32 @@
+ static int ehci_orion_drv_reset(struct usb_hcd *hcd)
+ {
+ struct device *dev = hcd->self.controller;
+- int ret;
++ int retval;
++ uint32_t regVal;
+
+- ret = ehci_setup(hcd);
+- if (ret)
+- return ret;
++ retval = ehci_setup(hcd);
++ if (retval)
++ dev_err(dev, "ehci_setup failed %d\n", retval);
+
+- /*
+- * For SoC without hlock, need to program sbuscfg value to guarantee
++ /* For SoC without hlock, need to program sbuscfg value to guarantee
+ * AHB master's burst would not overrun or underrun FIFO.
+ *
+ * sbuscfg reg has to be set after usb controller reset, otherwise
+ * the value would be override to 0.
++ *
++ * BAWR = BARD = 3 : Align read/write bursts packets larger than 128 bytes
++ * AHBBRST = 3 : Align AHB Burst to INCR16 (64 bytes)
+ */
+- if (of_device_is_compatible(dev->of_node, "marvell,armada-3700-ehci"))
++ if (of_device_is_compatible(dev->of_node, "marvell,armada-3700-ehci")) {
+ wrl(USB_SBUSCFG, USB_SBUSCFG_DEF_VAL);
++ /*
++ * Disable Streaming to guaratee DDR access in low bandwidth systems.
++ */
++ regVal = rdl(USB_MODE);
++ wrl(USB_MODE, regVal | USB_MODE_SDIS);
++ }
+
+- return ret;
++ return retval;
+ }
+
+ static const struct ehci_driver_overrides orion_overrides __initconst = {
+@@ -256,6 +270,11 @@
+ if (!IS_ERR(priv->clk))
+ clk_prepare_enable(priv->clk);
+
++ if (of_property_read_bool(pdev->dev.of_node, "needs-reset-on-resume"))
++ priv->reset_on_resume = true;
++ else
++ priv->reset_on_resume = false;
++
+ priv->phy = devm_phy_optional_get(&pdev->dev, "usb");
+ if (IS_ERR(priv->phy)) {
+ err = PTR_ERR(priv->phy);
+@@ -343,6 +362,125 @@
+ return 0;
+ }
+
++#ifdef CONFIG_PM
++static int ehci_orion_drv_suspend(struct platform_device *pdev,
++ pm_message_t state)
++{
++ struct usb_hcd *hcd = platform_get_drvdata(pdev);
++ bool do_wakeup = device_may_wakeup(&pdev->dev);
++ int addr, i, rc;
++
++ for (addr = USB_CAUSE, i = 0; addr <= USB_IPG; addr += 0x4, i++)
++ usb_save[i] = readl_relaxed(hcd->regs + addr);
++
++ for (addr = USB_PHY_PWR_CTRL; addr <= USB_PHY_TST_GRP_CTRL;
++ addr += 0x4, i++)
++ usb_save[i] = readl_relaxed(hcd->regs + addr);
++
++ rc = ehci_suspend(hcd, do_wakeup);
++ if (rc)
++ return rc;
++
++ /* Power off PHY */
++ phy_power_off(hcd->phy);
++ phy_exit(hcd->phy);
++
++ return 0;
++}
++#endif
++
++#define MV_USB_CORE_CMD_RESET_BIT 1
++#define MV_USB_CORE_CMD_RESET_MASK (1 << MV_USB_CORE_CMD_RESET_BIT)
++#define MV_USB_CORE_MODE_OFFSET 0
++#define MV_USB_CORE_MODE_MASK (3 << MV_USB_CORE_MODE_OFFSET)
++#define MV_USB_CORE_MODE_HOST (3 << MV_USB_CORE_MODE_OFFSET)
++#define MV_USB_CORE_MODE_DEVICE (2 << MV_USB_CORE_MODE_OFFSET)
++#define MV_USB_CORE_CMD_RUN_BIT 0
++#define MV_USB_CORE_CMD_RUN_MASK (1 << MV_USB_CORE_CMD_RUN_BIT)
++
++#ifdef CONFIG_PM
++static int ehci_orion_drv_resume(struct platform_device *pdev)
++{
++ struct usb_hcd *hcd = platform_get_drvdata(pdev);
++ struct orion_ehci_hcd *priv = hcd_to_orion_priv(hcd);
++ int addr, regVal, i, rc;
++
++ /* Init and power on PHY */
++ rc = phy_init(hcd->phy);
++ if (rc)
++ return rc;
++
++ rc = phy_power_on(hcd->phy);
++ if (rc) {
++ phy_exit(hcd->phy);
++ return rc;
++ }
++
++ for (addr = USB_CAUSE, i = 0; addr <= USB_IPG; addr += 0x4, i++)
++ writel_relaxed(usb_save[i], hcd->regs + addr);
++
++ for (addr = USB_PHY_PWR_CTRL; addr <= USB_PHY_TST_GRP_CTRL;
++ addr += 0x4, i++)
++ writel_relaxed(usb_save[i], hcd->regs + addr);
++
++ /* Clear Interrupt Cause and Mask registers */
++ writel_relaxed(0, hcd->regs + 0x310);
++ writel_relaxed(0, hcd->regs + 0x314);
++
++ /* Reset controller */
++ regVal = readl_relaxed(hcd->regs + 0x140);
++ writel_relaxed(regVal | MV_USB_CORE_CMD_RESET_MASK, hcd->regs + 0x140);
++ while (readl_relaxed(hcd->regs + 0x140) & MV_USB_CORE_CMD_RESET_MASK)
++ ;
++
++ /* Set Mode register (Stop and Reset USB Core before) */
++ /* Stop the controller */
++ regVal = readl_relaxed(hcd->regs + 0x140);
++ regVal &= ~MV_USB_CORE_CMD_RUN_MASK;
++ writel_relaxed(regVal, hcd->regs + 0x140);
++
++ /* Reset the controller to get default values */
++ regVal = readl_relaxed(hcd->regs + 0x140);
++ regVal |= MV_USB_CORE_CMD_RESET_MASK;
++ writel_relaxed(regVal, hcd->regs + 0x140);
++
++ /* Wait for the controller reset to complete */
++ do {
++ regVal = readl_relaxed(hcd->regs + 0x140);
++ } while (regVal & MV_USB_CORE_CMD_RESET_MASK);
++
++ /* Set USB_MODE register */
++ regVal = MV_USB_CORE_MODE_HOST;
++ writel_relaxed(regVal, hcd->regs + 0x1A8);
++
++ ehci_resume(hcd, priv->reset_on_resume);
++
++ return 0;
++}
++#endif
++
++static void ehci_orion_drv_shutdown(struct platform_device *pdev)
++{
++ struct usb_hcd *hcd = platform_get_drvdata(pdev);
++ static void __iomem *usb_pwr_ctrl_base;
++ struct clk *clk;
++
++ usb_hcd_platform_shutdown(pdev);
++
++ usb_pwr_ctrl_base = hcd->regs + USB_PHY_PWR_CTRL;
++ BUG_ON(!usb_pwr_ctrl_base);
++ /* Power Down & PLL Power down */
++ writel((readl(usb_pwr_ctrl_base) & ~(BIT(0) | BIT(1))),
++ usb_pwr_ctrl_base);
++
++ clk = clk_get(&pdev->dev, NULL);
++ if (!IS_ERR(clk)) {
++ clk_disable_unprepare(clk);
++ clk_put(clk);
++ }
++
++}
++
+ static const struct of_device_id ehci_orion_dt_ids[] = {
+ { .compatible = "marvell,orion-ehci", },
+ { .compatible = "marvell,armada-3700-ehci", },
+@@ -353,7 +491,11 @@
+ static struct platform_driver ehci_orion_driver = {
+ .probe = ehci_orion_drv_probe,
+ .remove = ehci_orion_drv_remove,
+- .shutdown = usb_hcd_platform_shutdown,
++#ifdef CONFIG_PM
++ .suspend = ehci_orion_drv_suspend,
++ .resume = ehci_orion_drv_resume,
++#endif
++ .shutdown = ehci_orion_drv_shutdown,
+ .driver = {
+ .name = "orion-ehci",
+ .of_match_table = ehci_orion_dt_ids,
diff --git a/target/linux/mvebu/patches-4.14/540-arm64-add-udc-driver.patch b/target/linux/mvebu/patches-4.14/540-arm64-add-udc-driver.patch
new file mode 100644
index 0000000000..f58925ecf2
--- /dev/null
+++ b/target/linux/mvebu/patches-4.14/540-arm64-add-udc-driver.patch
@@ -0,0 +1,4376 @@
+--- a/drivers/usb/gadget/udc/Kconfig
++++ b/drivers/usb/gadget/udc/Kconfig
+@@ -255,6 +255,12 @@ config USB_MV_U3D
+ MARVELL PXA2128 Processor series include a super speed USB3.0 device
+ controller, which support super speed USB peripheral.
+
++config USB_MVEBU_U3D
++ tristate "Marvell Armada 38X/3700/8K USB 3.0 controller"
++ help
++ MARVELL Armada 38X/3700/8K Processors series include a super speed
++ USB3.0 device controller, which support super speed USB peripheral.
++
+ config USB_SNP_CORE
+ depends on (USB_AMD5536UDC || USB_SNP_UDC_PLAT)
+ depends on HAS_DMA
+--- a/drivers/usb/gadget/udc/Makefile
++++ b/drivers/usb/gadget/udc/Makefile
+@@ -36,6 +36,7 @@ mv_udc-y := mv_udc_core.o
+ obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o
+ obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o
+ obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o
++obj-$(CONFIG_USB_MVEBU_U3D) += mvebu_glue.o mvebu_u3d.o
+ obj-$(CONFIG_USB_GR_UDC) += gr_udc.o
+ obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o
+ obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o
+--- a/drivers/usb/gadget/udc/core.c
++++ b/drivers/usb/gadget/udc/core.c
+@@ -32,26 +32,6 @@
+
+ #include "trace.h"
+
+-/**
+- * struct usb_udc - describes one usb device controller
+- * @driver - the gadget driver pointer. For use by the class code
+- * @dev - the child device to the actual controller
+- * @gadget - the gadget. For use by the class code
+- * @list - for use by the udc class driver
+- * @vbus - for udcs who care about vbus status, this value is real vbus status;
+- * for udcs who do not care about vbus status, this value is always true
+- *
+- * This represents the internal data structure which is used by the UDC-class
+- * to hold information about udc driver and gadget together.
+- */
+-struct usb_udc {
+- struct usb_gadget_driver *driver;
+- struct usb_gadget *gadget;
+- struct device dev;
+- struct list_head list;
+- bool vbus;
+-};
+-
+ static struct class *udc_class;
+ static LIST_HEAD(udc_list);
+ static LIST_HEAD(gadget_driver_pending_list);
+@@ -1358,35 +1338,14 @@ int usb_gadget_probe_driver(struct usb_g
+ return -EINVAL;
+
+ mutex_lock(&udc_lock);
+- if (driver->udc_name) {
+- list_for_each_entry(udc, &udc_list, list) {
+- ret = strcmp(driver->udc_name, dev_name(&udc->dev));
+- if (!ret)
+- break;
+- }
+- if (ret)
+- ret = -ENODEV;
+- else if (udc->driver)
+- ret = -EBUSY;
+- else
+- goto found;
+- } else {
+- list_for_each_entry(udc, &udc_list, list) {
+- /* For now we take the first one */
+- if (!udc->driver)
+- goto found;
+- }
+- }
+
+- if (!driver->match_existing_only) {
+- list_add_tail(&driver->pending, &gadget_driver_pending_list);
+- pr_info("udc-core: couldn't find an available UDC - added [%s] to list of pending drivers\n",
+- driver->function);
+- ret = 0;
+- }
++ udc = udc_detect(&udc_list, driver);
++ if (udc)
++ goto found;
+
++ pr_debug("couldn't find an available UDC\n");
+ mutex_unlock(&udc_lock);
+- return ret;
++ return -ENODEV;
+ found:
+ ret = udc_bind_to_driver(udc, driver);
+ mutex_unlock(&udc_lock);
+--- a/drivers/usb/gadget/udc/mv_udc.h
++++ b/drivers/usb/gadget/udc/mv_udc.h
+@@ -196,6 +196,8 @@ struct mv_udc {
+ struct mv_req *status_req;
+ struct usb_ctrlrequest local_setup_buff;
+
++ struct phy *utmi_phy;
++
+ unsigned int resume_state; /* USB state to resume */
+ unsigned int usb_state; /* USB current state */
+ unsigned int ep0_state; /* Endpoint zero state */
+@@ -214,6 +216,8 @@ struct mv_udc {
+ active:1,
+ stopped:1; /* stop bit is setted */
+
++
++ int vbus_pin;
+ struct work_struct vbus_work;
+ struct workqueue_struct *qwork;
+
+--- a/drivers/usb/gadget/udc/mv_udc_core.c
++++ b/drivers/usb/gadget/udc/mv_udc_core.c
+@@ -33,12 +33,16 @@
+ #include <linux/irq.h>
+ #include <linux/platform_device.h>
+ #include <linux/clk.h>
++#include <linux/of.h>
++#include <linux/of_gpio.h>
+ #include <linux/platform_data/mv_usb.h>
+ #include <asm/unaligned.h>
++#include <linux/gpio.h>
+
+ #include "mv_udc.h"
+
+ #define DRIVER_DESC "Marvell PXA USB Device Controller driver"
++#define DRIVER_VERSION "8 Nov 2010"
+
+ #define ep_dir(ep) (((ep)->ep_num == 0) ? \
+ ((ep)->udc->ep0_dir) : ((ep)->direction))
+@@ -68,7 +72,7 @@ static const struct usb_endpoint_descrip
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 0,
+ .bmAttributes = USB_ENDPOINT_XFER_CONTROL,
+- .wMaxPacketSize = EP0_MAX_PKT_SIZE,
++ .wMaxPacketSize = cpu_to_le16(EP0_MAX_PKT_SIZE),
+ };
+
+ static void ep0_reset(struct mv_udc *udc)
+@@ -86,11 +90,11 @@ static void ep0_reset(struct mv_udc *udc
+ ep->dqh = &udc->ep_dqh[i];
+
+ /* configure ep0 endpoint capabilities in dQH */
+- ep->dqh->max_packet_length =
++ ep->dqh->max_packet_length = cpu_to_le32(
+ (EP0_MAX_PKT_SIZE << EP_QUEUE_HEAD_MAX_PKT_LEN_POS)
+- | EP_QUEUE_HEAD_IOS;
++ | EP_QUEUE_HEAD_IOS);
+
+- ep->dqh->next_dtd_ptr = EP_QUEUE_HEAD_NEXT_TERMINATE;
++ ep->dqh->next_dtd_ptr = cpu_to_le32(EP_QUEUE_HEAD_NEXT_TERMINATE);
+
+ epctrlx = readl(&udc->op_regs->epctrlx[0]);
+ if (i) { /* TX */
+@@ -128,7 +132,7 @@ static int process_ep_req(struct mv_udc
+ {
+ struct mv_dtd *curr_dtd;
+ struct mv_dqh *curr_dqh;
+- int actual, remaining_length;
++ int td_complete, actual, remaining_length;
+ int i, direction;
+ int retval = 0;
+ u32 errors;
+@@ -138,19 +142,20 @@ static int process_ep_req(struct mv_udc
+ direction = index % 2;
+
+ curr_dtd = curr_req->head;
++ td_complete = 0;
+ actual = curr_req->req.length;
+
+ for (i = 0; i < curr_req->dtd_count; i++) {
+- if (curr_dtd->size_ioc_sts & DTD_STATUS_ACTIVE) {
++ if (le32_to_cpu(curr_dtd->size_ioc_sts) & DTD_STATUS_ACTIVE) {
+ dev_dbg(&udc->dev->dev, "%s, dTD not completed\n",
+ udc->eps[index].name);
+ return 1;
+ }
+
+- errors = curr_dtd->size_ioc_sts & DTD_ERROR_MASK;
++ errors = le32_to_cpu(curr_dtd->size_ioc_sts) & DTD_ERROR_MASK;
+ if (!errors) {
+ remaining_length =
+- (curr_dtd->size_ioc_sts & DTD_PACKET_SIZE)
++ (le32_to_cpu(curr_dtd->size_ioc_sts) & DTD_PACKET_SIZE)
+ >> DTD_LENGTH_BIT_POS;
+ actual -= remaining_length;
+
+@@ -170,7 +175,8 @@ static int process_ep_req(struct mv_udc
+ errors);
+ if (errors & DTD_STATUS_HALTED) {
+ /* Clear the errors and Halt condition */
+- curr_dqh->size_ioc_int_sts &= ~errors;
++ curr_dqh->size_ioc_int_sts =
++ cpu_to_le32(le32_to_cpu(curr_dqh->size_ioc_int_sts) & (~errors));
+ retval = -EPIPE;
+ } else if (errors & DTD_STATUS_DATA_BUFF_ERR) {
+ retval = -EPROTO;
+@@ -189,8 +195,8 @@ static int process_ep_req(struct mv_udc
+ else
+ bit_pos = 1 << (16 + curr_req->ep->ep_num);
+
+- while ((curr_dqh->curr_dtd_ptr == curr_dtd->td_dma)) {
+- if (curr_dtd->dtd_next == EP_QUEUE_HEAD_NEXT_TERMINATE) {
++ while ((curr_dqh->curr_dtd_ptr == cpu_to_le32(curr_dtd->td_dma))) {
++ if (curr_dtd->dtd_next == cpu_to_le32(EP_QUEUE_HEAD_NEXT_TERMINATE)) {
+ while (readl(&udc->op_regs->epstatus) & bit_pos)
+ udelay(1);
+ break;
+@@ -247,7 +253,8 @@ static void done(struct mv_ep *ep, struc
+
+ spin_unlock(&ep->udc->lock);
+
+- usb_gadget_giveback_request(&ep->ep, &req->req);
++ if (req->req.complete)
++ usb_gadget_giveback_request(&ep->ep, &req->req);
+
+ spin_lock(&ep->udc->lock);
+ ep->stopped = stopped;
+@@ -272,7 +279,7 @@ static int queue_dtd(struct mv_ep *ep, s
+ struct mv_req *lastreq;
+ lastreq = list_entry(ep->queue.prev, struct mv_req, queue);
+ lastreq->tail->dtd_next =
+- req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK;
++ cpu_to_le32(req->head->td_dma & EP_QUEUE_HEAD_NEXT_POINTER_MASK);
+
+ wmb();
+
+@@ -320,11 +327,12 @@ static int queue_dtd(struct mv_ep *ep, s
+ }
+
+ /* Write dQH next pointer and terminate bit to 0 */
+- dqh->next_dtd_ptr = req->head->td_dma
+- & EP_QUEUE_HEAD_NEXT_POINTER_MASK;
++ dqh->next_dtd_ptr = cpu_to_le32(req->head->td_dma
++ & EP_QUEUE_HEAD_NEXT_POINTER_MASK);
+
+ /* clear active and halt bit, in case set from a previous error */
+- dqh->size_ioc_int_sts &= ~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED);
++ dqh->size_ioc_int_sts =
++ cpu_to_le32(le32_to_cpu(dqh->size_ioc_int_sts) & (~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED)));
+
+ /* Ensure that updates to the QH will occur before priming. */
+ wmb();
+@@ -347,7 +355,7 @@ static struct mv_dtd *build_dtd(struct m
+ /* how big will this transfer be? */
+ if (usb_endpoint_xfer_isoc(req->ep->ep.desc)) {
+ dqh = req->ep->dqh;
+- mult = (dqh->max_packet_length >> EP_QUEUE_HEAD_MULT_POS)
++ mult = (le32_to_cpu(dqh->max_packet_length) >> EP_QUEUE_HEAD_MULT_POS)
+ & 0x3;
+ *length = min(req->req.length - req->req.actual,
+ (unsigned)(mult * req->ep->ep.maxpacket));
+@@ -397,7 +405,7 @@ static struct mv_dtd *build_dtd(struct m
+
+ temp |= mult << 10;
+
+- dtd->size_ioc_sts = temp;
++ dtd->size_ioc_sts = cpu_to_le32(temp);
+
+ mb();
+
+@@ -410,8 +418,11 @@ static int req_to_dtd(struct mv_req *req
+ unsigned count;
+ int is_last, is_first = 1;
+ struct mv_dtd *dtd, *last_dtd = NULL;
++ struct mv_udc *udc;
+ dma_addr_t dma;
+
++ udc = req->ep->udc;
++
+ do {
+ dtd = build_dtd(req, &count, &dma, &is_last);
+ if (dtd == NULL)
+@@ -421,7 +432,7 @@ static int req_to_dtd(struct mv_req *req
+ is_first = 0;
+ req->head = dtd;
+ } else {
+- last_dtd->dtd_next = dma;
++ last_dtd->dtd_next = cpu_to_le32(dma);
+ last_dtd->next_dtd_virt = dtd;
+ }
+ last_dtd = dtd;
+@@ -429,8 +440,7 @@ static int req_to_dtd(struct mv_req *req
+ } while (!is_last);
+
+ /* set terminate bit to 1 for the last dTD */
+- dtd->dtd_next = DTD_NEXT_TERMINATE;
+-
++ dtd->dtd_next = cpu_to_le32(DTD_NEXT_TERMINATE);
+ req->tail = dtd;
+
+ return 0;
+@@ -444,8 +454,7 @@ static int mv_ep_enable(struct usb_ep *_
+ struct mv_dqh *dqh;
+ u16 max = 0;
+ u32 bit_pos, epctrlx, direction;
+- const unsigned char zlt = 1;
+- unsigned char ios, mult;
++ unsigned char zlt = 0, ios = 0, mult = 0;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct mv_ep, ep);
+@@ -465,6 +474,8 @@ static int mv_ep_enable(struct usb_ep *_
+ * disable HW zero length termination select
+ * driver handles zero length packet through req->req.zero
+ */
++ zlt = 1;
++
+ bit_pos = 1 << ((direction == EP_DIR_OUT ? 0 : 16) + ep->ep_num);
+
+ /* Check if the Endpoint is Primed */
+@@ -479,20 +490,21 @@ static int mv_ep_enable(struct usb_ep *_
+ (unsigned)bit_pos);
+ goto en_done;
+ }
+-
+ /* Set the max packet length, interrupt on Setup and Mult fields */
+- ios = 0;
+- mult = 0;
+ switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
+ case USB_ENDPOINT_XFER_BULK:
+- case USB_ENDPOINT_XFER_INT:
++ zlt = 1;
++ mult = 0;
+ break;
+ case USB_ENDPOINT_XFER_CONTROL:
+ ios = 1;
++ case USB_ENDPOINT_XFER_INT:
++ mult = 0;
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ /* Calculate transactions needed for high bandwidth iso */
+- mult = usb_endpoint_maxp_mult(desc);
++ mult = (unsigned char)(1 + ((max >> 11) & 0x03));
++ max = max & 0x7ff; /* bit 0~10 */
+ /* 3 transactions at most */
+ if (mult > 3)
+ goto en_done;
+@@ -504,12 +516,12 @@ static int mv_ep_enable(struct usb_ep *_
+ spin_lock_irqsave(&udc->lock, flags);
+ /* Get the endpoint queue head address */
+ dqh = ep->dqh;
+- dqh->max_packet_length = (max << EP_QUEUE_HEAD_MAX_PKT_LEN_POS)
++ dqh->max_packet_length = cpu_to_le32((max << EP_QUEUE_HEAD_MAX_PKT_LEN_POS)
+ | (mult << EP_QUEUE_HEAD_MULT_POS)
+ | (zlt ? EP_QUEUE_HEAD_ZLT_SEL : 0)
+- | (ios ? EP_QUEUE_HEAD_IOS : 0);
+- dqh->next_dtd_ptr = 1;
+- dqh->size_ioc_int_sts = 0;
++ | (ios ? EP_QUEUE_HEAD_IOS : 0));
++ dqh->next_dtd_ptr = cpu_to_le32(1);
++ dqh->size_ioc_int_sts = cpu_to_le32(0);
+
+ ep->ep.maxpacket = max;
+ ep->ep.desc = desc;
+@@ -560,7 +572,7 @@ static int mv_ep_disable(struct usb_ep
+ struct mv_udc *udc;
+ struct mv_ep *ep;
+ struct mv_dqh *dqh;
+- u32 epctrlx, direction;
++ u32 bit_pos, epctrlx, direction;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct mv_ep, ep);
+@@ -575,9 +587,10 @@ static int mv_ep_disable(struct usb_ep
+ spin_lock_irqsave(&udc->lock, flags);
+
+ direction = ep_dir(ep);
++ bit_pos = 1 << ((direction == EP_DIR_OUT ? 0 : 16) + ep->ep_num);
+
+ /* Reset the max packet length and the interrupt on Setup */
+- dqh->max_packet_length = 0;
++ dqh->max_packet_length = cpu_to_le32(0);
+
+ /* Disable the endpoint for Rx or Tx and reset the endpoint type */
+ epctrlx = readl(&udc->op_regs->epctrlx[ep->ep_num]);
+@@ -757,11 +770,12 @@ static void mv_prime_ep(struct mv_ep *ep
+ u32 bit_pos;
+
+ /* Write dQH next pointer and terminate bit to 0 */
+- dqh->next_dtd_ptr = req->head->td_dma
+- & EP_QUEUE_HEAD_NEXT_POINTER_MASK;
++ dqh->next_dtd_ptr = cpu_to_le32(req->head->td_dma
++ & EP_QUEUE_HEAD_NEXT_POINTER_MASK);
+
+ /* clear active and halt bit, in case set from a previous error */
+- dqh->size_ioc_int_sts &= ~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED);
++ dqh->size_ioc_int_sts =
++ cpu_to_le32(le32_to_cpu(dqh->size_ioc_int_sts) & (~(DTD_STATUS_ACTIVE | DTD_STATUS_HALTED)));
+
+ /* Ensure that updates to the QH will occure before priming. */
+ wmb();
+@@ -825,8 +839,8 @@ static int mv_ep_dequeue(struct usb_ep *
+ struct mv_dqh *qh;
+
+ qh = ep->dqh;
+- qh->next_dtd_ptr = 1;
+- qh->size_ioc_int_sts = 0;
++ qh->next_dtd_ptr = cpu_to_le32(1);
++ qh->size_ioc_int_sts = cpu_to_le32(0);
+ }
+
+ /* The request hasn't been processed, patch up the TD chain */
+@@ -836,7 +850,6 @@ static int mv_ep_dequeue(struct usb_ep *
+ prev_req = list_entry(req->queue.prev, struct mv_req, queue);
+ writel(readl(&req->tail->dtd_next),
+ &prev_req->tail->dtd_next);
+-
+ }
+
+ done(ep, req, -ECONNRESET);
+@@ -944,7 +957,7 @@ static int mv_ep_set_wedge(struct usb_ep
+ return mv_ep_set_halt_wedge(_ep, 1, 1);
+ }
+
+-static const struct usb_ep_ops mv_ep_ops = {
++static struct usb_ep_ops mv_ep_ops = {
+ .enable = mv_ep_enable,
+ .disable = mv_ep_disable,
+
+@@ -959,9 +972,9 @@ static const struct usb_ep_ops mv_ep_ops
+ .fifo_flush = mv_ep_fifo_flush, /* flush fifo */
+ };
+
+-static int udc_clock_enable(struct mv_udc *udc)
++static void udc_clock_enable(struct mv_udc *udc)
+ {
+- return clk_prepare_enable(udc->clk);
++ clk_prepare_enable(udc->clk);
+ }
+
+ static void udc_clock_disable(struct mv_udc *udc)
+@@ -1069,11 +1082,8 @@ static int mv_udc_enable_internal(struct
+ return 0;
+
+ dev_dbg(&udc->dev->dev, "enable udc\n");
+- retval = udc_clock_enable(udc);
+- if (retval)
+- return retval;
+-
+- if (udc->pdata->phy_init) {
++ udc_clock_enable(udc);
++ if (udc->pdata && udc->pdata->phy_init) {
+ retval = udc->pdata->phy_init(udc->phy_regs);
+ if (retval) {
+ dev_err(&udc->dev->dev,
+@@ -1099,7 +1109,7 @@ static void mv_udc_disable_internal(stru
+ {
+ if (udc->active) {
+ dev_dbg(&udc->dev->dev, "disable udc\n");
+- if (udc->pdata->phy_deinit)
++ if (udc->pdata && udc->pdata->phy_deinit)
+ udc->pdata->phy_deinit(udc->phy_regs);
+ udc_clock_disable(udc);
+ udc->active = 0;
+@@ -1369,6 +1379,9 @@ static int mv_udc_start(struct usb_gadge
+ udc->ep0_state = WAIT_FOR_SETUP;
+ udc->ep0_dir = EP_DIR_OUT;
+
++ if (gpio_is_valid(udc->vbus_pin))
++ enable_irq(gpio_to_irq(udc->vbus_pin));
++
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ if (udc->transceiver) {
+@@ -1396,6 +1409,9 @@ static int mv_udc_stop(struct usb_gadget
+
+ udc = container_of(gadget, struct mv_udc, gadget);
+
++ if (gpio_is_valid(udc->vbus_pin))
++ disable_irq(gpio_to_irq(udc->vbus_pin));
++
+ spin_lock_irqsave(&udc->lock, flags);
+
+ mv_udc_enable(udc);
+@@ -1517,7 +1533,7 @@ static void mv_udc_testmode(struct mv_ud
+
+ static void ch9setaddress(struct mv_udc *udc, struct usb_ctrlrequest *setup)
+ {
+- udc->dev_addr = (u8)setup->wValue;
++ udc->dev_addr = le16_to_cpu(setup->wValue);
+
+ /* update usb state */
+ udc->usb_state = USB_STATE_ADDRESS;
+@@ -1547,8 +1563,8 @@ static void ch9getstatus(struct mv_udc *
+ == USB_RECIP_ENDPOINT) {
+ u8 ep_num, direction;
+
+- ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK;
+- direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK)
++ ep_num = le16_to_cpu(setup->wIndex) & USB_ENDPOINT_NUMBER_MASK;
++ direction = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK)
+ ? EP_DIR_IN : EP_DIR_OUT;
+ status = ep_is_stall(udc, ep_num, direction)
+ << USB_ENDPOINT_HALT;
+@@ -1569,7 +1585,7 @@ static void ch9clearfeature(struct mv_ud
+
+ if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK))
+ == ((USB_TYPE_STANDARD | USB_RECIP_DEVICE))) {
+- switch (setup->wValue) {
++ switch (le16_to_cpu(setup->wValue)) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ udc->remote_wakeup = 0;
+ break;
+@@ -1578,12 +1594,12 @@ static void ch9clearfeature(struct mv_ud
+ }
+ } else if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK))
+ == ((USB_TYPE_STANDARD | USB_RECIP_ENDPOINT))) {
+- switch (setup->wValue) {
++ switch (le16_to_cpu(setup->wValue)) {
+ case USB_ENDPOINT_HALT:
+- ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK;
+- direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK)
++ ep_num = le16_to_cpu(setup->wIndex) & USB_ENDPOINT_NUMBER_MASK;
++ direction = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK)
+ ? EP_DIR_IN : EP_DIR_OUT;
+- if (setup->wValue != 0 || setup->wLength != 0
++ if (le16_to_cpu(setup->wValue) != 0 || le16_to_cpu(setup->wLength) != 0
+ || ep_num > udc->max_eps)
+ goto out;
+ ep = &udc->eps[ep_num * 2 + direction];
+@@ -1612,12 +1628,12 @@ static void ch9setfeature(struct mv_udc
+
+ if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK))
+ == ((USB_TYPE_STANDARD | USB_RECIP_DEVICE))) {
+- switch (setup->wValue) {
++ switch (le16_to_cpu(setup->wValue)) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ udc->remote_wakeup = 1;
+ break;
+ case USB_DEVICE_TEST_MODE:
+- if (setup->wIndex & 0xFF
++ if (le16_to_cpu(setup->wIndex) & 0xFF
+ || udc->gadget.speed != USB_SPEED_HIGH)
+ ep0_stall(udc);
+
+@@ -1626,19 +1642,19 @@ static void ch9setfeature(struct mv_udc
+ && udc->usb_state != USB_STATE_DEFAULT)
+ ep0_stall(udc);
+
+- mv_udc_testmode(udc, (setup->wIndex >> 8));
++ mv_udc_testmode(udc, (le16_to_cpu(setup->wIndex) >> 8));
+ goto out;
+ default:
+ goto out;
+ }
+ } else if ((setup->bRequestType & (USB_TYPE_MASK | USB_RECIP_MASK))
+ == ((USB_TYPE_STANDARD | USB_RECIP_ENDPOINT))) {
+- switch (setup->wValue) {
++ switch (le16_to_cpu(setup->wValue)) {
+ case USB_ENDPOINT_HALT:
+- ep_num = setup->wIndex & USB_ENDPOINT_NUMBER_MASK;
+- direction = (setup->wIndex & USB_ENDPOINT_DIR_MASK)
++ ep_num = le16_to_cpu(setup->wIndex) & USB_ENDPOINT_NUMBER_MASK;
++ direction = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK)
+ ? EP_DIR_IN : EP_DIR_OUT;
+- if (setup->wValue != 0 || setup->wLength != 0
++ if (le16_to_cpu(setup->wValue) != 0 || le16_to_cpu(setup->wLength) != 0
+ || ep_num > udc->max_eps)
+ goto out;
+ spin_unlock(&udc->lock);
+@@ -1697,7 +1713,7 @@ static void handle_setup_packet(struct m
+ /* delegate USB standard requests to the gadget driver */
+ if (delegate == true) {
+ /* USB requests handled by gadget */
+- if (setup->wLength) {
++ if (le16_to_cpu(setup->wLength)) {
+ /* DATA phase from gadget, STATUS phase from udc */
+ udc->ep0_dir = (setup->bRequestType & USB_DIR_IN)
+ ? EP_DIR_IN : EP_DIR_OUT;
+@@ -1999,6 +2015,32 @@ static void irq_process_error(struct mv_
+ udc->errors++;
+ }
+
++static ATOMIC_NOTIFIER_HEAD(mv_udc_status_list);
++
++int mv_udc_register_status_notify(struct notifier_block *nb)
++{
++ int ret = 0;
++
++ ret = atomic_notifier_chain_register(&mv_udc_status_list, nb);
++ if (ret)
++ return ret;
++
++ return 0;
++
++}
++EXPORT_SYMBOL(mv_udc_register_status_notify);
++
++int mv_udc_unregister_status_notify(struct notifier_block *nb)
++{
++ return atomic_notifier_chain_unregister(&mv_udc_status_list, nb);
++}
++EXPORT_SYMBOL(mv_udc_unregister_status_notify);
++
++static void status_change(struct mv_udc *udc, int event)
++{
++ atomic_notifier_call_chain(&mv_udc_status_list, event, NULL);
++}
++
+ static irqreturn_t mv_udc_irq(int irq, void *dev)
+ {
+ struct mv_udc *udc = (struct mv_udc *)dev;
+@@ -2012,6 +2054,7 @@ static irqreturn_t mv_udc_irq(int irq, v
+
+ status = readl(&udc->op_regs->usbsts);
+ intr = readl(&udc->op_regs->usbintr);
++
+ status &= intr;
+
+ if (status == 0) {
+@@ -2025,8 +2068,10 @@ static irqreturn_t mv_udc_irq(int irq, v
+ if (status & USBSTS_ERR)
+ irq_process_error(udc);
+
+- if (status & USBSTS_RESET)
++ if (status & USBSTS_RESET) {
+ irq_process_reset(udc);
++ status_change(udc, 1);
++ }
+
+ if (status & USBSTS_PORT_CHANGE)
+ irq_process_port_change(udc);
+@@ -2034,8 +2079,10 @@ static irqreturn_t mv_udc_irq(int irq, v
+ if (status & USBSTS_INT)
+ irq_process_tr_complete(udc);
+
+- if (status & USBSTS_SUSPEND)
++ if (status & USBSTS_SUSPEND) {
+ irq_process_suspend(udc);
++ status_change(udc, 1);
++ }
+
+ spin_unlock(&udc->lock);
+
+@@ -2059,10 +2106,13 @@ static void mv_udc_vbus_work(struct work
+ unsigned int vbus;
+
+ udc = container_of(work, struct mv_udc, vbus_work);
+- if (!udc->pdata->vbus)
++ if (udc->pdata && udc->pdata->vbus)
++ vbus = udc->pdata->vbus->poll();
++ else if (gpio_is_valid(udc->vbus_pin))
++ vbus = gpio_get_value(udc->vbus_pin);
++ else
+ return;
+
+- vbus = udc->pdata->vbus->poll();
+ dev_info(&udc->dev->dev, "vbus is %d\n", vbus);
+
+ if (vbus == VBUS_HIGH)
+@@ -2106,6 +2156,12 @@ static int mv_udc_remove(struct platform
+ /* free dev, wait for the release() finished */
+ wait_for_completion(udc->done);
+
++ /* Power off PHY and exit */
++ if (udc->utmi_phy) {
++ phy_power_off(udc->utmi_phy);
++ phy_exit(udc->utmi_phy);
++ }
++
+ return 0;
+ }
+
+@@ -2114,69 +2170,103 @@ static int mv_udc_probe(struct platform_
+ struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct mv_udc *udc;
+ int retval = 0;
+- struct resource *r;
++ struct resource *capregs, *phyregs, *irq;
+ size_t size;
+-
+- if (pdata == NULL) {
+- dev_err(&pdev->dev, "missing platform_data\n");
+- return -ENODEV;
+- }
++ struct clk *clk;
++ int err;
+
+ udc = devm_kzalloc(&pdev->dev, sizeof(*udc), GFP_KERNEL);
+- if (udc == NULL)
++ if (!udc)
+ return -ENOMEM;
+
+- udc->done = &release_done;
+- udc->pdata = dev_get_platdata(&pdev->dev);
+- spin_lock_init(&udc->lock);
+-
+- udc->dev = pdev;
++ /* udc only have one sysclk. */
++ clk = devm_clk_get(&pdev->dev, NULL);
++ if (IS_ERR(clk))
++ return PTR_ERR(clk);
++
++ if (pdev->dev.of_node) {
++ udc->pdata = NULL;
++
++ capregs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ /* no phyregs for mvebu platform */
++ phyregs = NULL;
++
++ /* VBUS pin via GPIO */
++ udc->vbus_pin = of_get_named_gpio(pdev->dev.of_node, "vbus-gpio", 0);
++ if (udc->vbus_pin < 0)
++ udc->vbus_pin = -ENODEV;
++
++ /* Get comphy and init if there is */
++ udc->utmi_phy = devm_of_phy_get(&pdev->dev, pdev->dev.of_node, "usb");
++ if (!IS_ERR(udc->utmi_phy)) {
++ err = phy_init(udc->utmi_phy);
++ if (err)
++ goto disable_phys;
++
++ err = phy_power_on(udc->utmi_phy);
++ if (err) {
++ phy_exit(udc->utmi_phy);
++ goto disable_phys;
++ }
++ }
+
+- if (pdata->mode == MV_USB_MODE_OTG) {
+- udc->transceiver = devm_usb_get_phy(&pdev->dev,
+- USB_PHY_TYPE_USB2);
+- if (IS_ERR(udc->transceiver)) {
+- retval = PTR_ERR(udc->transceiver);
++ } else if (pdata) {
++ udc->pdata = pdev->dev.platform_data;
++ if (pdata->mode == MV_USB_MODE_OTG) {
++ udc->transceiver = devm_usb_get_phy(&pdev->dev,
++ USB_PHY_TYPE_USB2);
++ if (IS_ERR(udc->transceiver)) {
++ retval = PTR_ERR(udc->transceiver);
++ if (retval == -ENXIO)
++ return retval;
+
+- if (retval == -ENXIO)
+- return retval;
++ udc->transceiver = NULL;
++ return -EPROBE_DEFER;
++ }
++ }
+
+- udc->transceiver = NULL;
+- return -EPROBE_DEFER;
++ capregs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs");
++ phyregs = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs");
++ if (!phyregs) {
++ dev_err(&pdev->dev, "no phy I/O memory resource defined\n");
++ return -ENODEV;
+ }
+- }
+
+- /* udc only have one sysclk. */
+- udc->clk = devm_clk_get(&pdev->dev, NULL);
+- if (IS_ERR(udc->clk))
+- return PTR_ERR(udc->clk);
+-
+- r = platform_get_resource_byname(udc->dev, IORESOURCE_MEM, "capregs");
+- if (r == NULL) {
+- dev_err(&pdev->dev, "no I/O memory resource defined\n");
++ /* platform data registration doesn't use the VBUS GPIO subsystem */
++ udc->vbus_pin = -ENODEV;
++
++ } else {
++ dev_err(&pdev->dev, "missing platform_data or of_node\n");
+ return -ENODEV;
+ }
+
++ /* set udc struct*/
++ udc->done = &release_done;
++ udc->clk = clk;
++ udc->dev = pdev;
++ spin_lock_init(&udc->lock);
++
++ if (!capregs) {
++ dev_err(&pdev->dev, "no capregs I/O memory resource defined\n");
++ return -ENXIO;
++ }
++
+ udc->cap_regs = (struct mv_cap_regs __iomem *)
+- devm_ioremap(&pdev->dev, r->start, resource_size(r));
+- if (udc->cap_regs == NULL) {
++ devm_ioremap(&pdev->dev, capregs->start, resource_size(capregs));
++ if (!udc->cap_regs) {
+ dev_err(&pdev->dev, "failed to map I/O memory\n");
+ return -EBUSY;
+ }
+
+- r = platform_get_resource_byname(udc->dev, IORESOURCE_MEM, "phyregs");
+- if (r == NULL) {
+- dev_err(&pdev->dev, "no phy I/O memory resource defined\n");
+- return -ENODEV;
+- }
+-
+- udc->phy_regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+- if (udc->phy_regs == NULL) {
+- dev_err(&pdev->dev, "failed to map phy I/O memory\n");
+- return -EBUSY;
++ if (phyregs) {
++ udc->phy_regs = ioremap(phyregs->start, resource_size(capregs));
++ if (!udc->phy_regs) {
++ dev_err(&pdev->dev, "failed to map phy I/O memory\n");
++ return -EBUSY;
++ }
+ }
+
+- /* we will acces controller register, so enable the clk */
++ /* we will access controller register, so enable the clk */
+ retval = mv_udc_enable_internal(udc);
+ if (retval)
+ return retval;
+@@ -2243,13 +2333,14 @@ static int mv_udc_probe(struct platform_
+ udc->ep0_dir = EP_DIR_OUT;
+ udc->remote_wakeup = 0;
+
+- r = platform_get_resource(udc->dev, IORESOURCE_IRQ, 0);
+- if (r == NULL) {
++ /* request irq */
++ irq = platform_get_resource(udc->dev, IORESOURCE_IRQ, 0);
++ if (irq == NULL) {
+ dev_err(&pdev->dev, "no IRQ resource defined\n");
+ retval = -ENODEV;
+ goto err_destroy_dma;
+ }
+- udc->irq = r->start;
++ udc->irq = irq->start;
+ if (devm_request_irq(&pdev->dev, udc->irq, mv_udc_irq,
+ IRQF_SHARED, driver_name, udc)) {
+ dev_err(&pdev->dev, "Request irq %d for UDC failed\n",
+@@ -2273,7 +2364,7 @@ static int mv_udc_probe(struct platform_
+ /* VBUS detect: we can disable/enable clock on demand.*/
+ if (udc->transceiver)
+ udc->clock_gating = 1;
+- else if (pdata->vbus) {
++ else if (pdata && pdata->vbus) {
+ udc->clock_gating = 1;
+ retval = devm_request_threaded_irq(&pdev->dev,
+ pdata->vbus->irq, NULL,
+@@ -2285,6 +2376,28 @@ static int mv_udc_probe(struct platform_
+ udc->clock_gating = 0;
+ }
+
++ } else if (gpio_is_valid(udc->vbus_pin)) {
++ udc->clock_gating = 1;
++ if (!devm_gpio_request(&pdev->dev, udc->vbus_pin, "mv-udc")) {
++ retval = devm_request_irq(&pdev->dev,
++ gpio_to_irq(udc->vbus_pin),
++ mv_udc_vbus_irq, IRQ_TYPE_EDGE_BOTH,
++ "mv-udc", udc);
++ if (retval) {
++ udc->vbus_pin = -ENODEV;
++ dev_warn(&pdev->dev,
++ "failed to request vbus irq; "
++ "assuming always on\n");
++ } else
++ disable_irq(gpio_to_irq(udc->vbus_pin));
++ } else {
++ /* gpio_request fail so use -EINVAL for gpio_is_valid */
++ udc->vbus_pin = -EINVAL;
++ }
++ }
++
++ /* if using VBUS interrupt, initialize work queue */
++ if ((pdata && pdata->vbus) || gpio_is_valid(udc->vbus_pin)) {
+ udc->qwork = create_singlethread_workqueue("mv_udc_queue");
+ if (!udc->qwork) {
+ dev_err(&pdev->dev, "cannot create workqueue\n");
+@@ -2325,6 +2438,11 @@ err_free_dma:
+ udc->ep_dqh, udc->ep_dqh_dma);
+ err_disable_clock:
+ mv_udc_disable_internal(udc);
++disable_phys:
++ if (udc->utmi_phy) {
++ phy_power_off(udc->utmi_phy);
++ phy_exit(udc->utmi_phy);
++ }
+
+ return retval;
+ }
+@@ -2340,7 +2458,7 @@ static int mv_udc_suspend(struct device
+ if (udc->transceiver)
+ return 0;
+
+- if (udc->pdata->vbus && udc->pdata->vbus->poll)
++ if (udc->pdata && udc->pdata->vbus && udc->pdata->vbus->poll)
+ if (udc->pdata->vbus->poll() == VBUS_HIGH) {
+ dev_info(&udc->dev->dev, "USB cable is connected!\n");
+ return -EAGAIN;
+@@ -2361,6 +2479,12 @@ static int mv_udc_suspend(struct device
+ mv_udc_disable_internal(udc);
+ }
+
++ /* PHY exit if there is */
++ if (udc->utmi_phy) {
++ phy_power_off(udc->utmi_phy);
++ phy_exit(udc->utmi_phy);
++ }
++
+ return 0;
+ }
+
+@@ -2375,6 +2499,20 @@ static int mv_udc_resume(struct device *
+ if (udc->transceiver)
+ return 0;
+
++ /* PHY init if there is */
++ if (udc->utmi_phy) {
++ retval = phy_init(udc->utmi_phy);
++ if (retval)
++ return retval;
++
++ retval = phy_power_on(udc->utmi_phy);
++ if (retval) {
++ phy_power_off(udc->utmi_phy);
++ phy_exit(udc->utmi_phy);
++ return retval;
++ }
++ }
++
+ if (!udc->clock_gating) {
+ retval = mv_udc_enable_internal(udc);
+ if (retval)
+@@ -2410,12 +2548,19 @@ static void mv_udc_shutdown(struct platf
+ mv_udc_disable(udc);
+ }
+
++static const struct of_device_id mv_udc_dt_match[] = {
++ { .compatible = "marvell,mv-udc" },
++ {},
++};
++MODULE_DEVICE_TABLE(of, mv_udc_dt_match);
++
+ static struct platform_driver udc_driver = {
+ .probe = mv_udc_probe,
+ .remove = mv_udc_remove,
+ .shutdown = mv_udc_shutdown,
+ .driver = {
+ .name = "mv-udc",
++ .of_match_table = of_match_ptr(mv_udc_dt_match),
+ #ifdef CONFIG_PM
+ .pm = &mv_udc_pm_ops,
+ #endif
+@@ -2426,4 +2571,5 @@ module_platform_driver(udc_driver);
+ MODULE_ALIAS("platform:mv-udc");
+ MODULE_DESCRIPTION(DRIVER_DESC);
+ MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>");
++MODULE_VERSION(DRIVER_VERSION);
+ MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/usb/gadget/udc/mvebu_glue.c
+@@ -0,0 +1,189 @@
++/*
++ * Copyright (C) 2013 Marvell International Ltd. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ */
++
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/irq.h>
++#include <linux/err.h>
++#include <linux/io.h>
++#include <linux/interrupt.h>
++#include <linux/usb/gadget.h>
++#include <linux/pm.h>
++#include <linux/pm_qos.h>
++#include <linux/usb/composite.h>
++
++#include "mvebu_u3d.h"
++
++#define CONNECTION_MAX_NUM 3
++
++struct mvc2_glue glue;
++static struct work_struct glue_work;
++static DEFINE_MUTEX(work_lock);
++struct usb_udc *udc_detect(struct list_head *udc_list,
++ struct usb_gadget_driver *driver)
++{
++ struct usb_udc *udc20, *udc30, *udc;
++ struct mvc2 *cp;
++
++ udc20 = udc30 = NULL;
++ list_for_each_entry(udc, udc_list, list) {
++ if (strncmp(udc->gadget->name, "mv_udc", 6) == 0)
++ udc20 = udc;
++
++ if (strncmp(udc->gadget->name, "mvebu-u3d", 9) == 0)
++ udc30 = udc;
++ }
++
++ /* We need at least 3.0 controller driver being installed! */
++ if (!udc30) {
++ pr_err("Failed to detect usb3 device!\n");
++ return NULL;
++ }
++
++ cp = container_of(udc30->gadget, struct mvc2, gadget);
++ cp->work = &glue_work;
++ glue.u20 = udc20;
++ glue.u30 = udc30;
++
++ if (glue.usb2_connect)
++ return udc20;
++ else
++ return udc30;
++}
++
++void mvc2_usb2_connect(void)
++{
++ struct mvc2 *cp;
++ struct usb_udc *u30 = glue.u30;
++ struct usb_gadget_driver *driver = u30->driver;
++ struct usb_gadget *u3d = u30->gadget;
++
++ cp = container_of(u3d, struct mvc2, gadget);
++ pr_info("USB device: USB2.0 connected\n");
++ /*
++ * add de-bounce for usb cable plug
++ */
++ msleep(200);
++ if (mvc2_checkvbus(cp) == 0) {
++ pr_info("USB device: power off\n");
++ return;
++ }
++
++ /*
++ * The de-bounce time added before just can filter
++ * most cases but not all.
++ * The power off interrupt still has chance to break
++ * this workqueue.
++ * So we disable the USB3 irq here to guarantee this
++ * workqueue will not be interrupted by USB3 interrupt anymore,
++ * such as the power off interrupt, until all of the works have
++ * been done.
++ * The power off interrupt may happen when
++ * the USB3 irq was disabled.
++ * We hope this interrupt still there once
++ * we enabled USB3 irq again.
++ * To achieve this, need to keep the corresponding
++ * interrupt status(refer to mvc2_pullup,
++ * don't clear the ref int status register).
++ * Note, during the USB3 irq disabled, there may be
++ * may times plug/unplug, thus, the power on/off interrupt
++ * may co-exisit once enable the irq again.
++ * To avoid this, we need to check the VBUS of the final state,
++ * please refer to mvc2_irq.
++ */
++
++ disable_irq(cp->irq);
++
++ glue.usb2_connect = 1;
++ usb_gadget_unregister_driver(driver);
++ usb_gadget_probe_driver(driver);
++
++ enable_irq(cp->irq);
++
++}
++
++void mvc2_usb2_disconnect(void)
++{
++ struct mvc2 *cp;
++ struct usb_udc *u30 = glue.u30;
++ struct usb_gadget *u3d = u30->gadget;
++ struct usb_udc *u20 = glue.u20;
++ struct usb_gadget_driver *driver = u20->driver;
++ int has_setup = 0;
++
++ cp = container_of(u3d, struct mvc2, gadget);
++
++ if (u20->driver)
++ driver = u20->driver;
++ else if (u30->driver) {
++ driver = u30->driver;
++ return;
++ }
++
++ pr_info("USB device: USB2.0 disconnected\n");
++ glue.usb2_connect = 0;
++ usb_gadget_unregister_driver(driver);
++ disable_irq(cp->irq);
++ usb3_disconnect = false;
++
++ if (ioread32(cp->base + MVCP_SS_CORE_INT) & MVCP_SS_CORE_INT_SETUP)
++ has_setup = 1;
++ usb_gadget_probe_driver(driver);
++ usb3_disconnect = true;
++ enable_irq(cp->irq);
++ if (has_setup)
++ mvc2_handle_setup(cp);
++
++}
++
++static int
++u20_status_change(struct notifier_block *this, unsigned long event, void *ptr)
++{
++ struct mvc2 *cp;
++ struct usb_gadget *u30 = glue.u30->gadget;
++
++ cp = container_of(u30, struct mvc2, gadget);
++
++ mvc2_usb2_operation(cp, event);
++
++ return NOTIFY_DONE;
++}
++
++static struct notifier_block u20_status = {
++ .notifier_call = u20_status_change,
++};
++
++void mv_connect_work(struct work_struct *work)
++{
++ struct mvc2 *cp;
++ struct usb_gadget *u30 = glue.u30->gadget;
++
++ cp = container_of(u30, struct mvc2, gadget);
++
++ mutex_lock(&work_lock);
++
++ if (glue.status & MVCP_STATUS_USB2)
++ mvc2_usb2_connect();
++ else
++ mvc2_usb2_disconnect();
++
++ mutex_unlock(&work_lock);
++}
++
++static int __init mvc2_glue_init(void)
++{
++ glue.u20 = glue.u30 = NULL;
++ glue.usb2_connect = 0;
++ mv_udc_register_status_notify(&u20_status);
++ INIT_WORK(&glue_work, mv_connect_work);
++ return 0;
++}
++
++device_initcall(mvc2_glue_init);
+--- /dev/null
++++ b/drivers/usb/gadget/udc/mvebu_u3d.c
+@@ -0,0 +1,2626 @@
++/*
++ * Copyright (C) 2013 Marvell International Ltd. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify it
++ * under the terms and conditions of the GNU General Public License,
++ * version 2, as published by the Free Software Foundation.
++ */
++
++#include <linux/module.h>
++#include <linux/dma-mapping.h>
++#include <linux/kernel.h>
++#include <linux/delay.h>
++#include <linux/ioport.h>
++#include <linux/sched.h>
++#include <linux/slab.h>
++#include <linux/errno.h>
++#include <linux/init.h>
++#include <linux/timer.h>
++#include <linux/list.h>
++#include <linux/notifier.h>
++#include <linux/interrupt.h>
++#include <linux/moduleparam.h>
++#include <linux/device.h>
++#include <linux/usb/ch9.h>
++#include <linux/usb/gadget.h>
++#include <linux/usb/phy.h>
++#include <linux/pm.h>
++#include <linux/io.h>
++#include <linux/irq.h>
++#include <linux/platform_device.h>
++#include <linux/platform_data/mv_usb.h>
++#include <linux/clk.h>
++#include <asm/unaligned.h>
++#include <asm/byteorder.h>
++#include <linux/proc_fs.h>
++#include <linux/seq_file.h>
++#include <linux/of.h>
++#include <linux/of_device.h>
++#include <linux/pm_qos.h>
++#include <linux/time.h>
++#include <asm/cputype.h>
++#include <linux/highmem.h>
++#include <linux/of_gpio.h>
++#include <linux/gpio.h>
++#include <linux/phy/phy.h>
++#include <linux/usb/composite.h>
++
++#include "mvebu_u3d.h"
++
++#define DRIVER_DESC "Marvell Central IP USB3.0 Device Controller driver"
++
++static unsigned int u1u2;
++module_param(u1u2, uint, S_IRUGO | S_IWUSR);
++MODULE_PARM_DESC(u1u2, "u1u2 enable");
++
++static const char driver_desc[] = DRIVER_DESC;
++
++unsigned int u1u2_enabled(void)
++{
++ return u1u2;
++}
++
++#define EP0_MAX_PKT_SIZE 512
++
++/* for endpoint 0 operations */
++static const struct usb_endpoint_descriptor mvc2_ep0_out_desc = {
++ .bLength = USB_DT_ENDPOINT_SIZE,
++ .bDescriptorType = USB_DT_ENDPOINT,
++ .bEndpointAddress = USB_DIR_OUT,
++ .bmAttributes = USB_ENDPOINT_XFER_CONTROL,
++ .wMaxPacketSize = EP0_MAX_PKT_SIZE,
++};
++
++static const struct usb_endpoint_descriptor mvc2_ep0_in_desc = {
++ .bLength = USB_DT_ENDPOINT_SIZE,
++ .bDescriptorType = USB_DT_ENDPOINT,
++ .bEndpointAddress = USB_DIR_IN,
++ .bmAttributes = USB_ENDPOINT_XFER_CONTROL,
++ .wMaxPacketSize = EP0_MAX_PKT_SIZE,
++};
++
++static struct usb_ss_ep_comp_descriptor ep0_comp = {
++ .bLength = sizeof(ep0_comp),
++ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
++};
++
++static int mvc2_ep0_handle_status(struct mvc2 *cp, struct usb_ctrlrequest *ctrl)
++{
++ unsigned int recip;
++ u16 usb_status = 0, lowpower;
++ __le16 *response_pkt;
++ int num, dir;
++ struct mvc2_ep *ep;
++
++ recip = ctrl->bRequestType & USB_RECIP_MASK;
++ switch (recip) {
++ case USB_RECIP_DEVICE:
++ /*
++ * LTM will be set once we know how to set this in HW.
++ */
++ if (cp->status & MVCP_STATUS_SELF_POWERED)
++ usb_status |= USB_DEVICE_SELF_POWERED;
++
++ lowpower = MV_CP_READ(MVCP_LOWPOWER);
++ if (lowpower & MVCP_LOWPOWER_U1_EN)
++ usb_status |= 1 << USB_DEV_STAT_U1_ENABLED;
++
++ if (lowpower & MVCP_LOWPOWER_U2_EN)
++ usb_status |= 1 << USB_DEV_STAT_U2_ENABLED;
++
++ break;
++
++ case USB_RECIP_INTERFACE:
++ /*
++ * Function Remote Wake Capable D0
++ * Function Remote Wakeup D1
++ */
++ break;
++
++ case USB_RECIP_ENDPOINT:
++
++ num = ctrl->wIndex & USB_ENDPOINT_NUMBER_MASK;
++ dir = ctrl->wIndex & USB_DIR_IN;
++ ep = &cp->eps[2 * num + !!dir];
++
++ if (ep->state & MV_CP_EP_STALL)
++ usb_status = 1 << USB_ENDPOINT_HALT;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ response_pkt = (__le16 *) cp->setup_buf;
++ *response_pkt = cpu_to_le16(usb_status);
++
++ return sizeof(*response_pkt);
++}
++
++static void enable_lowpower(struct mvc2 *cp, unsigned int lowpower, int on)
++{
++ unsigned int val, state;
++
++ val = MV_CP_READ(MVCP_LOWPOWER);
++ if (lowpower == USB_DEVICE_U1_ENABLE)
++ state = MVCP_LOWPOWER_U1_EN;
++ else
++ state = MVCP_LOWPOWER_U2_EN;
++
++ if (on)
++ val |= state;
++ else
++ val &= ~state;
++
++ if (u1u2_enabled())
++ MV_CP_WRITE(val, MVCP_LOWPOWER);
++}
++
++static int mvc2_ep0_handle_feature(struct mvc2 *cp,
++ struct usb_ctrlrequest *ctrl, int set)
++{
++ u32 wValue, wIndex, recip;
++ int ret = -EINVAL;
++ int num, dir;
++ struct mvc2_ep *ep;
++ unsigned long flags;
++
++ wValue = le16_to_cpu(ctrl->wValue);
++ wIndex = le16_to_cpu(ctrl->wIndex);
++ recip = ctrl->bRequestType & USB_RECIP_MASK;
++
++ switch (recip) {
++ case USB_RECIP_DEVICE:
++ switch (wValue) {
++ case USB_DEVICE_REMOTE_WAKEUP:
++ ret = 0;
++ break;
++
++ case USB_DEVICE_U1_ENABLE:
++ case USB_DEVICE_U2_ENABLE:
++ if (cp->dev_state != MVCP_CONFIGURED_STATE) {
++ ret = -EINVAL;
++ break;
++ }
++
++ ret = 0;
++
++ enable_lowpower(cp, wValue, set);
++ break;
++ case USB_DEVICE_TEST_MODE:
++ if (set && (wIndex & 0xff))
++ cp->status |= MVCP_STATUS_TEST(wIndex >> 8);
++ break;
++ }
++ break;
++ case USB_RECIP_INTERFACE:
++ switch (wValue) {
++ case USB_INTRF_FUNC_SUSPEND:
++ ret = 0;
++ }
++ break;
++ case USB_RECIP_ENDPOINT:
++ switch (wValue) {
++ case USB_ENDPOINT_HALT:
++ num = wIndex & USB_ENDPOINT_NUMBER_MASK;
++ dir = wIndex & USB_DIR_IN;
++ ep = &cp->eps[2 * num + !!dir];
++ if (!set) {
++ spin_lock_irqsave(&ep->lock, flags);
++ reset_seqencenum(ep, num, dir);
++ spin_unlock_irqrestore(&ep->lock, flags);
++ if (!(ep->state & MV_CP_EP_WEDGE))
++ usb_ep_clear_halt(&ep->ep);
++ } else
++ usb_ep_set_halt(&ep->ep);
++ ret = 0;
++ }
++ }
++
++ return ret;
++}
++
++static void mvcp_ep0_set_sel_cmpl(struct usb_ep *ep, struct usb_request *req)
++{
++ struct mvc2_ep *_ep = container_of(ep, struct mvc2_ep, ep);
++ struct mvc2 *cp = _ep->cp;
++ struct timing {
++ u8 u1sel;
++ u8 u1pel;
++ u16 u2sel;
++ u16 u2pel;
++ } __packed timing;
++
++ memcpy(&timing, req->buf, sizeof(timing));
++ cp->u1sel = timing.u1sel;
++ cp->u1pel = timing.u1pel;
++ cp->u2sel = le16_to_cpu(timing.u2sel);
++ cp->u2pel = le16_to_cpu(timing.u2pel);
++}
++
++int mvc2_std_request(struct mvc2 *cp, struct usb_ctrlrequest *r,
++ bool *delegate)
++{
++ int ret = 0;
++ struct usb_request *req;
++ u16 wLength = le16_to_cpu(r->wLength);
++ u16 wValue = le16_to_cpu(r->wValue);
++ u16 wIndex = le16_to_cpu(r->wIndex);
++
++ *delegate = true;
++ req = &cp->ep0_req.req;
++ switch (r->bRequest) {
++ case USB_REQ_SET_ADDRESS:
++ if (r->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE))
++ break;
++
++ *delegate = false;
++ if (wValue > 127) {
++ dev_dbg(cp->dev, "invalid device address %d\n", wValue);
++ break;
++ }
++
++ if (cp->dev_state == MVCP_CONFIGURED_STATE) {
++ dev_dbg(cp->dev,
++ "trying to set address when configured\n");
++ break;
++ }
++
++ if (wValue)
++ cp->dev_state = MVCP_ADDRESS_STATE;
++ else
++ cp->dev_state = MVCP_DEFAULT_STATE;
++ break;
++ case USB_REQ_GET_STATUS:
++ if (r->bRequestType != (USB_DIR_IN | USB_RECIP_DEVICE) &&
++ r->bRequestType != (USB_DIR_IN | USB_RECIP_ENDPOINT) &&
++ r->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE))
++ break;
++
++ ret = mvc2_ep0_handle_status(cp, r);
++ *delegate = false;
++
++ break;
++ case USB_REQ_CLEAR_FEATURE:
++ case USB_REQ_SET_FEATURE:
++ if (r->bRequestType != (USB_DIR_OUT | USB_RECIP_DEVICE) &&
++ r->bRequestType != (USB_DIR_OUT | USB_RECIP_ENDPOINT) &&
++ r->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE))
++ break;
++
++ ret = mvc2_ep0_handle_feature(cp, r,
++ r->bRequest ==
++ USB_REQ_SET_FEATURE);
++ *delegate = false;
++ break;
++ case USB_REQ_SET_CONFIGURATION:
++ switch (cp->dev_state) {
++ case MVCP_DEFAULT_STATE:
++ break;
++ case MVCP_ADDRESS_STATE:
++ if (wValue) {
++ enable_lowpower(cp, USB_DEVICE_U1_ENABLE, 0);
++ enable_lowpower(cp, USB_DEVICE_U2_ENABLE, 0);
++ cp->dev_state = MVCP_CONFIGURED_STATE;
++ }
++ break;
++ case MVCP_CONFIGURED_STATE:
++ if (!wValue)
++ cp->dev_state = MVCP_ADDRESS_STATE;
++ break;
++ }
++ break;
++ case USB_REQ_SET_SEL:
++ *delegate = false;
++ if (cp->dev_state == MVCP_DEFAULT_STATE)
++ break;
++
++ if (wLength == 6) {
++ ret = wLength;
++ req->complete = mvcp_ep0_set_sel_cmpl;
++ }
++ break;
++ case USB_REQ_SET_ISOCH_DELAY:
++ *delegate = false;
++ if (!wIndex && !wLength) {
++ ret = 0;
++ cp->isoch_delay = wValue;
++ }
++ break;
++ }
++
++ if (ret > 0) {
++ req->length = ret;
++ req->zero = ret < wLength;
++ req->buf = cp->setup_buf;
++ ret = usb_ep_queue(cp->gadget.ep0, req, GFP_ATOMIC);
++ }
++
++ if (ret < 0)
++ *delegate = false;
++
++ return ret;
++}
++
++int eps_init(struct mvc2 *cp)
++{
++ struct mvc2_ep *ep;
++ int i, j, ret;
++ struct bd *bd;
++ unsigned int phys, bd_interval;
++
++ bd_interval = sizeof(struct bd);
++
++ /* initialize endpoints */
++ for (i = 0; i < cp->epnum * 2; i++) {
++ ep = &cp->eps[i];
++ ep->ep.name = ep->name;
++ ep->cp = cp;
++ INIT_LIST_HEAD(&ep->queue);
++ INIT_LIST_HEAD(&ep->wait);
++ INIT_LIST_HEAD(&ep->tmp);
++ spin_lock_init(&ep->lock);
++
++ if (i < 2) {
++
++ strncpy(ep->name, "ep0", MAXNAME);
++ usb_ep_set_maxpacket_limit(&ep->ep, EP0_MAX_PKT_SIZE);
++ ep->ep.desc = (i) ? &mvc2_ep0_in_desc :
++ &mvc2_ep0_out_desc;
++ ep->ep.comp_desc = &ep0_comp;
++ ep->bd_sz = MAX_QUEUE_SLOT;
++ ep->left_bds = MAX_QUEUE_SLOT;
++ ep->dir = i ? 1 : 0;
++ if (ep->dir == 1)
++ ep->ep.caps.dir_in = true;
++ else
++ ep->ep.caps.dir_out = true;
++ ep->ep.caps.type_control = true;
++
++ } else {
++ if (i & 0x1) {
++ ep->dir = 1;
++ snprintf(ep->name, MAXNAME, "ep%din", i >> 1);
++ ep->ep.caps.dir_in = true;
++ } else {
++ ep->dir = 0;
++ snprintf(ep->name, MAXNAME, "ep%dout", i >> 1);
++ ep->ep.caps.dir_out = true;
++ }
++ usb_ep_set_maxpacket_limit(&ep->ep, (unsigned short) ~0);
++ ep->bd_sz = MAX_QUEUE_SLOT;
++ ep->left_bds = MAX_QUEUE_SLOT;
++ ep->ep.caps.type_iso = true;
++ ep->ep.caps.type_bulk = true;
++ ep->ep.caps.type_int = true;
++ }
++
++ ep->ep_num = i / 2;
++
++ ep->doneq_start = dma_alloc_coherent(cp->dev,
++ sizeof(struct doneq) *
++ ep->bd_sz,
++ &ep->doneq_start_phys,
++ GFP_KERNEL);
++ if (ep->doneq_start == NULL) {
++ dev_err(cp->dev, "failed to allocate doneq buffer!\n");
++ return -ENOMEM;
++ }
++
++ ep->bd_ring = dma_alloc_coherent(cp->dev,
++ sizeof(struct bd) * ep->bd_sz,
++ &ep->bd_ring_phys, GFP_KERNEL);
++ if (ep->bd_ring == NULL) {
++ dev_err(cp->dev, "failed to allocate bd buffer!\n");
++ return -ENOMEM;
++ }
++ bd = (struct bd *)ep->bd_ring;
++ phys = ep->bd_ring_phys;
++ /* Generate the TransferQ ring */
++ for (j = 0; j < ep->bd_sz - 1; j++) {
++ phys += bd_interval;
++ bd->phys_next = phys;
++ bd->cmd = 0;
++ if (ip_ver(cp) < USB3_IP_VER_A0)
++ bd->cmd = BD_NXT_PTR_JUMP;
++ bd++;
++ }
++ bd->cmd = 0;
++ if (ip_ver(cp) < USB3_IP_VER_A0)
++ bd->cmd = BD_NXT_PTR_JUMP;
++ bd->phys_next = ep->bd_ring_phys;
++ }
++
++ cp->setup_buf = kzalloc(EP0_MAX_PKT_SIZE, GFP_KERNEL);
++ if (!cp->setup_buf)
++ ret = -ENOMEM;
++
++ return 0;
++}
++
++#define CREATE_TRACE_POINTS
++/* #define ASSEMBLE_REQ */
++
++static const char driver_name[] = "mvebu-u3d";
++
++#define ep_dir(ep) (((ep)->dir))
++
++static bool irq_enabled;
++bool usb3_disconnect = true;
++
++/* return the actual ep number */
++static int ip_ep_num(struct mvc2 *cp)
++{
++ return MVCP_EP_COUNT;
++}
++
++static void done(struct mvc2_ep *ep, struct mvc2_req *req, int status);
++static void nuke(struct mvc2_ep *ep, int status);
++static void stop_activity(struct mvc2 *udc, struct usb_gadget_driver *driver);
++
++static void set_top_int(struct mvc2 *cp, unsigned int val)
++{
++ if (ip_ver(cp) >= USB3_IP_VER_Z2)
++ MV_CP_WRITE(val, MVCP_TOP_INT_EN);
++}
++
++static void ep_dma_enable(struct mvc2 *cp, int num, int dir, int enable)
++{
++ unsigned int tmp, val, reg;
++
++ if (ip_ver(cp) <= USB3_IP_VER_Z2) {
++ tmp = (dir) ? 0x10000 : 0x1;
++ tmp = tmp << num;
++ reg = MVCP_DMA_ENABLE;
++ } else {
++ tmp = DONEQ_CONFIG;
++ if (dir)
++ reg = SS_IN_DMA_CONTROL_REG(num);
++ else
++ reg = SS_OUT_DMA_CONTROL_REG(num);
++ }
++
++ val = MV_CP_READ(reg);
++ if (enable)
++ MV_CP_WRITE(val | tmp, reg);
++ else
++ MV_CP_WRITE(val & ~tmp, reg);
++}
++
++static void ep_dma_struct_init(struct mvc2 *cp,
++ struct mvc2_ep *ep, int num, int dir)
++{
++ dma_addr_t addr;
++
++ addr = ep->doneq_start_phys + sizeof(struct doneq) * (ep->bd_sz - 1);
++ MV_CP_WRITE(ep->bd_ring_phys, ep_dma_addr(num, dir));
++
++ ep_dma_enable(cp, num, dir, 0);
++ MV_CP_WRITE(ep->doneq_start_phys, ep_doneq_start(num, dir));
++ MV_CP_WRITE(ep->doneq_start_phys, ep_doneq_read(num, dir));
++ MV_CP_WRITE(addr, ep_doneq_end(num, dir));
++ ep_dma_enable(cp, num, dir, 1);
++}
++
++/* Need to be included in ep lock protection */
++static void mvc2_dma_reset(struct mvc2 *cp,
++ struct mvc2_ep *ep, int num, int dir)
++{
++ unsigned int epbit, val, creg, sreg;
++ int timeout = 10000;
++ struct mvc2_req *req, *tmp;
++ unsigned long flags;
++
++ spin_lock_irqsave(&ep->lock, flags);
++ if (ip_ver(cp) <= USB3_IP_VER_Z2) {
++ epbit = EPBIT(num, dir);
++ MV_CP_WRITE(epbit, MVCP_DMA_HALT);
++ while ((!(MV_CP_READ(MVCP_DMA_HALT_DONE) &
++ epbit)) && timeout-- > 0)
++ cpu_relax();
++ MV_CP_WRITE(epbit, MVCP_DMA_HALT_DONE);
++ } else {
++ if (dir) {
++ creg = SS_IN_DMA_CONTROL_REG(num);
++ sreg = SS_IN_EP_INT_STATUS_REG(num);
++ } else {
++ creg = SS_OUT_DMA_CONTROL_REG(num);
++ sreg = SS_OUT_EP_INT_STATUS_REG(num);
++ }
++ val = MV_CP_READ(creg);
++ val |= DMA_HALT;
++ MV_CP_WRITE(val, creg);
++ while ((!(MV_CP_READ(sreg) & DMA_HALT_DONE)) && timeout-- > 0)
++ cpu_relax();
++ MV_CP_WRITE(DMA_HALT_DONE, sreg);
++ }
++
++ if (timeout <= 0) {
++ pr_info("### dma reset timeout, num = %d, dir = %d\n", num,
++ dir);
++ WARN_ON(1);
++ }
++
++ list_for_each_entry_safe(req, tmp, &ep->queue, queue)
++ done(ep, req, -ESHUTDOWN);
++
++ ep->bd_cur = ep->doneq_cur = 0;
++ ep_dma_struct_init(cp, &cp->eps[2 * num + !!dir], num, dir);
++ spin_unlock_irqrestore(&ep->lock, flags);
++}
++
++static struct usb_request *mvc2_alloc_request(struct usb_ep *_ep,
++ gfp_t gfp_flags)
++{
++ struct mvc2_req *req = NULL;
++
++ req = kzalloc(sizeof(*req), gfp_flags);
++ if (!req)
++ return NULL;
++
++ memset(req, 0, sizeof(*req));
++ INIT_LIST_HEAD(&req->queue);
++ return &req->req;
++}
++
++static void mvc2_free_request(struct usb_ep *_ep, struct usb_request *_req)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++ struct mvc2_req *req = container_of(_req, struct mvc2_req, req);
++ unsigned long flags;
++
++ spin_lock_irqsave(&ep->lock, flags);
++ list_del_init(&req->queue);
++ spin_unlock_irqrestore(&ep->lock, flags);
++ kfree(req);
++}
++
++static int
++alloc_one_bd_chain(struct mvc2 *cp, struct mvc2_ep *ep, struct mvc2_req *req,
++ int num, int dir, dma_addr_t dma, unsigned length,
++ unsigned offset, unsigned *last)
++{
++ unsigned int bd_num, remain, bd_cur, len, buf;
++ struct bd *bd;
++ int left_bds, cur_bd;
++
++ remain = length - offset;
++
++ /* In the zero length packet case, we still need one BD to make it happen */
++ if (remain)
++ bd_num = (remain + BD_MAX_SIZE - 1) >> BD_SEGMENT_SHIFT;
++ else
++ bd_num = 1;
++
++ bd_cur = ep->bd_cur;
++ left_bds = ep->left_bds;
++ if (left_bds == 0)
++ goto no_bds;
++ if (bd_num > left_bds)
++ goto no_bds;
++ ep->left_bds -= bd_num;
++ WARN_ON(ep->left_bds > ep->bd_sz);
++
++ ep->bd_cur += bd_num;
++ if (ep->bd_cur >= ep->bd_sz)
++ ep->bd_cur -= ep->bd_sz;
++
++ ep->state |= MV_CP_EP_TRANSERING;
++ req->bd_total += bd_num;
++ buf = (unsigned int)dma;
++ /*
++ * format BD chains:
++ * BD_NXT_RDY make a BD chain segment, and
++ * one BD chain segment is natually one usb_request.
++ * But with exception that if current number of BD
++ * cannot fulfill usb_request, so that we may divide
++ * one request into several segments, so that it could
++ * complete gradually.
++ * DMA engine would never cache across two segments
++ * without MVCP_EPDMA_START being set, which indicate
++ * new BD segment is coming.
++ */
++ cur_bd = bd_num;
++ do {
++ if (remain > BD_MAX_SIZE)
++ len = BD_MAX_SIZE;
++ else {
++ /*
++ * HW require out ep's BD length is 1024 aligned,
++ * or there is problem in receiving the compelte interrupt
++ */
++ len = remain;
++ if (!dir && (len & 0x3ff))
++ len = ((len + 0x3ff) >> 10) << 10;
++ }
++ remain -= len;
++
++ bd = ep->bd_ring + bd_cur;
++
++ bd_cur++;
++ if (bd_cur == ep->bd_sz)
++ bd_cur = 0;
++
++ if (!offset)
++ req->bd = bd;
++
++ /*
++ * There are three method to indicate one bd is finished
++ * 1. Receive the short packet which is less than 1024
++ * 2. Receive the zero length packet
++ * 3. Receive the data length equal to size set by BD
++ */
++ bd->cmd = BD_NXT_RDY | BD_BUF_RDY | BD_BUF_SZ(len);
++ if (ip_ver(cp) < USB3_IP_VER_A0)
++ bd->cmd |= BD_NXT_PTR_JUMP;
++ bd->buf = (unsigned int)dma + offset;
++
++ offset += len;
++ } while (--cur_bd > 0);
++
++ if (*last) {
++ /* Only raise the interrupt at the last bd */
++#ifndef ASSEMBLE_REQ
++#if 0
++ /* due to usb2 rx interrupt optimization, no_interrupt is
++ * is always 1. Due to HW bug, this is currently irrelevant for
++ * our case since an interrupt will be returned regardless of
++ * BD_INT_EN.
++ */
++ if (!req->req.no_interrupt)
++#endif
++#endif
++ bd->cmd |= BD_INT_EN;
++ /* At the end of one segment, clear the BD_NXT_RDY */
++ bd->cmd &= ~BD_NXT_RDY;
++ }
++ *last = left_bds;
++
++ return bd_num;
++no_bds:
++ WARN_ON(ep->ep_num == 0);
++ return 0;
++}
++
++static int alloc_bds(struct mvc2 *cp, struct mvc2_ep *ep, struct mvc2_req *req)
++{
++ dma_addr_t dma;
++ unsigned length, bd_num, actual;
++ struct usb_request *request = &req->req;
++ int num, dir, last;
++
++ bd_num = 0;
++ actual = req->req.actual;
++ num = ep->ep.desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
++ dir = ep->ep.desc->bEndpointAddress & USB_DIR_IN;
++
++ req->bd_total = 0;
++ last = 1;
++ if (req->req.num_mapped_sgs > 0) {
++ struct scatterlist *sg = request->sg;
++ struct scatterlist *s;
++ int i;
++
++ last = 0;
++ for_each_sg(sg, s, request->num_mapped_sgs, i) {
++ length = sg_dma_len(s);
++ if (actual >= length) {
++ actual -= length;
++ continue;
++ }
++
++ actual += sg->offset;
++ dma = sg_dma_address(s);
++ if (sg_is_last(s))
++ last = 1;
++
++ bd_num = alloc_one_bd_chain(cp, ep, req, num, dir,
++ dma, length, actual, &last);
++ if (last > 1)
++ last = 0;
++ if (!bd_num)
++ break;
++ }
++ } else {
++ dma = req->req.dma;
++ length = req->req.length;
++
++ bd_num = alloc_one_bd_chain(cp, ep, req, num, dir,
++ dma, length, actual, &last);
++ }
++
++ if (bd_num)
++ list_add_tail(&req->queue, &ep->queue);
++
++ return bd_num;
++}
++
++#ifdef ASSEMBLE_REQ
++static int
++alloc_in_bds(struct mvc2 *cp, struct mvc2_ep *ep, struct mvc2_req *req,
++ int *last)
++{
++ dma_addr_t dma;
++ unsigned length, bd_num, actual;
++ struct usb_request *request = &req->req;
++ int num, dir;
++
++ bd_num = 0;
++ actual = req->req.actual;
++ num = ep->ep.desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
++ dir = ep->ep.desc->bEndpointAddress & USB_DIR_IN;
++
++ req->bd_total = 0;
++
++ dma = req->req.dma;
++ length = req->req.length;
++
++ bd_num = alloc_one_bd_chain(cp, ep, req, num, dir,
++ dma, length, actual, last);
++
++ list_add_tail(&req->queue, &ep->queue);
++
++ return bd_num;
++}
++#endif
++
++static inline void
++mvc2_ring_incoming(struct mvc2 *cp, unsigned int num, unsigned dir)
++{
++ unsigned int reg;
++
++ /* Ensure that updates to the EP Context will occur before Ring Bell */
++ wmb();
++
++ /* Ring the data incoming bell to ask hw to reload the bd chain */
++ if (ip_ver(cp) <= USB3_IP_VER_Z2) {
++ MV_CP_WRITE(MVCP_EPDMA_START, ep_dma_config(num, dir));
++ } else {
++ if (dir)
++ reg = SS_IN_DMA_CONTROL_REG(num);
++ else
++ reg = SS_OUT_DMA_CONTROL_REG(num);
++ MV_CP_WRITE(MV_CP_READ(reg) | DMA_START, reg);
++ }
++}
++
++static void ep_enable(struct mvc2_ep *ep, int num, int in, int type)
++{
++ struct mvc2 *cp = ep->cp;
++ struct usb_ep *_ep = &ep->ep;
++ unsigned int config, val, config_base;
++ struct mvc2_req *req, *tmp;
++ unsigned long flags, ring, reg;
++
++ /* We suppose there is no item in run queue here */
++ WARN_ON(!list_empty(&ep->queue));
++
++ ring = ep->state = 0;
++ config_base = epcon(num, in);
++ config = MVCP_EP_MAX_PKT(_ep->desc->wMaxPacketSize);
++ if (_ep->comp_desc)
++ config |= MVCP_EP_BURST(_ep->comp_desc->bMaxBurst);
++
++ if (num) {
++ config |= MVCP_EP_ENABLE | MVCP_EP_NUM(num);
++ switch (type) {
++ case USB_ENDPOINT_XFER_BULK:
++ if (_ep->comp_desc &&
++ _ep->comp_desc->bmAttributes & 0x1f)
++ ep->state |= MV_CP_EP_BULK_STREAM;
++ else
++ ep->state &= ~MV_CP_EP_BULK_STREAM;
++
++ config |= MVCP_EP_TYPE_BLK;
++
++ /* Enable bulk stream if need */
++ spin_lock_irqsave(&cp->lock, flags);
++ if (ip_ver(cp) <= USB3_IP_VER_Z2) {
++ val = MV_CP_READ(MVCP_BULK_STREAMING_ENABLE);
++ if (ep->state & MV_CP_EP_BULK_STREAM)
++ val |= EPBIT(num, in);
++ else
++ val &= ~EPBIT(num, in);
++ MV_CP_WRITE(val, MVCP_BULK_STREAMING_ENABLE);
++ } else {
++ if (ep->state & MV_CP_EP_BULK_STREAM)
++ config |= MVCP_EP_BULK_STREAM_EN;
++ else
++ config &= ~MVCP_EP_BULK_STREAM_EN;
++ }
++ spin_unlock_irqrestore(&cp->lock, flags);
++ break;
++ case USB_ENDPOINT_XFER_ISOC:
++ config |= MVCP_EP_TYPE_ISO;
++ if (ip_ver(cp) <= USB3_IP_VER_Z2) {
++ if (in)
++ reg = EP_IN_BINTERVAL_REG_1_2_3 +
++ 4 * (num / 4);
++ else
++ reg = EP_OUT_BINTERVAL_REG_1_2_3 +
++ 4 * (num / 4);
++ val = MV_CP_READ(reg);
++ val |= (_ep->desc->bInterval) << (num % 4) * 8;
++ MV_CP_WRITE(val, reg);
++ } else {
++ if (in)
++ reg = EP_IN_BINTERVAL_REG(num);
++ else
++ reg = EP_OUT_BINTERVAL_REG(num);
++ MV_CP_WRITE(_ep->desc->bInterval, reg);
++ }
++ break;
++ case USB_ENDPOINT_XFER_INT:
++ config |= MVCP_EP_TYPE_INT;
++ break;
++ }
++ }
++
++ MV_CP_WRITE(config, config_base);
++ spin_unlock(&ep->lock);
++ mvc2_dma_reset(cp, ep, num, in);
++ spin_lock(&ep->lock);
++ /* Reset sequence number */
++ if (num != 0)
++ reset_seqencenum(ep, num, in);
++
++ /* Requeue the bd */
++ list_for_each_entry_safe(req, tmp, &ep->wait, queue) {
++ list_del_init(&req->queue);
++ val = alloc_bds(cp, ep, req);
++ /* Current all bds have been allocated, just wait for previous complete */
++ if (val)
++ ring = 1;
++ else {
++ dev_dbg(cp->dev, "%s %d\n", __func__, __LINE__);
++ list_add(&req->queue, &ep->wait);
++ break;
++ }
++ }
++
++ if (ring)
++ mvc2_ring_incoming(cp, num, in);
++}
++
++static int mvc2_ep_enable(struct usb_ep *_ep,
++ const struct usb_endpoint_descriptor *desc)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++ int n = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
++ int in = (desc->bEndpointAddress & USB_DIR_IN) != 0;
++ unsigned int state;
++ unsigned long flags;
++
++ _ep->maxpacket = le16_to_cpu(desc->wMaxPacketSize);
++ _ep->desc = desc;
++
++ spin_lock_irqsave(&ep->lock, flags);
++ ep_enable(ep, n, in, desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK);
++
++ state = (ep->state & MV_CP_EP_WEDGE) | MV_CP_EP_NUM(n);
++ state |= in ? MV_CP_EP_DIRIN : 0;
++ ep->state = state;
++ spin_unlock_irqrestore(&ep->lock, flags);
++
++ return 0;
++}
++
++static void ep_disable(struct mvc2 *cp, int num, int dir)
++{
++ unsigned int config;
++ struct mvc2_ep *ep = &cp->eps[2 * num + !!dir];
++
++ config = MV_CP_READ(epcon(num, dir));
++ config &= ~MVCP_EP_ENABLE;
++ MV_CP_WRITE(config, epcon(num, dir));
++
++ spin_unlock(&ep->lock);
++ /* nuke all pending requests (does flush) */
++ nuke(ep, -ESHUTDOWN);
++ spin_lock(&ep->lock);
++}
++
++static int mvc2_ep_disable(struct usb_ep *_ep)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++ struct mvc2 *cp = ep->cp;
++ unsigned long flags;
++
++ if (!(ep->state & MV_CP_EP_NUM_MASK))
++ return 0;
++
++ spin_lock_irqsave(&ep->lock, flags);
++ ep_disable(cp, ep->state & MV_CP_EP_NUM_MASK,
++ ep->state & MV_CP_EP_DIRIN);
++ spin_unlock_irqrestore(&ep->lock, flags);
++
++ return 0;
++}
++
++static inline void mvc2_send_erdy(struct mvc2 *cp)
++{
++ /* ep0 erdy should be smp safe, and no lock is needed */
++ MV_CP_WRITE(MV_CP_READ(MVCP_ENDPOINT_0_CONFIG) |
++ MVCP_ENDPOINT_0_CONFIG_CHG_STATE, MVCP_ENDPOINT_0_CONFIG);
++}
++
++#ifndef ASSEMBLE_REQ
++/* queues (submits) an I/O request to an endpoint */
++static int
++mvc2_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++ struct mvc2_req *req = container_of(_req, struct mvc2_req, req);
++ struct mvc2 *cp = ep->cp;
++ unsigned int dir, num;
++ unsigned long flags;
++ int ret;
++
++ if (_ep == NULL || _req == NULL)
++ return -EINVAL;
++
++ num = _ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
++ /* Reset the endpoint 0 to prevent previous left data */
++ if (num == 0) {
++ /*
++ * After USB_GADGET_DELAYED_STATUS is set and the USB upper layer in USB function thread
++ * finishes the handling, the USB compsite layer will send request to continue with the
++ * control transfer, within this request, the request length is set 0.
++ * Since the request length will not be 0 for normal transfer, once it is 0, it means
++ * that to continue the transfer after USB_GADGET_DELAYED_STATUS. Thus the erdy is set
++ * here to notify the host that device is ready for latter transfer.
++ */
++ if (!req->req.length) {
++ mvc2_send_erdy(cp);
++ return 0;
++ }
++
++ if (cp->ep0_dir == USB_DIR_IN)
++ ep = &cp->eps[1];
++ else
++ ep = &cp->eps[0];
++
++ dir = cp->ep0_dir;
++
++ spin_lock_irqsave(&ep->lock, flags);
++
++ MV_CP_WRITE(ep->doneq_cur * 8 + ep->doneq_start_phys,
++ ep_doneq_read(num, dir));
++ ep->doneq_cur++;
++ if (ep->doneq_cur == ep->bd_sz)
++ ep->doneq_cur = 0;
++
++ spin_unlock_irqrestore(&ep->lock, flags);
++ } else
++ dir = _ep->desc->bEndpointAddress & USB_DIR_IN;
++
++ ret = usb_gadget_map_request(&cp->gadget, &req->req, dir);
++ if (ret)
++ return ret;
++
++ _req->actual = 0;
++ _req->status = -EINPROGRESS;
++ spin_lock_irqsave(&ep->lock, flags);
++
++ ret = alloc_bds(cp, ep, req);
++ /* Current all bds have been allocated, just wait for previous complete */
++ if (!ret) {
++ dev_dbg(cp->dev, "%s %d\n", __func__, __LINE__);
++ list_add_tail(&req->queue, &ep->wait);
++ } else
++ mvc2_ring_incoming(cp, num, dir);
++
++ spin_unlock_irqrestore(&ep->lock, flags);
++
++ return 0;
++}
++#else
++static int
++mvc2_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++ struct mvc2_req *req = container_of(_req, struct mvc2_req, req), *tmp;
++ struct mvc2 *cp = ep->cp;
++ unsigned int dir, num;
++ unsigned long flags;
++ int ret, last, reqcnt;
++ static int cnt;
++#define CNT 10
++ if (_ep == NULL || _req == NULL)
++ return -EINVAL;
++
++ num = _ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
++ /* Reset the endpoint 0 to prevent previous left data */
++ if (num == 0) {
++ /*
++ * After USB_GADGET_DELAYED_STATUS is set and the USB upper layer in USB function thread
++ * finishes the handling, the USB compsite layer will send request to continue with the
++ * control transfer. Within this request, the request length is set 0.
++ * Since the request length will not be 0 for normal transfer, once it is 0, it means
++ * that to continue the transfer after USB_GADGET_DELAYED_STATUS. Thus the erdy is set
++ * here to notify the host that device is ready for latter transfer.
++ */
++ if (!req->req.length) {
++ mvc2_send_erdy(cp);
++ return 0;
++ }
++
++ if (cp->ep0_dir == USB_DIR_IN)
++ ep = &cp->eps[1];
++ else
++ ep = &cp->eps[0];
++
++ dir = cp->ep0_dir;
++
++ spin_lock_irqsave(&ep->lock, flags);
++ mvc2_dma_reset(cp, ep, num, dir);
++ MV_CP_WRITE(ep->doneq_cur + ep->doneq_start_phys,
++ ep_doneq_read(num, dir));
++ spin_unlock_irqrestore(&ep->lock, flags);
++ } else
++ dir = _ep->desc->bEndpointAddress & USB_DIR_IN;
++
++ ret = usb_gadget_map_request(&cp->gadget, &req->req, dir);
++ if (ret)
++ return ret;
++
++ _req->actual = 0;
++ _req->status = -EINPROGRESS;
++ spin_lock_irqsave(&ep->lock, flags);
++
++ if (dir == USB_DIR_OUT) {
++ ret = alloc_bds(cp, ep, req);
++ /* Current all bds have been allocated, just wait for previous complete */
++ if (!ret)
++ list_add_tail(&req->queue, &ep->wait);
++ else
++ mvc2_ring_incoming(cp, num, dir);
++ } else {
++ list_add_tail(&req->queue, &ep->tmp);
++ cnt++;
++
++ if (req->req.length > 1000 && cnt < CNT)
++ goto out;
++ if (cnt == CNT || req->req.length < 1000) {
++ list_for_each_entry_safe(req, tmp, &ep->tmp, queue) {
++ list_del_init(&req->queue);
++ cnt--;
++ if (cnt)
++ last = 0;
++ else
++ last = 1;
++#if 1
++ ret = alloc_in_bds(cp, ep, req, &last);
++#else
++ ret = alloc_bds(cp, ep, req);
++ /* Current all bds have been allocated, just wait for previous complete */
++ if (!ret)
++ list_add_tail(&req->queue, &ep->wait);
++#endif
++ }
++ mvc2_ring_incoming(cp, num, dir);
++ }
++ }
++out:
++ spin_unlock_irqrestore(&ep->lock, flags);
++
++ return 0;
++}
++#endif
++
++/*
++ * done() - retire a request; caller blocked irqs
++ * @status : request status to be set, only works when
++ * request is still in progress.
++ */
++static void done(struct mvc2_ep *ep, struct mvc2_req *req, int status)
++{
++ struct mvc2 *cp = NULL;
++
++ cp = (struct mvc2 *)ep->cp;
++ /* Removed the req from fsl_ep->queue */
++ list_del_init(&req->queue);
++
++ ep->left_bds += req->bd_total;
++ WARN_ON(ep->left_bds > ep->bd_sz);
++
++ /* req.status should be set as -EINPROGRESS in ep_queue() */
++ if (req->req.status == -EINPROGRESS)
++ req->req.status = status;
++ else
++ status = req->req.status;
++
++ usb_gadget_unmap_request(&cp->gadget, &req->req, ep_dir(ep));
++
++ if (status && (status != -ESHUTDOWN))
++ dev_info(cp->dev, "complete %s req %p stat %d len %u/%u",
++ ep->ep.name, &req->req, status,
++ req->req.actual, req->req.length);
++
++ spin_unlock(&ep->lock);
++ /*
++ * complete() is from gadget layer,
++ * eg fsg->bulk_in_complete()
++ */
++ if (req->req.complete)
++ req->req.complete(&ep->ep, &req->req);
++
++ spin_lock(&ep->lock);
++}
++
++static void ep_fifo_flush(struct mvc2 *cp, int num, int dir, int all)
++{
++ struct mvc2_ep *ep;
++
++ ep = &cp->eps[2 * num + !!dir];
++ /*
++ * Only current transferring bd would be transferred out,
++ * for those bd still chained after would be left untouched
++ */
++ mvc2_dma_reset(cp, ep, num, dir);
++}
++
++/* dequeues (cancels, unlinks) an I/O request from an endpoint */
++static int mvc2_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++ struct mvc2 *cp = ep->cp;
++ struct mvc2_req *req = container_of(_req, struct mvc2_req, req), *tmp;
++ int num = _ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
++ int dir = _ep->desc->bEndpointAddress & USB_DIR_IN;
++ int type = _ep->desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
++ unsigned long flags;
++ int ret, ring;
++
++ ring = ret = 0;
++ if (_ep == NULL || _req == NULL || list_empty(&req->queue))
++ return -EINVAL;
++
++ spin_lock_irqsave(&ep->lock, flags);
++ ep_disable(cp, num, dir);
++
++ list_for_each_entry(tmp, &ep->wait, queue)
++ if (tmp == req)
++ break;
++
++ /* If don't find the request in both run/wait queue, quit */
++ if (tmp != req) {
++ ret = -EINVAL;
++ goto out;
++ }
++
++ list_del_init(&req->queue);
++ if (req->req.length)
++ usb_gadget_unmap_request(&ep->cp->gadget, _req, dir);
++ spin_unlock_irqrestore(&ep->lock, flags);
++ if (req->req.complete) {
++ req->req.status = -ECONNRESET;
++ req->req.complete(&ep->ep, &req->req);
++ }
++
++ spin_lock_irqsave(&ep->lock, flags);
++out:
++ ep_enable(ep, num, dir, type);
++ spin_unlock_irqrestore(&ep->lock, flags);
++
++ return ret;
++}
++
++static int ep_set_halt(struct mvc2 *cp, int n, int in, int halt)
++{
++ unsigned int config, config_base;
++ struct mvc2_ep *ep = &cp->eps[2 * n + !!in];
++ unsigned long flags;
++ int bulk;
++
++ config_base = epcon(n, in);
++ bulk = ep->ep.desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
++
++ spin_lock_irqsave(&ep->lock, flags);
++
++ if (halt && (bulk == USB_ENDPOINT_XFER_BULK) && in
++ && !list_empty(&ep->queue)) {
++ spin_unlock_irqrestore(&ep->lock, flags);
++ return -EAGAIN;
++ }
++
++ config = MV_CP_READ(config_base);
++ if (halt) {
++ config |= MVCP_EP_STALL;
++ if (n)
++ ep->state |= MV_CP_EP_STALL;
++ } else {
++ config &= ~MVCP_EP_STALL;
++ ep->state &= ~MV_CP_EP_STALL;
++ }
++ MV_CP_WRITE(config, config_base);
++ spin_unlock_irqrestore(&ep->lock, flags);
++
++ return 0;
++}
++
++static int mvc2_ep_set_halt(struct usb_ep *_ep, int halt)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++ struct mvc2 *cp = ep->cp;
++ unsigned int n, in;
++
++ if (_ep == NULL || _ep->desc == NULL)
++ return -EINVAL;
++
++ if (usb_endpoint_xfer_isoc(_ep->desc))
++ return -EOPNOTSUPP;
++
++ if (!halt)
++ ep->state &= ~MV_CP_EP_WEDGE;
++
++ n = _ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
++ in = _ep->desc->bEndpointAddress & USB_DIR_IN;
++
++ return ep_set_halt(cp, n, in, halt);
++}
++
++static int mvc2_ep_set_wedge(struct usb_ep *_ep)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++
++ ep->state |= MV_CP_EP_WEDGE;
++ return mvc2_ep_set_halt(_ep, 1);
++}
++
++static void mvc2_ep_fifo_flush(struct usb_ep *_ep)
++{
++ struct mvc2_ep *ep = container_of(_ep, struct mvc2_ep, ep);
++ struct mvc2 *cp = ep->cp;
++
++ ep_fifo_flush(cp, ep->ep_num, ep_dir(ep), 1);
++}
++
++static struct usb_ep_ops mvc2_ep_ops = {
++ .enable = mvc2_ep_enable,
++ .disable = mvc2_ep_disable,
++
++ .alloc_request = mvc2_alloc_request,
++ .free_request = mvc2_free_request,
++
++ .queue = mvc2_ep_queue,
++ .dequeue = mvc2_ep_dequeue,
++
++ .set_wedge = mvc2_ep_set_wedge,
++ .set_halt = mvc2_ep_set_halt,
++ .fifo_flush = mvc2_ep_fifo_flush,
++};
++
++/* delete all endpoint requests, called with spinlock held */
++static void nuke(struct mvc2_ep *ep, int status)
++{
++ struct mvc2_req *req, *tmp;
++ unsigned long flags;
++ /* called with spinlock held */
++ ep->stopped = 1;
++
++ /* endpoint fifo flush */
++ mvc2_ep_fifo_flush(&ep->ep);
++ spin_lock_irqsave(&ep->lock, flags);
++ list_for_each_entry_safe(req, tmp, &ep->queue, queue)
++ done(ep, req, status);
++ list_for_each_entry_safe(req, tmp, &ep->wait, queue)
++ done(ep, req, status);
++ spin_unlock_irqrestore(&ep->lock, flags);
++}
++
++/* stop all USB activities */
++static void stop_activity(struct mvc2 *udc, struct usb_gadget_driver *driver)
++{
++ struct mvc2_ep *ep;
++
++ nuke(&udc->eps[0], -ESHUTDOWN);
++ nuke(&udc->eps[1], -ESHUTDOWN);
++
++ list_for_each_entry(ep, &udc->gadget.ep_list, ep.ep_list) {
++ if (ep->ep_num <= ip_ep_num(udc))
++ nuke(ep, -ESHUTDOWN);
++ }
++
++ /* report disconnect; the driver is already quiesced */
++ if (driver)
++ driver->disconnect(&udc->gadget);
++}
++
++static void mvc2_init_interrupt(struct mvc2 *cp)
++{
++ int i;
++
++ if (ip_ver(cp) <= USB3_IP_VER_Z2) {
++ MV_CP_WRITE(~0, MVCP_DMA_COMPLETE_SUCCESS);
++ MV_CP_WRITE(~0, MVCP_DMA_COMPLETE_ERROR);
++ MV_CP_WRITE(~0, MVCP_SS_CORE_INT);
++ MV_CP_WRITE(~0, MVCP_SS_SYS_INT);
++ /*
++ * Don't clear the ref int status.
++ * Refer to the comments in mvc2_usb2_connect for details.
++ * val = MV_CP_READ(cp->reg->ref_int);
++ * MV_CP_WRITE(val, cp->reg->ref_int);
++ */
++ MV_CP_WRITE(MVCP_SS_CORE_INTEN_SETUP
++ | MVCP_SS_CORE_INTEN_HOT_RESET
++ | MVCP_SS_CORE_INTEN_LTSSM_CHG, MVCP_SS_CORE_INTEN);
++ MV_CP_WRITE(MVCP_SS_SYS_INTEN_DMA, MVCP_SS_SYS_INTEN);
++ MV_CP_WRITE(MVCP_REF_INTEN_USB2_CNT
++ | MVCP_REF_INTEN_USB2_DISCNT
++ | MVCP_REF_INTEN_RESET
++ | MVCP_REF_INTEN_POWERON
++ | MVCP_REF_INTEN_POWEROFF
++ | MVCP_REF_INTEN_SUSPEND
++ | MVCP_REF_INTEN_RESUME, cp->reg->ref_inten);
++
++ set_top_int(cp, 0xf);
++ } else {
++ MV_CP_WRITE(~0, MVCP_SS_CORE_INT);
++ MV_CP_WRITE(~0, MVCP_TOP_INT_STATUS);
++ MV_CP_WRITE(~0, SS_EP_TOP_INT_STATUS_REG);
++ MV_CP_WRITE(~0, SS_EP_TOP_INT_ENABLE_REG);
++ MV_CP_WRITE(~0, SS_AXI_INT_STATUS_REG);
++ MV_CP_WRITE(~0, SS_AXI_INT_ENABLE_REG);
++
++ /* enbale all interrupts of endpoints
++ * except doneq_full interrupt
++ */
++ for (i = 0; i < cp->epnum; i++) {
++ MV_CP_WRITE(~0, SS_IN_EP_INT_STATUS_REG(i));
++ MV_CP_WRITE(~DONEQ_FULL, SS_IN_EP_INT_ENABLE_REG(i));
++ MV_CP_WRITE(~0, SS_OUT_EP_INT_STATUS_REG(i));
++ MV_CP_WRITE(~DONEQ_FULL, SS_OUT_EP_INT_ENABLE_REG(i));
++ }
++ /*
++ * Don't clear the ref int status.
++ * Refer to the comments in mvc2_usb2_connect for details.
++ * val = MV_CP_READ(cp->reg->ref_int);
++ * MV_CP_WRITE(val, cp->reg->ref_int);
++ */
++ /* Since decode_err_8_10b & disparity_err will
++ * generate very frequenlty when enable U1/U2,
++ * we disable these two error interrupt
++ */
++ MV_CP_WRITE(MVCP_SS_CORE_INTEN_SETUP
++ | MVCP_SS_CORE_INTEN_HOT_RESET
++ | MVCP_SS_CORE_INTEN_LTSSM_CHG
++ /*| 0x3F */, MVCP_SS_CORE_INTEN);
++ MV_CP_WRITE(MVCP_REF_INTEN_USB2_CNT
++ | MVCP_REF_INTEN_USB2_DISCNT
++ | MVCP_REF_INTEN_RESET
++ | MVCP_REF_INTEN_POWERON
++ | MVCP_REF_INTEN_POWEROFF
++ | MVCP_REF_INTEN_SUSPEND
++ | MVCP_REF_INTEN_RESUME, cp->reg->ref_inten);
++
++ set_top_int(cp, 0x4f);
++ }
++}
++
++static int mvc2_pullup(struct usb_gadget *gadget, int is_on)
++{
++ struct mvc2 *cp = container_of(gadget, struct mvc2, gadget);
++ unsigned int val;
++
++ /*
++ * For every switch from 2.0 to 3.0, this dma global config
++ * and interrupt enable register would get reset
++ */
++ if (is_on && !irq_enabled) {
++ irq_enabled = true;
++ enable_irq(cp->irq);
++ }
++
++ if (!usb3_disconnect)
++ is_on = 1;
++
++ mvc2_connect(cp, is_on);
++ mvc2_config_mac(cp);
++ val = MV_CP_READ(MVCP_DMA_GLOBAL_CONFIG);
++ val |= MVCP_DMA_GLOBAL_CONFIG_RUN | MVCP_DMA_GLOBAL_CONFIG_INTCLR;
++ MV_CP_WRITE(val, MVCP_DMA_GLOBAL_CONFIG);
++
++ mvc2_init_interrupt(cp);
++
++ if (is_on == 0)
++ stop_activity(cp, cp->driver);
++ return 0;
++}
++
++static int mvc2_start(struct usb_gadget *gadget,
++ struct usb_gadget_driver *driver)
++{
++ struct mvc2 *cp = container_of(gadget, struct mvc2, gadget);
++ unsigned long flags;
++ struct mvc2_ep *ep;
++
++ cp->driver = driver;
++
++ /* enable ep0, dma int */
++ ep = &cp->eps[0];
++ spin_lock_irqsave(&ep->lock, flags);
++ ep_enable(ep, 0, 0, 0);
++ spin_unlock_irqrestore(&ep->lock, flags);
++
++ ep = &cp->eps[1];
++ spin_lock_irqsave(&ep->lock, flags);
++ ep_enable(ep, 0, 1, 0);
++
++ spin_unlock_irqrestore(&ep->lock, flags);
++
++ /* pullup is always on */
++ mvc2_pullup(gadget, 1);
++
++ return 0;
++}
++
++static int mvc2_first_start(struct usb_gadget *gadget,
++ struct usb_gadget_driver *driver)
++{
++ struct mvc2 *cp = container_of(gadget, struct mvc2, gadget);
++
++ mvc2_start(gadget, driver);
++
++ /* When boot with cable attached, there will be no vbus irq occurred */
++ if (cp->qwork)
++ queue_work(cp->qwork, &cp->vbus_work);
++
++ return 0;
++}
++
++static int mvc2_stop(struct usb_gadget *gadget)
++{
++ struct mvc2 *cp = container_of(gadget, struct mvc2, gadget);
++
++ cp->driver = NULL;
++ return 0;
++}
++
++int mvc2_checkvbus(struct mvc2 *cp)
++{
++ int tmp;
++
++ tmp = MV_CP_READ(cp->reg->global_control);
++ return tmp & MVCP_GLOBAL_CONTROL_POWERPRESENT;
++}
++
++static int mvc2_vbus_session(struct usb_gadget *gadget, int is_active)
++{
++ struct mvc2 *cp = container_of(gadget, struct mvc2, gadget);
++ unsigned int val;
++
++ /* We only do real work when gadget driver is ready */
++ if (!cp->driver)
++ return -ENODEV;
++
++ val = MV_CP_READ(MVCP_DMA_GLOBAL_CONFIG);
++ if (is_active) {
++ /* For Armada 3700, need to skip PHY HW reset */
++ if (cp->phy_hw_reset)
++ mvc2_hw_reset(cp);
++ pm_stay_awake(cp->dev);
++ /* turn on dma int */
++ val |= MVCP_DMA_GLOBAL_CONFIG_RUN
++ | MVCP_DMA_GLOBAL_CONFIG_INTCLR;
++ MV_CP_WRITE(val, MVCP_DMA_GLOBAL_CONFIG);
++ usb_gadget_connect(&cp->gadget);
++ mvc2_start(gadget, cp->driver);
++
++ } else {
++ /* need to stop activity before disable dma engine.
++ * stop_activity will call mvc2_dma_reset,
++ * if disable dma before mvc2_dma_reset, then dma reset
++ * timeout issue will happen.
++ */
++ stop_activity(cp, cp->driver);
++
++ /* disable dma engine */
++ val &= ~MVCP_DMA_GLOBAL_CONFIG_RUN;
++ MV_CP_WRITE(val, MVCP_DMA_GLOBAL_CONFIG);
++ }
++
++ return 0;
++}
++
++static irqreturn_t mvc2_vbus_irq(int irq, void *dev)
++{
++ struct mvc2 *cp = (struct mvc2 *)dev;
++
++ /* polling VBUS and init phy may cause too much time */
++ if (cp->qwork)
++ queue_work(cp->qwork, &cp->vbus_work);
++
++ return IRQ_HANDLED;
++}
++
++static void mvc2_vbus_work(struct work_struct *work)
++{
++ struct mvc2 *cp;
++ unsigned int vbus;
++ unsigned int reg;
++
++ cp = container_of(work, struct mvc2, vbus_work);
++
++ if (gpio_is_valid(cp->vbus_pin))
++ vbus = gpio_get_value_cansleep(cp->vbus_pin);
++ else {
++ dev_err(cp->dev, "VBUS interrupt status is missing\n");
++ return;
++ }
++
++ if (cp->prev_vbus != vbus)
++ cp->prev_vbus = vbus;
++ else
++ return;
++
++ if (!cp->phy_base) {
++ dev_err(cp->dev, "PHY register is missing\n");
++ return;
++ }
++
++ if (vbus == VBUS_HIGH) {
++ reg = readl(cp->phy_base);
++ reg |= 0x8000;
++ writel(reg, cp->phy_base);
++ } else if (vbus == VBUS_LOW) {
++ reg = readl(cp->phy_base);
++ reg &= ~0x8000;
++ writel(reg, cp->phy_base);
++ }
++}
++
++static int mvc2_vbus_draw(struct usb_gadget *gadget, unsigned mA)
++{
++ return -ENOTSUPP;
++}
++
++static int mvc2_set_selfpowered(struct usb_gadget *gadget, int is_selfpowered)
++{
++ struct mvc2 *cp = container_of(gadget, struct mvc2, gadget);
++
++ if (is_selfpowered)
++ cp->status |= MVCP_STATUS_SELF_POWERED;
++ else
++ cp->status &= ~MVCP_STATUS_SELF_POWERED;
++
++ return 0;
++}
++
++#ifdef CONFIG_USB_REMOTE_WAKEUP
++
++#define MVCP_GLOBAL_CONTROL_STATUS 0x2c
++#define MVCP_GLOBAL_CONTROL_STATUS_LPFS_EXIT (1<<7)
++static int mvc2_wakeup(struct usb_gadget *gadget)
++{
++ struct mvc2 *cp = container_of(gadget, struct mvc2, gadget);
++ unsigned int phy, val;
++
++ phy = MV_CP_READ(MVCP_PHY);
++ if ((phy & MVCP_PHY_LTSSM_MASK) == LTSSM_U3) {
++ dev_info(cp->dev, "usb3 is enter u3 , can be wakeup now\n");
++ val = MV_CP_READ(MVCP_GLOBAL_CONTROL_STATUS);
++ val |= MVCP_GLOBAL_CONTROL_STATUS_LPFS_EXIT;
++ MV_CP_WRITE(val, MVCP_GLOBAL_CONTROL_STATUS);
++ }
++
++ return 0;
++}
++#endif
++
++/* device controller usb_gadget_ops structure */
++static const struct usb_gadget_ops mvc2_ops = {
++ /* notify controller that VBUS is powered or not */
++ .vbus_session = mvc2_vbus_session,
++
++ /* constrain controller's VBUS power usage */
++ .vbus_draw = mvc2_vbus_draw,
++ .set_selfpowered = mvc2_set_selfpowered,
++
++ .pullup = mvc2_pullup,
++ .udc_start = mvc2_first_start,
++ .udc_stop = mvc2_stop,
++#ifdef CONFIG_USB_REMOTE_WAKEUP
++ .wakeup = mvc2_wakeup,
++#endif
++};
++
++void mvc2_handle_setup(struct mvc2 *cp)
++{
++ struct usb_ctrlrequest *r;
++ unsigned int tmp[2];
++ int ret = -EINVAL;
++ bool delegate;
++
++ tmp[0] = MV_CP_READ(MVCP_SETUP_DP_LOW);
++ tmp[1] = MV_CP_READ(MVCP_SETUP_DP_HIGH);
++ MV_CP_WRITE(MVCP_SETUP_CONTROL_FETCHED, MVCP_SETUP_CONTROL);
++
++ r = (struct usb_ctrlrequest *)tmp;
++
++ if (r->wLength) {
++ if (r->bRequestType & USB_DIR_IN)
++ cp->ep0_dir = USB_DIR_IN;
++ else
++ cp->ep0_dir = USB_DIR_OUT;
++ } else
++ cp->ep0_dir = USB_DIR_IN;
++
++ ret = mvc2_std_request(cp, r, &delegate);
++ if (delegate)
++ ret = cp->driver->setup(&cp->gadget, r);
++ /* indicate setup pharse already complete */
++ mvc2_send_erdy(cp);
++
++ /* Stall the endpoint if protocol not support */
++ if (ret < 0)
++ ep_set_halt(cp, 0, 0, 1);
++ /*
++ * If current setup has no data pharse or failed, we would directly
++ * jump to status pharse.
++ * If the USB_GADGET_DELAYED_STATUS is set, the USB interface requests
++ * delay for it to handle the setup, thus here should not send erdy to
++ * continue the transfer. Instead, the erdy will be sent from mvc2_ep_queue,
++ * once a request with length 0 is issued.
++ */
++ if ((ret < 0) || (r->wLength == 0 && ret != USB_GADGET_DELAYED_STATUS))
++ mvc2_send_erdy(cp);
++}
++
++static void mvc2_dma_complete(struct mvc2 *cp)
++{
++ unsigned int val, i, n, in, short_packet, finish, ret;
++ struct doneq *done;
++ struct mvc2_ep *ep;
++ struct mvc2_req *req, *tmp;
++ struct bd *bd;
++ unsigned int writeq, len, doneq, ring;
++ unsigned int sreg, ep_status;
++
++ if (ip_ver(cp) <= USB3_IP_VER_Z2)
++ sreg = MVCP_DMA_COMPLETE_SUCCESS;
++ else
++ sreg = SS_EP_TOP_INT_STATUS_REG;
++ val = MV_CP_READ(sreg);
++ if (!val)
++ return;
++ MV_CP_WRITE(val, sreg);
++
++ for (i = 0; i < (cp->epnum << 1); i++) {
++ if (!(val & (1 << i)))
++ continue;
++
++ if (i < cp->epnum) {
++ n = i;
++ in = 0;
++ } else {
++ n = i - cp->epnum;
++ in = 1;
++ }
++
++ if (ip_ver(cp) >= USB3_IP_VER_Z3) {
++ in = in ? 0 : 1;
++ if (in)
++ sreg = SS_IN_EP_INT_STATUS_REG(n);
++ else
++ sreg = SS_OUT_EP_INT_STATUS_REG(n);
++ ep_status = MV_CP_READ(sreg);
++ /* clear interrupt status */
++ MV_CP_WRITE(ep_status, sreg);
++
++ if (ep_status & COMPLETION_SUCCESS)
++ goto success;
++
++ /* some error may happen */
++ pr_warn("### %s %d: num %d, dir %d, status 0x%x\n",
++ __func__, __LINE__, n, in, ep_status);
++ continue;
++ }
++
++success:
++ ep = &cp->eps[(n << 1) + in];
++
++ /*
++ * info hw that sw has prepared data
++ * hw would auto send erdy after data stage complete
++ */
++ if (n == 0) {
++ mvc2_send_erdy(cp);
++ ep->state &= ~MV_CP_EP_TRANSERING;
++ if (!list_empty(&ep->queue)) {
++ req = list_first_entry(&ep->queue,
++ struct mvc2_req, queue);
++
++ if (req->req.complete) {
++ req->req.status = 0;
++ req->req.complete(&ep->ep, &req->req);
++ }
++ ep->left_bds++;
++ WARN_ON(req->bd_total > 1);
++ WARN_ON(ep->left_bds > ep->bd_sz);
++ spin_lock(&ep->lock);
++ INIT_LIST_HEAD(&ep->queue);
++ INIT_LIST_HEAD(&ep->wait);
++ spin_unlock(&ep->lock);
++ }
++
++ continue;
++ }
++
++ writeq = MV_CP_READ(ep_doneq_write(n, in));
++ if (!writeq)
++ continue;
++
++ /* Get the DoneQ write pointer relative position */
++ writeq -= ep->doneq_start_phys;
++ writeq /= sizeof(struct doneq);
++ if (writeq == ep->bd_sz)
++ writeq = 0;
++
++ doneq = ep->doneq_cur;
++ short_packet = 0;
++ ring = 0;
++ spin_lock(&ep->lock);
++ while (doneq != writeq) {
++ len = 0;
++ req = list_first_entry_or_null(&ep->queue,
++ struct mvc2_req, queue);
++ if (!req) {
++ pr_info("req null, doneq = %d,writeq = %d\n",
++ doneq, writeq);
++ break;
++ }
++ bd = req->bd;
++ finish = 1;
++ do {
++ done = (struct doneq *)(ep->doneq_start
++ + doneq);
++
++ if (done->status & DONE_AXI_ERROR) {
++ req->req.status = -EPROTO;
++ break;
++ }
++
++ /*
++ * Note: for the short packet, if originally
++ * there are several BDs chained, but host
++ * only send short packet for the first BD,
++ * then doneq would be updated accordingly.
++ * And later BDs in the chain would be used
++ * for storing data that host send in another
++ * transfer.
++ *
++ * But if the first BD is not set as INT_EN,
++ * there would be no interrupt be generated.
++ */
++ if (done->status & DONE_SHORT_PKT)
++ short_packet = 1;
++
++ len += DONE_LEN(done->status);
++ doneq++;
++ if (doneq == ep->bd_sz)
++ doneq = 0;
++
++ WARN_ON(doneq == (writeq + 1));
++ bd->cmd = 0;
++ bd++;
++ ep->left_bds++;
++ WARN_ON(ep->left_bds > ep->bd_sz);
++
++ } while (--req->bd_total > 0);
++
++ /*
++ * Ring the finish data handle bell
++ * to kick hardware to continue
++ */
++ MV_CP_WRITE(doneq * 8 + ep->doneq_start_phys,
++ ep_doneq_read(n, in));
++ ep->doneq_cur = doneq;
++
++ req->req.actual += len;
++ list_del_init(&req->queue);
++ ep->state &= ~MV_CP_EP_TRANSERING;
++
++ ret = UINT_MAX;
++ /* There still something left not being transferred */
++ if ((req->req.actual < req->req.length)
++ && !short_packet) {
++ dev_dbg(cp->dev, "%s %d\n", __func__, __LINE__);
++ ret = alloc_bds(cp, ep, req);
++ finish = 0;
++ }
++
++ /*
++ * Refill BD if there is any request
++ * following in the chain
++ */
++ while (!list_empty(&ep->wait) && ret) {
++ tmp = list_first_entry(&ep->wait,
++ struct mvc2_req, queue);
++ list_del_init(&tmp->queue);
++ ret = alloc_bds(cp, ep, tmp);
++ if (!ret)
++ list_add(&tmp->queue, &ep->wait);
++ }
++
++ if (finish) {
++ spin_unlock(&ep->lock);
++ if (req->req.length)
++ usb_gadget_unmap_request(&cp->gadget,
++ &req->req, in);
++
++ if (req->req.complete) {
++ req->req.status = 0;
++ req->req.complete(&ep->ep, &req->req);
++ }
++ spin_lock(&ep->lock);
++ }
++
++ if (ret != UINT_MAX)
++ ring = 1;
++ }
++
++ if (ring)
++ mvc2_ring_incoming(cp, n, in);
++ spin_unlock(&ep->lock);
++ }
++}
++
++static void mvc2_process_link_change(struct mvc2 *cp)
++{
++ unsigned int val;
++
++ cp->status &= ~MVCP_STATUS_POWER_MASK;
++ val = MV_CP_READ(MVCP_PHY);
++ switch (val & MVCP_PHY_LTSSM_MASK) {
++ case LTSSM_U0:
++ cp->gadget.speed = USB_SPEED_SUPER;
++ cp->status |= MVCP_STATUS_U0;
++ cp->status |= MVCP_STATUS_CONNECTED;
++ break;
++ case LTSSM_U1:
++ cp->status |= MVCP_STATUS_U1;
++ break;
++ case LTSSM_U2:
++ cp->status |= MVCP_STATUS_U2;
++ break;
++ case LTSSM_U3:
++ cp->status |= MVCP_STATUS_U3;
++ break;
++ }
++}
++
++static irqreturn_t mvc2_irq(int irq, void *devid)
++{
++ struct mvc2 *cp = devid;
++ unsigned int topint, coreint, sysint, refint, val;
++
++ topint = MV_CP_READ(MVCP_TOP_INT_STATUS);
++
++ if (topint == 0)
++ return IRQ_HANDLED;
++
++ MV_CP_WRITE(topint, MVCP_TOP_INT_STATUS);
++
++ if (ip_ver(cp) <= USB3_IP_VER_Z2) {
++ if (topint & MVCP_TOP_INT_SS_SYS) {
++ sysint = MV_CP_READ(MVCP_SS_SYS_INT);
++ MV_CP_WRITE(sysint, MVCP_SS_SYS_INT);
++
++ if (sysint & MVCP_SS_SYS_INT_DMA)
++ mvc2_dma_complete(cp);
++ }
++ } else {
++ if (topint & MVCP_TOP_INT_SS_EP)
++ mvc2_dma_complete(cp);
++
++ if (topint & MVCP_TOP_INT_SS_AXI) {
++ val = MV_CP_READ(SS_AXI_INT_STATUS_REG);
++ MV_CP_WRITE(val, SS_AXI_INT_STATUS_REG);
++ pr_warn("### %s %d: SS_AXI_INT_STATUS_REG = 0x%x\r\n",
++ __func__, __LINE__, val);
++ }
++ }
++
++ if (topint & MVCP_TOP_INT_SS_CORE) {
++ coreint = MV_CP_READ(MVCP_SS_CORE_INT);
++ MV_CP_WRITE(coreint, MVCP_SS_CORE_INT);
++
++ if (coreint & MVCP_SS_CORE_INT_HOT_RESET) {
++ pr_info("USB device: hot reset\n");
++ stop_activity(cp, cp->driver);
++ }
++
++ if (coreint & MVCP_SS_CORE_INT_SETUP)
++ mvc2_handle_setup(cp);
++
++ if (coreint & MVCP_SS_CORE_INT_LTSSM_CHG)
++ mvc2_process_link_change(cp);
++
++ /* We enabled error interrupt from Z3,
++ * need to check the error here.
++ */
++#if 0
++ if (ip_ver(cp) >= USB3_IP_VER_Z3) {
++ if (coreint & 0x3F)
++ pr_warn("### coreint = 0x%x\n", coreint);
++ }
++#endif
++ }
++
++ if (topint & MVCP_TOP_INT_REF) {
++ refint = MV_CP_READ(cp->reg->ref_int);
++ MV_CP_WRITE(refint, cp->reg->ref_int);
++
++ if (refint & MVCP_REF_INTEN_POWERON) {
++ /*
++ * Note, during the USB3 irq disabled, there may be
++ * may times plug/unplug,
++ * thus, the power on/off interrupt
++ * may co-exisit once enable the irq again.
++ * To avoid this, we need to check
++ * the VBUS of the final state.
++ * Refer to mvc2_usb2_connect.
++ */
++ if (mvc2_checkvbus(cp)) {
++ pr_info("USB device: connected\n");
++ usb_gadget_vbus_connect(&cp->gadget);
++ cp->status |= MVCP_STATUS_CONNECTED;
++ }
++ }
++
++ if (refint & MVCP_REF_INTEN_POWEROFF) {
++ /*
++ * Note, during the USB3 irq disabled, there may be
++ * may times plug/unplug,
++ * thus, the power on/off interrupt
++ * may co-exisit once enable the irq again.
++ * To avoid this, we need to check
++ * the VBUS of the final state.
++ * Refer to mvc2_usb2_connect.
++ */
++ if (!mvc2_checkvbus(cp)) {
++ pr_info("USB device: disconnected\n");
++ usb3_disconnect = true;
++ usb_gadget_vbus_disconnect(&cp->gadget);
++ cp->status &= ~MVCP_STATUS_CONNECTED;
++
++ cp->gadget.speed = USB_SPEED_UNKNOWN;
++
++ cp->status &= ~MVCP_STATUS_USB2;
++ glue.status = cp->status;
++ if (cp->work)
++ schedule_work(cp->work);
++ }
++ }
++
++ if (refint & MVCP_REF_INTEN_RESET) {
++ pr_info("USB device: warm reset\n");
++ /*
++ * The doneq write point will be set to 0 when warm/hot reset occurred.
++ * This will cause device abnormal, one example is CV test can't pass
++ * at this situation.
++ * Add dma reset here will set doneq write point to doneq start point.
++ */
++ stop_activity(cp, cp->driver);
++ }
++
++ if ((refint & MVCP_REF_INTEN_USB2_CNT) &&
++ (MV_CP_READ(cp->reg->ref_inten) &
++ MVCP_REF_INTEN_USB2_CNT)) {
++ usb3_disconnect = false;
++ stop_activity(cp, cp->driver);
++
++ cp->status |= MVCP_STATUS_USB2;
++ glue.status = cp->status;
++ if (cp->work)
++ schedule_work(cp->work);
++ }
++
++ if ((refint & MVCP_REF_INTEN_USB2_DISCNT) &&
++ (MV_CP_READ(cp->reg->ref_inten) &
++ MVCP_REF_INTEN_USB2_DISCNT)) {
++ usb3_disconnect = true;
++ if (mvc2_checkvbus(cp)) {
++ cp->status &= ~MVCP_STATUS_USB2;
++ glue.status = cp->status;
++ if (cp->work)
++ schedule_work(cp->work);
++ }
++ }
++
++ if (refint & MVCP_REF_INTEN_RESUME)
++ pr_info("USB device: resume\n");
++
++ if (refint & MVCP_REF_INTEN_SUSPEND)
++ pr_info("USB device: suspend\n");
++ }
++
++ if (topint & MVCP_TOP_INT_USB2)
++ return IRQ_NONE;
++
++ return IRQ_HANDLED;
++}
++
++int mvc2_gadget_init(struct mvc2 *cp)
++{
++ int ret, i, irq;
++ struct mvc2_ep *ep;
++
++ irq = platform_get_irq(to_platform_device(cp->dev), 0);
++ ret = request_irq(irq, mvc2_irq, IRQF_SHARED, "mvcp_usb3", cp);
++ if (ret) {
++ dev_err(cp->dev, "can't request irq %i, err: %d\n", irq, ret);
++ return -EINVAL;
++ }
++
++ /* initialize gadget structure */
++ cp->gadget.ops = &mvc2_ops;
++ cp->gadget.ep0 = &cp->eps[0].ep;
++ INIT_LIST_HEAD(&cp->gadget.ep_list);
++ cp->gadget.speed = USB_SPEED_UNKNOWN;
++ cp->gadget.max_speed = USB_SPEED_SUPER;
++ cp->gadget.is_otg = 0;
++ cp->gadget.name = driver_name;
++ cp->gadget.dev.parent = cp->dev;
++ cp->gadget.dev.dma_mask = cp->dev->dma_mask;
++ cp->irq = irq;
++ disable_irq(cp->irq);
++
++ for (i = 0; i < cp->epnum * 2; i++) {
++ ep = &cp->eps[i];
++ ep->ep.ops = &mvc2_ep_ops;
++ if (i > 1) {
++ INIT_LIST_HEAD(&ep->ep.ep_list);
++ list_add_tail(&ep->ep.ep_list, &cp->gadget.ep_list);
++ }
++ }
++
++ ret = usb_add_gadget_udc(cp->dev, &cp->gadget);
++ if (ret)
++ return ret;
++
++ return 0;
++}
++
++void mvc2_config_mac(struct mvc2 *cp)
++{
++ unsigned int val;
++
++ /* NOTE: this setting is related to reference clock,
++ * it indicates number of ref clock pulses for 100 ns,
++ * refer to Q&A adjust 100ns timer
++ */
++ val = MV_CP_READ(cp->reg->counter_pulse);
++ val &= ~(0xff << 24);
++ /* The formula is 2*100ns/(ref clcok speed) */
++ val |= (5 << 24);
++ MV_CP_WRITE(val, cp->reg->counter_pulse);
++
++ /* set min value for transceiver side U1 tx_t12_t10 to 600ns
++ * set max value for transceiver side U1 tx_t12_t11 to 900ns
++ * set LFPS Receive side t13 - t11 duration for U2 to 600us
++ * Receive side t13 - t11 duration for u3, set to 200us
++ */
++ val = MV_CP_READ(lfps_signal(cp, 1));
++ val &= ~(0x0f00000);
++ val |= (0x0900000);
++ MV_CP_WRITE(val, lfps_signal(cp, 1));
++#if 0
++ val = MV_CP_READ(lfps_signal(cp, 1));
++ val &= ~(0xf8);
++ val |= (0x30);
++ MV_CP_WRITE(val, lfps_signal(cp, 1));
++#endif
++ val = MV_CP_READ(lfps_signal(cp, 1));
++ val &= ~(0xff << 16);
++ val |= (0x2 << 16);
++ MV_CP_WRITE(val, lfps_signal(cp, 1));
++
++ if (ip_ver(cp) < USB3_IP_VER_Z2) {
++ /* IP version 2.04, 2.05 */
++ val = MV_CP_READ(lfps_signal(cp, 1));
++ val &= ~(0x7);
++ val |= (0x3);
++ MV_CP_WRITE(val, lfps_signal(cp, 1));
++
++ val = MV_CP_READ(lfps_signal(cp, 2));
++ val &= ~(0x7fff);
++ val |= (0x4e20);
++ MV_CP_WRITE(val, lfps_signal(cp, 2));
++
++ val = MV_CP_READ(lfps_signal(cp, 4));
++ val &= ~(0x7fff);
++ val |= (0x7d0);
++ MV_CP_WRITE(val, lfps_signal(cp, 4));
++
++ MV_CP_WRITE(0x1388000d, lfps_signal(cp, 5));
++ } else {
++ /* IP version 2.06 above */
++ /* Transmit side t11 - t10 duration for u2, max value set to 2ms */
++ val = MV_CP_READ(lfps_signal(cp, 2));
++ val &= ~0xf;
++ val |= 0x3;
++ MV_CP_WRITE(val, lfps_signal(cp, 2));
++
++ val = MV_CP_READ(lfps_signal(cp, 2));
++ val &= ~(0xff << 16);
++ val |= (0x6 << 16);
++ MV_CP_WRITE(val, lfps_signal(cp, 2));
++
++ /*Transmit side min value of t12 - t11 duration for u2, set to 100us */
++ val = MV_CP_READ(lfps_signal(cp, 3));
++ val &= ~0x7fff;
++ val |= 0x4e20;
++ MV_CP_WRITE(val, lfps_signal(cp, 3));
++
++ val = MV_CP_READ(lfps_signal(cp, 3));
++ val &= ~0x7fff;
++ val |= 0x7d0;
++ MV_CP_WRITE(val, lfps_signal(cp, 3));
++
++ /*
++ * if U2 is disabled set U1 rx t13 - t11 to 900ns, if U2 is enabled,
++ * set U1 rx t13-t11 to 500us
++ */
++ MV_CP_WRITE(0x13880009, lfps_signal(cp, 6));
++ }
++
++ /*reconfig LFPS length for PING to 70ns */
++ val = MV_CP_READ(MVCP_TIMER_TIMEOUT(2));
++ val &= ~0xff;
++ val |= 0x50;
++ MV_CP_WRITE(val, MVCP_TIMER_TIMEOUT(2));
++
++ val = MV_CP_READ(MVCP_LFPS_TX_CONFIG);
++ val &= ~0xf;
++ val |= 0x3;
++ MV_CP_WRITE(val, MVCP_LFPS_TX_CONFIG);
++
++#ifdef ELECTRICAL_TEST
++ /*set min_num_tx_ts1 to 131us, set min_num_tx_ts2 to 2us */
++ val = MV_CP_READ(MVCP_TX_TSI_NUM);
++ val |= 0x1000 << 16;
++ MV_CP_WRITE(val, MVCP_TX_TSI_NUM);
++#else
++ /* for normal usage, 1us ts1 would be enough */
++ val = MV_CP_READ(MVCP_TX_TSI_NUM);
++ val |= 0x8 << 16;
++ MV_CP_WRITE(val, MVCP_TX_TSI_NUM);
++#endif
++ val = MV_CP_READ(MVCP_START_STATE_DELAY);
++ val |= 0x3e << 16;
++ MV_CP_WRITE(val, MVCP_START_STATE_DELAY);
++
++ val = MV_CP_READ(MVCP_TX_TSI_NUM);
++ val |= 0xfff0;
++ MV_CP_WRITE(val, MVCP_TX_TSI_NUM);
++
++ if (u1u2_enabled()) {
++ val = MV_CP_READ(MVCP_LOWPOWER);
++ val &= ~0x3;
++ MV_CP_WRITE(val, MVCP_LOWPOWER);
++ }
++
++ val = MV_CP_READ(MVCP_COUNTER_DELAY_TX);
++ val |= 4 << 16;
++ MV_CP_WRITE(val, MVCP_COUNTER_DELAY_TX);
++
++ val = MV_CP_READ(MVCP_COUNTER_DELAY_TX);
++ if (ip_ver(cp) <= USB3_IP_VER_Z3) {
++ /*
++ * Jira NEZHA3-152/153
++ * Set the U2 Timeout Value bigger than the Max value(65024).
++ * This will make device never send LFPS.Exit, thus can
++ * avoid NEZHA3-152/153.
++ */
++ val |= (65024 + 100);
++ } else
++ val |= 5;
++ MV_CP_WRITE(val, MVCP_COUNTER_DELAY_TX);
++}
++
++/* Need to be included in ep lock protection */
++void reset_seqencenum(struct mvc2_ep *ep, int num, int in)
++{
++ struct mvc2 *cp = ep->cp;
++ unsigned int config;
++
++ config = MV_CP_READ(epcon(num, in));
++ config |= MVCP_EP_RESETSEQ;
++ MV_CP_WRITE(config, epcon(num, in));
++}
++
++void mvc2_hw_reset(struct mvc2 *cp)
++{
++ unsigned int val, timeout = 5000;
++
++ if (ip_ver(cp) < USB3_IP_VER_Z2) {
++ val = MV_CP_READ(cp->reg->global_control);
++ val |= MVCP_GLOBAL_CONTROL_SOFT_RESET;
++ MV_CP_WRITE(val, cp->reg->global_control);
++ /* wait controller reset complete */
++ while (timeout-- > 0) {
++ val = MV_CP_READ(cp->reg->global_control);
++ if (!(val & MVCP_GLOBAL_CONTROL_SOFT_RESET))
++ break;
++ cpu_relax();
++ }
++ } else {
++ val = MV_CP_READ(cp->reg->global_control);
++ val |= MVCP_GLOBAL_CONTROL_PHYRESET;
++ MV_CP_WRITE(val, cp->reg->global_control);
++
++ val = MV_CP_READ(MVCP_SOFTWARE_RESET);
++ val |= 1;
++ MV_CP_WRITE(val, MVCP_SOFTWARE_RESET);
++ while (timeout-- > 0) {
++ val = MV_CP_READ(MVCP_SOFTWARE_RESET);
++ if (!(val & 1))
++ break;
++ cpu_relax();
++ }
++ }
++
++ /* delay before mac config */
++ mdelay(100);
++ mvc2_config_mac(cp);
++}
++
++void mvc2_usb2_operation(struct mvc2 *cp, int op)
++{
++ unsigned int val;
++
++ if (op) {
++ val = MV_CP_READ(cp->reg->global_control);
++ val |= MVCP_GLOBAL_CONTROL_USB2_BUS_RESET;
++ MV_CP_WRITE(val, cp->reg->global_control);
++ udelay(10);
++ val &= ~MVCP_GLOBAL_CONTROL_USB2_BUS_RESET;
++ MV_CP_WRITE(val, cp->reg->global_control);
++ }
++}
++
++void mvc2_connect(struct mvc2 *cp, int is_on)
++{
++ unsigned int val;
++
++ if (is_on) {
++ val = MV_CP_READ(cp->reg->global_control);
++ /* bypass lowpower mode */
++ val |= MVCP_GLOBAL_CONTROL_SAFE |
++ MVCP_GLOBAL_CONTROL_SOFT_CONNECT;
++ MV_CP_WRITE(val, cp->reg->global_control);
++ } else {
++ val = MV_CP_READ(cp->reg->ref_inten);
++ val &= ~MVCP_REF_INTEN_USB2_CNT;
++ MV_CP_WRITE(val, cp->reg->ref_inten);
++
++ val = MV_CP_READ(cp->reg->global_control);
++ val &= ~MVCP_GLOBAL_CONTROL_SOFT_CONNECT;
++ MV_CP_WRITE(val, cp->reg->global_control);
++ }
++}
++
++static int mvc2_probe(struct platform_device *pdev)
++{
++ struct mvc2 *cp = NULL;
++ struct resource *res;
++ unsigned int ver;
++ int ret = 0;
++ void __iomem *base;
++ void __iomem *phy_base = NULL;
++ struct clk *clk;
++
++ /* disable U1/U2 mode, as part of the detection WA */
++ u1u2 = 0;
++
++ /* private struct */
++ cp = devm_kzalloc(&pdev->dev, sizeof(*cp), GFP_KERNEL);
++ if (!cp)
++ return -ENOMEM;
++
++ /* a38x specific initializations */
++ /* ungate unit clocks */
++ clk = devm_clk_get(&pdev->dev, NULL);
++ if (IS_ERR(clk)) {
++ ret = PTR_ERR(clk);
++ goto err_mem;
++ }
++
++ ret = clk_prepare_enable(clk);
++ if (ret < 0)
++ goto err_mem;
++
++ /* phy address for VBUS toggling */
++ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
++ if (res) {
++ phy_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
++ if (!phy_base) {
++ dev_err(&pdev->dev, "%s: register mapping failed\n", __func__);
++ ret = -ENXIO;
++ goto err_clk;
++ }
++ }
++
++ /* general USB3 device initializations */
++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
++ if (!res) {
++ dev_err(&pdev->dev, "missing mem resource\n");
++ ret = -ENODEV;
++ goto err_clk;
++ }
++
++ base = devm_ioremap_resource(&pdev->dev, res);
++ if (!base) {
++ dev_err(&pdev->dev, "%s: register mapping failed\n", __func__);
++ ret = -ENXIO;
++ goto err_clk;
++ }
++
++ ver = ioread32(base);
++ if (ver == 0) {
++ dev_err(&pdev->dev, "IP version error!\n");
++ ret = -ENXIO;
++ goto err_clk;
++ }
++
++ cp->mvc2_version = ver & 0xFFFF;
++
++ /* setup vbus gpio */
++ cp->vbus_pin = of_get_named_gpio(pdev->dev.of_node, "vbus-gpio", 0);
++ if ((cp->vbus_pin == -ENODEV) || (cp->vbus_pin == -EPROBE_DEFER)) {
++ ret = -EPROBE_DEFER;
++ goto err_clk;
++ }
++
++ if (cp->vbus_pin < 0)
++ cp->vbus_pin = -ENODEV;
++
++ if (gpio_is_valid(cp->vbus_pin)) {
++ cp->prev_vbus = 0;
++ if (!devm_gpio_request(&pdev->dev, cp->vbus_pin, "mvebu-u3d")) {
++ /* Use the 'any_context' version of function to allow
++ * requesting both direct GPIO interrupt (hardirq) and
++ * IO-expander's GPIO (nested interrupt)
++ */
++ ret = devm_request_any_context_irq(&pdev->dev,
++ gpio_to_irq(cp->vbus_pin),
++ mvc2_vbus_irq,
++ IRQ_TYPE_EDGE_BOTH | IRQF_ONESHOT,
++ "mvebu-u3d", cp);
++ if (ret < 0) {
++ cp->vbus_pin = -ENODEV;
++ dev_warn(&pdev->dev,
++ "failed to request vbus irq; "
++ "assuming always on\n");
++ }
++ }
++
++ /* setup work queue */
++ cp->qwork = create_singlethread_workqueue("mvc2_queue");
++ if (!cp->qwork) {
++ dev_err(&pdev->dev, "cannot create workqueue\n");
++ ret = -ENOMEM;
++ goto err_clk;
++ }
++
++ INIT_WORK(&cp->vbus_work, mvc2_vbus_work);
++ }
++
++ cp->reg = devm_kzalloc(&pdev->dev, sizeof(struct mvc2_register),
++ GFP_KERNEL);
++ if (!cp->reg) {
++ ret = -ENOMEM;
++ goto err_qwork;
++ }
++
++ cp->dev = &pdev->dev;
++ cp->base = base;
++ cp->epnum = 16;
++ if (phy_base)
++ cp->phy_base = phy_base;
++
++ if (cp->mvc2_version >= USB3_IP_VER_Z2) {
++ cp->reg->lfps_signal = 0x8;
++ cp->reg->counter_pulse = 0x20;
++ cp->reg->ref_int = 0x24;
++ cp->reg->ref_inten = 0x28;
++ cp->reg->global_control = 0x2c;
++ } else {
++ cp->reg->lfps_signal = 0x4;
++ cp->reg->counter_pulse = 0x18;
++ cp->reg->ref_int = 0x1c;
++ cp->reg->ref_inten = 0x20;
++ cp->reg->global_control = 0x24;
++ }
++
++ /* For Armada 3700, need to skip PHY HW reset */
++ if (of_device_is_compatible(pdev->dev.of_node,
++ "marvell,armada3700-u3d"))
++ cp->phy_hw_reset = false;
++ else
++ cp->phy_hw_reset = true;
++
++ /* Get comphy and init if there is */
++ cp->comphy = devm_of_phy_get(&pdev->dev, pdev->dev.of_node, "usb");
++ if (!IS_ERR(cp->comphy)) {
++ ret = phy_init(cp->comphy);
++ if (ret)
++ goto disable_phy;
++
++ ret = phy_power_on(cp->comphy);
++ if (ret) {
++ phy_exit(cp->comphy);
++ goto disable_phy;
++ }
++ }
++
++ spin_lock_init(&cp->lock);
++
++ /* init irq status */
++ irq_enabled = false;
++
++ cp->eps = kzalloc(cp->epnum * sizeof(struct mvc2_ep) * 2, GFP_KERNEL);
++ if (!cp->eps) {
++ ret = -ENOMEM;
++ goto err_qwork;
++ }
++
++ ret = mvc2_gadget_init(cp);
++ if (ret < 0)
++ goto err_alloc_eps;
++
++ eps_init(cp);
++
++ if (cp->phy_hw_reset)
++ mvc2_hw_reset(cp);
++
++ dev_set_drvdata(cp->dev, cp);
++ dev_info(cp->dev, "Detected ver %x from Marvell Central IP.\n", ver);
++
++ return 0;
++
++err_alloc_eps:
++ kfree(cp->eps);
++err_qwork:
++ if (cp->qwork)
++ destroy_workqueue(cp->qwork);
++disable_phy:
++ if (cp->comphy) {
++ phy_power_off(cp->comphy);
++ phy_exit(cp->comphy);
++ }
++err_clk:
++ clk_disable_unprepare(cp->clk);
++err_mem:
++ devm_kfree(&pdev->dev, cp);
++ return ret;
++}
++
++#ifdef CONFIG_PM
++static int mvc2_suspend(struct device *dev)
++{
++ struct mvc2 *cp = (struct mvc2 *)dev_get_drvdata(dev);
++
++ /* Stop the current activities */
++ if (cp->driver)
++ stop_activity(cp, cp->driver);
++
++ /* PHY exit if there is */
++ if (cp->comphy) {
++ phy_power_off(cp->comphy);
++ phy_exit(cp->comphy);
++ }
++
++ return 0;
++}
++
++static int mvc2_resume(struct device *dev)
++{
++ struct mvc2 *cp = (struct mvc2 *)dev_get_drvdata(dev);
++ int ret;
++
++ /* PHY init if there is */
++ if (cp->comphy) {
++ ret = phy_init(cp->comphy);
++ if (ret)
++ return ret;
++
++ ret = phy_power_on(cp->comphy);
++ if (ret) {
++ phy_power_off(cp->comphy);
++ phy_exit(cp->comphy);
++ return ret;
++ }
++ }
++
++ /*
++ * USB device will be started only in mvc2_complete, once all other
++ * required device drivers have been resumed.
++ * This is done to avoid a state which U3D driver is resumed too early
++ * before mass storage thread has been resumed, which will lead to USB
++ * transfer time out.
++ */
++
++ return 0;
++}
++
++/*
++ * The PM core executes complete() callbacks after it has executed
++ * the appropriate resume callbacks for all device drivers.
++ * This routine enables USB3 irq in device mode, later on the USB device will be started
++ * once it receives VBUS on interrupt, which starts USB device by enabling EP, and starts
++ * the USB transfer between host and device.
++ * Later on the USB mass storage function thread will be resumed, which will finish the
++ * USB transfer to let the USB device continue to work after resume.
++ * If start the USB device in "resume" operation, some device resuming after USB device
++ * resuming might take long time, which leads to USB transfer time out.
++ */
++static void mvc2_complete(struct device *dev)
++{
++ struct mvc2 *cp = (struct mvc2 *)dev_get_drvdata(dev);
++
++ /* Re-enable USB3 device irq */
++ mvc2_init_interrupt(cp);
++}
++
++static const struct dev_pm_ops mvc2_pm_ops = {
++ .suspend = mvc2_suspend,
++ .resume = mvc2_resume,
++ .complete = mvc2_complete
++};
++#endif
++
++static int mvc2_remove(struct platform_device *dev)
++{
++ struct mvc2 *cp;
++
++ cp = (struct mvc2 *)platform_get_drvdata(dev);
++ mvc2_connect(cp, 0);
++
++ if (cp->qwork) {
++ flush_workqueue(cp->qwork);
++ destroy_workqueue(cp->qwork);
++ }
++
++ /* PHY exit if there is */
++ if (cp->comphy) {
++ phy_power_off(cp->comphy);
++ phy_exit(cp->comphy);
++ }
++
++ return 0;
++}
++
++static void mvc2_shutdown(struct platform_device *dev)
++{
++}
++
++static const struct of_device_id mv_usb3_dt_match[] = {
++ {.compatible = "marvell,mvebu-u3d"},
++ {.compatible = "marvell,armada3700-u3d"},
++ {},
++};
++
++MODULE_DEVICE_TABLE(of, mv_usb3_dt_match);
++
++static struct platform_driver mvc2_driver = {
++ .probe = mvc2_probe,
++ .remove = mvc2_remove,
++ .shutdown = mvc2_shutdown,
++ .driver = {
++ .name = "mvebu-u3d",
++#ifdef CONFIG_OF
++ .of_match_table = of_match_ptr(mv_usb3_dt_match),
++#endif
++#ifdef CONFIG_PM
++ .pm = &mvc2_pm_ops,
++#endif
++ },
++};
++
++module_platform_driver(mvc2_driver);
++MODULE_ALIAS("platform:mvc2");
++MODULE_DESCRIPTION(DRIVER_DESC);
++MODULE_AUTHOR("Lei Wen <leiwen@marvell.com>");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/usb/gadget/udc/mvebu_u3d.h
+@@ -0,0 +1,572 @@
++/**
++ * core.h - Marvell Central IP usb3 core header
++ *
++ * Copyright (C) 2013 Marvell Inc.
++ *
++ * Authors: Lei Wen <leiwen@marvell.com>
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions, and the following disclaimer,
++ * without modification.
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in the
++ * documentation and/or other materials provided with the distribution.
++ * 3. The names of the above-listed copyright holders may not be used
++ * to endorse or promote products derived from this software without
++ * specific prior written permission.
++ *
++ * ALTERNATIVELY, this software may be distributed under the terms of the
++ * GNU General Public License ("GPL") version 2, as published by the Free
++ * Software Foundation.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
++ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
++ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
++ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
++ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
++ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++#ifndef __DRIVERS_USB_MVC2_H
++#define __DRIVERS_USB_MVC2_H
++
++#define USB3_IP_VER_Z2 0x215
++#define USB3_IP_VER_Z3 0x220
++#define USB3_IP_VER_A0 0x221
++
++#define MVCP_DEV_INFO 0x0
++
++#define MVCP_EP_COUNT 2
++
++#define MVCP_LFPS_SIGNAL(n) (0x8 + ((n-1) << 2))
++#define MVCP_COUNTER_PULSE 0x20
++#define MVCP_REF_INT 0x24
++#define MVCP_REF_INTEN 0x28
++ #define MVCP_REF_INTEN_USB2_CNT (1 << 31)
++ #define MVCP_REF_INTEN_USB2_DISCNT (1 << 30)
++ #define MVCP_REF_INTEN_RESUME (1 << 29)
++ #define MVCP_REF_INTEN_SUSPEND (1 << 28)
++ #define MVCP_REF_INTEN_RESET (1 << 27)
++ #define MVCP_REF_INTEN_POWERON (1 << 26)
++ #define MVCP_REF_INTEN_POWEROFF (1 << 25)
++
++#define MVCP_GLOBAL_CONTROL 0x2C
++ #define MVCP_GLOBAL_CONTROL_SOFT_CONNECT (1 << 31)
++ #define MVCP_GLOBAL_CONTROL_SOFT_RESET (1 << 30)
++ #define MVCP_GLOBAL_CONTROL_SAFE (1 << 29)
++ #define MVCP_GLOBAL_CONTROL_PHYRESET (1 << 28)
++ #define MVCP_GLOBAL_CONTROL_SOCACCESS (1 << 27)
++ #define MVCP_GLOBAL_CONTROL_SS_VBUS (1 << 3)
++ #define MVCP_GLOBAL_CONTROL_POWERPRESENT (1 << 2)
++ #define MVCP_GLOBAL_CONTROL_USB2_BUS_RESET (1 << 0)
++
++#define MVCP_SYSTEM_DEBUG 0x30
++
++#define MVCP_POWER_MANAGEMENT_DEVICE 0xC8
++#define MVCP_POWER_MANAGEMENT_SOC 0xCC
++#define MVCP_LOW_POWER_STATUS 0xD0
++#define MVCP_SOFTWARE_RESET 0xD4
++
++#define MVCP_TOP_INT_STATUS 0xD8
++ #define MVCP_TOP_INT_SS_EP (0x1<<6)
++ #define MVCP_TOP_INT_VBUS (0x1<<5)
++ #define MVCP_TOP_INT_PME (0x1<<4)
++ #define MVCP_TOP_INT_REF (0x1<<3)
++ #define MVCP_TOP_INT_SS_CORE (0x1<<2)
++ #define MVCP_TOP_INT_SS_SYS (0x1<<1)
++ #define MVCP_TOP_INT_SS_AXI (0x1<<1)
++ #define MVCP_TOP_INT_USB2 (0x1<<0)
++
++#define MVCP_TOP_INT_EN 0xDC
++
++#define MVCP_TIMER_TIMEOUT(n) (0x194 + ((n-1) << 2))
++#define MVCP_LFPS_TX_CONFIG 0x19C
++#define MVCP_LFPS_RX_CONFIG 0x1A0
++#define MVCP_LFPS_WR_TRESET 0x1A4
++#define MVCP_COUNTER_DELAY_TX 0x1A8
++#define MVCP_DEV_USB_ADDRESS 0x320
++#define MVCP_FUNCTION_WAKEUP 0x324
++
++#define MVCP_ENDPOINT_0_CONFIG 0x328
++ #define MVCP_ENDPOINT_0_CONFIG_CHG_STATE (1 << 7)
++
++#define MVCP_OUT_ENDPOINT_CONFIG_BASE 0x32C
++
++#define MVCP_IN_ENDPOINT_CONFIG_BASE 0x368
++ #define MVCP_EP_BURST(x) ((x + 1) << 24)
++ #define MVCP_EP_MAX_PKT(x) ((((x >> 8) & 0x7) \
++ << 8) | ((x & 0xff) \
++ << 16))
++ #define MVCP_EP_RESETSEQ (1 << 14)
++ #define MVCP_EP_STALL (1 << 11)
++ #define MVCP_EP_BULK_STREAM_EN (1 << 7)
++ #define MVCP_EP_ENABLE (1 << 6)
++ #define MVCP_EP_TYPE_INT (0x3 << 4)
++ #define MVCP_EP_TYPE_BLK (0x2 << 4)
++ #define MVCP_EP_TYPE_ISO (0x1 << 4)
++ #define MVCP_EP_TYPE_CTL (0x0 << 4)
++ #define MVCP_EP_TYPE(x) ((x & 0x3) << 4)
++ #define MVCP_EP_NUM(x) (x & 0xf)
++
++static inline unsigned int epcon(int n, int in)
++{
++ if (n == 0)
++ return MVCP_ENDPOINT_0_CONFIG;
++
++ if (in)
++ return MVCP_IN_ENDPOINT_CONFIG_BASE + ((n - 1) << 2);
++ else
++ return MVCP_OUT_ENDPOINT_CONFIG_BASE + ((n - 1) << 2);
++}
++
++#define MVCP_PHY 0x3A4
++ #define MVCP_PHY_LTSSM_MASK 0x1f
++ #define LTSSM_DISABLED 0x1
++ #define LTSSM_U0 0xc
++ #define LTSSM_U1 0xd
++ #define LTSSM_U2 0xe
++ #define LTSSM_U3 0xf
++
++#define MVCP_SS_CORE_INT 0x3B8
++ #define MVCP_SS_CORE_INT_SETUP (1 << 14)
++ #define MVCP_SS_CORE_INT_HOT_RESET (1 << 11)
++ #define MVCP_SS_CORE_INT_LTSSM_CHG (1 << 8)
++
++#define MVCP_SS_CORE_INTEN 0x3BC
++ #define MVCP_SS_CORE_INTEN_SETUP (1 << 14)
++ #define MVCP_SS_CORE_INTEN_HOT_RESET (1 << 11)
++ #define MVCP_SS_CORE_INTEN_LTSSM_CHG (1 << 8)
++
++/* IP_VERSION <= USB3_IP_VER_Z2 */
++#define EP_IN_BINTERVAL_REG_1_2_3 0x03C0
++#define EP_IN_BINTERVAL_REG_4_5_6_7 0x03C4
++#define EP_IN_BINTERVAL_REG_8_9_10_11 0x03C8
++#define EP_IN_BINTERVAL_REG_12_13_14_15 0x03CC
++#define EP_OUT_BINTERVAL_REG_1_2_3 0x03D0
++#define EP_OUT_BINTERVAL_REG_4_5_6_7 0x03D4
++#define EP_OUT_BINTERVAL_REG_8_9_10_11 0x03D8
++#define EP_OUT_BINTERVAL_REG_12_13_14_15 0x03DC
++
++#define MVCP_TX_TSI_NUM 0x3EC
++#define MVCP_START_STATE_DELAY 0x3F0
++
++#define MVCP_LOWPOWER 0x3F4
++ #define MVCP_LOWPOWER_U2_EN (1 << 3)
++ #define MVCP_LOWPOWER_U1_EN (1 << 2)
++ #define MVCP_LOWPOWER_U2_REJ (1 << 1)
++ #define MVCP_LOWPOWER_U1_REJ (1 << 0)
++
++#define MVCP_SETUP_DP_LOW 0x3F8
++#define MVCP_SETUP_DP_HIGH 0x3FC
++
++#define MVCP_SETUP_CONTROL 0x400
++ #define MVCP_SETUP_CONTROL_FETCHED (1 << 0)
++
++#define MVCP_DMA_GLOBAL_CONFIG 0x7D0
++ #define MVCP_DMA_GLOBAL_CONFIG_INTCLR (1 << 3)
++ #define MVCP_DMA_GLOBAL_CONFIG_RESETDONE (1 << 2)
++ #define MVCP_DMA_GLOBAL_CONFIG_RUN (1 << 1)
++ #define MVCP_DMA_GLOBAL_CONFIG_RESET (1 << 0)
++
++#define MVCP_BULK_STREAMING_ENABLE 0x7D4
++#define MVCP_EP_OUT_REC_STREAM_ID_BASE 0x7D8
++#define MVCP_EP_IN_REC_STREAM_ID_BASE 0x814
++static inline int streamid(int n, int i)
++{
++ if (n)
++ return MVCP_EP_IN_REC_STREAM_ID_BASE + ((n - 1) << 2);
++ else
++ return MVCP_EP_OUT_REC_STREAM_ID_BASE + ((n - 1) << 2);
++}
++
++#define MVCP_DMA_COMPLETE_SUCCESS 0x850
++#define MVCP_DMA_COMPLETE_ERROR 0x854
++#define MVCP_DMA_BD_FETCH_ERROR 0x858
++#define MVCP_DMA_BD_FETCH_ERROR_EN 0x85C
++#define MVCP_DMA_DATA_ERROR 0x860
++#define MVCP_DMA_DATA_ERROR_EN 0x864
++#define MVCP_DMA_ERROR_HANDLING 0x868
++#define MVCP_EP_OUT_RX_DMA_CONFIG_BASE 0x86C
++
++#define MVCP_EP_IN_TX_DMA_CONFIG_BASE 0x8AC
++ #define MVCP_EPDMA_START (1 << 6)
++
++static inline int ep_dma_config(int num, int dir)
++{
++ if (dir)
++ return MVCP_EP_IN_TX_DMA_CONFIG_BASE + (num << 2);
++ else
++ return MVCP_EP_OUT_RX_DMA_CONFIG_BASE + (num << 2);
++}
++
++#define MVCP_EP_OUT_RX_DMA_START_BASE 0x8EC
++#define MVCP_EP_IN_TX_DMA_START_BASE 0x92C
++
++static inline int ep_dma_addr(int num, int dir)
++{
++ if (dir)
++ return MVCP_EP_IN_TX_DMA_START_BASE + (num << 2);
++ else
++ return MVCP_EP_OUT_RX_DMA_START_BASE + (num << 2);
++}
++
++#define MVCP_DMA_SUSPEND 0x9EC
++#define MVCP_DMA_SUSPEND_DONE 0x9F0
++#define MVCP_DMA_HALT 0x9F4
++#define MVCP_DMA_HALT_DONE 0x9F8
++
++#define MVCP_SS_SYS_INT 0xA0C
++ #define MVCP_SS_AXI_DATA_ERR (1 << 29)
++ #define MVCP_SS_AXI_BDF_ERR (1 << 28)
++ #define MVCP_SS_DONEQ_FULL_ERR (1 << 27)
++ #define MVCP_SS_SYS_INT_DMA (1 << 25)
++
++#define MVCP_SS_SYS_INTEN 0xA10
++ #define MVCP_SS_SYS_INTEN_DMA (1 << 25)
++
++#define MVCP_DMA_STATE(n) (0xA24 + ((n-1) << 2))
++ #define MVCP_DMA_STATE_DBG_CACHE(x) ((x & 0x3) << 16)
++
++#define MVCP_SEGMENT_COUNTER(n) (0xA38 + ((n-1) << 2))
++
++#define MVCP_EP_IN_DONEQ_START_BASE 0xA78
++#define MVCP_EP_IN_DONEQ_END_BASE 0xAB8
++#define MVCP_EP_OUT_DONEQ_START_BASE 0xAF8
++#define MVCP_EP_OUT_DONEQ_END_BASE 0xB38
++#define MVCP_EP_IN_DONEQ_WRITE_BASE 0xB78
++#define MVCP_EP_IN_DONEQ_READ_BASE 0xBB8
++#define MVCP_EP_OUT_DONEQ_WRITE_BASE 0xBF8
++#define MVCP_EP_OUT_DONEQ_READ_BASE 0xC38
++#define MVCP_DONEQ_FULL_STATUS 0xC78
++
++static inline int ep_doneq_start(int num, int dir)
++{
++ if (dir)
++ return MVCP_EP_IN_DONEQ_START_BASE + (num << 2);
++ else
++ return MVCP_EP_OUT_DONEQ_START_BASE + (num << 2);
++}
++
++static inline int ep_doneq_end(int num, int dir)
++{
++ if (dir)
++ return MVCP_EP_IN_DONEQ_END_BASE + (num << 2);
++ else
++ return MVCP_EP_OUT_DONEQ_END_BASE + (num << 2);
++}
++
++static inline int ep_doneq_write(int num, int dir)
++{
++ if (dir)
++ return MVCP_EP_IN_DONEQ_WRITE_BASE + (num << 2);
++ else
++ return MVCP_EP_OUT_DONEQ_WRITE_BASE + (num << 2);
++}
++
++static inline int ep_doneq_read(int num, int dir)
++{
++ if (dir)
++ return MVCP_EP_IN_DONEQ_READ_BASE + (num << 2);
++ else
++ return MVCP_EP_OUT_DONEQ_READ_BASE + (num << 2);
++}
++
++#define MVCP_BD_MEM_IN 0x123C
++#define MVCP_PL_DEBUG(n) (0x1264 + ((n-1) << 2))
++#define MVCP_DMA_DBG_IN(n) (0x1274 + ((n-1) << 2))
++#define MVCP_DMA_DBG_OUT(n) (0x127C + ((n-1) << 2))
++
++#define MVCP_DMA_ENABLE 0x1284
++
++/* IP_VERSION >= 0X218 */
++#define SS_IN_DMA_CONTROL_REG(x) (0x1300 + 4 * (x))
++#define SS_OUT_DMA_CONTROL_REG(x) (0x1340 + 4 * (x))
++#define DMA_START (1 << 0)
++#define DMA_HALT (1 << 1)
++#define DMA_SUSPEND (1 << 2)
++#define DONEQ_CONFIG (1 << 3)
++#define ABORT_REQ (1 << 4)
++#define ABORT_DONE (1 << 5)
++
++#define SS_IN_EP_INT_STATUS_REG(x) (0x1380 + 4 * (x))
++#define SS_OUT_EP_INT_STATUS_REG(x) (0x13C0 + 4 * (x))
++#define SS_IN_EP_INT_ENABLE_REG(x) (0x1400 + 4 * (x))
++#define SS_OUT_EP_INT_ENABLE_REG(x) (0x1440 + 4 * (x))
++
++#define COMPLETION_SUCCESS (1 << 0)
++#define COMPLETION_WITH_ERR (1 << 1)
++#define BD_FETCH_ERROR (1 << 2)
++#define DMA_DATA_ERROR (1 << 3)
++#define DONEQ_FULL (1 << 4)
++#define DMA_SUSPEND_DONE (1 << 5)
++#define DMA_HALT_DONE (1 << 6)
++#define PRIME_REC (1 << 16)
++#define HIMD_REC (1 << 17)
++#define STREAM_REJ (1 << 18)
++#define HOST_FLOW_CTRL (1 << 19)
++
++#define SS_EP_TOP_INT_STATUS_REG 0x1480
++#define SS_EP_TOP_INT_ENABLE_REG 0x1484
++#define SS_AXI_INT_STATUS_REG 0x1488
++#define SS_AXI_INT_ENABLE_REG 0x148C
++
++#define EP_IN_BINTERVAL_REG(x) (0x1490 + 4 * ((x)-1))
++#define EP_OUT_BINTERVAL_REG(x) (0x14CC + 4 * ((x)-1))
++/* END IP_VERSION >= 0X218 */
++
++struct mvc2_ep;
++
++struct mvc2_req {
++ struct usb_request req;
++ int bd_total;
++ struct bd *bd;
++ struct list_head queue;
++};
++
++enum mvc2_dev_state {
++ MVCP_DEFAULT_STATE,
++ MVCP_ADDRESS_STATE,
++ MVCP_CONFIGURED_STATE,
++};
++
++struct mvc2_register {
++ unsigned int lfps_signal;
++ unsigned int counter_pulse;
++ unsigned int ref_int;
++ unsigned int ref_inten;
++ unsigned int global_control;
++};
++
++struct mvc2 {
++ struct usb_gadget gadget;
++ struct usb_gadget_driver *driver;
++ struct device *dev;
++ struct clk *clk;
++ struct usb_phy *phy;
++ struct phy *comphy;
++ int irq;
++ void __iomem *base;
++ void __iomem *win_base;
++ void __iomem *phy_base;
++ #define MVCP_STATUS_USB2 (1 << 10)
++ #define MVCP_STATUS_CONNECTED (1 << 9)
++ #define MVCP_STATUS_TEST_MASK (0x7 << 5)
++ #define MVCP_STATUS_TEST(x) (((x) & 0x7) << 5)
++ #define MVCP_STATUS_U3 (1 << 4)
++ #define MVCP_STATUS_U2 (1 << 3)
++ #define MVCP_STATUS_U1 (1 << 2)
++ #define MVCP_STATUS_U0 (1 << 1)
++ #define MVCP_STATUS_POWER_MASK (0xf << 1)
++ #define MVCP_STATUS_SELF_POWERED (1 << 0)
++ unsigned int status;
++ enum mvc2_dev_state dev_state;
++ spinlock_t lock;
++
++ struct mvc2_req ep0_req;
++ int ep0_dir;
++ void *setup_buf;
++
++ struct dma_pool *bd_pool;
++ struct mvc2_ep *eps;
++
++ unsigned int epnum;
++ unsigned int dma_status;
++
++ struct work_struct *work;
++ struct pm_qos_request qos_idle;
++ s32 lpm_qos;
++
++ unsigned int isoch_delay;
++ unsigned int u1sel;
++ unsigned int u1pel;
++ unsigned int u2sel;
++ unsigned int u2pel;
++ struct mvc2_register *reg;
++ unsigned int mvc2_version;
++ int vbus_pin;
++ int prev_vbus;
++ struct work_struct vbus_work;
++ struct workqueue_struct *qwork;
++ /* Flags for HW reset. false: no need reset; true: need reset */
++ bool phy_hw_reset;
++};
++
++extern void mvc2_usb2_connect(void);
++extern void mvc2_usb2_disconnect(void);
++extern int eps_init(struct mvc2 *cp);
++extern void reset_seqencenum(struct mvc2_ep *ep, int num, int in);
++extern void mvc2_config_mac(struct mvc2 *cp);
++extern void mvc2_hw_reset(struct mvc2 *cp);
++extern int mvc2_std_request(struct mvc2 *cp, struct usb_ctrlrequest *r,
++ bool *delegate);
++extern int mvc2_gadget_init(struct mvc2 *cp);
++extern void mvc2_usb2_operation(struct mvc2 *cp, int op);
++extern void mvc2_connect(struct mvc2 *cp, int is_on);
++extern unsigned int u1u2_enabled(void);
++
++#define BD_DMA_BOUNDARY 4096
++#define BD_ADDR_ALIGN 4
++/*
++ * Although one BD could transfer 64k-1 bytes data,
++ * for calculation efficiency, we short it for 32k
++ */
++#define BD_SEGMENT_SHIFT (15)
++#define BD_MAX_SIZE (1 << BD_SEGMENT_SHIFT)
++struct bd {
++ #define BD_BUF_RDY (1 << 31)
++ #define BD_INT_EN (1 << 30)
++ #define BD_NXT_RDY (1 << 29)
++ #define BD_NXT_PTR_JUMP (1 << 28)
++ #define BD_FLUSH_BIT (1 << 27)
++ #define BD_ABORT_BIT (1 << 26)
++ #define BD_ZLP (1 << 25)
++ #define BD_CHAIN_BIT (1 << 24)
++ #define BD_ENCODED_STREAM_ID(x) ((x & 0xff) << 16)
++ #define BD_BUF_SZ(x) (x & 0xffff)
++ unsigned int cmd;
++ unsigned int buf;
++
++ /* This field should be next bd's physical addr */
++ unsigned int phys_next;
++#define BD_STREAM_ID(x) ((x & 0xffff) << 16)
++#define BD_STREAM_LEN(x) (x & 0xffff)
++ unsigned int stream;
++};
++
++/*
++ * Since each BD would transfer BD_MAX_SIZE, so for each endpoint, it allow to
++ * hold (MAX_QUEUE_SLOT-1)*BD_MAX_SIZE in pending status to be transferred
++ */
++#define MAX_QUEUE_SLOT 256
++struct doneq {
++ unsigned int addr;
++ #define DONE_LEN(x) ((x >> 16) & 0xffff)
++ #define DONE_AXI_ERROR (1 << 4)
++ #define DONE_SHORT_PKT (1 << 3)
++ #define DONE_FLUSH (1 << 2)
++ #define DONE_ABORT (1 << 1)
++ #define DONE_CYCLE (1 << 0)
++ unsigned int status;
++};
++
++#define MAXNAME 14
++/*
++ * For one ep, it would pending for several req,
++ * while hardware would handle only one req for one time.
++ * Other req is pending over queue list.
++ *
++ * For the transferring req, we would separate it into several bd to info
++ * hw to transfer. And dma engine would auto feature those chained bd, and
++ * send them out. When each bd finish, its BD_BUF_RDY flag would be cleared.
++ * If BD_INT_EN flag is set, interrupt would be raised correspondingly.
++ * ep --
++ * \--req->req->
++ * \ \--bd->bd->null
++ * \--bd->bd->null
++ */
++struct mvc2_ep {
++ struct usb_ep ep;
++ struct mvc2 *cp;
++
++ #define MV_CP_EP_TRANSERING (1 << 8)
++ #define MV_CP_EP_STALL (1 << 7)
++ #define MV_CP_EP_BULK_STREAM (1 << 6)
++ #define MV_CP_EP_WEDGE (1 << 5)
++ #define MV_CP_EP_DIRIN (1 << 4)
++ #define MV_CP_EP_NUM_MASK (0xf)
++ #define MV_CP_EP_NUM(x) (x & MV_CP_EP_NUM_MASK)
++ unsigned int state;
++
++ /*
++ * Actually in current hw solution,
++ * TransferQ and DoneQ size should be equal
++ */
++ /*
++ * TransferQ:
++ * doneq_cur bd_cur
++ * | |
++ * v v
++ * |-------0============------|
++ * ^
++ * |
++ * not ready bd
++ *
++ * In above diagram, "-" shows current available bd could be allocated,
++ * while "=" shows bd cannot be touched by the sw.
++ * When we need to do enqueue operation, we need to allocate bd
++ * from "-" pool.
++ *
++ * Note: we need ensure at least one bd in the ring as not ready
++ */
++ struct bd *bd_ring;
++ dma_addr_t bd_ring_phys;
++ unsigned int bd_cur;
++ unsigned int bd_sz;
++
++ /* DoneQ */
++ struct doneq *doneq_start;
++ dma_addr_t doneq_start_phys;
++ unsigned int doneq_cur;
++
++ char name[MAXNAME];
++ struct list_head queue;
++ struct list_head wait, tmp;
++ unsigned int dir;
++ unsigned stopped:1,
++ wedge:1,
++ ep_type:2,
++ ep_num:8;
++ unsigned int left_bds;
++ /* Lock to keep queue is safe operated */
++ spinlock_t lock;
++};
++
++#define EPBIT(epnum, dir) ({unsigned int tmp; \
++ tmp = (dir) ? (0x10000 << epnum) : (1 << epnum); tmp; })
++
++#define MV_CP_READ(reg) ({unsigned int val; \
++ val = ioread32(cp->base + reg); val; })
++#define MV_CP_WRITE(val, reg) ({iowrite32(val, cp->base + reg); })
++
++struct mvc2;
++
++static inline unsigned int ip_ver(struct mvc2 *cp)
++{
++ return cp->mvc2_version;
++}
++
++static inline unsigned int lfps_signal(struct mvc2 *cp, unsigned int n)
++{
++ return cp->reg->lfps_signal + ((n - 1) << 2);
++}
++
++/*
++ * struct mvc2_glue - glue structure to combine 2.0/3.0 udc together
++ * @u20: 2.0 driver udc
++ * @u30: 3.0 driver udc
++ * @usb2_connect: whether usb2.0 is in connection
++ * @connect_num: how many usb3 has been tried
++ */
++struct mvc2_glue {
++ struct usb_udc *u20;
++ struct usb_udc *u30;
++
++ int usb2_connect;
++ unsigned int status;
++};
++
++extern struct mvc2_glue glue;
++extern bool usb3_disconnect;
++
++int mvc2_checkvbus(struct mvc2 *cp);
++void mvc2_handle_setup(struct mvc2 *cp);
++int mv_udc_register_status_notify(struct notifier_block *nb);
++
++#endif
+--- a/include/linux/usb/gadget.h
++++ b/include/linux/usb/gadget.h
+@@ -863,4 +863,25 @@ extern void usb_ep_autoconfig_release(st
+
+ extern void usb_ep_autoconfig_reset(struct usb_gadget *);
+
++
++/**
++ * struct usb_udc - describes one usb device controller
++ * @driver - the gadget driver pointer. For use by the class code
++ * @dev - the child device to the actual controller
++ * @gadget - the gadget. For use by the class code
++ * @list - for use by the udc class driver
++ *
++ * This represents the internal data structure which is used by the UDC-class
++ * to hold information about udc driver and gadget together.
++ */
++struct usb_udc {
++ struct usb_gadget_driver *driver;
++ struct usb_gadget *gadget;
++ struct device dev;
++ struct list_head list;
++ bool vbus;
++};
++
++extern struct usb_udc *udc_detect(struct list_head *udc_list, struct usb_gadget_driver *driver);
++
+ #endif /* __LINUX_USB_GADGET_H */
diff --git a/target/linux/mvebu/patches-4.14/541-arm64-usb-device-dts.patch b/target/linux/mvebu/patches-4.14/541-arm64-usb-device-dts.patch
new file mode 100644
index 0000000000..55625b9bed
--- /dev/null
+++ b/target/linux/mvebu/patches-4.14/541-arm64-usb-device-dts.patch
@@ -0,0 +1,25 @@
+--- a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
++++ b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi
+@@ -299,6 +299,22 @@
+ status = "disabled";
+ };
+
++ u3d@50000 {
++ compatible = "marvell,armada3700-u3d";
++ /* 0: 0x50000: USB 3.0 Device port 0: DEV_INFO_REG(0:15 - version_id) */
++ reg = <0x50000 0x2000>;
++ interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&sb_periph_clk 12>;
++ status = "okay";
++ };
++ udc@54100 {
++ compatible = "marvell,mv-udc";
++ reg = <0x54100 0x2000>;
++ interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&sb_periph_clk 12>;
++ status = "okay";
++ };
++
+ xor@60900 {
+ compatible = "marvell,armada-3700-xor";
+ reg = <0x60900 0x100>,
diff --git a/target/linux/mvebu/patches-4.14/542-arm64-usb-gadget-ether-fix-mac.patch b/target/linux/mvebu/patches-4.14/542-arm64-usb-gadget-ether-fix-mac.patch
new file mode 100644
index 0000000000..e497c6a31c
--- /dev/null
+++ b/target/linux/mvebu/patches-4.14/542-arm64-usb-gadget-ether-fix-mac.patch
@@ -0,0 +1,51 @@
+--- a/drivers/usb/gadget/legacy/ether.c
++++ b/drivers/usb/gadget/legacy/ether.c
+@@ -24,6 +24,8 @@
+ #endif
+
+ #include "u_ether.h"
++#include <linux/etherdevice.h>
++#include <linux/mtd/mtd.h>
+
+
+ /*
+@@ -313,6 +315,11 @@ static int eth_bind(struct usb_composite
+ struct f_gether_opts *geth_opts = NULL;
+ struct net_device *net;
+ int status;
++ struct mtd_info *mtd;
++ int mtd_mac_ok = 1;
++ size_t retlen;
++ u8 mac[ETH_ALEN];
++ char mac_addr[ETH_ALEN];
+
+ /* set up main config label and device descriptor */
+ if (use_eem) {
+@@ -361,6 +368,27 @@ static int eth_bind(struct usb_composite
+ }
+
+ gether_set_qmult(net, qmult);
++
++ mtd = get_mtd_device_nm("art");
++ if (IS_ERR(mtd)){
++ mtd_mac_ok = 0;
++ } else {
++ mtd_read(mtd, 0, 6, &retlen, mac);
++ if (!is_valid_ether_addr(mac))
++ mtd_mac_ok = 0;
++ }
++
++ if(mtd_mac_ok){
++ mac[0] -= 2;
++ sprintf(mac_addr, "%x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
++ if (!gether_set_host_addr(net, mac_addr))
++ pr_info("using host ethernet address from mtd: %s", mac_addr);
++ mac[0] -= 4;
++ sprintf(mac_addr, "%x:%x:%x:%x:%x:%x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
++ if (!gether_set_dev_addr(net, mac_addr))
++ pr_info("using self ethernet address from mtd: %s", mac_addr);
++ }
++
+ if (!gether_set_host_addr(net, host_addr))
+ pr_info("using host ethernet address: %s", host_addr);
+ if (!gether_set_dev_addr(net, dev_addr))
diff --git a/target/linux/mvebu/patches-4.14/550_support_EC20_4g.patch b/target/linux/mvebu/patches-4.14/550_support_EC20_4g.patch
new file mode 100644
index 0000000000..f2bce020d3
--- /dev/null
+++ b/target/linux/mvebu/patches-4.14/550_support_EC20_4g.patch
@@ -0,0 +1,241 @@
+--- a/drivers/usb/serial/option.c 2020-03-26 15:01:47.986237134 +0800
++++ b/drivers/usb/serial/option.c 2020-03-26 16:51:56.912568912 +0800
+@@ -571,6 +571,18 @@
+
+
+ static const struct usb_device_id option_ids[] = {
++#if 1 //Added by Quectel
++ { USB_DEVICE(0x05C6, 0x9090) }, /* Quectel UC15 */
++ { USB_DEVICE(0x05C6, 0x9003) }, /* Quectel UC20 */
++ { USB_DEVICE(0x2C7C, 0x0125) }, /* Quectel EC25 */
++ { USB_DEVICE(0x2C7C, 0x0121) }, /* Quectel EC21 */
++ { USB_DEVICE(0x05C6, 0x9215) }, /* Quectel EC20 */
++ { USB_DEVICE(0x2C7C, 0x0191) }, /* Quectel EG91 */
++ { USB_DEVICE(0x2C7C, 0x0195) }, /* Quectel EG95 */
++ { USB_DEVICE(0x2C7C, 0x0306) }, /* Quectel EG06/EP06/EM06 */
++ { USB_DEVICE(0x2C7C, 0x0296) }, /* Quectel BG96 */
++ { USB_DEVICE(0x2C7C, 0x0435) }, /* Quectel AG35 */
++#endif
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) },
+ { USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_LIGHT) },
+@@ -2033,6 +2045,9 @@
+ #ifdef CONFIG_PM
+ .suspend = usb_wwan_suspend,
+ .resume = usb_wwan_resume,
++#if 1 //Added by Quectel
++ .reset_resume = usb_wwan_resume,
++#endif
+ #endif
+ };
+
+@@ -2045,6 +2060,24 @@
+ static int option_probe(struct usb_serial *serial,
+ const struct usb_device_id *id)
+ {
++#if 1 //Added by Quectel
++//Quectel UC20's interface 4 can be used as USB network device
++ if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6)
++ && serial->dev->descriptor.idProduct == cpu_to_le16(0x9003)
++ && serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4)
++ return -ENODEV;
++
++//Quectel EC20's interface 4 can be used as USB network device
++ if (serial->dev->descriptor.idVendor == cpu_to_le16(0x05C6)
++ && serial->dev->descriptor.idProduct == cpu_to_le16(0x9215)
++ && serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4)
++
++//Quectel EC25&EC21&EG91&EG95&EG06&EP06&EM06&BG96/AG35's interface 4 can be used as USB network device
++ if (serial->dev->descriptor.idVendor == cpu_to_le16(0x2C7C)
++ && serial->interface->cur_altsetting->desc.bInterfaceNumber >= 4)
++ return -ENODEV;
++#endif
++
+ struct usb_interface_descriptor *iface_desc =
+ &serial->interface->cur_altsetting->desc;
+ struct usb_device_descriptor *dev_desc = &serial->dev->descriptor;
+--- a/drivers/usb/serial/qcserial.c 2020-03-26 15:01:47.982237177 +0800
++++ b/drivers/usb/serial/qcserial.c 2020-03-26 16:51:56.908568950 +0800
+@@ -92,7 +92,6 @@
+ {USB_DEVICE(0x03f0, 0x241d)}, /* HP Gobi 2000 QDL device (VP412) */
+ {USB_DEVICE(0x03f0, 0x251d)}, /* HP Gobi 2000 Modem device (VP412) */
+ {USB_DEVICE(0x05c6, 0x9214)}, /* Acer Gobi 2000 QDL device (VP413) */
+- {USB_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */
+ {USB_DEVICE(0x05c6, 0x9264)}, /* Asus Gobi 2000 QDL device (VR305) */
+ {USB_DEVICE(0x05c6, 0x9265)}, /* Asus Gobi 2000 Modem device (VR305) */
+ {USB_DEVICE(0x05c6, 0x9234)}, /* Top Global Gobi 2000 QDL device (VR306) */
+--- a/drivers/usb/serial/usb_wwan.c 2020-03-26 15:01:47.986237134 +0800
++++ b/drivers/usb/serial/usb_wwan.c 2020-03-26 16:51:56.916568875 +0800
+@@ -502,6 +502,20 @@
+ usb_sndbulkpipe(serial->dev, endpoint) | dir,
+ buf, len, callback, ctx);
+
++#if 1 //Added by Quectel for zero packet
++ if (dir == USB_DIR_OUT) {
++ struct usb_device_descriptor *desc = &serial->dev->descriptor;
++ if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9090))
++ urb->transfer_flags |= URB_ZERO_PACKET;
++ if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9003))
++ urb->transfer_flags |= URB_ZERO_PACKET;
++ if (desc->idVendor == cpu_to_le16(0x05C6) && desc->idProduct == cpu_to_le16(0x9215))
++ urb->transfer_flags |= URB_ZERO_PACKET;
++ if (desc->idVendor == cpu_to_le16(0x2C7C))
++ urb->transfer_flags |= URB_ZERO_PACKET;
++ }
++#endif
++
+ return urb;
+ }
+
+--- a/drivers/net/usb/qmi_wwan.c 2020-03-27 12:03:55.799880288 +0800
++++ b/drivers/net/usb/qmi_wwan.c 2020-03-27 12:17:10.241977086 +0800
+@@ -23,6 +23,71 @@
+ #include <linux/usb/usbnet.h>
+ #include <linux/usb/cdc-wdm.h>
+
++#if 1 //Added by Quectel
++#include <linux/etherdevice.h>
++struct sk_buff *qmi_wwan_tx_fixup(struct usbnet *dev, struct sk_buff *skb, gfp_t flags)
++{
++ if (dev->udev->descriptor.idVendor != cpu_to_le16(0x2C7C))
++ return skb;
++ // Skip Ethernet header from message
++ if (skb_pull(skb, ETH_HLEN)) {
++ return skb;
++ } else {
++ dev_err(&dev->intf->dev, "Packet Dropped ");
++ }
++
++ // Filter the packet out, release it
++ dev_kfree_skb_any(skb);
++ return NULL;
++}
++
++#include <linux/version.h>
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,9,1 ))
++static int qmi_wwan_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
++{
++ __be16 proto;
++ if (dev->udev->descriptor.idVendor != cpu_to_le16(0x2C7C))
++ return 1;
++ /* This check is no longer done by usbnet */
++ if (skb->len < dev->net->hard_header_len)
++ return 0;
++ switch (skb->data[0] & 0xf0) {
++ case 0x40:
++ proto = htons(ETH_P_IP);
++ break;
++ case 0x60:
++ proto = htons(ETH_P_IPV6);
++ break;
++ case 0x00:
++ if (is_multicast_ether_addr(skb->data))
++ return 1;
++ /* possibly bogus destination - rewrite just in case */
++ skb_reset_mac_header(skb);
++ goto fix_dest;
++ default:
++ /* pass along other packets without modifications */
++ return 1;
++ }
++
++ if (skb_headroom(skb) < ETH_HLEN)
++ return 0;
++ skb_push(skb, ETH_HLEN);
++ skb_reset_mac_header(skb);
++ eth_hdr(skb)->h_proto = proto;
++ memset(eth_hdr(skb)->h_source, 0, ETH_ALEN);
++ fix_dest:
++ memcpy(eth_hdr(skb)->h_dest, dev->net->dev_addr, ETH_ALEN);
++ return 1;
++}
++
++/* very simplistic detection of IPv4 or IPv6 headers */
++static bool possibly_iphdr(const char *data)
++{
++ return (data[0] & 0xd0) == 0x40;
++}
++#endif
++#endif
++
+ /* This driver supports wwan (3G/LTE/?) devices using a vendor
+ * specific management protocol called Qualcomm MSM Interface (QMI) -
+ * in addition to the more common AT commands over serial interface
+@@ -750,6 +815,28 @@
+ }
+ dev->net->netdev_ops = &qmi_wwan_netdev_ops;
+ dev->net->sysfs_groups[0] = &qmi_wwan_sysfs_attr_group;
++#if 1 //Added by Quectel
++ if (dev->udev->descriptor.idVendor == cpu_to_le16(0x2C7C)) {
++ //dev_info(&intf->dev, "Quectel EC25&EC21&EG91&EG95&EG06&EP06&EM06&BG96&AG35 work on RawIP mode\n");
++ dev->net->flags |= IFF_NOARP;
++#if (LINUX_VERSION_CODE < KERNEL_VERSION( 3,9,1 ))
++ /* make MAC addr easily distinguishable from an IP header */
++ if (possibly_iphdr(dev->net->dev_addr)) {
++ dev->net->dev_addr[0] |= 0x02; /* set local assignment bit */
++ dev->net->dev_addr[0] &= 0xbf; /* clear "IP" bit */
++ }
++#endif
++ usb_control_msg(
++ interface_to_usbdev(intf),
++ usb_sndctrlpipe(interface_to_usbdev(intf), 0),
++ 0x22, //USB_CDC_REQ_SET_CONTROL_LINE_STATE
++ 0x21, //USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
++ 1, //active CDC DTR
++ intf->cur_altsetting->desc.bInterfaceNumber,
++ NULL, 0, 100);
++ }
++#endif
++
+ err:
+ return status;
+ }
+@@ -841,6 +928,10 @@
+ .unbind = qmi_wwan_unbind,
+ .manage_power = qmi_wwan_manage_power,
+ .rx_fixup = qmi_wwan_rx_fixup,
++#if 1 //Added by Quectel
++ .tx_fixup = qmi_wwan_tx_fixup,
++ .rx_fixup = qmi_wwan_rx_fixup,
++#endif
+ };
+
+ static const struct driver_info qmi_wwan_info_quirk_dtr = {
+@@ -893,6 +984,29 @@
+ .driver_info = (unsigned long)&qmi_wwan_info_quirk_quectel_dyncfg
+
+ static const struct usb_device_id products[] = {
++#if 1 //Added by Quectel
++#ifndef QMI_FIXED_INTF
++ /* map QMI/wwan function by a fixed interface number */
++ #define QMI_FIXED_INTF(vend, prod, num) \
++ .match_flags = USB_DEVICE_ID_MATCH_DEVICE |
++ USB_DEVICE_ID_MATCH_INT_INFO, \
++ .idVendor = vend, \
++ .idProduct = prod, \
++ .bInterfaceClass = 0xff, \
++ .bInterfaceSubClass = 0xff, \
++ .bInterfaceProtocol = 0xff, \
++ .driver_info = (unsigned long)&qmi_wwan_force_int##num,
++#endif
++ { QMI_FIXED_INTF(0x05C6, 0x9003, 4) }, /* Quectel UC20 */
++ { QMI_FIXED_INTF(0x2C7C, 0x0125, 4) }, /* Quectel EC25 */
++ { QMI_FIXED_INTF(0x2C7C, 0x0121, 4) }, /* Quectel EC21 */
++ { QMI_FIXED_INTF(0x05C6, 0x9215, 4) }, /* Quectel EC20 */
++ { QMI_FIXED_INTF(0x2C7C, 0x0191, 4) }, /* Quectel EG91 */
++ { QMI_FIXED_INTF(0x2C7C, 0x0195, 4) }, /* Quectel EG95 */
++ { QMI_FIXED_INTF(0x2C7C, 0x0306, 4) }, /* Quectel EG06/EP06/EM06 */
++ { QMI_FIXED_INTF(0x2C7C, 0x0296, 4) }, /* Quectel BG96 */
++ { QMI_FIXED_INTF(0x2C7C, 0x0435, 4) }, /* Quectel AG35 */
++ #endif
+ /* 1. CDC ECM like devices match on the control interface */
+ { /* Huawei E392, E398 and possibly others sharing both device id and more... */
+ USB_VENDOR_AND_INTERFACE_INFO(HUAWEI_VENDOR_ID, USB_CLASS_VENDOR_SPEC, 1, 9),
+@@ -1323,7 +1437,6 @@
+ {QMI_GOBI_DEVICE(0x05c6, 0x9225)}, /* Sony Gobi 2000 Modem device (N0279, VU730) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9245)}, /* Samsung Gobi 2000 Modem device (VL176) */
+ {QMI_GOBI_DEVICE(0x03f0, 0x251d)}, /* HP Gobi 2000 Modem device (VP412) */
+- {QMI_GOBI_DEVICE(0x05c6, 0x9215)}, /* Acer Gobi 2000 Modem device (VP413) */
+ {QMI_FIXED_INTF(0x05c6, 0x9215, 4)}, /* Quectel EC20 Mini PCIe */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9265)}, /* Asus Gobi 2000 Modem device (VR305) */
+ {QMI_GOBI_DEVICE(0x05c6, 0x9235)}, /* Top Global Gobi 2000 Modem device (VR306) */
diff --git a/target/linux/ramips/base-files/etc/board.d/01_leds b/target/linux/ramips/base-files/etc/board.d/01_leds
index 5c005db0c1..1add307bff 100755
--- a/target/linux/ramips/base-files/etc/board.d/01_leds
+++ b/target/linux/ramips/base-files/etc/board.d/01_leds
@@ -189,6 +189,9 @@ gl-mt300n-v2)
set_wifi_led "$boardname:red:wlan"
ucidef_set_led_switch "wan" "wan" "$boardname:green:wan" "switch0" "0x1"
;;
+microuter-n300)
+ set_wifi_led "$boardname:white:wlan" "ra0"
+ ;;
hc5661|\
hc5661a)
ucidef_set_led_netdev "internet" "internet" "$boardname:blue:internet" "eth0.2"
diff --git a/target/linux/ramips/base-files/etc/board.d/02_network b/target/linux/ramips/base-files/etc/board.d/02_network
index 8ca1831afe..eb068e47ef 100755
--- a/target/linux/ramips/base-files/etc/board.d/02_network
+++ b/target/linux/ramips/base-files/etc/board.d/02_network
@@ -93,6 +93,7 @@ ramips_setup_interfaces()
dlink,dwr-922-e2|\
ew1200|\
firewrt|\
+ gl-mt1300|\
hc5661a|\
hlk-rm04|\
k2p|\
@@ -288,6 +289,9 @@ ramips_setup_interfaces()
ucidef_add_switch "switch0" \
"1:lan" "0:wan" "6@eth0"
;;
+ microuter-n300)
+ ucidef_set_interface_lan "eth0"
+ ;;
awapn2403)
ucidef_add_switch "switch0" \
"0:lan" "1:wan" "6@eth0"
diff --git a/target/linux/ramips/base-files/lib/ramips.sh b/target/linux/ramips/base-files/lib/ramips.sh
index 093303892c..e0a7035f6b 100755
--- a/target/linux/ramips/base-files/lib/ramips.sh
+++ b/target/linux/ramips/base-files/lib/ramips.sh
@@ -214,6 +214,12 @@ ramips_board_detect() {
*"GL-MT300N-V2")
name="gl-mt300n-v2"
;;
+ *"microuter-N300")
+ name="microuter-n300"
+ ;;
+ *"GL-MT1300")
+ name="gl-mt1300"
+ ;;
*"HC5661")
name="hc5661"
;;
diff --git a/target/linux/ramips/dts/GL-MT1300.dts b/target/linux/ramips/dts/GL-MT1300.dts
new file mode 100644
index 0000000000..6a0adc7ba1
--- /dev/null
+++ b/target/linux/ramips/dts/GL-MT1300.dts
@@ -0,0 +1,136 @@
+/dts-v1/;
+
+#include "mt7621.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+/ {
+ compatible = "glinet,gl-mt1300", "mediatek,mt7621-soc";
+ model = "GL-MT1300";
+
+ aliases {
+ };
+
+ chosen {
+ bootargs = "console=ttyS0,115200";
+ };
+
+ palmbus: palmbus@1E000000 {
+ i2c@900 {
+ status = "okay";
+ };
+ };
+
+ keys {
+ compatible = "gpio-keys-polled";
+ poll-interval = <20>;
+
+ reset {
+ label = "reset";
+ gpios = <&gpio0 18 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_RESTART>;
+ };
+
+ BTN_0 {
+ label = "BTN_0";
+ gpios = <&gpio0 16 GPIO_ACTIVE_LOW>;
+ linux,code = <BTN_0>;
+ };
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ led_run: blue {
+ label = "gl-mt1300:blue";
+ gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
+ };
+
+ white {
+ label = "gl-mt1300:white";
+ gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
+ };
+ };
+};
+
+&sdhci {
+ status = "okay";
+};
+
+&spi0 {
+ status = "okay";
+
+ m25p80@0 {
+ compatible = "mx25l25635f", "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <10000000>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x0 0x30000>;
+ read-only;
+ };
+
+ partition@30000 {
+ label = "u-boot-env";
+ reg = <0x30000 0x10000>;
+ read-only;
+ };
+
+ factory: partition@40000 {
+ label = "factory";
+ reg = <0x40000 0x10000>;
+ read-only;
+ };
+
+ partition@50000 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x50000 0x1fb0000>;
+ };
+ };
+ };
+};
+
+&pcie {
+ status = "okay";
+};
+
+&pcie0 {
+ mt76@0,0 {
+ reg = <0x0000 0 0 0 0>;
+ mediatek,mtd-eeprom = <&factory 0x8000>;
+ ieee80211-freq-limit = <5000000 6000000>;
+ };
+};
+
+&pcie1 {
+ mt76@0,0 {
+ reg = <0x0000 0 0 0 0>;
+ mediatek,mtd-eeprom = <&factory 0x0000>;
+ ieee80211-freq-limit = <2400000 2500000>;
+ };
+};
+
+&ethernet {
+ mtd-mac-address = <&factory 0x4000>;
+};
+
+&uartlite3 {
+ status = "okay";
+};
+
+&pinctrl {
+ state_default: pinctrl0 {
+ gpio {
+ ralink,group = "wdt", "rgmii2", "jtag", "mdio";
+ ralink,function = "gpio";
+ };
+ };
+};
diff --git a/target/linux/ramips/dts/microuter-N300.dts b/target/linux/ramips/dts/microuter-N300.dts
new file mode 100644
index 0000000000..74e4b46bfa
--- /dev/null
+++ b/target/linux/ramips/dts/microuter-N300.dts
@@ -0,0 +1,108 @@
+/dts-v1/;
+
+#include "mt7628an.dtsi"
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/input.h>
+
+/{
+ compatible = "glinet,microuter-n300", "mediatek,mt7628an-soc";
+ model = "microuter-N300";
+
+ chosen {
+ bootargs = "console=ttyS0,115200";
+ };
+
+ memory@0 {
+ device_type = "memory";
+ reg = <0x0 0x8000000>;
+ };
+
+ leds {
+ compatible = "gpio-leds";
+
+ power: power {
+ label = "microuter-n300:blue:power";
+ default-state = "on";
+ gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
+ };
+
+ wlan {
+ label = "microuter-n300:white:wlan";
+ gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
+ };
+ };
+
+ keys {
+ compatible = "gpio-keys-polled";
+ poll-interval = <20>;
+
+ reset {
+ label = "reset";
+ gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
+ linux,code = <KEY_RESTART>;
+ };
+ };
+};
+
+&pinctrl {
+ state_default: pinctrl0 {
+ gpio {
+ ralink,group = "wdt", "wled_an", "p1led_an";
+ ralink,function = "gpio";
+ };
+ };
+};
+
+&ethernet {
+ mtd-mac-address = <&factory 0x4>;
+};
+
+&wmac {
+ status = "okay";
+ ralink,mtd-eeprom = <&factory 0x4>;
+};
+
+&spi0 {
+ status = "okay";
+
+ m25p80@0 {
+ compatible = "jedec,spi-nor";
+ reg = <0>;
+ spi-max-frequency = <10000000>;
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "u-boot";
+ reg = <0x0 0x30000>;
+ read-only;
+ };
+
+ partition@30000 {
+ label = "u-boot-env";
+ reg = <0x30000 0x10000>;
+ read-only;
+ };
+
+ factory: partition@40000 {
+ label = "factory";
+ reg = <0x40000 0x10000>;
+ read-only;
+ };
+
+ partition@50000 {
+ compatible = "denx,uimage";
+ label = "firmware";
+ reg = <0x50000 0xfb0000>;
+ };
+ };
+ };
+};
+
+&uart1 {
+ status = "okay";
+};
diff --git a/target/linux/ramips/image/mt7621.mk b/target/linux/ramips/image/mt7621.mk
index 28ae0d451f..708a606b98 100644
--- a/target/linux/ramips/image/mt7621.mk
+++ b/target/linux/ramips/image/mt7621.mk
@@ -204,6 +204,17 @@ define Device/gehua_ghl-r-001
endef
TARGET_DEVICES += gehua_ghl-r-001
+define Device/gl-mt1300
+ DTS := GL-MT1300
+ IMAGE_SIZE := $(ralink_default_fw_size_32M)
+ DEVICE_TITLE := GL-iNet GL-MT1300
+ DEVICE_PACKAGES := \
+ kmod-ata-core kmod-ata-ahci kmod-mt76x2 kmod-mt7603 kmod-usb3 \
+ kmod-usb-ledtrig-usbport wpad-basic
+ IMAGE/sysupgrade.bin := append-kernel | append-rootfs | pad-rootfs | append-gl-metadata | check-size $$$$(IMAGE_SIZE)
+endef
+TARGET_DEVICES += gl-mt1300
+
define Device/gnubee_gb-pc1
DTS := GB-PC1
DEVICE_TITLE := GnuBee Personal Cloud One
diff --git a/target/linux/ramips/image/mt76x8.mk b/target/linux/ramips/image/mt76x8.mk
index d0c66a0e23..6691d67211 100644
--- a/target/linux/ramips/image/mt76x8.mk
+++ b/target/linux/ramips/image/mt76x8.mk
@@ -60,9 +60,18 @@ define Device/gl-mt300n-v2
IMAGE_SIZE := 16064k
DEVICE_TITLE := GL-iNet GL-MT300N-V2
DEVICE_PACKAGES := kmod-usb2 kmod-usb-ohci
+ IMAGE/sysupgrade.bin := append-kernel | append-rootfs | pad-rootfs | append-gl-metadata | check-size $$$$(IMAGE_SIZE)
endef
TARGET_DEVICES += gl-mt300n-v2
+define Device/microuter-n300
+ DTS := microuter-N300
+ IMAGE_SIZE := 16064k
+ DEVICE_TITLE := GL.iNet microuter N300
+ IMAGE/sysupgrade.bin := append-kernel | append-rootfs | pad-rootfs | append-gl-metadata | check-size $$$$(IMAGE_SIZE)
+endef
+TARGET_DEVICES += microuter-n300
+
define Device/glinet_vixmini
DTS := VIXMINI
IMAGE_SIZE := 7872k
--
2.17.1