
Airoha AN8855 is a 5-port gigabit switch with a 2.5G HSGMII CPU port
Signed-off-by: Weijie Gao weijie.gao@mediatek.com --- drivers/net/mtk_eth/Kconfig | 4 + drivers/net/mtk_eth/Makefile | 1 + drivers/net/mtk_eth/an8855.c | 1096 ++++++++++++++++++++++++++++++++++ 3 files changed, 1101 insertions(+) create mode 100644 drivers/net/mtk_eth/an8855.c
diff --git a/drivers/net/mtk_eth/Kconfig b/drivers/net/mtk_eth/Kconfig index 467684749f1..e8cdf408237 100644 --- a/drivers/net/mtk_eth/Kconfig +++ b/drivers/net/mtk_eth/Kconfig @@ -32,4 +32,8 @@ config MTK_ETH_SWITCH_MT7988 depends on TARGET_MT7988 default y
+config MTK_ETH_SWITCH_AN8855 + bool "Support for Airoha AN8855 ethernet switch" + default y if TARGET_MT7981 || TARGET_MT7987 + endif # MEDIATEK_ETH diff --git a/drivers/net/mtk_eth/Makefile b/drivers/net/mtk_eth/Makefile index 885375c5b38..a342325ed5d 100644 --- a/drivers/net/mtk_eth/Makefile +++ b/drivers/net/mtk_eth/Makefile @@ -7,3 +7,4 @@ obj-y += mtk_eth.o obj-$(CONFIG_MTK_ETH_SWITCH_MT7530) += mt753x.o mt7530.o obj-$(CONFIG_MTK_ETH_SWITCH_MT7531) += mt753x.o mt7531.o obj-$(CONFIG_MTK_ETH_SWITCH_MT7988) += mt753x.o mt7988.o +obj-$(CONFIG_MTK_ETH_SWITCH_AN8855) += an8855.o diff --git a/drivers/net/mtk_eth/an8855.c b/drivers/net/mtk_eth/an8855.c new file mode 100644 index 00000000000..4bd7506a58b --- /dev/null +++ b/drivers/net/mtk_eth/an8855.c @@ -0,0 +1,1096 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 MediaTek Inc. + * + * Author: Neal Yen neal.yen@mediatek.com + * Author: Weijie Gao weijie.gao@mediatek.com + */ + +#include <phy.h> +#include <miiphy.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/mdio.h> +#include <linux/mii.h> +#include "mtk_eth.h" + +/* AN8855 Register Definitions */ +#define AN8855_SYS_CTRL_REG 0x100050c0 +#define AN8855_SW_SYS_RST BIT(31) + +#define AN8855_PMCR_REG(p) (0x10210000 + (p) * 0x200) +#define AN8855_FORCE_MODE_LNK BIT(31) +#define AN8855_FORCE_MODE 0xb31593f0 + +#define AN8855_PORT_CTRL_BASE (0x10208000) +#define AN8855_PORT_CTRL_REG(p, r) (AN8855_PORT_CTRL_BASE + (p) * 0x200 + (r)) + +#define AN8855_PORTMATRIX_REG(p) AN8855_PORT_CTRL_REG(p, 0x44) + +#define AN8855_PVC(p) AN8855_PORT_CTRL_REG(p, 0x10) +#define AN8855_STAG_VPID_S 16 +#define AN8855_STAG_VPID_M 0xffff0000 +#define AN8855_VLAN_ATTR_S 6 +#define AN8855_VLAN_ATTR_M 0xc0 + +#define VLAN_ATTR_USER 0 + +#define AN8855_INT_MASK 0x100050F0 +#define AN8855_INT_SYS_BIT BIT(15) + +#define AN8855_RG_CLK_CPU_ICG 0x10005034 +#define AN8855_MCU_ENABLE BIT(3) + +#define AN8855_RG_TIMER_CTL 0x1000a100 +#define AN8855_WDOG_ENABLE BIT(25) + +#define AN8855_CKGCR 0x10213e1c + +#define AN8855_SCU_BASE 0x10000000 +#define AN8855_RG_RGMII_TXCK_C (AN8855_SCU_BASE + 0x1d0) +#define AN8855_RG_GPIO_LED_MODE (AN8855_SCU_BASE + 0x0054) +#define AN8855_RG_GPIO_LED_SEL(i) (AN8855_SCU_BASE + (0x0058 + ((i) * 4))) +#define AN8855_RG_INTB_MODE (AN8855_SCU_BASE + 0x0080) +#define AN8855_RG_GDMP_RAM (AN8855_SCU_BASE + 0x10000) +#define AN8855_RG_GPIO_L_INV (AN8855_SCU_BASE + 0x0010) +#define AN8855_RG_GPIO_CTRL (AN8855_SCU_BASE + 0xa300) +#define AN8855_RG_GPIO_DATA (AN8855_SCU_BASE + 0xa304) +#define AN8855_RG_GPIO_OE (AN8855_SCU_BASE + 0xa314) + +#define AN8855_HSGMII_AN_CSR_BASE 0x10220000 +#define AN8855_SGMII_REG_AN0 (AN8855_HSGMII_AN_CSR_BASE + 0x000) +#define AN8855_SGMII_REG_AN_13 (AN8855_HSGMII_AN_CSR_BASE + 0x034) +#define AN8855_SGMII_REG_AN_FORCE_CL37 (AN8855_HSGMII_AN_CSR_BASE + 0x060) + +#define AN8855_HSGMII_CSR_PCS_BASE 0x10220000 +#define AN8855_RG_HSGMII_PCS_CTROL_1 (AN8855_HSGMII_CSR_PCS_BASE + 0xa00) +#define AN8855_RG_AN_SGMII_MODE_FORCE (AN8855_HSGMII_CSR_PCS_BASE + 0xa24) + +#define AN8855_MULTI_SGMII_CSR_BASE 0x10224000 +#define AN8855_SGMII_STS_CTRL_0 (AN8855_MULTI_SGMII_CSR_BASE + 0x018) +#define AN8855_MSG_RX_CTRL_0 (AN8855_MULTI_SGMII_CSR_BASE + 0x100) +#define AN8855_MSG_RX_LIK_STS_0 (AN8855_MULTI_SGMII_CSR_BASE + 0x514) +#define AN8855_MSG_RX_LIK_STS_2 (AN8855_MULTI_SGMII_CSR_BASE + 0x51c) +#define AN8855_PHY_RX_FORCE_CTRL_0 (AN8855_MULTI_SGMII_CSR_BASE + 0x520) + +#define AN8855_XFI_CSR_PCS_BASE 0x10225000 +#define AN8855_RG_USXGMII_AN_CONTROL_0 (AN8855_XFI_CSR_PCS_BASE + 0xbf8) + +#define AN8855_MULTI_PHY_RA_CSR_BASE 0x10226000 +#define AN8855_RG_RATE_ADAPT_CTRL_0 (AN8855_MULTI_PHY_RA_CSR_BASE + 0x000) +#define AN8855_RATE_ADP_P0_CTRL_0 (AN8855_MULTI_PHY_RA_CSR_BASE + 0x100) +#define AN8855_MII_RA_AN_ENABLE (AN8855_MULTI_PHY_RA_CSR_BASE + 0x300) + +#define AN8855_QP_DIG_CSR_BASE 0x1022a000 +#define AN8855_QP_CK_RST_CTRL_4 (AN8855_QP_DIG_CSR_BASE + 0x310) +#define AN8855_QP_DIG_MODE_CTRL_0 (AN8855_QP_DIG_CSR_BASE + 0x324) +#define AN8855_QP_DIG_MODE_CTRL_1 (AN8855_QP_DIG_CSR_BASE + 0x330) + +#define AN8855_QP_PMA_TOP_BASE 0x1022e000 +#define AN8855_PON_RXFEDIG_CTRL_0 (AN8855_QP_PMA_TOP_BASE + 0x100) +#define AN8855_PON_RXFEDIG_CTRL_9 (AN8855_QP_PMA_TOP_BASE + 0x124) + +#define AN8855_SS_LCPLL_PWCTL_SETTING_2 (AN8855_QP_PMA_TOP_BASE + 0x208) +#define AN8855_SS_LCPLL_TDC_FLT_2 (AN8855_QP_PMA_TOP_BASE + 0x230) +#define AN8855_SS_LCPLL_TDC_FLT_5 (AN8855_QP_PMA_TOP_BASE + 0x23c) +#define AN8855_SS_LCPLL_TDC_PCW_1 (AN8855_QP_PMA_TOP_BASE + 0x248) +#define AN8855_INTF_CTRL_8 (AN8855_QP_PMA_TOP_BASE + 0x320) +#define AN8855_INTF_CTRL_9 (AN8855_QP_PMA_TOP_BASE + 0x324) +#define AN8855_PLL_CTRL_0 (AN8855_QP_PMA_TOP_BASE + 0x400) +#define AN8855_PLL_CTRL_2 (AN8855_QP_PMA_TOP_BASE + 0x408) +#define AN8855_PLL_CTRL_3 (AN8855_QP_PMA_TOP_BASE + 0x40c) +#define AN8855_PLL_CTRL_4 (AN8855_QP_PMA_TOP_BASE + 0x410) +#define AN8855_PLL_CK_CTRL_0 (AN8855_QP_PMA_TOP_BASE + 0x414) +#define AN8855_RX_DLY_0 (AN8855_QP_PMA_TOP_BASE + 0x614) +#define AN8855_RX_CTRL_2 (AN8855_QP_PMA_TOP_BASE + 0x630) +#define AN8855_RX_CTRL_5 (AN8855_QP_PMA_TOP_BASE + 0x63c) +#define AN8855_RX_CTRL_6 (AN8855_QP_PMA_TOP_BASE + 0x640) +#define AN8855_RX_CTRL_7 (AN8855_QP_PMA_TOP_BASE + 0x644) +#define AN8855_RX_CTRL_8 (AN8855_QP_PMA_TOP_BASE + 0x648) +#define AN8855_RX_CTRL_26 (AN8855_QP_PMA_TOP_BASE + 0x690) +#define AN8855_RX_CTRL_42 (AN8855_QP_PMA_TOP_BASE + 0x6d0) + +#define AN8855_QP_ANA_CSR_BASE 0x1022f000 +#define AN8855_RG_QP_RX_DAC_EN (AN8855_QP_ANA_CSR_BASE + 0x00) +#define AN8855_RG_QP_RXAFE_RESERVE (AN8855_QP_ANA_CSR_BASE + 0x04) +#define AN8855_RG_QP_CDR_LPF_MJV_LIM (AN8855_QP_ANA_CSR_BASE + 0x0c) +#define AN8855_RG_QP_CDR_LPF_SETVALUE (AN8855_QP_ANA_CSR_BASE + 0x14) +#define AN8855_RG_QP_CDR_PR_CKREF_DIV1 (AN8855_QP_ANA_CSR_BASE + 0x18) +#define AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE (AN8855_QP_ANA_CSR_BASE + 0x1c) +#define AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF (AN8855_QP_ANA_CSR_BASE + 0x20) +#define AN8855_RG_QP_TX_MODE_16B_EN (AN8855_QP_ANA_CSR_BASE + 0x28) +#define AN8855_RG_QP_PLL_IPLL_DIG_PWR_SEL (AN8855_QP_ANA_CSR_BASE + 0x3c) +#define AN8855_RG_QP_PLL_SDM_ORD (AN8855_QP_ANA_CSR_BASE + 0x40) + +#define AN8855_ETHER_SYS_BASE 0x1028c800 +#define RG_GPHY_AFE_PWD (AN8855_ETHER_SYS_BASE + 0x40) + +#define AN8855_PKG_SEL 0x10000094 +#define PAG_SEL_AN8855H 0x2 + +/* PHY LED Register bitmap of define */ +#define PHY_LED_CTRL_SELECT 0x3e8 +#define PHY_SINGLE_LED_ON_CTRL(i) (0x3e0 + ((i) * 2)) +#define PHY_SINGLE_LED_BLK_CTRL(i) (0x3e1 + ((i) * 2)) +#define PHY_SINGLE_LED_ON_DUR(i) (0x3e9 + ((i) * 2)) +#define PHY_SINGLE_LED_BLK_DUR(i) (0x3ea + ((i) * 2)) + +#define PHY_PMA_CTRL (0x340) + +#define PHY_DEV1F 0x1f + +#define PHY_LED_ON_CTRL(i) (0x24 + ((i) * 2)) +#define LED_ON_EN (1 << 15) +#define LED_ON_POL (1 << 14) +#define LED_ON_EVT_MASK (0x7f) + +/* LED ON Event */ +#define LED_ON_EVT_FORCE (1 << 6) +#define LED_ON_EVT_LINK_HD (1 << 5) +#define LED_ON_EVT_LINK_FD (1 << 4) +#define LED_ON_EVT_LINK_DOWN (1 << 3) +#define LED_ON_EVT_LINK_10M (1 << 2) +#define LED_ON_EVT_LINK_100M (1 << 1) +#define LED_ON_EVT_LINK_1000M (1 << 0) + +#define PHY_LED_BLK_CTRL(i) (0x25 + ((i) * 2)) +#define LED_BLK_EVT_MASK (0x3ff) +/* LED Blinking Event */ +#define LED_BLK_EVT_FORCE (1 << 9) +#define LED_BLK_EVT_10M_RX_ACT (1 << 5) +#define LED_BLK_EVT_10M_TX_ACT (1 << 4) +#define LED_BLK_EVT_100M_RX_ACT (1 << 3) +#define LED_BLK_EVT_100M_TX_ACT (1 << 2) +#define LED_BLK_EVT_1000M_RX_ACT (1 << 1) +#define LED_BLK_EVT_1000M_TX_ACT (1 << 0) + +#define PHY_LED_BCR (0x21) +#define LED_BCR_EXT_CTRL (1 << 15) +#define LED_BCR_CLK_EN (1 << 3) +#define LED_BCR_TIME_TEST (1 << 2) +#define LED_BCR_MODE_MASK (3) +#define LED_BCR_MODE_DISABLE (0) + +#define PHY_LED_ON_DUR (0x22) +#define LED_ON_DUR_MASK (0xffff) + +#define PHY_LED_BLK_DUR (0x23) +#define LED_BLK_DUR_MASK (0xffff) + +#define PHY_LED_BLINK_DUR_CTRL (0x720) + +/* Definition of LED */ +#define LED_ON_EVENT (LED_ON_EVT_LINK_1000M | \ + LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M |\ + LED_ON_EVT_LINK_HD | LED_ON_EVT_LINK_FD) + +#define LED_BLK_EVENT (LED_BLK_EVT_1000M_TX_ACT | \ + LED_BLK_EVT_1000M_RX_ACT | \ + LED_BLK_EVT_100M_TX_ACT | \ + LED_BLK_EVT_100M_RX_ACT | \ + LED_BLK_EVT_10M_TX_ACT | \ + LED_BLK_EVT_10M_RX_ACT) + +#define LED_FREQ AIR_LED_BLK_DUR_64M + +#define AN8855_NUM_PHYS 5 +#define AN8855_NUM_PORTS 6 +#define AN8855_PHY_ADDR(base, addr) (((base) + (addr)) & 0x1f) + +/* PHY LED Register bitmap of define */ +#define PHY_LED_CTRL_SELECT 0x3e8 +#define PHY_SINGLE_LED_ON_CTRL(i) (0x3e0 + ((i) * 2)) +#define PHY_SINGLE_LED_BLK_CTRL(i) (0x3e1 + ((i) * 2)) +#define PHY_SINGLE_LED_ON_DUR(i) (0x3e9 + ((i) * 2)) +#define PHY_SINGLE_LED_BLK_DUR(i) (0x3ea + ((i) * 2)) + +/* AN8855 LED */ +enum an8855_led_blk_dur { + AIR_LED_BLK_DUR_32M, + AIR_LED_BLK_DUR_64M, + AIR_LED_BLK_DUR_128M, + AIR_LED_BLK_DUR_256M, + AIR_LED_BLK_DUR_512M, + AIR_LED_BLK_DUR_1024M, + AIR_LED_BLK_DUR_LAST +}; + +enum an8855_led_polarity { + LED_LOW, + LED_HIGH, +}; + +enum an8855_led_mode { + AN8855_LED_MODE_DISABLE, + AN8855_LED_MODE_USER_DEFINE, + AN8855_LED_MODE_LAST +}; + +enum phy_led_idx { + P0_LED0, + P0_LED1, + P0_LED2, + P0_LED3, + P1_LED0, + P1_LED1, + P1_LED2, + P1_LED3, + P2_LED0, + P2_LED1, + P2_LED2, + P2_LED3, + P3_LED0, + P3_LED1, + P3_LED2, + P3_LED3, + P4_LED0, + P4_LED1, + P4_LED2, + P4_LED3, + PHY_LED_MAX +}; + +struct an8855_led_cfg { + u16 en; + u8 phy_led_idx; + u16 pol; + u16 on_cfg; + u16 blk_cfg; + u8 led_freq; +}; + +struct an8855_switch_priv { + struct mtk_eth_switch_priv epriv; + struct mii_dev *mdio_bus; + u32 phy_base; +}; + +/* AN8855 Reference Board */ +static const struct an8855_led_cfg led_cfg[] = { +/************************************************************************* + * Enable, LED idx, LED Polarity, LED ON event, LED Blink event LED Freq + ************************************************************************* + */ + /* GPIO0 */ + {1, P4_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO1 */ + {1, P4_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO2 */ + {1, P0_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO3 */ + {1, P0_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO4 */ + {1, P1_LED0, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO5 */ + {1, P1_LED1, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO6 */ + {0, PHY_LED_MAX, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO7 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO8 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO9 */ + {1, P2_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO10 */ + {1, P2_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO11 */ + {1, P3_LED0, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO12 */ + {1, P3_LED1, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO13 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO14 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO15 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO16 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO17 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO18 */ + {0, PHY_LED_MAX, LED_HIGH, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO19 */ + {0, PHY_LED_MAX, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, + /* GPIO20 */ + {0, PHY_LED_MAX, LED_LOW, LED_ON_EVENT, LED_BLK_EVENT, LED_FREQ}, +}; + +static int __an8855_reg_read(struct mtk_eth_priv *priv, u8 phy_base, u32 reg, u32 *data) +{ + int ret, low_word, high_word; + + ret = mtk_mii_write(priv, phy_base, 0x1f, 0x4); + if (ret) + return ret; + + ret = mtk_mii_write(priv, phy_base, 0x10, 0); + if (ret) + return ret; + + ret = mtk_mii_write(priv, phy_base, 0x15, ((reg >> 16) & 0xFFFF)); + if (ret) + return ret; + + ret = mtk_mii_write(priv, phy_base, 0x16, (reg & 0xFFFF)); + if (ret) + return ret; + + low_word = mtk_mii_read(priv, phy_base, 0x18); + if (low_word < 0) + return low_word; + + high_word = mtk_mii_read(priv, phy_base, 0x17); + if (high_word < 0) + return high_word; + + ret = mtk_mii_write(priv, phy_base, 0x1f, 0); + if (ret) + return ret; + + ret = mtk_mii_write(priv, phy_base, 0x10, 0); + if (ret) + return ret; + + if (data) + *data = ((u32)high_word << 16) | (low_word & 0xffff); + + return 0; +} + +static int an8855_reg_read(struct an8855_switch_priv *priv, u32 reg, u32 *data) +{ + return __an8855_reg_read(priv->epriv.eth, priv->phy_base, reg, data); +} + +static int an8855_reg_write(struct an8855_switch_priv *priv, u32 reg, u32 data) +{ + int ret; + + ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x1f, 0x4); + if (ret) + return ret; + + ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x10, 0); + if (ret) + return ret; + + ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x11, + ((reg >> 16) & 0xFFFF)); + if (ret) + return ret; + + ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x12, + (reg & 0xFFFF)); + if (ret) + return ret; + + ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x13, + ((data >> 16) & 0xFFFF)); + if (ret) + return ret; + + ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x14, + (data & 0xFFFF)); + if (ret) + return ret; + + ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x1f, 0); + if (ret) + return ret; + + ret = mtk_mii_write(priv->epriv.eth, priv->phy_base, 0x10, 0); + if (ret) + return ret; + + return 0; +} + +static int an8855_phy_cl45_read(struct an8855_switch_priv *priv, int port, + int devad, int regnum, u16 *data) +{ + u16 phy_addr = AN8855_PHY_ADDR(priv->phy_base, port); + + *data = mtk_mmd_ind_read(priv->epriv.eth, phy_addr, devad, regnum); + + return 0; +} + +static int an8855_phy_cl45_write(struct an8855_switch_priv *priv, int port, + int devad, int regnum, u16 data) +{ + u16 phy_addr = AN8855_PHY_ADDR(priv->phy_base, port); + + mtk_mmd_ind_write(priv->epriv.eth, phy_addr, devad, regnum, data); + + return 0; +} + +static int an8855_port_sgmii_init(struct an8855_switch_priv *priv, u32 port) +{ + u32 val = 0; + + if (port != 5) { + printf("an8855: port %d is not a SGMII port\n", port); + return -EINVAL; + } + + /* PLL */ + an8855_reg_read(priv, AN8855_QP_DIG_MODE_CTRL_1, &val); + val &= ~(0x3 << 2); + val |= (0x1 << 2); + an8855_reg_write(priv, AN8855_QP_DIG_MODE_CTRL_1, val); + + /* PLL - LPF */ + an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val); + val &= ~(0x3 << 0); + val |= (0x1 << 0); + val &= ~(0x7 << 2); + val |= (0x5 << 2); + val &= ~GENMASK(7, 6); + val &= ~(0x7 << 8); + val |= (0x3 << 8); + val |= BIT(29); + val &= ~GENMASK(13, 12); + an8855_reg_write(priv, AN8855_PLL_CTRL_2, val); + + /* PLL - ICO */ + an8855_reg_read(priv, AN8855_PLL_CTRL_4, &val); + val |= BIT(2); + an8855_reg_write(priv, AN8855_PLL_CTRL_4, val); + + an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val); + val &= ~BIT(14); + an8855_reg_write(priv, AN8855_PLL_CTRL_2, val); + + /* PLL - CHP */ + an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val); + val &= ~(0xf << 16); + val |= (0x6 << 16); + an8855_reg_write(priv, AN8855_PLL_CTRL_2, val); + + /* PLL - PFD */ + an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val); + val &= ~(0x3 << 20); + val |= (0x1 << 20); + val &= ~(0x3 << 24); + val |= (0x1 << 24); + val &= ~BIT(26); + an8855_reg_write(priv, AN8855_PLL_CTRL_2, val); + + /* PLL - POSTDIV */ + an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val); + val |= BIT(22); + val &= ~BIT(27); + val &= ~BIT(28); + an8855_reg_write(priv, AN8855_PLL_CTRL_2, val); + + /* PLL - SDM */ + an8855_reg_read(priv, AN8855_PLL_CTRL_4, &val); + val &= ~GENMASK(4, 3); + an8855_reg_write(priv, AN8855_PLL_CTRL_4, val); + + an8855_reg_read(priv, AN8855_PLL_CTRL_2, &val); + val &= ~BIT(30); + an8855_reg_write(priv, AN8855_PLL_CTRL_2, val); + + an8855_reg_read(priv, AN8855_SS_LCPLL_PWCTL_SETTING_2, &val); + val &= ~(0x3 << 16); + val |= (0x1 << 16); + an8855_reg_write(priv, AN8855_SS_LCPLL_PWCTL_SETTING_2, val); + + an8855_reg_write(priv, AN8855_SS_LCPLL_TDC_FLT_2, 0x7a000000); + an8855_reg_write(priv, AN8855_SS_LCPLL_TDC_PCW_1, 0x7a000000); + + an8855_reg_read(priv, AN8855_SS_LCPLL_TDC_FLT_5, &val); + val &= ~BIT(24); + an8855_reg_write(priv, AN8855_SS_LCPLL_TDC_FLT_5, val); + + an8855_reg_read(priv, AN8855_PLL_CK_CTRL_0, &val); + val &= ~BIT(8); + an8855_reg_write(priv, AN8855_PLL_CK_CTRL_0, val); + + /* PLL - SS */ + an8855_reg_read(priv, AN8855_PLL_CTRL_3, &val); + val &= ~GENMASK(15, 0); + an8855_reg_write(priv, AN8855_PLL_CTRL_3, val); + + an8855_reg_read(priv, AN8855_PLL_CTRL_4, &val); + val &= ~GENMASK(1, 0); + an8855_reg_write(priv, AN8855_PLL_CTRL_4, val); + + an8855_reg_read(priv, AN8855_PLL_CTRL_3, &val); + val &= ~GENMASK(31, 16); + an8855_reg_write(priv, AN8855_PLL_CTRL_3, val); + + /* PLL - TDC */ + an8855_reg_read(priv, AN8855_PLL_CK_CTRL_0, &val); + val &= ~BIT(9); + an8855_reg_write(priv, AN8855_PLL_CK_CTRL_0, val); + + an8855_reg_read(priv, AN8855_RG_QP_PLL_SDM_ORD, &val); + val |= BIT(3); + val |= BIT(4); + an8855_reg_write(priv, AN8855_RG_QP_PLL_SDM_ORD, val); + + an8855_reg_read(priv, AN8855_RG_QP_RX_DAC_EN, &val); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + an8855_reg_write(priv, AN8855_RG_QP_RX_DAC_EN, val); + + /* TCL Disable (only for Co-SIM) */ + an8855_reg_read(priv, AN8855_PON_RXFEDIG_CTRL_0, &val); + val &= ~BIT(12); + an8855_reg_write(priv, AN8855_PON_RXFEDIG_CTRL_0, val); + + /* TX Init */ + an8855_reg_read(priv, AN8855_RG_QP_TX_MODE_16B_EN, &val); + val &= ~BIT(0); + val &= ~(0xffff << 16); + val |= (0x4 << 16); + an8855_reg_write(priv, AN8855_RG_QP_TX_MODE_16B_EN, val); + + /* RX Control */ + an8855_reg_read(priv, AN8855_RG_QP_RXAFE_RESERVE, &val); + val |= BIT(11); + an8855_reg_write(priv, AN8855_RG_QP_RXAFE_RESERVE, val); + + an8855_reg_read(priv, AN8855_RG_QP_CDR_LPF_MJV_LIM, &val); + val &= ~(0x3 << 4); + val |= (0x1 << 4); + an8855_reg_write(priv, AN8855_RG_QP_CDR_LPF_MJV_LIM, val); + + an8855_reg_read(priv, AN8855_RG_QP_CDR_LPF_SETVALUE, &val); + val &= ~(0xf << 25); + val |= (0x1 << 25); + val &= ~(0x7 << 29); + val |= (0x3 << 29); + an8855_reg_write(priv, AN8855_RG_QP_CDR_LPF_SETVALUE, val); + + an8855_reg_read(priv, AN8855_RG_QP_CDR_PR_CKREF_DIV1, &val); + val &= ~(0x1f << 8); + val |= (0xf << 8); + an8855_reg_write(priv, AN8855_RG_QP_CDR_PR_CKREF_DIV1, val); + + an8855_reg_read(priv, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE, &val); + val &= ~(0x3f << 0); + val |= (0x19 << 0); + val &= ~BIT(6); + an8855_reg_write(priv, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + an8855_reg_read(priv, AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF, &val); + val &= ~(0x7f << 6); + val |= (0x21 << 6); + val &= ~(0x3 << 16); + val |= (0x2 << 16); + val &= ~BIT(13); + an8855_reg_write(priv, AN8855_RG_QP_CDR_FORCE_IBANDLPF_R_OFF, val); + + an8855_reg_read(priv, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE, &val); + val &= ~BIT(30); + an8855_reg_write(priv, AN8855_RG_QP_CDR_PR_KBAND_DIV_PCIE, val); + + an8855_reg_read(priv, AN8855_RG_QP_CDR_PR_CKREF_DIV1, &val); + val &= ~(0x7 << 24); + val |= (0x4 << 24); + an8855_reg_write(priv, AN8855_RG_QP_CDR_PR_CKREF_DIV1, val); + + an8855_reg_read(priv, AN8855_PLL_CTRL_0, &val); + val |= BIT(0); + an8855_reg_write(priv, AN8855_PLL_CTRL_0, val); + + an8855_reg_read(priv, AN8855_RX_CTRL_26, &val); + val &= ~BIT(23); + val |= BIT(26); + an8855_reg_write(priv, AN8855_RX_CTRL_26, val); + + an8855_reg_read(priv, AN8855_RX_DLY_0, &val); + val &= ~(0xff << 0); + val |= (0x6f << 0); + val |= GENMASK(13, 8); + an8855_reg_write(priv, AN8855_RX_DLY_0, val); + + an8855_reg_read(priv, AN8855_RX_CTRL_42, &val); + val &= ~(0x1fff << 0); + val |= (0x150 << 0); + an8855_reg_write(priv, AN8855_RX_CTRL_42, val); + + an8855_reg_read(priv, AN8855_RX_CTRL_2, &val); + val &= ~(0x1fff << 16); + val |= (0x150 << 16); + an8855_reg_write(priv, AN8855_RX_CTRL_2, val); + + an8855_reg_read(priv, AN8855_PON_RXFEDIG_CTRL_9, &val); + val &= ~(0x7 << 0); + val |= (0x1 << 0); + an8855_reg_write(priv, AN8855_PON_RXFEDIG_CTRL_9, val); + + an8855_reg_read(priv, AN8855_RX_CTRL_8, &val); + val &= ~(0xfff << 16); + val |= (0x200 << 16); + val &= ~(0x7fff << 14); + val |= (0xfff << 14); + an8855_reg_write(priv, AN8855_RX_CTRL_8, val); + + /* Frequency memter */ + an8855_reg_read(priv, AN8855_RX_CTRL_5, &val); + val &= ~(0xfffff << 10); + val |= (0x10 << 10); + an8855_reg_write(priv, AN8855_RX_CTRL_5, val); + + an8855_reg_read(priv, AN8855_RX_CTRL_6, &val); + val &= ~(0xfffff << 0); + val |= (0x64 << 0); + an8855_reg_write(priv, AN8855_RX_CTRL_6, val); + + an8855_reg_read(priv, AN8855_RX_CTRL_7, &val); + val &= ~(0xfffff << 0); + val |= (0x2710 << 0); + an8855_reg_write(priv, AN8855_RX_CTRL_7, val); + + /* PCS Init */ + an8855_reg_read(priv, AN8855_RG_HSGMII_PCS_CTROL_1, &val); + val &= ~BIT(30); + an8855_reg_write(priv, AN8855_RG_HSGMII_PCS_CTROL_1, val); + + /* Rate Adaption */ + an8855_reg_read(priv, AN8855_RATE_ADP_P0_CTRL_0, &val); + val &= ~BIT(31); + an8855_reg_write(priv, AN8855_RATE_ADP_P0_CTRL_0, val); + + an8855_reg_read(priv, AN8855_RG_RATE_ADAPT_CTRL_0, &val); + val |= BIT(0); + val |= BIT(4); + val |= GENMASK(27, 26); + an8855_reg_write(priv, AN8855_RG_RATE_ADAPT_CTRL_0, val); + + /* Disable AN */ + an8855_reg_read(priv, AN8855_SGMII_REG_AN0, &val); + val &= ~BIT(12); + an8855_reg_write(priv, AN8855_SGMII_REG_AN0, val); + + /* Force Speed */ + an8855_reg_read(priv, AN8855_SGMII_STS_CTRL_0, &val); + val |= BIT(2); + val |= GENMASK(5, 4); + an8855_reg_write(priv, AN8855_SGMII_STS_CTRL_0, val); + + /* bypass flow control to MAC */ + an8855_reg_write(priv, AN8855_MSG_RX_LIK_STS_0, 0x01010107); + an8855_reg_write(priv, AN8855_MSG_RX_LIK_STS_2, 0x00000EEF); + + return 0; +} + +static void an8855_led_set_usr_def(struct an8855_switch_priv *priv, u8 entity, + enum an8855_led_polarity pol, u16 on_evt, + u16 blk_evt, u8 led_freq) +{ + u32 cl45_data; + + if (pol == LED_HIGH) + on_evt |= LED_ON_POL; + else + on_evt &= ~LED_ON_POL; + + /* LED on event */ + an8855_phy_cl45_write(priv, (entity / 4), 0x1e, + PHY_SINGLE_LED_ON_CTRL(entity % 4), + on_evt | LED_ON_EN); + + /* LED blink event */ + an8855_phy_cl45_write(priv, (entity / 4), 0x1e, + PHY_SINGLE_LED_BLK_CTRL(entity % 4), + blk_evt); + + /* LED freq */ + switch (led_freq) { + case AIR_LED_BLK_DUR_32M: + cl45_data = 0x30e; + break; + + case AIR_LED_BLK_DUR_64M: + cl45_data = 0x61a; + break; + + case AIR_LED_BLK_DUR_128M: + cl45_data = 0xc35; + break; + + case AIR_LED_BLK_DUR_256M: + cl45_data = 0x186a; + break; + + case AIR_LED_BLK_DUR_512M: + cl45_data = 0x30d4; + break; + + case AIR_LED_BLK_DUR_1024M: + cl45_data = 0x61a8; + break; + + default: + cl45_data = 0; + break; + } + + an8855_phy_cl45_write(priv, (entity / 4), 0x1e, + PHY_SINGLE_LED_BLK_DUR(entity % 4), + cl45_data); + + an8855_phy_cl45_write(priv, (entity / 4), 0x1e, + PHY_SINGLE_LED_ON_DUR(entity % 4), + (cl45_data >> 1)); + + /* Disable DATA & BAD_SSD for port LED blink behavior */ + cl45_data = mtk_mmd_ind_read(priv->epriv.eth, (entity / 4), 0x1e, PHY_PMA_CTRL); + cl45_data &= ~BIT(0); + cl45_data &= ~BIT(15); + an8855_phy_cl45_write(priv, (entity / 4), 0x1e, PHY_PMA_CTRL, cl45_data); +} + +static int an8855_led_set_mode(struct an8855_switch_priv *priv, u8 mode) +{ + u16 cl45_data; + + an8855_phy_cl45_read(priv, 0, 0x1f, PHY_LED_BCR, &cl45_data); + + switch (mode) { + case AN8855_LED_MODE_DISABLE: + cl45_data &= ~LED_BCR_EXT_CTRL; + cl45_data &= ~LED_BCR_MODE_MASK; + cl45_data |= LED_BCR_MODE_DISABLE; + break; + + case AN8855_LED_MODE_USER_DEFINE: + cl45_data |= LED_BCR_EXT_CTRL; + cl45_data |= LED_BCR_CLK_EN; + break; + + default: + printf("an8855: LED mode%d is not supported!\n", mode); + return -EINVAL; + } + + an8855_phy_cl45_write(priv, 0, 0x1f, PHY_LED_BCR, cl45_data); + + return 0; +} + +static int an8855_led_set_state(struct an8855_switch_priv *priv, u8 entity, + u8 state) +{ + u16 cl45_data = 0; + + /* Change to per port contorl */ + an8855_phy_cl45_read(priv, (entity / 4), 0x1e, PHY_LED_CTRL_SELECT, + &cl45_data); + + if (state == 1) + cl45_data |= (1 << (entity % 4)); + else + cl45_data &= ~(1 << (entity % 4)); + + an8855_phy_cl45_write(priv, (entity / 4), 0x1e, PHY_LED_CTRL_SELECT, + cl45_data); + + /* LED enable setting */ + an8855_phy_cl45_read(priv, (entity / 4), 0x1e, + PHY_SINGLE_LED_ON_CTRL(entity % 4), &cl45_data); + + if (state == 1) + cl45_data |= LED_ON_EN; + else + cl45_data &= ~LED_ON_EN; + + an8855_phy_cl45_write(priv, (entity / 4), 0x1e, + PHY_SINGLE_LED_ON_CTRL(entity % 4), cl45_data); + + return 0; +} + +static int an8855_led_init(struct an8855_switch_priv *priv) +{ + u32 val, id, tmp_id = 0; + int ret; + + ret = an8855_led_set_mode(priv, AN8855_LED_MODE_USER_DEFINE); + if (ret) { + printf("an8855: led_set_mode failed with %d!\n", ret); + return ret; + } + + for (id = 0; id < ARRAY_SIZE(led_cfg); id++) { + ret = an8855_led_set_state(priv, led_cfg[id].phy_led_idx, + led_cfg[id].en); + if (ret != 0) { + printf("an8855: led_set_state failed with %d!\n", ret); + return ret; + } + + if (led_cfg[id].en == 1) { + an8855_led_set_usr_def(priv, + led_cfg[id].phy_led_idx, + led_cfg[id].pol, + led_cfg[id].on_cfg, + led_cfg[id].blk_cfg, + led_cfg[id].led_freq); + } + } + + /* Setting for System LED & Loop LED */ + an8855_reg_write(priv, AN8855_RG_GPIO_OE, 0x0); + an8855_reg_write(priv, AN8855_RG_GPIO_CTRL, 0x0); + an8855_reg_write(priv, AN8855_RG_GPIO_L_INV, 0); + + an8855_reg_write(priv, AN8855_RG_GPIO_CTRL, 0x1001); + an8855_reg_read(priv, AN8855_RG_GPIO_DATA, &val); + val |= GENMASK(3, 1); + val &= ~(BIT(0)); + val &= ~(BIT(6)); + an8855_reg_write(priv, AN8855_RG_GPIO_DATA, val); + + an8855_reg_read(priv, AN8855_RG_GPIO_OE, &val); + val |= 0x41; + an8855_reg_write(priv, AN8855_RG_GPIO_OE, val); + + /* Mapping between GPIO & LED */ + val = 0; + for (id = 0; id < ARRAY_SIZE(led_cfg); id++) { + /* Skip GPIO6, due to GPIO6 does not support PORT LED */ + if (id == 6) + continue; + + if (led_cfg[id].en == 1) { + if (id < 7) + val |= led_cfg[id].phy_led_idx << ((id % 4) * 8); + else + val |= led_cfg[id].phy_led_idx << (((id - 1) % 4) * 8); + } + + if (id < 7) + tmp_id = id; + else + tmp_id = id - 1; + + if ((tmp_id % 4) == 0x3) { + an8855_reg_write(priv, + AN8855_RG_GPIO_LED_SEL(tmp_id / 4), + val); + val = 0; + } + } + + /* Turn on LAN LED mode */ + val = 0; + for (id = 0; id < ARRAY_SIZE(led_cfg); id++) { + if (led_cfg[id].en == 1) + val |= 0x1 << id; + } + an8855_reg_write(priv, AN8855_RG_GPIO_LED_MODE, val); + + /* Force clear blink pulse for per port LED */ + an8855_phy_cl45_write(priv, 0, 0x1f, PHY_LED_BLINK_DUR_CTRL, 0x1f); + udelay(1000); + an8855_phy_cl45_write(priv, 0, 0x1f, PHY_LED_BLINK_DUR_CTRL, 0); + + return 0; +} + +static void an8855_port_isolation(struct an8855_switch_priv *priv) +{ + u32 i; + + for (i = 0; i < AN8855_NUM_PORTS; i++) { + /* Set port matrix mode */ + if (i != 5) + an8855_reg_write(priv, AN8855_PORTMATRIX_REG(i), 0x20); + else + an8855_reg_write(priv, AN8855_PORTMATRIX_REG(i), 0x1f); + + /* Set port mode to user port */ + an8855_reg_write(priv, AN8855_PVC(i), + (0x8100 << AN8855_STAG_VPID_S) | + (VLAN_ATTR_USER << AN8855_VLAN_ATTR_S)); + } +} + +static void an8855_mac_control(struct mtk_eth_switch_priv *swpriv, bool enable) +{ + struct an8855_switch_priv *priv = (struct an8855_switch_priv *)swpriv; + u32 pmcr = AN8855_FORCE_MODE_LNK; + + if (enable) + pmcr = AN8855_FORCE_MODE; + + an8855_reg_write(priv, AN8855_PMCR_REG(5), pmcr); +} + +static int an8855_mdio_read(struct mii_dev *bus, int addr, int devad, int reg) +{ + struct an8855_switch_priv *priv = bus->priv; + + if (devad < 0) + return mtk_mii_read(priv->epriv.eth, addr, reg); + + return mtk_mmd_ind_read(priv->epriv.eth, addr, devad, reg); +} + +static int an8855_mdio_write(struct mii_dev *bus, int addr, int devad, int reg, + u16 val) +{ + struct an8855_switch_priv *priv = bus->priv; + + if (devad < 0) + return mtk_mii_write(priv->epriv.eth, addr, reg, val); + + return mtk_mmd_ind_write(priv->epriv.eth, addr, devad, reg, val); +} + +static int an8855_mdio_register(struct an8855_switch_priv *priv) +{ + struct mii_dev *mdio_bus = mdio_alloc(); + int ret; + + if (!mdio_bus) + return -ENOMEM; + + mdio_bus->read = an8855_mdio_read; + mdio_bus->write = an8855_mdio_write; + snprintf(mdio_bus->name, sizeof(mdio_bus->name), priv->epriv.sw->name); + + mdio_bus->priv = priv; + + ret = mdio_register(mdio_bus); + if (ret) { + mdio_free(mdio_bus); + return ret; + } + + priv->mdio_bus = mdio_bus; + + return 0; +} + +static int an8855_setup(struct mtk_eth_switch_priv *swpriv) +{ + struct an8855_switch_priv *priv = (struct an8855_switch_priv *)swpriv; + u16 phy_addr, phy_val; + u32 i, id, val = 0; + int ret; + + priv->phy_base = 1; + + /* Turn off PHYs */ + for (i = 0; i < AN8855_NUM_PHYS; i++) { + phy_addr = AN8855_PHY_ADDR(priv->phy_base, i); + phy_val = mtk_mii_read(priv->epriv.eth, phy_addr, MII_BMCR); + phy_val |= BMCR_PDOWN; + mtk_mii_write(priv->epriv.eth, phy_addr, MII_BMCR, phy_val); + } + + /* Force MAC link down before reset */ + an8855_reg_write(priv, AN8855_PMCR_REG(5), AN8855_FORCE_MODE_LNK); + + /* Switch soft reset */ + an8855_reg_write(priv, AN8855_SYS_CTRL_REG, AN8855_SW_SYS_RST); + udelay(100000); + + an8855_reg_read(priv, AN8855_PKG_SEL, &val); + if ((val & 0x7) == PAG_SEL_AN8855H) { + /* Release power down */ + an8855_reg_write(priv, RG_GPHY_AFE_PWD, 0x0); + + /* Invert for LED activity change */ + an8855_reg_read(priv, AN8855_RG_GPIO_L_INV, &val); + for (id = 0; id < ARRAY_SIZE(led_cfg); id++) { + if ((led_cfg[id].pol == LED_HIGH) && + (led_cfg[id].en == 1)) + val |= 0x1 << id; + } + an8855_reg_write(priv, AN8855_RG_GPIO_L_INV, (val | 0x1)); + + /* MCU NOP CMD */ + an8855_reg_write(priv, AN8855_RG_GDMP_RAM, 0x846); + an8855_reg_write(priv, AN8855_RG_GDMP_RAM + 4, 0x4a); + + /* Enable MCU */ + an8855_reg_read(priv, AN8855_RG_CLK_CPU_ICG, &val); + an8855_reg_write(priv, AN8855_RG_CLK_CPU_ICG, + val | AN8855_MCU_ENABLE); + udelay(1000); + + /* Disable MCU watchdog */ + an8855_reg_read(priv, AN8855_RG_TIMER_CTL, &val); + an8855_reg_write(priv, AN8855_RG_TIMER_CTL, + (val & (~AN8855_WDOG_ENABLE))); + + /* LED settings for T830 reference board */ + ret = an8855_led_init(priv); + if (ret < 0) { + printf("an8855: an8855_led_init failed with %d\n", ret); + return ret; + } + } + + switch (priv->epriv.phy_interface) { + case PHY_INTERFACE_MODE_2500BASEX: + an8855_port_sgmii_init(priv, 5); + break; + + default: + break; + } + + an8855_reg_read(priv, AN8855_CKGCR, &val); + val &= ~(0x3); + an8855_reg_write(priv, AN8855_CKGCR, val); + + /* Enable port isolation to block inter-port communication */ + an8855_port_isolation(priv); + + /* Turn on PHYs */ + for (i = 0; i < AN8855_NUM_PHYS; i++) { + phy_addr = AN8855_PHY_ADDR(priv->phy_base, i); + phy_val = mtk_mii_read(priv->epriv.eth, phy_addr, MII_BMCR); + phy_val &= ~BMCR_PDOWN; + mtk_mii_write(priv->epriv.eth, phy_addr, MII_BMCR, phy_val); + } + + return an8855_mdio_register(priv); +} + +static int an8855_cleanup(struct mtk_eth_switch_priv *swpriv) +{ + struct an8855_switch_priv *priv = (struct an8855_switch_priv *)swpriv; + + mdio_unregister(priv->mdio_bus); + + return 0; +} + +static int an8855_detect(struct mtk_eth_priv *priv) +{ + int ret; + u32 val; + + ret = __an8855_reg_read(priv, 1, 0x10005000, &val); + if (ret) + return ret; + + if (val == 0x8855) + return 0; + + return -ENODEV; +} + +MTK_ETH_SWITCH(an8855) = { + .name = "an8855", + .desc = "Airoha AN8855", + .priv_size = sizeof(struct an8855_switch_priv), + .reset_wait_time = 100, + + .detect = an8855_detect, + .setup = an8855_setup, + .cleanup = an8855_cleanup, + .mac_control = an8855_mac_control, +};