mirror of
https://github.com/FUjr/gl-infra-builder.git
synced 2025-12-16 09:10:02 +00:00
17843 lines
482 KiB
Diff
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>;
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ 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";
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
+ð1 {
|
|
+ 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";
|
|
};
|
|
|
|
ð1 {
|
|
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;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ status = "okay";
|
|
+
|
|
+ mtd-mac-address = <&art 0x0>;
|
|
+ ifname = "eth0";
|
|
+};
|
|
+
|
|
+ð1 {
|
|
+ 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;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ status = "okay";
|
|
+
|
|
+ mtd-mac-address = <&art 0x0>;
|
|
+
|
|
+ gmac-config {
|
|
+ device = <&gmac>;
|
|
+
|
|
+ switch-phy-addr-swap = <0>;
|
|
+ switch-phy-swap = <0>;
|
|
+ };
|
|
+};
|
|
+
|
|
+ð1 {
|
|
+ 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";
|
|
};
|
|
|
|
ð1 {
|
|
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 @@
|
|
};
|
|
};
|
|
};
|
|
+
|
|
+
|
|
};
|
|
|
|
ð0 {
|
|
status = "okay";
|
|
mtd-mac-address = <&art 0x0>;
|
|
phy-handle = <&swphy4>;
|
|
+ ifname = "eth0";
|
|
};
|
|
|
|
ð1 {
|
|
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 = ð0;
|
|
+ };
|
|
+
|
|
+ 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="";
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ status = "okay";
|
|
+
|
|
+ mtd-mac-address = <&art 0x0>;
|
|
+ phy-handle = <&swphy4>;
|
|
+};
|
|
+
|
|
+ð1 {
|
|
+ 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="";
|
|
+};*/
|
|
+
|
|
+ð0 {
|
|
+ status = "okay";
|
|
+ mtd-mac-address = <&art 0x0>;
|
|
+ phy-handle = <&swphy4>;
|
|
+ ifname = "eth0";
|
|
+};
|
|
+
|
|
+ð1 {
|
|
+ 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="";
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ status = "okay";
|
|
+ mtd-mac-address = <&art 0x0>;
|
|
+ phy-handle = <&swphy4>;
|
|
+ ifname = "eth0";
|
|
+};
|
|
+
|
|
+ð1 {
|
|
+ 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>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ status = "okay";
|
|
+ mtd-mac-address = <&art 0x0>;
|
|
+ phy-handle = <&swphy4>;
|
|
+ ifname = "eth1";
|
|
+};
|
|
+
|
|
+ð1 {
|
|
+ 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 */
|
|
+ >;
|
|
+ };
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ 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(®_mutex);
|
|
+ ret = __ar7240sw_reg_read(mii, reg_addr);
|
|
+ mutex_unlock(®_mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void ar7240sw_reg_write(struct mii_bus *mii, u32 reg_addr, u32 reg_val)
|
|
+{
|
|
+ mutex_lock(®_mutex);
|
|
+ __ar7240sw_reg_write(mii, reg_addr, reg_val);
|
|
+ mutex_unlock(®_mutex);
|
|
+}
|
|
+
|
|
+static u32 ar7240sw_reg_rmw(struct mii_bus *mii, u32 reg, u32 mask, u32 val)
|
|
+{
|
|
+ u32 t;
|
|
+
|
|
+ mutex_lock(®_mutex);
|
|
+ t = __ar7240sw_reg_read(mii, reg);
|
|
+ t &= ~mask;
|
|
+ t |= val;
|
|
+ __ar7240sw_reg_write(mii, reg, t);
|
|
+ mutex_unlock(®_mutex);
|
|
+
|
|
+ return t;
|
|
+}
|
|
+
|
|
+static void ar7240sw_reg_set(struct mii_bus *mii, u32 reg, u32 val)
|
|
+{
|
|
+ u32 t;
|
|
+
|
|
+ mutex_lock(®_mutex);
|
|
+ t = __ar7240sw_reg_read(mii, reg);
|
|
+ t |= val;
|
|
+ __ar7240sw_reg_write(mii, reg, t);
|
|
+ mutex_unlock(®_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(®_mutex);
|
|
+ ret = __ar7240sw_reg_wait(mii, reg, mask, val, timeout);
|
|
+ mutex_unlock(®_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(®_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(®_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(®_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(®_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[] = {
|
|
++ µn_spinand_manufacturer,
|
|
++ &etron_spinand_manufacturer,
|
|
++ &giga_spinand_manufacturer,
|
|
++ ¶gon_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 = µn_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 = ¶gon_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[] = {
|
|
++ µn_spinand_manufacturer,
|
|
++ &etron_spinand_manufacturer,
|
|
++ &giga_spinand_manufacturer,
|
|
++ ¶gon_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 = µn_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 = ¶gon_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 = <ð0>;
|
|
+ };
|
|
+
|
|
+ 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>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ 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 = <ð0>;
|
|
+ };
|
|
+
|
|
+ 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>;
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
+ð0 {
|
|
+ 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>;
|
|
+ };
|
|
+};
|
|
+
|
|
+ðernet {
|
|
+ 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";
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
+ðernet {
|
|
+ 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
|
|
|