From 4596c8b4b08588aae4b32e73256aaf471e50211a Mon Sep 17 00:00:00 2001 From: Jianhui Zhao 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 --- 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 +#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 #include -#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 = ; 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 +#include + +#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 = ; + 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 +#include + +#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 = ; + 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 = ; gpios = <&gpio 0 GPIO_ACTIVE_HIGH>; }; button3 { - label = "button left"; + label = "right"; linux,code = ; 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 = ; gpios = <&gpio 3 GPIO_ACTIVE_LOW>; }; + + mode { + label = "sw1"; + linux,code = ; + linux,input-type = ; + 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 +#include + +#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 = ; + gpios = <&gpio 3 GPIO_ACTIVE_LOW>; + }; + + switch { + label = "right"; + linux,code = ; + 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 +#include + +#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 = ; + 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 +#include + +#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 = ; + 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 +#include + +#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 = ; + 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 = ; linux,input-type = ; 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 +#include + +#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 = ; + 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 + * Copyright (c) 2010 Felix Fietkau + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 ++ * Peter Pan ++ */ ++ ++#define pr_fmt(fmt) "nand-bbt: " fmt ++ ++#include ++#include ++#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 ++#include ++#include ++#include ++#include "spinand.h" ++#include ++#include ++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;ibuf = 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"); ++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 ++#include ++#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 ++#include ++#include ++#include "spinand.h" ++#include ++ ++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 "); ++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 ++#include ++#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 ++#include ++#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 ++ * Peter Pan ++ */ ++ ++#define pr_fmt(fmt) "nand: " fmt ++ ++#include ++#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 ++#include ++#include ++#include ++#include ++#include ++ ++/** ++ * 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 ++#include ++#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 ++#include ++#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 + #include + ++ ++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 + #include + #include ++#include + + #include + #include +@@ -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 +#include +#include + +/ { + 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@194b000 { + /* select hostmode */ + compatible = "qcom,tcsr"; + reg = <0x194b000 0x100>; + qcom,usb-hsphy-mode-select = ; + status = "okay"; + }; + + ess_tcsr@1953000 { + compatible = "qcom,tcsr"; + reg = <0x1953000 0x1000>; + qcom,ess-interface-select = ; + }; + + tcsr@1957000 { + compatible = "qcom,tcsr"; + reg = <0x1957000 0x100>; + qcom,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 = ; + }; + + reset { + label = "reset"; + gpios = <&tlmm 63 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; + + 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 +#include +#include + +/ { + 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@194b000 { + /* select hostmode */ + compatible = "qcom,tcsr"; + reg = <0x194b000 0x100>; + qcom,usb-hsphy-mode-select = ; + status = "okay"; + }; + + ess_tcsr@1953000 { + compatible = "qcom,tcsr"; + reg = <0x1953000 0x1000>; + qcom,ess-interface-select = ; + }; + + tcsr@1957000 { + compatible = "qcom,tcsr"; + reg = <0x1957000 0x100>; + qcom,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 = ; + }; + + reset { + label = "reset"; + gpios = <&tlmm 18 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; + + 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 ++ * Peter Pan ++ */ ++ ++#define pr_fmt(fmt) "nand-bbt: " fmt ++ ++#include ++#include ++#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 ++#include ++#include ++#include ++#include "spinand.h" ++#include ++#include ++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;ibuf = 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"); ++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 ++#include ++#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 ++#include ++#include ++#include "spinand.h" ++#include ++ ++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 "); ++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 ++#include ++#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 ++#include ++#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 ++ * Peter Pan ++ */ ++ ++#define pr_fmt(fmt) "nand: " fmt ++ ++#include ++#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 ++#include ++#include ++#include ++#include ++#include ++ ++/** ++ * 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 ++#include ++#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 ++#include ++#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 --- 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 + 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 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 +#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 +#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 ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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 "); ++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 + #include + #include ++#include ++#include + #include + #include ++#include + + #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 "); ++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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 "); ++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 ++ * ++ * 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 = ; ++ clocks = <&sb_periph_clk 12>; ++ status = "okay"; ++ }; ++ udc@54100 { ++ compatible = "marvell,mv-udc"; ++ reg = <0x54100 0x2000>; ++ interrupts = ; ++ 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 ++#include + + + /* +@@ -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 + #include + ++#if 1 //Added by Quectel ++#include ++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 ++#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 +#include + +/ { + 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 = ; + }; + + BTN_0 { + label = "BTN_0"; + gpios = <&gpio0 16 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; + + 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 +#include + +/{ + 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 = ; + }; + }; +}; + +&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