Marco von Rosenberg 06cdc07f8c ath79: add support for Huawei AP5030DN
Huawei AP5030DN is a dual-band, dual-radio 802.11ac Wave 1 3x3 MIMO
enterprise access point with two Gigabit Ethernet ports and PoE
support.

Hardware highlights:
- CPU: QCA9550 SoC at 720MHz
- RAM: 256MB DDR2
- Flash: 32MB SPI-NOR
- Wi-Fi 2.4GHz: QCA9550-internal radio
- Wi-Fi 5GHz: QCA9880 PCIe WLAN SoC
- Ethernet 1: 10/100/1000 Mbps Ethernet through Broadcom B50612E PHY
- Ethernet 2: 10/100/1000 Mbps Ethernet through Marvell 88E1510 PHY
- PoE: input through Ethernet 1 port
- Standalone 12V/2A power input
- Serial console externally available through RJ45 port
- External watchdog: SGM706 (1.6s timeout)

Serial console:
  9600n8 (9600 baud, no stop bits, no parity, 8 data bits)

MAC addresses:
  Each device has 32 consecutive MAC addresses allocated by
  the vendor, which don't overlap between devices.
  This was confirmed with multiple devices with consecutive
  serial numbers.
  The MAC address range starts with the address on the label.
  To be able to distinguish between the interfaces,
  the following MAC address scheme is used:
    - eth0 = label MAC
    - eth1 = label MAC + 1
    - radio0 (Wi-Fi 5GHz) = label MAC + 2
    - radio1 (Wi-Fi 2.4GHz) = label MAC + 3

Installation:
0. Connect some sort of RJ45-to-USB adapter to "Console" port of the AP

1. Power up the AP

2. At prompt "Press f or F  to stop Auto-Boot in 3 seconds",
   do what they say.
   Log in with default admin password "admin@huawei.com".

3. Boot the OpenWrt initramfs from TFTP using the hidden script
   "run ramboot". Replace IP address as needed:

   > setenv serverip 192.168.1.10
   > setenv ipaddr 192.168.1.1
   > setenv rambootfile
     openwrt-ath79-generic-huawei_ap5030dn-initramfs-kernel.bin
   > saveenv
   > run ramboot

4. Optional but recommended as the factory firmware cannot
   be downloaded publicly:
   Back up contents of "firmware" partition using the web interface or ssh:

   $ ssh root@192.168.1.1 cat /dev/mtd11 > huawei_ap5030dn_fw_backup.bin

5. Run sysupgrade using sysupgrade image. OpenWrt
   shall boot from flash afterwards.

Return to factory firmware (using firmware upgrade package downloaded from
non-public Huawei website):
1. Start a TFTP server in the directory where
   the firmware upgrade package is located

2. Boot to u-boot as described above

3. Install firmware upgrade package and format the config partitions:

   > update system FatAP5X30XN_SOMEVERSION.bin
   > format_fs

Return to factory firmware (from previously created backup):
1. Copy over the firmware partition backup to /tmp,
   for example using scp

2. Use sysupgrade with force to restore the backup:
   sysupgrade -F huawei_ap5030dn_fw_backup.bin

3. Boot AP to U-Boot as described above

Quirks and known issues
-----------------------

- On initial power-up, the Huawei-modified bootloader suspends both
ethernet PHYs (it sets the "Power Down" bit in the MII control
register). Unfortunately, at the time of the initial port, the kernel
driver for the B50612E/BCM54612E PHY behind eth0 doesn't have a resume
callback defined which would clear this bit. This makes the PHY unusable
since it remains suspended forever. This is why the backported kernel
patches in this commit are required which add this callback and for
completeness also a suspend callback.

- The stock firmware has a semi dual boot concept where the primary
kernel uses a squashfs as root partition and the secondary kernel uses
an initramfs. This dual boot concept is circumvented on purpose to gain
more flash space and since the stock firmware's flash layout isn't
compatible with mtdsplit.

- The external watchdog's timeout of 1.6s is very hard to satisfy
during bootup. This is why the GPIO15 pin connected to the watchdog input
is configured directly in the LZMA loader to output the CPU_CLK/4 signal
which keeps the watchdog happy until the wdt-gpio kernel driver takes
over. Because it would also take too long to read the whole kernel image
from flash, the uImage header only includes the loader which then reads
the kernel image from flash after GPIO15 is configured.

Signed-off-by: Marco von Rosenberg <marcovr@selfnet.de>
[fixed 6.6 backport patch naming]
Signed-off-by: David Bauer <mail@david-bauer.net>
2024-03-31 18:09:43 +02:00

216 lines
5.5 KiB
C

/*
* LZMA compressed kernel loader for Atheros AR7XXX/AR9XXX based boards
*
* Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
*
* 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 <stddef.h>
#include "config.h"
#include "printf.h"
#include "ar71xx_regs.h"
#define READREG(r) *(volatile unsigned int *)(r)
#define WRITEREG(r,v) *(volatile unsigned int *)(r) = v
#define KSEG1ADDR(_x) (((_x) & 0x1fffffff) | 0xa0000000)
#define UART_BASE 0xb8020000
#define UART_TX 0
#define UART_LSR 5
#define UART_LSR_THRE 0x20
#define UART_READ(r) READREG(UART_BASE + 4 * (r))
#define UART_WRITE(r,v) WRITEREG(UART_BASE + 4 * (r), (v))
void board_putc(int ch)
{
while (((UART_READ(UART_LSR)) & UART_LSR_THRE) == 0);
UART_WRITE(UART_TX, ch);
while (((UART_READ(UART_LSR)) & UART_LSR_THRE) == 0);
}
#ifdef CONFIG_BOARD_TL_WR1043ND_V1
static void tlwr1043nd_init(void)
{
unsigned int reg = KSEG1ADDR(AR71XX_RESET_BASE);
unsigned int t;
t = READREG(reg + AR913X_RESET_REG_RESET_MODULE);
t |= AR71XX_RESET_GE0_PHY;
WRITEREG(reg + AR913X_RESET_REG_RESET_MODULE, t);
/* flush write */
t = READREG(reg + AR913X_RESET_REG_RESET_MODULE);
}
#else
static inline void tlwr1043nd_init(void) {}
#endif
#ifdef CONFIG_BOARD_MERAKI_MR18
static int mr18_extract_sgmii_res_cal(void)
{
unsigned int base;
unsigned int reversed_sgmii_value;
unsigned int otp_value, otp_per_val, rbias_per, read_data;
unsigned int rbias_pos_or_neg;
unsigned int sgmii_res_cal_value;
int res_cal_val;
base = KSEG1ADDR(QCA955X_OTP_BASE);
WRITEREG(base + QCA955X_OTP_REG_INTF2, 0x7d);
WRITEREG(base + QCA955X_OTP_REG_LDO_CTRL, 0x00);
while (READREG(base + QCA955X_OTP_REG_LDO_STATUS) &
QCA955X_OTP_LDO_STATUS_POWER_ON)
;
READREG(base + QCA955X_OTP_REG_MEM_0 + 4);
while (!(READREG(base + QCA955X_OTP_REG_STATUS0) &
QCA955X_OTP_STATUS0_EFUSE_VALID))
;
read_data = READREG(base + QCA955X_OTP_REG_STATUS1);
if (!(read_data & 0x1fff))
return 0;
if (read_data & 0x00001000)
otp_value = (read_data & 0xfc0) >> 6;
else
otp_value = read_data & 0x3f;
if (otp_value > 31) {
otp_per_val = 63 - otp_value;
rbias_pos_or_neg = 1;
} else {
otp_per_val = otp_value;
rbias_pos_or_neg = 0;
}
rbias_per = otp_per_val * 15;
if (rbias_pos_or_neg == 1)
res_cal_val = (rbias_per + 34) / 21;
else if (rbias_per > 34)
res_cal_val = -((rbias_per - 34) / 21);
else
res_cal_val = (34 - rbias_per) / 21;
sgmii_res_cal_value = (8 + res_cal_val) & 0xf;
reversed_sgmii_value = (sgmii_res_cal_value & 8) >> 3;
reversed_sgmii_value |= (sgmii_res_cal_value & 4) >> 1;
reversed_sgmii_value |= (sgmii_res_cal_value & 2) << 1;
reversed_sgmii_value |= (sgmii_res_cal_value & 1) << 3;
printf("SGMII cal value = 0x%x\n", reversed_sgmii_value);
return reversed_sgmii_value;
}
#define QCA955X_SGMII_SERDES_RES_CALIBRATION BIT(23)
#define QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK 0xf
#define QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT 23
#define QCA955X_SGMII_SERDES_LOCK_DETECT_STATUS BIT(15)
#define QCA955X_PLL_ETH_SGMII_SERDES_LOCK_DETECT BIT(2)
#define QCA955X_PLL_ETH_SGMII_SERDES_PLL_REFCLK BIT(1)
#define QCA955X_PLL_ETH_SGMII_SERDES_EN_PLL BIT(0)
#define QCA955X_PLL_CLK_CTRL_REG 0x08
#define QCA955X_PLL_ETH_XMII_CONTROL_REG 0x28
#define QCA955X_PLL_ETH_SGMII_CONTROL_REG 0x48
#define QCA955X_PLL_ETH_SGMII_SERDES_REG 0x4c
static void qca955x_device_reset_clear(unsigned int mask)
{
unsigned int t, reg;
reg = KSEG1ADDR(AR71XX_RESET_BASE +
QCA955X_RESET_REG_RESET_MODULE);
t = READREG(reg);
WRITEREG(reg, t & ~mask);
}
static void mr18_setup_qca955x_eth_serdes_cal(unsigned int sgmii_value)
{
unsigned int ethbase, pllbase, t;
ethbase = KSEG1ADDR(QCA955X_GMAC_BASE);
pllbase = KSEG1ADDR(AR71XX_PLL_BASE);
/* To Check the locking of the SGMII PLL */
t = READREG(ethbase + QCA955X_GMAC_REG_SGMII_SERDES);
t &= ~(QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK <<
QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT);
t |= (sgmii_value & QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK) <<
QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT;
WRITEREG(ethbase + QCA955X_GMAC_REG_SGMII_SERDES, t);
WRITEREG(pllbase + QCA955X_PLL_ETH_SGMII_SERDES_REG,
QCA955X_PLL_ETH_SGMII_SERDES_LOCK_DETECT |
QCA955X_PLL_ETH_SGMII_SERDES_PLL_REFCLK |
QCA955X_PLL_ETH_SGMII_SERDES_EN_PLL)
;
qca955x_device_reset_clear(QCA955X_RESET_SGMII_ANALOG);
qca955x_device_reset_clear(QCA955X_RESET_SGMII);
while (!(READREG(ethbase + QCA955X_GMAC_REG_SGMII_SERDES) &
QCA955X_SGMII_SERDES_LOCK_DETECT_STATUS))
;
}
static inline void mr18_init(void)
{
int res;
printf("Meraki MR18\n");
res = mr18_extract_sgmii_res_cal();
if (res >= 0)
mr18_setup_qca955x_eth_serdes_cal(res);
}
#else
static inline void mr18_init(void) { }
#endif
#ifdef CONFIG_BOARD_HUAWEI_AP5030DN
static inline void ap5030dn_init(void)
{
const unsigned int ap5030dn_watchdog_gpio = 15;
unsigned int gpiobase, reg;
gpiobase = KSEG1ADDR(AR71XX_GPIO_BASE);
printf("Huawei AP5030DN\n");
reg = READREG(gpiobase + AR71XX_GPIO_REG_OE);
WRITEREG(gpiobase + AR71XX_GPIO_REG_OE,
reg & ~(1 << ap5030dn_watchdog_gpio));
/* Set GPIO15 MUX to output CLK_OBS5 (= CPU_CLK/4)
* to keep the watchdog happy until wdt-gpio takes over
*/
reg = READREG(gpiobase + AR934X_GPIO_REG_OUT_FUNC3);
WRITEREG(gpiobase + AR934X_GPIO_REG_OUT_FUNC3,
reg | (QCA955X_GPIO_OUTSEL_CLK_OBS5 << 24));
}
#else
static inline void ap5030dn_init(void) { }
#endif
void board_init(void)
{
tlwr1043nd_init();
mr18_init();
ap5030dn_init();
}