mirror of
https://github.com/hanwckf/immortalwrt-mt798x.git
synced 2025-01-05 00:53:32 +08:00
mediatek: add support for airoha an8855 switch (from mtk-openwrt-feeds)
This commit is contained in:
parent
606983a97b
commit
9bce0f9947
@ -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.
|
@ -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
@ -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 */
|
@ -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, ®))
|
||||
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, ®))
|
||||
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);
|
||||
}
|
@ -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_ */
|
@ -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;
|
||||
}
|
@ -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_ */
|
@ -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
|
||||
|
@ -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.
|
@ -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
@ -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_ */
|
@ -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);
|
||||
}
|
@ -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");
|
@ -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, ®))
|
||||
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, ®))
|
||||
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);
|
||||
}
|
@ -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_ */
|
@ -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_ */
|
@ -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);
|
||||
}
|
@ -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_ */
|
@ -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];
|
||||
}
|
||||
}
|
@ -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_ */
|
133
target/linux/mediatek/files-5.4/net/dsa/tag_arht.c
Normal file
133
target/linux/mediatek/files-5.4/net/dsa/tag_arht.c
Normal 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);
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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/
|
@ -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/
|
||||
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user