mediatek: add support for airoha an8855 switch (from mtk-openwrt-feeds)

This commit is contained in:
hanwckf 2024-11-12 02:26:34 +08:00
parent 606983a97b
commit 9bce0f9947
28 changed files with 8267 additions and 0 deletions

View File

@ -0,0 +1,10 @@
config NET_DSA_AN8855
tristate "Airoha AN8855 Ethernet switch support"
depends on NET_DSA
select NET_DSA_TAG_AIROHA
help
AN8855 support 2.5G speed and managed by SMI interface.
This enables support for the Airoha AN8855 Ethernet switch
chip.
To compile this driver as a module, choose M here.

View File

@ -0,0 +1,5 @@
#
# Makefile for Airoha AN8855 gigabit switch
#
obj-$(CONFIG_NET_DSA_AN8855) += an8855-dsa.o
an8855-dsa-objs := an8855.o an8855_nl.o an8855_phy.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,679 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2023 Min Yao <min.yao@airoha.com>
*/
#ifndef __AN8855_H
#define __AN8855_H
#define BITS(m, n) (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n)))
#define AN8855_NUM_PORTS 6
#define AN8855_CPU_PORT 5
#define AN8855_WORD_SIZE 4
#define AN8855_NUM_FDB_RECORDS 2048
#define AN8855_ALL_MEMBERS 0x3f
#define AN8855_RESERVED_VLAN 2
#define AN8855_GPHY_SMI_ADDR_DEFAULT 1
#define AN8855_DFL_INTR_ID 0xd
#define AN8855_DFL_EXT_SURGE 0x0
enum an8855_id {
ID_AN8855 = 0,
};
enum sgmii_mode {
SGMII_MODE_AN,
SGMII_MODE_FORCE,
};
/* Registers to mac forward control for unknown frames */
#define AN8855_MFC 0x10200010
#define CPU_EN BIT(15)
#define CPU_PORT(x) ((x) << 8)
#define CPU_MASK (0x9f << 8)
#define AN8855_UNUF 0x102000b4
#define AN8855_UNMF 0x102000b8
#define AN8855_BCF 0x102000bc
/* Registers for mirror port control */
#define AN8855_MIR 0x102000cc
#define AN8855_MIRROR_EN BIT(7)
#define AN8855_MIRROR_MASK (0x1f)
#define AN8855_MIRROR_PORT_GET(x) ((x) & AN8855_MIRROR_MASK)
#define AN8855_MIRROR_PORT_SET(x) ((x) & AN8855_MIRROR_MASK)
/* Registers for BPDU and PAE frame control*/
#define AN8855_BPC 0x102000D0
#define AN8855_BPDU_PORT_FW_MASK GENMASK(2, 0)
enum an8855_bpdu_port_fw {
AN8855_BPDU_FOLLOW_MFC,
AN8855_BPDU_CPU_EXCLUDE = 4,
AN8855_BPDU_CPU_INCLUDE = 5,
AN8855_BPDU_CPU_ONLY = 6,
AN8855_BPDU_DROP = 7,
};
/* Registers for address table access */
#define AN8855_ATA1 0x10200304
#define AN8855_ATA2 0x10200308
/* Register for address table write data */
#define AN8855_ATWD 0x10200324
#define AN8855_ATWD2 0x10200328
/* Register for address table control */
#define AN8855_ATC 0x10200300
#define ATC_BUSY BIT(31)
#define ATC_INVALID ~BIT(30)
#define ATC_HASH 16
#define ATC_HASH_MASK 0x1ff
#define ATC_HIT 12
#define ATC_HIT_MASK 0xf
#define ATC_MAT(x) (((x) & 0x1f) << 7)
#define ATC_MAT_MACTAB ATC_MAT(1)
enum an8855_fdb_cmds {
AN8855_FDB_READ = 0,
AN8855_FDB_WRITE = 1,
AN8855_FDB_FLUSH = 2,
AN8855_FDB_START = 4,
AN8855_FDB_NEXT = 5,
};
/* Registers for table search read address */
#define AN8855_ATRDS 0x10200330
#define AN8855_ATRD0 0x10200334
#define CVID 10
#define CVID_MASK 0xfff
enum an8855_fdb_type {
AN8855_MAC_TB_TY_MAC = 0,
AN8855_MAC_TB_TY_DIP = 1,
AN8855_MAC_TB_TY_DIP_SIP = 2,
};
#define AN8855_ATRD1 0x10200338
#define MAC_BYTE_4 24
#define MAC_BYTE_5 16
#define AGE_TIMER 3
#define AGE_TIMER_MASK 0x1ff
#define AN8855_ATRD2 0x1020033c
#define MAC_BYTE_0 24
#define MAC_BYTE_1 16
#define MAC_BYTE_2 8
#define MAC_BYTE_3 0
#define MAC_BYTE_MASK 0xff
#define AN8855_ATRD3 0x10200340
#define PORT_MAP 4
#define PORT_MAP_MASK 0xff
/* Register for vlan table control */
#define AN8855_VTCR 0x10200600
#define VTCR_BUSY BIT(31)
#define VTCR_FUNC(x) (((x) & 0xf) << 12)
#define VTCR_VID ((x) & 0xfff)
enum an8855_vlan_cmd {
/* Read/Write the specified VID entry from VAWD register based
* on VID.
*/
AN8855_VTCR_RD_VID = 0,
AN8855_VTCR_WR_VID = 1,
};
/* Register for setup vlan write data */
#define AN8855_VAWD0 0x10200604
/* Independent VLAN Learning */
#define IVL_MAC BIT(5)
/* Per VLAN Egress Tag Control */
#define VTAG_EN BIT(10)
/* Egress Tag Control */
#define PORT_EG_CTRL_SHIFT 12
/* VLAN Member Control */
#define PORT_MEM_SHFT 26
#define PORT_MEM_MASK 0x7f
#define PORT_MEM(x) (((x) & PORT_MEM_MASK) << PORT_MEM_SHFT)
/* VLAN Entry Valid */
#define VLAN_VALID BIT(0)
#define AN8855_VAWD1 0x10200608
#define PORT_STAG BIT(1)
/* Egress Tag Control */
#define ETAG_CTRL_P(p, x) (((x) & 0x3) << ((p) << 1))
#define ETAG_CTRL_P_MASK(p) ETAG_CTRL_P(p, 3)
#define ETAG_CTRL_MASK (0x3FFF)
#define AN8855_VARD0 0x10200618
enum an8855_vlan_egress_attr {
AN8855_VLAN_EGRESS_UNTAG = 0,
AN8855_VLAN_EGRESS_TAG = 2,
AN8855_VLAN_EGRESS_STACK = 3,
};
/* Register for port STP state control */
#define AN8855_SSP_P(x) (0x10208000 + ((x) * 0x200))
#define FID_PST(x) ((x) & 0x3)
#define FID_PST_MASK FID_PST(0x3)
enum an8855_stp_state {
AN8855_STP_DISABLED = 0,
AN8855_STP_BLOCKING = 1,
AN8855_STP_LISTENING = 1,
AN8855_STP_LEARNING = 2,
AN8855_STP_FORWARDING = 3
};
/* Register for port control */
#define AN8855_PCR_P(x) (0x10208004 + ((x) * 0x200))
#define PORT_TX_MIR BIT(20)
#define PORT_RX_MIR BIT(16)
#define PORT_VLAN(x) ((x) & 0x3)
enum an8855_port_mode {
/* Port Matrix Mode: Frames are forwarded by the PCR_MATRIX members. */
AN8855_PORT_MATRIX_MODE = PORT_VLAN(0),
/* Fallback Mode: Forward received frames with ingress ports that do
* not belong to the VLAN member. Frames whose VID is not listed on
* the VLAN table are forwarded by the PCR_MATRIX members.
*/
AN8855_PORT_FALLBACK_MODE = PORT_VLAN(1),
/* Security Mode: Discard any frame due to ingress membership
* violation or VID missed on the VLAN table.
*/
AN8855_PORT_SECURITY_MODE = PORT_VLAN(3),
};
#define PORT_PRI(x) (((x) & 0x7) << 24)
#define EG_TAG(x) (((x) & 0x3) << 28)
#define PCR_PORT_VLAN_MASK PORT_VLAN(3)
/* Register for port security control */
#define AN8855_PSC_P(x) (0x1020800c + ((x) * 0x200))
#define SA_DIS BIT(4)
/* Register for port vlan control */
#define AN8855_PVC_P(x) (0x10208010 + ((x) * 0x200))
#define PORT_SPEC_REPLACE_MODE BIT(11)
#define PORT_SPEC_TAG BIT(5)
#define PVC_EG_TAG(x) (((x) & 0x7) << 8)
#define PVC_EG_TAG_MASK PVC_EG_TAG(7)
#define VLAN_ATTR(x) (((x) & 0x3) << 6)
#define VLAN_ATTR_MASK VLAN_ATTR(3)
#define AN8855_PORTMATRIX_P(x) (0x10208044 + ((x) * 0x200))
#define PORTMATRIX_MATRIX(x) ((x) & 0x3f)
#define PORTMATRIX_MASK PORTMATRIX_MATRIX(0x3f)
#define PORTMATRIX_CLR PORTMATRIX_MATRIX(0)
enum an8855_vlan_port_eg_tag {
AN8855_VLAN_EG_DISABLED = 0,
AN8855_VLAN_EG_CONSISTENT = 1,
};
enum an8855_vlan_port_attr {
AN8855_VLAN_USER = 0,
AN8855_VLAN_TRANSPARENT = 3,
};
/* Register for port PVID */
#define AN8855_PVID_P(x) (0x10208048 + ((x) * 0x200))
#define G0_PORT_VID(x) (((x) & 0xfff) << 0)
#define G0_PORT_VID_MASK G0_PORT_VID(0xfff)
#define G0_PORT_VID_DEF G0_PORT_VID(1)
/* Register for port MAC control register */
#define AN8855_PMCR_P(x) (0x10210000 + ((x) * 0x200))
#define PMCR_IFG_XMIT(x) (((x) & 0x3) << 20)
#define PMCR_EXT_PHY BIT(19)
#define PMCR_MAC_MODE BIT(18)
#define PMCR_FORCE_MODE BIT(31)
#define PMCR_TX_EN BIT(16)
#define PMCR_RX_EN BIT(15)
#define PMCR_BACKOFF_EN BIT(12)
#define PMCR_BACKPR_EN BIT(11)
#define PMCR_FORCE_EEE2P5G BIT(8)
#define PMCR_FORCE_EEE1G BIT(7)
#define PMCR_FORCE_EEE100 BIT(6)
#define PMCR_TX_FC_EN BIT(5)
#define PMCR_RX_FC_EN BIT(4)
#define PMCR_FORCE_SPEED_2500 (0x3 << 28)
#define PMCR_FORCE_SPEED_1000 (0x2 << 28)
#define PMCR_FORCE_SPEED_100 (0x1 << 28)
#define PMCR_FORCE_FDX BIT(25)
#define PMCR_FORCE_LNK BIT(24)
#define PMCR_SPEED_MASK BITS(28, 30)
#define AN8855_FORCE_LNK BIT(31)
#define AN8855_FORCE_MODE (AN8855_FORCE_LNK)
#define PMCR_LINK_SETTINGS_MASK (PMCR_TX_EN | \
PMCR_RX_EN | PMCR_FORCE_SPEED_2500 | \
PMCR_TX_FC_EN | PMCR_RX_FC_EN | \
PMCR_FORCE_FDX | PMCR_FORCE_LNK)
#define PMCR_CPU_PORT_SETTING(id) (AN8855_FORCE_MODE | \
PMCR_IFG_XMIT(1) | PMCR_MAC_MODE | \
PMCR_BACKOFF_EN | PMCR_BACKPR_EN | \
PMCR_TX_EN | PMCR_RX_EN | \
PMCR_TX_FC_EN | PMCR_RX_FC_EN | \
PMCR_FORCE_SPEED_2500 | \
PMCR_FORCE_FDX | PMCR_FORCE_LNK)
#define AN8855_PMSR_P(x) (0x10210010 + (x) * 0x200)
#define PMSR_EEE1G BIT(7)
#define PMSR_EEE100M BIT(6)
#define PMSR_RX_FC BIT(5)
#define PMSR_TX_FC BIT(4)
#define PMSR_SPEED_2500 (0x3 << 28)
#define PMSR_SPEED_1000 (0x2 << 28)
#define PMSR_SPEED_100 (0x1 << 28)
#define PMSR_SPEED_10 (0x0 << 28)
#define PMSR_SPEED_MASK BITS(28, 30)
#define PMSR_DPX BIT(25)
#define PMSR_LINK BIT(24)
#define AN8855_PMEEECR_P(x) (0x10210004 + (x) * 0x200)
#define WAKEUP_TIME_2500(x) ((x & 0xFF) << 16)
#define WAKEUP_TIME_1000(x) ((x & 0xFF) << 8)
#define WAKEUP_TIME_100(x) ((x & 0xFF) << 0)
#define LPI_MODE_EN BIT(31)
#define AN8855_PMEEECR2_P(x) (0x10210008 + (x) * 0x200)
#define WAKEUP_TIME_5000(x) ((x & 0xFF) << 0)
#define AN8855_CKGCR (0x10213e1c)
#define LPI_TXIDLE_THD 14
#define LPI_TXIDLE_THD_MASK BITS(14, 31)
#define CKG_LNKDN_GLB_STOP 0x01
#define CKG_LNKDN_PORT_STOP 0x02
/* Register for MIB */
#define AN8855_PORT_MIB_COUNTER(x) (0x10214000 + (x) * 0x200)
#define AN8855_MIB_CCR 0x10213e30
#define CCR_MIB_ENABLE BIT(31)
#define CCR_RX_OCT_CNT_GOOD BIT(7)
#define CCR_RX_OCT_CNT_BAD BIT(6)
#define CCR_TX_OCT_CNT_GOOD BIT(5)
#define CCR_TX_OCT_CNT_BAD BIT(4)
#define CCR_RX_OCT_CNT_GOOD_2 BIT(3)
#define CCR_RX_OCT_CNT_BAD_2 BIT(2)
#define CCR_TX_OCT_CNT_GOOD_2 BIT(1)
#define CCR_TX_OCT_CNT_BAD_2 BIT(0)
#define CCR_MIB_FLUSH (CCR_RX_OCT_CNT_GOOD | \
CCR_RX_OCT_CNT_BAD | \
CCR_TX_OCT_CNT_GOOD | \
CCR_TX_OCT_CNT_BAD | \
CCR_RX_OCT_CNT_BAD_2 | \
CCR_TX_OCT_CNT_BAD_2)
#define CCR_MIB_ACTIVATE (CCR_MIB_ENABLE | \
CCR_RX_OCT_CNT_GOOD | \
CCR_RX_OCT_CNT_BAD | \
CCR_TX_OCT_CNT_GOOD | \
CCR_TX_OCT_CNT_BAD | \
CCR_RX_OCT_CNT_BAD_2 | \
CCR_TX_OCT_CNT_BAD_2)
/* AN8855 SGMII register group */
#define AN8855_SGMII_REG_BASE 0x10220000
#define AN8855_SGMII_REG(p, r) (AN8855_SGMII_REG_BASE + \
((p) - 5) * 0x1000 + (r))
/* Register forSGMII PCS_CONTROL_1 */
#define AN8855_PCS_CONTROL_1(p) AN8855_SGMII_REG(p, 0x00)
#define AN8855_SGMII_AN_ENABLE BIT(12)
#define AN8855_SGMII_AN_RESTART BIT(9)
/* Register for system reset */
#define AN8855_RST_CTRL 0x100050c0
#define SYS_CTRL_SYS_RST BIT(31)
#define INT_MASK 0x100050F0
#define INT_SYS_BIT BIT(15)
#define RG_CLK_CPU_ICG 0x10005034
#define MCU_ENABLE BIT(3)
#define RG_TIMER_CTL 0x1000a100
#define WDOG_ENABLE BIT(25)
#define CKGCR 0x10213E1C
#define CKG_LNKDN_GLB_STOP 0x01
#define CKG_LNKDN_PORT_STOP 0x02
#define PKG_SEL 0x10000094
#define PAG_SEL_AN8855H 0x2
/* Register for hw trap status */
#define AN8855_HWTRAP 0x1000009c
#define AN8855_CREV 0x10005000
#define AN8855_ID 0x8855
#define SCU_BASE 0x10000000
#define RG_RGMII_TXCK_C (SCU_BASE + 0x1d0)
#define RG_GPIO_LED_MODE (SCU_BASE + 0x0054)
#define RG_GPIO_LED_SEL(i) (SCU_BASE + (0x0058 + ((i) * 4)))
#define RG_INTB_MODE (SCU_BASE + 0x0080)
#define RG_GDMP_RAM (SCU_BASE + 0x10000)
#define RG_GPIO_L_INV (SCU_BASE + 0x0010)
#define RG_GPIO_CTRL (SCU_BASE + 0xa300)
#define RG_GPIO_DATA (SCU_BASE + 0xa304)
#define RG_GPIO_OE (SCU_BASE + 0xa314)
#define HSGMII_AN_CSR_BASE 0x10220000
#define SGMII_REG_AN0 (HSGMII_AN_CSR_BASE + 0x000)
#define SGMII_REG_AN_13 (HSGMII_AN_CSR_BASE + 0x034)
#define SGMII_REG_AN_FORCE_CL37 (HSGMII_AN_CSR_BASE + 0x060)
#define HSGMII_CSR_PCS_BASE 0x10220000
#define RG_HSGMII_PCS_CTROL_1 (HSGMII_CSR_PCS_BASE + 0xa00)
#define RG_AN_SGMII_MODE_FORCE (HSGMII_CSR_PCS_BASE + 0xa24)
#define MULTI_SGMII_CSR_BASE 0x10224000
#define SGMII_STS_CTRL_0 (MULTI_SGMII_CSR_BASE + 0x018)
#define MSG_RX_CTRL_0 (MULTI_SGMII_CSR_BASE + 0x100)
#define MSG_RX_LIK_STS_0 (MULTI_SGMII_CSR_BASE + 0x514)
#define MSG_RX_LIK_STS_2 (MULTI_SGMII_CSR_BASE + 0x51c)
#define PHY_RX_FORCE_CTRL_0 (MULTI_SGMII_CSR_BASE + 0x520)
#define XFI_CSR_PCS_BASE 0x10225000
#define RG_USXGMII_AN_CONTROL_0 (XFI_CSR_PCS_BASE + 0xbf8)
#define MULTI_PHY_RA_CSR_BASE 0x10226000
#define RG_RATE_ADAPT_CTRL_0 (MULTI_PHY_RA_CSR_BASE + 0x000)
#define RATE_ADP_P0_CTRL_0 (MULTI_PHY_RA_CSR_BASE + 0x100)
#define MII_RA_AN_ENABLE (MULTI_PHY_RA_CSR_BASE + 0x300)
#define QP_DIG_CSR_BASE 0x1022a000
#define QP_CK_RST_CTRL_4 (QP_DIG_CSR_BASE + 0x310)
#define QP_DIG_MODE_CTRL_0 (QP_DIG_CSR_BASE + 0x324)
#define QP_DIG_MODE_CTRL_1 (QP_DIG_CSR_BASE + 0x330)
#define SERDES_WRAPPER_BASE 0x1022c000
#define USGMII_CTRL_0 (SERDES_WRAPPER_BASE + 0x000)
#define QP_PMA_TOP_BASE 0x1022e000
#define PON_RXFEDIG_CTRL_0 (QP_PMA_TOP_BASE + 0x100)
#define PON_RXFEDIG_CTRL_9 (QP_PMA_TOP_BASE + 0x124)
#define SS_LCPLL_PWCTL_SETTING_2 (QP_PMA_TOP_BASE + 0x208)
#define SS_LCPLL_TDC_FLT_2 (QP_PMA_TOP_BASE + 0x230)
#define SS_LCPLL_TDC_FLT_5 (QP_PMA_TOP_BASE + 0x23c)
#define SS_LCPLL_TDC_PCW_1 (QP_PMA_TOP_BASE + 0x248)
#define INTF_CTRL_8 (QP_PMA_TOP_BASE + 0x320)
#define INTF_CTRL_9 (QP_PMA_TOP_BASE + 0x324)
#define INTF_CTRL_10 (QP_PMA_TOP_BASE + 0x328)
#define INTF_CTRL_11 (QP_PMA_TOP_BASE + 0x32c)
#define PLL_CTRL_0 (QP_PMA_TOP_BASE + 0x400)
#define PLL_CTRL_2 (QP_PMA_TOP_BASE + 0x408)
#define PLL_CTRL_3 (QP_PMA_TOP_BASE + 0x40c)
#define PLL_CTRL_4 (QP_PMA_TOP_BASE + 0x410)
#define PLL_CK_CTRL_0 (QP_PMA_TOP_BASE + 0x414)
#define RX_DLY_0 (QP_PMA_TOP_BASE + 0x614)
#define RX_CTRL_2 (QP_PMA_TOP_BASE + 0x630)
#define RX_CTRL_5 (QP_PMA_TOP_BASE + 0x63c)
#define RX_CTRL_6 (QP_PMA_TOP_BASE + 0x640)
#define RX_CTRL_7 (QP_PMA_TOP_BASE + 0x644)
#define RX_CTRL_8 (QP_PMA_TOP_BASE + 0x648)
#define RX_CTRL_26 (QP_PMA_TOP_BASE + 0x690)
#define RX_CTRL_42 (QP_PMA_TOP_BASE + 0x6d0)
#define QP_ANA_CSR_BASE 0x1022f000
#define RG_QP_RX_DAC_EN (QP_ANA_CSR_BASE + 0x00)
#define RG_QP_RXAFE_RESERVE (QP_ANA_CSR_BASE + 0x04)
#define RG_QP_CDR_LPF_BOT_LIM (QP_ANA_CSR_BASE + 0x08)
#define RG_QP_CDR_LPF_MJV_LIM (QP_ANA_CSR_BASE + 0x0c)
#define RG_QP_CDR_LPF_SETVALUE (QP_ANA_CSR_BASE + 0x14)
#define RG_QP_CDR_PR_CKREF_DIV1 (QP_ANA_CSR_BASE + 0x18)
#define RG_QP_CDR_PR_KBAND_DIV_PCIE (QP_ANA_CSR_BASE + 0x1c)
#define RG_QP_CDR_FORCE_IBANDLPF_R_OFF (QP_ANA_CSR_BASE + 0x20)
#define RG_QP_TX_MODE_16B_EN (QP_ANA_CSR_BASE + 0x28)
#define RG_QP_PLL_IPLL_DIG_PWR_SEL (QP_ANA_CSR_BASE + 0x3c)
#define RG_QP_PLL_SDM_ORD (QP_ANA_CSR_BASE + 0x40)
#define ETHER_SYS_BASE 0x1028c800
#define RG_GPHY_AFE_PWD (ETHER_SYS_BASE + 0x40)
#define RG_GPHY_SMI_ADDR (ETHER_SYS_BASE + 0x48)
#define MIB_DESC(_s, _o, _n) \
{ \
.size = (_s), \
.offset = (_o), \
.name = (_n), \
}
struct an8855_mib_desc {
unsigned int size;
unsigned int offset;
const char *name;
};
struct an8855_fdb {
u16 vid;
u8 port_mask;
u8 aging;
u8 mac[6];
bool noarp;
u8 live;
u8 type;
u8 fid;
u8 ivl;
};
/* 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
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
};
/* TBD */
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
};
struct an8855_led_cfg {
u16 en;
u8 phy_led_idx;
u16 pol;
u16 on_cfg;
u16 blk_cfg;
u8 led_freq;
};
/* struct an8855_port - This is the main data structure for holding the state
* of the port.
* @enable: The status used for show port is enabled or not.
* @pm: The matrix used to show all connections with the port.
* @pvid: The VLAN specified is to be considered a PVID at ingress. Any
* untagged frames will be assigned to the related VLAN.
* @vlan_filtering: The flags indicating whether the port that can recognize
* VLAN-tagged frames.
*/
struct an8855_port {
bool enable;
u32 pm;
u16 pvid;
};
/* struct an8855_info - This is the main data structure for holding the specific
* part for each supported device
* @sw_setup: Holding the handler to a device initialization
* @phy_read: Holding the way reading PHY port
* @phy_write: Holding the way writing PHY port
* @pad_setup: Holding the way setting up the bus pad for a certain
* MAC port
* @phy_mode_supported: Check if the PHY type is being supported on a certain
* port
* @mac_port_validate: Holding the way to set addition validate type for a
* certan MAC port
* @mac_port_get_state: Holding the way getting the MAC/PCS state for a certain
* MAC port
* @mac_port_config: Holding the way setting up the PHY attribute to a
* certain MAC port
* @mac_pcs_an_restart Holding the way restarting PCS autonegotiation for a
* certain MAC port
* @mac_pcs_link_up: Holding the way setting up the PHY attribute to the pcs
* of the certain MAC port
*/
struct an8855_dev_info {
enum an8855_id id;
int (*sw_setup)(struct dsa_switch *ds);
int (*phy_read)(struct dsa_switch *ds, int port, int regnum);
int (*phy_write)(struct dsa_switch *ds, int port, int regnum,
u16 val);
int (*pad_setup)(struct dsa_switch *ds, phy_interface_t interface);
int (*cpu_port_config)(struct dsa_switch *ds, int port);
bool (*phy_mode_supported)(struct dsa_switch *ds, int port,
const struct phylink_link_state *state);
void (*mac_port_validate)(struct dsa_switch *ds, int port,
unsigned long *supported);
int (*mac_port_get_state)(struct dsa_switch *ds, int port,
struct phylink_link_state *state);
int (*mac_port_config)(struct dsa_switch *ds, int port,
unsigned int mode, phy_interface_t interface);
void (*mac_pcs_an_restart)(struct dsa_switch *ds, int port);
};
/* struct an8855_priv - This is the main data structure for holding the state
* of the driver
* @dev: The device pointer
* @ds: The pointer to the dsa core structure
* @bus: The bus used for the device and built-in PHY
* @rstc: The pointer to reset control used by MCM
* @core_pwr: The power supplied into the core
* @io_pwr: The power supplied into the I/O
* @reset: The descriptor for GPIO line tied to its reset pin
* @mcm: Flag for distinguishing if standalone IC or module
* coupling
* @ports: Holding the state among ports
* @reg_mutex: The lock for protecting among process accessing
* registers
* @p6_interface Holding the current port 6 interface
* @p5_intf_sel: Holding the current port 5 interface select
*/
struct an8855_priv {
struct device *dev;
struct dsa_switch *ds;
struct mii_bus *bus;
struct reset_control *rstc;
struct regulator *core_pwr;
struct regulator *io_pwr;
struct gpio_desc *reset;
void __iomem *base;
const struct an8855_dev_info *info;
unsigned int phy_base;
int phy_base_new;
unsigned int id;
u32 intr_pin;
phy_interface_t p5_interface;
unsigned int p5_intf_sel;
u8 mirror_rx;
u8 mirror_tx;
u8 eee_enable;
u32 extSurge;
struct an8855_port ports[AN8855_NUM_PORTS];
/* protect among processes for registers access */
struct mutex reg_mutex;
};
struct an8855_hw_vlan_entry {
int port;
u8 old_members;
bool untagged;
};
static inline void an8855_hw_vlan_entry_init(struct an8855_hw_vlan_entry *e,
int port, bool untagged)
{
e->port = port;
e->untagged = untagged;
}
typedef void (*an8855_vlan_op) (struct an8855_priv *,
struct an8855_hw_vlan_entry *);
struct an8855_hw_stats {
const char *string;
u16 reg;
u8 sizeof_stat;
};
struct an8855_dummy_poll {
struct an8855_priv *priv;
u32 reg;
};
static inline void INIT_AN8855_DUMMY_POLL(struct an8855_dummy_poll *p,
struct an8855_priv *priv, u32 reg)
{
p->priv = priv;
p->reg = reg;
}
int an8855_phy_setup(struct dsa_switch *ds);
u32 an8855_read(struct an8855_priv *priv, u32 reg);
void an8855_write(struct an8855_priv *priv, u32 reg, u32 val);
int an8855_phy_cl22_read(struct an8855_priv *priv, int port, int regnum);
int an8855_phy_cl22_write(struct an8855_priv *priv, int port,
int regnum, u16 val);
int an8855_phy_cl45_read(struct an8855_priv *priv, int port, int devad,
int regnum);
int an8855_phy_cl45_write(struct an8855_priv *priv, int port, int devad,
int regnum, u16 val);
#endif /* __AN8855_H */

View File

@ -0,0 +1,320 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <net/genetlink.h>
#include <linux/of_mdio.h>
#include <linux/phylink.h>
#include <net/dsa.h>
#include "an8855.h"
#include "an8855_nl.h"
struct an8855_nl_cmd_item {
enum an8855_cmd cmd;
bool require_dev;
int (*process)(struct genl_info *info);
u32 nr_required_attrs;
const enum an8855_attr *required_attrs;
};
struct an8855_priv *an8855_sw_priv;
static DEFINE_MUTEX(an8855_devs_lock);
void
an8855_put(void)
{
mutex_unlock(&an8855_devs_lock);
}
void
an8855_lock(void)
{
mutex_lock(&an8855_devs_lock);
}
static int an8855_nl_response(struct sk_buff *skb, struct genl_info *info);
static const struct nla_policy an8855_nl_cmd_policy[] = {
[AN8855_ATTR_TYPE_MESG] = {.type = NLA_STRING},
[AN8855_ATTR_TYPE_PHY] = {.type = NLA_S32},
[AN8855_ATTR_TYPE_REG] = {.type = NLA_U32},
[AN8855_ATTR_TYPE_VAL] = {.type = NLA_U32},
[AN8855_ATTR_TYPE_DEV_NAME] = {.type = NLA_S32},
[AN8855_ATTR_TYPE_DEV_ID] = {.type = NLA_S32},
[AN8855_ATTR_TYPE_DEVAD] = {.type = NLA_S32},
};
static const struct genl_ops an8855_nl_ops[] = {
{
.cmd = AN8855_CMD_REQUEST,
.doit = an8855_nl_response,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = AN8855_CMD_READ,
.doit = an8855_nl_response,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = AN8855_CMD_WRITE,
.doit = an8855_nl_response,
.flags = GENL_ADMIN_PERM,
},
};
static struct genl_family an8855_nl_family = {
.name = AN8855_DSA_GENL_NAME,
.version = AN8855_GENL_VERSION,
.maxattr = AN8855_NR_ATTR_TYPE,
.ops = an8855_nl_ops,
.n_ops = ARRAY_SIZE(an8855_nl_ops),
.policy = an8855_nl_cmd_policy,
};
static int
an8855_nl_prepare_reply(struct genl_info *info, u8 cmd,
struct sk_buff **skbp)
{
struct sk_buff *msg;
void *reply;
if (!info)
return -EINVAL;
msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (!msg)
return -ENOMEM;
/* Construct send-back message header */
reply = genlmsg_put(msg, info->snd_portid, info->snd_seq,
&an8855_nl_family, 0, cmd);
if (!reply) {
nlmsg_free(msg);
return -EINVAL;
}
*skbp = msg;
return 0;
}
static int
an8855_nl_send_reply(struct sk_buff *skb, struct genl_info *info)
{
struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb));
void *reply = genlmsg_data(genlhdr);
/* Finalize a generic netlink message (update message header) */
genlmsg_end(skb, reply);
/* reply to a request */
return genlmsg_reply(skb, info);
}
static s32
an8855_nl_get_s32(struct genl_info *info, enum an8855_attr attr,
s32 defval)
{
struct nlattr *na;
na = info->attrs[attr];
if (na)
return nla_get_s32(na);
return defval;
}
static int
an8855_nl_get_u32(struct genl_info *info, enum an8855_attr attr,
u32 *val)
{
struct nlattr *na;
na = info->attrs[attr];
if (na) {
*val = nla_get_u32(na);
return 0;
}
return -1;
}
static int
an8855_nl_reply_read(struct genl_info *info)
{
struct sk_buff *rep_skb = NULL;
s32 phy, devad;
u32 reg = 0;
int value = 0;
int ret = 0;
phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1);
devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1);
if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_REG, &reg))
goto err;
ret = an8855_nl_prepare_reply(info, AN8855_CMD_READ, &rep_skb);
if (ret < 0)
goto err;
if (phy >= 0) {
if (devad < 0)
value = an8855_phy_cl22_read(an8855_sw_priv, phy, reg);
else
value = an8855_phy_cl45_read(an8855_sw_priv, phy,
devad, reg);
} else
value = an8855_read(an8855_sw_priv, reg);
ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_REG, reg);
if (ret < 0)
goto err;
ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_VAL, value);
if (ret < 0)
goto err;
return an8855_nl_send_reply(rep_skb, info);
err:
if (rep_skb)
nlmsg_free(rep_skb);
return ret;
}
static int
an8855_nl_reply_write(struct genl_info *info)
{
struct sk_buff *rep_skb = NULL;
s32 phy, devad;
u32 value = 0, reg = 0;
int ret = 0;
phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1);
devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1);
if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_REG, &reg))
goto err;
if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_VAL, &value))
goto err;
ret = an8855_nl_prepare_reply(info, AN8855_CMD_WRITE, &rep_skb);
if (ret < 0)
goto err;
if (phy >= 0) {
if (devad < 0)
an8855_phy_cl22_write(an8855_sw_priv, phy, reg, value);
else
an8855_phy_cl45_write(an8855_sw_priv, phy, devad, reg,
value);
} else
an8855_write(an8855_sw_priv, reg, value);
ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_REG, reg);
if (ret < 0)
goto err;
ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_VAL, value);
if (ret < 0)
goto err;
return an8855_nl_send_reply(rep_skb, info);
err:
if (rep_skb)
nlmsg_free(rep_skb);
return ret;
}
static const enum an8855_attr an8855_nl_cmd_read_attrs[] = {
AN8855_ATTR_TYPE_REG
};
static const enum an8855_attr an8855_nl_cmd_write_attrs[] = {
AN8855_ATTR_TYPE_REG,
AN8855_ATTR_TYPE_VAL
};
static const struct an8855_nl_cmd_item an8855_nl_cmds[] = {
{
.cmd = AN8855_CMD_READ,
.require_dev = true,
.process = an8855_nl_reply_read,
.required_attrs = an8855_nl_cmd_read_attrs,
.nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_read_attrs),
},
{
.cmd = AN8855_CMD_WRITE,
.require_dev = true,
.process = an8855_nl_reply_write,
.required_attrs = an8855_nl_cmd_write_attrs,
.nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_write_attrs),
}
};
static int
an8855_nl_response(struct sk_buff *skb, struct genl_info *info)
{
struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
const struct an8855_nl_cmd_item *cmditem = NULL;
u32 sat_req_attrs = 0;
int i, ret;
for (i = 0; i < ARRAY_SIZE(an8855_nl_cmds); i++) {
if (hdr->cmd == an8855_nl_cmds[i].cmd) {
cmditem = &an8855_nl_cmds[i];
break;
}
}
if (!cmditem) {
pr_info("an8855-nl: unknown cmd %u\n", hdr->cmd);
return -EINVAL;
}
for (i = 0; i < cmditem->nr_required_attrs; i++) {
if (info->attrs[cmditem->required_attrs[i]])
sat_req_attrs++;
}
if (sat_req_attrs != cmditem->nr_required_attrs) {
pr_info("an8855-nl: missing required attr(s) for cmd %u\n",
hdr->cmd);
return -EINVAL;
}
ret = cmditem->process(info);
an8855_put();
return ret;
}
int
an8855_nl_init(struct an8855_priv **priv)
{
int ret;
pr_info("an8855-nl: genl_register_family_with_ops\n");
an8855_sw_priv = *priv;
ret = genl_register_family(&an8855_nl_family);
if (ret)
return ret;
return 0;
}
void
an8855_nl_exit(void)
{
an8855_sw_priv = NULL;
genl_unregister_family(&an8855_nl_family);
}

View File

@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#ifndef _AN8855_NL_H_
#define _AN8855_NL_H_
#define AN8855_DSA_GENL_NAME "an8855_dsa"
#define AN8855_GENL_VERSION 0x1
enum an8855_cmd {
AN8855_CMD_UNSPEC = 0,
AN8855_CMD_REQUEST,
AN8855_CMD_REPLY,
AN8855_CMD_READ,
AN8855_CMD_WRITE,
__AN8855_CMD_MAX,
};
enum an8855_attr {
AN8855_ATTR_TYPE_UNSPEC = 0,
AN8855_ATTR_TYPE_MESG,
AN8855_ATTR_TYPE_PHY,
AN8855_ATTR_TYPE_DEVAD,
AN8855_ATTR_TYPE_REG,
AN8855_ATTR_TYPE_VAL,
AN8855_ATTR_TYPE_DEV_NAME,
AN8855_ATTR_TYPE_DEV_ID,
__AN8855_ATTR_TYPE_MAX,
};
#define AN8855_NR_ATTR_TYPE (__AN8855_ATTR_TYPE_MAX - 1)
#ifdef __KERNEL__
int an8855_nl_init(struct an8855_priv **priv);
void an8855_nl_exit(void);
#endif /* __KERNEL__ */
#endif /* _AN8855_NL_H_ */

View File

@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Common part for Airoha AN8855 gigabit switch
*
* Copyright (C) 2023 Airoha Inc. All Rights Reserved.
*
* Author: Min Yao <min.yao@airoha.com>
*/
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <linux/kernel.h>
#include <net/dsa.h>
#include "an8855.h"
#include "an8855_phy.h"
#define AN8855_EFUSE_DATA0 0x1000a500
const u8 dsa_r50ohm_table[] = {
127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
127, 127, 127, 127, 127, 127, 127, 126, 122, 117,
112, 109, 104, 101, 97, 94, 90, 88, 84, 80,
78, 74, 72, 68, 66, 64, 61, 58, 56, 53,
51, 48, 47, 44, 42, 40, 38, 36, 34, 32,
31, 28, 27, 24, 24, 22, 20, 18, 16, 16,
14, 12, 11, 9
};
static u8 shift_check(u8 base)
{
u8 i;
u32 sz = sizeof(dsa_r50ohm_table)/sizeof(u8);
for (i = 0; i < sz; ++i)
if (dsa_r50ohm_table[i] == base)
break;
if (i < 8 || i >= sz)
return 25; /* index of 94 */
return (i - 8);
}
static u8 get_shift_val(u8 idx)
{
return dsa_r50ohm_table[idx];
}
static void
an8855_switch_phy_write(struct dsa_switch *ds, u32 port_num,
u32 reg_addr, u32 write_data)
{
struct an8855_priv *priv = ds->priv;
priv->info->phy_write(ds, port_num, reg_addr, write_data);
}
static u32
an8855_switch_phy_read(struct dsa_switch *ds, u32 port_num,
u32 reg_addr)
{
struct an8855_priv *priv = ds->priv;
return priv->info->phy_read(ds, port_num, reg_addr);
}
static void
an8855_phy_setting(struct dsa_switch *ds)
{
struct an8855_priv *priv = ds->priv;
int i, j;
u8 shift_sel = 0, rsel_tx_a = 0, rsel_tx_b = 0;
u8 rsel_tx_c = 0, rsel_tx_d = 0;
u16 cl45_data = 0;
u32 val;
/* Release power down */
an8855_write(priv, RG_GPHY_AFE_PWD, 0x0);
for (i = 0; i < AN8855_NUM_PHYS; i++) {
/* Enable HW auto downshift */
an8855_switch_phy_write(ds, i, 0x1f, 0x1);
val = an8855_switch_phy_read(ds, i, PHY_EXT_REG_14);
val |= PHY_EN_DOWN_SHFIT;
an8855_switch_phy_write(ds, i, PHY_EXT_REG_14, val);
an8855_switch_phy_write(ds, i, 0x1f, 0x0);
/* Enable Asymmetric Pause Capability */
val = an8855_switch_phy_read(ds, i, MII_ADVERTISE);
val |= ADVERTISE_PAUSE_ASYM;
an8855_switch_phy_write(ds, i, MII_ADVERTISE, val);
}
if (priv->extSurge) {
for (i = 0; i < AN8855_NUM_PHYS; i++) {
/* Read data */
for (j = 0; j < AN8855_WORD_SIZE; j++) {
val = an8855_read(priv, AN8855_EFUSE_DATA0 +
(AN8855_WORD_SIZE * (3 + j + (4 * i))));
shift_sel = shift_check((val & 0x7f000000) >> 24);
switch (j) {
case 0:
rsel_tx_a = get_shift_val(shift_sel);
break;
case 1:
rsel_tx_b = get_shift_val(shift_sel);
break;
case 2:
rsel_tx_c = get_shift_val(shift_sel);
break;
case 3:
rsel_tx_d = get_shift_val(shift_sel);
break;
default:
continue;
}
}
cl45_data = an8855_phy_cl45_read(priv, i, PHY_DEV1E, 0x174);
cl45_data &= ~(0x7f7f);
cl45_data |= (rsel_tx_a << 8);
cl45_data |= rsel_tx_b;
an8855_phy_cl45_write(priv, i, PHY_DEV1E, 0x174, cl45_data);
cl45_data = an8855_phy_cl45_read(priv, i, PHY_DEV1E, 0x175);
cl45_data &= ~(0x7f7f);
cl45_data |= (rsel_tx_c << 8);
cl45_data |= rsel_tx_d;
an8855_phy_cl45_write(priv, i, PHY_DEV1E, 0x175, cl45_data);
}
}
}
static void
an8855_low_power_setting(struct dsa_switch *ds)
{
int port, addr;
struct an8855_priv *priv = ds->priv;
for (port = 0; port < AN8855_NUM_PHYS; port++) {
an8855_phy_cl45_write(priv, port, 0x1e, 0x11, 0x0f00);
an8855_phy_cl45_write(priv, port, 0x1e, 0x3c, 0x0000);
an8855_phy_cl45_write(priv, port, 0x1e, 0x3d, 0x0000);
an8855_phy_cl45_write(priv, port, 0x1e, 0x3e, 0x0000);
an8855_phy_cl45_write(priv, port, 0x1e, 0xc6, 0x53aa);
}
an8855_phy_cl45_write(priv, 0, 0x1f, 0x268, 0x07f1);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x269, 0x2111);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x26a, 0x0000);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x26b, 0x0074);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x26e, 0x00f6);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x26f, 0x6666);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x271, 0x2c02);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x272, 0x0c22);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x700, 0x0001);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x701, 0x0803);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x702, 0x01b6);
an8855_phy_cl45_write(priv, 0, 0x1f, 0x703, 0x2111);
an8855_phy_cl45_write(priv, 1, 0x1f, 0x700, 0x0001);
for (addr = 0x200; addr <= 0x230; addr += 2)
an8855_phy_cl45_write(priv, 0, 0x1f, addr, 0x2020);
for (addr = 0x201; addr <= 0x231; addr += 2)
an8855_phy_cl45_write(priv, 0, 0x1f, addr, 0x0020);
}
static void
an8855_eee_setting(struct dsa_switch *ds, u32 port)
{
struct an8855_priv *priv = ds->priv;
/* Disable EEE */
an8855_phy_cl45_write(priv, port, PHY_DEV07, PHY_DEV07_REG_03C, 0);
}
int
an8855_phy_setup(struct dsa_switch *ds)
{
int ret = 0;
int i;
an8855_phy_setting(ds);
for (i = 0; i < AN8855_NUM_PHYS; i++)
an8855_eee_setting(ds, i);
return ret;
}

View File

@ -0,0 +1,321 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Common part for Airoha AN8855 gigabit switch
*
* Copyright (C) 2023 Airoha Inc. All Rights Reserved.
*
* Author: Min Yao <min.yao@airoha.com>
*/
#ifndef _AN8855_PHY_H_
#define _AN8855_PHY_H_
#include <linux/bitops.h>
#define AN8855_NUM_PHYS 5
/*phy calibration use*/
#define DEV_1E 0x1E
/*global device 0x1f, always set P0*/
#define DEV_1F 0x1F
/************IEXT/REXT CAL***************/
/* bits range: for example BITS(16,23) = 0xFF0000*/
#define BITS(m, n) (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n)))
#define ANACAL_INIT 0x01
#define ANACAL_ERROR 0xFD
#define ANACAL_SATURATION 0xFE
#define ANACAL_FINISH 0xFF
#define ANACAL_PAIR_A 0
#define ANACAL_PAIR_B 1
#define ANACAL_PAIR_C 2
#define ANACAL_PAIR_D 3
#define DAC_IN_0V 0x00
#define DAC_IN_2V 0xf0
#define TX_AMP_OFFSET_0MV 0x20
#define TX_AMP_OFFSET_VALID_BITS 6
#define R0 0
#define PHY0 0
#define PHY1 1
#define PHY2 2
#define PHY3 3
#define PHY4 4
#define ANA_TEST_MODE BITS(8, 15)
#define TST_TCLK_SEL BITs(6, 7)
#define ANA_TEST_VGA_RG 0x100
#define FORCE_MDI_CROSS_OVER BITS(3, 4)
#define T10_TEST_CTL_RG 0x145
#define RG_185 0x185
#define RG_TX_SLEW BIT(0)
#define ANA_CAL_0 0xdb
#define RG_CAL_CKINV BIT(12)
#define RG_ANA_CALEN BIT(8)
#define RG_REXT_CALEN BIT(4)
#define RG_ZCALEN_A BIT(0)
#define ANA_CAL_1 0xdc
#define RG_ZCALEN_B BIT(12)
#define RG_ZCALEN_C BIT(8)
#define RG_ZCALEN_D BIT(4)
#define RG_TXVOS_CALEN BIT(0)
#define ANA_CAL_6 0xe1
#define RG_CAL_REFSEL BIT(4)
#define RG_CAL_COMP_PWD BIT(0)
#define ANA_CAL_5 0xe0
#define RG_REXT_TRIM BITs(8, 13)
#define RG_ZCAL_CTRL BITs(0, 5)
#define RG_17A 0x17a
#define AD_CAL_COMP_OUT BIT(8)
#define RG_17B 0x17b
#define AD_CAL_CLK bit(0)
#define RG_17C 0x17c
#define DA_CALIN_FLAG bit(0)
/************R50 CAL****************************/
#define RG_174 0x174
#define RG_R50OHM_RSEL_TX_A_EN BIT[15]
#define CR_R50OHM_RSEL_TX_A BITS[8:14]
#define RG_R50OHM_RSEL_TX_B_EN BIT[7]
#define CR_R50OHM_RSEL_TX_B BITS[6:0]
#define RG_175 0x175
#define RG_R50OHM_RSEL_TX_C_EN BITS[15]
#define CR_R50OHM_RSEL_TX_C BITS[8:14]
#define RG_R50OHM_RSEL_TX_D_EN BIT[7]
#define CR_R50OHM_RSEL_TX_D BITS[0:6]
/**********TX offset Calibration***************************/
#define RG_95 0x96
#define BYPASS_TX_OFFSET_CAL BIT(15)
#define RG_3E 0x3e
#define BYPASS_PD_TXVLD_A BIT(15)
#define BYPASS_PD_TXVLD_B BIT(14)
#define BYPASS_PD_TXVLD_C BIT(13)
#define BYPASS_PD_TXVLD_D BIT(12)
#define BYPASS_PD_TX_10M BIT(11)
#define POWER_DOWN_TXVLD_A BIT(7)
#define POWER_DOWN_TXVLD_B BIT(6)
#define POWER_DOWN_TXVLD_C BIT(5)
#define POWER_DOWN_TXVLD_D BIT(4)
#define POWER_DOWN_TX_10M BIT(3)
#define RG_DD 0xdd
#define RG_TXG_CALEN_A BIT(12)
#define RG_TXG_CALEN_B BIT(8)
#define RG_TXG_CALEN_C BIT(4)
#define RG_TXG_CALEN_D BIT(0)
#define RG_17D 0x17D
#define FORCE_DASN_DAC_IN0_A BIT(15)
#define DASN_DAC_IN0_A BITS(0, 9)
#define RG_17E 0x17E
#define FORCE_DASN_DAC_IN0_B BIT(15)
#define DASN_DAC_IN0_B BITS(0, 9)
#define RG_17F 0x17F
#define FORCE_DASN_DAC_IN0_C BIT(15)
#define DASN_DAC_IN0_C BITS(0, 9)
#define RG_180 0x180
#define FORCE_DASN_DAC_IN0_D BIT(15)
#define DASN_DAC_IN0_D BITS(0, 9)
#define RG_181 0x181
#define FORCE_DASN_DAC_IN1_A BIT(15)
#define DASN_DAC_IN1_A BITS(0, 9)
#define RG_182 0x182
#define FORCE_DASN_DAC_IN1_B BIT(15)
#define DASN_DAC_IN1_B BITS(0, 9)
#define RG_183 0x183
#define FORCE_DASN_DAC_IN1_C BIT(15)
#define DASN_DAC_IN1_C BITS(0, 9)
#define RG_184 0x184
#define FORCE_DASN_DAC_IN1_D BIT(15)
#define DASN_DAC_IN1_D BITS(0, 9)
#define RG_172 0x172
#define CR_TX_AMP_OFFSET_A BITS(8, 13)
#define CR_TX_AMP_OFFSET_B BITS(0, 5)
#define RG_173 0x173
#define CR_TX_AMP_OFFSET_C BITS(8, 13)
#define CR_TX_AMP_OFFSET_D BITS(0, 5)
/**********TX Amp Calibration ***************************/
#define RG_12 0x12
#define DA_TX_I2MPB_A_GBE BITS(10, 15)
#define RG_17 0x17
#define DA_TX_I2MPB_B_GBE BITS(8, 13)
#define RG_19 0x19
#define DA_TX_I2MPB_C_GBE BITS(8, 13)
#define RG_21 0x21
#define DA_TX_I2MPB_D_GBE BITS(8, 13)
#define TX_AMP_MAX 0x3f
#define TX_AMP_MAX_OFFSET 0xb
#define TX_AMP_HIGHEST_TS ((TX_AMP_MAX) + 3)
#define TX_AMP_LOWEST_TS (0 - 3)
#define TX_AMP_HIGH_TS (TX_AMP_MAX)
#define TX_AMP_LOW_TS 0
/* PHY Extend Register 0x14 bitmap of define */
#define PHY_EXT_REG_14 0x14
/* Fields of PHY_EXT_REG_14 */
#define PHY_EN_DOWN_SHFIT BIT(4)
/* PHY Extend Register 0x17 bitmap of define */
#define PHY_EXT_REG_17 0x17
/* Fields of PHY_EXT_REG_17 */
#define PHY_LINKDOWN_POWER_SAVING_EN BIT(4)
/* PHY PMA Register 0x17 bitmap of define */
#define SLV_DSP_READY_TIME_S 15
#define SLV_DSP_READY_TIME_M (0xff << SLV_DSP_READY_TIME_S)
/* PHY PMA Register 0x18 bitmap of define */
#define ENABLE_RANDOM_UPDATE_TRIGGER BIT(8)
/* PHY EEE Register bitmap of define */
#define PHY_DEV07 0x07
#define PHY_DEV07_REG_03C 0x3c
#define ADV_EEE_100 0x2
#define ADV_EEE_1000 0x4
/* PHY DEV 0x1e Register bitmap of define */
#define PHY_DEV1E 0x1e
/* PHY TX PAIR DELAY SELECT Register */
#define PHY_TX_PAIR_DLY_SEL_GBE 0x013
/* PHY ADC Register */
#define PHY_RXADC_CTRL 0x0d8
#define PHY_RXADC_REV_0 0x0d9
#define PHY_RXADC_REV_1 0x0da
/* 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)
/* Proprietory Control Register of Internal Phy device 0x1e */
#define PHY_TX_MLT3_BASE 0x0
#define PHY_DEV1E_REG_13 0x13
#define PHY_DEV1E_REG_14 0x14
#define PHY_DEV1E_REG_41 0x41
#define PHY_DEV1E_REG_A6 0xa6
#define RXADC_CONTROL_3 0xc2
#define PHY_DEV1E_REG_0C6 0xc6
#define RXADC_LDO_CONTROL_2 0xd3
#define PHY_DEV1E_REG_0FE 0xfe
#define PHY_DEV1E_REG_123 0x123
#define PHY_DEV1E_REG_189 0x189
#define PHY_DEV1E_REG_234 0x234
/* Proprietory Control Register of Internal Phy device 0x1f */
#define PHY_DEV1F_REG_44 0x44
#define PHY_DEV1F_REG_268 0x268
#define PHY_DEV1F_REG_269 0x269
#define PHY_DEV1F_REG_26A 0x26A
#define TXVLD_DA_271 0x271
#define TXVLD_DA_272 0x272
#define TXVLD_DA_273 0x273
/* Fields of PHY_DEV1E_REG_0C6 */
#define PHY_POWER_SAVING_S 8
#define PHY_POWER_SAVING_M 0x300
#define PHY_POWER_SAVING_TX 0x0
/* Fields of PHY_DEV1E_REG_189 */
#define DESCRAMBLER_CLEAR_EN 0x1
/* Fields of PHY_DEV1E_REG_234 */
#define TR_OPEN_LOOP_EN BIT(0)
/* Internal GPHY Page Control Register */
#define PHY_CL22_PAGE_CTRL 0x1f
#define PHY_TR_PAGE 0x52b5
/* Internal GPHY Token Ring Access Registers */
#define PHY_TR_CTRL 0x10
#define PHY_TR_LOW_DATA 0x11
#define PHY_TR_HIGH_DATA 0x12
/* Fields of PHY_TR_CTRL */
#define PHY_TR_PKT_XMT_STA BIT(15)
#define PHY_TR_WR_S 13
#define PHY_TR_CH_ADDR_S 11
#define PHY_TR_NODE_ADDR_S 7
#define PHY_TR_DATA_ADDR_S 1
enum phy_tr_wr {
PHY_TR_WRITE = 0,
PHY_TR_READ = 1,
};
/* Helper macro for GPHY Token Ring Access */
#define PHY_TR_LOW_VAL(x) ((x) & 0xffff)
#define PHY_TR_HIGH_VAL(x) (((x) & 0xff0000) >> 16)
/* Token Ring Channels */
#define PMA_CH 0x1
#define DSP_CH 0x2
/* Token Ring Nodes */
#define PMA_NOD 0xf
#define DSP_NOD 0xd
/* Token Ring register range */
enum tr_pma_reg_addr {
PMA_MIN = 0x0,
PMA_01 = 0x1,
PMA_17 = 0x17,
PMA_18 = 0x18,
PMA_MAX = 0x3d,
};
enum tr_dsp_reg_addr {
DSP_MIN = 0x0,
DSP_06 = 0x6,
DSP_08 = 0x8,
DSP_0f = 0xf,
DSP_10 = 0x10,
DSP_MAX = 0x3e,
};
#endif /* _AN8855_REGS_H_ */

View File

@ -2,4 +2,8 @@ ccflags-y=-Werror
obj-$(CONFIG_NET_MEDIATEK_HNAT) += mtkhnat.o
mtkhnat-objs := hnat.o hnat_nf_hook.o hnat_debugfs.o hnat_mcast.o
ifeq ($(CONFIG_NET_DSA_AN8855), y)
mtkhnat-y += hnat_stag.o
else
mtkhnat-$(CONFIG_NET_DSA_MT7530) += hnat_stag.o
endif

View File

@ -0,0 +1,8 @@
config AN8855_GSW
tristate "Driver for the Airoha AN8855 switch"
help
Airoha AN8855 devices and swconfig driver code,
Provide swconfig command for basic switch operation.
AN8855 support 2.5G speed and managed by SMI interface.
To compile this driver as a module, choose M here.

View File

@ -0,0 +1,10 @@
#
# Makefile for Airoha AN8855 gigabit switch
#
obj-$(CONFIG_AN8855_GSW) += an8855.o
an8855-$(CONFIG_SWCONFIG) += an8855_swconfig.o
an8855-y += an8855_mdio.o an8855.o \
an8855_common.o an8855_vlan.o an8855_nl.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,194 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#ifndef _AN8855_H_
#define _AN8855_H_
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
#include <linux/of_mdio.h>
#include <linux/workqueue.h>
#include <linux/gpio/consumer.h>
#include <linux/phy.h>
#ifdef CONFIG_SWCONFIG
#include <linux/switch.h>
#endif
#include "an8855_vlan.h"
#define AN8855_DFL_CPU_PORT 5
#define AN8855_NUM_PHYS 5
#define AN8855_WORD_SIZE 4
#define AN8855_DFL_SMI_ADDR 0x1
#define AN8855_SMI_ADDR_MASK 0x1f
#define AN8855_DFL_INTR_ID 0xd
#define AN8855_DFL_EXT_SURGE 0x0
#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
struct gsw_an8855;
enum an8855_model {
AN8855 = 0x8855,
};
enum sgmii_mode {
SGMII_MODE_AN,
SGMII_MODE_FORCE,
};
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
};
/* TBD */
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
};
struct an8855_led_cfg {
u16 en;
u8 phy_led_idx;
u16 pol;
u16 on_cfg;
u16 blk_cfg;
u8 led_freq;
};
struct an8855_port_cfg {
struct device_node *np;
phy_interface_t phy_mode;
u32 enabled: 1;
u32 force_link: 1;
u32 speed: 2;
u32 duplex: 1;
bool stag_on;
};
struct gsw_an8855 {
u32 id;
struct device *dev;
struct mii_bus *host_bus;
u32 smi_addr;
u32 new_smi_addr;
u32 phy_base;
u32 intr_pin;
u32 extSurge;
enum an8855_model model;
const char *name;
struct an8855_port_cfg port5_cfg;
int phy_link_sts;
int irq;
int reset_pin;
struct work_struct irq_worker;
#ifdef CONFIG_SWCONFIG
struct switch_dev swdev;
u32 cpu_port;
#endif
int global_vlan_enable;
struct an8855_vlan_entry vlan_entries[AN8855_NUM_VLANS];
struct an8855_port_entry port_entries[AN8855_NUM_PORTS];
int (*mii_read)(struct gsw_an8855 *gsw, int phy, int reg);
void (*mii_write)(struct gsw_an8855 *gsw, int phy, int reg, u16 val);
int (*mmd_read)(struct gsw_an8855 *gsw, int addr, int devad, u16 reg);
void (*mmd_write)(struct gsw_an8855 *gsw, int addr, int devad, u16 reg,
u16 val);
struct list_head list;
};
struct chip_rev {
const char *name;
u32 rev;
};
struct an8855_sw_id {
enum an8855_model model;
int (*detect)(struct gsw_an8855 *gsw, struct chip_rev *crev);
int (*init)(struct gsw_an8855 *gsw);
int (*post_init)(struct gsw_an8855 *gsw);
};
extern struct list_head an8855_devs;
extern struct an8855_sw_id an8855_id;
struct gsw_an8855 *an8855_get_gsw(u32 id);
struct gsw_an8855 *an8855_get_first_gsw(void);
void an8855_put_gsw(void);
void an8855_lock_gsw(void);
u32 an8855_reg_read(struct gsw_an8855 *gsw, u32 reg);
void an8855_reg_write(struct gsw_an8855 *gsw, u32 reg, u32 val);
int an8855_mii_read(struct gsw_an8855 *gsw, int phy, int reg);
void an8855_mii_write(struct gsw_an8855 *gsw, int phy, int reg, u16 val);
int an8855_mmd_read(struct gsw_an8855 *gsw, int addr, int devad, u16 reg);
void an8855_mmd_write(struct gsw_an8855 *gsw, int addr, int devad, u16 reg,
u16 val);
void an8855_irq_worker(struct work_struct *work);
void an8855_irq_enable(struct gsw_an8855 *gsw);
#endif /* _AN8855_H_ */

View File

@ -0,0 +1,96 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#include <linux/kernel.h>
#include <linux/delay.h>
#include "an8855.h"
#include "an8855_regs.h"
void an8855_irq_enable(struct gsw_an8855 *gsw)
{
u32 val;
int i;
/* Record initial PHY link status */
for (i = 0; i < AN8855_NUM_PHYS; i++) {
val = gsw->mii_read(gsw, i, MII_BMSR);
if (val & BMSR_LSTATUS)
gsw->phy_link_sts |= BIT(i);
}
val = BIT(AN8855_NUM_PHYS) - 1;
an8855_reg_write(gsw, SYS_INT_EN, val);
val = an8855_reg_read(gsw, INT_MASK);
val |= INT_SYS_BIT;
an8855_reg_write(gsw, INT_MASK, val);
}
static void display_port_link_status(struct gsw_an8855 *gsw, u32 port)
{
u32 pmsr, speed_bits;
const char *speed;
pmsr = an8855_reg_read(gsw, PMSR(port));
speed_bits = (pmsr & MAC_SPD_STS_M) >> MAC_SPD_STS_S;
switch (speed_bits) {
case MAC_SPD_10:
speed = "10Mbps";
break;
case MAC_SPD_100:
speed = "100Mbps";
break;
case MAC_SPD_1000:
speed = "1Gbps";
break;
case MAC_SPD_2500:
speed = "2.5Gbps";
break;
default:
dev_info(gsw->dev, "Invalid speed\n");
return;
}
if (pmsr & MAC_LNK_STS) {
dev_info(gsw->dev, "Port %d Link is Up - %s/%s\n",
port, speed, (pmsr & MAC_DPX_STS) ? "Full" : "Half");
} else {
dev_info(gsw->dev, "Port %d Link is Down\n", port);
}
}
void an8855_irq_worker(struct work_struct *work)
{
struct gsw_an8855 *gsw;
u32 sts, physts, laststs;
int i;
gsw = container_of(work, struct gsw_an8855, irq_worker);
sts = an8855_reg_read(gsw, SYS_INT_STS);
/* Check for changed PHY link status */
for (i = 0; i < AN8855_NUM_PHYS; i++) {
if (!(sts & PHY_LC_INT(i)))
continue;
laststs = gsw->phy_link_sts & BIT(i);
physts = !!(gsw->mii_read(gsw, i, MII_BMSR) & BMSR_LSTATUS);
physts <<= i;
if (physts ^ laststs) {
gsw->phy_link_sts ^= BIT(i);
display_port_link_status(gsw, i);
}
}
an8855_reg_write(gsw, SYS_INT_STS, sts);
enable_irq(gsw->irq);
}

View File

@ -0,0 +1,569 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/reset.h>
#include <linux/hrtimer.h>
#include <linux/mii.h>
#include <linux/of_mdio.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_net.h>
#include <linux/of_irq.h>
#include <linux/phy.h>
#include <linux/version.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include "an8855.h"
#include "an8855_swconfig.h"
#include "an8855_regs.h"
#include "an8855_nl.h"
/* AN8855 driver version */
#define ARHT_AN8855_SWCFG_DRIVER_VER "1.0.6"
#define ARHT_CHIP_NAME "an8855"
#define ARHT_PROC_DIR "air_sw"
#define ARHT_PROC_NODE_DEVICE "device"
static u32 an8855_gsw_id;
struct list_head an8855_devs;
static DEFINE_MUTEX(an8855_devs_lock);
struct proc_dir_entry *proc_an8855_gsw_dir;
static struct an8855_sw_id *an8855_sw_ids[] = {
&an8855_id,
};
u32 an8855_reg_read(struct gsw_an8855 *gsw, u32 reg)
{
u32 high, low;
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x4);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x15,
((reg >> 16) & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x16,
(reg & 0xFFFF));
low = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x18);
high = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x17);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x0);
mutex_unlock(&gsw->host_bus->mdio_lock);
return (high << 16) | (low & 0xffff);
}
void an8855_reg_write(struct gsw_an8855 *gsw, u32 reg, u32 val)
{
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x4);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x11,
((reg >> 16) & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x12,
(reg & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x13,
((val >> 16) & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x14,
(val & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x0);
mutex_unlock(&gsw->host_bus->mdio_lock);
}
int an8855_mii_read(struct gsw_an8855 *gsw, int phy, int reg)
{
int val;
if (phy < AN8855_NUM_PHYS)
phy = (gsw->phy_base + phy) & AN8855_SMI_ADDR_MASK;
mutex_lock(&gsw->host_bus->mdio_lock);
val = gsw->host_bus->read(gsw->host_bus, phy, reg);
mutex_unlock(&gsw->host_bus->mdio_lock);
return val;
}
void an8855_mii_write(struct gsw_an8855 *gsw, int phy, int reg, u16 val)
{
if (phy < AN8855_NUM_PHYS)
phy = (gsw->phy_base + phy) & AN8855_SMI_ADDR_MASK;
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, phy, reg, val);
mutex_unlock(&gsw->host_bus->mdio_lock);
}
int an8855_mmd_read(struct gsw_an8855 *gsw, int addr, int devad, u16 reg)
{
int val;
if (addr < AN8855_NUM_PHYS)
addr = (gsw->phy_base + addr) & AN8855_SMI_ADDR_MASK;
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, addr, 0x0d, devad);
gsw->host_bus->write(gsw->host_bus, addr, 0x0e, reg);
gsw->host_bus->write(gsw->host_bus, addr, 0x0d, devad | (0x4000));
val = gsw->host_bus->read(gsw->host_bus, addr, 0xe);
mutex_unlock(&gsw->host_bus->mdio_lock);
return val;
}
void an8855_mmd_write(struct gsw_an8855 *gsw, int addr, int devad, u16 reg,
u16 val)
{
if (addr < AN8855_NUM_PHYS)
addr = (gsw->phy_base + addr) & AN8855_SMI_ADDR_MASK;
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, addr, 0x0d, devad);
gsw->host_bus->write(gsw->host_bus, addr, 0x0e, reg);
gsw->host_bus->write(gsw->host_bus, addr, 0x0d, devad | (0x4000));
gsw->host_bus->write(gsw->host_bus, addr, 0x0e, val);
mutex_unlock(&gsw->host_bus->mdio_lock);
}
static inline int an8855_get_duplex(const struct device_node *np)
{
return of_property_read_bool(np, "full-duplex");
}
static void an8855_load_port_cfg(struct gsw_an8855 *gsw)
{
struct device_node *port_np;
struct device_node *fixed_link_node;
struct an8855_port_cfg *port_cfg;
u32 port;
#if (KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE)
int ret;
#endif
for_each_child_of_node(gsw->dev->of_node, port_np) {
if (!of_device_is_compatible(port_np, "airoha,an8855-port"))
continue;
if (!of_device_is_available(port_np))
continue;
if (of_property_read_u32(port_np, "reg", &port))
continue;
switch (port) {
case 5:
port_cfg = &gsw->port5_cfg;
break;
default:
continue;
}
if (port_cfg->enabled) {
dev_info(gsw->dev, "duplicated node for port%d\n",
port_cfg->phy_mode);
continue;
}
port_cfg->np = port_np;
#if (KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE)
ret = of_get_phy_mode(port_np, &port_cfg->phy_mode);
if (ret < 0) {
#else
port_cfg->phy_mode = of_get_phy_mode(port_np);
if (port_cfg->phy_mode < 0) {
#endif
dev_info(gsw->dev, "incorrect phy-mode %d\n", port);
continue;
}
fixed_link_node = of_get_child_by_name(port_np, "fixed-link");
if (fixed_link_node) {
u32 speed;
port_cfg->force_link = 1;
port_cfg->duplex = an8855_get_duplex(fixed_link_node);
if (of_property_read_u32(fixed_link_node, "speed",
&speed)) {
speed = 0;
continue;
}
of_node_put(fixed_link_node);
switch (speed) {
case 10:
port_cfg->speed = MAC_SPD_10;
break;
case 100:
port_cfg->speed = MAC_SPD_100;
break;
case 1000:
port_cfg->speed = MAC_SPD_1000;
break;
case 2500:
port_cfg->speed = MAC_SPD_2500;
break;
default:
dev_info(gsw->dev, "incorrect speed %d\n",
speed);
continue;
}
}
port_cfg->stag_on =
of_property_read_bool(port_cfg->np, "airoha,stag-on");
port_cfg->enabled = 1;
}
}
static void an8855_add_gsw(struct gsw_an8855 *gsw)
{
mutex_lock(&an8855_devs_lock);
gsw->id = an8855_gsw_id++;
INIT_LIST_HEAD(&gsw->list);
list_add_tail(&gsw->list, &an8855_devs);
mutex_unlock(&an8855_devs_lock);
}
static void an8855_remove_gsw(struct gsw_an8855 *gsw)
{
mutex_lock(&an8855_devs_lock);
list_del(&gsw->list);
mutex_unlock(&an8855_devs_lock);
}
struct gsw_an8855 *an8855_get_gsw(u32 id)
{
struct gsw_an8855 *dev;
mutex_lock(&an8855_devs_lock);
list_for_each_entry(dev, &an8855_devs, list) {
if (dev->id == id)
return dev;
}
mutex_unlock(&an8855_devs_lock);
return NULL;
}
struct gsw_an8855 *an8855_get_first_gsw(void)
{
struct gsw_an8855 *dev;
mutex_lock(&an8855_devs_lock);
list_for_each_entry(dev, &an8855_devs, list)
return dev;
mutex_unlock(&an8855_devs_lock);
return NULL;
}
void an8855_put_gsw(void)
{
mutex_unlock(&an8855_devs_lock);
}
void an8855_lock_gsw(void)
{
mutex_lock(&an8855_devs_lock);
}
static int an8855_hw_reset(struct gsw_an8855 *gsw)
{
struct device_node *np = gsw->dev->of_node;
int ret;
gsw->reset_pin = of_get_named_gpio(np, "reset-gpios", 0);
if (gsw->reset_pin < 0) {
dev_info(gsw->dev, "No reset pin of switch\n");
return 0;
}
ret = devm_gpio_request(gsw->dev, gsw->reset_pin, "an8855-reset");
if (ret) {
dev_info(gsw->dev, "Failed to request gpio %d\n",
gsw->reset_pin);
return ret;
}
gpio_direction_output(gsw->reset_pin, 0);
gpio_set_value(gsw->reset_pin, 0);
usleep_range(100000, 150000);
gpio_set_value(gsw->reset_pin, 1);
usleep_range(100000, 150000);
return 0;
}
static irqreturn_t an8855_irq_handler(int irq, void *dev)
{
struct gsw_an8855 *gsw = dev;
disable_irq_nosync(gsw->irq);
schedule_work(&gsw->irq_worker);
return IRQ_HANDLED;
}
static int an8855_proc_device_read(struct seq_file *seq, void *v)
{
seq_printf(seq, "%s\n", ARHT_CHIP_NAME);
return 0;
}
static int an8855_proc_device_open(struct inode *inode, struct file *file)
{
return single_open(file, an8855_proc_device_read, 0);
}
#if (KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE)
static const struct proc_ops an8855_proc_device_fops = {
.proc_open = an8855_proc_device_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
#else
static const struct file_operations an8855_proc_device_fops = {
.owner = THIS_MODULE,
.open = an8855_proc_device_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#endif
static int an8855_proc_device_init(struct gsw_an8855 *gsw)
{
if (!proc_an8855_gsw_dir)
proc_an8855_gsw_dir = proc_mkdir(ARHT_PROC_DIR, 0);
proc_create(ARHT_PROC_NODE_DEVICE, 0400, proc_an8855_gsw_dir,
&an8855_proc_device_fops);
return 0;
}
static void an8855_proc_device_exit(void)
{
remove_proc_entry(ARHT_PROC_NODE_DEVICE, 0);
}
static int an8855_probe(struct platform_device *pdev)
{
struct gsw_an8855 *gsw;
struct an8855_sw_id *sw;
struct device_node *np = pdev->dev.of_node;
struct device_node *mdio;
struct mii_bus *mdio_bus;
int ret = -EINVAL;
struct chip_rev rev;
struct an8855_mapping *map;
int i;
mdio = of_parse_phandle(np, "airoha,mdio", 0);
if (!mdio)
return -EINVAL;
mdio_bus = of_mdio_find_bus(mdio);
if (!mdio_bus)
return -EPROBE_DEFER;
gsw = devm_kzalloc(&pdev->dev, sizeof(struct gsw_an8855), GFP_KERNEL);
if (!gsw)
return -ENOMEM;
gsw->host_bus = mdio_bus;
gsw->dev = &pdev->dev;
dev_info(gsw->dev, "AN8855 Driver Version=%s\n",
ARHT_AN8855_SWCFG_DRIVER_VER);
/* Switch hard reset */
if (an8855_hw_reset(gsw)) {
dev_info(&pdev->dev, "reset switch fail.\n");
goto fail;
}
/* Fetch the SMI address first */
gsw->smi_addr = AN8855_DFL_SMI_ADDR;
if (of_property_read_u32(np, "airoha,smi-addr", &gsw->new_smi_addr))
gsw->new_smi_addr = AN8855_DFL_SMI_ADDR;
/* Assign AN8855 interrupt pin */
if (of_property_read_u32(np, "airoha,intr", &gsw->intr_pin))
gsw->intr_pin = AN8855_DFL_INTR_ID;
/* AN8855 surge enhancement */
if (of_property_read_u32(np, "airoha,extSurge", &gsw->extSurge))
gsw->extSurge = AN8855_DFL_EXT_SURGE;
/* Get LAN/WAN port mapping */
map = an8855_find_mapping(np);
if (map) {
an8855_apply_mapping(gsw, map);
gsw->global_vlan_enable = 1;
dev_info(gsw->dev, "LAN/WAN VLAN setting=%s\n", map->name);
}
/* Load MAC port configurations */
an8855_load_port_cfg(gsw);
/* Check for valid switch and then initialize */
an8855_gsw_id = 0;
for (i = 0; i < ARRAY_SIZE(an8855_sw_ids); i++) {
if (!an8855_sw_ids[i]->detect(gsw, &rev)) {
sw = an8855_sw_ids[i];
gsw->name = rev.name;
gsw->model = sw->model;
dev_info(gsw->dev, "Switch is Airoha %s rev %d",
gsw->name, rev.rev);
/* Initialize the switch */
ret = sw->init(gsw);
if (ret)
goto fail;
break;
}
}
if (i >= ARRAY_SIZE(an8855_sw_ids)) {
dev_err(gsw->dev, "No an8855 switch found\n");
goto fail;
}
gsw->irq = platform_get_irq(pdev, 0);
if (gsw->irq >= 0) {
ret = devm_request_irq(gsw->dev, gsw->irq, an8855_irq_handler,
0, dev_name(gsw->dev), gsw);
if (ret) {
dev_err(gsw->dev, "Failed to request irq %d\n",
gsw->irq);
goto fail;
}
INIT_WORK(&gsw->irq_worker, an8855_irq_worker);
}
platform_set_drvdata(pdev, gsw);
an8855_add_gsw(gsw);
an8855_gsw_nl_init();
an8855_proc_device_init(gsw);
an8855_swconfig_init(gsw);
if (sw->post_init)
sw->post_init(gsw);
if (gsw->irq >= 0)
an8855_irq_enable(gsw);
return 0;
fail:
devm_kfree(&pdev->dev, gsw);
return ret;
}
static int an8855_remove(struct platform_device *pdev)
{
struct gsw_an8855 *gsw = platform_get_drvdata(pdev);
if (gsw->irq >= 0)
cancel_work_sync(&gsw->irq_worker);
if (gsw->reset_pin >= 0)
devm_gpio_free(&pdev->dev, gsw->reset_pin);
#ifdef CONFIG_SWCONFIG
an8855_swconfig_destroy(gsw);
#endif
an8855_proc_device_exit();
an8855_gsw_nl_exit();
an8855_remove_gsw(gsw);
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct of_device_id an8855_ids[] = {
{.compatible = "airoha,an8855"},
{},
};
MODULE_DEVICE_TABLE(of, an8855_ids);
static struct platform_driver an8855_driver = {
.probe = an8855_probe,
.remove = an8855_remove,
.driver = {
.name = "an8855",
.of_match_table = an8855_ids,
},
};
static int __init an8855_init(void)
{
int ret;
INIT_LIST_HEAD(&an8855_devs);
ret = platform_driver_register(&an8855_driver);
return ret;
}
module_init(an8855_init);
static void __exit an8855_exit(void)
{
platform_driver_unregister(&an8855_driver);
}
module_exit(an8855_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Min Yao <min.yao@airoha.com>");
MODULE_VERSION(ARHT_AN8855_SWCFG_DRIVER_VER);
MODULE_DESCRIPTION("Driver for Airoha AN8855 Gigabit Switch");

View File

@ -0,0 +1,382 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <net/genetlink.h>
#include "an8855.h"
#include "an8855_nl.h"
struct an8855_nl_cmd_item {
enum an8855_cmd cmd;
bool require_dev;
int (*process)(struct genl_info *info, struct gsw_an8855 *gsw);
u32 nr_required_attrs;
const enum an8855_attr *required_attrs;
};
static int an8855_nl_response(struct sk_buff *skb, struct genl_info *info);
static const struct nla_policy an8855_nl_cmd_policy[] = {
[AN8855_ATTR_TYPE_MESG] = { .type = NLA_STRING },
[AN8855_ATTR_TYPE_PHY] = { .type = NLA_S32 },
[AN8855_ATTR_TYPE_REG] = { .type = NLA_U32 },
[AN8855_ATTR_TYPE_VAL] = { .type = NLA_U32 },
[AN8855_ATTR_TYPE_DEV_NAME] = { .type = NLA_S32 },
[AN8855_ATTR_TYPE_DEV_ID] = { .type = NLA_S32 },
[AN8855_ATTR_TYPE_DEVAD] = { .type = NLA_S32 },
};
static const struct genl_ops an8855_nl_ops[] = {
{
.cmd = AN8855_CMD_REQUEST,
.doit = an8855_nl_response,
// .policy = an8855_nl_cmd_policy,
.flags = GENL_ADMIN_PERM,
}, {
.cmd = AN8855_CMD_READ,
.doit = an8855_nl_response,
// .policy = an8855_nl_cmd_policy,
.flags = GENL_ADMIN_PERM,
}, {
.cmd = AN8855_CMD_WRITE,
.doit = an8855_nl_response,
// .policy = an8855_nl_cmd_policy,
.flags = GENL_ADMIN_PERM,
},
};
static struct genl_family an8855_nl_family = {
.name = AN8855_GENL_NAME,
.version = AN8855_GENL_VERSION,
.maxattr = AN8855_NR_ATTR_TYPE,
.ops = an8855_nl_ops,
.n_ops = ARRAY_SIZE(an8855_nl_ops),
.policy = an8855_nl_cmd_policy,
};
static int an8855_nl_list_devs(char *buff, int size)
{
struct gsw_an8855 *gsw;
int len, total = 0;
char buf[80];
memset(buff, 0, size);
an8855_lock_gsw();
list_for_each_entry(gsw, &an8855_devs, list) {
len = snprintf(buf, sizeof(buf),
"id: %d, model: %s, node: %s\n",
gsw->id, gsw->name, gsw->dev->of_node->name);
if (len == strlen(buf)) {
if (size - total > 0)
strncat(buff, buf, size - total);
total += len;
}
}
an8855_put_gsw();
return total;
}
static int an8855_nl_prepare_reply(struct genl_info *info, u8 cmd,
struct sk_buff **skbp)
{
struct sk_buff *msg;
void *reply;
if (!info)
return -EINVAL;
msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (!msg)
return -ENOMEM;
/* Construct send-back message header */
reply = genlmsg_put(msg, info->snd_portid, info->snd_seq,
&an8855_nl_family, 0, cmd);
if (!reply) {
nlmsg_free(msg);
return -EINVAL;
}
*skbp = msg;
return 0;
}
static int an8855_nl_send_reply(struct sk_buff *skb, struct genl_info *info)
{
struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb));
void *reply = genlmsg_data(genlhdr);
/* Finalize a generic netlink message (update message header) */
genlmsg_end(skb, reply);
/* reply to a request */
return genlmsg_reply(skb, info);
}
static s32 an8855_nl_get_s32(struct genl_info *info, enum an8855_attr attr,
s32 defval)
{
struct nlattr *na;
na = info->attrs[attr];
if (na)
return nla_get_s32(na);
return defval;
}
static int an8855_nl_get_u32(struct genl_info *info, enum an8855_attr attr,
u32 *val)
{
struct nlattr *na;
na = info->attrs[attr];
if (na) {
*val = nla_get_u32(na);
return 0;
}
return -1;
}
static struct gsw_an8855 *an8855_nl_parse_find_gsw(struct genl_info *info)
{
struct gsw_an8855 *gsw;
struct nlattr *na;
int gsw_id;
na = info->attrs[AN8855_ATTR_TYPE_DEV_ID];
if (na) {
gsw_id = nla_get_s32(na);
if (gsw_id >= 0)
gsw = an8855_get_gsw(gsw_id);
else
gsw = an8855_get_first_gsw();
} else {
gsw = an8855_get_first_gsw();
}
return gsw;
}
static int an8855_nl_get_swdevs(struct genl_info *info, struct gsw_an8855 *gsw)
{
struct sk_buff *rep_skb = NULL;
char dev_info[512];
int ret;
ret = an8855_nl_list_devs(dev_info, sizeof(dev_info) - 1);
if (!ret) {
pr_info("No switch registered\n");
return -EINVAL;
}
ret = an8855_nl_prepare_reply(info, AN8855_CMD_REPLY, &rep_skb);
if (ret < 0)
goto err;
ret = nla_put_string(rep_skb, AN8855_ATTR_TYPE_MESG, dev_info);
if (ret < 0)
goto err;
return an8855_nl_send_reply(rep_skb, info);
err:
if (rep_skb)
nlmsg_free(rep_skb);
return ret;
}
static int an8855_nl_reply_read(struct genl_info *info, struct gsw_an8855 *gsw)
{
struct sk_buff *rep_skb = NULL;
s32 phy, devad;
u32 reg = 0;
int value = 0;
int ret = 0;
phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1);
devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1);
if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_REG, &reg))
goto err;
ret = an8855_nl_prepare_reply(info, AN8855_CMD_READ, &rep_skb);
if (ret < 0)
goto err;
if (phy >= 0) {
if (devad < 0)
value = gsw->mii_read(gsw, phy, reg);
else
value = gsw->mmd_read(gsw, phy, devad, reg);
} else {
value = an8855_reg_read(gsw, reg);
}
ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_REG, reg);
if (ret < 0)
goto err;
ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_VAL, value);
if (ret < 0)
goto err;
return an8855_nl_send_reply(rep_skb, info);
err:
if (rep_skb)
nlmsg_free(rep_skb);
return ret;
}
static int an8855_nl_reply_write(struct genl_info *info, struct gsw_an8855 *gsw)
{
struct sk_buff *rep_skb = NULL;
s32 phy, devad;
u32 value = 0, reg = 0;
int ret = 0;
phy = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_PHY, -1);
devad = an8855_nl_get_s32(info, AN8855_ATTR_TYPE_DEVAD, -1);
if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_REG, &reg))
goto err;
if (an8855_nl_get_u32(info, AN8855_ATTR_TYPE_VAL, &value))
goto err;
ret = an8855_nl_prepare_reply(info, AN8855_CMD_WRITE, &rep_skb);
if (ret < 0)
goto err;
if (phy >= 0) {
if (devad < 0)
gsw->mii_write(gsw, phy, reg, value);
else
gsw->mmd_write(gsw, phy, devad, reg, value);
} else {
an8855_reg_write(gsw, reg, value);
}
ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_REG, reg);
if (ret < 0)
goto err;
ret = nla_put_u32(rep_skb, AN8855_ATTR_TYPE_VAL, value);
if (ret < 0)
goto err;
return an8855_nl_send_reply(rep_skb, info);
err:
if (rep_skb)
nlmsg_free(rep_skb);
return ret;
}
static const enum an8855_attr an8855_nl_cmd_read_attrs[] = {
AN8855_ATTR_TYPE_REG
};
static const enum an8855_attr an8855_nl_cmd_write_attrs[] = {
AN8855_ATTR_TYPE_REG,
AN8855_ATTR_TYPE_VAL
};
static const struct an8855_nl_cmd_item an8855_nl_cmds[] = {
{
.cmd = AN8855_CMD_REQUEST,
.require_dev = false,
.process = an8855_nl_get_swdevs
}, {
.cmd = AN8855_CMD_READ,
.require_dev = true,
.process = an8855_nl_reply_read,
.required_attrs = an8855_nl_cmd_read_attrs,
.nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_read_attrs),
}, {
.cmd = AN8855_CMD_WRITE,
.require_dev = true,
.process = an8855_nl_reply_write,
.required_attrs = an8855_nl_cmd_write_attrs,
.nr_required_attrs = ARRAY_SIZE(an8855_nl_cmd_write_attrs),
}
};
static int an8855_nl_response(struct sk_buff *skb, struct genl_info *info)
{
struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
const struct an8855_nl_cmd_item *cmditem = NULL;
struct gsw_an8855 *gsw = NULL;
u32 sat_req_attrs = 0;
int i, ret;
for (i = 0; i < ARRAY_SIZE(an8855_nl_cmds); i++) {
if (hdr->cmd == an8855_nl_cmds[i].cmd) {
cmditem = &an8855_nl_cmds[i];
break;
}
}
if (!cmditem) {
pr_info("an8855-nl: unknown cmd %u\n", hdr->cmd);
return -EINVAL;
}
for (i = 0; i < cmditem->nr_required_attrs; i++) {
if (info->attrs[cmditem->required_attrs[i]])
sat_req_attrs++;
}
if (sat_req_attrs != cmditem->nr_required_attrs) {
pr_info("an8855-nl: missing required attr(s) for cmd %u\n",
hdr->cmd);
return -EINVAL;
}
if (cmditem->require_dev) {
gsw = an8855_nl_parse_find_gsw(info);
if (!gsw) {
pr_info("an8855-nl: failed to find switch dev\n");
return -EINVAL;
}
}
ret = cmditem->process(info, gsw);
an8855_put_gsw();
return ret;
}
int an8855_gsw_nl_init(void)
{
int ret;
ret = genl_register_family(&an8855_nl_family);
if (ret) {
pr_info("an8855-nl: genl_register_family_with_ops failed\n");
return ret;
}
return 0;
}
void an8855_gsw_nl_exit(void)
{
genl_unregister_family(&an8855_nl_family);
}

View File

@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#ifndef _AN8855_NL_H_
#define _AN8855_NL_H_
#define AN8855_GENL_NAME "an8855"
#define AN8855_GENL_VERSION 0x1
enum an8855_cmd {
AN8855_CMD_UNSPEC = 0,
AN8855_CMD_REQUEST,
AN8855_CMD_REPLY,
AN8855_CMD_READ,
AN8855_CMD_WRITE,
__AN8855_CMD_MAX,
};
enum an8855_attr {
AN8855_ATTR_TYPE_UNSPEC = 0,
AN8855_ATTR_TYPE_MESG,
AN8855_ATTR_TYPE_PHY,
AN8855_ATTR_TYPE_DEVAD,
AN8855_ATTR_TYPE_REG,
AN8855_ATTR_TYPE_VAL,
AN8855_ATTR_TYPE_DEV_NAME,
AN8855_ATTR_TYPE_DEV_ID,
__AN8855_ATTR_TYPE_MAX,
};
#define AN8855_NR_ATTR_TYPE (__AN8855_ATTR_TYPE_MAX - 1)
#ifdef __KERNEL__
int an8855_gsw_nl_init(void);
void an8855_gsw_nl_exit(void);
#endif /* __KERNEL__ */
#endif /* _AN8855_NL_H_ */

View File

@ -0,0 +1,197 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#ifndef _AN8855_REGS_H_
#define _AN8855_REGS_H_
#include <linux/bitops.h>
#define BITS(m, n) (~(BIT(m) - 1) & ((BIT(n) - 1) | BIT(n)))
/* Values of Egress TAG Control */
#define ETAG_CTRL_UNTAG 0
#define ETAG_CTRL_TAG 2
#define ETAG_CTRL_SWAP 1
#define ETAG_CTRL_STACK 3
#define VTCR 0x10200600
#define VAWD0 0x10200604
#define VAWD1 0x10200608
#define VARD0 0x10200618
#define VARD1 0x1020061C
/* Fields of VTCR */
#define VTCR_BUSY BIT(31)
#define VTCR_FUNC_S 12
#define VTCR_FUNC_M 0xf000
#define VTCR_VID_S 0
#define VTCR_VID_M 0xfff
/* Values of VTCR_FUNC */
#define VTCR_READ_VLAN_ENTRY 0
#define VTCR_WRITE_VLAN_ENTRY 1
/* VLAN entry fields */
#define IVL_MAC BIT(5)
#define EG_CON BIT(11)
#define VTAG_EN BIT(10)
#define PORT_MEM_S 26
#define PORT_MEM_M 0xfc000000
#define VENTRY_VALID BIT(0)
#define ETAG_M 0x3fff000
#define PORT_ETAG_S(p) (((p) * 2) + 12)
#define PORT_ETAG_M 0x03
#define PORT_CTRL_BASE 0x10208000
#define PORT_CTRL_PORT_OFFSET 0x200
#define PORT_CTRL_REG(p, r) (PORT_CTRL_BASE + \
(p) * PORT_CTRL_PORT_OFFSET + (r))
#define PCR(p) PORT_CTRL_REG(p, 0x04)
#define PVC(p) PORT_CTRL_REG(p, 0x10)
#define PORTMATRIX(p) PORT_CTRL_REG(p, 0x44)
#define PVID(p) PORT_CTRL_REG(p, 0x48)
#define GRP_PORT_VID_M 0xfff
/* Values of PORT_VLAN */
#define PORT_MATRIX_MODE 0
#define FALLBACK_MODE 1
#define CHECK_MODE 2
#define SECURITY_MODE 3
/* Fields of PVC */
#define STAG_VPID_S 16
#define STAG_VPID_M 0xffff0000
#define VLAN_ATTR_S 6
#define VLAN_ATTR_M 0xc0
#define PVC_STAG_REPLACE BIT(11)
#define PVC_PORT_STAG BIT(5)
/* Values of VLAN_ATTR */
#define VA_USER_PORT 0
#define VA_STACK_PORT 1
#define VA_TRANSLATION_PORT 2
#define VA_TRANSPARENT_PORT 3
#define PORT_MATRIX_M ((1 << AN8855_NUM_PORTS) - 1)
#define PORT_MAC_CTRL_BASE 0x10210000
#define PORT_MAC_CTRL_PORT_OFFSET 0x200
#define PORT_MAC_CTRL_REG(p, r) (PORT_MAC_CTRL_BASE + \
(p) * PORT_MAC_CTRL_PORT_OFFSET + (r))
#define PMCR(p) PORT_MAC_CTRL_REG(p, 0x00)
#define PMSR(p) PORT_MAC_CTRL_REG(p, 0x10)
#define GMACCR (PORT_MAC_CTRL_BASE + 0x3e00)
#define MAX_RX_JUMBO_S 4
#define MAX_RX_JUMBO_M 0xf0
#define MAX_RX_PKT_LEN_S 0
#define MAX_RX_PKT_LEN_M 0x3
/* Values of MAX_RX_PKT_LEN */
#define RX_PKT_LEN_1518 0
#define RX_PKT_LEN_1536 1
#define RX_PKT_LEN_1522 2
#define RX_PKT_LEN_MAX_JUMBO 3
/* Fields of PMSR */
#define EEE1G_STS BIT(7)
#define EEE100_STS BIT(6)
#define RX_FC_STS BIT(5)
#define TX_FC_STS BIT(4)
#define MAC_SPD_STS_S 28
#define MAC_SPD_STS_M 0x70000000
#define MAC_DPX_STS BIT(25)
#define MAC_LNK_STS BIT(24)
/* Values of MAC_SPD_STS */
#define MAC_SPD_10 0
#define MAC_SPD_100 1
#define MAC_SPD_1000 2
#define MAC_SPD_2500 3
#define MIB_COUNTER_BASE 0x10214000
#define MIB_COUNTER_PORT_OFFSET 0x200
#define MIB_COUNTER_REG(p, r) (MIB_COUNTER_BASE + \
(p) * MIB_COUNTER_PORT_OFFSET + (r))
#define STATS_TDPC 0x00
#define STATS_TCRC 0x04
#define STATS_TUPC 0x08
#define STATS_TMPC 0x0C
#define STATS_TBPC 0x10
#define STATS_TCEC 0x14
#define STATS_TSCEC 0x18
#define STATS_TMCEC 0x1C
#define STATS_TDEC 0x20
#define STATS_TLCEC 0x24
#define STATS_TXCEC 0x28
#define STATS_TPPC 0x2C
#define STATS_TL64PC 0x30
#define STATS_TL65PC 0x34
#define STATS_TL128PC 0x38
#define STATS_TL256PC 0x3C
#define STATS_TL512PC 0x40
#define STATS_TL1024PC 0x44
#define STATS_TL1519PC 0x48
#define STATS_TOC 0x4C
#define STATS_TODPC 0x54
#define STATS_TOC2 0x58
#define STATS_RDPC 0x80
#define STATS_RFPC 0x84
#define STATS_RUPC 0x88
#define STATS_RMPC 0x8C
#define STATS_RBPC 0x90
#define STATS_RAEPC 0x94
#define STATS_RCEPC 0x98
#define STATS_RUSPC 0x9C
#define STATS_RFEPC 0xA0
#define STATS_ROSPC 0xA4
#define STATS_RJEPC 0xA8
#define STATS_RPPC 0xAC
#define STATS_RL64PC 0xB0
#define STATS_RL65PC 0xB4
#define STATS_RL128PC 0xB8
#define STATS_RL256PC 0xBC
#define STATS_RL512PC 0xC0
#define STATS_RL1024PC 0xC4
#define STATS_RL1519PC 0xC8
#define STATS_ROC 0xCC
#define STATS_RDPC_CTRL 0xD4
#define STATS_RDPC_ING 0xD8
#define STATS_RDPC_ARL 0xDC
#define STATS_RDPC_FC 0xE0
#define STATS_RDPC_WRED 0xE4
#define STATS_RDPC_MIR 0xE8
#define STATS_ROC2 0xEC
#define STATS_RSFSPC 0xF4
#define STATS_RSFTPC 0xF8
#define STATS_RXCDPC 0xFC
#define RG_CLK_CPU_ICG 0x10005034
#define MCU_ENABLE BIT(3)
#define RG_TIMER_CTL 0x1000a100
#define WDOG_ENABLE BIT(25)
#define SYS_CTRL 0x100050C0
#define SW_SYS_RST BIT(31)
#define INT_MASK 0x100050F0
#define INT_SYS_BIT BIT(15)
#define SYS_INT_EN 0x1021C010
#define SYS_INT_STS 0x1021C014
#define PHY_LC_INT(p) BIT(p)
#define CKGCR 0x10213E1C
#define CKG_LNKDN_GLB_STOP 0x01
#define CKG_LNKDN_PORT_STOP 0x02
#define PKG_SEL 0x10000094
#define PAG_SEL_AN8855H 0x2
#endif /* _AN8855_REGS_H_ */

View File

@ -0,0 +1,528 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#include <linux/if.h>
#include <linux/list.h>
#include <linux/if_ether.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/netlink.h>
#include <linux/bitops.h>
#include <net/genetlink.h>
#include <linux/delay.h>
#include <linux/phy.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/lockdep.h>
#include <linux/workqueue.h>
#include <linux/of_device.h>
#include "an8855.h"
#include "an8855_swconfig.h"
#include "an8855_regs.h"
#define AN8855_PORT_MIB_TXB_ID 19 /* TxByte */
#define AN8855_PORT_MIB_RXB_ID 40 /* RxByte */
#define MIB_DESC(_s, _o, _n) \
{ \
.size = (_s), \
.offset = (_o), \
.name = (_n), \
}
struct an8855_mib_desc {
unsigned int size;
unsigned int offset;
const char *name;
};
static const struct an8855_mib_desc an8855_mibs[] = {
MIB_DESC(1, STATS_TDPC, "TxDrop"),
MIB_DESC(1, STATS_TCRC, "TxCRC"),
MIB_DESC(1, STATS_TUPC, "TxUni"),
MIB_DESC(1, STATS_TMPC, "TxMulti"),
MIB_DESC(1, STATS_TBPC, "TxBroad"),
MIB_DESC(1, STATS_TCEC, "TxCollision"),
MIB_DESC(1, STATS_TSCEC, "TxSingleCol"),
MIB_DESC(1, STATS_TMCEC, "TxMultiCol"),
MIB_DESC(1, STATS_TDEC, "TxDefer"),
MIB_DESC(1, STATS_TLCEC, "TxLateCol"),
MIB_DESC(1, STATS_TXCEC, "TxExcCol"),
MIB_DESC(1, STATS_TPPC, "TxPause"),
MIB_DESC(1, STATS_TL64PC, "Tx64Byte"),
MIB_DESC(1, STATS_TL65PC, "Tx65Byte"),
MIB_DESC(1, STATS_TL128PC, "Tx128Byte"),
MIB_DESC(1, STATS_TL256PC, "Tx256Byte"),
MIB_DESC(1, STATS_TL512PC, "Tx512Byte"),
MIB_DESC(1, STATS_TL1024PC, "Tx1024Byte"),
MIB_DESC(1, STATS_TL1519PC, "Tx1519Byte"),
MIB_DESC(2, STATS_TOC, "TxByte"),
MIB_DESC(1, STATS_TODPC, "TxOverSize"),
MIB_DESC(2, STATS_TOC2, "SecondaryTxByte"),
MIB_DESC(1, STATS_RDPC, "RxDrop"),
MIB_DESC(1, STATS_RFPC, "RxFiltered"),
MIB_DESC(1, STATS_RUPC, "RxUni"),
MIB_DESC(1, STATS_RMPC, "RxMulti"),
MIB_DESC(1, STATS_RBPC, "RxBroad"),
MIB_DESC(1, STATS_RAEPC, "RxAlignErr"),
MIB_DESC(1, STATS_RCEPC, "RxCRC"),
MIB_DESC(1, STATS_RUSPC, "RxUnderSize"),
MIB_DESC(1, STATS_RFEPC, "RxFragment"),
MIB_DESC(1, STATS_ROSPC, "RxOverSize"),
MIB_DESC(1, STATS_RJEPC, "RxJabber"),
MIB_DESC(1, STATS_RPPC, "RxPause"),
MIB_DESC(1, STATS_RL64PC, "Rx64Byte"),
MIB_DESC(1, STATS_RL65PC, "Rx65Byte"),
MIB_DESC(1, STATS_RL128PC, "Rx128Byte"),
MIB_DESC(1, STATS_RL256PC, "Rx256Byte"),
MIB_DESC(1, STATS_RL512PC, "Rx512Byte"),
MIB_DESC(1, STATS_RL1024PC, "Rx1024Byte"),
MIB_DESC(2, STATS_ROC, "RxByte"),
MIB_DESC(1, STATS_RDPC_CTRL, "RxCtrlDrop"),
MIB_DESC(1, STATS_RDPC_ING, "RxIngDrop"),
MIB_DESC(1, STATS_RDPC_ARL, "RxARLDrop"),
MIB_DESC(1, STATS_RDPC_FC, "RxFCDrop"),
MIB_DESC(1, STATS_RDPC_WRED, "RxWREDDrop"),
MIB_DESC(1, STATS_RDPC_MIR, "RxMIRDrop"),
MIB_DESC(2, STATS_ROC2, "SecondaryRxByte"),
MIB_DESC(1, STATS_RSFSPC, "RxsFlowSampling"),
MIB_DESC(1, STATS_RSFTPC, "RxsFlowTotal"),
MIB_DESC(1, STATS_RXCDPC, "RxPortDrop"),
};
enum {
/* Global attributes. */
AN8855_ATTR_ENABLE_VLAN,
};
static int an8855_get_vlan_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
val->value.i = gsw->global_vlan_enable;
return 0;
}
static int an8855_set_vlan_enable(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
gsw->global_vlan_enable = val->value.i != 0;
return 0;
}
static int an8855_get_port_pvid(struct switch_dev *dev, int port, int *val)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
if (port < 0 || port >= AN8855_NUM_PORTS)
return -EINVAL;
*val = an8855_reg_read(gsw, PVID(port));
*val &= GRP_PORT_VID_M;
return 0;
}
static int an8855_set_port_pvid(struct switch_dev *dev, int port, int pvid)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
if (port < 0 || port >= AN8855_NUM_PORTS)
return -EINVAL;
if (pvid < AN8855_MIN_VID || pvid > AN8855_MAX_VID)
return -EINVAL;
gsw->port_entries[port].pvid = pvid;
return 0;
}
static int an8855_get_vlan_ports(struct switch_dev *dev, struct switch_val *val)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
u32 member;
u32 etags;
int i;
val->len = 0;
if (val->port_vlan >= AN8855_NUM_VLANS)
return -EINVAL;
an8855_vlan_ctrl(gsw, VTCR_READ_VLAN_ENTRY, val->port_vlan);
member = an8855_reg_read(gsw, VARD0);
member &= PORT_MEM_M;
member >>= PORT_MEM_S;
member |= ((an8855_reg_read(gsw, VARD1) & 0x1) << 6);
etags = an8855_reg_read(gsw, VARD0) & ETAG_M;
for (i = 0; i < AN8855_NUM_PORTS; i++) {
struct switch_port *p;
int etag;
if (!(member & BIT(i)))
continue;
p = &val->value.ports[val->len++];
p->id = i;
etag = (etags >> PORT_ETAG_S(i)) & PORT_ETAG_M;
if (etag == ETAG_CTRL_TAG)
p->flags |= BIT(SWITCH_PORT_FLAG_TAGGED);
else if (etag != ETAG_CTRL_UNTAG)
dev_info(gsw->dev,
"vlan egress tag control neither untag nor tag.\n");
}
return 0;
}
static int an8855_set_vlan_ports(struct switch_dev *dev, struct switch_val *val)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
u8 member = 0;
u8 etags = 0;
int i;
if (val->port_vlan >= AN8855_NUM_VLANS ||
val->len > AN8855_NUM_PORTS)
return -EINVAL;
for (i = 0; i < val->len; i++) {
struct switch_port *p = &val->value.ports[i];
if (p->id >= AN8855_NUM_PORTS)
return -EINVAL;
member |= BIT(p->id);
if (p->flags & BIT(SWITCH_PORT_FLAG_TAGGED))
etags |= BIT(p->id);
}
gsw->vlan_entries[val->port_vlan].member = member;
gsw->vlan_entries[val->port_vlan].etags = etags;
return 0;
}
static int an8855_set_vid(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
int vlan;
u16 vid;
vlan = val->port_vlan;
vid = (u16)val->value.i;
if (vlan < 0 || vlan >= AN8855_NUM_VLANS)
return -EINVAL;
if (vid > AN8855_MAX_VID)
return -EINVAL;
gsw->vlan_entries[vlan].vid = vid;
return 0;
}
static int an8855_get_vid(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
val->value.i = val->port_vlan;
return 0;
}
static int an8855_get_port_link(struct switch_dev *dev, int port,
struct switch_port_link *link)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
u32 speed, pmsr;
if (port < 0 || port >= AN8855_NUM_PORTS)
return -EINVAL;
pmsr = an8855_reg_read(gsw, PMSR(port));
link->link = pmsr & MAC_LNK_STS;
link->duplex = pmsr & MAC_DPX_STS;
speed = (pmsr & MAC_SPD_STS_M) >> MAC_SPD_STS_S;
switch (speed) {
case MAC_SPD_10:
link->speed = SWITCH_PORT_SPEED_10;
break;
case MAC_SPD_100:
link->speed = SWITCH_PORT_SPEED_100;
break;
case MAC_SPD_1000:
link->speed = SWITCH_PORT_SPEED_1000;
break;
case MAC_SPD_2500:
link->speed = SWITCH_PORT_SPEED_2500;
break;
}
return 0;
}
static int an8855_set_port_link(struct switch_dev *dev, int port,
struct switch_port_link *link)
{
#ifndef MODULE
if (port >= AN8855_NUM_PHYS)
return -EINVAL;
return switch_generic_set_link(dev, port, link);
#else
return -ENOTSUPP;
#endif
}
static u64 get_mib_counter(struct gsw_an8855 *gsw, int i, int port)
{
unsigned int offset;
u64 lo = 0, hi = 0, hi2 = 0;
if (i >= 0) {
offset = an8855_mibs[i].offset;
if (an8855_mibs[i].size == 1)
return an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset));
do {
hi = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset + 4));
lo = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset));
hi2 = an8855_reg_read(gsw, MIB_COUNTER_REG(port, offset + 4));
} while (hi2 != hi);
}
return (hi << 32) | lo;
}
static int an8855_get_port_mib(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
static char buf[4096];
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
int i, len = 0;
if (val->port_vlan >= AN8855_NUM_PORTS)
return -EINVAL;
len += snprintf(buf + len, sizeof(buf) - len,
"Port %d MIB counters\n", val->port_vlan);
for (i = 0; i < ARRAY_SIZE(an8855_mibs); ++i) {
u64 counter;
len += snprintf(buf + len, sizeof(buf) - len,
"%-11s: ", an8855_mibs[i].name);
counter = get_mib_counter(gsw, i, val->port_vlan);
len += snprintf(buf + len, sizeof(buf) - len, "%llu\n",
counter);
}
val->value.s = buf;
val->len = len;
return 0;
}
static int an8855_get_port_stats(struct switch_dev *dev, int port,
struct switch_port_stats *stats)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
if (port < 0 || port >= AN8855_NUM_PORTS)
return -EINVAL;
stats->tx_bytes = get_mib_counter(gsw, AN8855_PORT_MIB_TXB_ID, port);
stats->rx_bytes = get_mib_counter(gsw, AN8855_PORT_MIB_RXB_ID, port);
return 0;
}
static void an8855_port_isolation(struct gsw_an8855 *gsw)
{
int i;
for (i = 0; i < AN8855_NUM_PORTS; i++)
an8855_reg_write(gsw, PORTMATRIX(i),
BIT(gsw->cpu_port));
an8855_reg_write(gsw, PORTMATRIX(gsw->cpu_port), PORT_MATRIX_M);
for (i = 0; i < AN8855_NUM_PORTS; i++) {
u32 pvc_mode = 0x9100 << STAG_VPID_S;
if (gsw->port5_cfg.stag_on && i == 5)
pvc_mode |= PVC_PORT_STAG | PVC_STAG_REPLACE;
else
pvc_mode |= (VA_TRANSPARENT_PORT << VLAN_ATTR_S);
an8855_reg_write(gsw, PVC(i), pvc_mode);
}
}
static int an8855_apply_config(struct switch_dev *dev)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
if (!gsw->global_vlan_enable) {
an8855_port_isolation(gsw);
return 0;
}
an8855_apply_vlan_config(gsw);
return 0;
}
static int an8855_reset_switch(struct switch_dev *dev)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
int i;
memset(gsw->port_entries, 0, sizeof(gsw->port_entries));
memset(gsw->vlan_entries, 0, sizeof(gsw->vlan_entries));
/* set default vid of each vlan to the same number of vlan, so the vid
* won't need be set explicitly.
*/
for (i = 0; i < AN8855_NUM_VLANS; i++)
gsw->vlan_entries[i].vid = i;
return 0;
}
static int an8855_phy_read16(struct switch_dev *dev, int addr, u8 reg,
u16 *value)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
*value = gsw->mii_read(gsw, addr, reg);
return 0;
}
static int an8855_phy_write16(struct switch_dev *dev, int addr, u8 reg,
u16 value)
{
struct gsw_an8855 *gsw = container_of(dev, struct gsw_an8855, swdev);
gsw->mii_write(gsw, addr, reg, value);
return 0;
}
static const struct switch_attr an8855_global[] = {
{
.type = SWITCH_TYPE_INT,
.name = "enable_vlan",
.description = "VLAN mode (1:enabled)",
.max = 1,
.id = AN8855_ATTR_ENABLE_VLAN,
.get = an8855_get_vlan_enable,
.set = an8855_set_vlan_enable,
}
};
static const struct switch_attr an8855_port[] = {
{
.type = SWITCH_TYPE_STRING,
.name = "mib",
.description = "Get MIB counters for port",
.get = an8855_get_port_mib,
.set = NULL,
},
};
static const struct switch_attr an8855_vlan[] = {
{
.type = SWITCH_TYPE_INT,
.name = "vid",
.description = "VLAN ID (0-4094)",
.set = an8855_set_vid,
.get = an8855_get_vid,
.max = 4094,
},
};
static const struct switch_dev_ops an8855_swdev_ops = {
.attr_global = {
.attr = an8855_global,
.n_attr = ARRAY_SIZE(an8855_global),
},
.attr_port = {
.attr = an8855_port,
.n_attr = ARRAY_SIZE(an8855_port),
},
.attr_vlan = {
.attr = an8855_vlan,
.n_attr = ARRAY_SIZE(an8855_vlan),
},
.get_vlan_ports = an8855_get_vlan_ports,
.set_vlan_ports = an8855_set_vlan_ports,
.get_port_pvid = an8855_get_port_pvid,
.set_port_pvid = an8855_set_port_pvid,
.get_port_link = an8855_get_port_link,
.set_port_link = an8855_set_port_link,
.get_port_stats = an8855_get_port_stats,
.apply_config = an8855_apply_config,
.reset_switch = an8855_reset_switch,
.phy_read16 = an8855_phy_read16,
.phy_write16 = an8855_phy_write16,
};
int an8855_swconfig_init(struct gsw_an8855 *gsw)
{
struct device_node *np = gsw->dev->of_node;
struct switch_dev *swdev;
int ret;
if (of_property_read_u32(np, "airoha,cpuport", &gsw->cpu_port))
gsw->cpu_port = AN8855_DFL_CPU_PORT;
swdev = &gsw->swdev;
swdev->name = gsw->name;
swdev->alias = gsw->name;
swdev->cpu_port = gsw->cpu_port;
swdev->ports = AN8855_NUM_PORTS;
swdev->vlans = AN8855_NUM_VLANS;
swdev->ops = &an8855_swdev_ops;
ret = register_switch(swdev, NULL);
if (ret) {
dev_notice(gsw->dev, "Failed to register switch %s\n",
swdev->name);
return ret;
}
an8855_apply_config(swdev);
return 0;
}
void an8855_swconfig_destroy(struct gsw_an8855 *gsw)
{
unregister_switch(&gsw->swdev);
}

View File

@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#ifndef _AN8855_SWCONFIG_H_
#define _AN8855_SWCONFIG_H_
#ifdef CONFIG_SWCONFIG
#include <linux/switch.h>
#include "an8855.h"
int an8855_swconfig_init(struct gsw_an8855 *gsw);
void an8855_swconfig_destroy(struct gsw_an8855 *gsw);
#else
static inline int an8855_swconfig_init(struct gsw_an8855 *gsw)
{
an8855_apply_vlan_config(gsw);
return 0;
}
static inline void an8855_swconfig_destroy(struct gsw_an8855 *gsw)
{
}
#endif
#endif /* _AN8855_SWCONFIG_H_ */

View File

@ -0,0 +1,200 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 Airoha Inc.
*/
#include "an8855.h"
#include "an8855_regs.h"
struct an8855_mapping an8855_def_mapping[] = {
{
.name = "llllw",
.pvids = { 1, 1, 1, 1, 2, 1 },
.members = { 0, 0x2f, 0x30 },
.etags = { 0, 0, 0 },
.vids = { 0, 1, 2 },
}, {
.name = "wllll",
.pvids = { 2, 1, 1, 1, 1, 1 },
.members = { 0, 0x3e, 0x21 },
.etags = { 0, 0, 0 },
.vids = { 0, 1, 2 },
}, {
.name = "lwlll",
.pvids = { 1, 2, 1, 1, 1, 1 },
.members = { 0, 0x3d, 0x22 },
.etags = { 0, 0, 0 },
.vids = { 0, 1, 2 },
}, {
.name = "lllll",
.pvids = { 1, 1, 1, 1, 1, 1 },
.members = { 0, 0x3f },
.etags = { 0, 0 },
.vids = { 0, 1 },
},
};
void an8855_vlan_ctrl(struct gsw_an8855 *gsw, u32 cmd, u32 val)
{
int i;
an8855_reg_write(gsw, VTCR,
VTCR_BUSY | ((cmd << VTCR_FUNC_S) & VTCR_FUNC_M) |
(val & VTCR_VID_M));
for (i = 0; i < 300; i++) {
u32 val = an8855_reg_read(gsw, VTCR);
if ((val & VTCR_BUSY) == 0)
break;
usleep_range(1000, 1100);
}
if (i == 300)
dev_info(gsw->dev, "vtcr timeout\n");
}
static void an8855_write_vlan_entry(struct gsw_an8855 *gsw, int vlan, u16 vid,
u8 ports, u8 etags)
{
int port;
u32 val;
/* vlan port membership */
if (ports) {
val = IVL_MAC | VTAG_EN | VENTRY_VALID
| ((ports << PORT_MEM_S) & PORT_MEM_M);
/* egress mode */
for (port = 0; port < AN8855_NUM_PORTS; port++) {
if (etags & BIT(port))
val |= (ETAG_CTRL_TAG << PORT_ETAG_S(port));
else
val |= (ETAG_CTRL_UNTAG << PORT_ETAG_S(port));
}
an8855_reg_write(gsw, VAWD0, val);
} else {
an8855_reg_write(gsw, VAWD0, 0);
}
if (ports & 0x40)
an8855_reg_write(gsw, VAWD1, 0x1);
else
an8855_reg_write(gsw, VAWD1, 0x0);
/* write to vlan table */
an8855_vlan_ctrl(gsw, VTCR_WRITE_VLAN_ENTRY, vid);
}
void an8855_apply_vlan_config(struct gsw_an8855 *gsw)
{
int i, j;
u8 tag_ports;
u8 untag_ports;
u32 val;
/* set all ports as security mode */
for (i = 0; i < AN8855_NUM_PORTS; i++) {
val = an8855_reg_read(gsw, PCR(i));
an8855_reg_write(gsw, PCR(i), val | SECURITY_MODE);
an8855_reg_write(gsw, PORTMATRIX(i), PORT_MATRIX_M);
}
/* check if a port is used in tag/untag vlan egress mode */
tag_ports = 0;
untag_ports = 0;
for (i = 0; i < AN8855_NUM_VLANS; i++) {
u8 member = gsw->vlan_entries[i].member;
u8 etags = gsw->vlan_entries[i].etags;
if (!member)
continue;
for (j = 0; j < AN8855_NUM_PORTS; j++) {
if (!(member & BIT(j)))
continue;
if (etags & BIT(j))
tag_ports |= 1u << j;
else
untag_ports |= 1u << j;
}
}
/* set all untag-only ports as transparent and the rest as user port */
for (i = 0; i < AN8855_NUM_PORTS; i++) {
u32 pvc_mode = 0x9100 << STAG_VPID_S;
if (untag_ports & BIT(i) && !(tag_ports & BIT(i)))
pvc_mode = (0x9100 << STAG_VPID_S) |
(VA_TRANSPARENT_PORT << VLAN_ATTR_S);
if (gsw->port5_cfg.stag_on && i == 5)
pvc_mode = (u32)((0x9100 << STAG_VPID_S) | PVC_PORT_STAG
| PVC_STAG_REPLACE);
an8855_reg_write(gsw, PVC(i), pvc_mode);
}
/* first clear the switch vlan table */
for (i = 0; i < AN8855_NUM_VLANS; i++)
an8855_write_vlan_entry(gsw, i, i, 0, 0);
/* now program only vlans with members to avoid
* clobbering remapped entries in later iterations
*/
for (i = 0; i < AN8855_NUM_VLANS; i++) {
u16 vid = gsw->vlan_entries[i].vid;
u8 member = gsw->vlan_entries[i].member;
u8 etags = gsw->vlan_entries[i].etags;
if (member)
an8855_write_vlan_entry(gsw, i, vid, member, etags);
}
/* Port Default PVID */
for (i = 0; i < AN8855_NUM_PORTS; i++) {
int vlan = gsw->port_entries[i].pvid;
u16 pvid = 0;
u32 val;
if ((vlan >= 0) && (vlan < AN8855_NUM_VLANS)
&& (gsw->vlan_entries[vlan].member))
pvid = gsw->vlan_entries[vlan].vid;
val = an8855_reg_read(gsw, PVID(i));
val &= ~GRP_PORT_VID_M;
val |= pvid;
an8855_reg_write(gsw, PVID(i), val);
}
}
struct an8855_mapping *an8855_find_mapping(struct device_node *np)
{
const char *map;
int i;
if (of_property_read_string(np, "airoha,portmap", &map))
return NULL;
for (i = 0; i < ARRAY_SIZE(an8855_def_mapping); i++)
if (!strcmp(map, an8855_def_mapping[i].name))
return &an8855_def_mapping[i];
return NULL;
}
void an8855_apply_mapping(struct gsw_an8855 *gsw, struct an8855_mapping *map)
{
int i = 0;
for (i = 0; i < AN8855_NUM_PORTS; i++)
gsw->port_entries[i].pvid = map->pvids[i];
for (i = 0; i < AN8855_NUM_VLANS; i++) {
gsw->vlan_entries[i].member = map->members[i];
gsw->vlan_entries[i].etags = map->etags[i];
gsw->vlan_entries[i].vid = map->vids[i];
}
}

View File

@ -0,0 +1,40 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2023 Airoha Inc.
*/
#ifndef _AN8855_VLAN_H_
#define _AN8855_VLAN_H_
#define AN8855_NUM_PORTS 6
#define AN8855_NUM_VLANS 4095
#define AN8855_MAX_VID 4095
#define AN8855_MIN_VID 0
struct gsw_an8855;
struct an8855_port_entry {
u16 pvid;
};
struct an8855_vlan_entry {
u16 vid;
u8 member;
u8 etags;
};
struct an8855_mapping {
char *name;
u16 pvids[AN8855_NUM_PORTS];
u8 members[AN8855_NUM_VLANS];
u8 etags[AN8855_NUM_VLANS];
u16 vids[AN8855_NUM_VLANS];
};
extern struct an8855_mapping an8855_defaults[];
void an8855_vlan_ctrl(struct gsw_an8855 *gsw, u32 cmd, u32 val);
void an8855_apply_vlan_config(struct gsw_an8855 *gsw);
struct an8855_mapping *an8855_find_mapping(struct device_node *np);
void an8855_apply_mapping(struct gsw_an8855 *gsw, struct an8855_mapping *map);
#endif /* _AN8855_VLAN_H_ */

View File

@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Airoha DSA Tag support
* Copyright (C) 2023 Min Yao <min.yao@airoha.com>
*/
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include "dsa_priv.h"
#define AIR_HDR_LEN 4
#define AIR_HDR_XMIT_UNTAGGED 0
#define AIR_HDR_XMIT_TAGGED_TPID_8100 1
#define AIR_HDR_XMIT_TAGGED_TPID_88A8 2
#define AIR_HDR_RECV_SOURCE_PORT_MASK GENMASK(2, 0)
#define AIR_HDR_XMIT_DP_BIT_MASK GENMASK(5, 0)
static struct sk_buff *air_tag_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct dsa_port *dp = dsa_slave_to_port(dev);
u8 xmit_tpid;
u8 *air_tag;
unsigned char *dest = eth_hdr(skb)->h_dest;
/* Build the special tag after the MAC Source Address. If VLAN header
* is present, it's required that VLAN header and special tag is
* being combined. Only in this way we can allow the switch can parse
* the both special and VLAN tag at the same time and then look up VLAN
* table with VID.
*/
switch (skb->protocol) {
case htons(ETH_P_8021Q):
xmit_tpid = AIR_HDR_XMIT_TAGGED_TPID_8100;
break;
case htons(ETH_P_8021AD):
xmit_tpid = AIR_HDR_XMIT_TAGGED_TPID_88A8;
break;
default:
if (skb_cow_head(skb, AIR_HDR_LEN) < 0)
return NULL;
xmit_tpid = AIR_HDR_XMIT_UNTAGGED;
skb_push(skb, AIR_HDR_LEN);
memmove(skb->data, skb->data + AIR_HDR_LEN, 2 * ETH_ALEN);
}
air_tag = skb->data + 2 * ETH_ALEN;
/* Mark tag attribute on special tag insertion to notify hardware
* whether that's a combined special tag with 802.1Q header.
*/
air_tag[0] = xmit_tpid;
air_tag[1] = (1 << dp->index) & AIR_HDR_XMIT_DP_BIT_MASK;
/* Tag control information is kept for 802.1Q */
if (xmit_tpid == AIR_HDR_XMIT_UNTAGGED) {
air_tag[2] = 0;
air_tag[3] = 0;
}
return skb;
}
static struct sk_buff *air_tag_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt)
{
int port;
__be16 *phdr, hdr;
unsigned char *dest = eth_hdr(skb)->h_dest;
bool is_multicast_skb = is_multicast_ether_addr(dest) &&
!is_broadcast_ether_addr(dest);
if (dev->features & NETIF_F_HW_VLAN_CTAG_RX) {
hdr = ntohs(skb->vlan_proto);
skb->vlan_proto = 0;
skb->vlan_tci = 0;
} else {
if (unlikely(!pskb_may_pull(skb, AIR_HDR_LEN)))
return NULL;
/* The AIR header is added by the switch between src addr
* and ethertype at this point, skb->data points to 2 bytes
* after src addr so header should be 2 bytes right before.
*/
phdr = (__be16 *)(skb->data - 2);
hdr = ntohs(*phdr);
/* Remove AIR tag and recalculate checksum. */
skb_pull_rcsum(skb, AIR_HDR_LEN);
memmove(skb->data - ETH_HLEN,
skb->data - ETH_HLEN - AIR_HDR_LEN,
2 * ETH_ALEN);
}
/* Get source port information */
port = (hdr & AIR_HDR_RECV_SOURCE_PORT_MASK);
skb->dev = dsa_master_find_slave(dev, 0, port);
if (!skb->dev)
return NULL;
/* Only unicast or broadcast frames are offloaded */
if (likely(!is_multicast_skb))
skb->offload_fwd_mark = 1;
return skb;
}
static int air_tag_flow_dissect(const struct sk_buff *skb, __be16 *proto,
int *offset)
{
*offset = 4;
*proto = ((__be16 *)skb->data)[1];
return 0;
}
static const struct dsa_device_ops air_netdev_ops = {
.name = "air",
.proto = DSA_TAG_PROTO_ARHT,
.xmit = air_tag_xmit,
.rcv = air_tag_rcv,
.flow_dissect = air_tag_flow_dissect,
.overhead = AIR_HDR_LEN,
};
MODULE_LICENSE("GPL");
MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_AIR);
module_dsa_tag_driver(air_netdev_ops);

View File

@ -3,6 +3,7 @@ CONFIG_AHCI_MTK=y
# CONFIG_AIROHA_EN8801SC_PHY is not set
# CONFIG_AIROHA_EN8801S_PHY is not set
# CONFIG_AIR_EN8811H_PHY is not set
CONFIG_AN8855_GSW=y
CONFIG_ARCH_CLOCKSOURCE_DATA=y
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
CONFIG_ARCH_KEEP_MEMBLOCK=y
@ -335,7 +336,9 @@ CONFIG_NETFILTER_XT_NAT=m
CONFIG_NETFILTER_XT_TARGET_MASQUERADE=m
CONFIG_NET_DEVLINK=y
CONFIG_NET_DSA=y
# CONFIG_NET_DSA_AN8855 is not set
CONFIG_NET_DSA_MT7530=y
# CONFIG_NET_DSA_TAG_AIROHA is not set
CONFIG_NET_DSA_TAG_MTK=y
CONFIG_NET_FLOW_LIMIT=y
# CONFIG_NET_MEDIATEK_HNAT is not set

View File

@ -3,6 +3,7 @@ CONFIG_AHCI_MTK=y
# CONFIG_AIROHA_EN8801SC_PHY is not set
# CONFIG_AIROHA_EN8801S_PHY is not set
# CONFIG_AIR_EN8811H_PHY is not set
# CONFIG_AN8855_GSW is not set
CONFIG_ARCH_CLOCKSOURCE_DATA=y
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
CONFIG_ARCH_KEEP_MEMBLOCK=y
@ -335,7 +336,9 @@ CONFIG_NETFILTER_XT_NAT=m
CONFIG_NETFILTER_XT_TARGET_MASQUERADE=m
CONFIG_NET_DEVLINK=y
CONFIG_NET_DSA=y
# CONFIG_NET_DSA_AN8855 is not set
CONFIG_NET_DSA_MT7530=y
# CONFIG_NET_DSA_TAG_AIROHA is not set
CONFIG_NET_DSA_TAG_MTK=y
CONFIG_NET_FLOW_LIMIT=y
# CONFIG_NET_MEDIATEK_HNAT is not set

View File

@ -0,0 +1,25 @@
Index: linux-5.4.238/drivers/net/dsa/Kconfig
===================================================================
--- linux-5.4.238.orig/drivers/net/dsa/Kconfig 2023-12-26 15:31:49.427259000 +0800
+++ linux-5.4.238/drivers/net/dsa/Kconfig 2023-12-26 15:46:52.655226000 +0800
@@ -48,6 +48,8 @@
This enables support for the Marvell 88E6060 ethernet switch
chip.
+source "drivers/net/dsa/airoha/an8855/Kconfig"
+
source "drivers/net/dsa/microchip/Kconfig"
source "drivers/net/dsa/mv88e6xxx/Kconfig"
Index: linux-5.4.238/drivers/net/dsa/Makefile
===================================================================
--- linux-5.4.238.orig/drivers/net/dsa/Makefile 2023-12-26 15:32:08.081306000 +0800
+++ linux-5.4.238/drivers/net/dsa/Makefile 2023-12-26 15:47:59.858217000 +0800
@@ -18,6 +18,7 @@
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX) += vitesse-vsc73xx-core.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_PLATFORM) += vitesse-vsc73xx-platform.o
obj-$(CONFIG_NET_DSA_VITESSE_VSC73XX_SPI) += vitesse-vsc73xx-spi.o
+obj-y += airoha/an8855/
obj-y += b53/
obj-y += microchip/
obj-y += mv88e6xxx/

View File

@ -0,0 +1,24 @@
Index: linux-5.4.238/drivers/net/phy/Kconfig
===================================================================
--- linux-5.4.238.orig/drivers/net/phy/Kconfig 2023-12-19 12:20:34.714805000 +0800
+++ linux-5.4.238/drivers/net/phy/Kconfig 2023-12-25 10:11:11.328554000 +0800
@@ -337,6 +337,8 @@
source "drivers/net/phy/mtk/mt753x/Kconfig"
+source "drivers/net/phy/airoha/an8855/Kconfig"
+
comment "MII PHY device drivers"
config SFP
Index: linux-5.4.238/drivers/net/phy/Makefile
===================================================================
--- linux-5.4.238.orig/drivers/net/phy/Makefile 2023-12-19 12:20:34.718809000 +0800
+++ linux-5.4.238/drivers/net/phy/Makefile 2023-12-25 10:13:11.891535000 +0800
@@ -121,5 +121,6 @@
obj-$(CONFIG_VITESSE_PHY) += vitesse.o
obj-$(CONFIG_XILINX_GMII2RGMII) += xilinx_gmii2rgmii.o
obj-$(CONFIG_MT753X_GSW) += mtk/mt753x/
+obj-$(CONFIG_AN8855_GSW) += airoha/an8855/
obj-$(CONFIG_RTL8367S_GSW) += rtk/

View File

@ -0,0 +1,46 @@
Index: linux-5.4.238/net/dsa/Makefile
===================================================================
--- linux-5.4.238.orig/net/dsa/Makefile 2023-12-09 09:43:04.335694000 +0800
+++ linux-5.4.238/net/dsa/Makefile 2023-12-09 10:24:27.672514000 +0800
@@ -16,3 +16,4 @@
obj-$(CONFIG_NET_DSA_TAG_QCA) += tag_qca.o
obj-$(CONFIG_NET_DSA_TAG_SJA1105) += tag_sja1105.o
obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
+obj-$(CONFIG_NET_DSA_TAG_AIROHA) += tag_arht.o
Index: linux-5.4.238/net/dsa/Kconfig
===================================================================
--- linux-5.4.238.orig/net/dsa/Kconfig 2023-12-09 09:43:04.332694000 +0800
+++ linux-5.4.238/net/dsa/Kconfig 2023-12-09 10:26:13.596504000 +0800
@@ -74,6 +74,12 @@
Say Y or M if you want to enable support for tagging frames for
Mediatek switches.
+config NET_DSA_TAG_AIROHA
+ tristate "Tag driver for Airoha switches"
+ help
+ Say Y or M if you want to enable support for tagging frames for
+ Airoha switches.
+
config NET_DSA_TAG_KSZ
tristate "Tag driver for Microchip 8795/9477/9893 families of switches"
help
Index: linux-5.4.238/include/net/dsa.h
===================================================================
--- linux-5.4.238.orig/include/net/dsa.h 2023-12-09 09:43:17.940694000 +0800
+++ linux-5.4.238/include/net/dsa.h 2023-12-09 10:30:06.432504000 +0800
@@ -43,6 +43,7 @@
#define DSA_TAG_PROTO_SJA1105_VALUE 13
#define DSA_TAG_PROTO_KSZ8795_VALUE 14
#define DSA_TAG_PROTO_RTL4_A_VALUE 17
+#define DSA_TAG_PROTO_ARHT_VALUE 28
enum dsa_tag_protocol {
DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE,
@@ -61,6 +62,7 @@
DSA_TAG_PROTO_SJA1105 = DSA_TAG_PROTO_SJA1105_VALUE,
DSA_TAG_PROTO_KSZ8795 = DSA_TAG_PROTO_KSZ8795_VALUE,
DSA_TAG_PROTO_RTL4_A = DSA_TAG_PROTO_RTL4_A_VALUE,
+ DSA_TAG_PROTO_ARHT = DSA_TAG_PROTO_ARHT_VALUE,
};
struct packet_type;