From 4943afd7818f56053231a5a7ae90e55da44f1f08 Mon Sep 17 00:00:00 2001 From: Chris Blake Date: Sat, 10 Mar 2018 10:59:18 +0100 Subject: [PATCH] ipq40xx: add Cisco Meraki MR33 Support This patch adds support for Cisco Meraki MR33 hardware highlights: SOC: IPQ4029 Quad-Core ARMv7 Processor rev 5 (v7l) Cortex-A7 DRAM: 256 MiB DDR3L-1600 @ 627 MHz Micron MT41K128M16JT-125IT NAND: 128 MiB SLC NAND Spansion S34ML01G200TFV00 (106 MiB usable) ETH: Qualcomm Atheros AR8035 Gigabit PHY (1 x LAN/WAN) + PoE WLAN1: QCA9887 (168c:0050) PCIe 1x1:1 802.11abgn ac Dualband VHT80 WLAN2: Qualcomm Atheros QCA4029 2.4GHz 802.11bgn 2:2x2 WLAN3: Qualcomm Atheros QCA4029 5GHz 802.11a/n/ac 2:2x2 VHT80 LEDS: 1 x Programmable RGB+White Status LED (driven by Ti LP5562 on i2c-1) 1 x Orange LED Fault Indicator (shared with LP5562) 2 x LAN Activity / Speed LEDs (On the RJ45 Port) BUTTON: one Reset button MISC: Bluetooth LE Ti cc2650 PG2.3 4x4mm - BL_CONFIG at 0x0001FFD8 AT24C64 8KiB EEPROM Kensington Lock Serial: WARNING: The serial port needs a TTL/RS-232 3V3 level converter! The Serial setting is 115200-8-N-1. The board has a populated 1x4 0.1" header with half-height/low profile pins. The pinout is: VCC (little white arrow), RX, TX, GND. Flashing needs a serial adaptor, as well as patched ubootwrite utility (needs Little-Endian support). And a modified u-boot (enabled Ethernet). Meraki's original u-boot source can be found in: Add images to do an installation via bootloader: 0. open up the MR33 and connect the serial console. 1. start the 2nd stage bootloader transfer from client pc: # ubootwrite.py --write=mr33-uboot.bin (The ubootwrite tool will interrupt the boot-process and hence it needs to listen for cues. If the connection is bad (due to the low-profile pins), the tool can fail multiple times and in weird ways. If you are not sure, just use a terminal program and see what the device is doing there. 2. power on the MR33 (with ethernet + serial cables attached) Warning: Make sure you do this in a private LAN that has no connection to the internet. - let it upload the u-boot this can take 250-300 seconds - 3. use a tftp client (in binary mode!) on your PC to upload the sysupgrade.bin (the u-boot is listening on 192.168.1.1) # tftp 192.168.1.1 binary put openwrt-ipq40xx-meraki_mr33-squashfs-sysupgrade.bin 4. wait for it to reboot 5. connect to your MR33 via ssh on 192.168.1.1 For more detailed instructions, please take a look at the: "Flashing Instructions for the MR33" PDF. This can be found on the wiki: (A link to the mr33-uboot.bin + the modified ubootwrite is also there) Thanks to Jerome C. for sending an MR33 to Chris. Signed-off-by: Chris Blake Signed-off-by: Mathias Kresin Signed-off-by: Christian Lamparter --- package/firmware/ipq-wifi/Makefile | 3 +- .../firmware/ipq-wifi/board-meraki_mr33.bin | Bin 0 -> 24276 bytes .../ipq40xx/base-files/etc/board.d/01_leds | 3 + .../ipq40xx/base-files/etc/board.d/02_network | 4 + .../etc/hotplug.d/firmware/11-ath10k-caldata | 74 ++++ .../lib/preinit/05_set_iface_mac_ipq40xx.sh | 14 + .../base-files/lib/upgrade/platform.sh | 7 + target/linux/ipq40xx/config-4.14 | 3 + .../arch/arm/boot/dts/qcom-ipq4029-mr33.dts | 403 ++++++++++++++++++ target/linux/ipq40xx/image/Makefile | 11 + .../069-arm-boot-add-dts-files.patch | 3 +- .../patches-4.14/712-mr33-essedma.patch | 340 +++++++++++++++ 12 files changed, 863 insertions(+), 2 deletions(-) create mode 100644 package/firmware/ipq-wifi/board-meraki_mr33.bin create mode 100644 target/linux/ipq40xx/base-files/lib/preinit/05_set_iface_mac_ipq40xx.sh create mode 100644 target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4029-mr33.dts create mode 100644 target/linux/ipq40xx/patches-4.14/712-mr33-essedma.patch diff --git a/package/firmware/ipq-wifi/Makefile b/package/firmware/ipq-wifi/Makefile index 39d29a4ff38..a92be6fd41b 100644 --- a/package/firmware/ipq-wifi/Makefile +++ b/package/firmware/ipq-wifi/Makefile @@ -13,7 +13,7 @@ endef define Build/Compile endef -ALLWIFIBOARDS:=asus_rt-ac58u avm_fritzbox-4040 glinet_gl-b1300 +ALLWIFIBOARDS:=asus_rt-ac58u avm_fritzbox-4040 glinet_gl-b1300 meraki_mr33 ALLWIFIPACKAGES:=$(foreach BOARD,$(ALLWIFIBOARDS),ipq-wifi-$(BOARD)) define Package/ipq-wifi-default @@ -50,5 +50,6 @@ endef $(eval $(call generate-ipq-wifi-package,asus_rt-ac58u,board-asus_rt-ac58u.bin,ASUS RT-AC58U)) $(eval $(call generate-ipq-wifi-package,avm_fritzbox-4040,board-avm_fritzbox-4040.bin,AVM FRITZ!Box 4040)) $(eval $(call generate-ipq-wifi-package,glinet_gl-b1300,board-glinet_gl-b1300.bin,GL.iNet GL-B1300)) +$(eval $(call generate-ipq-wifi-package,meraki_mr33,board-meraki_mr33.bin,Cisco Meraki MR33)) $(foreach PACKAGE,$(ALLWIFIPACKAGES),$(eval $(call BuildPackage,$(PACKAGE)))) diff --git a/package/firmware/ipq-wifi/board-meraki_mr33.bin b/package/firmware/ipq-wifi/board-meraki_mr33.bin new file mode 100644 index 0000000000000000000000000000000000000000..e569b32fe36ad5df8f76532af1a949b3f05e9b60 GIT binary patch literal 24276 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 literal 0 HcmV?d00001 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 6a493be3ec4..f1f49abb2ea 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,9 @@ avm,fritzbox-4040) glinet,gl-b1300) ucidef_set_led_wlan "wlan" "WLAN" "${boardname}:green:wlan" "phy0tpt" ;; +meraki,mr33) + ucidef_set_interface_lan "eth0" + ;; *) ;; esac 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 8f8d67db32d..d25a039f2d5 100755 --- a/target/linux/ipq40xx/base-files/etc/board.d/02_network +++ b/target/linux/ipq40xx/base-files/etc/board.d/02_network @@ -35,6 +35,10 @@ glinet,gl-b1300) openmesh,a42) ucidef_set_interfaces_lan_wan "eth1" "eth0" ;; + +meraki,mr33) + ucidef_set_interface_lan "eth0" + ;; *) echo "Unsupported hardware. Network interfaces not intialized" ;; 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 bc64d5b8ab0..4322c4f3faf 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 @@ -1,5 +1,21 @@ #!/bin/sh +# xor multiple hex values of the same length +xor() { + local val + local ret="0x$1" + local retlen=${#1} + + shift + while [ -n "$1" ]; do + val="0x$1" + ret=$((ret ^ val)) + shift + done + + printf "%0${retlen}x" "$ret" +} + ath10kcal_die() { echo "ath10cal: " "$*" exit 1 @@ -37,6 +53,45 @@ ath10kcal_ubi_extract() { ath10kcal_die "failed to extract from $ubi" } +ath10kcal_patch_mac() { + local mac=$1 + + [ -z "$mac" ] && return + + macaddr_2bin $mac | dd of=/lib/firmware/$FIRMWARE conv=notrunc bs=1 seek=6 count=6 +} + +ath10kcal_patch_mac_crc() { + local mac=$1 + local mac_offset=6 + local chksum_offset=2 + local xor_mac + local xor_fw_mac + local xor_fw_chksum + + xor_fw_mac=$(hexdump -v -n 6 -s $mac_offset -e '/1 "%02x"' /lib/firmware/$FIRMWARE) + xor_fw_mac="${xor_fw_mac:0:4} ${xor_fw_mac:4:4} ${xor_fw_mac:8:4}" + + ath10kcal_patch_mac "$mac" && { + xor_mac=${mac//:/} + xor_mac="${xor_mac:0:4} ${xor_mac:4:4} ${xor_mac:8:4}" + + xor_fw_chksum=$(hexdump -v -n 2 -s $chksum_offset -e '/1 "%02x"' /lib/firmware/$FIRMWARE) + xor_fw_chksum=$(xor $xor_fw_chksum $xor_fw_mac $xor_mac) + + printf "%b" "\x${xor_fw_chksum:0:2}\x${xor_fw_chksum:2:2}" | \ + dd of=/lib/firmware/$FIRMWARE conv=notrunc bs=1 seek=$chksum_offset count=2 + } +} + +ath10kcal_is_caldata_valid() { + local expected="$1" + + magic=$(hexdump -v -n 2 -e '1/1 "%02x"' /lib/firmware/$FIRMWARE) + [[ "$magic" == "$expected" ]] + return $? +} + [ -e /lib/firmware/$FIRMWARE ] && exit 0 . /lib/functions.sh @@ -46,6 +101,15 @@ board=$(board_name) case "$FIRMWARE" in +"ath10k/cal-pci-0000:01:00.0.bin") + case "$board" in + meraki,mr33) + ath10kcal_ubi_extract "ART" 36864 2116 + ath10kcal_is_caldata_valid "4408" || ath10kcal_extract "ART" 36864 2116 + ath10kcal_patch_mac $(macaddr_add $(get_mac_binary "/sys/bus/i2c/devices/0-0050/eeprom" 102) +1) + ;; + esac + ;; "ath10k/pre-cal-ahb-a000000.wifi.bin") case "$board" in asus,rt-ac58u) @@ -59,6 +123,11 @@ case "$FIRMWARE" in qcom,ap-dk01.1-c1) ath10kcal_extract "ART" 4096 12064 ;; + meraki,mr33) + ath10kcal_ubi_extract "ART" 4096 12064 + ath10kcal_is_caldata_valid "202f" || ath10kcal_extract "ART" 4096 12064 + ath10kcal_patch_mac_crc $(macaddr_add $(get_mac_binary "/sys/bus/i2c/devices/0-0050/eeprom" 102) +2) + ;; openmesh,a42) ath10kcal_extract "0:ART" 4096 12064 ;; @@ -77,6 +146,11 @@ case "$FIRMWARE" in qcom,ap-dk01.1-c1) ath10kcal_extract "ART" 20480 12064 ;; + meraki,mr33) + ath10kcal_ubi_extract "ART" 20480 12064 + ath10kcal_is_caldata_valid "202f" || ath10kcal_extract "ART" 20480 12064 + ath10kcal_patch_mac_crc $(macaddr_add $(get_mac_binary "/sys/bus/i2c/devices/0-0050/eeprom" 102) +3) + ;; openmesh,a42) ath10kcal_extract "0:ART" 20480 12064 ;; diff --git a/target/linux/ipq40xx/base-files/lib/preinit/05_set_iface_mac_ipq40xx.sh b/target/linux/ipq40xx/base-files/lib/preinit/05_set_iface_mac_ipq40xx.sh new file mode 100644 index 00000000000..1acd7366c81 --- /dev/null +++ b/target/linux/ipq40xx/base-files/lib/preinit/05_set_iface_mac_ipq40xx.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +. /lib/functions.sh + +preinit_set_mac_address() { + case $(board_name) in + meraki,mr33) + mac_lan=$(get_mac_binary "/sys/bus/i2c/devices/0-0050/eeprom" 102) + [ -n "$mac_lan" ] && ip link set dev eth0 address "$mac_lan" + ;; + esac +} + +boot_hook_add preinit_main preinit_set_mac_address diff --git a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh index 261c468813a..52aa2321e3e 100644 --- a/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh +++ b/target/linux/ipq40xx/base-files/lib/upgrade/platform.sh @@ -40,6 +40,10 @@ platform_do_upgrade() { PART_NAME="inactive" platform_do_upgrade_openmesh "$ARGV" ;; + meraki,mr33) + CI_KERNPART="part.safe" + nand_do_upgrade "$1" + ;; *) default_do_upgrade "$ARGV" ;; @@ -52,6 +56,9 @@ platform_nand_pre_upgrade() { CI_UBIPART="UBI_DEV" CI_KERNPART="linux" ;; + meraki,mr33) + CI_KERNPART="part.safe" + ;; esac } diff --git a/target/linux/ipq40xx/config-4.14 b/target/linux/ipq40xx/config-4.14 index fe01754d20a..1bba0617614 100644 --- a/target/linux/ipq40xx/config-4.14 +++ b/target/linux/ipq40xx/config-4.14 @@ -159,6 +159,7 @@ CONFIG_DT_IDLE_STATES=y CONFIG_DYNAMIC_DEBUG=y CONFIG_EDAC_ATOMIC_SCRUB=y CONFIG_EDAC_SUPPORT=y +CONFIG_EEPROM_AT24=y CONFIG_ESSEDMA=y CONFIG_EXPORTFS=y CONFIG_EXTCON=y @@ -268,6 +269,8 @@ CONFIG_IRQ_DOMAIN=y CONFIG_IRQ_DOMAIN_HIERARCHY=y CONFIG_IRQ_FORCED_THREADING=y CONFIG_IRQ_WORK=y +CONFIG_LEDS_LP5562=y +CONFIG_LEDS_LP55XX_COMMON=y CONFIG_LIBFDT=y CONFIG_LOCK_SPIN_ON_OWNER=y CONFIG_LZO_COMPRESS=y diff --git a/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4029-mr33.dts b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4029-mr33.dts new file mode 100644 index 00000000000..9c1ef4f020e --- /dev/null +++ b/target/linux/ipq40xx/files-4.14/arch/arm/boot/dts/qcom-ipq4029-mr33.dts @@ -0,0 +1,403 @@ +/* + * Device Tree Source for Meraki MR33 (Stinkbug) + * + * Copyright (C) 2017 Chris Blake + * Copyright (C) 2017 Christian Lamparter + * + * Based on Cisco Meraki DTS from GPL release r25-linux-3.14-20170427 + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without + * any warranty of any kind, whether express or implied. + */ + +#include "qcom-ipq4019.dtsi" +#include "qcom-ipq4019-bus.dtsi" +#include +#include +#include + +/ { + model = "Meraki MR33 Access Point"; + compatible = "meraki,mr33", "qcom,ipq4019"; + + aliases { + led-boot = &status_green; + led-failsafe = &status_red; + led-running = &status_green; + led-upgrade = &power_orange; + }; + + /* Do we really need this defined? */ + memory { + device_type = "memory"; + reg = <0x80000000 0x10000000>; + }; + + reserved-memory { + #address-cells = <0x1>; + #size-cells = <0x1>; + ranges; + + tz_apps@87b80000 { + reg = <0x87b80000 0x280000>; + reusable; + }; + + smem@87e00000 { + reg = <0x87e00000 0x080000>; + no-map; + }; + + tz@87e80000 { + reg = <0x87e80000 0x180000>; + no-map; + }; + }; + + soc { + mdio@90000 { + status = "okay"; + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + phy-reset-gpio = <&tlmm 47 0>; + /delete-node/ ethernet-phy@0; + /delete-node/ ethernet-phy@2; + /delete-node/ ethernet-phy@3; + /delete-node/ ethernet-phy@4; + }; + + /* It is a 56-bit counter that supplies the count to the ARM arch + timers and without upstream driver */ + counter@4a1000 { + compatible = "qcom,qca-gcnt"; + reg = <0x4a1000 0x4>; + }; + + ess_tcsr@1953000 { + compatible = "qcom,tcsr"; + reg = <0x1953000 0x1000>; + qcom,ess-interface-select = ; + }; + + tcsr@1949000 { + compatible = "qcom,tcsr"; + reg = <0x1949000 0x100>; + qcom,wifi_glb_cfg = ; + }; + + tcsr@1957000 { + compatible = "qcom,tcsr"; + reg = <0x1957000 0x100>; + qcom,wifi_noc_memtype_m0_m2 = ; + }; + + serial@78af000 { + pinctrl-0 = <&serial_0_pins>; + pinctrl-names = "default"; + status = "okay"; + }; + + serial@78b0000 { + pinctrl-0 = <&serial_1_pins>; + pinctrl-names = "default"; + status = "okay"; + + bluetooth { + compatible = "ti,cc2650"; + enable-gpios = <&tlmm 12 GPIO_ACTIVE_LOW>; + }; + }; + + crypto@8e3a000 { + status = "okay"; + }; + + watchdog@b017000 { + status = "okay"; + }; + + ess-switch@c000000 { + switch_mac_mode = <0x3>; /* mac mode for RGMII RMII */ + switch_lan_bmp = <0x0>; /* lan port bitmap */ + switch_wan_bmp = <0x10>; /* wan port bitmap */ + }; + + edma@c080000 { + qcom,single-phy; + qcom,num_gmac = <1>; + phy-mode = "rgmii-rxid"; + status = "okay"; + }; + }; + + gpio-keys { + compatible = "gpio-keys"; + + reset { + label = "reset"; + gpios = <&tlmm 18 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; + + gpio-leds { + compatible = "gpio-leds"; + + power_orange: power { + label = "mr33:orange:power"; + gpios = <&tlmm 49 GPIO_ACTIVE_LOW>; + panic-indicator; + }; + }; +}; + +&blsp_dma { + status = "okay"; +}; + +&cryptobam { + status = "okay"; +}; + +&gmac0 { + qcom,phy_mdio_addr = <1>; + qcom,poll_required = <1>; + vlan_tag = <0 0x20>; +}; + +&i2c_0 { + pinctrl-0 = <&i2c_0_pins>; + pinctrl-names = "default"; + status = "okay"; + at24@50 { + compatible = "atmel,24c64"; + pagesize = <32>; + reg = <0x50>; + read-only; /* This holds our MAC & Meraki board-data */ + }; +}; + +&i2c_1 { + pinctrl-0 = <&i2c_1_pins>; + pinctrl-names = "default"; + status = "okay"; + + lp5562@30 { + enable-gpio = <&tlmm 48 GPIO_ACTIVE_HIGH>; + compatible = "ti,lp5562"; + clock-mode = /bits/8 <2>; + reg = <0x30>; + + /* RGB led */ + status_red: chan0 { + chan-name = "mr33:red:status"; + led-cur = /bits/ 8 <0x20>; + max-cur = /bits/ 8 <0x60>; + }; + + status_green: chan1 { + chan-name = "mr33:green:status"; + led-cur = /bits/ 8 <0x20>; + max-cur = /bits/ 8 <0x60>; + }; + + chan2 { + chan-name = "mr33:blue:status"; + led-cur = /bits/ 8 <0x20>; + max-cur = /bits/ 8 <0x60>; + }; + + chan3 { + chan-name = "mr33:white:status"; + led-cur = /bits/ 8 <0x20>; + max-cur = /bits/ 8 <0x60>; + }; + }; +}; + +&nand { + pinctrl-0 = <&nand_pins>; + pinctrl-names = "default"; + status = "okay"; + + nand@0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "sbl1"; + reg = <0x000000000000 0x000000100000>; + read-only; + }; + partition@1 { + label = "mibib"; + reg = <0x000000100000 0x000000100000>; + read-only; + }; + partition@2 { + label = "bootconfig"; + reg = <0x000000200000 0x000000100000>; + read-only; + }; + partition@3 { + label = "qsee"; + reg = <0x000000300000 0x000000100000>; + read-only; + }; + partition@4 { + label = "qsee_alt"; + reg = <0x000000400000 0x000000100000>; + read-only; + }; + partition@5 { + label = "cdt"; + reg = <0x000000500000 0x000000080000>; + read-only; + }; + partition@6 { + label = "cdt_alt"; + reg = <0x000000580000 0x000000080000>; + read-only; + }; + partition@7 { + label = "ddrparams"; + reg = <0x000000600000 0x000000080000>; + read-only; + }; + partition@8 { + label = "u-boot"; + reg = <0x000000700000 0x000000200000>; + read-only; + }; + partition@9 { + label = "u-boot-backup"; + reg = <0x000000900000 0x000000200000>; + read-only; + }; + partition@10 { + label = "ART"; + reg = <0x000000b00000 0x000000080000>; + read-only; + }; + partition@11 { + label = "ubi"; + reg = <0x000000c00000 0x000007000000>; + }; + }; + }; +}; + +&pcie0 { + status = "okay"; + perst-gpio = <&tlmm 38 GPIO_ACTIVE_LOW>; + wake-gpio = <&tlmm 50 GPIO_ACTIVE_LOW>; +}; + +&qpic_bam { + status = "okay"; +}; + +&tlmm { + /* + * GPIO43 should be 0/1 whenever the unit is + * powered through PoE or AC-Adapter. + * That said, playing with this seems to + * reset the AP. + */ + + mdio_pins: mdio_pinmux { + mux_1 { + pins = "gpio6"; + function = "mdio"; + bias-pull-up; + }; + mux_2 { + pins = "gpio7"; + function = "mdc"; + bias-pull-up; + }; + }; + + serial_0_pins: serial_pinmux { + mux { + pins = "gpio16", "gpio17"; + function = "blsp_uart0"; + bias-disable; + }; + }; + + serial_1_pins: serial1_pinmux { + mux { + /* We use the i2c-0 pins for serial_1 */ + pins = "gpio8", "gpio9"; + function = "blsp_uart1"; + bias-disable; + }; + }; + + i2c_0_pins: i2c_0_pinmux { + pinmux { + function = "blsp_i2c0"; + pins = "gpio20", "gpio21"; + }; + pinconf { + pins = "gpio20", "gpio21"; + drive-strength = <16>; + bias-disable; + }; + }; + + i2c_1_pins: i2c_1_pinmux { + pinmux { + function = "blsp_i2c1"; + pins = "gpio34", "gpio35"; + }; + pinconf { + pins = "gpio34", "gpio35"; + drive-strength = <16>; + bias-disable; + }; + }; + + nand_pins: nand_pins { + /* + * There are 18 pins. 15 pins are common between LCD and NAND. + * The QPIC controller arbitrates between LCD and NAND. Of the + * remaining 4, 2 are for NAND and 2 are for LCD exclusively. + * + * The meraki source hints that the bluetooth module claims + * pin 52 as well. But sadly, there's no data whenever this + * is a NAND or LCD exclusive pin or not. + */ + + pullups { + pins = "gpio52", "gpio53", "gpio58", + "gpio59"; + function = "qpic"; + bias-pull-up; + }; + + pulldowns { + pins = "gpio54", "gpio55", "gpio56", + "gpio57", "gpio60", "gpio61", + "gpio62", "gpio63", "gpio64", + "gpio65", "gpio66", "gpio67", + "gpio68", "gpio69"; + function = "qpic"; + bias-pull-down; + }; + }; +}; + +&wifi0 { + status = "okay"; + /* qcom,ath10k-calibration-variant = "MERAKI-MR33"; */ +}; + +&wifi1 { + status = "okay"; + /* qcom,ath10k-calibration-variant = "MERAKI-MR33"; */ +}; diff --git a/target/linux/ipq40xx/image/Makefile b/target/linux/ipq40xx/image/Makefile index dd71746cc02..1abbb404d76 100644 --- a/target/linux/ipq40xx/image/Makefile +++ b/target/linux/ipq40xx/image/Makefile @@ -77,6 +77,17 @@ define Device/glinet_gl-b1300 endef TARGET_DEVICES += glinet_gl-b1300 +define Device/meraki_mr33 + $(call Device/FitImage) + DEVICE_DTS := qcom-ipq4029-mr33 + BLOCKSIZE := 131072 + PAGESIZE := 2048 + DEVICE_TITLE := Cisco Meraki MR33 + IMAGES = sysupgrade.bin + DEVICE_PACKAGES := -swconfig ipq-wifi-meraki_mr33 ath10k-firmware-qca9887 +endef +TARGET_DEVICES += meraki_mr33 + define Device/openmesh_a42 $(call Device/FitImageLzma) DEVICE_DTS := qcom-ipq4018-a42 diff --git a/target/linux/ipq40xx/patches-4.14/069-arm-boot-add-dts-files.patch b/target/linux/ipq40xx/patches-4.14/069-arm-boot-add-dts-files.patch index 745a39e05db..b472c122d09 100644 --- a/target/linux/ipq40xx/patches-4.14/069-arm-boot-add-dts-files.patch +++ b/target/linux/ipq40xx/patches-4.14/069-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,12 @@ dtb-$(CONFIG_ARCH_QCOM) += \ +@@ -697,7 +697,13 @@ dtb-$(CONFIG_ARCH_QCOM) += \ qcom-apq8074-dragonboard.dtb \ qcom-apq8084-ifc6540.dtb \ qcom-apq8084-mtp.dtb \ @@ -20,6 +20,7 @@ Signed-off-by: John Crispin + qcom-ipq4019-ap.dk04.1-c1.dtb \ + qcom-ipq4019-fritz4040.dtb \ + qcom-ipq4029-gl-b1300.dtb \ ++ qcom-ipq4029-mr33.dtb \ qcom-ipq8064-ap148.dtb \ qcom-msm8660-surf.dtb \ qcom-msm8960-cdp.dtb \ diff --git a/target/linux/ipq40xx/patches-4.14/712-mr33-essedma.patch b/target/linux/ipq40xx/patches-4.14/712-mr33-essedma.patch new file mode 100644 index 00000000000..6be30070d66 --- /dev/null +++ b/target/linux/ipq40xx/patches-4.14/712-mr33-essedma.patch @@ -0,0 +1,340 @@ +--- a/drivers/net/ethernet/qualcomm/essedma/edma_axi.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma_axi.c +@@ -17,6 +17,11 @@ + #include + #include + #include ++#include ++#include ++#include ++#include ++#include + #include "edma.h" + #include "ess_edma.h" + +@@ -83,7 +88,103 @@ void edma_read_reg(u16 reg_addr, volatil + *reg_value = readl((void __iomem *)(edma_hw_addr + reg_addr)); + } + +-/* edma_change_tx_coalesce() ++static void ess_write_reg(struct edma_common_info *edma, u16 reg_addr, u32 reg_value) ++{ ++ writel(reg_value, ((void __iomem *) ++ ((unsigned long)edma->ess_hw_addr + reg_addr))); ++} ++ ++static void ess_read_reg(struct edma_common_info *edma, u16 reg_addr, ++ volatile u32 *reg_value) ++{ ++ *reg_value = readl((void __iomem *) ++ ((unsigned long)edma->ess_hw_addr + reg_addr)); ++} ++ ++static int ess_reset(struct edma_common_info *edma) ++{ ++ struct device_node *switch_node = NULL; ++ struct reset_control *ess_rst; ++ u32 regval; ++ ++ switch_node = of_find_node_by_name(NULL, "ess-switch"); ++ if (!switch_node) { ++ pr_err("switch-node not found\n"); ++ return -EINVAL; ++ } ++ ++ ess_rst = of_reset_control_get(switch_node, "ess_rst"); ++ of_node_put(switch_node); ++ ++ if (IS_ERR(ess_rst)) { ++ pr_err("failed to find ess_rst!\n"); ++ return -ENOENT; ++ } ++ ++ reset_control_assert(ess_rst); ++ msleep(10); ++ reset_control_deassert(ess_rst); ++ msleep(100); ++ reset_control_put(ess_rst); ++ ++ /* Enable only port 5 <--> port 0 ++ * bits 0:6 bitmap of ports it can fwd to */ ++#define SET_PORT_BMP(r,v) \ ++ ess_read_reg(edma, r, ®val); \ ++ ess_write_reg(edma, r, ((regval & ~0x3F) | v)); ++ ++ SET_PORT_BMP(ESS_PORT0_LOOKUP_CTRL,0x20); ++ SET_PORT_BMP(ESS_PORT1_LOOKUP_CTRL,0x00); ++ SET_PORT_BMP(ESS_PORT2_LOOKUP_CTRL,0x00); ++ SET_PORT_BMP(ESS_PORT3_LOOKUP_CTRL,0x00); ++ SET_PORT_BMP(ESS_PORT4_LOOKUP_CTRL,0x00); ++ SET_PORT_BMP(ESS_PORT5_LOOKUP_CTRL,0x01); ++ ess_write_reg(edma, ESS_RGMII_CTRL, 0x400); ++ ess_write_reg(edma, ESS_PORT0_STATUS, ESS_PORT_1G_FDX); ++ ess_write_reg(edma, ESS_PORT5_STATUS, ESS_PORT_1G_FDX); ++ ess_write_reg(edma, ESS_PORT0_HEADER_CTRL, 0); ++#undef SET_PORT_BMP ++ ++ /* forward multicast and broadcast frames to CPU */ ++ ess_write_reg(edma, ESS_FWD_CTRL1, ++ (ESS_PORTS_ALL << ESS_FWD_CTRL1_UC_FLOOD_S) | ++ (ESS_PORTS_ALL << ESS_FWD_CTRL1_MC_FLOOD_S) | ++ (ESS_PORTS_ALL << ESS_FWD_CTRL1_BC_FLOOD_S)); ++ ++ return 0; ++} ++ ++void ess_set_port_status_speed(struct edma_common_info *edma, ++ struct phy_device *phydev, uint8_t port_id) ++{ ++ uint16_t reg_off = ESS_PORT0_STATUS + (4 * port_id); ++ uint32_t reg_val = 0; ++ ++ ess_read_reg(edma, reg_off, ®_val); ++ ++ /* reset the speed bits [0:1] */ ++ reg_val &= ~ESS_PORT_STATUS_SPEED_INV; ++ ++ /* set the new speed */ ++ switch(phydev->speed) { ++ case SPEED_1000: reg_val |= ESS_PORT_STATUS_SPEED_1000; break; ++ case SPEED_100: reg_val |= ESS_PORT_STATUS_SPEED_100; break; ++ case SPEED_10: reg_val |= ESS_PORT_STATUS_SPEED_10; break; ++ default: reg_val |= ESS_PORT_STATUS_SPEED_INV; break; ++ } ++ ++ /* check full/half duplex */ ++ if (phydev->duplex) { ++ reg_val |= ESS_PORT_STATUS_DUPLEX_MODE; ++ } else { ++ reg_val &= ~ESS_PORT_STATUS_DUPLEX_MODE; ++ } ++ ++ ess_write_reg(edma, reg_off, reg_val); ++} ++ ++/* ++ * edma_change_tx_coalesce() + * change tx interrupt moderation timer + */ + void edma_change_tx_coalesce(int usecs) +@@ -551,6 +652,31 @@ static struct ctl_table edma_table[] = { + {} + }; + ++static int ess_parse(struct edma_common_info *edma) ++{ ++ struct device_node *switch_node; ++ int ret = -EINVAL; ++ ++ switch_node = of_find_node_by_name(NULL, "ess-switch"); ++ if (!switch_node) { ++ pr_err("cannot find ess-switch node\n"); ++ goto out; ++ } ++ ++ edma->ess_hw_addr = of_io_request_and_map(switch_node, ++ 0, KBUILD_MODNAME); ++ if (!edma->ess_hw_addr) { ++ pr_err("%s ioremap fail.", __func__); ++ goto out; ++ } ++ ++ edma->ess_clk = of_clk_get_by_name(switch_node, "ess_clk"); ++ ret = clk_prepare_enable(edma->ess_clk); ++out: ++ of_node_put(switch_node); ++ return ret; ++} ++ + /* edma_axi_netdev_ops + * Describe the operations supported by registered netdevices + * +@@ -786,6 +912,17 @@ static int edma_axi_probe(struct platfor + miibus = mdio_data->mii_bus; + } + ++ if (of_property_read_bool(np, "qcom,single-phy") && ++ edma_cinfo->num_gmac == 1) { ++ err = ess_parse(edma_cinfo); ++ if (!err) ++ err = ess_reset(edma_cinfo); ++ if (err) ++ goto err_single_phy_init; ++ else ++ edma_cinfo->is_single_phy = true; ++ } ++ + for_each_available_child_of_node(np, pnp) { + const char *mac_addr; + +@@ -1074,11 +1211,15 @@ static int edma_axi_probe(struct platfor + + for (i = 0; i < edma_cinfo->num_gmac; i++) { + if (adapter[i]->poll_required) { ++ int phy_mode = of_get_phy_mode(np); ++ ++ if (phy_mode < 0) ++ phy_mode = PHY_INTERFACE_MODE_SGMII; + adapter[i]->phydev = + phy_connect(edma_netdev[i], + (const char *)adapter[i]->phy_id, + &edma_adjust_link, +- PHY_INTERFACE_MODE_SGMII); ++ phy_mode); + if (IS_ERR(adapter[i]->phydev)) { + dev_dbg(&pdev->dev, "PHY attach FAIL"); + err = -EIO; +@@ -1125,6 +1266,9 @@ err_rmap_alloc_fail: + for (i = 0; i < edma_cinfo->num_gmac; i++) + unregister_netdev(edma_netdev[i]); + err_register: ++err_single_phy_init: ++ iounmap(edma_cinfo->ess_hw_addr); ++ clk_disable_unprepare(edma_cinfo->ess_clk); + err_mdiobus_init_fail: + edma_free_rx_rings(edma_cinfo); + err_rx_rinit: +@@ -1185,6 +1329,8 @@ static int edma_axi_remove(struct platfo + del_timer_sync(&edma_stats_timer); + edma_free_irqs(adapter); + unregister_net_sysctl_table(edma_cinfo->edma_ctl_table_hdr); ++ iounmap(edma_cinfo->ess_hw_addr); ++ clk_disable_unprepare(edma_cinfo->ess_clk); + edma_free_tx_resources(edma_cinfo); + edma_free_rx_resources(edma_cinfo); + edma_free_tx_rings(edma_cinfo); +--- a/drivers/net/ethernet/qualcomm/essedma/edma.c ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.c +@@ -161,8 +161,10 @@ static void edma_configure_rx(struct edm + /* Set Rx FIFO threshold to start to DMA data to host */ + rxq_ctrl_data = EDMA_FIFO_THRESH_128_BYTE; + +- /* Set RX remove vlan bit */ +- rxq_ctrl_data |= EDMA_RXQ_CTRL_RMV_VLAN; ++ if (!edma_cinfo->is_single_phy) { ++ /* Set RX remove vlan bit */ ++ rxq_ctrl_data |= EDMA_RXQ_CTRL_RMV_VLAN; ++ } + + edma_write_reg(EDMA_REG_RXQ_CTRL, rxq_ctrl_data); + } +@@ -1295,6 +1297,10 @@ void edma_adjust_link(struct net_device + if (status == __EDMA_LINKUP && adapter->link_state == __EDMA_LINKDOWN) { + dev_info(&adapter->pdev->dev, "%s: GMAC Link is up with phy_speed=%d\n", netdev->name, phydev->speed); + adapter->link_state = __EDMA_LINKUP; ++ if (adapter->edma_cinfo->is_single_phy) { ++ ess_set_port_status_speed(adapter->edma_cinfo, phydev, ++ ffs(adapter->dp_bitmap) - 1); ++ } + netif_carrier_on(netdev); + if (netif_running(netdev)) + netif_tx_wake_all_queues(netdev); +@@ -1388,10 +1394,12 @@ netdev_tx_t edma_xmit(struct sk_buff *sk + } + + /* Check and mark VLAN tag offload */ +- if (skb_vlan_tag_present(skb)) +- flags_transmit |= EDMA_VLAN_TX_TAG_INSERT_FLAG; +- else if (adapter->default_vlan_tag) +- flags_transmit |= EDMA_VLAN_TX_TAG_INSERT_DEFAULT_FLAG; ++ if (!adapter->edma_cinfo->is_single_phy) { ++ if (unlikely(skb_vlan_tag_present(skb))) ++ flags_transmit |= EDMA_VLAN_TX_TAG_INSERT_FLAG; ++ else if (adapter->default_vlan_tag) ++ flags_transmit |= EDMA_VLAN_TX_TAG_INSERT_DEFAULT_FLAG; ++ } + + /* Check and mark checksum offload */ + if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) +--- a/drivers/net/ethernet/qualcomm/essedma/edma.h ++++ b/drivers/net/ethernet/qualcomm/essedma/edma.h +@@ -31,6 +31,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -331,6 +332,10 @@ struct edma_common_info { + struct edma_hw hw; /* edma hw specific structure */ + struct edma_per_cpu_queues_info edma_percpu_info[CONFIG_NR_CPUS]; /* per cpu information */ + spinlock_t stats_lock; /* protect edma stats area for updation */ ++ ++ bool is_single_phy; ++ void __iomem *ess_hw_addr; ++ struct clk *ess_clk; + }; + + /* transimit packet descriptor (tpd) ring */ +@@ -443,4 +448,6 @@ void edma_change_tx_coalesce(int usecs); + void edma_change_rx_coalesce(int usecs); + void edma_get_tx_rx_coalesce(u32 *reg_val); + void edma_clear_irq_status(void); ++void ess_set_port_status_speed(struct edma_common_info *edma_cinfo, ++ struct phy_device *phydev, uint8_t port_id); + #endif /* _EDMA_H_ */ +--- a/drivers/net/ethernet/qualcomm/essedma/ess_edma.h ++++ b/drivers/net/ethernet/qualcomm/essedma/ess_edma.h +@@ -329,4 +329,61 @@ struct edma_hw; + #define EDMA_RRD_PRIORITY_MASK 0x7 + #define EDMA_RRD_PORT_TYPE_SHIFT 7 + #define EDMA_RRD_PORT_TYPE_MASK 0x1F ++ ++#define ESS_RGMII_CTRL 0x0004 ++ ++/* Port status registers */ ++#define ESS_PORT0_STATUS 0x007C ++#define ESS_PORT1_STATUS 0x0080 ++#define ESS_PORT2_STATUS 0x0084 ++#define ESS_PORT3_STATUS 0x0088 ++#define ESS_PORT4_STATUS 0x008C ++#define ESS_PORT5_STATUS 0x0090 ++ ++#define ESS_PORT_STATUS_HDX_FLOW_CTL 0x80 ++#define ESS_PORT_STATUS_DUPLEX_MODE 0x40 ++#define ESS_PORT_STATUS_RX_FLOW_EN 0x20 ++#define ESS_PORT_STATUS_TX_FLOW_EN 0x10 ++#define ESS_PORT_STATUS_RX_MAC_EN 0x08 ++#define ESS_PORT_STATUS_TX_MAC_EN 0x04 ++#define ESS_PORT_STATUS_SPEED_INV 0x03 ++#define ESS_PORT_STATUS_SPEED_1000 0x02 ++#define ESS_PORT_STATUS_SPEED_100 0x01 ++#define ESS_PORT_STATUS_SPEED_10 0x00 ++ ++#define ESS_PORT_1G_FDX (ESS_PORT_STATUS_DUPLEX_MODE | ESS_PORT_STATUS_RX_FLOW_EN | \ ++ ESS_PORT_STATUS_TX_FLOW_EN | ESS_PORT_STATUS_RX_MAC_EN | \ ++ ESS_PORT_STATUS_TX_MAC_EN | ESS_PORT_STATUS_SPEED_1000) ++ ++#define PHY_STATUS_REG 0x11 ++#define PHY_STATUS_SPEED 0xC000 ++#define PHY_STATUS_SPEED_SHIFT 14 ++#define PHY_STATUS_DUPLEX 0x2000 ++#define PHY_STATUS_DUPLEX_SHIFT 13 ++#define PHY_STATUS_SPEED_DUPLEX_RESOLVED 0x0800 ++#define PHY_STATUS_CARRIER 0x0400 ++#define PHY_STATUS_CARRIER_SHIFT 10 ++ ++/* Port lookup control registers */ ++#define ESS_PORT0_LOOKUP_CTRL 0x0660 ++#define ESS_PORT1_LOOKUP_CTRL 0x066C ++#define ESS_PORT2_LOOKUP_CTRL 0x0678 ++#define ESS_PORT3_LOOKUP_CTRL 0x0684 ++#define ESS_PORT4_LOOKUP_CTRL 0x0690 ++#define ESS_PORT5_LOOKUP_CTRL 0x069C ++ ++#define ESS_PORT0_HEADER_CTRL 0x009C ++ ++#define ESS_PORTS_ALL 0x3f ++ ++#define ESS_FWD_CTRL1 0x0624 ++#define ESS_FWD_CTRL1_UC_FLOOD BITS(0, 7) ++#define ESS_FWD_CTRL1_UC_FLOOD_S 0 ++#define ESS_FWD_CTRL1_MC_FLOOD BITS(8, 7) ++#define ESS_FWD_CTRL1_MC_FLOOD_S 8 ++#define ESS_FWD_CTRL1_BC_FLOOD BITS(16, 7) ++#define ESS_FWD_CTRL1_BC_FLOOD_S 16 ++#define ESS_FWD_CTRL1_IGMP BITS(24, 7) ++#define ESS_FWD_CTRL1_IGMP_S 24 ++ + #endif /* _ESS_EDMA_H_ */ -- 2.30.2