Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+246
View File
@@ -0,0 +1,246 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# TI device configuration
#
config NET_VENDOR_TI
bool "Texas Instruments (TI) devices"
default y
depends on PCI || EISA || ARCH_DAVINCI || ARCH_OMAP2PLUS || ARCH_KEYSTONE || ARCH_K3
help
If you have a network (Ethernet) card belonging to this class, say Y.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all
the questions about TI devices. If you say Y, you will be asked for
your specific card in the following questions.
if NET_VENDOR_TI
config TI_DAVINCI_EMAC
tristate "TI DaVinci EMAC Support"
depends on ARM && ( ARCH_DAVINCI || ARCH_OMAP3 ) || COMPILE_TEST
select TI_DAVINCI_MDIO
select PHYLIB
select GENERIC_ALLOCATOR
help
This driver supports TI's DaVinci Ethernet .
To compile this driver as a module, choose M here: the module
will be called davinci_emac_driver. This is recommended.
config TI_DAVINCI_MDIO
tristate "TI DaVinci MDIO Support"
depends on ARCH_DAVINCI || ARCH_OMAP2PLUS || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
select PHYLIB
select MDIO_BITBANG
help
This driver supports TI's DaVinci MDIO module.
To compile this driver as a module, choose M here: the module
will be called davinci_mdio. This is recommended.
config TI_CPSW_PHY_SEL
bool "TI CPSW Phy mode Selection (DEPRECATED)"
default n
help
This driver supports configuring of the phy mode connected to
the CPSW. DEPRECATED: use PHY_TI_GMII_SEL.
config TI_CPSW
tristate "TI CPSW Switch Support"
depends on ARCH_DAVINCI || ARCH_OMAP2PLUS || COMPILE_TEST
depends on TI_CPTS || !TI_CPTS
select TI_DAVINCI_MDIO
select MFD_SYSCON
select PAGE_POOL
select REGMAP
imply PHY_TI_GMII_SEL
help
This driver supports TI's CPSW Ethernet Switch.
To compile this driver as a module, choose M here: the module
will be called cpsw.
config TI_CPSW_SWITCHDEV
tristate "TI CPSW Switch Support with switchdev"
depends on ARCH_DAVINCI || ARCH_OMAP2PLUS || COMPILE_TEST
depends on NET_SWITCHDEV
depends on TI_CPTS || !TI_CPTS
select PAGE_POOL
select TI_DAVINCI_MDIO
select MFD_SYSCON
select REGMAP
select NET_DEVLINK
imply PHY_TI_GMII_SEL
help
This driver supports TI's CPSW Ethernet Switch.
To compile this driver as a module, choose M here: the module
will be called cpsw_new.
config TI_CPTS
tristate "TI Common Platform Time Sync (CPTS) Support"
depends on ARCH_OMAP2PLUS || ARCH_KEYSTONE || COMPILE_TEST
depends on COMMON_CLK
depends on PTP_1588_CLOCK
help
This driver supports the Common Platform Time Sync unit of
the CPSW Ethernet Switch and Keystone 2 1g/10g Switch Subsystem.
The unit can time stamp PTP UDP/IPv4 and Layer 2 packets, and the
driver offers a PTP Hardware Clock.
config TI_K3_CPPI_DESC_POOL
tristate
config TI_K3_AM65_CPSW_NUSS
tristate "TI K3 AM654x/J721E CPSW Ethernet driver"
depends on ARCH_K3 && OF && TI_K3_UDMA_GLUE_LAYER
select NET_DEVLINK
select TI_DAVINCI_MDIO
select PHYLINK
select PAGE_POOL
select TI_K3_CPPI_DESC_POOL
imply PHY_TI_GMII_SEL
depends on TI_K3_AM65_CPTS || !TI_K3_AM65_CPTS
help
This driver supports TI K3 AM654/J721E CPSW2G Ethernet SubSystem.
The two-port Gigabit Ethernet MAC (MCU_CPSW0) subsystem provides
Ethernet packet communication for the device: One Ethernet port
(port 1) with selectable RGMII and RMII interfaces and an internal
Communications Port Programming Interface (CPPI) port (port 0).
To compile this driver as a module, choose M here: the module
will be called ti-am65-cpsw-nuss.
config TI_K3_AM65_CPSW_SWITCHDEV
bool "TI K3 AM654x/J721E CPSW Switch mode support"
depends on TI_K3_AM65_CPSW_NUSS
depends on NET_SWITCHDEV
help
This enables switchdev support for TI K3 CPSWxG Ethernet
Switch. Enable this driver to support hardware switch support for AM65
CPSW NUSS driver.
config TI_K3_AM65_CPTS
tristate "TI K3 AM65x CPTS"
depends on ARCH_K3 && OF
depends on PTP_1588_CLOCK
help
Say y here to support the TI K3 AM65x CPTS with 1588 features such as
PTP hardware clock for each CPTS device and network packets
timestamping where applicable.
Depending on integration CPTS blocks enable compliance with
the IEEE 1588-2008 standard for a precision clock synchronization
protocol, Ethernet Enhanced Scheduled Traffic Operations (CPTS_ESTFn)
and PCIe Subsystem Precision Time Measurement (PTM).
config TI_AM65_CPSW_QOS
bool "Enable QoS offload features in AM65 CPSW"
depends on TI_K3_AM65_CPSW_NUSS && NET_SCH_TAPRIO && TI_K3_AM65_CPTS
help
This option enables QoS offload features in AM65 CPSW like
Time Aware Shaper (TAS) / Enhanced Scheduled Traffic (EST),
MQPRIO qdisc offload and Frame-Preemption MAC Merge / Interspersing
Express Traffic (IET).
The EST scheduler runs on CPTS and the TAS/EST schedule is
updated in the Fetch RAM memory of the CPSW.
config TI_KEYSTONE_NETCP
tristate "TI Keystone NETCP Core Support"
select TI_DAVINCI_MDIO
depends on OF
depends on KEYSTONE_NAVIGATOR_DMA && KEYSTONE_NAVIGATOR_QMSS
depends on TI_CPTS || !TI_CPTS
help
This driver supports TI's Keystone NETCP Core.
To compile this driver as a module, choose M here: the module
will be called keystone_netcp.
config TI_KEYSTONE_NETCP_ETHSS
depends on TI_KEYSTONE_NETCP
tristate "TI Keystone NETCP Ethernet subsystem Support"
help
To compile this driver as a module, choose M here: the module
will be called keystone_netcp_ethss.
config TLAN
tristate "TI ThunderLAN support"
depends on (PCI || EISA) && HAS_IOPORT
help
If you have a PCI Ethernet network card based on the ThunderLAN chip
which is supported by this driver, say Y here.
Devices currently supported by this driver are Compaq Netelligent,
Compaq NetFlex and Olicom cards. Please read the file
<file:Documentation/networking/device_drivers/ethernet/ti/tlan.rst>
for more details.
To compile this driver as a module, choose M here. The module
will be called tlan.
Please email feedback to <torben.mathiasen@compaq.com>.
config TI_ICSSG_PRUETH
tristate "TI Gigabit PRU Ethernet driver"
select PHYLIB
select TI_ICSS_IEP
select TI_K3_CPPI_DESC_POOL
depends on PRU_REMOTEPROC
depends on NET_SWITCHDEV
depends on ARCH_K3 && OF && TI_K3_UDMA_GLUE_LAYER
depends on PTP_1588_CLOCK_OPTIONAL
depends on HSR || !HSR
help
Support dual Gigabit Ethernet ports over the ICSSG PRU Subsystem.
This subsystem is available starting with the AM65 platform.
This driver requires firmware binaries which will run on the PRUs
to support the Ethernet operation. Currently, it supports Ethernet
with 1G and 100M link speed.
config TI_ICSSG_PRUETH_SR1
tristate "TI Gigabit PRU SR1.0 Ethernet driver"
select PHYLIB
select TI_ICSS_IEP
select TI_K3_CPPI_DESC_POOL
select PAGE_POOL
depends on PRU_REMOTEPROC
depends on NET_SWITCHDEV
depends on ARCH_K3 && OF && TI_K3_UDMA_GLUE_LAYER
depends on PTP_1588_CLOCK_OPTIONAL
help
Support dual Gigabit Ethernet ports over the ICSSG PRU Subsystem.
This subsystem is available on the AM65 SR1.0 platform.
This driver requires firmware binaries which will run on the PRUs
to support the Ethernet operation. Currently, it supports Ethernet
with 1G, 100M and 10M link speed.
config TI_ICSS_IEP
tristate "TI PRU ICSS IEP driver"
depends on PTP_1588_CLOCK_OPTIONAL
depends on TI_PRUSS
default TI_PRUSS
help
This driver enables support for the PRU-ICSS Industrial Ethernet
Peripheral within a PRU-ICSS subsystem present on various TI SoCs.
To compile this driver as a module, choose M here. The module
will be called icss_iep.
config TI_PRUETH
tristate "TI PRU Ethernet EMAC driver"
depends on PRU_REMOTEPROC
depends on NET_SWITCHDEV
select TI_ICSS_IEP
depends on PTP_1588_CLOCK_OPTIONAL
help
Some TI SoCs has Programmable Realtime Unit (PRU) cores which can
support Single or Dual Ethernet ports with the help of firmware code
running on PRU cores. This driver supports remoteproc based
communication to PRU firmware to expose Ethernet interface to Linux.
endif # NET_VENDOR_TI
@@ -0,0 +1,51 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the TI network device drivers.
#
obj-$(CONFIG_TI_PRUETH) += icssm-prueth.o
icssm-prueth-y := icssm/icssm_prueth.o icssm/icssm_prueth_switch.o icssm/icssm_switchdev.o
ti-cpsw-common-y += cpsw-common.o davinci_cpdma.o
ti-cpsw-priv-y += cpsw_priv.o cpsw_ethtool.o
ti-cpsw-ale-y += cpsw_ale.o
ti-cpsw-sl-y += cpsw_sl.o
obj-$(CONFIG_TLAN) += tlan.o
obj-$(CONFIG_TI_DAVINCI_EMAC) += davinci_emac.o ti-cpsw-common.o
obj-$(CONFIG_TI_DAVINCI_MDIO) += davinci_mdio.o
obj-$(CONFIG_TI_CPSW_PHY_SEL) += cpsw-phy-sel.o
obj-$(CONFIG_TI_CPTS) += cpts.o
obj-$(CONFIG_TI_CPSW) += ti_cpsw.o ti-cpsw-common.o ti-cpsw-priv.o ti-cpsw-ale.o ti-cpsw-sl.o
ti_cpsw-y := cpsw.o
obj-$(CONFIG_TI_CPSW_SWITCHDEV) += ti_cpsw_new.o ti-cpsw-common.o ti-cpsw-priv.o ti-cpsw-ale.o ti-cpsw-sl.o
ti_cpsw_new-y := cpsw_switchdev.o cpsw_new.o
obj-$(CONFIG_TI_KEYSTONE_NETCP) += keystone_netcp.o ti-cpsw-ale.o
keystone_netcp-y := netcp_core.o
obj-$(CONFIG_TI_KEYSTONE_NETCP_ETHSS) += keystone_netcp_ethss.o ti-cpsw-ale.o
keystone_netcp_ethss-y := netcp_ethss.o netcp_sgmii.o netcp_xgbepcsr.o
obj-$(CONFIG_TI_K3_CPPI_DESC_POOL) += k3-cppi-desc-pool.o
obj-$(CONFIG_TI_K3_AM65_CPSW_NUSS) += ti-am65-cpsw-nuss.o ti-cpsw-sl.o ti-cpsw-ale.o
ti-am65-cpsw-nuss-y := am65-cpsw-nuss.o am65-cpsw-ethtool.o
ti-am65-cpsw-nuss-$(CONFIG_TI_AM65_CPSW_QOS) += am65-cpsw-qos.o
ti-am65-cpsw-nuss-$(CONFIG_TI_K3_AM65_CPSW_SWITCHDEV) += am65-cpsw-switchdev.o
obj-$(CONFIG_TI_K3_AM65_CPTS) += am65-cpts.o
obj-$(CONFIG_TI_ICSSG_PRUETH) += icssg-prueth.o icssg.o
icssg-prueth-y := icssg/icssg_prueth.o icssg/icssg_switchdev.o
obj-$(CONFIG_TI_ICSSG_PRUETH_SR1) += icssg-prueth-sr1.o icssg.o
icssg-prueth-sr1-y := icssg/icssg_prueth_sr1.o
icssg-y := icssg/icssg_common.o \
icssg/icssg_classifier.o \
icssg/icssg_queues.o \
icssg/icssg_config.o \
icssg/icssg_mii_cfg.o \
icssg/icssg_stats.o \
icssg/icssg_ethtool.o
obj-$(CONFIG_TI_ICSS_IEP) += icssg/icss_iep.o
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,231 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
*
*/
#ifndef AM65_CPSW_NUSS_H_
#define AM65_CPSW_NUSS_H_
#include <linux/if_ether.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/phylink.h>
#include <linux/platform_device.h>
#include <linux/soc/ti/k3-ringacc.h>
#include <net/devlink.h>
#include <net/xdp.h>
#include "am65-cpsw-qos.h"
struct am65_cpts;
#define HOST_PORT_NUM 0
#define AM65_CPSW_MAX_QUEUES 8 /* both TX & RX */
#define AM65_CPSW_PORT_VLAN_REG_OFFSET 0x014
struct am65_cpsw_slave_data {
bool mac_only;
struct cpsw_sl *mac_sl;
struct device_node *port_np;
phy_interface_t phy_if;
struct phy *ifphy;
struct phy *serdes_phy;
bool rx_pause;
bool tx_pause;
u8 mac_addr[ETH_ALEN];
int port_vlan;
struct phylink *phylink;
struct phylink_config phylink_config;
};
struct am65_cpsw_port {
struct am65_cpsw_common *common;
struct net_device *ndev;
const char *name;
u32 port_id;
void __iomem *port_base;
void __iomem *sgmii_base;
void __iomem *stat_base;
void __iomem *fetch_ram_base;
bool disabled;
struct am65_cpsw_slave_data slave;
bool tx_ts_enabled;
enum hwtstamp_rx_filters rx_ts_filter;
struct am65_cpsw_qos qos;
struct devlink_port devlink_port;
struct bpf_prog *xdp_prog;
struct xdp_rxq_info xdp_rxq[AM65_CPSW_MAX_QUEUES];
/* Only for suspend resume context */
u32 vid_context;
};
enum am65_cpsw_tx_buf_type {
AM65_CPSW_TX_BUF_TYPE_SKB,
AM65_CPSW_TX_BUF_TYPE_XDP_TX,
AM65_CPSW_TX_BUF_TYPE_XDP_NDO,
};
struct am65_cpsw_host {
struct am65_cpsw_common *common;
void __iomem *port_base;
void __iomem *stat_base;
/* Only for suspend resume context */
u32 vid_context;
};
struct am65_cpsw_tx_chn {
struct device *dma_dev;
struct napi_struct napi_tx;
struct am65_cpsw_common *common;
struct k3_cppi_desc_pool *desc_pool;
struct k3_udma_glue_tx_channel *tx_chn;
spinlock_t lock; /* protect TX rings in multi-port mode */
struct hrtimer tx_hrtimer;
unsigned long tx_pace_timeout;
int irq;
u32 id;
u32 descs_num;
unsigned char dsize_log2;
char tx_chn_name[128];
u32 rate_mbps;
};
struct am65_cpsw_rx_flow {
u32 id;
struct napi_struct napi_rx;
struct am65_cpsw_common *common;
int irq;
bool irq_disabled;
struct hrtimer rx_hrtimer;
unsigned long rx_pace_timeout;
struct page_pool *page_pool;
char name[32];
};
struct am65_cpsw_tx_swdata {
struct net_device *ndev;
union {
struct sk_buff *skb;
struct xdp_frame *xdpf;
};
};
struct am65_cpsw_swdata {
u32 flow_id;
struct page *page;
};
struct am65_cpsw_rx_chn {
struct device *dev;
struct device *dma_dev;
struct k3_cppi_desc_pool *desc_pool;
struct k3_udma_glue_rx_channel *rx_chn;
u32 descs_num;
unsigned char dsize_log2;
struct am65_cpsw_rx_flow flows[AM65_CPSW_MAX_QUEUES];
};
#define AM65_CPSW_QUIRK_I2027_NO_TX_CSUM BIT(0)
#define AM64_CPSW_QUIRK_DMA_RX_TDOWN_IRQ BIT(1)
struct am65_cpsw_pdata {
u32 quirks;
u64 extra_modes;
enum k3_ring_mode fdqring_mode;
const char *ale_dev_id;
};
enum cpsw_devlink_param_id {
AM65_CPSW_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX,
AM65_CPSW_DL_PARAM_SWITCH_MODE,
};
struct am65_cpsw_devlink {
struct am65_cpsw_common *common;
};
struct am65_cpsw_common {
struct device *dev;
struct device *mdio_dev;
struct am65_cpsw_pdata pdata;
void __iomem *ss_base;
void __iomem *cpsw_base;
u32 port_num;
struct am65_cpsw_host host;
struct am65_cpsw_port *ports;
u32 disabled_ports_mask;
struct net_device *dma_ndev;
int usage_count; /* number of opened ports */
struct cpsw_ale *ale;
int tx_ch_num;
u32 tx_ch_rate_msk;
u32 rx_flow_id_base;
struct am65_cpsw_tx_chn tx_chns[AM65_CPSW_MAX_QUEUES];
struct completion tdown_complete;
atomic_t tdown_cnt;
int rx_ch_num_flows;
struct am65_cpsw_rx_chn rx_chns;
u32 nuss_ver;
u32 cpsw_ver;
unsigned long bus_freq;
bool pf_p0_rx_ptype_rrobin;
struct am65_cpts *cpts;
int est_enabled;
bool iet_enabled;
bool is_emac_mode;
u16 br_members;
int default_vlan;
struct devlink *devlink;
struct net_device *hw_bridge_dev;
struct notifier_block am65_cpsw_netdevice_nb;
unsigned char switch_id[MAX_PHYS_ITEM_ID_LEN];
/* only for suspend/resume context restore */
u32 *ale_context;
};
struct am65_cpsw_ndev_priv {
u32 msg_enable;
struct am65_cpsw_port *port;
bool offload_fwd_mark;
/* Serialize access to MAC Merge state between ethtool requests
* and link state updates
*/
struct mutex mm_lock;
};
#define am65_ndev_to_priv(ndev) \
((struct am65_cpsw_ndev_priv *)netdev_priv(ndev))
#define am65_ndev_to_port(ndev) (am65_ndev_to_priv(ndev)->port)
#define am65_ndev_to_common(ndev) (am65_ndev_to_port(ndev)->common)
#define am65_ndev_to_slave(ndev) (&am65_ndev_to_port(ndev)->slave)
#define am65_common_get_host(common) (&(common)->host)
#define am65_common_get_port(common, id) (&(common)->ports[(id) - 1])
#define am65_cpsw_napi_to_rx_flow(pnapi) \
container_of(pnapi, struct am65_cpsw_rx_flow, napi_rx)
#define am65_cpsw_napi_to_tx_chn(pnapi) \
container_of(pnapi, struct am65_cpsw_tx_chn, napi_tx)
#define AM65_CPSW_DRV_NAME "am65-cpsw-nuss"
#define AM65_CPSW_IS_CPSW2G(common) ((common)->port_num == 1)
extern const struct ethtool_ops am65_cpsw_ethtool_ops_slave;
void am65_cpsw_nuss_set_p0_ptype(struct am65_cpsw_common *common);
int am65_cpsw_nuss_update_tx_rx_chns(struct am65_cpsw_common *common,
int num_tx, int num_rx);
bool am65_cpsw_port_dev_check(const struct net_device *dev);
#endif /* AM65_CPSW_NUSS_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,227 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
*/
#ifndef AM65_CPSW_QOS_H_
#define AM65_CPSW_QOS_H_
#include <linux/netdevice.h>
#include <net/pkt_sched.h>
struct am65_cpsw_common;
struct am65_cpsw_port;
struct am65_cpsw_est {
int buf;
/* has to be the last one */
struct tc_taprio_qopt_offload taprio;
};
struct am65_cpsw_mqprio {
struct tc_mqprio_qopt_offload mqprio_hw;
u64 max_rate_total;
bool shaper_en;
};
struct am65_cpsw_iet {
u8 preemptible_tcs;
u32 original_max_blks;
int verify_time_ms;
};
struct am65_cpsw_ale_ratelimit {
unsigned long cookie;
u64 rate_packet_ps;
};
struct am65_cpsw_qos {
struct am65_cpsw_est *est_admin;
struct am65_cpsw_est *est_oper;
ktime_t link_down_time;
int link_speed;
struct am65_cpsw_mqprio mqprio;
struct am65_cpsw_iet iet;
struct am65_cpsw_ale_ratelimit ale_bc_ratelimit;
struct am65_cpsw_ale_ratelimit ale_mc_ratelimit;
};
#define AM65_CPSW_REG_CTL 0x004
#define AM65_CPSW_PN_REG_CTL 0x004
#define AM65_CPSW_PN_REG_FIFO_STATUS 0x050
#define AM65_CPSW_PN_REG_EST_CTL 0x060
#define AM65_CPSW_PN_REG_PRI_CIR(pri) (0x140 + 4 * (pri))
#define AM65_CPSW_P0_REG_PRI_EIR(pri) (0x160 + 4 * (pri))
#define AM65_CPSW_PN_REG_CTL 0x004
#define AM65_CPSW_PN_REG_TX_PRI_MAP 0x018
#define AM65_CPSW_PN_REG_RX_PRI_MAP 0x020
#define AM65_CPSW_PN_REG_FIFO_STATUS 0x050
#define AM65_CPSW_PN_REG_EST_CTL 0x060
#define AM65_CPSW_PN_REG_PRI_CIR(pri) (0x140 + 4 * (pri))
#define AM65_CPSW_PN_REG_PRI_EIR(pri) (0x160 + 4 * (pri))
/* AM65_CPSW_REG_CTL register fields */
#define AM65_CPSW_CTL_EST_EN BIT(18)
/* AM65_CPSW_PN_REG_CTL register fields */
#define AM65_CPSW_PN_CTL_EST_PORT_EN BIT(17)
/* AM65_CPSW_PN_REG_EST_CTL register fields */
#define AM65_CPSW_PN_EST_ONEBUF BIT(0)
#define AM65_CPSW_PN_EST_BUFSEL BIT(1)
#define AM65_CPSW_PN_EST_TS_EN BIT(2)
#define AM65_CPSW_PN_EST_TS_FIRST BIT(3)
#define AM65_CPSW_PN_EST_ONEPRI BIT(4)
#define AM65_CPSW_PN_EST_TS_PRI_MSK GENMASK(7, 5)
/* AM65_CPSW_PN_REG_FIFO_STATUS register fields */
#define AM65_CPSW_PN_FST_TX_PRI_ACTIVE_MSK GENMASK(7, 0)
#define AM65_CPSW_PN_FST_TX_E_MAC_ALLOW_MSK GENMASK(15, 8)
#define AM65_CPSW_PN_FST_EST_CNT_ERR BIT(16)
#define AM65_CPSW_PN_FST_EST_ADD_ERR BIT(17)
#define AM65_CPSW_PN_FST_EST_BUFACT BIT(18)
/* EST FETCH COMMAND RAM */
#define AM65_CPSW_FETCH_RAM_CMD_NUM 0x80
#define AM65_CPSW_FETCH_CNT_MSK GENMASK(21, 8)
#define AM65_CPSW_FETCH_CNT_MAX (AM65_CPSW_FETCH_CNT_MSK >> 8)
#define AM65_CPSW_FETCH_CNT_OFFSET 8
#define AM65_CPSW_FETCH_ALLOW_MSK GENMASK(7, 0)
#define AM65_CPSW_FETCH_ALLOW_MAX AM65_CPSW_FETCH_ALLOW_MSK
/* number of priority queues per port FIFO */
#define AM65_CPSW_PN_FIFO_PRIO_NUM 8
#if IS_ENABLED(CONFIG_TI_AM65_CPSW_QOS)
int am65_cpsw_qos_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
void *type_data);
void am65_cpsw_qos_link_up(struct net_device *ndev, int link_speed);
void am65_cpsw_qos_link_down(struct net_device *ndev);
int am65_cpsw_qos_ndo_tx_p0_set_maxrate(struct net_device *ndev, int queue, u32 rate_mbps);
void am65_cpsw_qos_tx_p0_rate_init(struct am65_cpsw_common *common);
void am65_cpsw_iet_commit_preemptible_tcs(struct am65_cpsw_port *port);
void am65_cpsw_iet_common_enable(struct am65_cpsw_common *common);
#else
static inline int am65_cpsw_qos_ndo_setup_tc(struct net_device *ndev,
enum tc_setup_type type,
void *type_data)
{
return -EOPNOTSUPP;
}
static inline void am65_cpsw_qos_link_up(struct net_device *ndev,
int link_speed)
{ }
static inline void am65_cpsw_qos_link_down(struct net_device *ndev)
{ }
static inline int am65_cpsw_qos_ndo_tx_p0_set_maxrate(struct net_device *ndev,
int queue,
u32 rate_mbps)
{
return 0;
}
static inline void am65_cpsw_qos_tx_p0_rate_init(struct am65_cpsw_common *common)
{ }
static inline void am65_cpsw_iet_commit_preemptible_tcs(struct am65_cpsw_port *port)
{ }
static inline void am65_cpsw_iet_common_enable(struct am65_cpsw_common *common)
{ }
#endif
#define AM65_CPSW_REG_CTL 0x004
#define AM65_CPSW_PN_REG_CTL 0x004
#define AM65_CPSW_PN_REG_MAX_BLKS 0x008
#define AM65_CPSW_PN_REG_TX_PRI_MAP 0x018
#define AM65_CPSW_PN_REG_RX_PRI_MAP 0x020
#define AM65_CPSW_PN_REG_IET_CTRL 0x040
#define AM65_CPSW_PN_REG_IET_STATUS 0x044
#define AM65_CPSW_PN_REG_IET_VERIFY 0x048
#define AM65_CPSW_PN_REG_FIFO_STATUS 0x050
#define AM65_CPSW_PN_REG_EST_CTL 0x060
#define AM65_CPSW_PN_REG_PRI_CIR(pri) (0x140 + 4 * (pri))
#define AM65_CPSW_PN_REG_PRI_EIR(pri) (0x160 + 4 * (pri))
/* AM65_CPSW_REG_CTL register fields */
#define AM65_CPSW_CTL_IET_EN BIT(17)
#define AM65_CPSW_CTL_EST_EN BIT(18)
/* AM65_CPSW_PN_REG_CTL register fields */
#define AM65_CPSW_PN_CTL_IET_PORT_EN BIT(16)
#define AM65_CPSW_PN_CTL_EST_PORT_EN BIT(17)
/* AM65_CPSW_PN_REG_EST_CTL register fields */
#define AM65_CPSW_PN_EST_ONEBUF BIT(0)
#define AM65_CPSW_PN_EST_BUFSEL BIT(1)
#define AM65_CPSW_PN_EST_TS_EN BIT(2)
#define AM65_CPSW_PN_EST_TS_FIRST BIT(3)
#define AM65_CPSW_PN_EST_ONEPRI BIT(4)
#define AM65_CPSW_PN_EST_TS_PRI_MSK GENMASK(7, 5)
/* AM65_CPSW_PN_REG_IET_CTRL register fields */
#define AM65_CPSW_PN_IET_MAC_PENABLE BIT(0)
#define AM65_CPSW_PN_IET_MAC_DISABLEVERIFY BIT(2)
#define AM65_CPSW_PN_IET_MAC_LINKFAIL BIT(3)
#define AM65_CPSW_PN_IET_MAC_MAC_ADDFRAGSIZE_MASK GENMASK(10, 8)
#define AM65_CPSW_PN_IET_MAC_MAC_ADDFRAGSIZE_OFFSET 8
#define AM65_CPSW_PN_IET_MAC_PREMPT_MASK GENMASK(23, 16)
#define AM65_CPSW_PN_IET_MAC_PREMPT_OFFSET 16
#define AM65_CPSW_PN_IET_MAC_SET_ADDFRAGSIZE(n) (((n) << AM65_CPSW_PN_IET_MAC_MAC_ADDFRAGSIZE_OFFSET) & \
AM65_CPSW_PN_IET_MAC_MAC_ADDFRAGSIZE_MASK)
#define AM65_CPSW_PN_IET_MAC_GET_ADDFRAGSIZE(n) (((n) & AM65_CPSW_PN_IET_MAC_MAC_ADDFRAGSIZE_MASK) >> \
AM65_CPSW_PN_IET_MAC_MAC_ADDFRAGSIZE_OFFSET)
#define AM65_CPSW_PN_IET_MAC_SET_PREEMPT(n) (((n) << AM65_CPSW_PN_IET_MAC_PREMPT_OFFSET) & \
AM65_CPSW_PN_IET_MAC_PREMPT_MASK)
#define AM65_CPSW_PN_IET_MAC_GET_PREEMPT(n) (((n) & AM65_CPSW_PN_IET_MAC_PREMPT_MASK) >> \
AM65_CPSW_PN_IET_MAC_PREMPT_OFFSET)
/* AM65_CPSW_PN_REG_IET_STATUS register fields */
#define AM65_CPSW_PN_MAC_STATUS GENMASK(3, 0)
#define AM65_CPSW_PN_MAC_VERIFIED BIT(0)
#define AM65_CPSW_PN_MAC_VERIFY_FAIL BIT(1)
#define AM65_CPSW_PN_MAC_RESPOND_ERR BIT(2)
#define AM65_CPSW_PN_MAC_VERIFY_ERR BIT(3)
/* AM65_CPSW_PN_REG_IET_VERIFY register fields */
#define AM65_CPSW_PN_MAC_VERIFY_CNT_MASK GENMASK(23, 0)
#define AM65_CPSW_PN_MAC_GET_VERIFY_CNT(n) ((n) & AM65_CPSW_PN_MAC_VERIFY_CNT_MASK)
/* 10 msec converted to NSEC */
#define AM65_CPSW_IET_VERIFY_CNT_MS (10)
#define AM65_CPSW_IET_VERIFY_CNT_NS (AM65_CPSW_IET_VERIFY_CNT_MS * \
NSEC_PER_MSEC)
/* AM65_CPSW_PN_REG_FIFO_STATUS register fields */
#define AM65_CPSW_PN_FST_TX_PRI_ACTIVE_MSK GENMASK(7, 0)
#define AM65_CPSW_PN_FST_TX_E_MAC_ALLOW_MSK GENMASK(15, 8)
#define AM65_CPSW_PN_FST_EST_CNT_ERR BIT(16)
#define AM65_CPSW_PN_FST_EST_ADD_ERR BIT(17)
#define AM65_CPSW_PN_FST_EST_BUFACT BIT(18)
/* EST FETCH COMMAND RAM */
#define AM65_CPSW_FETCH_RAM_CMD_NUM 0x80
#define AM65_CPSW_FETCH_CNT_MSK GENMASK(21, 8)
#define AM65_CPSW_FETCH_CNT_MAX (AM65_CPSW_FETCH_CNT_MSK >> 8)
#define AM65_CPSW_FETCH_CNT_OFFSET 8
#define AM65_CPSW_FETCH_ALLOW_MSK GENMASK(7, 0)
#define AM65_CPSW_FETCH_ALLOW_MAX AM65_CPSW_FETCH_ALLOW_MSK
/* AM65_CPSW_PN_REG_MAX_BLKS fields for IET and No IET cases */
/* 7 blocks for pn_rx_max_blks, 13 for pn_tx_max_blks*/
#define AM65_CPSW_PN_TX_RX_MAX_BLKS_IET 0xD07
/* Slave IET Stats. register offsets */
#define AM65_CPSW_STATN_IET_RX_ASSEMBLY_ERROR 0x140
#define AM65_CPSW_STATN_IET_RX_ASSEMBLY_OK 0x144
#define AM65_CPSW_STATN_IET_RX_SMD_ERROR 0x148
#define AM65_CPSW_STATN_IET_RX_FRAG 0x14c
#define AM65_CPSW_STATN_IET_TX_HOLD 0x150
#define AM65_CPSW_STATN_IET_TX_FRAG 0x154
/* number of priority queues per port FIFO */
#define AM65_CPSW_PN_FIFO_PRIO_NUM 8
#endif /* AM65_CPSW_QOS_H_ */
@@ -0,0 +1,534 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Texas Instruments K3 AM65 Ethernet Switchdev Driver
*
* Copyright (C) 2020 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/netdevice.h>
#include <linux/workqueue.h>
#include <net/switchdev.h>
#include "am65-cpsw-nuss.h"
#include "am65-cpsw-switchdev.h"
#include "cpsw_ale.h"
struct am65_cpsw_switchdev_event_work {
struct work_struct work;
struct switchdev_notifier_fdb_info fdb_info;
struct am65_cpsw_port *port;
unsigned long event;
};
static int am65_cpsw_port_stp_state_set(struct am65_cpsw_port *port, u8 state)
{
struct am65_cpsw_common *cpsw = port->common;
u8 cpsw_state;
int ret = 0;
switch (state) {
case BR_STATE_FORWARDING:
cpsw_state = ALE_PORT_STATE_FORWARD;
break;
case BR_STATE_LEARNING:
cpsw_state = ALE_PORT_STATE_LEARN;
break;
case BR_STATE_DISABLED:
cpsw_state = ALE_PORT_STATE_DISABLE;
break;
case BR_STATE_LISTENING:
case BR_STATE_BLOCKING:
cpsw_state = ALE_PORT_STATE_BLOCK;
break;
default:
return -EOPNOTSUPP;
}
ret = cpsw_ale_control_set(cpsw->ale, port->port_id,
ALE_PORT_STATE, cpsw_state);
netdev_dbg(port->ndev, "ale state: %u\n", cpsw_state);
return ret;
}
static int am65_cpsw_port_attr_br_flags_set(struct am65_cpsw_port *port,
struct net_device *orig_dev,
struct switchdev_brport_flags flags)
{
struct am65_cpsw_common *cpsw = port->common;
if (flags.mask & BR_MCAST_FLOOD) {
bool unreg_mcast_add = false;
if (flags.val & BR_MCAST_FLOOD)
unreg_mcast_add = true;
netdev_dbg(port->ndev, "BR_MCAST_FLOOD: %d port %u\n",
unreg_mcast_add, port->port_id);
cpsw_ale_set_unreg_mcast(cpsw->ale, BIT(port->port_id),
unreg_mcast_add);
}
return 0;
}
static int am65_cpsw_port_attr_br_flags_pre_set(struct net_device *netdev,
struct switchdev_brport_flags flags)
{
if (flags.mask & ~(BR_LEARNING | BR_MCAST_FLOOD))
return -EINVAL;
return 0;
}
static int am65_cpsw_port_attr_set(struct net_device *ndev, const void *ctx,
const struct switchdev_attr *attr,
struct netlink_ext_ack *extack)
{
struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
int ret;
netdev_dbg(ndev, "attr: id %u port: %u\n", attr->id, port->port_id);
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
ret = am65_cpsw_port_attr_br_flags_pre_set(ndev,
attr->u.brport_flags);
break;
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
ret = am65_cpsw_port_stp_state_set(port, attr->u.stp_state);
netdev_dbg(ndev, "stp state: %u\n", attr->u.stp_state);
break;
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
ret = am65_cpsw_port_attr_br_flags_set(port, attr->orig_dev,
attr->u.brport_flags);
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static u16 am65_cpsw_get_pvid(struct am65_cpsw_port *port)
{
struct am65_cpsw_common *cpsw = port->common;
struct am65_cpsw_host *host_p = am65_common_get_host(cpsw);
u32 pvid;
if (port->port_id)
pvid = readl(port->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
else
pvid = readl(host_p->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
pvid = pvid & 0xfff;
return pvid;
}
static void am65_cpsw_set_pvid(struct am65_cpsw_port *port, u16 vid, bool cfi, u32 cos)
{
struct am65_cpsw_common *cpsw = port->common;
struct am65_cpsw_host *host_p = am65_common_get_host(cpsw);
u32 pvid;
pvid = vid;
pvid |= cfi ? BIT(12) : 0;
pvid |= (cos & 0x7) << 13;
if (port->port_id)
writel(pvid, port->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
else
writel(pvid, host_p->port_base + AM65_CPSW_PORT_VLAN_REG_OFFSET);
}
static int am65_cpsw_port_vlan_add(struct am65_cpsw_port *port, bool untag, bool pvid,
u16 vid, struct net_device *orig_dev)
{
bool cpu_port = netif_is_bridge_master(orig_dev);
struct am65_cpsw_common *cpsw = port->common;
int unreg_mcast_mask = 0;
int reg_mcast_mask = 0;
int untag_mask = 0;
int port_mask;
int ret = 0;
u32 flags;
if (cpu_port) {
port_mask = BIT(HOST_PORT_NUM);
flags = orig_dev->flags;
unreg_mcast_mask = port_mask;
} else {
port_mask = BIT(port->port_id);
flags = port->ndev->flags;
}
if (flags & IFF_MULTICAST)
reg_mcast_mask = port_mask;
if (untag)
untag_mask = port_mask;
ret = cpsw_ale_vlan_add_modify(cpsw->ale, vid, port_mask, untag_mask,
reg_mcast_mask, unreg_mcast_mask);
if (ret) {
netdev_err(port->ndev, "Unable to add vlan\n");
return ret;
}
if (cpu_port)
cpsw_ale_add_ucast(cpsw->ale, port->slave.mac_addr,
HOST_PORT_NUM, ALE_VLAN | ALE_SECURE, vid);
if (!pvid)
return ret;
am65_cpsw_set_pvid(port, vid, 0, 0);
netdev_dbg(port->ndev, "VID add: %s: vid:%u ports:%X\n",
port->ndev->name, vid, port_mask);
return ret;
}
static int am65_cpsw_port_vlan_del(struct am65_cpsw_port *port, u16 vid,
struct net_device *orig_dev)
{
bool cpu_port = netif_is_bridge_master(orig_dev);
struct am65_cpsw_common *cpsw = port->common;
int port_mask;
int ret = 0;
if (cpu_port)
port_mask = BIT(HOST_PORT_NUM);
else
port_mask = BIT(port->port_id);
ret = cpsw_ale_del_vlan(cpsw->ale, vid, port_mask);
if (ret != 0)
return ret;
/* We don't care for the return value here, error is returned only if
* the unicast entry is not present
*/
if (cpu_port)
cpsw_ale_del_ucast(cpsw->ale, port->slave.mac_addr,
HOST_PORT_NUM, ALE_VLAN, vid);
if (vid == am65_cpsw_get_pvid(port))
am65_cpsw_set_pvid(port, 0, 0, 0);
/* We don't care for the return value here, error is returned only if
* the multicast entry is not present
*/
cpsw_ale_del_mcast(cpsw->ale, port->ndev->broadcast, port_mask,
ALE_VLAN, vid);
netdev_dbg(port->ndev, "VID del: %s: vid:%u ports:%X\n",
port->ndev->name, vid, port_mask);
return ret;
}
static int am65_cpsw_port_vlans_add(struct am65_cpsw_port *port,
const struct switchdev_obj_port_vlan *vlan)
{
bool untag = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
struct net_device *orig_dev = vlan->obj.orig_dev;
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
netdev_dbg(port->ndev, "VID add: %s: vid:%u flags:%X\n",
port->ndev->name, vlan->vid, vlan->flags);
return am65_cpsw_port_vlan_add(port, untag, pvid, vlan->vid, orig_dev);
}
static int am65_cpsw_port_vlans_del(struct am65_cpsw_port *port,
const struct switchdev_obj_port_vlan *vlan)
{
return am65_cpsw_port_vlan_del(port, vlan->vid, vlan->obj.orig_dev);
}
static int am65_cpsw_port_mdb_add(struct am65_cpsw_port *port,
struct switchdev_obj_port_mdb *mdb)
{
struct net_device *orig_dev = mdb->obj.orig_dev;
bool cpu_port = netif_is_bridge_master(orig_dev);
struct am65_cpsw_common *cpsw = port->common;
int port_mask;
int err;
if (cpu_port)
port_mask = BIT(HOST_PORT_NUM);
else
port_mask = BIT(port->port_id);
err = cpsw_ale_add_mcast(cpsw->ale, mdb->addr, port_mask,
ALE_VLAN, mdb->vid, 0);
netdev_dbg(port->ndev, "MDB add: %s: vid %u:%pM ports: %X\n",
port->ndev->name, mdb->vid, mdb->addr, port_mask);
return err;
}
static int am65_cpsw_port_mdb_del(struct am65_cpsw_port *port,
struct switchdev_obj_port_mdb *mdb)
{
struct net_device *orig_dev = mdb->obj.orig_dev;
bool cpu_port = netif_is_bridge_master(orig_dev);
struct am65_cpsw_common *cpsw = port->common;
int del_mask;
if (cpu_port)
del_mask = BIT(HOST_PORT_NUM);
else
del_mask = BIT(port->port_id);
/* Ignore error as error code is returned only when entry is already removed */
cpsw_ale_del_mcast(cpsw->ale, mdb->addr, del_mask,
ALE_VLAN, mdb->vid);
netdev_dbg(port->ndev, "MDB del: %s: vid %u:%pM ports: %X\n",
port->ndev->name, mdb->vid, mdb->addr, del_mask);
return 0;
}
static int am65_cpsw_port_obj_add(struct net_device *ndev, const void *ctx,
const struct switchdev_obj *obj,
struct netlink_ext_ack *extack)
{
struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
int err = 0;
netdev_dbg(ndev, "obj_add: id %u port: %u\n", obj->id, port->port_id);
switch (obj->id) {
case SWITCHDEV_OBJ_ID_PORT_VLAN:
err = am65_cpsw_port_vlans_add(port, vlan);
break;
case SWITCHDEV_OBJ_ID_PORT_MDB:
case SWITCHDEV_OBJ_ID_HOST_MDB:
err = am65_cpsw_port_mdb_add(port, mdb);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
static int am65_cpsw_port_obj_del(struct net_device *ndev, const void *ctx,
const struct switchdev_obj *obj)
{
struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
int err = 0;
netdev_dbg(ndev, "obj_del: id %u port: %u\n", obj->id, port->port_id);
switch (obj->id) {
case SWITCHDEV_OBJ_ID_PORT_VLAN:
err = am65_cpsw_port_vlans_del(port, vlan);
break;
case SWITCHDEV_OBJ_ID_PORT_MDB:
case SWITCHDEV_OBJ_ID_HOST_MDB:
err = am65_cpsw_port_mdb_del(port, mdb);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
static void am65_cpsw_fdb_offload_notify(struct net_device *ndev,
struct switchdev_notifier_fdb_info *rcv)
{
struct switchdev_notifier_fdb_info info = {};
info.addr = rcv->addr;
info.vid = rcv->vid;
info.offloaded = true;
call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
ndev, &info.info, NULL);
}
static void am65_cpsw_switchdev_event_work(struct work_struct *work)
{
struct am65_cpsw_switchdev_event_work *switchdev_work =
container_of(work, struct am65_cpsw_switchdev_event_work, work);
struct am65_cpsw_port *port = switchdev_work->port;
struct switchdev_notifier_fdb_info *fdb;
struct am65_cpsw_common *cpsw = port->common;
int port_id = port->port_id;
rtnl_lock();
switch (switchdev_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
fdb = &switchdev_work->fdb_info;
netdev_dbg(port->ndev, "cpsw_fdb_add: MACID = %pM vid = %u flags = %u %u -- port %d\n",
fdb->addr, fdb->vid, fdb->added_by_user,
fdb->offloaded, port_id);
if (!fdb->added_by_user || fdb->is_local)
break;
if (memcmp(port->slave.mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
port_id = HOST_PORT_NUM;
cpsw_ale_add_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
fdb->vid ? ALE_VLAN : 0, fdb->vid);
am65_cpsw_fdb_offload_notify(port->ndev, fdb);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb = &switchdev_work->fdb_info;
netdev_dbg(port->ndev, "cpsw_fdb_del: MACID = %pM vid = %u flags = %u %u -- port %d\n",
fdb->addr, fdb->vid, fdb->added_by_user,
fdb->offloaded, port_id);
if (!fdb->added_by_user || fdb->is_local)
break;
if (memcmp(port->slave.mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
port_id = HOST_PORT_NUM;
cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
fdb->vid ? ALE_VLAN : 0, fdb->vid);
break;
default:
break;
}
rtnl_unlock();
kfree(switchdev_work->fdb_info.addr);
kfree(switchdev_work);
dev_put(port->ndev);
}
/* called under rcu_read_lock() */
static int am65_cpsw_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
struct am65_cpsw_switchdev_event_work *switchdev_work;
struct am65_cpsw_port *port = am65_ndev_to_port(ndev);
struct switchdev_notifier_fdb_info *fdb_info = ptr;
int err;
if (event == SWITCHDEV_PORT_ATTR_SET) {
err = switchdev_handle_port_attr_set(ndev, ptr,
am65_cpsw_port_dev_check,
am65_cpsw_port_attr_set);
return notifier_from_errno(err);
}
if (!am65_cpsw_port_dev_check(ndev))
return NOTIFY_DONE;
switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC);
if (WARN_ON(!switchdev_work))
return NOTIFY_BAD;
INIT_WORK(&switchdev_work->work, am65_cpsw_switchdev_event_work);
switchdev_work->port = port;
switchdev_work->event = event;
switch (event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
case SWITCHDEV_FDB_DEL_TO_DEVICE:
memcpy(&switchdev_work->fdb_info, ptr,
sizeof(switchdev_work->fdb_info));
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!switchdev_work->fdb_info.addr)
goto err_addr_alloc;
ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
fdb_info->addr);
dev_hold(ndev);
break;
default:
kfree(switchdev_work);
return NOTIFY_DONE;
}
queue_work(system_long_wq, &switchdev_work->work);
return NOTIFY_DONE;
err_addr_alloc:
kfree(switchdev_work);
return NOTIFY_BAD;
}
static struct notifier_block cpsw_switchdev_notifier = {
.notifier_call = am65_cpsw_switchdev_event,
};
static int am65_cpsw_switchdev_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
int err;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = switchdev_handle_port_obj_add(dev, ptr,
am65_cpsw_port_dev_check,
am65_cpsw_port_obj_add);
return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL:
err = switchdev_handle_port_obj_del(dev, ptr,
am65_cpsw_port_dev_check,
am65_cpsw_port_obj_del);
return notifier_from_errno(err);
case SWITCHDEV_PORT_ATTR_SET:
err = switchdev_handle_port_attr_set(dev, ptr,
am65_cpsw_port_dev_check,
am65_cpsw_port_attr_set);
return notifier_from_errno(err);
default:
break;
}
return NOTIFY_DONE;
}
static struct notifier_block cpsw_switchdev_bl_notifier = {
.notifier_call = am65_cpsw_switchdev_blocking_event,
};
int am65_cpsw_switchdev_register_notifiers(struct am65_cpsw_common *cpsw)
{
int ret = 0;
ret = register_switchdev_notifier(&cpsw_switchdev_notifier);
if (ret) {
dev_err(cpsw->dev, "register switchdev notifier fail ret:%d\n",
ret);
return ret;
}
ret = register_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
if (ret) {
dev_err(cpsw->dev, "register switchdev blocking notifier ret:%d\n",
ret);
unregister_switchdev_notifier(&cpsw_switchdev_notifier);
}
return ret;
}
void am65_cpsw_switchdev_unregister_notifiers(struct am65_cpsw_common *cpsw)
{
unregister_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
unregister_switchdev_notifier(&cpsw_switchdev_notifier);
}
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2020 Texas Instruments Incorporated - https://www.ti.com/
*/
#ifndef DRIVERS_NET_ETHERNET_TI_AM65_CPSW_SWITCHDEV_H_
#define DRIVERS_NET_ETHERNET_TI_AM65_CPSW_SWITCHDEV_H_
#include <linux/skbuff.h>
#if IS_ENABLED(CONFIG_TI_K3_AM65_CPSW_SWITCHDEV)
static inline void am65_cpsw_nuss_set_offload_fwd_mark(struct sk_buff *skb, bool val)
{
skb->offload_fwd_mark = val;
}
int am65_cpsw_switchdev_register_notifiers(struct am65_cpsw_common *cpsw);
void am65_cpsw_switchdev_unregister_notifiers(struct am65_cpsw_common *cpsw);
#else
static inline int am65_cpsw_switchdev_register_notifiers(struct am65_cpsw_common *cpsw)
{
return -EOPNOTSUPP;
}
static inline void am65_cpsw_switchdev_unregister_notifiers(struct am65_cpsw_common *cpsw)
{
}
static inline void am65_cpsw_nuss_set_offload_fwd_mark(struct sk_buff *skb, bool val)
{
}
#endif
#endif /* DRIVERS_NET_ETHERNET_TI_AM65_CPSW_SWITCHDEV_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,94 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* TI K3 AM65 CPTS driver interface
*
* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
*/
#ifndef K3_CPTS_H_
#define K3_CPTS_H_
#include <linux/device.h>
#include <linux/of.h>
struct am65_cpts;
struct am65_cpts_estf_cfg {
u64 ns_period;
u64 ns_start;
};
#if IS_ENABLED(CONFIG_TI_K3_AM65_CPTS)
void am65_cpts_release(struct am65_cpts *cpts);
struct am65_cpts *am65_cpts_create(struct device *dev, void __iomem *regs,
struct device_node *node);
int am65_cpts_phc_index(struct am65_cpts *cpts);
void am65_cpts_rx_timestamp(struct am65_cpts *cpts, unsigned int port_id,
struct sk_buff *skb);
void am65_cpts_tx_timestamp(struct am65_cpts *cpts, struct sk_buff *skb);
void am65_cpts_prep_tx_timestamp(struct am65_cpts *cpts, unsigned int port_id,
struct sk_buff *skb);
u64 am65_cpts_ns_gettime(struct am65_cpts *cpts);
int am65_cpts_estf_enable(struct am65_cpts *cpts, int idx,
struct am65_cpts_estf_cfg *cfg);
void am65_cpts_estf_disable(struct am65_cpts *cpts, int idx);
void am65_cpts_suspend(struct am65_cpts *cpts);
void am65_cpts_resume(struct am65_cpts *cpts);
#else
static inline void am65_cpts_release(struct am65_cpts *cpts)
{
}
static inline struct am65_cpts *am65_cpts_create(struct device *dev,
void __iomem *regs,
struct device_node *node)
{
return ERR_PTR(-EOPNOTSUPP);
}
static inline int am65_cpts_phc_index(struct am65_cpts *cpts)
{
return -1;
}
static inline void am65_cpts_rx_timestamp(struct am65_cpts *cpts,
unsigned int port_id,
struct sk_buff *skb)
{
}
static inline void am65_cpts_tx_timestamp(struct am65_cpts *cpts,
struct sk_buff *skb)
{
}
static inline void am65_cpts_prep_tx_timestamp(struct am65_cpts *cpts,
unsigned int port_id,
struct sk_buff *skb)
{
}
static inline s64 am65_cpts_ns_gettime(struct am65_cpts *cpts)
{
return 0;
}
static inline int am65_cpts_estf_enable(struct am65_cpts *cpts, int idx,
struct am65_cpts_estf_cfg *cfg)
{
return 0;
}
static inline void am65_cpts_estf_disable(struct am65_cpts *cpts, int idx)
{
}
static inline void am65_cpts_suspend(struct am65_cpts *cpts)
{
}
static inline void am65_cpts_resume(struct am65_cpts *cpts)
{
}
#endif
#endif /* K3_CPTS_H_ */
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0+
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include "cpsw.h"
#define CTRL_MAC_LO_REG(offset, id) ((offset) + 0x8 * (id))
#define CTRL_MAC_HI_REG(offset, id) ((offset) + 0x8 * (id) + 0x4)
static int davinci_emac_3517_get_macid(struct device *dev, u16 offset,
int slave, u8 *mac_addr)
{
u32 macid_lsb;
u32 macid_msb;
struct regmap *syscon;
syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
if (IS_ERR(syscon)) {
if (PTR_ERR(syscon) == -ENODEV)
return 0;
return PTR_ERR(syscon);
}
regmap_read(syscon, CTRL_MAC_LO_REG(offset, slave), &macid_lsb);
regmap_read(syscon, CTRL_MAC_HI_REG(offset, slave), &macid_msb);
mac_addr[0] = (macid_msb >> 16) & 0xff;
mac_addr[1] = (macid_msb >> 8) & 0xff;
mac_addr[2] = macid_msb & 0xff;
mac_addr[3] = (macid_lsb >> 16) & 0xff;
mac_addr[4] = (macid_lsb >> 8) & 0xff;
mac_addr[5] = macid_lsb & 0xff;
return 0;
}
static int cpsw_am33xx_cm_get_macid(struct device *dev, u16 offset, int slave,
u8 *mac_addr)
{
u32 macid_lo;
u32 macid_hi;
struct regmap *syscon;
syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
if (IS_ERR(syscon)) {
if (PTR_ERR(syscon) == -ENODEV)
return 0;
return PTR_ERR(syscon);
}
regmap_read(syscon, CTRL_MAC_LO_REG(offset, slave), &macid_lo);
regmap_read(syscon, CTRL_MAC_HI_REG(offset, slave), &macid_hi);
mac_addr[5] = (macid_lo >> 8) & 0xff;
mac_addr[4] = macid_lo & 0xff;
mac_addr[3] = (macid_hi >> 24) & 0xff;
mac_addr[2] = (macid_hi >> 16) & 0xff;
mac_addr[1] = (macid_hi >> 8) & 0xff;
mac_addr[0] = macid_hi & 0xff;
return 0;
}
int ti_cm_get_macid(struct device *dev, int slave, u8 *mac_addr)
{
if (of_machine_is_compatible("ti,dm8148"))
return cpsw_am33xx_cm_get_macid(dev, 0x630, slave, mac_addr);
if (of_machine_is_compatible("ti,am33xx"))
return cpsw_am33xx_cm_get_macid(dev, 0x630, slave, mac_addr);
if (of_device_is_compatible(dev->of_node, "ti,am3517-emac"))
return davinci_emac_3517_get_macid(dev, 0x110, slave, mac_addr);
if (of_device_is_compatible(dev->of_node, "ti,dm816-emac"))
return cpsw_am33xx_cm_get_macid(dev, 0x30, slave, mac_addr);
if (of_machine_is_compatible("ti,am43"))
return cpsw_am33xx_cm_get_macid(dev, 0x630, slave, mac_addr);
if (of_machine_is_compatible("ti,dra7"))
return davinci_emac_3517_get_macid(dev, 0x514, slave, mac_addr);
dev_info(dev, "incompatible machine/device type for reading mac address\n");
return -ENOENT;
}
EXPORT_SYMBOL_GPL(ti_cm_get_macid);
MODULE_DESCRIPTION("TI CPSW Switch common module");
MODULE_LICENSE("GPL");
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments Ethernet Switch Driver
*
* Copyright (C) 2013 Texas Instruments
*
* Module Author: Mugunthan V N <mugunthanvnm@ti.com>
*
*/
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/of.h>
#include "cpsw.h"
/* AM33xx SoC specific definitions for the CONTROL port */
#define AM33XX_GMII_SEL_MODE_MII 0
#define AM33XX_GMII_SEL_MODE_RMII 1
#define AM33XX_GMII_SEL_MODE_RGMII 2
#define AM33XX_GMII_SEL_RMII2_IO_CLK_EN BIT(7)
#define AM33XX_GMII_SEL_RMII1_IO_CLK_EN BIT(6)
#define AM33XX_GMII_SEL_RGMII2_IDMODE BIT(5)
#define AM33XX_GMII_SEL_RGMII1_IDMODE BIT(4)
#define GMII_SEL_MODE_MASK 0x3
struct cpsw_phy_sel_priv {
struct device *dev;
u32 __iomem *gmii_sel;
bool rmii_clock_external;
void (*cpsw_phy_sel)(struct cpsw_phy_sel_priv *priv,
phy_interface_t phy_mode, int slave);
};
static void cpsw_gmii_sel_am3352(struct cpsw_phy_sel_priv *priv,
phy_interface_t phy_mode, int slave)
{
u32 reg;
u32 mask;
u32 mode = 0;
bool rgmii_id = false;
reg = readl(priv->gmii_sel);
switch (phy_mode) {
case PHY_INTERFACE_MODE_RMII:
mode = AM33XX_GMII_SEL_MODE_RMII;
break;
case PHY_INTERFACE_MODE_RGMII:
mode = AM33XX_GMII_SEL_MODE_RGMII;
break;
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII_RXID:
case PHY_INTERFACE_MODE_RGMII_TXID:
mode = AM33XX_GMII_SEL_MODE_RGMII;
rgmii_id = true;
break;
default:
dev_warn(priv->dev,
"Unsupported PHY mode: \"%s\". Defaulting to MII.\n",
phy_modes(phy_mode));
fallthrough;
case PHY_INTERFACE_MODE_MII:
mode = AM33XX_GMII_SEL_MODE_MII;
break;
}
mask = GMII_SEL_MODE_MASK << (slave * 2) | BIT(slave + 6);
mask |= BIT(slave + 4);
mode <<= slave * 2;
if (priv->rmii_clock_external) {
if (slave == 0)
mode |= AM33XX_GMII_SEL_RMII1_IO_CLK_EN;
else
mode |= AM33XX_GMII_SEL_RMII2_IO_CLK_EN;
}
if (rgmii_id) {
if (slave == 0)
mode |= AM33XX_GMII_SEL_RGMII1_IDMODE;
else
mode |= AM33XX_GMII_SEL_RGMII2_IDMODE;
}
reg &= ~mask;
reg |= mode;
writel(reg, priv->gmii_sel);
}
static void cpsw_gmii_sel_dra7xx(struct cpsw_phy_sel_priv *priv,
phy_interface_t phy_mode, int slave)
{
u32 reg;
u32 mask;
u32 mode = 0;
reg = readl(priv->gmii_sel);
switch (phy_mode) {
case PHY_INTERFACE_MODE_RMII:
mode = AM33XX_GMII_SEL_MODE_RMII;
break;
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII_RXID:
case PHY_INTERFACE_MODE_RGMII_TXID:
mode = AM33XX_GMII_SEL_MODE_RGMII;
break;
default:
dev_warn(priv->dev,
"Unsupported PHY mode: \"%s\". Defaulting to MII.\n",
phy_modes(phy_mode));
fallthrough;
case PHY_INTERFACE_MODE_MII:
mode = AM33XX_GMII_SEL_MODE_MII;
break;
}
switch (slave) {
case 0:
mask = GMII_SEL_MODE_MASK;
break;
case 1:
mask = GMII_SEL_MODE_MASK << 4;
mode <<= 4;
break;
default:
dev_err(priv->dev, "invalid slave number...\n");
return;
}
if (priv->rmii_clock_external)
dev_err(priv->dev, "RMII External clock is not supported\n");
reg &= ~mask;
reg |= mode;
writel(reg, priv->gmii_sel);
}
static struct platform_driver cpsw_phy_sel_driver;
static int match(struct device *dev, const void *data)
{
const struct device_node *node = (const struct device_node *)data;
return dev->of_node == node &&
dev->driver == &cpsw_phy_sel_driver.driver;
}
void cpsw_phy_sel(struct device *dev, phy_interface_t phy_mode, int slave)
{
struct device_node *node;
struct cpsw_phy_sel_priv *priv;
node = of_parse_phandle(dev->of_node, "cpsw-phy-sel", 0);
if (!node) {
node = of_get_child_by_name(dev->of_node, "cpsw-phy-sel");
if (!node) {
dev_err(dev, "Phy mode driver DT not found\n");
return;
}
}
dev = bus_find_device(&platform_bus_type, NULL, node, match);
if (!dev) {
dev_err(dev, "unable to find platform device for %pOF\n", node);
goto out;
}
priv = dev_get_drvdata(dev);
priv->cpsw_phy_sel(priv, phy_mode, slave);
put_device(dev);
out:
of_node_put(node);
}
EXPORT_SYMBOL_GPL(cpsw_phy_sel);
static const struct of_device_id cpsw_phy_sel_id_table[] = {
{
.compatible = "ti,am3352-cpsw-phy-sel",
.data = &cpsw_gmii_sel_am3352,
},
{
.compatible = "ti,dra7xx-cpsw-phy-sel",
.data = &cpsw_gmii_sel_dra7xx,
},
{
.compatible = "ti,am43xx-cpsw-phy-sel",
.data = &cpsw_gmii_sel_am3352,
},
{}
};
static int cpsw_phy_sel_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id;
struct cpsw_phy_sel_priv *priv;
of_id = of_match_node(cpsw_phy_sel_id_table, pdev->dev.of_node);
if (!of_id)
return -EINVAL;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "unable to alloc memory for cpsw phy sel\n");
return -ENOMEM;
}
priv->dev = &pdev->dev;
priv->cpsw_phy_sel = of_id->data;
priv->gmii_sel = devm_platform_ioremap_resource_byname(pdev, "gmii-sel");
if (IS_ERR(priv->gmii_sel))
return PTR_ERR(priv->gmii_sel);
priv->rmii_clock_external = of_property_read_bool(pdev->dev.of_node, "rmii-clock-ext");
dev_set_drvdata(&pdev->dev, priv);
return 0;
}
static struct platform_driver cpsw_phy_sel_driver = {
.probe = cpsw_phy_sel_probe,
.driver = {
.name = "cpsw-phy-sel",
.of_match_table = cpsw_phy_sel_id_table,
},
};
builtin_platform_driver(cpsw_phy_sel_driver);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Texas Instruments Ethernet Switch Driver
*
* Copyright (C) 2013 Texas Instruments
*
*/
#ifndef __CPSW_H__
#define __CPSW_H__
#include <linux/if_ether.h>
#include <linux/phy.h>
#define mac_hi(mac) (((mac)[0] << 0) | ((mac)[1] << 8) | \
((mac)[2] << 16) | ((mac)[3] << 24))
#define mac_lo(mac) (((mac)[4] << 0) | ((mac)[5] << 8))
#if IS_ENABLED(CONFIG_TI_CPSW_PHY_SEL)
void cpsw_phy_sel(struct device *dev, phy_interface_t phy_mode, int slave);
#else
static inline
void cpsw_phy_sel(struct device *dev, phy_interface_t phy_mode, int slave)
{}
#endif
int ti_cm_get_macid(struct device *dev, int slave, u8 *mac_addr);
#endif /* __CPSW_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,199 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Texas Instruments N-Port Ethernet Switch Address Lookup Engine APIs
*
* Copyright (C) 2012 Texas Instruments
*
*/
#ifndef __TI_CPSW_ALE_H__
#define __TI_CPSW_ALE_H__
struct reg_fields;
struct cpsw_ale_params {
struct device *dev;
void __iomem *ale_regs;
unsigned long ale_ageout; /* in secs */
unsigned long ale_entries;
unsigned long num_policers;
unsigned long ale_ports;
/* NU Switch has specific handling as number of bits in ALE entries
* are different than other versions of ALE. Also there are specific
* registers for unknown vlan specific fields. So use nu_switch_ale
* to identify this hardware.
*/
bool nu_switch_ale;
const struct reg_field *reg_fields;
int num_fields;
const char *dev_id;
unsigned long bus_freq;
};
struct ale_entry_fld;
struct regmap;
enum ale_fields {
MINOR_VER,
MAJOR_VER,
ALE_ENTRIES,
ALE_POLICERS,
POL_PORT_MEN,
POL_TRUNK_ID,
POL_PORT_NUM,
POL_PRI_MEN,
POL_PRI_VAL,
POL_OUI_MEN,
POL_OUI_INDEX,
POL_DST_MEN,
POL_DST_INDEX,
POL_SRC_MEN,
POL_SRC_INDEX,
POL_OVLAN_MEN,
POL_OVLAN_INDEX,
POL_IVLAN_MEN,
POL_IVLAN_INDEX,
POL_ETHERTYPE_MEN,
POL_ETHERTYPE_INDEX,
POL_IPSRC_MEN,
POL_IPSRC_INDEX,
POL_IPDST_MEN,
POL_IPDST_INDEX,
POL_EN,
POL_RED_DROP_EN,
POL_YELLOW_DROP_EN,
POL_YELLOW_THRESH,
POL_POL_MATCH_MODE,
POL_PRIORITY_THREAD_EN,
POL_MAC_ONLY_DEF_DIS,
POL_TEST_CLR,
POL_TEST_CLR_RED,
POL_TEST_CLR_YELLOW,
POL_TEST_CLR_SELECTED,
POL_TEST_ENTRY,
POL_STATUS_HIT,
POL_STATUS_HIT_RED,
POL_STATUS_HIT_YELLOW,
ALE_DEFAULT_THREAD_EN,
ALE_DEFAULT_THREAD_VAL,
ALE_THREAD_CLASS_INDEX,
ALE_THREAD_ENABLE,
ALE_THREAD_VALUE,
/* terminator */
ALE_FIELDS_MAX,
};
struct cpsw_ale {
struct cpsw_ale_params params;
struct timer_list timer;
struct regmap *regmap;
struct regmap_field *fields[ALE_FIELDS_MAX];
unsigned long ageout;
u32 version;
u32 features;
/* These bits are different on NetCP NU Switch ALE */
u32 port_mask_bits;
u32 port_num_bits;
u32 vlan_field_bits;
unsigned long *p0_untag_vid_mask;
const struct ale_entry_fld *vlan_entry_tbl;
};
enum cpsw_ale_control {
/* global */
ALE_ENABLE,
ALE_CLEAR,
ALE_AGEOUT,
ALE_P0_UNI_FLOOD,
ALE_VLAN_NOLEARN,
ALE_NO_PORT_VLAN,
ALE_OUI_DENY,
ALE_BYPASS,
ALE_RATE_LIMIT_TX,
ALE_VLAN_AWARE,
ALE_AUTH_ENABLE,
ALE_RATE_LIMIT,
/* port controls */
ALE_PORT_STATE,
ALE_PORT_DROP_UNTAGGED,
ALE_PORT_DROP_UNKNOWN_VLAN,
ALE_PORT_NOLEARN,
ALE_PORT_NO_SA_UPDATE,
ALE_PORT_UNKNOWN_VLAN_MEMBER,
ALE_PORT_UNKNOWN_MCAST_FLOOD,
ALE_PORT_UNKNOWN_REG_MCAST_FLOOD,
ALE_PORT_UNTAGGED_EGRESS,
ALE_PORT_MACONLY,
ALE_PORT_MACONLY_CAF,
ALE_PORT_BCAST_LIMIT,
ALE_PORT_MCAST_LIMIT,
ALE_DEFAULT_THREAD_ID,
ALE_DEFAULT_THREAD_ENABLE,
ALE_NUM_CONTROLS,
};
enum cpsw_ale_port_state {
ALE_PORT_STATE_DISABLE = 0x00,
ALE_PORT_STATE_BLOCK = 0x01,
ALE_PORT_STATE_LEARN = 0x02,
ALE_PORT_STATE_FORWARD = 0x03,
};
/* ALE unicast entry flags - passed into cpsw_ale_add_ucast() */
#define ALE_SECURE BIT(0)
#define ALE_BLOCKED BIT(1)
#define ALE_SUPER BIT(2)
#define ALE_VLAN BIT(3)
#define ALE_PORT_HOST BIT(0)
#define ALE_PORT_1 BIT(1)
#define ALE_PORT_2 BIT(2)
#define ALE_MCAST_FWD 0
#define ALE_MCAST_BLOCK_LEARN_FWD 1
#define ALE_MCAST_FWD_LEARN 2
#define ALE_MCAST_FWD_2 3
#define ALE_ENTRY_BITS 68
#define ALE_ENTRY_WORDS DIV_ROUND_UP(ALE_ENTRY_BITS, 32)
struct cpsw_ale *cpsw_ale_create(struct cpsw_ale_params *params);
void cpsw_ale_start(struct cpsw_ale *ale);
void cpsw_ale_stop(struct cpsw_ale *ale);
int cpsw_ale_flush_multicast(struct cpsw_ale *ale, int port_mask, int vid);
int cpsw_ale_add_ucast(struct cpsw_ale *ale, const u8 *addr, int port,
int flags, u16 vid);
int cpsw_ale_del_ucast(struct cpsw_ale *ale, const u8 *addr, int port,
int flags, u16 vid);
int cpsw_ale_add_mcast(struct cpsw_ale *ale, const u8 *addr, int port_mask,
int flags, u16 vid, int mcast_state);
int cpsw_ale_del_mcast(struct cpsw_ale *ale, const u8 *addr, int port_mask,
int flags, u16 vid);
int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port, int untag,
int reg_mcast, int unreg_mcast);
int cpsw_ale_del_vlan(struct cpsw_ale *ale, u16 vid, int port);
void cpsw_ale_set_allmulti(struct cpsw_ale *ale, int allmulti, int port);
int cpsw_ale_rx_ratelimit_bc(struct cpsw_ale *ale, int port, unsigned int ratelimit_pps);
int cpsw_ale_rx_ratelimit_mc(struct cpsw_ale *ale, int port, unsigned int ratelimit_pps);
int cpsw_ale_control_get(struct cpsw_ale *ale, int port, int control);
int cpsw_ale_control_set(struct cpsw_ale *ale, int port,
int control, int value);
void cpsw_ale_dump(struct cpsw_ale *ale, u32 *data);
void cpsw_ale_restore(struct cpsw_ale *ale, u32 *data);
u32 cpsw_ale_get_num_entries(struct cpsw_ale *ale);
static inline int cpsw_ale_get_vlan_p0_untag(struct cpsw_ale *ale, u16 vid)
{
return test_bit(vid, ale->p0_untag_vid_mask);
}
int cpsw_ale_vlan_add_modify(struct cpsw_ale *ale, u16 vid, int port_mask,
int untag_mask, int reg_mcast, int unreg_mcast);
int cpsw_ale_vlan_del_modify(struct cpsw_ale *ale, u16 vid, int port_mask);
void cpsw_ale_set_unreg_mcast(struct cpsw_ale *ale, int unreg_mcast_mask,
bool add);
void cpsw_ale_classifier_setup_default(struct cpsw_ale *ale, int num_rx_ch);
#endif
@@ -0,0 +1,756 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Texas Instruments Ethernet Switch Driver ethtool intf
*
* Copyright (C) 2019 Texas Instruments
*/
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/kmemleak.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/net_tstamp.h>
#include <linux/phy.h>
#include <linux/pm_runtime.h>
#include <linux/skbuff.h>
#include "cpsw.h"
#include "cpts.h"
#include "cpsw_ale.h"
#include "cpsw_priv.h"
#include "davinci_cpdma.h"
struct cpsw_hw_stats {
u32 rxgoodframes;
u32 rxbroadcastframes;
u32 rxmulticastframes;
u32 rxpauseframes;
u32 rxcrcerrors;
u32 rxaligncodeerrors;
u32 rxoversizedframes;
u32 rxjabberframes;
u32 rxundersizedframes;
u32 rxfragments;
u32 __pad_0[2];
u32 rxoctets;
u32 txgoodframes;
u32 txbroadcastframes;
u32 txmulticastframes;
u32 txpauseframes;
u32 txdeferredframes;
u32 txcollisionframes;
u32 txsinglecollframes;
u32 txmultcollframes;
u32 txexcessivecollisions;
u32 txlatecollisions;
u32 txunderrun;
u32 txcarriersenseerrors;
u32 txoctets;
u32 octetframes64;
u32 octetframes65t127;
u32 octetframes128t255;
u32 octetframes256t511;
u32 octetframes512t1023;
u32 octetframes1024tup;
u32 netoctets;
u32 rxsofoverruns;
u32 rxmofoverruns;
u32 rxdmaoverruns;
};
struct cpsw_stats {
char stat_string[ETH_GSTRING_LEN];
int type;
int sizeof_stat;
int stat_offset;
};
enum {
CPSW_STATS,
CPDMA_RX_STATS,
CPDMA_TX_STATS,
};
#define CPSW_STAT(m) CPSW_STATS, \
sizeof_field(struct cpsw_hw_stats, m), \
offsetof(struct cpsw_hw_stats, m)
#define CPDMA_RX_STAT(m) CPDMA_RX_STATS, \
sizeof_field(struct cpdma_chan_stats, m), \
offsetof(struct cpdma_chan_stats, m)
#define CPDMA_TX_STAT(m) CPDMA_TX_STATS, \
sizeof_field(struct cpdma_chan_stats, m), \
offsetof(struct cpdma_chan_stats, m)
static const struct cpsw_stats cpsw_gstrings_stats[] = {
{ "Good Rx Frames", CPSW_STAT(rxgoodframes) },
{ "Broadcast Rx Frames", CPSW_STAT(rxbroadcastframes) },
{ "Multicast Rx Frames", CPSW_STAT(rxmulticastframes) },
{ "Pause Rx Frames", CPSW_STAT(rxpauseframes) },
{ "Rx CRC Errors", CPSW_STAT(rxcrcerrors) },
{ "Rx Align/Code Errors", CPSW_STAT(rxaligncodeerrors) },
{ "Oversize Rx Frames", CPSW_STAT(rxoversizedframes) },
{ "Rx Jabbers", CPSW_STAT(rxjabberframes) },
{ "Undersize (Short) Rx Frames", CPSW_STAT(rxundersizedframes) },
{ "Rx Fragments", CPSW_STAT(rxfragments) },
{ "Rx Octets", CPSW_STAT(rxoctets) },
{ "Good Tx Frames", CPSW_STAT(txgoodframes) },
{ "Broadcast Tx Frames", CPSW_STAT(txbroadcastframes) },
{ "Multicast Tx Frames", CPSW_STAT(txmulticastframes) },
{ "Pause Tx Frames", CPSW_STAT(txpauseframes) },
{ "Deferred Tx Frames", CPSW_STAT(txdeferredframes) },
{ "Collisions", CPSW_STAT(txcollisionframes) },
{ "Single Collision Tx Frames", CPSW_STAT(txsinglecollframes) },
{ "Multiple Collision Tx Frames", CPSW_STAT(txmultcollframes) },
{ "Excessive Collisions", CPSW_STAT(txexcessivecollisions) },
{ "Late Collisions", CPSW_STAT(txlatecollisions) },
{ "Tx Underrun", CPSW_STAT(txunderrun) },
{ "Carrier Sense Errors", CPSW_STAT(txcarriersenseerrors) },
{ "Tx Octets", CPSW_STAT(txoctets) },
{ "Rx + Tx 64 Octet Frames", CPSW_STAT(octetframes64) },
{ "Rx + Tx 65-127 Octet Frames", CPSW_STAT(octetframes65t127) },
{ "Rx + Tx 128-255 Octet Frames", CPSW_STAT(octetframes128t255) },
{ "Rx + Tx 256-511 Octet Frames", CPSW_STAT(octetframes256t511) },
{ "Rx + Tx 512-1023 Octet Frames", CPSW_STAT(octetframes512t1023) },
{ "Rx + Tx 1024-Up Octet Frames", CPSW_STAT(octetframes1024tup) },
{ "Net Octets", CPSW_STAT(netoctets) },
{ "Rx Start of Frame Overruns", CPSW_STAT(rxsofoverruns) },
{ "Rx Middle of Frame Overruns", CPSW_STAT(rxmofoverruns) },
{ "Rx DMA Overruns", CPSW_STAT(rxdmaoverruns) },
};
static const struct cpsw_stats cpsw_gstrings_ch_stats[] = {
{ "head_enqueue", CPDMA_RX_STAT(head_enqueue) },
{ "tail_enqueue", CPDMA_RX_STAT(tail_enqueue) },
{ "pad_enqueue", CPDMA_RX_STAT(pad_enqueue) },
{ "misqueued", CPDMA_RX_STAT(misqueued) },
{ "desc_alloc_fail", CPDMA_RX_STAT(desc_alloc_fail) },
{ "pad_alloc_fail", CPDMA_RX_STAT(pad_alloc_fail) },
{ "runt_receive_buf", CPDMA_RX_STAT(runt_receive_buff) },
{ "runt_transmit_buf", CPDMA_RX_STAT(runt_transmit_buff) },
{ "empty_dequeue", CPDMA_RX_STAT(empty_dequeue) },
{ "busy_dequeue", CPDMA_RX_STAT(busy_dequeue) },
{ "good_dequeue", CPDMA_RX_STAT(good_dequeue) },
{ "requeue", CPDMA_RX_STAT(requeue) },
{ "teardown_dequeue", CPDMA_RX_STAT(teardown_dequeue) },
};
#define CPSW_STATS_COMMON_LEN ARRAY_SIZE(cpsw_gstrings_stats)
#define CPSW_STATS_CH_LEN ARRAY_SIZE(cpsw_gstrings_ch_stats)
u32 cpsw_get_msglevel(struct net_device *ndev)
{
struct cpsw_priv *priv = netdev_priv(ndev);
return priv->msg_enable;
}
EXPORT_SYMBOL_GPL(cpsw_get_msglevel);
void cpsw_set_msglevel(struct net_device *ndev, u32 value)
{
struct cpsw_priv *priv = netdev_priv(ndev);
priv->msg_enable = value;
}
EXPORT_SYMBOL_GPL(cpsw_set_msglevel);
int cpsw_get_coalesce(struct net_device *ndev, struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
coal->rx_coalesce_usecs = cpsw->coal_intvl;
return 0;
}
EXPORT_SYMBOL_GPL(cpsw_get_coalesce);
int cpsw_set_coalesce(struct net_device *ndev, struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct cpsw_priv *priv = netdev_priv(ndev);
u32 int_ctrl;
u32 num_interrupts = 0;
u32 prescale = 0;
u32 addnl_dvdr = 1;
u32 coal_intvl = 0;
struct cpsw_common *cpsw = priv->cpsw;
coal_intvl = coal->rx_coalesce_usecs;
int_ctrl = readl(&cpsw->wr_regs->int_control);
prescale = cpsw->bus_freq_mhz * 4;
if (!coal->rx_coalesce_usecs) {
int_ctrl &= ~(CPSW_INTPRESCALE_MASK | CPSW_INTPACEEN);
goto update_return;
}
if (coal_intvl < CPSW_CMINTMIN_INTVL)
coal_intvl = CPSW_CMINTMIN_INTVL;
if (coal_intvl > CPSW_CMINTMAX_INTVL) {
/* Interrupt pacer works with 4us Pulse, we can
* throttle further by dilating the 4us pulse.
*/
addnl_dvdr = CPSW_INTPRESCALE_MASK / prescale;
if (addnl_dvdr > 1) {
prescale *= addnl_dvdr;
if (coal_intvl > (CPSW_CMINTMAX_INTVL * addnl_dvdr))
coal_intvl = (CPSW_CMINTMAX_INTVL
* addnl_dvdr);
} else {
addnl_dvdr = 1;
coal_intvl = CPSW_CMINTMAX_INTVL;
}
}
num_interrupts = (1000 * addnl_dvdr) / coal_intvl;
writel(num_interrupts, &cpsw->wr_regs->rx_imax);
writel(num_interrupts, &cpsw->wr_regs->tx_imax);
int_ctrl |= CPSW_INTPACEEN;
int_ctrl &= (~CPSW_INTPRESCALE_MASK);
int_ctrl |= (prescale & CPSW_INTPRESCALE_MASK);
update_return:
writel(int_ctrl, &cpsw->wr_regs->int_control);
cpsw_notice(priv, timer, "Set coalesce to %d usecs.\n", coal_intvl);
cpsw->coal_intvl = coal_intvl;
return 0;
}
EXPORT_SYMBOL_GPL(cpsw_set_coalesce);
int cpsw_get_sset_count(struct net_device *ndev, int sset)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
switch (sset) {
case ETH_SS_STATS:
return (CPSW_STATS_COMMON_LEN +
(cpsw->rx_ch_num + cpsw->tx_ch_num) *
CPSW_STATS_CH_LEN);
default:
return -EOPNOTSUPP;
}
}
EXPORT_SYMBOL_GPL(cpsw_get_sset_count);
static void cpsw_add_ch_strings(u8 **p, int ch_num, int rx_dir)
{
int ch_stats_len;
int line;
int i;
ch_stats_len = CPSW_STATS_CH_LEN * ch_num;
for (i = 0; i < ch_stats_len; i++) {
line = i % CPSW_STATS_CH_LEN;
snprintf(*p, ETH_GSTRING_LEN,
"%s DMA chan %ld: %s", rx_dir ? "Rx" : "Tx",
(long)(i / CPSW_STATS_CH_LEN),
cpsw_gstrings_ch_stats[line].stat_string);
*p += ETH_GSTRING_LEN;
}
}
void cpsw_get_strings(struct net_device *ndev, u32 stringset, u8 *data)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
u8 *p = data;
int i;
switch (stringset) {
case ETH_SS_STATS:
for (i = 0; i < CPSW_STATS_COMMON_LEN; i++) {
memcpy(p, cpsw_gstrings_stats[i].stat_string,
ETH_GSTRING_LEN);
p += ETH_GSTRING_LEN;
}
cpsw_add_ch_strings(&p, cpsw->rx_ch_num, 1);
cpsw_add_ch_strings(&p, cpsw->tx_ch_num, 0);
break;
}
}
EXPORT_SYMBOL_GPL(cpsw_get_strings);
void cpsw_get_ethtool_stats(struct net_device *ndev,
struct ethtool_stats *stats, u64 *data)
{
u8 *p;
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
struct cpdma_chan_stats ch_stats;
int i, l, ch;
/* Collect Davinci CPDMA stats for Rx and Tx Channel */
for (l = 0; l < CPSW_STATS_COMMON_LEN; l++)
data[l] = readl(cpsw->hw_stats +
cpsw_gstrings_stats[l].stat_offset);
for (ch = 0; ch < cpsw->rx_ch_num; ch++) {
cpdma_chan_get_stats(cpsw->rxv[ch].ch, &ch_stats);
for (i = 0; i < CPSW_STATS_CH_LEN; i++, l++) {
p = (u8 *)&ch_stats +
cpsw_gstrings_ch_stats[i].stat_offset;
data[l] = *(u32 *)p;
}
}
for (ch = 0; ch < cpsw->tx_ch_num; ch++) {
cpdma_chan_get_stats(cpsw->txv[ch].ch, &ch_stats);
for (i = 0; i < CPSW_STATS_CH_LEN; i++, l++) {
p = (u8 *)&ch_stats +
cpsw_gstrings_ch_stats[i].stat_offset;
data[l] = *(u32 *)p;
}
}
}
EXPORT_SYMBOL_GPL(cpsw_get_ethtool_stats);
void cpsw_get_pauseparam(struct net_device *ndev,
struct ethtool_pauseparam *pause)
{
struct cpsw_priv *priv = netdev_priv(ndev);
pause->autoneg = AUTONEG_DISABLE;
pause->rx_pause = priv->rx_pause ? true : false;
pause->tx_pause = priv->tx_pause ? true : false;
}
EXPORT_SYMBOL_GPL(cpsw_get_pauseparam);
void cpsw_get_wol(struct net_device *ndev, struct ethtool_wolinfo *wol)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int slave_no = cpsw_slave_index(cpsw, priv);
wol->supported = 0;
wol->wolopts = 0;
if (cpsw->slaves[slave_no].phy)
phy_ethtool_get_wol(cpsw->slaves[slave_no].phy, wol);
}
EXPORT_SYMBOL_GPL(cpsw_get_wol);
int cpsw_set_wol(struct net_device *ndev, struct ethtool_wolinfo *wol)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int slave_no = cpsw_slave_index(cpsw, priv);
if (cpsw->slaves[slave_no].phy)
return phy_ethtool_set_wol(cpsw->slaves[slave_no].phy, wol);
else
return -EOPNOTSUPP;
}
EXPORT_SYMBOL_GPL(cpsw_set_wol);
int cpsw_get_regs_len(struct net_device *ndev)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
return cpsw_ale_get_num_entries(cpsw->ale) *
ALE_ENTRY_WORDS * sizeof(u32);
}
EXPORT_SYMBOL_GPL(cpsw_get_regs_len);
void cpsw_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *p)
{
u32 *reg = p;
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
/* update CPSW IP version */
regs->version = cpsw->version;
cpsw_ale_dump(cpsw->ale, reg);
}
EXPORT_SYMBOL_GPL(cpsw_get_regs);
int cpsw_ethtool_op_begin(struct net_device *ndev)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int ret;
ret = pm_runtime_resume_and_get(cpsw->dev);
if (ret < 0)
cpsw_err(priv, drv, "ethtool begin failed %d\n", ret);
return ret;
}
EXPORT_SYMBOL_GPL(cpsw_ethtool_op_begin);
void cpsw_ethtool_op_complete(struct net_device *ndev)
{
struct cpsw_priv *priv = netdev_priv(ndev);
pm_runtime_put(priv->cpsw->dev);
}
EXPORT_SYMBOL_GPL(cpsw_ethtool_op_complete);
void cpsw_get_channels(struct net_device *ndev, struct ethtool_channels *ch)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
ch->max_rx = cpsw->quirk_irq ? 1 : CPSW_MAX_QUEUES;
ch->max_tx = cpsw->quirk_irq ? 1 : CPSW_MAX_QUEUES;
ch->max_combined = 0;
ch->max_other = 0;
ch->other_count = 0;
ch->rx_count = cpsw->rx_ch_num;
ch->tx_count = cpsw->tx_ch_num;
ch->combined_count = 0;
}
EXPORT_SYMBOL_GPL(cpsw_get_channels);
int cpsw_get_link_ksettings(struct net_device *ndev,
struct ethtool_link_ksettings *ecmd)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int slave_no = cpsw_slave_index(cpsw, priv);
if (!cpsw->slaves[slave_no].phy)
return -EOPNOTSUPP;
phy_ethtool_ksettings_get(cpsw->slaves[slave_no].phy, ecmd);
return 0;
}
EXPORT_SYMBOL_GPL(cpsw_get_link_ksettings);
int cpsw_set_link_ksettings(struct net_device *ndev,
const struct ethtool_link_ksettings *ecmd)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int slave_no = cpsw_slave_index(cpsw, priv);
if (!cpsw->slaves[slave_no].phy)
return -EOPNOTSUPP;
return phy_ethtool_ksettings_set(cpsw->slaves[slave_no].phy, ecmd);
}
EXPORT_SYMBOL_GPL(cpsw_set_link_ksettings);
int cpsw_get_eee(struct net_device *ndev, struct ethtool_keee *edata)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int slave_no = cpsw_slave_index(cpsw, priv);
if (cpsw->slaves[slave_no].phy)
return phy_ethtool_get_eee(cpsw->slaves[slave_no].phy, edata);
else
return -EOPNOTSUPP;
}
EXPORT_SYMBOL_GPL(cpsw_get_eee);
int cpsw_nway_reset(struct net_device *ndev)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int slave_no = cpsw_slave_index(cpsw, priv);
if (cpsw->slaves[slave_no].phy)
return genphy_restart_aneg(cpsw->slaves[slave_no].phy);
else
return -EOPNOTSUPP;
}
EXPORT_SYMBOL_GPL(cpsw_nway_reset);
static void cpsw_suspend_data_pass(struct net_device *ndev)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
int i;
/* Disable NAPI scheduling */
cpsw_intr_disable(cpsw);
/* Stop all transmit queues for every network device.
*/
for (i = 0; i < cpsw->data.slaves; i++) {
ndev = cpsw->slaves[i].ndev;
if (!(ndev && netif_running(ndev)))
continue;
netif_tx_stop_all_queues(ndev);
/* Barrier, so that stop_queue visible to other cpus */
smp_mb__after_atomic();
}
/* Handle rest of tx packets and stop cpdma channels */
cpdma_ctlr_stop(cpsw->dma);
}
static int cpsw_resume_data_pass(struct net_device *ndev)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
int i, ret;
/* After this receive is started */
if (cpsw->usage_count) {
ret = cpsw_fill_rx_channels(priv);
if (ret)
return ret;
cpdma_ctlr_start(cpsw->dma);
cpsw_intr_enable(cpsw);
}
/* Resume transmit for every affected interface */
for (i = 0; i < cpsw->data.slaves; i++) {
ndev = cpsw->slaves[i].ndev;
if (ndev && netif_running(ndev))
netif_tx_start_all_queues(ndev);
}
return 0;
}
static int cpsw_check_ch_settings(struct cpsw_common *cpsw,
struct ethtool_channels *ch)
{
if (cpsw->quirk_irq) {
dev_err(cpsw->dev, "Maximum one tx/rx queue is allowed");
return -EOPNOTSUPP;
}
if (ch->combined_count)
return -EINVAL;
/* verify we have at least one channel in each direction */
if (!ch->rx_count || !ch->tx_count)
return -EINVAL;
if (ch->rx_count > cpsw->data.channels ||
ch->tx_count > cpsw->data.channels)
return -EINVAL;
return 0;
}
static int cpsw_update_channels_res(struct cpsw_priv *priv, int ch_num, int rx,
cpdma_handler_fn rx_handler)
{
struct cpsw_common *cpsw = priv->cpsw;
void (*handler)(void *, int, int);
struct netdev_queue *queue;
struct cpsw_vector *vec;
int ret, *ch, vch;
if (rx) {
ch = &cpsw->rx_ch_num;
vec = cpsw->rxv;
handler = rx_handler;
} else {
ch = &cpsw->tx_ch_num;
vec = cpsw->txv;
handler = cpsw_tx_handler;
}
while (*ch < ch_num) {
vch = rx ? *ch : 7 - *ch;
vec[*ch].ch = cpdma_chan_create(cpsw->dma, vch, handler, rx);
queue = netdev_get_tx_queue(priv->ndev, *ch);
queue->tx_maxrate = 0;
if (IS_ERR(vec[*ch].ch))
return PTR_ERR(vec[*ch].ch);
if (!vec[*ch].ch)
return -EINVAL;
cpsw_info(priv, ifup, "created new %d %s channel\n", *ch,
(rx ? "rx" : "tx"));
(*ch)++;
}
while (*ch > ch_num) {
(*ch)--;
ret = cpdma_chan_destroy(vec[*ch].ch);
if (ret)
return ret;
cpsw_info(priv, ifup, "destroyed %d %s channel\n", *ch,
(rx ? "rx" : "tx"));
}
return 0;
}
static void cpsw_fail(struct cpsw_common *cpsw)
{
struct net_device *ndev;
int i;
for (i = 0; i < cpsw->data.slaves; i++) {
ndev = cpsw->slaves[i].ndev;
if (ndev)
dev_close(ndev);
}
}
int cpsw_set_channels_common(struct net_device *ndev,
struct ethtool_channels *chs,
cpdma_handler_fn rx_handler)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
struct net_device *sl_ndev;
int i, new_pools, ret;
ret = cpsw_check_ch_settings(cpsw, chs);
if (ret < 0)
return ret;
cpsw_suspend_data_pass(ndev);
new_pools = (chs->rx_count != cpsw->rx_ch_num) && cpsw->usage_count;
ret = cpsw_update_channels_res(priv, chs->rx_count, 1, rx_handler);
if (ret)
goto err;
ret = cpsw_update_channels_res(priv, chs->tx_count, 0, rx_handler);
if (ret)
goto err;
for (i = 0; i < cpsw->data.slaves; i++) {
sl_ndev = cpsw->slaves[i].ndev;
if (!(sl_ndev && netif_running(sl_ndev)))
continue;
/* Inform stack about new count of queues */
ret = netif_set_real_num_tx_queues(sl_ndev, cpsw->tx_ch_num);
if (ret) {
dev_err(priv->dev, "cannot set real number of tx queues\n");
goto err;
}
ret = netif_set_real_num_rx_queues(sl_ndev, cpsw->rx_ch_num);
if (ret) {
dev_err(priv->dev, "cannot set real number of rx queues\n");
goto err;
}
}
cpsw_split_res(cpsw);
if (new_pools) {
cpsw_destroy_xdp_rxqs(cpsw);
ret = cpsw_create_xdp_rxqs(cpsw);
if (ret)
goto err;
}
ret = cpsw_resume_data_pass(ndev);
if (!ret)
return 0;
err:
dev_err(priv->dev, "cannot update channels number, closing device\n");
cpsw_fail(cpsw);
return ret;
}
EXPORT_SYMBOL_GPL(cpsw_set_channels_common);
void cpsw_get_ringparam(struct net_device *ndev,
struct ethtool_ringparam *ering,
struct kernel_ethtool_ringparam *kernel_ering,
struct netlink_ext_ack *extack)
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
/* not supported */
ering->tx_max_pending = cpsw->descs_pool_size - CPSW_MAX_QUEUES;
ering->tx_pending = cpdma_get_num_tx_descs(cpsw->dma);
ering->rx_max_pending = cpsw->descs_pool_size - CPSW_MAX_QUEUES;
ering->rx_pending = cpdma_get_num_rx_descs(cpsw->dma);
}
EXPORT_SYMBOL_GPL(cpsw_get_ringparam);
int cpsw_set_ringparam(struct net_device *ndev,
struct ethtool_ringparam *ering,
struct kernel_ethtool_ringparam *kernel_ering,
struct netlink_ext_ack *extack)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
int descs_num, ret;
/* ignore ering->tx_pending - only rx_pending adjustment is supported */
if (ering->rx_mini_pending || ering->rx_jumbo_pending ||
ering->rx_pending < CPSW_MAX_QUEUES ||
ering->rx_pending > (cpsw->descs_pool_size - CPSW_MAX_QUEUES))
return -EINVAL;
descs_num = cpdma_get_num_rx_descs(cpsw->dma);
if (ering->rx_pending == descs_num)
return 0;
cpsw_suspend_data_pass(ndev);
ret = cpdma_set_num_rx_descs(cpsw->dma, ering->rx_pending);
if (ret) {
if (cpsw_resume_data_pass(ndev))
goto err;
return ret;
}
if (cpsw->usage_count) {
cpsw_destroy_xdp_rxqs(cpsw);
ret = cpsw_create_xdp_rxqs(cpsw);
if (ret)
goto err;
}
ret = cpsw_resume_data_pass(ndev);
if (!ret)
return 0;
err:
cpdma_set_num_rx_descs(cpsw->dma, descs_num);
dev_err(cpsw->dev, "cannot set ring params, closing device\n");
cpsw_fail(cpsw);
return ret;
}
EXPORT_SYMBOL_GPL(cpsw_set_ringparam);
#if IS_ENABLED(CONFIG_TI_CPTS)
int cpsw_get_ts_info(struct net_device *ndev, struct kernel_ethtool_ts_info *info)
{
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
info->so_timestamping =
SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
info->phc_index = cpsw->cpts->phc_index;
info->tx_types =
(1 << HWTSTAMP_TX_OFF) |
(1 << HWTSTAMP_TX_ON);
info->rx_filters =
(1 << HWTSTAMP_FILTER_NONE) |
(1 << HWTSTAMP_FILTER_PTP_V2_EVENT);
return 0;
}
EXPORT_SYMBOL_GPL(cpsw_get_ts_info);
#else
int cpsw_get_ts_info(struct net_device *ndev, struct kernel_ethtool_ts_info *info)
{
info->so_timestamping =
SOF_TIMESTAMPING_TX_SOFTWARE;
info->tx_types = 0;
info->rx_filters = 0;
return 0;
}
EXPORT_SYMBOL_GPL(cpsw_get_ts_info);
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,519 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Texas Instruments Ethernet Switch Driver
*/
#ifndef DRIVERS_NET_ETHERNET_TI_CPSW_PRIV_H_
#define DRIVERS_NET_ETHERNET_TI_CPSW_PRIV_H_
#include <net/xdp.h>
#include <uapi/linux/bpf.h>
#include "davinci_cpdma.h"
#define CPSW_DEBUG (NETIF_MSG_HW | NETIF_MSG_WOL | \
NETIF_MSG_DRV | NETIF_MSG_LINK | \
NETIF_MSG_IFUP | NETIF_MSG_INTR | \
NETIF_MSG_PROBE | NETIF_MSG_TIMER | \
NETIF_MSG_IFDOWN | NETIF_MSG_RX_ERR | \
NETIF_MSG_TX_ERR | NETIF_MSG_TX_DONE | \
NETIF_MSG_PKTDATA | NETIF_MSG_TX_QUEUED | \
NETIF_MSG_RX_STATUS)
#define cpsw_info(priv, type, format, ...) \
do { \
if (netif_msg_##type(priv) && net_ratelimit()) \
dev_info(priv->dev, format, ## __VA_ARGS__); \
} while (0)
#define cpsw_err(priv, type, format, ...) \
do { \
if (netif_msg_##type(priv) && net_ratelimit()) \
dev_err(priv->dev, format, ## __VA_ARGS__); \
} while (0)
#define cpsw_dbg(priv, type, format, ...) \
do { \
if (netif_msg_##type(priv) && net_ratelimit()) \
dev_dbg(priv->dev, format, ## __VA_ARGS__); \
} while (0)
#define cpsw_notice(priv, type, format, ...) \
do { \
if (netif_msg_##type(priv) && net_ratelimit()) \
dev_notice(priv->dev, format, ## __VA_ARGS__); \
} while (0)
#define ALE_ALL_PORTS 0x7
#define CPSW_MAJOR_VERSION(reg) (reg >> 8 & 0x7)
#define CPSW_MINOR_VERSION(reg) (reg & 0xff)
#define CPSW_RTL_VERSION(reg) ((reg >> 11) & 0x1f)
#define CPSW_VERSION_1 0x19010a
#define CPSW_VERSION_2 0x19010c
#define CPSW_VERSION_3 0x19010f
#define CPSW_VERSION_4 0x190112
#define HOST_PORT_NUM 0
#define CPSW_ALE_PORTS_NUM 3
#define CPSW_SLAVE_PORTS_NUM 2
#define SLIVER_SIZE 0x40
#define CPSW1_HOST_PORT_OFFSET 0x028
#define CPSW1_SLAVE_OFFSET 0x050
#define CPSW1_SLAVE_SIZE 0x040
#define CPSW1_CPDMA_OFFSET 0x100
#define CPSW1_STATERAM_OFFSET 0x200
#define CPSW1_HW_STATS 0x400
#define CPSW1_CPTS_OFFSET 0x500
#define CPSW1_ALE_OFFSET 0x600
#define CPSW1_SLIVER_OFFSET 0x700
#define CPSW1_WR_OFFSET 0x900
#define CPSW2_HOST_PORT_OFFSET 0x108
#define CPSW2_SLAVE_OFFSET 0x200
#define CPSW2_SLAVE_SIZE 0x100
#define CPSW2_CPDMA_OFFSET 0x800
#define CPSW2_HW_STATS 0x900
#define CPSW2_STATERAM_OFFSET 0xa00
#define CPSW2_CPTS_OFFSET 0xc00
#define CPSW2_ALE_OFFSET 0xd00
#define CPSW2_SLIVER_OFFSET 0xd80
#define CPSW2_BD_OFFSET 0x2000
#define CPSW2_WR_OFFSET 0x1200
#define CPDMA_RXTHRESH 0x0c0
#define CPDMA_RXFREE 0x0e0
#define CPDMA_TXHDP 0x00
#define CPDMA_RXHDP 0x20
#define CPDMA_TXCP 0x40
#define CPDMA_RXCP 0x60
#define CPSW_RX_VLAN_ENCAP_HDR_SIZE 4
#define CPSW_MIN_PACKET_SIZE_VLAN (VLAN_ETH_ZLEN)
#define CPSW_MIN_PACKET_SIZE (ETH_ZLEN)
#define CPSW_MAX_PACKET_SIZE (VLAN_ETH_FRAME_LEN +\
ETH_FCS_LEN +\
CPSW_RX_VLAN_ENCAP_HDR_SIZE)
#define RX_PRIORITY_MAPPING 0x76543210
#define TX_PRIORITY_MAPPING 0x33221100
#define CPDMA_TX_PRIORITY_MAP 0x76543210
#define CPSW_VLAN_AWARE BIT(1)
#define CPSW_RX_VLAN_ENCAP BIT(2)
#define CPSW_ALE_VLAN_AWARE 1
#define CPSW_FIFO_NORMAL_MODE (0 << 16)
#define CPSW_FIFO_DUAL_MAC_MODE (1 << 16)
#define CPSW_FIFO_RATE_LIMIT_MODE (2 << 16)
#define CPSW_INTPACEEN (0x3f << 16)
#define CPSW_INTPRESCALE_MASK (0x7FF << 0)
#define CPSW_CMINTMAX_CNT 63
#define CPSW_CMINTMIN_CNT 2
#define CPSW_CMINTMAX_INTVL (1000 / CPSW_CMINTMIN_CNT)
#define CPSW_CMINTMIN_INTVL ((1000 / CPSW_CMINTMAX_CNT) + 1)
#define IRQ_NUM 2
#define CPSW_MAX_QUEUES 8
#define CPSW_CPDMA_DESCS_POOL_SIZE_DEFAULT 256
#define CPSW_ALE_AGEOUT_DEFAULT 10 /* sec */
#define CPSW_FIFO_QUEUE_TYPE_SHIFT 16
#define CPSW_FIFO_SHAPE_EN_SHIFT 16
#define CPSW_FIFO_RATE_EN_SHIFT 20
#define CPSW_TC_NUM 4
#define CPSW_FIFO_SHAPERS_NUM (CPSW_TC_NUM - 1)
#define CPSW_PCT_MASK 0x7f
#define CPSW_BD_RAM_SIZE 0x2000
#define CPSW_RX_VLAN_ENCAP_HDR_PRIO_SHIFT 29
#define CPSW_RX_VLAN_ENCAP_HDR_PRIO_MSK GENMASK(2, 0)
#define CPSW_RX_VLAN_ENCAP_HDR_VID_SHIFT 16
#define CPSW_RX_VLAN_ENCAP_HDR_PKT_TYPE_SHIFT 8
#define CPSW_RX_VLAN_ENCAP_HDR_PKT_TYPE_MSK GENMASK(1, 0)
enum {
CPSW_RX_VLAN_ENCAP_HDR_PKT_VLAN_TAG = 0,
CPSW_RX_VLAN_ENCAP_HDR_PKT_RESERV,
CPSW_RX_VLAN_ENCAP_HDR_PKT_PRIO_TAG,
CPSW_RX_VLAN_ENCAP_HDR_PKT_UNTAG,
};
struct cpsw_wr_regs {
u32 id_ver;
u32 soft_reset;
u32 control;
u32 int_control;
u32 rx_thresh_en;
u32 rx_en;
u32 tx_en;
u32 misc_en;
u32 mem_allign1[8];
u32 rx_thresh_stat;
u32 rx_stat;
u32 tx_stat;
u32 misc_stat;
u32 mem_allign2[8];
u32 rx_imax;
u32 tx_imax;
};
struct cpsw_ss_regs {
u32 id_ver;
u32 control;
u32 soft_reset;
u32 stat_port_en;
u32 ptype;
u32 soft_idle;
u32 thru_rate;
u32 gap_thresh;
u32 tx_start_wds;
u32 flow_control;
u32 vlan_ltype;
u32 ts_ltype;
u32 dlr_ltype;
};
/* CPSW_PORT_V1 */
#define CPSW1_MAX_BLKS 0x00 /* Maximum FIFO Blocks */
#define CPSW1_BLK_CNT 0x04 /* FIFO Block Usage Count (Read Only) */
#define CPSW1_TX_IN_CTL 0x08 /* Transmit FIFO Control */
#define CPSW1_PORT_VLAN 0x0c /* VLAN Register */
#define CPSW1_TX_PRI_MAP 0x10 /* Tx Header Priority to Switch Pri Mapping */
#define CPSW1_TS_CTL 0x14 /* Time Sync Control */
#define CPSW1_TS_SEQ_LTYPE 0x18 /* Time Sync Sequence ID Offset and Msg Type */
#define CPSW1_TS_VLAN 0x1c /* Time Sync VLAN1 and VLAN2 */
/* CPSW_PORT_V2 */
#define CPSW2_CONTROL 0x00 /* Control Register */
#define CPSW2_MAX_BLKS 0x08 /* Maximum FIFO Blocks */
#define CPSW2_BLK_CNT 0x0c /* FIFO Block Usage Count (Read Only) */
#define CPSW2_TX_IN_CTL 0x10 /* Transmit FIFO Control */
#define CPSW2_PORT_VLAN 0x14 /* VLAN Register */
#define CPSW2_TX_PRI_MAP 0x18 /* Tx Header Priority to Switch Pri Mapping */
#define CPSW2_TS_SEQ_MTYPE 0x1c /* Time Sync Sequence ID Offset and Msg Type */
/* CPSW_PORT_V1 and V2 */
#define SA_LO 0x20 /* CPGMAC_SL Source Address Low */
#define SA_HI 0x24 /* CPGMAC_SL Source Address High */
#define SEND_PERCENT 0x28 /* Transmit Queue Send Percentages */
/* CPSW_PORT_V2 only */
#define RX_DSCP_PRI_MAP0 0x30 /* Rx DSCP Priority to Rx Packet Mapping */
#define RX_DSCP_PRI_MAP1 0x34 /* Rx DSCP Priority to Rx Packet Mapping */
#define RX_DSCP_PRI_MAP2 0x38 /* Rx DSCP Priority to Rx Packet Mapping */
#define RX_DSCP_PRI_MAP3 0x3c /* Rx DSCP Priority to Rx Packet Mapping */
#define RX_DSCP_PRI_MAP4 0x40 /* Rx DSCP Priority to Rx Packet Mapping */
#define RX_DSCP_PRI_MAP5 0x44 /* Rx DSCP Priority to Rx Packet Mapping */
#define RX_DSCP_PRI_MAP6 0x48 /* Rx DSCP Priority to Rx Packet Mapping */
#define RX_DSCP_PRI_MAP7 0x4c /* Rx DSCP Priority to Rx Packet Mapping */
/* Bit definitions for the CPSW2_CONTROL register */
#define PASS_PRI_TAGGED BIT(24) /* Pass Priority Tagged */
#define VLAN_LTYPE2_EN BIT(21) /* VLAN LTYPE 2 enable */
#define VLAN_LTYPE1_EN BIT(20) /* VLAN LTYPE 1 enable */
#define DSCP_PRI_EN BIT(16) /* DSCP Priority Enable */
#define TS_107 BIT(15) /* Tyme Sync Dest IP Address 107 */
#define TS_320 BIT(14) /* Time Sync Dest Port 320 enable */
#define TS_319 BIT(13) /* Time Sync Dest Port 319 enable */
#define TS_132 BIT(12) /* Time Sync Dest IP Addr 132 enable */
#define TS_131 BIT(11) /* Time Sync Dest IP Addr 131 enable */
#define TS_130 BIT(10) /* Time Sync Dest IP Addr 130 enable */
#define TS_129 BIT(9) /* Time Sync Dest IP Addr 129 enable */
#define TS_TTL_NONZERO BIT(8) /* Time Sync Time To Live Non-zero enable */
#define TS_ANNEX_F_EN BIT(6) /* Time Sync Annex F enable */
#define TS_ANNEX_D_EN BIT(4) /* Time Sync Annex D enable */
#define TS_LTYPE2_EN BIT(3) /* Time Sync LTYPE 2 enable */
#define TS_LTYPE1_EN BIT(2) /* Time Sync LTYPE 1 enable */
#define TS_TX_EN BIT(1) /* Time Sync Transmit Enable */
#define TS_RX_EN BIT(0) /* Time Sync Receive Enable */
#define CTRL_V2_TS_BITS \
(TS_320 | TS_319 | TS_132 | TS_131 | TS_130 | TS_129 |\
TS_TTL_NONZERO | TS_ANNEX_D_EN | TS_LTYPE1_EN | VLAN_LTYPE1_EN)
#define CTRL_V2_ALL_TS_MASK (CTRL_V2_TS_BITS | TS_TX_EN | TS_RX_EN)
#define CTRL_V2_TX_TS_BITS (CTRL_V2_TS_BITS | TS_TX_EN)
#define CTRL_V2_RX_TS_BITS (CTRL_V2_TS_BITS | TS_RX_EN)
#define CTRL_V3_TS_BITS \
(TS_107 | TS_320 | TS_319 | TS_132 | TS_131 | TS_130 | TS_129 |\
TS_TTL_NONZERO | TS_ANNEX_F_EN | TS_ANNEX_D_EN |\
TS_LTYPE1_EN | VLAN_LTYPE1_EN)
#define CTRL_V3_ALL_TS_MASK (CTRL_V3_TS_BITS | TS_TX_EN | TS_RX_EN)
#define CTRL_V3_TX_TS_BITS (CTRL_V3_TS_BITS | TS_TX_EN)
#define CTRL_V3_RX_TS_BITS (CTRL_V3_TS_BITS | TS_RX_EN)
/* Bit definitions for the CPSW2_TS_SEQ_MTYPE register */
#define TS_SEQ_ID_OFFSET_SHIFT (16) /* Time Sync Sequence ID Offset */
#define TS_SEQ_ID_OFFSET_MASK (0x3f)
#define TS_MSG_TYPE_EN_SHIFT (0) /* Time Sync Message Type Enable */
#define TS_MSG_TYPE_EN_MASK (0xffff)
/* The PTP event messages - Sync, Delay_Req, Pdelay_Req, and Pdelay_Resp. */
#define EVENT_MSG_BITS ((1<<0) | (1<<1) | (1<<2) | (1<<3))
/* Bit definitions for the CPSW1_TS_CTL register */
#define CPSW_V1_TS_RX_EN BIT(0)
#define CPSW_V1_TS_TX_EN BIT(4)
#define CPSW_V1_MSG_TYPE_OFS 16
/* Bit definitions for the CPSW1_TS_SEQ_LTYPE register */
#define CPSW_V1_SEQ_ID_OFS_SHIFT 16
#define CPSW_MAX_BLKS_TX 15
#define CPSW_MAX_BLKS_TX_SHIFT 4
#define CPSW_MAX_BLKS_RX 5
struct cpsw_host_regs {
u32 max_blks;
u32 blk_cnt;
u32 tx_in_ctl;
u32 port_vlan;
u32 tx_pri_map;
u32 cpdma_tx_pri_map;
u32 cpdma_rx_chan_map;
};
struct cpsw_slave_data {
struct device_node *slave_node;
struct device_node *phy_node;
char phy_id[MII_BUS_ID_SIZE];
phy_interface_t phy_if;
u8 mac_addr[ETH_ALEN];
u16 dual_emac_res_vlan; /* Reserved VLAN for DualEMAC */
struct phy *ifphy;
bool disabled;
};
struct cpsw_platform_data {
struct cpsw_slave_data *slave_data;
u32 ss_reg_ofs; /* Subsystem control register offset */
u32 channels; /* number of cpdma channels (symmetric) */
u32 slaves; /* number of slave cpgmac ports */
u32 active_slave;/* time stamping, ethtool and SIOCGMIIPHY slave */
u32 bd_ram_size; /*buffer descriptor ram size */
u32 mac_control; /* Mac control register */
u16 default_vlan; /* Def VLAN for ALE lookup in VLAN aware mode*/
bool dual_emac; /* Enable Dual EMAC mode */
};
struct cpsw_slave {
void __iomem *regs;
int slave_num;
u32 mac_control;
struct cpsw_slave_data *data;
struct phy_device *phy;
struct net_device *ndev;
u32 port_vlan;
struct cpsw_sl *mac_sl;
};
static inline u32 slave_read(struct cpsw_slave *slave, u32 offset)
{
return readl_relaxed(slave->regs + offset);
}
static inline void slave_write(struct cpsw_slave *slave, u32 val, u32 offset)
{
writel_relaxed(val, slave->regs + offset);
}
struct cpsw_vector {
struct cpdma_chan *ch;
int budget;
};
struct cpsw_common {
struct device *dev;
struct cpsw_platform_data data;
struct napi_struct napi_rx;
struct napi_struct napi_tx;
struct cpsw_ss_regs __iomem *regs;
struct cpsw_wr_regs __iomem *wr_regs;
u8 __iomem *hw_stats;
struct cpsw_host_regs __iomem *host_port_regs;
u32 version;
u32 coal_intvl;
u32 bus_freq_mhz;
int rx_packet_max;
int descs_pool_size;
struct cpsw_slave *slaves;
struct cpdma_ctlr *dma;
struct cpsw_vector txv[CPSW_MAX_QUEUES];
struct cpsw_vector rxv[CPSW_MAX_QUEUES];
struct cpsw_ale *ale;
bool quirk_irq;
bool rx_irq_disabled;
bool tx_irq_disabled;
u32 irqs_table[IRQ_NUM];
int misc_irq;
struct cpts *cpts;
struct devlink *devlink;
int rx_ch_num, tx_ch_num;
int speed;
int usage_count;
struct page_pool *page_pool[CPSW_MAX_QUEUES];
u8 br_members;
struct net_device *hw_bridge_dev;
bool ale_bypass;
u8 base_mac[ETH_ALEN];
};
struct cpsw_ale_ratelimit {
unsigned long cookie;
u64 rate_packet_ps;
};
struct cpsw_priv {
struct net_device *ndev;
struct device *dev;
u32 msg_enable;
u8 mac_addr[ETH_ALEN];
bool rx_pause;
bool tx_pause;
bool mqprio_hw;
int fifo_bw[CPSW_TC_NUM];
int shp_cfg_speed;
int tx_ts_enabled;
int rx_ts_enabled;
struct bpf_prog *xdp_prog;
struct xdp_rxq_info xdp_rxq[CPSW_MAX_QUEUES];
struct xdp_attachment_info xdpi;
u32 emac_port;
struct cpsw_common *cpsw;
int offload_fwd_mark;
u32 tx_packet_min;
struct cpsw_ale_ratelimit ale_bc_ratelimit;
struct cpsw_ale_ratelimit ale_mc_ratelimit;
struct work_struct rx_mode_work;
};
#define ndev_to_cpsw(ndev) (((struct cpsw_priv *)netdev_priv(ndev))->cpsw)
#define napi_to_cpsw(napi) container_of(napi, struct cpsw_common, napi)
extern int (*cpsw_slave_index)(struct cpsw_common *cpsw,
struct cpsw_priv *priv);
struct addr_sync_ctx {
struct net_device *ndev;
const u8 *addr; /* address to be synched */
int consumed; /* number of address instances */
int flush; /* flush flag */
};
#define CPSW_XMETA_OFFSET ALIGN(sizeof(struct xdp_frame), sizeof(long))
#define CPSW_XDP_CONSUMED 1
#define CPSW_XDP_PASS 0
struct __aligned(sizeof(long)) cpsw_meta_xdp {
struct net_device *ndev;
int ch;
};
/* The buf includes headroom compatible with both skb and xdpf */
#define CPSW_HEADROOM_NA (max(XDP_PACKET_HEADROOM, NET_SKB_PAD) + NET_IP_ALIGN)
static inline int cpsw_is_xdpf_handle(void *handle)
{
return (unsigned long)handle & BIT(0);
}
static inline void *cpsw_xdpf_to_handle(struct xdp_frame *xdpf)
{
return (void *)((unsigned long)xdpf | BIT(0));
}
static inline struct xdp_frame *cpsw_handle_to_xdpf(void *handle)
{
return (struct xdp_frame *)((unsigned long)handle & ~BIT(0));
}
int cpsw_init_common(struct cpsw_common *cpsw, void __iomem *ss_regs,
int ale_ageout, phys_addr_t desc_mem_phys,
int descs_pool_size);
void cpsw_split_res(struct cpsw_common *cpsw);
int cpsw_fill_rx_channels(struct cpsw_priv *priv);
void cpsw_intr_enable(struct cpsw_common *cpsw);
void cpsw_intr_disable(struct cpsw_common *cpsw);
void cpsw_tx_handler(void *token, int len, int status);
int cpsw_create_xdp_rxqs(struct cpsw_common *cpsw);
void cpsw_destroy_xdp_rxqs(struct cpsw_common *cpsw);
int cpsw_ndo_bpf(struct net_device *ndev, struct netdev_bpf *bpf);
int cpsw_xdp_tx_frame(struct cpsw_priv *priv, struct xdp_frame *xdpf,
struct page *page, int port);
int cpsw_run_xdp(struct cpsw_priv *priv, int ch, struct xdp_buff *xdp,
struct page *page, int port, int *len);
irqreturn_t cpsw_tx_interrupt(int irq, void *dev_id);
irqreturn_t cpsw_rx_interrupt(int irq, void *dev_id);
irqreturn_t cpsw_misc_interrupt(int irq, void *dev_id);
int cpsw_tx_mq_poll(struct napi_struct *napi_tx, int budget);
int cpsw_tx_poll(struct napi_struct *napi_tx, int budget);
int cpsw_rx_mq_poll(struct napi_struct *napi_rx, int budget);
int cpsw_rx_poll(struct napi_struct *napi_rx, int budget);
void cpsw_rx_vlan_encap(struct sk_buff *skb);
void cpsw_soft_reset(const char *module, void __iomem *reg);
void cpsw_set_slave_mac(struct cpsw_slave *slave, struct cpsw_priv *priv);
void cpsw_ndo_tx_timeout(struct net_device *ndev, unsigned int txqueue);
int cpsw_need_resplit(struct cpsw_common *cpsw);
int cpsw_ndo_set_tx_maxrate(struct net_device *ndev, int queue, u32 rate);
int cpsw_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
void *type_data);
bool cpsw_shp_is_off(struct cpsw_priv *priv);
void cpsw_cbs_resume(struct cpsw_slave *slave, struct cpsw_priv *priv);
void cpsw_mqprio_resume(struct cpsw_slave *slave, struct cpsw_priv *priv);
void cpsw_qos_clsflower_resume(struct cpsw_priv *priv);
int cpsw_hwtstamp_get(struct net_device *dev,
struct kernel_hwtstamp_config *cfg);
int cpsw_hwtstamp_set(struct net_device *dev,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack);
/* ethtool */
u32 cpsw_get_msglevel(struct net_device *ndev);
void cpsw_set_msglevel(struct net_device *ndev, u32 value);
int cpsw_get_coalesce(struct net_device *ndev, struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack);
int cpsw_set_coalesce(struct net_device *ndev, struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack);
int cpsw_get_sset_count(struct net_device *ndev, int sset);
void cpsw_get_strings(struct net_device *ndev, u32 stringset, u8 *data);
void cpsw_get_ethtool_stats(struct net_device *ndev,
struct ethtool_stats *stats, u64 *data);
void cpsw_get_pauseparam(struct net_device *ndev,
struct ethtool_pauseparam *pause);
void cpsw_get_wol(struct net_device *ndev, struct ethtool_wolinfo *wol);
int cpsw_set_wol(struct net_device *ndev, struct ethtool_wolinfo *wol);
int cpsw_get_regs_len(struct net_device *ndev);
void cpsw_get_regs(struct net_device *ndev, struct ethtool_regs *regs, void *p);
int cpsw_ethtool_op_begin(struct net_device *ndev);
void cpsw_ethtool_op_complete(struct net_device *ndev);
void cpsw_get_channels(struct net_device *ndev, struct ethtool_channels *ch);
int cpsw_get_link_ksettings(struct net_device *ndev,
struct ethtool_link_ksettings *ecmd);
int cpsw_set_link_ksettings(struct net_device *ndev,
const struct ethtool_link_ksettings *ecmd);
int cpsw_get_eee(struct net_device *ndev, struct ethtool_keee *edata);
int cpsw_nway_reset(struct net_device *ndev);
void cpsw_get_ringparam(struct net_device *ndev,
struct ethtool_ringparam *ering,
struct kernel_ethtool_ringparam *kernel_ering,
struct netlink_ext_ack *extack);
int cpsw_set_ringparam(struct net_device *ndev,
struct ethtool_ringparam *ering,
struct kernel_ethtool_ringparam *kernel_ering,
struct netlink_ext_ack *extack);
int cpsw_set_channels_common(struct net_device *ndev,
struct ethtool_channels *chs,
cpdma_handler_fn rx_handler);
int cpsw_get_ts_info(struct net_device *ndev, struct kernel_ethtool_ts_info *info);
#endif /* DRIVERS_NET_ETHERNET_TI_CPSW_PRIV_H_ */
@@ -0,0 +1,339 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Texas Instruments Ethernet Switch media-access-controller (MAC) submodule/
* Ethernet MAC Sliver (CPGMAC_SL)
*
* Copyright (C) 2019 Texas Instruments
*
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include "cpsw_sl.h"
#define CPSW_SL_REG_NOTUSED U16_MAX
static const u16 cpsw_sl_reg_map_cpsw[] = {
[CPSW_SL_IDVER] = 0x00,
[CPSW_SL_MACCONTROL] = 0x04,
[CPSW_SL_MACSTATUS] = 0x08,
[CPSW_SL_SOFT_RESET] = 0x0c,
[CPSW_SL_RX_MAXLEN] = 0x10,
[CPSW_SL_BOFFTEST] = 0x14,
[CPSW_SL_RX_PAUSE] = 0x18,
[CPSW_SL_TX_PAUSE] = 0x1c,
[CPSW_SL_EMCONTROL] = 0x20,
[CPSW_SL_RX_PRI_MAP] = 0x24,
[CPSW_SL_TX_GAP] = 0x28,
};
static const u16 cpsw_sl_reg_map_66ak2hk[] = {
[CPSW_SL_IDVER] = 0x00,
[CPSW_SL_MACCONTROL] = 0x04,
[CPSW_SL_MACSTATUS] = 0x08,
[CPSW_SL_SOFT_RESET] = 0x0c,
[CPSW_SL_RX_MAXLEN] = 0x10,
[CPSW_SL_BOFFTEST] = CPSW_SL_REG_NOTUSED,
[CPSW_SL_RX_PAUSE] = 0x18,
[CPSW_SL_TX_PAUSE] = 0x1c,
[CPSW_SL_EMCONTROL] = 0x20,
[CPSW_SL_RX_PRI_MAP] = 0x24,
[CPSW_SL_TX_GAP] = CPSW_SL_REG_NOTUSED,
};
static const u16 cpsw_sl_reg_map_66ak2x_xgbe[] = {
[CPSW_SL_IDVER] = 0x00,
[CPSW_SL_MACCONTROL] = 0x04,
[CPSW_SL_MACSTATUS] = 0x08,
[CPSW_SL_SOFT_RESET] = 0x0c,
[CPSW_SL_RX_MAXLEN] = 0x10,
[CPSW_SL_BOFFTEST] = CPSW_SL_REG_NOTUSED,
[CPSW_SL_RX_PAUSE] = 0x18,
[CPSW_SL_TX_PAUSE] = 0x1c,
[CPSW_SL_EMCONTROL] = 0x20,
[CPSW_SL_RX_PRI_MAP] = CPSW_SL_REG_NOTUSED,
[CPSW_SL_TX_GAP] = 0x28,
};
static const u16 cpsw_sl_reg_map_66ak2elg_am65[] = {
[CPSW_SL_IDVER] = CPSW_SL_REG_NOTUSED,
[CPSW_SL_MACCONTROL] = 0x00,
[CPSW_SL_MACSTATUS] = 0x04,
[CPSW_SL_SOFT_RESET] = 0x08,
[CPSW_SL_RX_MAXLEN] = CPSW_SL_REG_NOTUSED,
[CPSW_SL_BOFFTEST] = 0x0c,
[CPSW_SL_RX_PAUSE] = 0x10,
[CPSW_SL_TX_PAUSE] = 0x40,
[CPSW_SL_EMCONTROL] = 0x70,
[CPSW_SL_RX_PRI_MAP] = CPSW_SL_REG_NOTUSED,
[CPSW_SL_TX_GAP] = 0x74,
};
#define CPSW_SL_SOFT_RESET_BIT BIT(0)
#define CPSW_SL_STATUS_PN_IDLE BIT(31)
#define CPSW_SL_AM65_STATUS_PN_E_IDLE BIT(30)
#define CPSW_SL_AM65_STATUS_PN_P_IDLE BIT(29)
#define CPSW_SL_AM65_STATUS_PN_TX_IDLE BIT(28)
#define CPSW_SL_STATUS_IDLE_MASK_BASE (CPSW_SL_STATUS_PN_IDLE)
#define CPSW_SL_STATUS_IDLE_MASK_K3 \
(CPSW_SL_STATUS_IDLE_MASK_BASE | CPSW_SL_AM65_STATUS_PN_E_IDLE | \
CPSW_SL_AM65_STATUS_PN_P_IDLE | CPSW_SL_AM65_STATUS_PN_TX_IDLE)
#define CPSW_SL_CTL_FUNC_BASE \
(CPSW_SL_CTL_FULLDUPLEX |\
CPSW_SL_CTL_LOOPBACK |\
CPSW_SL_CTL_RX_FLOW_EN |\
CPSW_SL_CTL_TX_FLOW_EN |\
CPSW_SL_CTL_GMII_EN |\
CPSW_SL_CTL_TX_PACE |\
CPSW_SL_CTL_GIG |\
CPSW_SL_CTL_CMD_IDLE |\
CPSW_SL_CTL_IFCTL_A |\
CPSW_SL_CTL_IFCTL_B |\
CPSW_SL_CTL_GIG_FORCE |\
CPSW_SL_CTL_EXT_EN |\
CPSW_SL_CTL_RX_CEF_EN |\
CPSW_SL_CTL_RX_CSF_EN |\
CPSW_SL_CTL_RX_CMF_EN)
struct cpsw_sl {
struct device *dev;
void __iomem *sl_base;
const u16 *regs;
u32 control_features;
u32 idle_mask;
};
struct cpsw_sl_dev_id {
const char *device_id;
const u16 *regs;
const u32 control_features;
const u32 regs_offset;
const u32 idle_mask;
};
static const struct cpsw_sl_dev_id cpsw_sl_id_match[] = {
{
.device_id = "cpsw",
.regs = cpsw_sl_reg_map_cpsw,
.control_features = CPSW_SL_CTL_FUNC_BASE |
CPSW_SL_CTL_MTEST |
CPSW_SL_CTL_TX_SHORT_GAP_EN |
CPSW_SL_CTL_TX_SG_LIM_EN,
.idle_mask = CPSW_SL_STATUS_IDLE_MASK_BASE,
},
{
.device_id = "66ak2hk",
.regs = cpsw_sl_reg_map_66ak2hk,
.control_features = CPSW_SL_CTL_FUNC_BASE |
CPSW_SL_CTL_TX_SHORT_GAP_EN,
.idle_mask = CPSW_SL_STATUS_IDLE_MASK_BASE,
},
{
.device_id = "66ak2x_xgbe",
.regs = cpsw_sl_reg_map_66ak2x_xgbe,
.control_features = CPSW_SL_CTL_FUNC_BASE |
CPSW_SL_CTL_XGIG |
CPSW_SL_CTL_TX_SHORT_GAP_EN |
CPSW_SL_CTL_CRC_TYPE |
CPSW_SL_CTL_XGMII_EN,
.idle_mask = CPSW_SL_STATUS_IDLE_MASK_BASE,
},
{
.device_id = "66ak2el",
.regs = cpsw_sl_reg_map_66ak2elg_am65,
.regs_offset = 0x330,
.control_features = CPSW_SL_CTL_FUNC_BASE |
CPSW_SL_CTL_MTEST |
CPSW_SL_CTL_TX_SHORT_GAP_EN |
CPSW_SL_CTL_CRC_TYPE |
CPSW_SL_CTL_EXT_EN_RX_FLO |
CPSW_SL_CTL_EXT_EN_TX_FLO |
CPSW_SL_CTL_TX_SG_LIM_EN,
.idle_mask = CPSW_SL_STATUS_IDLE_MASK_BASE,
},
{
.device_id = "66ak2g",
.regs = cpsw_sl_reg_map_66ak2elg_am65,
.regs_offset = 0x330,
.control_features = CPSW_SL_CTL_FUNC_BASE |
CPSW_SL_CTL_MTEST |
CPSW_SL_CTL_CRC_TYPE |
CPSW_SL_CTL_EXT_EN_RX_FLO |
CPSW_SL_CTL_EXT_EN_TX_FLO,
},
{
.device_id = "am65",
.regs = cpsw_sl_reg_map_66ak2elg_am65,
.regs_offset = 0x330,
.control_features = CPSW_SL_CTL_FUNC_BASE |
CPSW_SL_CTL_MTEST |
CPSW_SL_CTL_XGIG |
CPSW_SL_CTL_TX_SHORT_GAP_EN |
CPSW_SL_CTL_CRC_TYPE |
CPSW_SL_CTL_XGMII_EN |
CPSW_SL_CTL_EXT_EN_RX_FLO |
CPSW_SL_CTL_EXT_EN_TX_FLO |
CPSW_SL_CTL_TX_SG_LIM_EN |
CPSW_SL_CTL_EXT_EN_XGIG,
.idle_mask = CPSW_SL_STATUS_IDLE_MASK_K3,
},
{ },
};
u32 cpsw_sl_reg_read(struct cpsw_sl *sl, enum cpsw_sl_regs reg)
{
int val;
if (sl->regs[reg] == CPSW_SL_REG_NOTUSED) {
dev_err(sl->dev, "cpsw_sl: not sup r reg: %04X\n",
sl->regs[reg]);
return 0;
}
val = readl(sl->sl_base + sl->regs[reg]);
dev_dbg(sl->dev, "cpsw_sl: reg: %04X r 0x%08X\n", sl->regs[reg], val);
return val;
}
EXPORT_SYMBOL_GPL(cpsw_sl_reg_read);
void cpsw_sl_reg_write(struct cpsw_sl *sl, enum cpsw_sl_regs reg, u32 val)
{
if (sl->regs[reg] == CPSW_SL_REG_NOTUSED) {
dev_err(sl->dev, "cpsw_sl: not sup w reg: %04X\n",
sl->regs[reg]);
return;
}
dev_dbg(sl->dev, "cpsw_sl: reg: %04X w 0x%08X\n", sl->regs[reg], val);
writel(val, sl->sl_base + sl->regs[reg]);
}
EXPORT_SYMBOL_GPL(cpsw_sl_reg_write);
static const struct cpsw_sl_dev_id *cpsw_sl_match_id(
const struct cpsw_sl_dev_id *id,
const char *device_id)
{
if (!id || !device_id)
return NULL;
while (id->device_id) {
if (strcmp(device_id, id->device_id) == 0)
return id;
id++;
}
return NULL;
}
struct cpsw_sl *cpsw_sl_get(const char *device_id, struct device *dev,
void __iomem *sl_base)
{
const struct cpsw_sl_dev_id *sl_dev_id;
struct cpsw_sl *sl;
sl = devm_kzalloc(dev, sizeof(struct cpsw_sl), GFP_KERNEL);
if (!sl)
return ERR_PTR(-ENOMEM);
sl->dev = dev;
sl->sl_base = sl_base;
sl_dev_id = cpsw_sl_match_id(cpsw_sl_id_match, device_id);
if (!sl_dev_id) {
dev_err(sl->dev, "cpsw_sl: dev_id %s not found.\n", device_id);
return ERR_PTR(-EINVAL);
}
sl->regs = sl_dev_id->regs;
sl->control_features = sl_dev_id->control_features;
sl->idle_mask = sl_dev_id->idle_mask;
sl->sl_base += sl_dev_id->regs_offset;
return sl;
}
EXPORT_SYMBOL_GPL(cpsw_sl_get);
void cpsw_sl_reset(struct cpsw_sl *sl, unsigned long tmo)
{
unsigned long timeout = jiffies + msecs_to_jiffies(tmo);
/* Set the soft reset bit */
cpsw_sl_reg_write(sl, CPSW_SL_SOFT_RESET, CPSW_SL_SOFT_RESET_BIT);
/* Wait for the bit to clear */
do {
usleep_range(100, 200);
} while ((cpsw_sl_reg_read(sl, CPSW_SL_SOFT_RESET) &
CPSW_SL_SOFT_RESET_BIT) &&
time_after(timeout, jiffies));
if (cpsw_sl_reg_read(sl, CPSW_SL_SOFT_RESET) & CPSW_SL_SOFT_RESET_BIT)
dev_err(sl->dev, "cpsw_sl failed to soft-reset.\n");
}
EXPORT_SYMBOL_GPL(cpsw_sl_reset);
u32 cpsw_sl_ctl_set(struct cpsw_sl *sl, u32 ctl_funcs)
{
u32 val;
if (ctl_funcs & ~sl->control_features) {
dev_err(sl->dev, "cpsw_sl: unsupported func 0x%08X\n",
ctl_funcs & (~sl->control_features));
return -EINVAL;
}
val = cpsw_sl_reg_read(sl, CPSW_SL_MACCONTROL);
val |= ctl_funcs;
cpsw_sl_reg_write(sl, CPSW_SL_MACCONTROL, val);
return 0;
}
EXPORT_SYMBOL_GPL(cpsw_sl_ctl_set);
u32 cpsw_sl_ctl_clr(struct cpsw_sl *sl, u32 ctl_funcs)
{
u32 val;
if (ctl_funcs & ~sl->control_features) {
dev_err(sl->dev, "cpsw_sl: unsupported func 0x%08X\n",
ctl_funcs & (~sl->control_features));
return -EINVAL;
}
val = cpsw_sl_reg_read(sl, CPSW_SL_MACCONTROL);
val &= ~ctl_funcs;
cpsw_sl_reg_write(sl, CPSW_SL_MACCONTROL, val);
return 0;
}
EXPORT_SYMBOL_GPL(cpsw_sl_ctl_clr);
void cpsw_sl_ctl_reset(struct cpsw_sl *sl)
{
cpsw_sl_reg_write(sl, CPSW_SL_MACCONTROL, 0);
}
EXPORT_SYMBOL_GPL(cpsw_sl_ctl_reset);
int cpsw_sl_wait_for_idle(struct cpsw_sl *sl, unsigned long tmo)
{
unsigned long timeout = jiffies + msecs_to_jiffies(tmo);
do {
usleep_range(100, 200);
} while (!(cpsw_sl_reg_read(sl, CPSW_SL_MACSTATUS) &
sl->idle_mask) && time_after(timeout, jiffies));
if (!(cpsw_sl_reg_read(sl, CPSW_SL_MACSTATUS) & sl->idle_mask)) {
dev_err(sl->dev, "cpsw_sl failed to soft-reset.\n");
return -ETIMEDOUT;
}
return 0;
}
EXPORT_SYMBOL_GPL(cpsw_sl_wait_for_idle);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TI Ethernet Switch media-access-controller (MAC) submodule");
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Texas Instruments Ethernet Switch media-access-controller (MAC) submodule/
* Ethernet MAC Sliver (CPGMAC_SL) APIs
*
* Copyright (C) 2019 Texas Instruments
*
*/
#ifndef __TI_CPSW_SL_H__
#define __TI_CPSW_SL_H__
#include <linux/device.h>
enum cpsw_sl_regs {
CPSW_SL_IDVER,
CPSW_SL_MACCONTROL,
CPSW_SL_MACSTATUS,
CPSW_SL_SOFT_RESET,
CPSW_SL_RX_MAXLEN,
CPSW_SL_BOFFTEST,
CPSW_SL_RX_PAUSE,
CPSW_SL_TX_PAUSE,
CPSW_SL_EMCONTROL,
CPSW_SL_RX_PRI_MAP,
CPSW_SL_TX_GAP,
};
enum {
CPSW_SL_CTL_FULLDUPLEX = BIT(0), /* Full Duplex mode */
CPSW_SL_CTL_LOOPBACK = BIT(1), /* Loop Back Mode */
CPSW_SL_CTL_MTEST = BIT(2), /* Manufacturing Test mode */
CPSW_SL_CTL_RX_FLOW_EN = BIT(3), /* Receive Flow Control Enable */
CPSW_SL_CTL_TX_FLOW_EN = BIT(4), /* Transmit Flow Control Enable */
CPSW_SL_CTL_GMII_EN = BIT(5), /* GMII Enable */
CPSW_SL_CTL_TX_PACE = BIT(6), /* Transmit Pacing Enable */
CPSW_SL_CTL_GIG = BIT(7), /* Gigabit Mode */
CPSW_SL_CTL_XGIG = BIT(8), /* 10 Gigabit Mode */
CPSW_SL_CTL_TX_SHORT_GAP_EN = BIT(10), /* Transmit Short Gap Enable */
CPSW_SL_CTL_CMD_IDLE = BIT(11), /* Command Idle */
CPSW_SL_CTL_CRC_TYPE = BIT(12), /* Port CRC Type */
CPSW_SL_CTL_XGMII_EN = BIT(13), /* XGMII Enable */
CPSW_SL_CTL_IFCTL_A = BIT(15), /* Interface Control A */
CPSW_SL_CTL_IFCTL_B = BIT(16), /* Interface Control B */
CPSW_SL_CTL_GIG_FORCE = BIT(17), /* Gigabit Mode Force */
CPSW_SL_CTL_EXT_EN = BIT(18), /* External Control Enable */
CPSW_SL_CTL_EXT_EN_RX_FLO = BIT(19), /* Ext RX Flow Control Enable */
CPSW_SL_CTL_EXT_EN_TX_FLO = BIT(20), /* Ext TX Flow Control Enable */
CPSW_SL_CTL_TX_SG_LIM_EN = BIT(21), /* TXt Short Gap Limit Enable */
CPSW_SL_CTL_RX_CEF_EN = BIT(22), /* RX Copy Error Frames Enable */
CPSW_SL_CTL_RX_CSF_EN = BIT(23), /* RX Copy Short Frames Enable */
CPSW_SL_CTL_RX_CMF_EN = BIT(24), /* RX Copy MAC Control Frames Enable */
CPSW_SL_CTL_EXT_EN_XGIG = BIT(25), /* Ext XGIG Control En, k3 only */
CPSW_SL_CTL_FUNCS_COUNT
};
struct cpsw_sl;
struct cpsw_sl *cpsw_sl_get(const char *device_id, struct device *dev,
void __iomem *sl_base);
void cpsw_sl_reset(struct cpsw_sl *sl, unsigned long tmo);
u32 cpsw_sl_ctl_set(struct cpsw_sl *sl, u32 ctl_funcs);
u32 cpsw_sl_ctl_clr(struct cpsw_sl *sl, u32 ctl_funcs);
void cpsw_sl_ctl_reset(struct cpsw_sl *sl);
int cpsw_sl_wait_for_idle(struct cpsw_sl *sl, unsigned long tmo);
u32 cpsw_sl_reg_read(struct cpsw_sl *sl, enum cpsw_sl_regs reg);
void cpsw_sl_reg_write(struct cpsw_sl *sl, enum cpsw_sl_regs reg, u32 val);
#endif /* __TI_CPSW_SL_H__ */
@@ -0,0 +1,544 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Texas Instruments switchdev Driver
*
* Copyright (C) 2019 Texas Instruments
*
*/
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/netdevice.h>
#include <linux/workqueue.h>
#include <net/switchdev.h>
#include "cpsw.h"
#include "cpsw_ale.h"
#include "cpsw_priv.h"
#include "cpsw_switchdev.h"
struct cpsw_switchdev_event_work {
struct work_struct work;
struct switchdev_notifier_fdb_info fdb_info;
struct cpsw_priv *priv;
unsigned long event;
};
static int cpsw_port_stp_state_set(struct cpsw_priv *priv, u8 state)
{
struct cpsw_common *cpsw = priv->cpsw;
u8 cpsw_state;
int ret = 0;
switch (state) {
case BR_STATE_FORWARDING:
cpsw_state = ALE_PORT_STATE_FORWARD;
break;
case BR_STATE_LEARNING:
cpsw_state = ALE_PORT_STATE_LEARN;
break;
case BR_STATE_DISABLED:
cpsw_state = ALE_PORT_STATE_DISABLE;
break;
case BR_STATE_LISTENING:
case BR_STATE_BLOCKING:
cpsw_state = ALE_PORT_STATE_BLOCK;
break;
default:
return -EOPNOTSUPP;
}
ret = cpsw_ale_control_set(cpsw->ale, priv->emac_port,
ALE_PORT_STATE, cpsw_state);
dev_dbg(priv->dev, "ale state: %u\n", cpsw_state);
return ret;
}
static int cpsw_port_attr_br_flags_set(struct cpsw_priv *priv,
struct net_device *orig_dev,
struct switchdev_brport_flags flags)
{
struct cpsw_common *cpsw = priv->cpsw;
if (flags.mask & BR_MCAST_FLOOD) {
bool unreg_mcast_add = false;
if (flags.val & BR_MCAST_FLOOD)
unreg_mcast_add = true;
dev_dbg(priv->dev, "BR_MCAST_FLOOD: %d port %u\n",
unreg_mcast_add, priv->emac_port);
cpsw_ale_set_unreg_mcast(cpsw->ale, BIT(priv->emac_port),
unreg_mcast_add);
}
return 0;
}
static int cpsw_port_attr_br_flags_pre_set(struct net_device *netdev,
struct switchdev_brport_flags flags)
{
if (flags.mask & ~(BR_LEARNING | BR_MCAST_FLOOD))
return -EINVAL;
return 0;
}
static int cpsw_port_attr_set(struct net_device *ndev, const void *ctx,
const struct switchdev_attr *attr,
struct netlink_ext_ack *extack)
{
struct cpsw_priv *priv = netdev_priv(ndev);
int ret;
dev_dbg(priv->dev, "attr: id %u port: %u\n", attr->id, priv->emac_port);
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
ret = cpsw_port_attr_br_flags_pre_set(ndev,
attr->u.brport_flags);
break;
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
ret = cpsw_port_stp_state_set(priv, attr->u.stp_state);
dev_dbg(priv->dev, "stp state: %u\n", attr->u.stp_state);
break;
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
ret = cpsw_port_attr_br_flags_set(priv, attr->orig_dev,
attr->u.brport_flags);
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static u16 cpsw_get_pvid(struct cpsw_priv *priv)
{
struct cpsw_common *cpsw = priv->cpsw;
u32 __iomem *port_vlan_reg;
u32 pvid;
if (priv->emac_port) {
int reg = CPSW2_PORT_VLAN;
if (cpsw->version == CPSW_VERSION_1)
reg = CPSW1_PORT_VLAN;
pvid = slave_read(cpsw->slaves + (priv->emac_port - 1), reg);
} else {
port_vlan_reg = &cpsw->host_port_regs->port_vlan;
pvid = readl(port_vlan_reg);
}
pvid = pvid & 0xfff;
return pvid;
}
static void cpsw_set_pvid(struct cpsw_priv *priv, u16 vid, bool cfi, u32 cos)
{
struct cpsw_common *cpsw = priv->cpsw;
void __iomem *port_vlan_reg;
u32 pvid;
pvid = vid;
pvid |= cfi ? BIT(12) : 0;
pvid |= (cos & 0x7) << 13;
if (priv->emac_port) {
int reg = CPSW2_PORT_VLAN;
if (cpsw->version == CPSW_VERSION_1)
reg = CPSW1_PORT_VLAN;
/* no barrier */
slave_write(cpsw->slaves + (priv->emac_port - 1), pvid, reg);
} else {
/* CPU port */
port_vlan_reg = &cpsw->host_port_regs->port_vlan;
writel(pvid, port_vlan_reg);
}
}
static int cpsw_port_vlan_add(struct cpsw_priv *priv, bool untag, bool pvid,
u16 vid, struct net_device *orig_dev)
{
bool cpu_port = netif_is_bridge_master(orig_dev);
struct cpsw_common *cpsw = priv->cpsw;
int unreg_mcast_mask = 0;
int reg_mcast_mask = 0;
int untag_mask = 0;
int port_mask;
int ret = 0;
u32 flags;
if (cpu_port) {
port_mask = BIT(HOST_PORT_NUM);
flags = orig_dev->flags;
unreg_mcast_mask = port_mask;
} else {
port_mask = BIT(priv->emac_port);
flags = priv->ndev->flags;
}
if (flags & IFF_MULTICAST)
reg_mcast_mask = port_mask;
if (untag)
untag_mask = port_mask;
ret = cpsw_ale_vlan_add_modify(cpsw->ale, vid, port_mask, untag_mask,
reg_mcast_mask, unreg_mcast_mask);
if (ret) {
dev_err(priv->dev, "Unable to add vlan\n");
return ret;
}
if (cpu_port)
cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr,
HOST_PORT_NUM, ALE_VLAN, vid);
if (!pvid)
return ret;
cpsw_set_pvid(priv, vid, 0, 0);
dev_dbg(priv->dev, "VID add: %s: vid:%u ports:%X\n",
priv->ndev->name, vid, port_mask);
return ret;
}
static int cpsw_port_vlan_del(struct cpsw_priv *priv, u16 vid,
struct net_device *orig_dev)
{
bool cpu_port = netif_is_bridge_master(orig_dev);
struct cpsw_common *cpsw = priv->cpsw;
int port_mask;
int ret = 0;
if (cpu_port)
port_mask = BIT(HOST_PORT_NUM);
else
port_mask = BIT(priv->emac_port);
ret = cpsw_ale_vlan_del_modify(cpsw->ale, vid, port_mask);
if (ret != 0)
return ret;
/* We don't care for the return value here, error is returned only if
* the unicast entry is not present
*/
if (cpu_port)
cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr,
HOST_PORT_NUM, ALE_VLAN, vid);
if (vid == cpsw_get_pvid(priv))
cpsw_set_pvid(priv, 0, 0, 0);
/* We don't care for the return value here, error is returned only if
* the multicast entry is not present
*/
cpsw_ale_del_mcast(cpsw->ale, priv->ndev->broadcast,
port_mask, ALE_VLAN, vid);
dev_dbg(priv->dev, "VID del: %s: vid:%u ports:%X\n",
priv->ndev->name, vid, port_mask);
return ret;
}
static int cpsw_port_vlans_add(struct cpsw_priv *priv,
const struct switchdev_obj_port_vlan *vlan)
{
bool untag = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
struct net_device *orig_dev = vlan->obj.orig_dev;
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
dev_dbg(priv->dev, "VID add: %s: vid:%u flags:%X\n",
priv->ndev->name, vlan->vid, vlan->flags);
return cpsw_port_vlan_add(priv, untag, pvid, vlan->vid, orig_dev);
}
static int cpsw_port_mdb_add(struct cpsw_priv *priv,
struct switchdev_obj_port_mdb *mdb)
{
struct net_device *orig_dev = mdb->obj.orig_dev;
bool cpu_port = netif_is_bridge_master(orig_dev);
struct cpsw_common *cpsw = priv->cpsw;
int port_mask;
int err;
if (cpu_port)
port_mask = BIT(HOST_PORT_NUM);
else
port_mask = BIT(priv->emac_port);
err = cpsw_ale_add_mcast(cpsw->ale, mdb->addr, port_mask,
ALE_VLAN, mdb->vid, 0);
dev_dbg(priv->dev, "MDB add: %s: vid %u:%pM ports: %X\n",
priv->ndev->name, mdb->vid, mdb->addr, port_mask);
return err;
}
static int cpsw_port_mdb_del(struct cpsw_priv *priv,
struct switchdev_obj_port_mdb *mdb)
{
struct net_device *orig_dev = mdb->obj.orig_dev;
bool cpu_port = netif_is_bridge_master(orig_dev);
struct cpsw_common *cpsw = priv->cpsw;
int del_mask;
int err;
if (cpu_port)
del_mask = BIT(HOST_PORT_NUM);
else
del_mask = BIT(priv->emac_port);
err = cpsw_ale_del_mcast(cpsw->ale, mdb->addr, del_mask,
ALE_VLAN, mdb->vid);
dev_dbg(priv->dev, "MDB del: %s: vid %u:%pM ports: %X\n",
priv->ndev->name, mdb->vid, mdb->addr, del_mask);
return err;
}
static int cpsw_port_obj_add(struct net_device *ndev, const void *ctx,
const struct switchdev_obj *obj,
struct netlink_ext_ack *extack)
{
struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
struct cpsw_priv *priv = netdev_priv(ndev);
int err = 0;
dev_dbg(priv->dev, "obj_add: id %u port: %u\n",
obj->id, priv->emac_port);
switch (obj->id) {
case SWITCHDEV_OBJ_ID_PORT_VLAN:
err = cpsw_port_vlans_add(priv, vlan);
break;
case SWITCHDEV_OBJ_ID_PORT_MDB:
case SWITCHDEV_OBJ_ID_HOST_MDB:
err = cpsw_port_mdb_add(priv, mdb);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
static int cpsw_port_obj_del(struct net_device *ndev, const void *ctx,
const struct switchdev_obj *obj)
{
struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
struct cpsw_priv *priv = netdev_priv(ndev);
int err = 0;
dev_dbg(priv->dev, "obj_del: id %u port: %u\n",
obj->id, priv->emac_port);
switch (obj->id) {
case SWITCHDEV_OBJ_ID_PORT_VLAN:
err = cpsw_port_vlan_del(priv, vlan->vid, vlan->obj.orig_dev);
break;
case SWITCHDEV_OBJ_ID_PORT_MDB:
case SWITCHDEV_OBJ_ID_HOST_MDB:
err = cpsw_port_mdb_del(priv, mdb);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
static void cpsw_fdb_offload_notify(struct net_device *ndev,
struct switchdev_notifier_fdb_info *rcv)
{
struct switchdev_notifier_fdb_info info = {};
info.addr = rcv->addr;
info.vid = rcv->vid;
info.offloaded = true;
call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
ndev, &info.info, NULL);
}
static void cpsw_switchdev_event_work(struct work_struct *work)
{
struct cpsw_switchdev_event_work *switchdev_work =
container_of(work, struct cpsw_switchdev_event_work, work);
struct cpsw_priv *priv = switchdev_work->priv;
struct switchdev_notifier_fdb_info *fdb;
struct cpsw_common *cpsw = priv->cpsw;
int port = priv->emac_port;
rtnl_lock();
switch (switchdev_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
fdb = &switchdev_work->fdb_info;
dev_dbg(cpsw->dev, "cpsw_fdb_add: MACID = %pM vid = %u flags = %u %u -- port %d\n",
fdb->addr, fdb->vid, fdb->added_by_user,
fdb->offloaded, port);
if (!fdb->added_by_user || fdb->is_local)
break;
if (memcmp(priv->mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
port = HOST_PORT_NUM;
cpsw_ale_add_ucast(cpsw->ale, (u8 *)fdb->addr, port,
fdb->vid ? ALE_VLAN : 0, fdb->vid);
cpsw_fdb_offload_notify(priv->ndev, fdb);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb = &switchdev_work->fdb_info;
dev_dbg(cpsw->dev, "cpsw_fdb_del: MACID = %pM vid = %u flags = %u %u -- port %d\n",
fdb->addr, fdb->vid, fdb->added_by_user,
fdb->offloaded, port);
if (!fdb->added_by_user || fdb->is_local)
break;
if (memcmp(priv->mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
port = HOST_PORT_NUM;
cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port,
fdb->vid ? ALE_VLAN : 0, fdb->vid);
break;
default:
break;
}
rtnl_unlock();
kfree(switchdev_work->fdb_info.addr);
kfree(switchdev_work);
dev_put(priv->ndev);
}
/* called under rcu_read_lock() */
static int cpsw_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
struct switchdev_notifier_fdb_info *fdb_info = ptr;
struct cpsw_switchdev_event_work *switchdev_work;
struct cpsw_priv *priv = netdev_priv(ndev);
int err;
if (event == SWITCHDEV_PORT_ATTR_SET) {
err = switchdev_handle_port_attr_set(ndev, ptr,
cpsw_port_dev_check,
cpsw_port_attr_set);
return notifier_from_errno(err);
}
if (!cpsw_port_dev_check(ndev))
return NOTIFY_DONE;
switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC);
if (WARN_ON(!switchdev_work))
return NOTIFY_BAD;
INIT_WORK(&switchdev_work->work, cpsw_switchdev_event_work);
switchdev_work->priv = priv;
switchdev_work->event = event;
switch (event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
case SWITCHDEV_FDB_DEL_TO_DEVICE:
memcpy(&switchdev_work->fdb_info, ptr,
sizeof(switchdev_work->fdb_info));
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!switchdev_work->fdb_info.addr)
goto err_addr_alloc;
ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
fdb_info->addr);
dev_hold(ndev);
break;
default:
kfree(switchdev_work);
return NOTIFY_DONE;
}
queue_work(system_long_wq, &switchdev_work->work);
return NOTIFY_DONE;
err_addr_alloc:
kfree(switchdev_work);
return NOTIFY_BAD;
}
static struct notifier_block cpsw_switchdev_notifier = {
.notifier_call = cpsw_switchdev_event,
};
static int cpsw_switchdev_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
int err;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = switchdev_handle_port_obj_add(dev, ptr,
cpsw_port_dev_check,
cpsw_port_obj_add);
return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL:
err = switchdev_handle_port_obj_del(dev, ptr,
cpsw_port_dev_check,
cpsw_port_obj_del);
return notifier_from_errno(err);
case SWITCHDEV_PORT_ATTR_SET:
err = switchdev_handle_port_attr_set(dev, ptr,
cpsw_port_dev_check,
cpsw_port_attr_set);
return notifier_from_errno(err);
default:
break;
}
return NOTIFY_DONE;
}
static struct notifier_block cpsw_switchdev_bl_notifier = {
.notifier_call = cpsw_switchdev_blocking_event,
};
int cpsw_switchdev_register_notifiers(struct cpsw_common *cpsw)
{
int ret = 0;
ret = register_switchdev_notifier(&cpsw_switchdev_notifier);
if (ret) {
dev_err(cpsw->dev, "register switchdev notifier fail ret:%d\n",
ret);
return ret;
}
ret = register_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
if (ret) {
dev_err(cpsw->dev, "register switchdev blocking notifier ret:%d\n",
ret);
unregister_switchdev_notifier(&cpsw_switchdev_notifier);
}
return ret;
}
void cpsw_switchdev_unregister_notifiers(struct cpsw_common *cpsw)
{
unregister_switchdev_blocking_notifier(&cpsw_switchdev_bl_notifier);
unregister_switchdev_notifier(&cpsw_switchdev_notifier);
}
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Texas Instruments Ethernet Switch Driver
*/
#ifndef DRIVERS_NET_ETHERNET_TI_CPSW_SWITCHDEV_H_
#define DRIVERS_NET_ETHERNET_TI_CPSW_SWITCHDEV_H_
#include <net/switchdev.h>
bool cpsw_port_dev_check(const struct net_device *dev);
int cpsw_switchdev_register_notifiers(struct cpsw_common *cpsw);
void cpsw_switchdev_unregister_notifiers(struct cpsw_common *cpsw);
#endif /* DRIVERS_NET_ETHERNET_TI_CPSW_SWITCHDEV_H_ */
+814
View File
@@ -0,0 +1,814 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* TI Common Platform Time Sync
*
* Copyright (C) 2012 Richard Cochran <richardcochran@gmail.com>
*
*/
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/if.h>
#include <linux/hrtimer.h>
#include <linux/module.h>
#include <linux/net_tstamp.h>
#include <linux/ptp_classify.h>
#include <linux/time.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include "cpts.h"
#define CPTS_SKB_TX_WORK_TIMEOUT 1 /* jiffies */
#define CPTS_SKB_RX_TX_TMO 100 /*ms */
#define CPTS_EVENT_RX_TX_TIMEOUT (100) /* ms */
struct cpts_skb_cb_data {
u32 skb_mtype_seqid;
unsigned long tmo;
};
#define cpts_read32(c, r) readl_relaxed(&c->reg->r)
#define cpts_write32(c, v, r) writel_relaxed(v, &c->reg->r)
static int cpts_event_port(struct cpts_event *event)
{
return (event->high >> PORT_NUMBER_SHIFT) & PORT_NUMBER_MASK;
}
static int event_expired(struct cpts_event *event)
{
return time_after(jiffies, event->tmo);
}
static int event_type(struct cpts_event *event)
{
return (event->high >> EVENT_TYPE_SHIFT) & EVENT_TYPE_MASK;
}
static int cpts_fifo_pop(struct cpts *cpts, u32 *high, u32 *low)
{
u32 r = cpts_read32(cpts, intstat_raw);
if (r & TS_PEND_RAW) {
*high = cpts_read32(cpts, event_high);
*low = cpts_read32(cpts, event_low);
cpts_write32(cpts, EVENT_POP, event_pop);
return 0;
}
return -1;
}
static int cpts_purge_events(struct cpts *cpts)
{
struct list_head *this, *next;
struct cpts_event *event;
int removed = 0;
list_for_each_safe(this, next, &cpts->events) {
event = list_entry(this, struct cpts_event, list);
if (event_expired(event)) {
list_del_init(&event->list);
list_add(&event->list, &cpts->pool);
++removed;
}
}
if (removed)
dev_dbg(cpts->dev, "cpts: event pool cleaned up %d\n", removed);
return removed ? 0 : -1;
}
static void cpts_purge_txq(struct cpts *cpts)
{
struct cpts_skb_cb_data *skb_cb;
struct sk_buff *skb, *tmp;
int removed = 0;
skb_queue_walk_safe(&cpts->txq, skb, tmp) {
skb_cb = (struct cpts_skb_cb_data *)skb->cb;
if (time_after(jiffies, skb_cb->tmo)) {
__skb_unlink(skb, &cpts->txq);
dev_consume_skb_any(skb);
++removed;
}
}
if (removed)
dev_dbg(cpts->dev, "txq cleaned up %d\n", removed);
}
/*
* Returns zero if matching event type was found.
*/
static int cpts_fifo_read(struct cpts *cpts, int match)
{
struct ptp_clock_event pevent;
bool need_schedule = false;
struct cpts_event *event;
unsigned long flags;
int i, type = -1;
u32 hi, lo;
spin_lock_irqsave(&cpts->lock, flags);
for (i = 0; i < CPTS_FIFO_DEPTH; i++) {
if (cpts_fifo_pop(cpts, &hi, &lo))
break;
if (list_empty(&cpts->pool) && cpts_purge_events(cpts)) {
dev_warn(cpts->dev, "cpts: event pool empty\n");
break;
}
event = list_first_entry(&cpts->pool, struct cpts_event, list);
event->high = hi;
event->low = lo;
event->timestamp = timecounter_cyc2time(&cpts->tc, event->low);
type = event_type(event);
dev_dbg(cpts->dev, "CPTS_EV: %d high:%08X low:%08x\n",
type, event->high, event->low);
switch (type) {
case CPTS_EV_PUSH:
WRITE_ONCE(cpts->cur_timestamp, lo);
timecounter_read(&cpts->tc);
if (cpts->mult_new) {
cpts->cc.mult = cpts->mult_new;
cpts->mult_new = 0;
}
if (!cpts->irq_poll)
complete(&cpts->ts_push_complete);
break;
case CPTS_EV_TX:
case CPTS_EV_RX:
event->tmo = jiffies +
msecs_to_jiffies(CPTS_EVENT_RX_TX_TIMEOUT);
list_del_init(&event->list);
list_add_tail(&event->list, &cpts->events);
need_schedule = true;
break;
case CPTS_EV_ROLL:
case CPTS_EV_HALF:
break;
case CPTS_EV_HW:
pevent.timestamp = event->timestamp;
pevent.type = PTP_CLOCK_EXTTS;
pevent.index = cpts_event_port(event) - 1;
ptp_clock_event(cpts->clock, &pevent);
break;
default:
dev_err(cpts->dev, "cpts: unknown event type\n");
break;
}
if (type == match)
break;
}
spin_unlock_irqrestore(&cpts->lock, flags);
if (!cpts->irq_poll && need_schedule)
ptp_schedule_worker(cpts->clock, 0);
return type == match ? 0 : -1;
}
void cpts_misc_interrupt(struct cpts *cpts)
{
cpts_fifo_read(cpts, -1);
}
EXPORT_SYMBOL_GPL(cpts_misc_interrupt);
static u64 cpts_systim_read(struct cyclecounter *cc)
{
struct cpts *cpts = container_of(cc, struct cpts, cc);
return READ_ONCE(cpts->cur_timestamp);
}
static void cpts_update_cur_time(struct cpts *cpts, int match,
struct ptp_system_timestamp *sts)
{
unsigned long flags;
reinit_completion(&cpts->ts_push_complete);
/* use spin_lock_irqsave() here as it has to run very fast */
spin_lock_irqsave(&cpts->lock, flags);
ptp_read_system_prets(sts);
cpts_write32(cpts, TS_PUSH, ts_push);
cpts_read32(cpts, ts_push);
ptp_read_system_postts(sts);
spin_unlock_irqrestore(&cpts->lock, flags);
if (cpts->irq_poll && cpts_fifo_read(cpts, match) && match != -1)
dev_err(cpts->dev, "cpts: unable to obtain a time stamp\n");
if (!cpts->irq_poll &&
!wait_for_completion_timeout(&cpts->ts_push_complete, HZ))
dev_err(cpts->dev, "cpts: obtain a time stamp timeout\n");
}
/* PTP clock operations */
static int cpts_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct cpts *cpts = container_of(ptp, struct cpts, info);
mutex_lock(&cpts->ptp_clk_mutex);
cpts->mult_new = adjust_by_scaled_ppm(cpts->cc_mult, scaled_ppm);
cpts_update_cur_time(cpts, CPTS_EV_PUSH, NULL);
mutex_unlock(&cpts->ptp_clk_mutex);
return 0;
}
static int cpts_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct cpts *cpts = container_of(ptp, struct cpts, info);
mutex_lock(&cpts->ptp_clk_mutex);
timecounter_adjtime(&cpts->tc, delta);
mutex_unlock(&cpts->ptp_clk_mutex);
return 0;
}
static int cpts_ptp_gettimeex(struct ptp_clock_info *ptp,
struct timespec64 *ts,
struct ptp_system_timestamp *sts)
{
struct cpts *cpts = container_of(ptp, struct cpts, info);
u64 ns;
mutex_lock(&cpts->ptp_clk_mutex);
cpts_update_cur_time(cpts, CPTS_EV_PUSH, sts);
ns = timecounter_read(&cpts->tc);
mutex_unlock(&cpts->ptp_clk_mutex);
*ts = ns_to_timespec64(ns);
return 0;
}
static int cpts_ptp_settime(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct cpts *cpts = container_of(ptp, struct cpts, info);
u64 ns;
ns = timespec64_to_ns(ts);
mutex_lock(&cpts->ptp_clk_mutex);
timecounter_init(&cpts->tc, &cpts->cc, ns);
mutex_unlock(&cpts->ptp_clk_mutex);
return 0;
}
static int cpts_extts_enable(struct cpts *cpts, u32 index, int on)
{
u32 v;
if (((cpts->hw_ts_enable & BIT(index)) >> index) == on)
return 0;
mutex_lock(&cpts->ptp_clk_mutex);
v = cpts_read32(cpts, control);
if (on) {
v |= BIT(8 + index);
cpts->hw_ts_enable |= BIT(index);
} else {
v &= ~BIT(8 + index);
cpts->hw_ts_enable &= ~BIT(index);
}
cpts_write32(cpts, v, control);
mutex_unlock(&cpts->ptp_clk_mutex);
return 0;
}
static int cpts_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
struct cpts *cpts = container_of(ptp, struct cpts, info);
switch (rq->type) {
case PTP_CLK_REQ_EXTTS:
return cpts_extts_enable(cpts, rq->extts.index, on);
default:
break;
}
return -EOPNOTSUPP;
}
static bool cpts_match_tx_ts(struct cpts *cpts, struct cpts_event *event)
{
struct sk_buff_head txq_list;
struct sk_buff *skb, *tmp;
unsigned long flags;
bool found = false;
u32 mtype_seqid;
mtype_seqid = event->high &
((MESSAGE_TYPE_MASK << MESSAGE_TYPE_SHIFT) |
(SEQUENCE_ID_MASK << SEQUENCE_ID_SHIFT) |
(EVENT_TYPE_MASK << EVENT_TYPE_SHIFT));
__skb_queue_head_init(&txq_list);
spin_lock_irqsave(&cpts->txq.lock, flags);
skb_queue_splice_init(&cpts->txq, &txq_list);
spin_unlock_irqrestore(&cpts->txq.lock, flags);
skb_queue_walk_safe(&txq_list, skb, tmp) {
struct skb_shared_hwtstamps ssh;
struct cpts_skb_cb_data *skb_cb =
(struct cpts_skb_cb_data *)skb->cb;
if (mtype_seqid == skb_cb->skb_mtype_seqid) {
memset(&ssh, 0, sizeof(ssh));
ssh.hwtstamp = ns_to_ktime(event->timestamp);
skb_tstamp_tx(skb, &ssh);
found = true;
__skb_unlink(skb, &txq_list);
dev_consume_skb_any(skb);
dev_dbg(cpts->dev, "match tx timestamp mtype_seqid %08x\n",
mtype_seqid);
break;
}
if (time_after(jiffies, skb_cb->tmo)) {
/* timeout any expired skbs over 1s */
dev_dbg(cpts->dev, "expiring tx timestamp from txq\n");
__skb_unlink(skb, &txq_list);
dev_consume_skb_any(skb);
}
}
spin_lock_irqsave(&cpts->txq.lock, flags);
skb_queue_splice(&txq_list, &cpts->txq);
spin_unlock_irqrestore(&cpts->txq.lock, flags);
return found;
}
static void cpts_process_events(struct cpts *cpts)
{
struct list_head *this, *next;
struct cpts_event *event;
LIST_HEAD(events_free);
unsigned long flags;
LIST_HEAD(events);
spin_lock_irqsave(&cpts->lock, flags);
list_splice_init(&cpts->events, &events);
spin_unlock_irqrestore(&cpts->lock, flags);
list_for_each_safe(this, next, &events) {
event = list_entry(this, struct cpts_event, list);
if (cpts_match_tx_ts(cpts, event) ||
time_after(jiffies, event->tmo)) {
list_del_init(&event->list);
list_add(&event->list, &events_free);
}
}
spin_lock_irqsave(&cpts->lock, flags);
list_splice_tail(&events, &cpts->events);
list_splice_tail(&events_free, &cpts->pool);
spin_unlock_irqrestore(&cpts->lock, flags);
}
static long cpts_overflow_check(struct ptp_clock_info *ptp)
{
struct cpts *cpts = container_of(ptp, struct cpts, info);
unsigned long delay = cpts->ov_check_period;
unsigned long flags;
u64 ns;
mutex_lock(&cpts->ptp_clk_mutex);
cpts_update_cur_time(cpts, -1, NULL);
ns = timecounter_read(&cpts->tc);
cpts_process_events(cpts);
spin_lock_irqsave(&cpts->txq.lock, flags);
if (!skb_queue_empty(&cpts->txq)) {
cpts_purge_txq(cpts);
if (!skb_queue_empty(&cpts->txq))
delay = CPTS_SKB_TX_WORK_TIMEOUT;
}
spin_unlock_irqrestore(&cpts->txq.lock, flags);
dev_dbg(cpts->dev, "cpts overflow check at %lld\n", ns);
mutex_unlock(&cpts->ptp_clk_mutex);
return (long)delay;
}
static const struct ptp_clock_info cpts_info = {
.owner = THIS_MODULE,
.name = "CTPS timer",
.max_adj = 1000000,
.n_ext_ts = 0,
.n_pins = 0,
.pps = 0,
.adjfine = cpts_ptp_adjfine,
.adjtime = cpts_ptp_adjtime,
.gettimex64 = cpts_ptp_gettimeex,
.settime64 = cpts_ptp_settime,
.enable = cpts_ptp_enable,
.do_aux_work = cpts_overflow_check,
};
static int cpts_skb_get_mtype_seqid(struct sk_buff *skb, u32 *mtype_seqid)
{
unsigned int ptp_class = ptp_classify_raw(skb);
struct ptp_header *hdr;
u8 msgtype;
u16 seqid;
if (ptp_class == PTP_CLASS_NONE)
return 0;
hdr = ptp_parse_header(skb, ptp_class);
if (!hdr)
return 0;
msgtype = ptp_get_msgtype(hdr, ptp_class);
seqid = ntohs(hdr->sequence_id);
*mtype_seqid = (msgtype & MESSAGE_TYPE_MASK) << MESSAGE_TYPE_SHIFT;
*mtype_seqid |= (seqid & SEQUENCE_ID_MASK) << SEQUENCE_ID_SHIFT;
return 1;
}
static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb,
int ev_type, u32 skb_mtype_seqid)
{
struct list_head *this, *next;
struct cpts_event *event;
unsigned long flags;
u32 mtype_seqid;
u64 ns = 0;
cpts_fifo_read(cpts, -1);
spin_lock_irqsave(&cpts->lock, flags);
list_for_each_safe(this, next, &cpts->events) {
event = list_entry(this, struct cpts_event, list);
if (event_expired(event)) {
list_del_init(&event->list);
list_add(&event->list, &cpts->pool);
continue;
}
mtype_seqid = event->high &
((MESSAGE_TYPE_MASK << MESSAGE_TYPE_SHIFT) |
(SEQUENCE_ID_MASK << SEQUENCE_ID_SHIFT) |
(EVENT_TYPE_MASK << EVENT_TYPE_SHIFT));
if (mtype_seqid == skb_mtype_seqid) {
ns = event->timestamp;
list_del_init(&event->list);
list_add(&event->list, &cpts->pool);
break;
}
}
spin_unlock_irqrestore(&cpts->lock, flags);
return ns;
}
void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb)
{
struct cpts_skb_cb_data *skb_cb = (struct cpts_skb_cb_data *)skb->cb;
struct skb_shared_hwtstamps *ssh;
int ret;
u64 ns;
/* cpts_rx_timestamp() is called before eth_type_trans(), so
* skb MAC Hdr properties are not configured yet. Hence need to
* reset skb MAC header here
*/
skb_reset_mac_header(skb);
ret = cpts_skb_get_mtype_seqid(skb, &skb_cb->skb_mtype_seqid);
if (!ret)
return;
skb_cb->skb_mtype_seqid |= (CPTS_EV_RX << EVENT_TYPE_SHIFT);
dev_dbg(cpts->dev, "%s mtype seqid %08x\n",
__func__, skb_cb->skb_mtype_seqid);
ns = cpts_find_ts(cpts, skb, CPTS_EV_RX, skb_cb->skb_mtype_seqid);
if (!ns)
return;
ssh = skb_hwtstamps(skb);
memset(ssh, 0, sizeof(*ssh));
ssh->hwtstamp = ns_to_ktime(ns);
}
EXPORT_SYMBOL_GPL(cpts_rx_timestamp);
void cpts_tx_timestamp(struct cpts *cpts, struct sk_buff *skb)
{
struct cpts_skb_cb_data *skb_cb = (struct cpts_skb_cb_data *)skb->cb;
int ret;
if (!(skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS))
return;
ret = cpts_skb_get_mtype_seqid(skb, &skb_cb->skb_mtype_seqid);
if (!ret)
return;
skb_cb->skb_mtype_seqid |= (CPTS_EV_TX << EVENT_TYPE_SHIFT);
dev_dbg(cpts->dev, "%s mtype seqid %08x\n",
__func__, skb_cb->skb_mtype_seqid);
/* Always defer TX TS processing to PTP worker */
skb_get(skb);
/* get the timestamp for timeouts */
skb_cb->tmo = jiffies + msecs_to_jiffies(CPTS_SKB_RX_TX_TMO);
skb_queue_tail(&cpts->txq, skb);
ptp_schedule_worker(cpts->clock, 0);
}
EXPORT_SYMBOL_GPL(cpts_tx_timestamp);
int cpts_register(struct cpts *cpts)
{
int err, i;
skb_queue_head_init(&cpts->txq);
INIT_LIST_HEAD(&cpts->events);
INIT_LIST_HEAD(&cpts->pool);
for (i = 0; i < CPTS_MAX_EVENTS; i++)
list_add(&cpts->pool_data[i].list, &cpts->pool);
err = clk_enable(cpts->refclk);
if (err)
return err;
cpts_write32(cpts, CPTS_EN, control);
cpts_write32(cpts, TS_PEND_EN, int_enable);
timecounter_init(&cpts->tc, &cpts->cc, ktime_get_real_ns());
cpts->clock = ptp_clock_register(&cpts->info, cpts->dev);
if (IS_ERR(cpts->clock)) {
err = PTR_ERR(cpts->clock);
cpts->clock = NULL;
goto err_ptp;
}
cpts->phc_index = ptp_clock_index(cpts->clock);
ptp_schedule_worker(cpts->clock, cpts->ov_check_period);
return 0;
err_ptp:
clk_disable(cpts->refclk);
return err;
}
EXPORT_SYMBOL_GPL(cpts_register);
void cpts_unregister(struct cpts *cpts)
{
if (WARN_ON(!cpts->clock))
return;
ptp_clock_unregister(cpts->clock);
cpts->clock = NULL;
cpts->phc_index = -1;
cpts_write32(cpts, 0, int_enable);
cpts_write32(cpts, 0, control);
/* Drop all packet */
skb_queue_purge(&cpts->txq);
clk_disable(cpts->refclk);
}
EXPORT_SYMBOL_GPL(cpts_unregister);
static void cpts_calc_mult_shift(struct cpts *cpts)
{
u64 frac, maxsec, ns;
u32 freq;
freq = clk_get_rate(cpts->refclk);
/* Calc the maximum number of seconds which we can run before
* wrapping around.
*/
maxsec = cpts->cc.mask;
do_div(maxsec, freq);
/* limit conversation rate to 10 sec as higher values will produce
* too small mult factors and so reduce the conversion accuracy
*/
if (maxsec > 10)
maxsec = 10;
/* Calc overflow check period (maxsec / 2) */
cpts->ov_check_period = (HZ * maxsec) / 2;
dev_info(cpts->dev, "cpts: overflow check period %lu (jiffies)\n",
cpts->ov_check_period);
if (cpts->cc.mult || cpts->cc.shift)
return;
clocks_calc_mult_shift(&cpts->cc.mult, &cpts->cc.shift,
freq, NSEC_PER_SEC, maxsec);
frac = 0;
ns = cyclecounter_cyc2ns(&cpts->cc, freq, cpts->cc.mask, &frac);
dev_info(cpts->dev,
"CPTS: ref_clk_freq:%u calc_mult:%u calc_shift:%u error:%lld nsec/sec\n",
freq, cpts->cc.mult, cpts->cc.shift, (ns - NSEC_PER_SEC));
}
static void cpts_clk_unregister(void *clk)
{
clk_hw_unregister_mux(clk);
}
static void cpts_clk_del_provider(void *np)
{
of_clk_del_provider(np);
}
static int cpts_of_mux_clk_setup(struct cpts *cpts, struct device_node *node)
{
struct device_node *refclk_np;
const char **parent_names;
unsigned int num_parents;
struct clk_hw *clk_hw;
int ret = -EINVAL;
u32 *mux_table;
refclk_np = of_get_child_by_name(node, "cpts-refclk-mux");
if (!refclk_np)
/* refclk selection supported not for all SoCs */
return 0;
num_parents = of_clk_get_parent_count(refclk_np);
if (num_parents < 1) {
dev_err(cpts->dev, "mux-clock %s must have parents\n",
refclk_np->name);
goto mux_fail;
}
parent_names = devm_kcalloc(cpts->dev, num_parents,
sizeof(*parent_names), GFP_KERNEL);
mux_table = devm_kcalloc(cpts->dev, num_parents, sizeof(*mux_table),
GFP_KERNEL);
if (!mux_table || !parent_names) {
ret = -ENOMEM;
goto mux_fail;
}
of_clk_parent_fill(refclk_np, parent_names, num_parents);
ret = of_property_read_variable_u32_array(refclk_np, "ti,mux-tbl",
mux_table,
num_parents, num_parents);
if (ret < 0)
goto mux_fail;
clk_hw = clk_hw_register_mux_table(cpts->dev, refclk_np->name,
parent_names, num_parents,
0,
&cpts->reg->rftclk_sel, 0, 0x1F,
0, mux_table, NULL);
if (IS_ERR(clk_hw)) {
ret = PTR_ERR(clk_hw);
goto mux_fail;
}
ret = devm_add_action_or_reset(cpts->dev, cpts_clk_unregister, clk_hw);
if (ret) {
dev_err(cpts->dev, "add clkmux unreg action %d", ret);
goto mux_fail;
}
ret = of_clk_add_hw_provider(refclk_np, of_clk_hw_simple_get, clk_hw);
if (ret)
goto mux_fail;
ret = devm_add_action_or_reset(cpts->dev, cpts_clk_del_provider,
refclk_np);
if (ret) {
dev_err(cpts->dev, "add clkmux provider unreg action %d", ret);
goto mux_fail;
}
return ret;
mux_fail:
of_node_put(refclk_np);
return ret;
}
static int cpts_of_parse(struct cpts *cpts, struct device_node *node)
{
int ret = -EINVAL;
u32 prop;
if (!of_property_read_u32(node, "cpts_clock_mult", &prop))
cpts->cc.mult = prop;
if (!of_property_read_u32(node, "cpts_clock_shift", &prop))
cpts->cc.shift = prop;
if ((cpts->cc.mult && !cpts->cc.shift) ||
(!cpts->cc.mult && cpts->cc.shift))
goto of_error;
return cpts_of_mux_clk_setup(cpts, node);
of_error:
dev_err(cpts->dev, "CPTS: Missing property in the DT.\n");
return ret;
}
struct cpts *cpts_create(struct device *dev, void __iomem *regs,
struct device_node *node, u32 n_ext_ts)
{
struct cpts *cpts;
int ret;
cpts = devm_kzalloc(dev, sizeof(*cpts), GFP_KERNEL);
if (!cpts)
return ERR_PTR(-ENOMEM);
cpts->dev = dev;
cpts->reg = (struct cpsw_cpts __iomem *)regs;
cpts->irq_poll = true;
spin_lock_init(&cpts->lock);
mutex_init(&cpts->ptp_clk_mutex);
init_completion(&cpts->ts_push_complete);
ret = cpts_of_parse(cpts, node);
if (ret)
return ERR_PTR(ret);
cpts->refclk = devm_get_clk_from_child(dev, node, "cpts");
if (IS_ERR(cpts->refclk))
/* try get clk from dev node for compatibility */
cpts->refclk = devm_clk_get(dev, "cpts");
if (IS_ERR(cpts->refclk)) {
dev_err(dev, "Failed to get cpts refclk %ld\n",
PTR_ERR(cpts->refclk));
return ERR_CAST(cpts->refclk);
}
ret = clk_prepare(cpts->refclk);
if (ret)
return ERR_PTR(ret);
cpts->cc.read = cpts_systim_read;
cpts->cc.mask = CLOCKSOURCE_MASK(32);
cpts->info = cpts_info;
cpts->phc_index = -1;
if (n_ext_ts)
cpts->info.n_ext_ts = n_ext_ts;
cpts_calc_mult_shift(cpts);
/* save cc.mult original value as it can be modified
* by cpts_ptp_adjfine().
*/
cpts->cc_mult = cpts->cc.mult;
return cpts;
}
EXPORT_SYMBOL_GPL(cpts_create);
void cpts_release(struct cpts *cpts)
{
if (!cpts)
return;
if (WARN_ON(!cpts->refclk))
return;
clk_unprepare(cpts->refclk);
}
EXPORT_SYMBOL_GPL(cpts_release);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TI CPTS driver");
MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>");
+196
View File
@@ -0,0 +1,196 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* TI Common Platform Time Sync
*
* Copyright (C) 2012 Richard Cochran <richardcochran@gmail.com>
*
*/
#ifndef _TI_CPTS_H_
#define _TI_CPTS_H_
#if IS_ENABLED(CONFIG_TI_CPTS)
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/clocksource.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/skbuff.h>
#include <linux/ptp_classify.h>
#include <linux/timecounter.h>
struct cpsw_cpts {
u32 idver; /* Identification and version */
u32 control; /* Time sync control */
u32 rftclk_sel; /* Reference Clock Select Register */
u32 ts_push; /* Time stamp event push */
u32 ts_load_val; /* Time stamp load value */
u32 ts_load_en; /* Time stamp load enable */
u32 res2[2];
u32 intstat_raw; /* Time sync interrupt status raw */
u32 intstat_masked; /* Time sync interrupt status masked */
u32 int_enable; /* Time sync interrupt enable */
u32 res3;
u32 event_pop; /* Event interrupt pop */
u32 event_low; /* 32 Bit Event Time Stamp */
u32 event_high; /* Event Type Fields */
};
/* Bit definitions for the IDVER register */
#define TX_IDENT_SHIFT (16) /* TX Identification Value */
#define TX_IDENT_MASK (0xffff)
#define RTL_VER_SHIFT (11) /* RTL Version Value */
#define RTL_VER_MASK (0x1f)
#define MAJOR_VER_SHIFT (8) /* Major Version Value */
#define MAJOR_VER_MASK (0x7)
#define MINOR_VER_SHIFT (0) /* Minor Version Value */
#define MINOR_VER_MASK (0xff)
/* Bit definitions for the CONTROL register */
#define HW4_TS_PUSH_EN (1<<11) /* Hardware push 4 enable */
#define HW3_TS_PUSH_EN (1<<10) /* Hardware push 3 enable */
#define HW2_TS_PUSH_EN (1<<9) /* Hardware push 2 enable */
#define HW1_TS_PUSH_EN (1<<8) /* Hardware push 1 enable */
#define INT_TEST (1<<1) /* Interrupt Test */
#define CPTS_EN (1<<0) /* Time Sync Enable */
/*
* Definitions for the single bit resisters:
* TS_PUSH TS_LOAD_EN INTSTAT_RAW INTSTAT_MASKED INT_ENABLE EVENT_POP
*/
#define TS_PUSH (1<<0) /* Time stamp event push */
#define TS_LOAD_EN (1<<0) /* Time Stamp Load */
#define TS_PEND_RAW (1<<0) /* int read (before enable) */
#define TS_PEND (1<<0) /* masked interrupt read (after enable) */
#define TS_PEND_EN (1<<0) /* masked interrupt enable */
#define EVENT_POP (1<<0) /* writing discards one event */
/* Bit definitions for the EVENT_HIGH register */
#define PORT_NUMBER_SHIFT (24) /* Indicates Ethernet port or HW pin */
#define PORT_NUMBER_MASK (0x1f)
#define EVENT_TYPE_SHIFT (20) /* Time sync event type */
#define EVENT_TYPE_MASK (0xf)
#define MESSAGE_TYPE_SHIFT (16) /* PTP message type */
#define MESSAGE_TYPE_MASK (0xf)
#define SEQUENCE_ID_SHIFT (0) /* PTP message sequence ID */
#define SEQUENCE_ID_MASK (0xffff)
enum {
CPTS_EV_PUSH, /* Time Stamp Push Event */
CPTS_EV_ROLL, /* Time Stamp Rollover Event */
CPTS_EV_HALF, /* Time Stamp Half Rollover Event */
CPTS_EV_HW, /* Hardware Time Stamp Push Event */
CPTS_EV_RX, /* Ethernet Receive Event */
CPTS_EV_TX, /* Ethernet Transmit Event */
};
#define CPTS_FIFO_DEPTH 16
#define CPTS_MAX_EVENTS 32
struct cpts_event {
struct list_head list;
unsigned long tmo;
u32 high;
u32 low;
u64 timestamp;
};
struct cpts {
struct device *dev;
struct cpsw_cpts __iomem *reg;
int tx_enable;
int rx_enable;
struct ptp_clock_info info;
struct ptp_clock *clock;
spinlock_t lock; /* protects fifo/events */
u32 cc_mult; /* for the nominal frequency */
struct cyclecounter cc;
struct timecounter tc;
int phc_index;
struct clk *refclk;
struct list_head events;
struct list_head pool;
struct cpts_event pool_data[CPTS_MAX_EVENTS];
unsigned long ov_check_period;
struct sk_buff_head txq;
u64 cur_timestamp;
u32 mult_new;
struct mutex ptp_clk_mutex; /* sync PTP interface and worker */
bool irq_poll;
struct completion ts_push_complete;
u32 hw_ts_enable;
};
void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb);
void cpts_tx_timestamp(struct cpts *cpts, struct sk_buff *skb);
int cpts_register(struct cpts *cpts);
void cpts_unregister(struct cpts *cpts);
struct cpts *cpts_create(struct device *dev, void __iomem *regs,
struct device_node *node, u32 n_ext_ts);
void cpts_release(struct cpts *cpts);
void cpts_misc_interrupt(struct cpts *cpts);
static inline bool cpts_can_timestamp(struct cpts *cpts, struct sk_buff *skb)
{
unsigned int class = ptp_classify_raw(skb);
if (class == PTP_CLASS_NONE)
return false;
return true;
}
static inline void cpts_set_irqpoll(struct cpts *cpts, bool en)
{
cpts->irq_poll = en;
}
#else
struct cpts;
static inline void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb)
{
}
static inline void cpts_tx_timestamp(struct cpts *cpts, struct sk_buff *skb)
{
}
static inline
struct cpts *cpts_create(struct device *dev, void __iomem *regs,
struct device_node *node, u32 n_ext_ts)
{
return NULL;
}
static inline void cpts_release(struct cpts *cpts)
{
}
static inline int
cpts_register(struct cpts *cpts)
{
return 0;
}
static inline void cpts_unregister(struct cpts *cpts)
{
}
static inline bool cpts_can_timestamp(struct cpts *cpts, struct sk_buff *skb)
{
return false;
}
static inline void cpts_misc_interrupt(struct cpts *cpts)
{
}
static inline void cpts_set_irqpoll(struct cpts *cpts, bool en)
{
}
#endif
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,122 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Texas Instruments CPDMA Driver
*
* Copyright (C) 2010 Texas Instruments
*
*/
#ifndef __DAVINCI_CPDMA_H__
#define __DAVINCI_CPDMA_H__
#define CPDMA_MAX_CHANNELS BITS_PER_LONG
#define CPDMA_RX_SOURCE_PORT(__status__) ((__status__ >> 16) & 0x7)
#define CPDMA_RX_VLAN_ENCAP BIT(19)
#define CPDMA_EOI_RX_THRESH 0x0
#define CPDMA_EOI_RX 0x1
#define CPDMA_EOI_TX 0x2
#define CPDMA_EOI_MISC 0x3
struct cpdma_params {
struct device *dev;
void __iomem *dmaregs;
void __iomem *txhdp, *rxhdp, *txcp, *rxcp;
void __iomem *rxthresh, *rxfree;
int num_chan;
bool has_soft_reset;
int min_packet_size;
dma_addr_t desc_mem_phys;
dma_addr_t desc_hw_addr;
int desc_mem_size;
int desc_align;
u32 bus_freq_mhz;
u32 descs_pool_size;
/*
* Some instances of embedded cpdma controllers have extra control and
* status registers. The following flag enables access to these
* "extended" registers.
*/
bool has_ext_regs;
};
struct cpdma_chan_stats {
u32 head_enqueue;
u32 tail_enqueue;
u32 pad_enqueue;
u32 misqueued;
u32 desc_alloc_fail;
u32 pad_alloc_fail;
u32 runt_receive_buff;
u32 runt_transmit_buff;
u32 empty_dequeue;
u32 busy_dequeue;
u32 good_dequeue;
u32 requeue;
u32 teardown_dequeue;
};
struct cpdma_ctlr;
struct cpdma_chan;
typedef void (*cpdma_handler_fn)(void *token, int len, int status);
struct cpdma_ctlr *cpdma_ctlr_create(struct cpdma_params *params);
int cpdma_ctlr_destroy(struct cpdma_ctlr *ctlr);
int cpdma_ctlr_start(struct cpdma_ctlr *ctlr);
int cpdma_ctlr_stop(struct cpdma_ctlr *ctlr);
struct cpdma_chan *cpdma_chan_create(struct cpdma_ctlr *ctlr, int chan_num,
cpdma_handler_fn handler, int rx_type);
int cpdma_chan_get_rx_buf_num(struct cpdma_chan *chan);
int cpdma_chan_destroy(struct cpdma_chan *chan);
int cpdma_chan_start(struct cpdma_chan *chan);
int cpdma_chan_stop(struct cpdma_chan *chan);
int cpdma_chan_get_stats(struct cpdma_chan *chan,
struct cpdma_chan_stats *stats);
int cpdma_chan_submit_mapped(struct cpdma_chan *chan, void *token,
dma_addr_t data, int len, int directed);
int cpdma_chan_submit(struct cpdma_chan *chan, void *token, void *data,
int len, int directed);
int cpdma_chan_idle_submit_mapped(struct cpdma_chan *chan, void *token,
dma_addr_t data, int len, int directed);
int cpdma_chan_idle_submit(struct cpdma_chan *chan, void *token, void *data,
int len, int directed);
int cpdma_chan_process(struct cpdma_chan *chan, int quota);
int cpdma_ctlr_int_ctrl(struct cpdma_ctlr *ctlr, bool enable);
void cpdma_ctlr_eoi(struct cpdma_ctlr *ctlr, u32 value);
int cpdma_chan_int_ctrl(struct cpdma_chan *chan, bool enable);
u32 cpdma_ctrl_rxchs_state(struct cpdma_ctlr *ctlr);
u32 cpdma_ctrl_txchs_state(struct cpdma_ctlr *ctlr);
bool cpdma_check_free_tx_desc(struct cpdma_chan *chan);
int cpdma_chan_set_weight(struct cpdma_chan *ch, int weight);
int cpdma_chan_set_rate(struct cpdma_chan *ch, u32 rate);
u32 cpdma_chan_get_rate(struct cpdma_chan *ch);
u32 cpdma_chan_get_min_rate(struct cpdma_ctlr *ctlr);
enum cpdma_control {
CPDMA_TX_RLIM, /* read-write */
CPDMA_CMD_IDLE, /* write-only */
CPDMA_COPY_ERROR_FRAMES, /* read-write */
CPDMA_RX_OFF_LEN_UPDATE, /* read-write */
CPDMA_RX_OWNERSHIP_FLIP, /* read-write */
CPDMA_TX_PRIO_FIXED, /* read-write */
CPDMA_STAT_IDLE, /* read-only */
CPDMA_STAT_TX_ERR_CHAN, /* read-only */
CPDMA_STAT_TX_ERR_CODE, /* read-only */
CPDMA_STAT_RX_ERR_CHAN, /* read-only */
CPDMA_STAT_RX_ERR_CODE, /* read-only */
CPDMA_RX_BUFFER_OFFSET, /* read-write */
};
int cpdma_control_get(struct cpdma_ctlr *ctlr, int control);
int cpdma_control_set(struct cpdma_ctlr *ctlr, int control, int value);
int cpdma_get_num_rx_descs(struct cpdma_ctlr *ctlr);
int cpdma_set_num_rx_descs(struct cpdma_ctlr *ctlr, int num_rx_desc);
int cpdma_get_num_tx_descs(struct cpdma_ctlr *ctlr);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,768 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* DaVinci MDIO Module driver
*
* Copyright (C) 2010 Texas Instruments.
*
* Shamelessly ripped out of davinci_emac.c, original copyrights follow:
*
* Copyright (C) 2009 Texas Instruments.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/phy.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/pm_runtime.h>
#include <linux/davinci_emac.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/mdio-bitbang.h>
#include <linux/sys_soc.h>
/*
* This timeout definition is a worst-case ultra defensive measure against
* unexpected controller lock ups. Ideally, we should never ever hit this
* scenario in practice.
*/
#define MDIO_TIMEOUT 100 /* msecs */
#define PHY_REG_MASK 0x1f
#define PHY_ID_MASK 0x1f
#define DEF_OUT_FREQ 2200000 /* 2.2 MHz */
struct davinci_mdio_of_param {
int autosuspend_delay_ms;
bool manual_mode;
};
struct davinci_mdio_regs {
u32 version;
u32 control;
#define CONTROL_IDLE BIT(31)
#define CONTROL_ENABLE BIT(30)
#define CONTROL_MAX_DIV (0xffff)
#define CONTROL_CLKDIV GENMASK(15, 0)
#define MDIO_MAN_MDCLK_O BIT(2)
#define MDIO_MAN_OE BIT(1)
#define MDIO_MAN_PIN BIT(0)
#define MDIO_MANUALMODE BIT(31)
#define MDIO_PIN 0
u32 alive;
u32 link;
u32 linkintraw;
u32 linkintmasked;
u32 __reserved_0[2];
u32 userintraw;
u32 userintmasked;
u32 userintmaskset;
u32 userintmaskclr;
u32 manualif;
u32 poll;
u32 __reserved_1[18];
struct {
u32 access;
#define USERACCESS_GO BIT(31)
#define USERACCESS_WRITE BIT(30)
#define USERACCESS_ACK BIT(29)
#define USERACCESS_READ (0)
#define USERACCESS_DATA (0xffff)
u32 physel;
} user[];
};
static const struct mdio_platform_data default_pdata = {
.bus_freq = DEF_OUT_FREQ,
};
struct davinci_mdio_data {
struct mdio_platform_data pdata;
struct mdiobb_ctrl bb_ctrl;
struct davinci_mdio_regs __iomem *regs;
struct clk *clk;
struct device *dev;
struct mii_bus *bus;
bool active_in_suspend;
unsigned long access_time; /* jiffies */
/* Indicates that driver shouldn't modify phy_mask in case
* if MDIO bus is registered from DT.
*/
bool skip_scan;
u32 clk_div;
bool manual_mode;
};
static void davinci_mdio_init_clk(struct davinci_mdio_data *data)
{
u32 mdio_in, div, mdio_out_khz, access_time;
mdio_in = clk_get_rate(data->clk);
div = (mdio_in / data->pdata.bus_freq) - 1;
if (div > CONTROL_MAX_DIV)
div = CONTROL_MAX_DIV;
data->clk_div = div;
/*
* One mdio transaction consists of:
* 32 bits of preamble
* 32 bits of transferred data
* 24 bits of bus yield (not needed unless shared?)
*/
mdio_out_khz = mdio_in / (1000 * (div + 1));
access_time = (88 * 1000) / mdio_out_khz;
/*
* In the worst case, we could be kicking off a user-access immediately
* after the mdio bus scan state-machine triggered its own read. If
* so, our request could get deferred by one access cycle. We
* defensively allow for 4 access cycles.
*/
data->access_time = usecs_to_jiffies(access_time * 4);
if (!data->access_time)
data->access_time = 1;
}
static void davinci_mdio_enable(struct davinci_mdio_data *data)
{
/* set enable and clock divider */
writel(data->clk_div | CONTROL_ENABLE, &data->regs->control);
}
static void davinci_mdio_disable(struct davinci_mdio_data *data)
{
u32 reg;
/* Disable MDIO state machine */
reg = readl(&data->regs->control);
reg &= ~CONTROL_CLKDIV;
reg |= data->clk_div;
reg &= ~CONTROL_ENABLE;
writel(reg, &data->regs->control);
}
static void davinci_mdio_enable_manual_mode(struct davinci_mdio_data *data)
{
u32 reg;
/* set manual mode */
reg = readl(&data->regs->poll);
reg |= MDIO_MANUALMODE;
writel(reg, &data->regs->poll);
}
static void davinci_set_mdc(struct mdiobb_ctrl *ctrl, int level)
{
struct davinci_mdio_data *data;
u32 reg;
data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
reg = readl(&data->regs->manualif);
if (level)
reg |= MDIO_MAN_MDCLK_O;
else
reg &= ~MDIO_MAN_MDCLK_O;
writel(reg, &data->regs->manualif);
}
static void davinci_set_mdio_dir(struct mdiobb_ctrl *ctrl, int output)
{
struct davinci_mdio_data *data;
u32 reg;
data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
reg = readl(&data->regs->manualif);
if (output)
reg |= MDIO_MAN_OE;
else
reg &= ~MDIO_MAN_OE;
writel(reg, &data->regs->manualif);
}
static void davinci_set_mdio_data(struct mdiobb_ctrl *ctrl, int value)
{
struct davinci_mdio_data *data;
u32 reg;
data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
reg = readl(&data->regs->manualif);
if (value)
reg |= MDIO_MAN_PIN;
else
reg &= ~MDIO_MAN_PIN;
writel(reg, &data->regs->manualif);
}
static int davinci_get_mdio_data(struct mdiobb_ctrl *ctrl)
{
struct davinci_mdio_data *data;
unsigned long reg;
data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
reg = readl(&data->regs->manualif);
return test_bit(MDIO_PIN, &reg);
}
static int davinci_mdiobb_read_c22(struct mii_bus *bus, int phy, int reg)
{
int ret;
ret = pm_runtime_resume_and_get(bus->parent);
if (ret < 0)
return ret;
ret = mdiobb_read_c22(bus, phy, reg);
pm_runtime_put_autosuspend(bus->parent);
return ret;
}
static int davinci_mdiobb_write_c22(struct mii_bus *bus, int phy, int reg,
u16 val)
{
int ret;
ret = pm_runtime_resume_and_get(bus->parent);
if (ret < 0)
return ret;
ret = mdiobb_write_c22(bus, phy, reg, val);
pm_runtime_put_autosuspend(bus->parent);
return ret;
}
static int davinci_mdiobb_read_c45(struct mii_bus *bus, int phy, int devad,
int reg)
{
int ret;
ret = pm_runtime_resume_and_get(bus->parent);
if (ret < 0)
return ret;
ret = mdiobb_read_c45(bus, phy, devad, reg);
pm_runtime_put_autosuspend(bus->parent);
return ret;
}
static int davinci_mdiobb_write_c45(struct mii_bus *bus, int phy, int devad,
int reg, u16 val)
{
int ret;
ret = pm_runtime_resume_and_get(bus->parent);
if (ret < 0)
return ret;
ret = mdiobb_write_c45(bus, phy, devad, reg, val);
pm_runtime_put_autosuspend(bus->parent);
return ret;
}
static int davinci_mdio_common_reset(struct davinci_mdio_data *data)
{
u32 phy_mask, ver;
int ret;
ret = pm_runtime_resume_and_get(data->dev);
if (ret < 0)
return ret;
if (data->manual_mode) {
davinci_mdio_disable(data);
davinci_mdio_enable_manual_mode(data);
}
/* wait for scan logic to settle */
msleep(PHY_MAX_ADDR * data->access_time);
/* dump hardware version info */
ver = readl(&data->regs->version);
dev_info(data->dev,
"davinci mdio revision %d.%d, bus freq %ld\n",
(ver >> 8) & 0xff, ver & 0xff,
data->pdata.bus_freq);
if (data->skip_scan)
goto done;
/* get phy mask from the alive register */
phy_mask = readl(&data->regs->alive);
if (phy_mask) {
/* restrict mdio bus to live phys only */
dev_info(data->dev, "detected phy mask %x\n", ~phy_mask);
phy_mask = ~phy_mask;
} else {
/* desperately scan all phys */
dev_warn(data->dev, "no live phy, scanning all\n");
phy_mask = 0;
}
data->bus->phy_mask = phy_mask;
done:
pm_runtime_put_autosuspend(data->dev);
return 0;
}
static int davinci_mdio_reset(struct mii_bus *bus)
{
struct davinci_mdio_data *data = bus->priv;
return davinci_mdio_common_reset(data);
}
static int davinci_mdiobb_reset(struct mii_bus *bus)
{
struct mdiobb_ctrl *ctrl = bus->priv;
struct davinci_mdio_data *data;
data = container_of(ctrl, struct davinci_mdio_data, bb_ctrl);
return davinci_mdio_common_reset(data);
}
/* wait until hardware is ready for another user access */
static inline int wait_for_user_access(struct davinci_mdio_data *data)
{
struct davinci_mdio_regs __iomem *regs = data->regs;
unsigned long timeout = jiffies + msecs_to_jiffies(MDIO_TIMEOUT);
u32 reg;
while (time_after(timeout, jiffies)) {
reg = readl(&regs->user[0].access);
if ((reg & USERACCESS_GO) == 0)
return 0;
reg = readl(&regs->control);
if ((reg & CONTROL_IDLE) == 0) {
usleep_range(100, 200);
continue;
}
/*
* An emac soft_reset may have clobbered the mdio controller's
* state machine. We need to reset and retry the current
* operation
*/
dev_warn(data->dev, "resetting idled controller\n");
davinci_mdio_enable(data);
return -EAGAIN;
}
reg = readl(&regs->user[0].access);
if ((reg & USERACCESS_GO) == 0)
return 0;
dev_err(data->dev, "timed out waiting for user access\n");
return -ETIMEDOUT;
}
/* wait until hardware state machine is idle */
static inline int wait_for_idle(struct davinci_mdio_data *data)
{
struct davinci_mdio_regs __iomem *regs = data->regs;
u32 val, ret;
ret = readl_poll_timeout(&regs->control, val, val & CONTROL_IDLE,
0, MDIO_TIMEOUT * 1000);
if (ret)
dev_err(data->dev, "timed out waiting for idle\n");
return ret;
}
static int davinci_mdio_read(struct mii_bus *bus, int phy_id, int phy_reg)
{
struct davinci_mdio_data *data = bus->priv;
u32 reg;
int ret;
if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
return -EINVAL;
ret = pm_runtime_resume_and_get(data->dev);
if (ret < 0)
return ret;
reg = (USERACCESS_GO | USERACCESS_READ | (phy_reg << 21) |
(phy_id << 16));
while (1) {
ret = wait_for_user_access(data);
if (ret == -EAGAIN)
continue;
if (ret < 0)
break;
writel(reg, &data->regs->user[0].access);
ret = wait_for_user_access(data);
if (ret == -EAGAIN)
continue;
if (ret < 0)
break;
reg = readl(&data->regs->user[0].access);
ret = (reg & USERACCESS_ACK) ? (reg & USERACCESS_DATA) : -EIO;
break;
}
pm_runtime_put_autosuspend(data->dev);
return ret;
}
static int davinci_mdio_write(struct mii_bus *bus, int phy_id,
int phy_reg, u16 phy_data)
{
struct davinci_mdio_data *data = bus->priv;
u32 reg;
int ret;
if (phy_reg & ~PHY_REG_MASK || phy_id & ~PHY_ID_MASK)
return -EINVAL;
ret = pm_runtime_resume_and_get(data->dev);
if (ret < 0)
return ret;
reg = (USERACCESS_GO | USERACCESS_WRITE | (phy_reg << 21) |
(phy_id << 16) | (phy_data & USERACCESS_DATA));
while (1) {
ret = wait_for_user_access(data);
if (ret == -EAGAIN)
continue;
if (ret < 0)
break;
writel(reg, &data->regs->user[0].access);
ret = wait_for_user_access(data);
if (ret == -EAGAIN)
continue;
break;
}
pm_runtime_put_autosuspend(data->dev);
return ret;
}
static int davinci_mdio_probe_dt(struct mdio_platform_data *data,
struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
u32 prop;
if (!node)
return -EINVAL;
if (of_property_read_u32(node, "bus_freq", &prop)) {
dev_err(&pdev->dev, "Missing bus_freq property in the DT.\n");
return -EINVAL;
}
data->bus_freq = prop;
return 0;
}
struct k3_mdio_soc_data {
bool manual_mode;
};
static const struct k3_mdio_soc_data am65_mdio_soc_data = {
.manual_mode = true,
};
static const struct soc_device_attribute k3_mdio_socinfo[] = {
{ .family = "AM62X", .data = &am65_mdio_soc_data },
{ .family = "AM64X", .data = &am65_mdio_soc_data },
{ .family = "AM65X", .data = &am65_mdio_soc_data },
{ .family = "J7200", .data = &am65_mdio_soc_data },
{ .family = "J721E", .data = &am65_mdio_soc_data },
{ .family = "J721S2", .data = &am65_mdio_soc_data },
{ /* sentinel */ },
};
#if IS_ENABLED(CONFIG_OF)
static const struct davinci_mdio_of_param of_cpsw_mdio_data = {
.autosuspend_delay_ms = 100,
};
static const struct of_device_id davinci_mdio_of_mtable[] = {
{ .compatible = "ti,davinci_mdio", },
{ .compatible = "ti,cpsw-mdio", .data = &of_cpsw_mdio_data},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, davinci_mdio_of_mtable);
#endif
static const struct mdiobb_ops davinci_mdiobb_ops = {
.owner = THIS_MODULE,
.set_mdc = davinci_set_mdc,
.set_mdio_dir = davinci_set_mdio_dir,
.set_mdio_data = davinci_set_mdio_data,
.get_mdio_data = davinci_get_mdio_data,
};
static int davinci_mdio_probe(struct platform_device *pdev)
{
struct mdio_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device *dev = &pdev->dev;
struct davinci_mdio_data *data;
struct resource *res;
struct phy_device *phy;
int autosuspend_delay_ms = -1;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->manual_mode = false;
data->bb_ctrl.ops = &davinci_mdiobb_ops;
if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
const struct soc_device_attribute *soc_match_data;
soc_match_data = soc_device_match(k3_mdio_socinfo);
if (soc_match_data && soc_match_data->data) {
const struct k3_mdio_soc_data *socdata =
soc_match_data->data;
data->manual_mode = socdata->manual_mode;
}
}
if (data->manual_mode)
data->bus = alloc_mdio_bitbang(&data->bb_ctrl);
else
data->bus = devm_mdiobus_alloc(dev);
if (!data->bus) {
dev_err(dev, "failed to alloc mii bus\n");
return -ENOMEM;
}
if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
const struct davinci_mdio_of_param *of_mdio_data;
ret = davinci_mdio_probe_dt(&data->pdata, pdev);
if (ret)
return ret;
snprintf(data->bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
of_mdio_data = of_device_get_match_data(&pdev->dev);
if (of_mdio_data) {
autosuspend_delay_ms =
of_mdio_data->autosuspend_delay_ms;
}
} else {
data->pdata = pdata ? (*pdata) : default_pdata;
snprintf(data->bus->id, MII_BUS_ID_SIZE, "%s-%x",
pdev->name, pdev->id);
}
data->bus->name = dev_name(dev);
if (data->manual_mode) {
data->bus->read = davinci_mdiobb_read_c22;
data->bus->write = davinci_mdiobb_write_c22;
data->bus->read_c45 = davinci_mdiobb_read_c45;
data->bus->write_c45 = davinci_mdiobb_write_c45;
data->bus->reset = davinci_mdiobb_reset;
dev_info(dev, "Configuring MDIO in manual mode\n");
} else {
data->bus->read = davinci_mdio_read;
data->bus->write = davinci_mdio_write;
data->bus->reset = davinci_mdio_reset;
data->bus->priv = data;
}
data->bus->parent = dev;
data->clk = devm_clk_get(dev, "fck");
if (IS_ERR(data->clk)) {
dev_err(dev, "failed to get device clock\n");
return PTR_ERR(data->clk);
}
dev_set_drvdata(dev, data);
data->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EINVAL;
data->regs = devm_ioremap(dev, res->start, resource_size(res));
if (!data->regs)
return -ENOMEM;
davinci_mdio_init_clk(data);
pm_runtime_set_autosuspend_delay(&pdev->dev, autosuspend_delay_ms);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
/* register the mii bus
* Create PHYs from DT only in case if PHY child nodes are explicitly
* defined to support backward compatibility with DTs which assume that
* Davinci MDIO will always scan the bus for PHYs detection.
*/
if (dev->of_node && of_get_child_count(dev->of_node))
data->skip_scan = true;
ret = of_mdiobus_register(data->bus, dev->of_node);
if (ret)
goto bail_out;
/* scan and dump the bus */
mdiobus_for_each_phy(data->bus, phy)
dev_info(dev, "phy[%d]: device %s, driver %s\n",
phy->mdio.addr, phydev_name(phy),
phy->drv ? phy->drv->name : "unknown");
return 0;
bail_out:
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return ret;
}
static void davinci_mdio_remove(struct platform_device *pdev)
{
struct davinci_mdio_data *data = platform_get_drvdata(pdev);
if (data->bus) {
mdiobus_unregister(data->bus);
if (data->manual_mode)
free_mdio_bitbang(data->bus);
}
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
#ifdef CONFIG_PM
static int davinci_mdio_runtime_suspend(struct device *dev)
{
struct davinci_mdio_data *data = dev_get_drvdata(dev);
u32 ctrl;
/* shutdown the scan state machine */
ctrl = readl(&data->regs->control);
ctrl &= ~CONTROL_ENABLE;
writel(ctrl, &data->regs->control);
if (!data->manual_mode)
wait_for_idle(data);
return 0;
}
static int davinci_mdio_runtime_resume(struct device *dev)
{
struct davinci_mdio_data *data = dev_get_drvdata(dev);
if (data->manual_mode) {
davinci_mdio_disable(data);
davinci_mdio_enable_manual_mode(data);
} else {
davinci_mdio_enable(data);
}
return 0;
}
#endif
#ifdef CONFIG_PM_SLEEP
static int davinci_mdio_suspend(struct device *dev)
{
struct davinci_mdio_data *data = dev_get_drvdata(dev);
int ret = 0;
data->active_in_suspend = !pm_runtime_status_suspended(dev);
if (data->active_in_suspend)
ret = pm_runtime_force_suspend(dev);
if (ret < 0)
return ret;
/* Select sleep pin state */
pinctrl_pm_select_sleep_state(dev);
return 0;
}
static int davinci_mdio_resume(struct device *dev)
{
struct davinci_mdio_data *data = dev_get_drvdata(dev);
/* Select default pin state */
pinctrl_pm_select_default_state(dev);
if (data->active_in_suspend)
pm_runtime_force_resume(dev);
return 0;
}
#endif
static const struct dev_pm_ops davinci_mdio_pm_ops = {
SET_RUNTIME_PM_OPS(davinci_mdio_runtime_suspend,
davinci_mdio_runtime_resume, NULL)
SET_LATE_SYSTEM_SLEEP_PM_OPS(davinci_mdio_suspend, davinci_mdio_resume)
};
static struct platform_driver davinci_mdio_driver = {
.driver = {
.name = "davinci_mdio",
.pm = &davinci_mdio_pm_ops,
.of_match_table = of_match_ptr(davinci_mdio_of_mtable),
},
.probe = davinci_mdio_probe,
.remove = davinci_mdio_remove,
};
static int __init davinci_mdio_init(void)
{
return platform_driver_register(&davinci_mdio_driver);
}
device_initcall(davinci_mdio_init);
static void __exit davinci_mdio_exit(void)
{
platform_driver_unregister(&davinci_mdio_driver);
}
module_exit(davinci_mdio_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DaVinci MDIO driver");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,112 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Texas Instruments ICSSG Industrial Ethernet Peripheral (IEP) Driver
*
* Copyright (C) 2023 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#ifndef __NET_TI_ICSS_IEP_H
#define __NET_TI_ICSS_IEP_H
#include <linux/mutex.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/regmap.h>
enum {
ICSS_IEP_GLOBAL_CFG_REG,
ICSS_IEP_GLOBAL_STATUS_REG,
ICSS_IEP_COMPEN_REG,
ICSS_IEP_SLOW_COMPEN_REG,
ICSS_IEP_COUNT_REG0,
ICSS_IEP_COUNT_REG1,
ICSS_IEP_CAPTURE_CFG_REG,
ICSS_IEP_CAPTURE_STAT_REG,
ICSS_IEP_CAP6_RISE_REG0,
ICSS_IEP_CAP6_RISE_REG1,
ICSS_IEP_CAP7_RISE_REG0,
ICSS_IEP_CAP7_RISE_REG1,
ICSS_IEP_CMP_CFG_REG,
ICSS_IEP_CMP_STAT_REG,
ICSS_IEP_CMP0_REG0,
ICSS_IEP_CMP0_REG1,
ICSS_IEP_CMP1_REG0,
ICSS_IEP_CMP1_REG1,
ICSS_IEP_CMP8_REG0,
ICSS_IEP_CMP8_REG1,
ICSS_IEP_SYNC_CTRL_REG,
ICSS_IEP_SYNC0_STAT_REG,
ICSS_IEP_SYNC1_STAT_REG,
ICSS_IEP_SYNC_PWIDTH_REG,
ICSS_IEP_SYNC0_PERIOD_REG,
ICSS_IEP_SYNC1_DELAY_REG,
ICSS_IEP_SYNC_START_REG,
ICSS_IEP_MAX_REGS,
};
/**
* struct icss_iep_plat_data - Plat data to handle SoC variants
* @config: Regmap configuration data
* @reg_offs: register offsets to capture offset differences across SoCs
* @flags: Flags to represent IEP properties
*/
struct icss_iep_plat_data {
const struct regmap_config *config;
u32 reg_offs[ICSS_IEP_MAX_REGS];
u32 flags;
};
struct icss_iep {
struct device *dev;
void __iomem *base;
const struct icss_iep_plat_data *plat_data;
struct regmap *map;
struct device_node *client_np;
unsigned long refclk_freq;
int clk_tick_time; /* one refclk tick time in ns */
struct ptp_clock_info ptp_info;
struct ptp_clock *ptp_clock;
struct mutex ptp_clk_mutex; /* PHC access serializer */
u32 def_inc;
s16 slow_cmp_inc;
u32 slow_cmp_count;
const struct icss_iep_clockops *ops;
void *clockops_data;
u32 cycle_time_ns;
u32 perout_enabled;
bool pps_enabled;
int cap_cmp_irq;
u64 period;
u32 latch_enable;
struct work_struct work;
};
extern const struct icss_iep_clockops prueth_iep_clockops;
/* Firmware specific clock operations */
struct icss_iep_clockops {
void (*settime)(void *clockops_data, u64 ns);
void (*adjtime)(void *clockops_data, s64 delta);
u64 (*gettime)(void *clockops_data, struct ptp_system_timestamp *sts);
int (*perout_enable)(void *clockops_data,
struct ptp_perout_request *req, int on,
u64 *cmp);
int (*extts_enable)(void *clockops_data, u32 index, int on);
};
struct icss_iep *icss_iep_get(struct device_node *np);
struct icss_iep *icss_iep_get_idx(struct device_node *np, int idx);
void icss_iep_put(struct icss_iep *iep);
int icss_iep_init(struct icss_iep *iep, const struct icss_iep_clockops *clkops,
void *clockops_data, u32 cycle_time_ns);
int icss_iep_exit(struct icss_iep *iep);
int icss_iep_get_count_low(struct icss_iep *iep);
int icss_iep_get_count_hi(struct icss_iep *iep);
int icss_iep_get_ptp_clock_idx(struct icss_iep *iep);
void icss_iep_init_fw(struct icss_iep *iep);
void icss_iep_exit_fw(struct icss_iep *iep);
#endif /* __NET_TI_ICSS_IEP_H */
@@ -0,0 +1,534 @@
// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments ICSSG Ethernet Driver
*
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#include <linux/etherdevice.h>
#include <linux/types.h>
#include <linux/regmap.h>
#include "icssg_prueth.h"
#define ICSSG_NUM_CLASSIFIERS 16
#define ICSSG_NUM_FT1_SLOTS 8
#define ICSSG_NUM_FT3_SLOTS 16
#define ICSSG_NUM_CLASSIFIERS_IN_USE 5
/* Filter 1 - FT1 */
#define FT1_NUM_SLOTS 8
#define FT1_SLOT_SIZE 0x10 /* bytes */
/* offsets from FT1 slot base i.e. slot 1 start */
#define FT1_DA0 0x0
#define FT1_DA1 0x4
#define FT1_DA0_MASK 0x8
#define FT1_DA1_MASK 0xc
#define FT1_N_REG(slice, n, reg) \
(offs[slice].ft1_slot_base + FT1_SLOT_SIZE * (n) + (reg))
#define FT1_LEN_MASK GENMASK(19, 16)
#define FT1_LEN_SHIFT 16
#define FT1_LEN(len) (((len) << FT1_LEN_SHIFT) & FT1_LEN_MASK)
#define FT1_START_MASK GENMASK(14, 0)
#define FT1_START(start) ((start) & FT1_START_MASK)
#define FT1_MATCH_SLOT(n) (GENMASK(23, 16) & (BIT(n) << 16))
/* FT1 config type */
enum ft1_cfg_type {
FT1_CFG_TYPE_DISABLED = 0,
FT1_CFG_TYPE_EQ,
FT1_CFG_TYPE_GT,
FT1_CFG_TYPE_LT,
};
#define FT1_CFG_SHIFT(n) (2 * (n))
#define FT1_CFG_MASK(n) (0x3 << FT1_CFG_SHIFT((n)))
/* Filter 3 - FT3 */
#define FT3_NUM_SLOTS 16
#define FT3_SLOT_SIZE 0x20 /* bytes */
/* offsets from FT3 slot n's base */
#define FT3_START 0
#define FT3_START_AUTO 0x4
#define FT3_START_OFFSET 0x8
#define FT3_JUMP_OFFSET 0xc
#define FT3_LEN 0x10
#define FT3_CFG 0x14
#define FT3_T 0x18
#define FT3_T_MASK 0x1c
#define FT3_N_REG(slice, n, reg) \
(offs[slice].ft3_slot_base + FT3_SLOT_SIZE * (n) + (reg))
/* offsets from rx_class n's base */
#define RX_CLASS_AND_EN 0
#define RX_CLASS_OR_EN 0x4
#define RX_CLASS_NUM_SLOTS 16
#define RX_CLASS_EN_SIZE 0x8 /* bytes */
#define RX_CLASS_N_REG(slice, n, reg) \
(offs[slice].rx_class_base + RX_CLASS_EN_SIZE * (n) + (reg))
#define RX_CLASS_OR_REG(slice, n, reg) \
(offs[slice].rx_class_or_base + RX_CLASS_EN_SIZE * (n) + (reg))
/* RX Class Gates */
#define RX_CLASS_GATES_SIZE 0x4 /* bytes */
#define RX_CLASS_GATES_N_REG(slice, n) \
(offs[slice].rx_class_gates_base + RX_CLASS_GATES_SIZE * (n))
#define RX_CLASS_GATES_ALLOW_MASK BIT(6)
#define RX_CLASS_GATES_RAW_MASK BIT(5)
#define RX_CLASS_GATES_PHASE_MASK BIT(4)
/* RX Class traffic data matching bits */
#define RX_CLASS_FT_UC BIT(31)
#define RX_CLASS_FT_MC BIT(30)
#define RX_CLASS_FT_BC BIT(29)
#define RX_CLASS_FT_FW BIT(28)
#define RX_CLASS_FT_RCV BIT(27)
#define RX_CLASS_FT_VLAN BIT(26)
#define RX_CLASS_FT_DA_P BIT(25)
#define RX_CLASS_FT_DA_I BIT(24)
#define RX_CLASS_FT_FT1_MATCH_MASK GENMASK(23, 16)
#define RX_CLASS_FT_FT1_MATCH_SHIFT 16
#define RX_CLASS_FT_FT3_MATCH_MASK GENMASK(15, 0)
#define RX_CLASS_FT_FT3_MATCH_SHIFT 0
#define RX_CLASS_FT_FT1_MATCH(slot) \
((BIT(slot) << RX_CLASS_FT_FT1_MATCH_SHIFT) & \
RX_CLASS_FT_FT1_MATCH_MASK)
/* HSR/PRP protocol frame filtering */
#define HSR_PTP_ETHERTYPE_OFFSET 0x12
#define PRP_PTP_ETHERTYPE_OFFSET 0x0C
#define FT3_PTP_PATTERN 0xF788
#define FT3_ETHERTYPE_MASK 0xFFFF0000U
#define FT3_VLAN_MODE_BOTH 0x1
#define RX_CLASS_OR_DUP_PTP 0x4200
#define RX_CLASS_OR_HSR_TAG 0x4000
#define RX_CLASS_GATE_PTP 0x50
#define RX_CLASS_DISABLE 0x0
/* HSR/PRP classifier indices */
#define CLASSIFIER_PTP_DUP 10
#define CLASSIFIER_HSR_TAG 11
#define FT3_PTP_SLOT 14
/* RX class type */
enum rx_class_sel_type {
RX_CLASS_SEL_TYPE_OR = 0,
RX_CLASS_SEL_TYPE_AND = 1,
RX_CLASS_SEL_TYPE_OR_AND_AND = 2,
RX_CLASS_SEL_TYPE_OR_OR_AND = 3,
};
#define FT1_CFG_SHIFT(n) (2 * (n))
#define FT1_CFG_MASK(n) (0x3 << FT1_CFG_SHIFT((n)))
#define RX_CLASS_SEL_SHIFT(n) (2 * (n))
#define RX_CLASS_SEL_MASK(n) (0x3 << RX_CLASS_SEL_SHIFT((n)))
#define ICSSG_CFG_OFFSET 0
#define MAC_INTERFACE_0 0x18
#define MAC_INTERFACE_1 0x1c
#define ICSSG_CFG_RX_L2_G_EN BIT(2)
/* These are register offsets per PRU */
struct miig_rt_offsets {
u32 mac0;
u32 mac1;
u32 ft1_start_len;
u32 ft1_cfg;
u32 ft1_slot_base;
u32 ft3_slot_base;
u32 ft3_p_base;
u32 ft_rx_ptr;
u32 rx_class_base;
u32 rx_class_or_base;
u32 rx_class_cfg1;
u32 rx_class_cfg2;
u32 rx_class_gates_base;
u32 rx_green;
u32 rx_rate_cfg_base;
u32 rx_rate_src_sel0;
u32 rx_rate_src_sel1;
u32 tx_rate_cfg_base;
u32 stat_base;
u32 tx_hsr_tag;
u32 tx_hsr_seq;
u32 tx_vlan_type;
u32 tx_vlan_ins;
};
/* These are the offset values for miig_rt_offsets registers */
static const struct miig_rt_offsets offs[] = {
/* PRU0 */
{
0x8,
0xc,
0x80,
0x84,
0x88,
0x108,
0x308,
0x408,
0x40c,
0x410,
0x48c,
0x490,
0x494,
0x4d4,
0x4e4,
0x504,
0x508,
0x50c,
0x54c,
0x63c,
0x640,
0x644,
0x648,
},
/* PRU1 */
{
0x10,
0x14,
0x64c,
0x650,
0x654,
0x6d4,
0x8d4,
0x9d4,
0x9d8,
0x9dc,
0xa58,
0xa5c,
0xa60,
0xaa0,
0xab0,
0xad0,
0xad4,
0xad8,
0xb18,
0xc08,
0xc0c,
0xc10,
0xc14,
},
};
static void rx_class_ft1_set_start_len(struct regmap *miig_rt, int slice,
u16 start, u8 len)
{
u32 offset, val;
offset = offs[slice].ft1_start_len;
val = FT1_LEN(len) | FT1_START(start);
regmap_write(miig_rt, offset, val);
}
static void rx_class_ft1_set_da(struct regmap *miig_rt, int slice,
int n, const u8 *addr)
{
u32 offset;
offset = FT1_N_REG(slice, n, FT1_DA0);
regmap_write(miig_rt, offset, (u32)(addr[0] | addr[1] << 8 |
addr[2] << 16 | addr[3] << 24));
offset = FT1_N_REG(slice, n, FT1_DA1);
regmap_write(miig_rt, offset, (u32)(addr[4] | addr[5] << 8));
}
static void rx_class_ft1_set_da_mask(struct regmap *miig_rt, int slice,
int n, const u8 *addr)
{
u32 offset;
offset = FT1_N_REG(slice, n, FT1_DA0_MASK);
regmap_write(miig_rt, offset, (u32)(addr[0] | addr[1] << 8 |
addr[2] << 16 | addr[3] << 24));
offset = FT1_N_REG(slice, n, FT1_DA1_MASK);
regmap_write(miig_rt, offset, (u32)(addr[4] | addr[5] << 8));
}
static void rx_class_ft1_cfg_set_type(struct regmap *miig_rt, int slice, int n,
enum ft1_cfg_type type)
{
u32 offset;
offset = offs[slice].ft1_cfg;
regmap_update_bits(miig_rt, offset, FT1_CFG_MASK(n),
type << FT1_CFG_SHIFT(n));
}
static void rx_class_sel_set_type(struct regmap *miig_rt, int slice, int n,
enum rx_class_sel_type type)
{
u32 offset;
offset = offs[slice].rx_class_cfg1;
regmap_update_bits(miig_rt, offset, RX_CLASS_SEL_MASK(n),
type << RX_CLASS_SEL_SHIFT(n));
}
static void rx_class_set_and(struct regmap *miig_rt, int slice, int n,
u32 data)
{
u32 offset;
offset = RX_CLASS_N_REG(slice, n, RX_CLASS_AND_EN);
regmap_write(miig_rt, offset, data);
}
static void rx_class_set_or(struct regmap *miig_rt, int slice, int n,
u32 data)
{
u32 offset;
offset = RX_CLASS_N_REG(slice, n, RX_CLASS_OR_EN);
regmap_write(miig_rt, offset, data);
}
static u32 rx_class_get_or(struct regmap *miig_rt, int slice, int n)
{
u32 offset, val;
offset = RX_CLASS_N_REG(slice, n, RX_CLASS_OR_EN);
regmap_read(miig_rt, offset, &val);
return val;
}
void icssg_class_set_host_mac_addr(struct regmap *miig_rt, const u8 *mac)
{
regmap_write(miig_rt, MAC_INTERFACE_0, (u32)(mac[0] | mac[1] << 8 |
mac[2] << 16 | mac[3] << 24));
regmap_write(miig_rt, MAC_INTERFACE_1, (u32)(mac[4] | mac[5] << 8));
}
EXPORT_SYMBOL_GPL(icssg_class_set_host_mac_addr);
void icssg_class_set_mac_addr(struct regmap *miig_rt, int slice, u8 *mac)
{
regmap_write(miig_rt, offs[slice].mac0, (u32)(mac[0] | mac[1] << 8 |
mac[2] << 16 | mac[3] << 24));
regmap_write(miig_rt, offs[slice].mac1, (u32)(mac[4] | mac[5] << 8));
}
EXPORT_SYMBOL_GPL(icssg_class_set_mac_addr);
static void icssg_class_ft1_add_mcast(struct regmap *miig_rt, int slice,
int slot, const u8 *addr, const u8 *mask)
{
u32 val;
int i;
WARN(slot >= FT1_NUM_SLOTS, "invalid slot: %d\n", slot);
rx_class_ft1_set_da(miig_rt, slice, slot, addr);
rx_class_ft1_set_da_mask(miig_rt, slice, slot, mask);
rx_class_ft1_cfg_set_type(miig_rt, slice, slot, FT1_CFG_TYPE_EQ);
/* Enable the FT1 slot in OR enable for all classifiers */
for (i = 0; i < ICSSG_NUM_CLASSIFIERS_IN_USE; i++) {
val = rx_class_get_or(miig_rt, slice, i);
val |= RX_CLASS_FT_FT1_MATCH(slot);
rx_class_set_or(miig_rt, slice, i, val);
}
}
/* disable all RX traffic */
void icssg_class_disable(struct regmap *miig_rt, int slice)
{
u32 data, offset;
int n;
/* Enable RX_L2_G */
regmap_update_bits(miig_rt, ICSSG_CFG_OFFSET, ICSSG_CFG_RX_L2_G_EN,
ICSSG_CFG_RX_L2_G_EN);
for (n = 0; n < ICSSG_NUM_CLASSIFIERS; n++) {
/* AND_EN = 0 */
rx_class_set_and(miig_rt, slice, n, 0);
/* OR_EN = 0 */
rx_class_set_or(miig_rt, slice, n, 0);
/* set CFG1 to OR */
rx_class_sel_set_type(miig_rt, slice, n, RX_CLASS_SEL_TYPE_OR);
/* configure gate */
offset = RX_CLASS_GATES_N_REG(slice, n);
regmap_read(miig_rt, offset, &data);
/* clear class_raw so we go through filters */
data &= ~RX_CLASS_GATES_RAW_MASK;
/* set allow and phase mask */
data |= RX_CLASS_GATES_ALLOW_MASK | RX_CLASS_GATES_PHASE_MASK;
regmap_write(miig_rt, offset, data);
}
/* FT1 Disabled */
for (n = 0; n < ICSSG_NUM_FT1_SLOTS; n++) {
const u8 addr[] = { 0, 0, 0, 0, 0, 0, };
rx_class_ft1_cfg_set_type(miig_rt, slice, n,
FT1_CFG_TYPE_DISABLED);
rx_class_ft1_set_da(miig_rt, slice, n, addr);
rx_class_ft1_set_da_mask(miig_rt, slice, n, addr);
}
/* clear CFG2 */
regmap_write(miig_rt, offs[slice].rx_class_cfg2, 0);
}
EXPORT_SYMBOL_GPL(icssg_class_disable);
void icssg_class_default(struct regmap *miig_rt, int slice, bool allmulti,
bool is_sr1)
{
int num_classifiers = is_sr1 ? ICSSG_NUM_CLASSIFIERS_IN_USE : 1;
u32 data;
int n;
/* defaults */
icssg_class_disable(miig_rt, slice);
/* Setup Classifier */
for (n = 0; n < num_classifiers; n++) {
/* match on Broadcast or MAC_PRU address */
data = RX_CLASS_FT_BC | RX_CLASS_FT_DA_P;
/* multicast */
if (allmulti)
data |= RX_CLASS_FT_MC;
rx_class_set_or(miig_rt, slice, n, data);
/* set CFG1 for OR_OR_AND for classifier */
rx_class_sel_set_type(miig_rt, slice, n,
RX_CLASS_SEL_TYPE_OR_OR_AND);
}
/* clear CFG2 */
regmap_write(miig_rt, offs[slice].rx_class_cfg2, 0);
}
EXPORT_SYMBOL_GPL(icssg_class_default);
void icssg_class_promiscuous_sr1(struct regmap *miig_rt, int slice)
{
u32 data, offset;
int n;
/* defaults */
icssg_class_disable(miig_rt, slice);
/* Setup Classifier */
for (n = 0; n < ICSSG_NUM_CLASSIFIERS_IN_USE; n++) {
/* set RAW_MASK to bypass filters */
offset = RX_CLASS_GATES_N_REG(slice, n);
regmap_read(miig_rt, offset, &data);
data |= RX_CLASS_GATES_RAW_MASK;
regmap_write(miig_rt, offset, data);
}
}
EXPORT_SYMBOL_GPL(icssg_class_promiscuous_sr1);
void icssg_class_add_mcast_sr1(struct regmap *miig_rt, int slice,
struct net_device *ndev)
{
u8 mask_addr[6] = { 0, 0, 0, 0, 0, 0xff };
struct netdev_hw_addr *ha;
int slot = 2;
rx_class_ft1_set_start_len(miig_rt, slice, 0, 6);
/* reserve first 2 slots for
* 1) 01-80-C2-00-00-XX Known Service Ethernet Multicast addresses
* 2) 01-00-5e-00-00-XX Local Network Control Block
* (224.0.0.0 - 224.0.0.255 (224.0.0/24))
*/
icssg_class_ft1_add_mcast(miig_rt, slice, 0,
eth_reserved_addr_base, mask_addr);
icssg_class_ft1_add_mcast(miig_rt, slice, 1,
eth_ipv4_mcast_addr_base, mask_addr);
mask_addr[5] = 0;
netdev_for_each_mc_addr(ha, ndev) {
/* skip addresses matching reserved slots */
if (!memcmp(eth_reserved_addr_base, ha->addr, 5) ||
!memcmp(eth_ipv4_mcast_addr_base, ha->addr, 5)) {
netdev_dbg(ndev, "mcast skip %pM\n", ha->addr);
continue;
}
if (slot >= FT1_NUM_SLOTS) {
netdev_dbg(ndev,
"can't add more than %d MC addresses, enabling allmulti\n",
FT1_NUM_SLOTS);
icssg_class_default(miig_rt, slice, 1, true);
break;
}
netdev_dbg(ndev, "mcast add %pM\n", ha->addr);
icssg_class_ft1_add_mcast(miig_rt, slice, slot,
ha->addr, mask_addr);
slot++;
}
}
EXPORT_SYMBOL_GPL(icssg_class_add_mcast_sr1);
/* required for SAV check */
void icssg_ft1_set_mac_addr(struct regmap *miig_rt, int slice, u8 *mac_addr)
{
const u8 mask_addr[] = { 0, 0, 0, 0, 0, 0, };
rx_class_ft1_set_start_len(miig_rt, slice, ETH_ALEN, ETH_ALEN);
rx_class_ft1_set_da(miig_rt, slice, 0, mac_addr);
rx_class_ft1_set_da_mask(miig_rt, slice, 0, mask_addr);
rx_class_ft1_cfg_set_type(miig_rt, slice, 0, FT1_CFG_TYPE_EQ);
}
EXPORT_SYMBOL_GPL(icssg_ft1_set_mac_addr);
/**
* icssg_ft3_hsr_configurations - Configure filter table for HSR/PRP protocol frames
* @miig_rt: Pointer to the MII-G register map
* @slice: ICSSG slice number (0 or 1)
* @prueth: Pointer to prueth structure to determine HSR/PRP mode
*
* Configures FT3 to detect PTP frames (EtherType 0x88F7) in HSR/PRP tagged packets.
* HSR frames have a 6-byte tag, while PRP has no tag offset for EtherType detection.
*/
void icssg_ft3_hsr_configurations(struct regmap *miig_rt, int slice,
struct prueth *prueth)
{
u8 offset = (prueth->hsr_prp_version == PRP_V1) ?
PRP_PTP_ETHERTYPE_OFFSET : HSR_PTP_ETHERTYPE_OFFSET;
regmap_write(miig_rt, FT3_N_REG(slice, FT3_PTP_SLOT, FT3_START), offset);
regmap_write(miig_rt, FT3_N_REG(slice, FT3_PTP_SLOT, FT3_START_AUTO), 0);
regmap_write(miig_rt, FT3_N_REG(slice, FT3_PTP_SLOT, FT3_START_OFFSET), 0);
regmap_write(miig_rt, FT3_N_REG(slice, FT3_PTP_SLOT, FT3_JUMP_OFFSET), 0);
regmap_write(miig_rt, FT3_N_REG(slice, FT3_PTP_SLOT, FT3_LEN), 0);
regmap_write(miig_rt, FT3_N_REG(slice, FT3_PTP_SLOT, FT3_CFG), FT3_VLAN_MODE_BOTH);
regmap_write(miig_rt, FT3_N_REG(slice, FT3_PTP_SLOT, FT3_T), FT3_PTP_PATTERN);
regmap_write(miig_rt, FT3_N_REG(slice, FT3_PTP_SLOT, FT3_T_MASK),
FT3_ETHERTYPE_MASK);
regmap_write(miig_rt, RX_CLASS_N_REG(slice, CLASSIFIER_PTP_DUP, RX_CLASS_AND_EN),
RX_CLASS_DISABLE);
regmap_write(miig_rt, RX_CLASS_OR_REG(slice, CLASSIFIER_PTP_DUP, RX_CLASS_AND_EN),
RX_CLASS_OR_DUP_PTP);
regmap_write(miig_rt, RX_CLASS_GATES_N_REG(slice, CLASSIFIER_PTP_DUP),
RX_CLASS_GATE_PTP);
if (prueth->hsr_prp_version != PRP_V1) {
regmap_write(miig_rt, RX_CLASS_N_REG(slice, CLASSIFIER_HSR_TAG, RX_CLASS_AND_EN),
RX_CLASS_DISABLE);
regmap_write(miig_rt, RX_CLASS_OR_REG(slice, CLASSIFIER_HSR_TAG, RX_CLASS_AND_EN),
RX_CLASS_OR_HSR_TAG);
regmap_write(miig_rt, RX_CLASS_GATES_N_REG(slice, CLASSIFIER_HSR_TAG),
RX_CLASS_GATE_PTP);
}
}
EXPORT_SYMBOL_GPL(icssg_ft3_hsr_configurations);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,864 @@
// SPDX-License-Identifier: GPL-2.0
/* ICSSG Ethernet driver
*
* Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com
*/
#include <linux/iopoll.h>
#include <linux/regmap.h>
#include <uapi/linux/if_ether.h>
#include "icssg_config.h"
#include "icssg_prueth.h"
#include "icssg_switch_map.h"
#include "icssg_mii_rt.h"
/* TX IPG Values to be set for 100M link speed. These values are
* in ocp_clk cycles. So need change if ocp_clk is changed for a specific
* h/w design.
*/
/* IPG is in core_clk cycles */
#define MII_RT_TX_IPG_100M 0x17
#define MII_RT_TX_IPG_1G 0xb
#define MII_RT_TX_IPG_100M_SR1 0x166
#define MII_RT_TX_IPG_1G_SR1 0x1a
#define ICSSG_QUEUES_MAX 64
#define ICSSG_QUEUE_OFFSET 0xd00
#define ICSSG_QUEUE_PEEK_OFFSET 0xe00
#define ICSSG_QUEUE_CNT_OFFSET 0xe40
#define ICSSG_QUEUE_RESET_OFFSET 0xf40
#define ICSSG_NUM_TX_QUEUES 8
#define RECYCLE_Q_SLICE0 16
#define RECYCLE_Q_SLICE1 17
#define ICSSG_NUM_OTHER_QUEUES 5 /* port, host and special queues */
#define PORT_HI_Q_SLICE0 32
#define PORT_LO_Q_SLICE0 33
#define HOST_HI_Q_SLICE0 34
#define HOST_LO_Q_SLICE0 35
#define HOST_SPL_Q_SLICE0 40 /* Special Queue */
#define PORT_HI_Q_SLICE1 36
#define PORT_LO_Q_SLICE1 37
#define HOST_HI_Q_SLICE1 38
#define HOST_LO_Q_SLICE1 39
#define HOST_SPL_Q_SLICE1 41 /* Special Queue */
#define MII_RXCFG_DEFAULT (PRUSS_MII_RT_RXCFG_RX_ENABLE | \
PRUSS_MII_RT_RXCFG_RX_DATA_RDY_MODE_DIS | \
PRUSS_MII_RT_RXCFG_RX_L2_EN | \
PRUSS_MII_RT_RXCFG_RX_L2_EOF_SCLR_DIS)
#define MII_TXCFG_DEFAULT (PRUSS_MII_RT_TXCFG_TX_ENABLE | \
PRUSS_MII_RT_TXCFG_TX_AUTO_PREAMBLE | \
PRUSS_MII_RT_TXCFG_TX_32_MODE_EN | \
PRUSS_MII_RT_TXCFG_TX_IPG_WIRE_CLK_EN)
#define ICSSG_CFG_DEFAULT (ICSSG_CFG_TX_L1_EN | \
ICSSG_CFG_TX_L2_EN | ICSSG_CFG_RX_L2_G_EN | \
ICSSG_CFG_TX_PRU_EN | \
ICSSG_CFG_SGMII_MODE)
#define FDB_GEN_CFG1 0x60
#define SMEM_VLAN_OFFSET 8
#define SMEM_VLAN_OFFSET_MASK GENMASK(25, 8)
#define FDB_HASH_SIZE_MASK GENMASK(6, 3)
#define FDB_HASH_SIZE_SHIFT 3
#define FDB_HASH_SIZE 3
#define FDB_GEN_CFG2 0x64
#define FDB_VLAN_EN BIT(6)
#define FDB_HOST_EN BIT(2)
#define FDB_PRU1_EN BIT(1)
#define FDB_PRU0_EN BIT(0)
#define FDB_EN_ALL (FDB_PRU0_EN | FDB_PRU1_EN | \
FDB_HOST_EN | FDB_VLAN_EN)
/**
* struct map - ICSSG Queue Map
* @queue: Queue number
* @pd_addr_start: Packet descriptor queue reserved memory
* @flags: Flags
* @special: Indicates whether this queue is a special queue or not
*/
struct map {
int queue;
u32 pd_addr_start;
u32 flags;
bool special;
};
/* Hardware queue map for ICSSG */
static const struct map hwq_map[2][ICSSG_NUM_OTHER_QUEUES] = {
{
{ PORT_HI_Q_SLICE0, PORT_DESC0_HI, 0x200000, 0 },
{ PORT_LO_Q_SLICE0, PORT_DESC0_LO, 0, 0 },
{ HOST_HI_Q_SLICE0, HOST_DESC0_HI, 0x200000, 0 },
{ HOST_LO_Q_SLICE0, HOST_DESC0_LO, 0, 0 },
{ HOST_SPL_Q_SLICE0, HOST_SPPD0, 0x400000, 1 },
},
{
{ PORT_HI_Q_SLICE1, PORT_DESC1_HI, 0xa00000, 0 },
{ PORT_LO_Q_SLICE1, PORT_DESC1_LO, 0x800000, 0 },
{ HOST_HI_Q_SLICE1, HOST_DESC1_HI, 0xa00000, 0 },
{ HOST_LO_Q_SLICE1, HOST_DESC1_LO, 0x800000, 0 },
{ HOST_SPL_Q_SLICE1, HOST_SPPD1, 0xc00000, 1 },
},
};
static void icssg_config_mii_init_fw_offload(struct prueth_emac *emac)
{
struct prueth *prueth = emac->prueth;
int mii = prueth_emac_slice(emac);
u32 txcfg_reg, pcnt_reg, txcfg;
struct regmap *mii_rt;
mii_rt = prueth->mii_rt;
txcfg_reg = (mii == ICSS_MII0) ? PRUSS_MII_RT_TXCFG0 :
PRUSS_MII_RT_TXCFG1;
pcnt_reg = (mii == ICSS_MII0) ? PRUSS_MII_RT_RX_PCNT0 :
PRUSS_MII_RT_RX_PCNT1;
txcfg = PRUSS_MII_RT_TXCFG_TX_ENABLE |
PRUSS_MII_RT_TXCFG_TX_AUTO_PREAMBLE |
PRUSS_MII_RT_TXCFG_TX_IPG_WIRE_CLK_EN;
if (emac->phy_if == PHY_INTERFACE_MODE_MII && mii == ICSS_MII1)
txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL;
else if (emac->phy_if != PHY_INTERFACE_MODE_MII && mii == ICSS_MII0)
txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL;
regmap_write(mii_rt, txcfg_reg, txcfg);
regmap_write(mii_rt, pcnt_reg, 0x1);
}
static void icssg_config_mii_init(struct prueth_emac *emac)
{
struct prueth *prueth = emac->prueth;
int slice = prueth_emac_slice(emac);
u32 txcfg, txcfg_reg, pcnt_reg;
struct regmap *mii_rt;
mii_rt = prueth->mii_rt;
txcfg_reg = (slice == ICSS_MII0) ? PRUSS_MII_RT_TXCFG0 :
PRUSS_MII_RT_TXCFG1;
pcnt_reg = (slice == ICSS_MII0) ? PRUSS_MII_RT_RX_PCNT0 :
PRUSS_MII_RT_RX_PCNT1;
txcfg = MII_TXCFG_DEFAULT;
/* In MII mode TX lines swapped inside ICSSG, so TX_MUX_SEL cfg need
* to be swapped also comparing to RGMII mode.
*/
if (emac->phy_if == PHY_INTERFACE_MODE_MII && slice == ICSS_MII0)
txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL;
else if (emac->phy_if != PHY_INTERFACE_MODE_MII && slice == ICSS_MII1)
txcfg |= PRUSS_MII_RT_TXCFG_TX_MUX_SEL;
regmap_write(mii_rt, txcfg_reg, txcfg);
regmap_write(mii_rt, pcnt_reg, 0x1);
}
static void icssg_miig_queues_init(struct prueth *prueth, int slice)
{
struct regmap *miig_rt = prueth->miig_rt;
void __iomem *smem = prueth->shram.va;
u8 pd[ICSSG_SPECIAL_PD_SIZE];
int queue = 0, i, j;
u32 *pdword;
/* reset hwqueues */
if (slice)
queue = ICSSG_NUM_TX_QUEUES;
for (i = 0; i < ICSSG_NUM_TX_QUEUES; i++) {
regmap_write(miig_rt, ICSSG_QUEUE_RESET_OFFSET, queue);
queue++;
}
queue = slice ? RECYCLE_Q_SLICE1 : RECYCLE_Q_SLICE0;
regmap_write(miig_rt, ICSSG_QUEUE_RESET_OFFSET, queue);
for (i = 0; i < ICSSG_NUM_OTHER_QUEUES; i++) {
regmap_write(miig_rt, ICSSG_QUEUE_RESET_OFFSET,
hwq_map[slice][i].queue);
}
/* initialize packet descriptors in SMEM */
/* push pakcet descriptors to hwqueues */
pdword = (u32 *)pd;
for (j = 0; j < ICSSG_NUM_OTHER_QUEUES; j++) {
const struct map *mp;
int pd_size, num_pds;
u32 pdaddr;
mp = &hwq_map[slice][j];
if (mp->special) {
pd_size = ICSSG_SPECIAL_PD_SIZE;
num_pds = ICSSG_NUM_SPECIAL_PDS;
} else {
pd_size = ICSSG_NORMAL_PD_SIZE;
num_pds = ICSSG_NUM_NORMAL_PDS;
}
for (i = 0; i < num_pds; i++) {
memset(pd, 0, pd_size);
pdword[0] &= ICSSG_FLAG_MASK;
pdword[0] |= mp->flags;
pdaddr = mp->pd_addr_start + i * pd_size;
memcpy_toio(smem + pdaddr, pd, pd_size);
queue = mp->queue;
regmap_write(miig_rt, ICSSG_QUEUE_OFFSET + 4 * queue,
pdaddr);
}
}
}
void icssg_config_ipg(struct prueth_emac *emac)
{
struct prueth *prueth = emac->prueth;
int slice = prueth_emac_slice(emac);
u32 ipg;
switch (emac->speed) {
case SPEED_1000:
ipg = emac->is_sr1 ? MII_RT_TX_IPG_1G_SR1 : MII_RT_TX_IPG_1G;
break;
case SPEED_100:
ipg = emac->is_sr1 ? MII_RT_TX_IPG_100M_SR1 : MII_RT_TX_IPG_100M;
break;
case SPEED_10:
/* Firmware hardcodes IPG for SR1.0 */
if (emac->is_sr1)
return;
/* IPG for 10M is same as 100M */
ipg = MII_RT_TX_IPG_100M;
break;
default:
/* Other links speeds not supported */
netdev_err(emac->ndev, "Unsupported link speed\n");
return;
}
icssg_mii_update_ipg(prueth->mii_rt, slice, ipg);
}
EXPORT_SYMBOL_GPL(icssg_config_ipg);
static void emac_r30_cmd_init(struct prueth_emac *emac)
{
struct icssg_r30_cmd __iomem *p;
int i;
p = emac->dram.va + MGR_R30_CMD_OFFSET;
for (i = 0; i < 4; i++)
writel(EMAC_NONE, &p->cmd[i]);
}
static int emac_r30_is_done(struct prueth_emac *emac)
{
const struct icssg_r30_cmd __iomem *p;
u32 cmd;
int i;
p = emac->dram.va + MGR_R30_CMD_OFFSET;
for (i = 0; i < 4; i++) {
cmd = readl(&p->cmd[i]);
if (cmd != EMAC_NONE)
return 0;
}
return 1;
}
static int prueth_fw_offload_buffer_setup(struct prueth_emac *emac)
{
struct icssg_buffer_pool_cfg __iomem *bpool_cfg;
struct icssg_rxq_ctx __iomem *rxq_ctx;
struct prueth *prueth = emac->prueth;
int slice = prueth_emac_slice(emac);
u32 addr;
int i;
addr = lower_32_bits(prueth->msmcram.pa);
if (slice) {
if (prueth->pdata.banked_ms_ram)
addr += MSMC_RAM_BANK_SIZE;
else
addr += PRUETH_SW_TOTAL_BUF_SIZE_PER_SLICE;
}
if (addr % SZ_64K) {
dev_warn(prueth->dev, "buffer pool needs to be 64KB aligned\n");
return -EINVAL;
}
bpool_cfg = emac->dram.va + BUFFER_POOL_0_ADDR_OFFSET;
/* Configure buffer pools for forwarding buffers
* - used by firmware to store packets to be forwarded to other port
* - 8 total pools per slice
*/
for (i = 0; i < PRUETH_NUM_FWD_BUF_POOLS_PER_SLICE; i++) {
writel(addr, &bpool_cfg[i].addr);
writel(PRUETH_SW_FWD_BUF_POOL_SIZE, &bpool_cfg[i].len);
addr += PRUETH_SW_FWD_BUF_POOL_SIZE;
}
/* Configure buffer pools for Local Injection buffers
* - used by firmware to store packets received from host core
* - 16 total pools per slice
*/
for (i = 0; i < PRUETH_NUM_LI_BUF_POOLS_PER_SLICE; i++) {
int cfg_idx = i + PRUETH_NUM_FWD_BUF_POOLS_PER_SLICE;
/* The driver only uses first 4 queues per PRU,
* so only initialize buffer for them
*/
if ((i % PRUETH_NUM_LI_BUF_POOLS_PER_PORT_PER_SLICE)
< PRUETH_SW_USED_LI_BUF_POOLS_PER_PORT_PER_SLICE) {
writel(addr, &bpool_cfg[cfg_idx].addr);
writel(PRUETH_SW_LI_BUF_POOL_SIZE,
&bpool_cfg[cfg_idx].len);
addr += PRUETH_SW_LI_BUF_POOL_SIZE;
} else {
writel(0, &bpool_cfg[cfg_idx].addr);
writel(0, &bpool_cfg[cfg_idx].len);
}
}
/* Express RX buffer queue
* - used by firmware to store express packets to be transmitted
* to the host core
*/
rxq_ctx = emac->dram.va + HOST_RX_Q_EXP_CONTEXT_OFFSET;
for (i = 0; i < 3; i++)
writel(addr, &rxq_ctx->start[i]);
addr += PRUETH_SW_HOST_EXP_BUF_POOL_SIZE;
writel(addr, &rxq_ctx->end);
/* Pre-emptible RX buffer queue
* - used by firmware to store preemptible packets to be transmitted
* to the host core
*/
rxq_ctx = emac->dram.va + HOST_RX_Q_PRE_CONTEXT_OFFSET;
for (i = 0; i < 3; i++)
writel(addr, &rxq_ctx->start[i]);
addr += PRUETH_SW_HOST_PRE_BUF_POOL_SIZE;
writel(addr, &rxq_ctx->end);
/* Set pointer for default dropped packet write
* - used by firmware to temporarily store packet to be dropped
*/
rxq_ctx = emac->dram.va + DEFAULT_MSMC_Q_OFFSET;
writel(addr, &rxq_ctx->start[0]);
return 0;
}
static int prueth_emac_buffer_setup(struct prueth_emac *emac)
{
struct icssg_buffer_pool_cfg __iomem *bpool_cfg;
struct icssg_rxq_ctx __iomem *rxq_ctx;
struct prueth *prueth = emac->prueth;
int slice = prueth_emac_slice(emac);
u32 addr;
int i;
addr = lower_32_bits(prueth->msmcram.pa);
if (slice) {
if (prueth->pdata.banked_ms_ram)
addr += MSMC_RAM_BANK_SIZE;
else
addr += PRUETH_EMAC_TOTAL_BUF_SIZE_PER_SLICE;
}
if (addr % SZ_64K) {
dev_warn(prueth->dev, "buffer pool needs to be 64KB aligned\n");
return -EINVAL;
}
bpool_cfg = emac->dram.va + BUFFER_POOL_0_ADDR_OFFSET;
/* Configure buffer pools for forwarding buffers
* - in mac mode - no forwarding so initialize all pools to 0
* - 8 total pools per slice
*/
for (i = 0; i < PRUETH_NUM_FWD_BUF_POOLS_PER_SLICE; i++) {
writel(0, &bpool_cfg[i].addr);
writel(0, &bpool_cfg[i].len);
}
/* Configure buffer pools for Local Injection buffers
* - used by firmware to store packets received from host core
* - 16 total pools per slice
*/
bpool_cfg = emac->dram.va + BUFFER_POOL_0_ADDR_OFFSET;
for (i = 0; i < PRUETH_NUM_LI_BUF_POOLS_PER_SLICE; i++) {
int cfg_idx = i + PRUETH_NUM_FWD_BUF_POOLS_PER_SLICE;
/* In EMAC mode, only first 4 buffers are used,
* as 1 slice needs to handle only 1 port
*/
if (i < PRUETH_EMAC_USED_LI_BUF_POOLS_PER_PORT_PER_SLICE) {
writel(addr, &bpool_cfg[cfg_idx].addr);
writel(PRUETH_EMAC_LI_BUF_POOL_SIZE,
&bpool_cfg[cfg_idx].len);
addr += PRUETH_EMAC_LI_BUF_POOL_SIZE;
} else {
writel(0, &bpool_cfg[cfg_idx].addr);
writel(0, &bpool_cfg[cfg_idx].len);
}
}
/* Express RX buffer queue
* - used by firmware to store express packets to be transmitted
* to host core
*/
rxq_ctx = emac->dram.va + HOST_RX_Q_EXP_CONTEXT_OFFSET;
for (i = 0; i < 3; i++)
writel(addr, &rxq_ctx->start[i]);
addr += PRUETH_EMAC_HOST_EXP_BUF_POOL_SIZE;
writel(addr, &rxq_ctx->end);
/* Pre-emptible RX buffer queue
* - used by firmware to store preemptible packets to be transmitted
* to host core
*/
rxq_ctx = emac->dram.va + HOST_RX_Q_PRE_CONTEXT_OFFSET;
for (i = 0; i < 3; i++)
writel(addr, &rxq_ctx->start[i]);
addr += PRUETH_EMAC_HOST_PRE_BUF_POOL_SIZE;
writel(addr, &rxq_ctx->end);
/* Set pointer for default dropped packet write
* - used by firmware to temporarily store packet to be dropped
*/
rxq_ctx = emac->dram.va + DEFAULT_MSMC_Q_OFFSET;
writel(addr, &rxq_ctx->start[0]);
return 0;
}
void icssg_init_emac_mode(struct prueth *prueth)
{
/* When the device is configured as a bridge and it is being brought
* back to the emac mode, the host mac address has to be set as 0.
*/
u32 addr = prueth->shram.pa + EMAC_ICSSG_SWITCH_DEFAULT_VLAN_TABLE_OFFSET;
int i;
u8 mac[ETH_ALEN] = { 0 };
/* Set VLAN TABLE address base */
regmap_update_bits(prueth->miig_rt, FDB_GEN_CFG1, SMEM_VLAN_OFFSET_MASK,
addr << SMEM_VLAN_OFFSET);
regmap_update_bits(prueth->miig_rt, FDB_GEN_CFG1, FDB_HASH_SIZE_MASK,
FDB_HASH_SIZE << FDB_HASH_SIZE_SHIFT);
/* Set enable VLAN aware mode, and FDBs for all PRUs */
regmap_write(prueth->miig_rt, FDB_GEN_CFG2, (FDB_PRU0_EN | FDB_PRU1_EN | FDB_HOST_EN));
prueth->vlan_tbl = (struct prueth_vlan_tbl __force *)(prueth->shram.va +
EMAC_ICSSG_SWITCH_DEFAULT_VLAN_TABLE_OFFSET);
for (i = 0; i < SZ_4K - 1; i++) {
prueth->vlan_tbl[i].fid = i;
prueth->vlan_tbl[i].fid_c1 = 0;
}
/* Clear host MAC address */
icssg_class_set_host_mac_addr(prueth->miig_rt, mac);
}
EXPORT_SYMBOL_GPL(icssg_init_emac_mode);
void icssg_init_fw_offload_mode(struct prueth *prueth)
{
u32 addr = prueth->shram.pa + EMAC_ICSSG_SWITCH_DEFAULT_VLAN_TABLE_OFFSET;
int i;
/* Set VLAN TABLE address base */
regmap_update_bits(prueth->miig_rt, FDB_GEN_CFG1, SMEM_VLAN_OFFSET_MASK,
addr << SMEM_VLAN_OFFSET);
regmap_update_bits(prueth->miig_rt, FDB_GEN_CFG1, FDB_HASH_SIZE_MASK,
FDB_HASH_SIZE << FDB_HASH_SIZE_SHIFT);
/* Set enable VLAN aware mode, and FDBs for all PRUs */
regmap_write(prueth->miig_rt, FDB_GEN_CFG2, FDB_EN_ALL);
prueth->vlan_tbl = (struct prueth_vlan_tbl __force *)(prueth->shram.va +
EMAC_ICSSG_SWITCH_DEFAULT_VLAN_TABLE_OFFSET);
for (i = 0; i < SZ_4K - 1; i++) {
prueth->vlan_tbl[i].fid = i;
prueth->vlan_tbl[i].fid_c1 = 0;
}
if (prueth->hw_bridge_dev)
icssg_class_set_host_mac_addr(prueth->miig_rt, prueth->hw_bridge_dev->dev_addr);
icssg_set_pvid(prueth, prueth->default_vlan, PRUETH_PORT_HOST);
}
EXPORT_SYMBOL_GPL(icssg_init_fw_offload_mode);
int icssg_config(struct prueth *prueth, struct prueth_emac *emac, int slice)
{
void __iomem *config = emac->dram.va + ICSSG_CONFIG_OFFSET;
struct icssg_flow_cfg __iomem *flow_cfg;
int ret;
memset_io(config, 0, TAS_GATE_MASK_LIST0);
icssg_miig_queues_init(prueth, slice);
emac->speed = SPEED_1000;
emac->duplex = DUPLEX_FULL;
if (!phy_interface_mode_is_rgmii(emac->phy_if)) {
emac->speed = SPEED_100;
emac->duplex = DUPLEX_FULL;
}
regmap_update_bits(prueth->miig_rt, ICSSG_CFG_OFFSET,
ICSSG_CFG_DEFAULT, ICSSG_CFG_DEFAULT);
icssg_miig_set_interface_mode(prueth->miig_rt, slice, emac->phy_if);
if (prueth->is_switch_mode || prueth->is_hsr_offload_mode)
icssg_config_mii_init_fw_offload(emac);
else
icssg_config_mii_init(emac);
icssg_config_ipg(emac);
icssg_update_rgmii_cfg(prueth->miig_rt, emac);
/* set GPI mode */
pruss_cfg_gpimode(prueth->pruss, prueth->pru_id[slice],
PRUSS_GPI_MODE_MII);
/* enable XFR shift for PRU and RTU */
pruss_cfg_xfr_enable(prueth->pruss, PRU_TYPE_PRU, true);
pruss_cfg_xfr_enable(prueth->pruss, PRU_TYPE_RTU, true);
/* set C28 to 0x100 */
pru_rproc_set_ctable(prueth->pru[slice], PRU_C28, 0x100 << 8);
pru_rproc_set_ctable(prueth->rtu[slice], PRU_C28, 0x100 << 8);
pru_rproc_set_ctable(prueth->txpru[slice], PRU_C28, 0x100 << 8);
flow_cfg = config + PSI_L_REGULAR_FLOW_ID_BASE_OFFSET;
writew(emac->rx_flow_id_base, &flow_cfg->rx_base_flow);
writew(0, &flow_cfg->mgm_base_flow);
writeb(0, config + SPL_PKT_DEFAULT_PRIORITY);
writeb(0, config + QUEUE_NUM_UNTAGGED);
if (prueth->is_switch_mode || prueth->is_hsr_offload_mode)
ret = prueth_fw_offload_buffer_setup(emac);
else
ret = prueth_emac_buffer_setup(emac);
if (ret)
return ret;
emac_r30_cmd_init(emac);
return 0;
}
EXPORT_SYMBOL_GPL(icssg_config);
/* Bitmask for ICSSG r30 commands */
static const struct icssg_r30_cmd emac_r32_bitmask[] = {
{{0xffff0004, 0xffff0100, 0xffff0004, EMAC_NONE}}, /* EMAC_PORT_DISABLE */
{{0xfffb0040, 0xfeff0200, 0xfeff0200, EMAC_NONE}}, /* EMAC_PORT_BLOCK */
{{0xffbb0000, 0xfcff0000, 0xdcfb0000, EMAC_NONE}}, /* EMAC_PORT_FORWARD */
{{0xffbb0000, 0xfcff0000, 0xfcff2000, EMAC_NONE}}, /* EMAC_PORT_FORWARD_WO_LEARNING */
{{0xffff0001, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT ALL */
{{0xfffe0002, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT TAGGED */
{{0xfffc0000, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* ACCEPT UNTAGGED and PRIO */
{{EMAC_NONE, 0xffff0020, EMAC_NONE, EMAC_NONE}}, /* TAS Trigger List change */
{{EMAC_NONE, 0xdfff1000, EMAC_NONE, EMAC_NONE}}, /* TAS set state ENABLE*/
{{EMAC_NONE, 0xefff2000, EMAC_NONE, EMAC_NONE}}, /* TAS set state RESET*/
{{EMAC_NONE, 0xcfff0000, EMAC_NONE, EMAC_NONE}}, /* TAS set state DISABLE*/
{{EMAC_NONE, EMAC_NONE, 0xffff0400, EMAC_NONE}}, /* UC flooding ENABLE*/
{{EMAC_NONE, EMAC_NONE, 0xfbff0000, EMAC_NONE}}, /* UC flooding DISABLE*/
{{EMAC_NONE, EMAC_NONE, 0xffff0800, EMAC_NONE}}, /* MC flooding ENABLE*/
{{EMAC_NONE, EMAC_NONE, 0xf7ff0000, EMAC_NONE}}, /* MC flooding DISABLE*/
{{EMAC_NONE, 0xffff4000, EMAC_NONE, EMAC_NONE}}, /* Preemption on Tx ENABLE*/
{{EMAC_NONE, 0xbfff0000, EMAC_NONE, EMAC_NONE}}, /* Preemption on Tx DISABLE*/
{{0xffff0010, EMAC_NONE, 0xffff0010, EMAC_NONE}}, /* VLAN AWARE*/
{{0xffef0000, EMAC_NONE, 0xffef0000, EMAC_NONE}}, /* VLAN UNWARE*/
{{0xffff2000, EMAC_NONE, EMAC_NONE, EMAC_NONE}}, /* HSR_RX_OFFLOAD_ENABLE */
{{0xdfff0000, EMAC_NONE, EMAC_NONE, EMAC_NONE}} /* HSR_RX_OFFLOAD_DISABLE */
};
int icssg_set_port_state(struct prueth_emac *emac,
enum icssg_port_state_cmd cmd)
{
struct icssg_r30_cmd __iomem *p;
int ret = -ETIMEDOUT;
int done = 0;
int i;
p = emac->dram.va + MGR_R30_CMD_OFFSET;
if (cmd >= ICSSG_EMAC_PORT_MAX_COMMANDS) {
netdev_err(emac->ndev, "invalid port command\n");
return -EINVAL;
}
/* only one command at a time allowed to firmware */
mutex_lock(&emac->cmd_lock);
for (i = 0; i < 4; i++)
writel(emac_r32_bitmask[cmd].cmd[i], &p->cmd[i]);
/* wait for done */
ret = read_poll_timeout(emac_r30_is_done, done, done == 1,
1000, 10000, false, emac);
if (ret == -ETIMEDOUT)
netdev_err(emac->ndev, "timeout waiting for command done\n");
mutex_unlock(&emac->cmd_lock);
return ret;
}
EXPORT_SYMBOL_GPL(icssg_set_port_state);
void icssg_config_half_duplex(struct prueth_emac *emac)
{
u32 val;
if (!emac->half_duplex)
return;
val = get_random_u32();
writel(val, emac->dram.va + HD_RAND_SEED_OFFSET);
}
EXPORT_SYMBOL_GPL(icssg_config_half_duplex);
void icssg_config_set_speed(struct prueth_emac *emac)
{
u8 fw_speed;
switch (emac->speed) {
case SPEED_1000:
fw_speed = FW_LINK_SPEED_1G;
break;
case SPEED_100:
fw_speed = FW_LINK_SPEED_100M;
break;
case SPEED_10:
fw_speed = FW_LINK_SPEED_10M;
break;
default:
/* Other links speeds not supported */
netdev_err(emac->ndev, "Unsupported link speed\n");
return;
}
if (emac->duplex == DUPLEX_HALF)
fw_speed |= FW_LINK_SPEED_HD;
writeb(fw_speed, emac->dram.va + PORT_LINK_SPEED_OFFSET);
}
EXPORT_SYMBOL_GPL(icssg_config_set_speed);
int icssg_send_fdb_msg(struct prueth_emac *emac, struct mgmt_cmd *cmd,
struct mgmt_cmd_rsp *rsp)
{
struct prueth *prueth = emac->prueth;
int slice = prueth_emac_slice(emac);
int addr, ret;
addr = icssg_queue_pop(prueth, slice == 0 ?
ICSSG_CMD_POP_SLICE0 : ICSSG_CMD_POP_SLICE1);
if (addr < 0)
return addr;
/* First 4 bytes have FW owned buffer linking info which should
* not be touched
*/
memcpy_toio(prueth->shram.va + addr + 4, cmd, sizeof(*cmd));
icssg_queue_push(prueth, slice == 0 ?
ICSSG_CMD_PUSH_SLICE0 : ICSSG_CMD_PUSH_SLICE1, addr);
ret = read_poll_timeout(icssg_queue_pop, addr, addr >= 0,
2000, 20000000, false, prueth, slice == 0 ?
ICSSG_RSP_POP_SLICE0 : ICSSG_RSP_POP_SLICE1);
if (ret) {
netdev_err(emac->ndev, "Timedout sending HWQ message\n");
return ret;
}
memcpy_fromio(rsp, prueth->shram.va + addr, sizeof(*rsp));
/* Return buffer back for to pool */
icssg_queue_push(prueth, slice == 0 ?
ICSSG_RSP_PUSH_SLICE0 : ICSSG_RSP_PUSH_SLICE1, addr);
return 0;
}
EXPORT_SYMBOL_GPL(icssg_send_fdb_msg);
static void icssg_fdb_setup(struct prueth_emac *emac, struct mgmt_cmd *fdb_cmd,
const unsigned char *addr, u8 fid, int cmd)
{
int slice = prueth_emac_slice(emac);
u8 mac_fid[ETH_ALEN + 2];
u16 fdb_slot;
ether_addr_copy(mac_fid, addr);
/* 1-1 VID-FID mapping is already setup */
mac_fid[ETH_ALEN] = fid;
mac_fid[ETH_ALEN + 1] = 0;
fdb_slot = bitrev32(crc32_le(0, mac_fid, 8)) & PRUETH_SWITCH_FDB_MASK;
fdb_cmd->header = ICSSG_FW_MGMT_CMD_HEADER;
fdb_cmd->type = ICSSG_FW_MGMT_FDB_CMD_TYPE;
fdb_cmd->seqnum = ++(emac->prueth->icssg_hwcmdseq);
fdb_cmd->param = cmd;
fdb_cmd->param |= (slice << 4);
memcpy(&fdb_cmd->cmd_args[0], addr, 4);
memcpy(&fdb_cmd->cmd_args[1], &addr[4], 2);
fdb_cmd->cmd_args[2] = fdb_slot;
netdev_dbg(emac->ndev, "MAC %pM slot %X FID %X\n", addr, fdb_slot, fid);
}
int icssg_fdb_add_del(struct prueth_emac *emac, const unsigned char *addr,
u8 vid, u8 fid_c2, bool add)
{
struct mgmt_cmd_rsp fdb_cmd_rsp = { 0 };
struct mgmt_cmd fdb_cmd = { 0 };
u8 fid = vid;
int ret;
icssg_fdb_setup(emac, &fdb_cmd, addr, fid, add ? ICSS_CMD_ADD_FDB : ICSS_CMD_DEL_FDB);
fid_c2 |= ICSSG_FDB_ENTRY_VALID;
fdb_cmd.cmd_args[1] |= ((fid << 16) | (fid_c2 << 24));
ret = icssg_send_fdb_msg(emac, &fdb_cmd, &fdb_cmd_rsp);
if (ret)
return ret;
WARN_ON(fdb_cmd.seqnum != fdb_cmd_rsp.seqnum);
if (fdb_cmd_rsp.status == 1)
return 0;
return -EINVAL;
}
EXPORT_SYMBOL_GPL(icssg_fdb_add_del);
int icssg_fdb_lookup(struct prueth_emac *emac, const unsigned char *addr,
u8 vid)
{
struct mgmt_cmd_rsp fdb_cmd_rsp = { 0 };
struct mgmt_cmd fdb_cmd = { 0 };
struct prueth_fdb_slot *slot;
u8 fid = vid;
int ret, i;
icssg_fdb_setup(emac, &fdb_cmd, addr, fid, ICSS_CMD_GET_FDB_SLOT);
fdb_cmd.cmd_args[1] |= fid << 16;
ret = icssg_send_fdb_msg(emac, &fdb_cmd, &fdb_cmd_rsp);
if (ret)
return ret;
WARN_ON(fdb_cmd.seqnum != fdb_cmd_rsp.seqnum);
slot = (struct prueth_fdb_slot __force *)(emac->dram.va + FDB_CMD_BUFFER);
for (i = 0; i < 4; i++) {
if (ether_addr_equal(addr, slot->mac) && vid == slot->fid)
return (slot->fid_c2 & ~ICSSG_FDB_ENTRY_VALID);
slot++;
}
return 0;
}
EXPORT_SYMBOL_GPL(icssg_fdb_lookup);
void icssg_vtbl_modify(struct prueth_emac *emac, u8 vid, u8 port_mask,
u8 untag_mask, bool add)
{
struct prueth *prueth = emac->prueth;
struct prueth_vlan_tbl *tbl;
u8 fid_c1;
tbl = prueth->vlan_tbl;
spin_lock(&prueth->vtbl_lock);
fid_c1 = tbl[vid].fid_c1;
/* FID_C1: bit0..2 port membership mask,
* bit3..5 tagging mask for each port
* bit6 Stream VID (not handled currently)
* bit7 MC flood (not handled currently)
*/
if (add) {
fid_c1 |= (port_mask | port_mask << 3);
fid_c1 &= ~(untag_mask << 3);
} else {
fid_c1 &= ~(port_mask | port_mask << 3);
}
tbl[vid].fid_c1 = fid_c1;
spin_unlock(&prueth->vtbl_lock);
}
EXPORT_SYMBOL_GPL(icssg_vtbl_modify);
u16 icssg_get_pvid(struct prueth_emac *emac)
{
struct prueth *prueth = emac->prueth;
u32 pvid;
if (emac->port_id == PRUETH_PORT_MII0)
pvid = readl(prueth->shram.va + EMAC_ICSSG_SWITCH_PORT1_DEFAULT_VLAN_OFFSET);
else
pvid = readl(prueth->shram.va + EMAC_ICSSG_SWITCH_PORT2_DEFAULT_VLAN_OFFSET);
pvid = pvid >> 24;
return pvid;
}
EXPORT_SYMBOL_GPL(icssg_get_pvid);
void icssg_set_pvid(struct prueth *prueth, u8 vid, u8 port)
{
u32 pvid;
/* only 256 VLANs are supported */
pvid = (u32 __force)cpu_to_be32((ETH_P_8021Q << 16) | (vid & 0xff));
if (port == PRUETH_PORT_MII0)
writel(pvid, prueth->shram.va + EMAC_ICSSG_SWITCH_PORT1_DEFAULT_VLAN_OFFSET);
else if (port == PRUETH_PORT_MII1)
writel(pvid, prueth->shram.va + EMAC_ICSSG_SWITCH_PORT2_DEFAULT_VLAN_OFFSET);
else
writel(pvid, prueth->shram.va + EMAC_ICSSG_SWITCH_PORT0_DEFAULT_VLAN_OFFSET);
}
EXPORT_SYMBOL_GPL(icssg_set_pvid);
int emac_fdb_flow_id_updated(struct prueth_emac *emac)
{
struct mgmt_cmd_rsp fdb_cmd_rsp = { 0 };
int slice = prueth_emac_slice(emac);
struct mgmt_cmd fdb_cmd = { 0 };
int ret;
fdb_cmd.header = ICSSG_FW_MGMT_CMD_HEADER;
fdb_cmd.type = ICSSG_FW_MGMT_FDB_CMD_TYPE_RX_FLOW;
fdb_cmd.seqnum = ++(emac->prueth->icssg_hwcmdseq);
fdb_cmd.param = 0;
fdb_cmd.param |= (slice << 4);
fdb_cmd.cmd_args[0] = 0;
ret = icssg_send_fdb_msg(emac, &fdb_cmd, &fdb_cmd_rsp);
if (ret)
return ret;
WARN_ON(fdb_cmd.seqnum != fdb_cmd_rsp.seqnum);
return fdb_cmd_rsp.status == 1 ? 0 : -EINVAL;
}
EXPORT_SYMBOL_GPL(emac_fdb_flow_id_updated);
@@ -0,0 +1,335 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Texas Instruments ICSSG Ethernet driver
*
* Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#ifndef __NET_TI_ICSSG_CONFIG_H
#define __NET_TI_ICSSG_CONFIG_H
struct icssg_buffer_pool_cfg {
__le32 addr;
__le32 len;
} __packed;
struct icssg_flow_cfg {
__le16 rx_base_flow;
__le16 mgm_base_flow;
} __packed;
#define PRUETH_PKT_TYPE_CMD 0x10
#define PRUETH_NAV_PS_DATA_SIZE 16 /* Protocol specific data size */
#define PRUETH_NAV_SW_DATA_SIZE 16 /* SW related data size */
#define PRUETH_MAX_TX_DESC 512
#define PRUETH_MAX_RX_DESC 512
#define PRUETH_MAX_RX_FLOWS 1 /* excluding default flow */
#define PRUETH_RX_FLOW_DATA 0
/* Defines for forwarding path buffer pools:
* - used by firmware to store packets to be forwarded to other port
* - 8 total pools per slice
* - only used in switch mode (as no forwarding in mac mode)
*/
#define PRUETH_NUM_FWD_BUF_POOLS_PER_SLICE 8
#define PRUETH_SW_FWD_BUF_POOL_SIZE (SZ_8K)
/* Defines for local injection path buffer pools:
* - used by firmware to store packets received from host core
* - 16 total pools per slice
* - 8 pools per port per slice and each slice handles both ports
* - only 4 out of 8 pools used per port (as only 4 real QoS levels in ICSSG)
* - switch mode: 8 total pools used
* - mac mode: 4 total pools used
*/
#define PRUETH_NUM_LI_BUF_POOLS_PER_SLICE 16
#define PRUETH_NUM_LI_BUF_POOLS_PER_PORT_PER_SLICE 8
#define PRUETH_SW_LI_BUF_POOL_SIZE SZ_4K
#define PRUETH_SW_USED_LI_BUF_POOLS_PER_SLICE 8
#define PRUETH_SW_USED_LI_BUF_POOLS_PER_PORT_PER_SLICE 4
#define PRUETH_EMAC_LI_BUF_POOL_SIZE SZ_8K
#define PRUETH_EMAC_USED_LI_BUF_POOLS_PER_SLICE 4
#define PRUETH_EMAC_USED_LI_BUF_POOLS_PER_PORT_PER_SLICE 4
/* Defines for host egress path - express and preemptible buffers
* - used by firmware to store express and preemptible packets
* to be transmitted to host core
* - used by both mac/switch modes
*/
#define PRUETH_SW_HOST_EXP_BUF_POOL_SIZE SZ_16K
#define PRUETH_SW_HOST_PRE_BUF_POOL_SIZE (SZ_16K - SZ_2K)
#define PRUETH_EMAC_HOST_EXP_BUF_POOL_SIZE PRUETH_SW_HOST_EXP_BUF_POOL_SIZE
#define PRUETH_EMAC_HOST_PRE_BUF_POOL_SIZE PRUETH_SW_HOST_PRE_BUF_POOL_SIZE
/* Buffer used by firmware to temporarily store packet to be dropped */
#define PRUETH_SW_DROP_PKT_BUF_SIZE SZ_2K
#define PRUETH_EMAC_DROP_PKT_BUF_SIZE PRUETH_SW_DROP_PKT_BUF_SIZE
/* Total switch mode memory usage for buffers per slice */
#define PRUETH_SW_TOTAL_BUF_SIZE_PER_SLICE \
(PRUETH_SW_FWD_BUF_POOL_SIZE * PRUETH_NUM_FWD_BUF_POOLS_PER_SLICE + \
PRUETH_SW_LI_BUF_POOL_SIZE * PRUETH_SW_USED_LI_BUF_POOLS_PER_SLICE + \
PRUETH_SW_HOST_EXP_BUF_POOL_SIZE + \
PRUETH_SW_HOST_PRE_BUF_POOL_SIZE + \
PRUETH_SW_DROP_PKT_BUF_SIZE)
/* Total switch mode memory usage for all buffers */
#define PRUETH_SW_TOTAL_BUF_SIZE \
(2 * PRUETH_SW_TOTAL_BUF_SIZE_PER_SLICE)
/* Total mac mode memory usage for buffers per slice */
#define PRUETH_EMAC_TOTAL_BUF_SIZE_PER_SLICE \
(PRUETH_EMAC_LI_BUF_POOL_SIZE * \
PRUETH_EMAC_USED_LI_BUF_POOLS_PER_SLICE + \
PRUETH_EMAC_HOST_EXP_BUF_POOL_SIZE + \
PRUETH_EMAC_HOST_PRE_BUF_POOL_SIZE + \
PRUETH_EMAC_DROP_PKT_BUF_SIZE)
/* Total mac mode memory usage for all buffers */
#define PRUETH_EMAC_TOTAL_BUF_SIZE \
(2 * PRUETH_EMAC_TOTAL_BUF_SIZE_PER_SLICE)
/* Size of 1 bank of MSMC/OC_SRAM memory */
#define MSMC_RAM_BANK_SIZE SZ_256K
#define PRUETH_SWITCH_FDB_MASK ((SIZE_OF_FDB / NUMBER_OF_FDB_BUCKET_ENTRIES) - 1)
struct icssg_rxq_ctx {
__le32 start[3];
__le32 end;
} __packed;
/* Load time Fiwmware Configuration */
#define ICSSG_FW_MGMT_CMD_HEADER 0x81
#define ICSSG_FW_MGMT_FDB_CMD_TYPE 0x03
#define ICSSG_FW_MGMT_CMD_TYPE 0x04
#define ICSSG_FW_MGMT_PKT 0x80000000
#define ICSSG_FW_MGMT_FDB_CMD_TYPE_RX_FLOW 0x05
struct icssg_r30_cmd {
u32 cmd[4];
} __packed;
enum icssg_port_state_cmd {
ICSSG_EMAC_PORT_DISABLE = 0,
ICSSG_EMAC_PORT_BLOCK,
ICSSG_EMAC_PORT_FORWARD,
ICSSG_EMAC_PORT_FORWARD_WO_LEARNING,
ICSSG_EMAC_PORT_ACCEPT_ALL,
ICSSG_EMAC_PORT_ACCEPT_TAGGED,
ICSSG_EMAC_PORT_ACCEPT_UNTAGGED_N_PRIO,
ICSSG_EMAC_PORT_TAS_TRIGGER,
ICSSG_EMAC_PORT_TAS_ENABLE,
ICSSG_EMAC_PORT_TAS_RESET,
ICSSG_EMAC_PORT_TAS_DISABLE,
ICSSG_EMAC_PORT_UC_FLOODING_ENABLE,
ICSSG_EMAC_PORT_UC_FLOODING_DISABLE,
ICSSG_EMAC_PORT_MC_FLOODING_ENABLE,
ICSSG_EMAC_PORT_MC_FLOODING_DISABLE,
ICSSG_EMAC_PORT_PREMPT_TX_ENABLE,
ICSSG_EMAC_PORT_PREMPT_TX_DISABLE,
ICSSG_EMAC_PORT_VLAN_AWARE_ENABLE,
ICSSG_EMAC_PORT_VLAN_AWARE_DISABLE,
ICSSG_EMAC_HSR_RX_OFFLOAD_ENABLE,
ICSSG_EMAC_HSR_RX_OFFLOAD_DISABLE,
ICSSG_EMAC_PORT_MAX_COMMANDS
};
#define EMAC_NONE 0xffff0000
#define EMAC_PRU0_P_DI 0xffff0004
#define EMAC_PRU1_P_DI 0xffff0040
#define EMAC_TX_P_DI 0xffff0100
#define EMAC_PRU0_P_EN 0xfffb0000
#define EMAC_PRU1_P_EN 0xffbf0000
#define EMAC_TX_P_EN 0xfeff0000
#define EMAC_P_BLOCK 0xffff0040
#define EMAC_TX_P_BLOCK 0xffff0200
#define EMAC_P_UNBLOCK 0xffbf0000
#define EMAC_TX_P_UNBLOCK 0xfdff0000
#define EMAC_LEAN_EN 0xfff70000
#define EMAC_LEAN_DI 0xffff0008
#define EMAC_ACCEPT_ALL 0xffff0001
#define EMAC_ACCEPT_TAG 0xfffe0002
#define EMAC_ACCEPT_PRIOR 0xfffc0000
/* Config area lies in DRAM */
#define ICSSG_CONFIG_OFFSET 0x0
/* Config area lies in shared RAM */
#define ICSSG_CONFIG_OFFSET_SLICE0 0
#define ICSSG_CONFIG_OFFSET_SLICE1 0x8000
#define ICSSG_NUM_NORMAL_PDS 64
#define ICSSG_NUM_SPECIAL_PDS 16
#define ICSSG_NORMAL_PD_SIZE 8
#define ICSSG_SPECIAL_PD_SIZE 20
#define ICSSG_FLAG_MASK 0xff00ffff
/* SR1.0-specific bits */
#define PRUETH_MAX_RX_FLOWS_SR1 4 /* excluding default flow */
#define PRUETH_RX_FLOW_DATA_SR1 3 /* highest priority flow */
#define PRUETH_MAX_RX_MGM_DESC_SR1 8
#define PRUETH_MAX_RX_MGM_FLOWS_SR1 2 /* excluding default flow */
#define PRUETH_RX_MGM_FLOW_RESPONSE_SR1 0
#define PRUETH_RX_MGM_FLOW_TIMESTAMP_SR1 1
#define PRUETH_NUM_BUF_POOLS_SR1 16
#define PRUETH_EMAC_BUF_POOL_START_SR1 8
#define PRUETH_EMAC_BUF_POOL_MIN_SIZE_SR1 128
#define PRUETH_EMAC_BUF_SIZE_SR1 1536
#define PRUETH_EMAC_NUM_BUF_SR1 4
#define PRUETH_EMAC_BUF_POOL_SIZE_SR1 (PRUETH_EMAC_NUM_BUF_SR1 * \
PRUETH_EMAC_BUF_SIZE_SR1)
#define MSMC_RAM_SIZE_SR1 (SZ_64K + SZ_32K + SZ_2K) /* 0x1880 x 8 x 2 */
struct icssg_sr1_config {
__le32 status; /* Firmware status */
__le32 addr_lo; /* MSMC Buffer pool base address low. */
__le32 addr_hi; /* MSMC Buffer pool base address high. Must be 0 */
__le32 tx_buf_sz[16]; /* Array of buffer pool sizes */
__le32 num_tx_threads; /* Number of active egress threads, 1 to 4 */
__le32 tx_rate_lim_en; /* Bitmask: Egress rate limit en per thread */
__le32 rx_flow_id; /* RX flow id for first rx ring */
__le32 rx_mgr_flow_id; /* RX flow id for the first management ring */
__le32 flags; /* TBD */
__le32 n_burst; /* for debug */
__le32 rtu_status; /* RTU status */
__le32 info; /* reserved */
__le32 reserve;
__le32 rand_seed; /* Used for the random number generation at fw */
} __packed;
/* SR1.0 shutdown command to stop processing at firmware.
* Command format: 0x8101ss00, where
* - ss: sequence number. Currently not used by driver.
*/
#define ICSSG_SHUTDOWN_CMD_SR1 0x81010000
/* SR1.0 pstate speed/duplex command to set speed and duplex settings
* in firmware.
* Command format: 0x8102ssPN, where
* - ss: sequence number. Currently not used by driver.
* - P: port number (for switch mode).
* - N: Speed/Duplex state:
* 0x0 - 10Mbps/Half duplex;
* 0x8 - 10Mbps/Full duplex;
* 0x2 - 100Mbps/Half duplex;
* 0xa - 100Mbps/Full duplex;
* 0xc - 1Gbps/Full duplex;
* NOTE: The above are the same value as bits [3..1](slice 0)
* or bits [7..5](slice 1) of RGMII CFG register.
*/
#define ICSSG_PSTATE_SPEED_DUPLEX_CMD_SR1 0x81020000
struct icssg_setclock_desc {
u8 request;
u8 restore;
u8 acknowledgment;
u8 cmp_status;
u32 margin;
u32 cyclecounter0_set;
u32 cyclecounter1_set;
u32 iepcount_set;
u32 rsvd1;
u32 rsvd2;
u32 CMP0_current;
u32 iepcount_current;
u32 difference;
u32 cyclecounter0_new;
u32 cyclecounter1_new;
u32 CMP0_new;
} __packed;
#define ICSSG_CMD_POP_SLICE0 56
#define ICSSG_CMD_POP_SLICE1 60
#define ICSSG_CMD_PUSH_SLICE0 57
#define ICSSG_CMD_PUSH_SLICE1 61
#define ICSSG_RSP_POP_SLICE0 58
#define ICSSG_RSP_POP_SLICE1 62
#define ICSSG_RSP_PUSH_SLICE0 56
#define ICSSG_RSP_PUSH_SLICE1 60
#define ICSSG_TS_POP_SLICE0 59
#define ICSSG_TS_POP_SLICE1 63
#define ICSSG_TS_PUSH_SLICE0 40
#define ICSSG_TS_PUSH_SLICE1 41
struct mgmt_cmd {
u8 param;
u8 seqnum;
u8 type;
u8 header;
u32 cmd_args[3];
};
struct mgmt_cmd_rsp {
u32 reserved;
u8 status;
u8 seqnum;
u8 type;
u8 header;
u32 cmd_args[3];
};
/* FDB FID_C2 flag definitions */
/* Indicates host port membership.*/
#define ICSSG_FDB_ENTRY_P0_MEMBERSHIP BIT(0)
/* Indicates that MAC ID is connected to physical port 1 */
#define ICSSG_FDB_ENTRY_P1_MEMBERSHIP BIT(1)
/* Indicates that MAC ID is connected to physical port 2 */
#define ICSSG_FDB_ENTRY_P2_MEMBERSHIP BIT(2)
/* Ageable bit is set for learned entries and cleared for static entries */
#define ICSSG_FDB_ENTRY_AGEABLE BIT(3)
/* If set for DA then packet is determined to be a special packet */
#define ICSSG_FDB_ENTRY_BLOCK BIT(4)
/* If set for DA then the SA from the packet is not learned */
#define ICSSG_FDB_ENTRY_SECURE BIT(5)
/* If set, it means packet has been seen recently with source address + FID
* matching MAC address/FID of entry
*/
#define ICSSG_FDB_ENTRY_TOUCHED BIT(6)
/* Set if entry is valid */
#define ICSSG_FDB_ENTRY_VALID BIT(7)
/**
* struct prueth_vlan_tbl - VLAN table entries struct in ICSSG SMEM
* @fid_c1: membership and forwarding rules flag to this table. See
* above to defines for bit definitions
* @fid: FDB index for this VID (there is 1-1 mapping b/w VID and FID)
*/
struct prueth_vlan_tbl {
u8 fid_c1;
u8 fid;
} __packed;
/**
* struct prueth_fdb_slot - Result of FDB slot lookup
* @mac: MAC address
* @fid: fid to be associated with MAC
* @fid_c2: FID_C2 entry for this MAC
*/
struct prueth_fdb_slot {
u8 mac[ETH_ALEN];
u8 fid;
u8 fid_c2;
} __packed;
enum icssg_ietfpe_verify_states {
ICSSG_IETFPE_STATE_UNKNOWN = 0,
ICSSG_IETFPE_STATE_INITIAL,
ICSSG_IETFPE_STATE_VERIFYING,
ICSSG_IETFPE_STATE_SUCCEEDED,
ICSSG_IETFPE_STATE_FAILED,
ICSSG_IETFPE_STATE_DISABLED
};
#endif /* __NET_TI_ICSSG_CONFIG_H */
@@ -0,0 +1,321 @@
// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments ICSSG Ethernet driver
*
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#include "icssg_prueth.h"
#include "icssg_stats.h"
static void emac_get_drvinfo(struct net_device *ndev,
struct ethtool_drvinfo *info)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
strscpy(info->driver, dev_driver_string(prueth->dev),
sizeof(info->driver));
strscpy(info->bus_info, dev_name(prueth->dev), sizeof(info->bus_info));
}
static u32 emac_get_msglevel(struct net_device *ndev)
{
struct prueth_emac *emac = netdev_priv(ndev);
return emac->msg_enable;
}
static void emac_set_msglevel(struct net_device *ndev, u32 value)
{
struct prueth_emac *emac = netdev_priv(ndev);
emac->msg_enable = value;
}
static int emac_get_link_ksettings(struct net_device *ndev,
struct ethtool_link_ksettings *ecmd)
{
return phy_ethtool_get_link_ksettings(ndev, ecmd);
}
static int emac_set_link_ksettings(struct net_device *ndev,
const struct ethtool_link_ksettings *ecmd)
{
return phy_ethtool_set_link_ksettings(ndev, ecmd);
}
static int emac_get_eee(struct net_device *ndev, struct ethtool_keee *edata)
{
if (!ndev->phydev)
return -EOPNOTSUPP;
return phy_ethtool_get_eee(ndev->phydev, edata);
}
static int emac_set_eee(struct net_device *ndev, struct ethtool_keee *edata)
{
if (!ndev->phydev)
return -EOPNOTSUPP;
return phy_ethtool_set_eee(ndev->phydev, edata);
}
static int emac_nway_reset(struct net_device *ndev)
{
return phy_ethtool_nway_reset(ndev);
}
static int emac_get_sset_count(struct net_device *ndev, int stringset)
{
struct prueth_emac *emac = netdev_priv(ndev);
switch (stringset) {
case ETH_SS_STATS:
if (emac->prueth->pa_stats)
return ICSSG_NUM_ETHTOOL_STATS;
else
return ICSSG_NUM_ETHTOOL_STATS - ICSSG_NUM_PA_STATS;
default:
return -EOPNOTSUPP;
}
}
static void emac_get_strings(struct net_device *ndev, u32 stringset, u8 *data)
{
struct prueth_emac *emac = netdev_priv(ndev);
u8 *p = data;
int i;
switch (stringset) {
case ETH_SS_STATS:
for (i = 0; i < ARRAY_SIZE(icssg_all_miig_stats); i++)
if (!icssg_all_miig_stats[i].standard_stats)
ethtool_puts(&p, icssg_all_miig_stats[i].name);
if (emac->prueth->pa_stats)
for (i = 0; i < ARRAY_SIZE(icssg_all_pa_stats); i++)
ethtool_puts(&p, icssg_all_pa_stats[i].name);
break;
default:
break;
}
}
static void emac_get_ethtool_stats(struct net_device *ndev,
struct ethtool_stats *stats, u64 *data)
{
struct prueth_emac *emac = netdev_priv(ndev);
int i;
emac_update_hardware_stats(emac);
for (i = 0; i < ARRAY_SIZE(icssg_all_miig_stats); i++)
if (!icssg_all_miig_stats[i].standard_stats)
*(data++) = emac->stats[i];
if (emac->prueth->pa_stats)
for (i = 0; i < ARRAY_SIZE(icssg_all_pa_stats); i++)
*(data++) = emac->pa_stats[i];
}
static int emac_get_ts_info(struct net_device *ndev,
struct kernel_ethtool_ts_info *info)
{
struct prueth_emac *emac = netdev_priv(ndev);
info->so_timestamping =
SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_TX_SOFTWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
info->phc_index = icss_iep_get_ptp_clock_idx(emac->iep);
info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON);
info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | BIT(HWTSTAMP_FILTER_ALL);
return 0;
}
static int emac_set_channels(struct net_device *ndev,
struct ethtool_channels *ch)
{
struct prueth_emac *emac = netdev_priv(ndev);
/* Check if interface is up. Can change the num queues when
* the interface is down.
*/
if (netif_running(emac->ndev))
return -EBUSY;
emac->tx_ch_num = ch->tx_count;
if (emac->is_sr1)
emac->tx_ch_num++;
return 0;
}
static void emac_get_channels(struct net_device *ndev,
struct ethtool_channels *ch)
{
struct prueth_emac *emac = netdev_priv(ndev);
ch->max_rx = 1;
ch->max_tx = PRUETH_MAX_TX_QUEUES;
/* Disable multiple TX channels due to timeouts
* when using more than one queue */
if (emac->is_sr1)
ch->max_tx = 1;
ch->rx_count = 1;
ch->tx_count = emac->tx_ch_num;
if (emac->is_sr1)
ch->tx_count--;
}
static const struct ethtool_rmon_hist_range emac_rmon_ranges[] = {
{ 0, 64},
{ 65, 128},
{ 129, 256},
{ 257, 512},
{ 513, PRUETH_MAX_PKT_SIZE},
{}
};
static void emac_get_rmon_stats(struct net_device *ndev,
struct ethtool_rmon_stats *rmon_stats,
const struct ethtool_rmon_hist_range **ranges)
{
struct prueth_emac *emac = netdev_priv(ndev);
*ranges = emac_rmon_ranges;
rmon_stats->undersize_pkts = emac_get_stat_by_name(emac, "rx_bucket1_frames") -
emac_get_stat_by_name(emac, "rx_64B_frames");
rmon_stats->hist[0] = emac_get_stat_by_name(emac, "rx_bucket1_frames");
rmon_stats->hist[1] = emac_get_stat_by_name(emac, "rx_bucket2_frames");
rmon_stats->hist[2] = emac_get_stat_by_name(emac, "rx_bucket3_frames");
rmon_stats->hist[3] = emac_get_stat_by_name(emac, "rx_bucket4_frames");
rmon_stats->hist[4] = emac_get_stat_by_name(emac, "rx_bucket5_frames");
rmon_stats->hist_tx[0] = emac_get_stat_by_name(emac, "tx_bucket1_frames");
rmon_stats->hist_tx[1] = emac_get_stat_by_name(emac, "tx_bucket2_frames");
rmon_stats->hist_tx[2] = emac_get_stat_by_name(emac, "tx_bucket3_frames");
rmon_stats->hist_tx[3] = emac_get_stat_by_name(emac, "tx_bucket4_frames");
rmon_stats->hist_tx[4] = emac_get_stat_by_name(emac, "tx_bucket5_frames");
}
static int emac_get_coalesce(struct net_device *ndev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth_tx_chn *tx_chn;
tx_chn = &emac->tx_chns[0];
coal->rx_coalesce_usecs = emac->rx_pace_timeout_ns / 1000;
coal->tx_coalesce_usecs = tx_chn->tx_pace_timeout_ns / 1000;
return 0;
}
static int emac_get_per_queue_coalesce(struct net_device *ndev, u32 queue,
struct ethtool_coalesce *coal)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth_tx_chn *tx_chn;
if (queue >= PRUETH_MAX_TX_QUEUES)
return -EINVAL;
tx_chn = &emac->tx_chns[queue];
coal->tx_coalesce_usecs = tx_chn->tx_pace_timeout_ns / 1000;
return 0;
}
static int emac_set_coalesce(struct net_device *ndev,
struct ethtool_coalesce *coal,
struct kernel_ethtool_coalesce *kernel_coal,
struct netlink_ext_ack *extack)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
struct prueth_tx_chn *tx_chn;
tx_chn = &emac->tx_chns[0];
if (coal->rx_coalesce_usecs &&
coal->rx_coalesce_usecs < ICSSG_MIN_COALESCE_USECS) {
dev_info(prueth->dev, "defaulting to min value of %dus for rx-usecs\n",
ICSSG_MIN_COALESCE_USECS);
coal->rx_coalesce_usecs = ICSSG_MIN_COALESCE_USECS;
}
if (coal->tx_coalesce_usecs &&
coal->tx_coalesce_usecs < ICSSG_MIN_COALESCE_USECS) {
dev_info(prueth->dev, "defaulting to min value of %dus for tx-usecs\n",
ICSSG_MIN_COALESCE_USECS);
coal->tx_coalesce_usecs = ICSSG_MIN_COALESCE_USECS;
}
emac->rx_pace_timeout_ns = coal->rx_coalesce_usecs * 1000;
tx_chn->tx_pace_timeout_ns = coal->tx_coalesce_usecs * 1000;
return 0;
}
static int emac_set_per_queue_coalesce(struct net_device *ndev, u32 queue,
struct ethtool_coalesce *coal)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
struct prueth_tx_chn *tx_chn;
if (queue >= PRUETH_MAX_TX_QUEUES)
return -EINVAL;
tx_chn = &emac->tx_chns[queue];
if (coal->tx_coalesce_usecs &&
coal->tx_coalesce_usecs < ICSSG_MIN_COALESCE_USECS) {
dev_info(prueth->dev, "defaulting to min value of %dus for tx-usecs for tx-%u\n",
ICSSG_MIN_COALESCE_USECS, queue);
coal->tx_coalesce_usecs = ICSSG_MIN_COALESCE_USECS;
}
tx_chn->tx_pace_timeout_ns = coal->tx_coalesce_usecs * 1000;
return 0;
}
const struct ethtool_ops icssg_ethtool_ops = {
.get_drvinfo = emac_get_drvinfo,
.get_msglevel = emac_get_msglevel,
.set_msglevel = emac_set_msglevel,
.get_sset_count = emac_get_sset_count,
.get_ethtool_stats = emac_get_ethtool_stats,
.get_strings = emac_get_strings,
.get_ts_info = emac_get_ts_info,
.supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS |
ETHTOOL_COALESCE_TX_USECS,
.get_coalesce = emac_get_coalesce,
.set_coalesce = emac_set_coalesce,
.get_per_queue_coalesce = emac_get_per_queue_coalesce,
.set_per_queue_coalesce = emac_set_per_queue_coalesce,
.get_channels = emac_get_channels,
.set_channels = emac_set_channels,
.get_link_ksettings = emac_get_link_ksettings,
.set_link_ksettings = emac_set_link_ksettings,
.get_link = ethtool_op_get_link,
.get_eee = emac_get_eee,
.set_eee = emac_set_eee,
.nway_reset = emac_nway_reset,
.get_rmon_stats = emac_get_rmon_stats,
};
EXPORT_SYMBOL_GPL(icssg_ethtool_ops);
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments ICSSG Ethernet Driver
*
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#include <linux/etherdevice.h>
#include <linux/regmap.h>
#include <linux/types.h>
#include "icssg_mii_rt.h"
#include "icssg_prueth.h"
void icssg_mii_update_ipg(struct regmap *mii_rt, int mii, u32 ipg)
{
u32 val;
if (mii == ICSS_MII0) {
regmap_write(mii_rt, PRUSS_MII_RT_TX_IPG0, ipg);
} else {
regmap_read(mii_rt, PRUSS_MII_RT_TX_IPG0, &val);
regmap_write(mii_rt, PRUSS_MII_RT_TX_IPG1, ipg);
regmap_write(mii_rt, PRUSS_MII_RT_TX_IPG0, val);
}
}
void icssg_mii_update_mtu(struct regmap *mii_rt, int mii, int mtu)
{
mtu += (ETH_HLEN + ETH_FCS_LEN);
if (mii == ICSS_MII0) {
regmap_update_bits(mii_rt,
PRUSS_MII_RT_RX_FRMS0,
PRUSS_MII_RT_RX_FRMS_MAX_FRM_MASK,
(mtu - 1) << PRUSS_MII_RT_RX_FRMS_MAX_FRM_SHIFT);
} else {
regmap_update_bits(mii_rt,
PRUSS_MII_RT_RX_FRMS1,
PRUSS_MII_RT_RX_FRMS_MAX_FRM_MASK,
(mtu - 1) << PRUSS_MII_RT_RX_FRMS_MAX_FRM_SHIFT);
}
}
EXPORT_SYMBOL_GPL(icssg_mii_update_mtu);
void icssg_update_rgmii_cfg(struct regmap *miig_rt, struct prueth_emac *emac)
{
u32 gig_en_mask, gig_val = 0, full_duplex_mask, full_duplex_val = 0;
int slice = prueth_emac_slice(emac);
u32 inband_en_mask, inband_val = 0;
gig_en_mask = (slice == ICSS_MII0) ? RGMII_CFG_GIG_EN_MII0 :
RGMII_CFG_GIG_EN_MII1;
if (emac->speed == SPEED_1000)
gig_val = gig_en_mask;
regmap_update_bits(miig_rt, RGMII_CFG_OFFSET, gig_en_mask, gig_val);
inband_en_mask = (slice == ICSS_MII0) ? RGMII_CFG_INBAND_EN_MII0 :
RGMII_CFG_INBAND_EN_MII1;
if (emac->speed == SPEED_10 && phy_interface_mode_is_rgmii(emac->phy_if))
inband_val = inband_en_mask;
regmap_update_bits(miig_rt, RGMII_CFG_OFFSET, inband_en_mask, inband_val);
full_duplex_mask = (slice == ICSS_MII0) ? RGMII_CFG_FULL_DUPLEX_MII0 :
RGMII_CFG_FULL_DUPLEX_MII1;
if (emac->duplex == DUPLEX_FULL)
full_duplex_val = full_duplex_mask;
regmap_update_bits(miig_rt, RGMII_CFG_OFFSET, full_duplex_mask,
full_duplex_val);
}
EXPORT_SYMBOL_GPL(icssg_update_rgmii_cfg);
void icssg_miig_set_interface_mode(struct regmap *miig_rt, int mii, phy_interface_t phy_if)
{
u32 val, mask, shift;
mask = mii == ICSS_MII0 ? ICSSG_CFG_MII0_MODE : ICSSG_CFG_MII1_MODE;
shift = mii == ICSS_MII0 ? ICSSG_CFG_MII0_MODE_SHIFT : ICSSG_CFG_MII1_MODE_SHIFT;
val = MII_MODE_RGMII;
if (phy_if == PHY_INTERFACE_MODE_MII)
val = MII_MODE_MII;
val <<= shift;
regmap_update_bits(miig_rt, ICSSG_CFG_OFFSET, mask, val);
regmap_read(miig_rt, ICSSG_CFG_OFFSET, &val);
}
u32 icssg_rgmii_cfg_get_bitfield(struct regmap *miig_rt, u32 mask, u32 shift)
{
u32 val;
regmap_read(miig_rt, RGMII_CFG_OFFSET, &val);
val &= mask;
val >>= shift;
return val;
}
u32 icssg_rgmii_get_speed(struct regmap *miig_rt, int mii)
{
u32 shift = RGMII_CFG_SPEED_MII0_SHIFT, mask = RGMII_CFG_SPEED_MII0;
if (mii == ICSS_MII1) {
shift = RGMII_CFG_SPEED_MII1_SHIFT;
mask = RGMII_CFG_SPEED_MII1;
}
return icssg_rgmii_cfg_get_bitfield(miig_rt, mask, shift);
}
EXPORT_SYMBOL_GPL(icssg_rgmii_get_speed);
u32 icssg_rgmii_get_fullduplex(struct regmap *miig_rt, int mii)
{
u32 shift = RGMII_CFG_FULLDUPLEX_MII0_SHIFT;
u32 mask = RGMII_CFG_FULLDUPLEX_MII0;
if (mii == ICSS_MII1) {
shift = RGMII_CFG_FULLDUPLEX_MII1_SHIFT;
mask = RGMII_CFG_FULLDUPLEX_MII1;
}
return icssg_rgmii_cfg_get_bitfield(miig_rt, mask, shift);
}
EXPORT_SYMBOL_GPL(icssg_rgmii_get_fullduplex);
@@ -0,0 +1,151 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* PRU-ICSS MII_RT register definitions
*
* Copyright (C) 2015-2022 Texas Instruments Incorporated - https://www.ti.com
*/
#ifndef __NET_PRUSS_MII_RT_H__
#define __NET_PRUSS_MII_RT_H__
#include <linux/if_ether.h>
#include <linux/phy.h>
/* PRUSS_MII_RT Registers */
#define PRUSS_MII_RT_RXCFG0 0x0
#define PRUSS_MII_RT_RXCFG1 0x4
#define PRUSS_MII_RT_TXCFG0 0x10
#define PRUSS_MII_RT_TXCFG1 0x14
#define PRUSS_MII_RT_TX_CRC0 0x20
#define PRUSS_MII_RT_TX_CRC1 0x24
#define PRUSS_MII_RT_TX_IPG0 0x30
#define PRUSS_MII_RT_TX_IPG1 0x34
#define PRUSS_MII_RT_PRS0 0x38
#define PRUSS_MII_RT_PRS1 0x3c
#define PRUSS_MII_RT_RX_FRMS0 0x40
#define PRUSS_MII_RT_RX_FRMS1 0x44
#define PRUSS_MII_RT_RX_PCNT0 0x48
#define PRUSS_MII_RT_RX_PCNT1 0x4c
#define PRUSS_MII_RT_RX_ERR0 0x50
#define PRUSS_MII_RT_RX_ERR1 0x54
/* PRUSS_MII_RT_RXCFG0/1 bits */
#define PRUSS_MII_RT_RXCFG_RX_ENABLE BIT(0)
#define PRUSS_MII_RT_RXCFG_RX_DATA_RDY_MODE_DIS BIT(1)
#define PRUSS_MII_RT_RXCFG_RX_CUT_PREAMBLE BIT(2)
#define PRUSS_MII_RT_RXCFG_RX_MUX_SEL BIT(3)
#define PRUSS_MII_RT_RXCFG_RX_L2_EN BIT(4)
#define PRUSS_MII_RT_RXCFG_RX_BYTE_SWAP BIT(5)
#define PRUSS_MII_RT_RXCFG_RX_AUTO_FWD_PRE BIT(6)
#define PRUSS_MII_RT_RXCFG_RX_L2_EOF_SCLR_DIS BIT(9)
/* PRUSS_MII_RT_TXCFG0/1 bits */
#define PRUSS_MII_RT_TXCFG_TX_ENABLE BIT(0)
#define PRUSS_MII_RT_TXCFG_TX_AUTO_PREAMBLE BIT(1)
#define PRUSS_MII_RT_TXCFG_TX_EN_MODE BIT(2)
#define PRUSS_MII_RT_TXCFG_TX_BYTE_SWAP BIT(3)
#define PRUSS_MII_RT_TXCFG_TX_MUX_SEL BIT(8)
#define PRUSS_MII_RT_TXCFG_PRE_TX_AUTO_SEQUENCE BIT(9)
#define PRUSS_MII_RT_TXCFG_PRE_TX_AUTO_ESC_ERR BIT(10)
#define PRUSS_MII_RT_TXCFG_TX_32_MODE_EN BIT(11)
#define PRUSS_MII_RT_TXCFG_TX_IPG_WIRE_CLK_EN BIT(12) /* SR2.0 onwards */
#define PRUSS_MII_RT_TXCFG_TX_START_DELAY_SHIFT 16
#define PRUSS_MII_RT_TXCFG_TX_START_DELAY_MASK GENMASK(25, 16)
#define PRUSS_MII_RT_TXCFG_TX_CLK_DELAY_SHIFT 28
#define PRUSS_MII_RT_TXCFG_TX_CLK_DELAY_MASK GENMASK(30, 28)
/* PRUSS_MII_RT_TX_IPG0/1 bits */
#define PRUSS_MII_RT_TX_IPG_IPG_SHIFT 0
#define PRUSS_MII_RT_TX_IPG_IPG_MASK GENMASK(9, 0)
/* PRUSS_MII_RT_PRS0/1 bits */
#define PRUSS_MII_RT_PRS_COL BIT(0)
#define PRUSS_MII_RT_PRS_CRS BIT(1)
/* PRUSS_MII_RT_RX_FRMS0/1 bits */
#define PRUSS_MII_RT_RX_FRMS_MIN_FRM_SHIFT 0
#define PRUSS_MII_RT_RX_FRMS_MIN_FRM_MASK GENMASK(15, 0)
#define PRUSS_MII_RT_RX_FRMS_MAX_FRM_SHIFT 16
#define PRUSS_MII_RT_RX_FRMS_MAX_FRM_MASK GENMASK(31, 16)
/* Min/Max in MII_RT_RX_FRMS */
/* For EMAC and Switch */
#define PRUSS_MII_RT_RX_FRMS_MAX (VLAN_ETH_FRAME_LEN + ETH_FCS_LEN)
#define PRUSS_MII_RT_RX_FRMS_MIN_FRM (64)
/* for HSR and PRP */
#define PRUSS_MII_RT_RX_FRMS_MAX_FRM_LRE (PRUSS_MII_RT_RX_FRMS_MAX + \
ICSS_LRE_TAG_RCT_SIZE)
/* PRUSS_MII_RT_RX_PCNT0/1 bits */
#define PRUSS_MII_RT_RX_PCNT_MIN_PCNT_SHIFT 0
#define PRUSS_MII_RT_RX_PCNT_MIN_PCNT_MASK GENMASK(3, 0)
#define PRUSS_MII_RT_RX_PCNT_MAX_PCNT_SHIFT 4
#define PRUSS_MII_RT_RX_PCNT_MAX_PCNT_MASK GENMASK(7, 4)
/* PRUSS_MII_RT_RX_ERR0/1 bits */
#define PRUSS_MII_RT_RX_ERR_MIN_PCNT_ERR BIT(0)
#define PRUSS_MII_RT_RX_ERR_MAX_PCNT_ERR BIT(1)
#define PRUSS_MII_RT_RX_ERR_MIN_FRM_ERR BIT(2)
#define PRUSS_MII_RT_RX_ERR_MAX_FRM_ERR BIT(3)
#define ICSSG_CFG_OFFSET 0
#define RGMII_CFG_OFFSET 4
/* Constant to choose between MII0 and MII1 */
#define ICSS_MII0 0
#define ICSS_MII1 1
/* ICSSG_CFG Register bits */
#define ICSSG_CFG_SGMII_MODE BIT(16)
#define ICSSG_CFG_TX_PRU_EN BIT(11)
#define ICSSG_CFG_RX_SFD_TX_SOF_EN BIT(10)
#define ICSSG_CFG_RTU_PRU_PSI_SHARE_EN BIT(9)
#define ICSSG_CFG_IEP1_TX_EN BIT(8)
#define ICSSG_CFG_MII1_MODE GENMASK(6, 5)
#define ICSSG_CFG_MII1_MODE_SHIFT 5
#define ICSSG_CFG_MII0_MODE GENMASK(4, 3)
#define ICSSG_CFG_MII0_MODE_SHIFT 3
#define ICSSG_CFG_RX_L2_G_EN BIT(2)
#define ICSSG_CFG_TX_L2_EN BIT(1)
#define ICSSG_CFG_TX_L1_EN BIT(0)
enum mii_mode {
MII_MODE_MII = 0,
MII_MODE_RGMII
};
/* RGMII CFG Register bits */
#define RGMII_CFG_INBAND_EN_MII0 BIT(16)
#define RGMII_CFG_GIG_EN_MII0 BIT(17)
#define RGMII_CFG_INBAND_EN_MII1 BIT(20)
#define RGMII_CFG_GIG_EN_MII1 BIT(21)
#define RGMII_CFG_FULL_DUPLEX_MII0 BIT(18)
#define RGMII_CFG_FULL_DUPLEX_MII1 BIT(22)
#define RGMII_CFG_SPEED_MII0 GENMASK(2, 1)
#define RGMII_CFG_SPEED_MII1 GENMASK(6, 5)
#define RGMII_CFG_SPEED_MII0_SHIFT 1
#define RGMII_CFG_SPEED_MII1_SHIFT 5
#define RGMII_CFG_FULLDUPLEX_MII0 BIT(3)
#define RGMII_CFG_FULLDUPLEX_MII1 BIT(7)
#define RGMII_CFG_FULLDUPLEX_MII0_SHIFT 3
#define RGMII_CFG_FULLDUPLEX_MII1_SHIFT 7
#define RGMII_CFG_SPEED_10M 0
#define RGMII_CFG_SPEED_100M 1
#define RGMII_CFG_SPEED_1G 2
struct regmap;
struct prueth_emac;
void icssg_mii_update_ipg(struct regmap *mii_rt, int mii, u32 ipg);
void icssg_mii_update_mtu(struct regmap *mii_rt, int mii, int mtu);
void icssg_update_rgmii_cfg(struct regmap *miig_rt, struct prueth_emac *emac);
u32 icssg_rgmii_cfg_get_bitfield(struct regmap *miig_rt, u32 mask, u32 shift);
u32 icssg_rgmii_get_speed(struct regmap *miig_rt, int mii);
u32 icssg_rgmii_get_fullduplex(struct regmap *miig_rt, int mii);
void icssg_miig_set_interface_mode(struct regmap *miig_rt, int mii, phy_interface_t phy_if);
#endif /* __NET_PRUSS_MII_RT_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,527 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Texas Instruments ICSSG Ethernet driver
*
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#ifndef __NET_TI_ICSSG_PRUETH_H
#define __NET_TI_ICSSG_PRUETH_H
#include <linux/bpf.h>
#include <linux/bpf_trace.h>
#include <linux/etherdevice.h>
#include <linux/genalloc.h>
#include <linux/if_vlan.h>
#include <linux/if_hsr.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/net_tstamp.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_mdio.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include <linux/phy.h>
#include <linux/remoteproc/pruss.h>
#include <linux/pruss_driver.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/remoteproc.h>
#include <linux/dma-mapping.h>
#include <linux/dma/ti-cppi5.h>
#include <linux/dma/k3-udma-glue.h>
#include <net/devlink.h>
#include <net/xdp.h>
#include <net/page_pool/helpers.h>
#include <net/xsk_buff_pool.h>
#include <net/xdp_sock_drv.h>
#include "icssg_config.h"
#include "icss_iep.h"
#include "icssg_switch_map.h"
#define PRUETH_MAX_MTU (2000 - ETH_HLEN - ETH_FCS_LEN)
#define PRUETH_MIN_PKT_SIZE (VLAN_ETH_ZLEN)
#define PRUETH_MAX_PKT_SIZE (PRUETH_MAX_MTU + ETH_HLEN + ETH_FCS_LEN)
#define ICSS_SLICE0 0
#define ICSS_SLICE1 1
#define ICSS_FW_PRU 0
#define ICSS_FW_RTU 1
#define ICSSG_MAX_RFLOWS 8 /* per slice */
#define ICSSG_NUM_PA_STATS 32
#define ICSSG_NUM_MIIG_STATS 60
/* Number of ICSSG related stats */
#define ICSSG_NUM_STATS (ICSSG_NUM_MIIG_STATS + ICSSG_NUM_PA_STATS)
#define ICSSG_NUM_STANDARD_STATS 31
#define ICSSG_NUM_ETHTOOL_STATS (ICSSG_NUM_STATS - ICSSG_NUM_STANDARD_STATS)
#define IEP_DEFAULT_CYCLE_TIME_NS 1000000 /* 1 ms */
#define PRUETH_UNDIRECTED_PKT_DST_TAG 0
#define PRUETH_UNDIRECTED_PKT_TAG_INS BIT(30)
/* Firmware status codes */
#define ICSS_HS_FW_READY 0x55555555
#define ICSS_HS_FW_DEAD 0xDEAD0000 /* lower 16 bits contain error code */
/* Firmware command codes */
#define ICSS_HS_CMD_BUSY 0x40000000
#define ICSS_HS_CMD_DONE 0x80000000
#define ICSS_HS_CMD_CANCEL 0x10000000
/* Firmware commands */
#define ICSS_CMD_SPAD 0x20
#define ICSS_CMD_RXTX 0x10
#define ICSS_CMD_ADD_FDB 0x1
#define ICSS_CMD_DEL_FDB 0x2
#define ICSS_CMD_SET_RUN 0x4
#define ICSS_CMD_GET_FDB_SLOT 0x5
#define ICSS_CMD_ENABLE_VLAN 0x5
#define ICSS_CMD_DISABLE_VLAN 0x6
#define ICSS_CMD_ADD_FILTER 0x7
#define ICSS_CMD_ADD_MAC 0x8
/* VLAN Filtering Related MACROs */
#define PRUETH_DFLT_VLAN_HSR 1
#define PRUETH_DFLT_VLAN_SW 1
#define PRUETH_DFLT_VLAN_MAC 0
#define MAX_VLAN_ID 256
/* In switch mode there are 3 real ports i.e. 3 mac addrs.
* however Linux sees only the host side port. The other 2 ports
* are the switch ports.
* In emac mode there are 2 real ports i.e. 2 mac addrs.
* Linux sees both the ports.
*/
enum prueth_port {
PRUETH_PORT_HOST = 0, /* host side port */
PRUETH_PORT_MII0, /* physical port RG/SG MII 0 */
PRUETH_PORT_MII1, /* physical port RG/SG MII 1 */
PRUETH_PORT_INVALID, /* Invalid prueth port */
};
enum prueth_mac {
PRUETH_MAC0 = 0,
PRUETH_MAC1,
PRUETH_NUM_MACS,
PRUETH_MAC_INVALID,
};
struct prueth_tx_chn {
struct device *dma_dev;
struct napi_struct napi_tx;
struct k3_cppi_desc_pool *desc_pool;
struct k3_udma_glue_tx_channel *tx_chn;
struct prueth_emac *emac;
u32 id;
u32 descs_num;
unsigned int irq;
char name[32];
struct hrtimer tx_hrtimer;
unsigned long tx_pace_timeout_ns;
struct xsk_buff_pool *xsk_pool;
bool irq_disabled;
};
struct prueth_rx_chn {
struct device *dev;
struct device *dma_dev;
struct k3_cppi_desc_pool *desc_pool;
struct k3_udma_glue_rx_channel *rx_chn;
u32 descs_num;
unsigned int irq[ICSSG_MAX_RFLOWS]; /* separate irq per flow */
char name[32];
struct page_pool *pg_pool;
struct xdp_rxq_info xdp_rxq;
struct xsk_buff_pool *xsk_pool;
bool irq_disabled;
};
enum prueth_swdata_type {
PRUETH_SWDATA_INVALID = 0,
PRUETH_SWDATA_SKB,
PRUETH_SWDATA_PAGE,
PRUETH_SWDATA_CMD,
PRUETH_SWDATA_XDPF,
PRUETH_SWDATA_XSK,
};
enum prueth_tx_buff_type {
PRUETH_TX_BUFF_TYPE_XDP_TX,
PRUETH_TX_BUFF_TYPE_XDP_NDO,
};
struct prueth_swdata {
enum prueth_swdata_type type;
union prueth_data {
struct sk_buff *skb;
struct page *page;
u32 cmd;
struct xdp_frame *xdpf;
struct xdp_buff *xdp;
} data;
};
/* There are 4 Tx DMA channels, but the highest priority is CH3 (thread 3)
* and lower three are lower priority channels or threads.
*/
#define PRUETH_MAX_TX_QUEUES 4
#define PRUETH_MAX_TX_TS_REQUESTS 50 /* Max simultaneous TX_TS requests */
/* XDP BPF state */
#define ICSSG_XDP_PASS 0
#define ICSSG_XDP_CONSUMED BIT(0)
#define ICSSG_XDP_TX BIT(1)
#define ICSSG_XDP_REDIR BIT(2)
/* Minimum coalesce time in usecs for both Tx and Rx */
#define ICSSG_MIN_COALESCE_USECS 20
/* data for each emac port */
struct prueth_emac {
bool is_sr1;
struct prueth *prueth;
struct net_device *ndev;
u8 mac_addr[6];
struct napi_struct napi_rx;
u32 msg_enable;
int link;
int speed;
int duplex;
const char *phy_id;
struct device_node *phy_node;
phy_interface_t phy_if;
enum prueth_port port_id;
struct icss_iep *iep;
unsigned int rx_ts_enabled : 1;
unsigned int tx_ts_enabled : 1;
unsigned int half_duplex : 1;
/* DMA related */
struct prueth_tx_chn tx_chns[PRUETH_MAX_TX_QUEUES];
struct completion tdown_complete;
atomic_t tdown_cnt;
struct prueth_rx_chn rx_chns;
int rx_flow_id_base;
int tx_ch_num;
/* SR1.0 Management channel */
struct prueth_rx_chn rx_mgm_chn;
int rx_mgm_flow_id_base;
spinlock_t lock; /* serialize access */
/* TX HW Timestamping */
/* TX TS cookie will be index to the tx_ts_skb array */
struct sk_buff *tx_ts_skb[PRUETH_MAX_TX_TS_REQUESTS];
atomic_t tx_ts_pending;
int tx_ts_irq;
u8 cmd_seq;
/* shutdown related */
__le32 cmd_data[4];
struct completion cmd_complete;
/* Mutex to serialize access to firmware command interface */
struct mutex cmd_lock;
struct work_struct rx_mode_work;
struct pruss_mem_region dram;
bool offload_fwd_mark;
int port_vlan;
struct delayed_work stats_work;
u64 stats[ICSSG_NUM_MIIG_STATS];
u64 pa_stats[ICSSG_NUM_PA_STATS];
/* RX IRQ Coalescing Related */
struct hrtimer rx_hrtimer;
unsigned long rx_pace_timeout_ns;
struct netdev_hw_addr_list vlan_mcast_list[MAX_VLAN_ID];
struct bpf_prog *xdp_prog;
struct xdp_attachment_info xdpi;
int xsk_qid;
};
/* The buf includes headroom compatible with both skb and xdpf */
#define PRUETH_HEADROOM_NA (max(XDP_PACKET_HEADROOM, NET_SKB_PAD) + NET_IP_ALIGN)
#define PRUETH_HEADROOM ALIGN(PRUETH_HEADROOM_NA, sizeof(long))
/**
* struct prueth_pdata - PRUeth platform data
* @fdqring_mode: Free desc queue mode
* @quirk_10m_link_issue: 10M link detect errata
* @switch_mode: switch firmware support
* @banked_ms_ram: banked memory support
*/
struct prueth_pdata {
enum k3_ring_mode fdqring_mode;
u32 quirk_10m_link_issue:1;
u32 switch_mode:1;
u32 banked_ms_ram:1;
};
struct icssg_firmwares {
const char *pru;
const char *rtu;
const char *txpru;
};
/**
* struct prueth - PRUeth structure
* @dev: device
* @pruss: pruss handle
* @pru: rproc instances of PRUs
* @rtu: rproc instances of RTUs
* @txpru: rproc instances of TX_PRUs
* @shram: PRUSS shared RAM region
* @sram_pool: MSMC RAM pool for buffers
* @msmcram: MSMC RAM region
* @eth_node: DT node for the port
* @emac: private EMAC data structure
* @registered_netdevs: list of registered netdevs
* @miig_rt: regmap to mii_g_rt block
* @mii_rt: regmap to mii_rt block
* @pa_stats: regmap to pa_stats block
* @pru_id: ID for each of the PRUs
* @pdev: pointer to ICSSG platform device
* @pdata: pointer to platform data for ICSSG driver
* @icssg_hwcmdseq: seq counter or HWQ messages
* @emacs_initialized: num of EMACs/ext ports that are up/running
* @iep0: pointer to IEP0 device
* @iep1: pointer to IEP1 device
* @vlan_tbl: VLAN-FID table pointer
* @hw_bridge_dev: pointer to HW bridge net device
* @hsr_dev: pointer to the HSR net device
* @hsr_prp_version: enum to store the protocol version of hsr master
* @br_members: bitmask of bridge member ports
* @hsr_members: bitmask of hsr member ports
* @prueth_netdevice_nb: netdevice notifier block
* @prueth_switchdev_nb: switchdev notifier block
* @prueth_switchdev_bl_nb: switchdev blocking notifier block
* @is_switch_mode: flag to indicate if device is in Switch mode
* @is_hsr_offload_mode: flag to indicate if device is in hsr offload mode
* @is_switchmode_supported: indicates platform support for switch mode
* @switch_id: ID for mapping switch ports to bridge
* @default_vlan: Default VLAN for host
* @icssg_emac_firmwares: Firmware names for EMAC mode, indexed per MAC
* @icssg_switch_firmwares: Firmware names for SWITCH mode, indexed per MAC
* @icssg_hsr_firmwares: Firmware names for HSR mode, indexed per MAC
* @icssg_prp_firmwares: Firmware names for PRP mode, indexed per MAC
*/
struct prueth {
struct device *dev;
struct pruss *pruss;
struct rproc *pru[PRUSS_NUM_PRUS];
struct rproc *rtu[PRUSS_NUM_PRUS];
struct rproc *txpru[PRUSS_NUM_PRUS];
struct pruss_mem_region shram;
struct gen_pool *sram_pool;
struct pruss_mem_region msmcram;
struct device_node *eth_node[PRUETH_NUM_MACS];
struct prueth_emac *emac[PRUETH_NUM_MACS];
struct net_device *registered_netdevs[PRUETH_NUM_MACS];
struct regmap *miig_rt;
struct regmap *mii_rt;
struct regmap *pa_stats;
enum pruss_pru_id pru_id[PRUSS_NUM_PRUS];
struct platform_device *pdev;
struct prueth_pdata pdata;
u8 icssg_hwcmdseq;
int emacs_initialized;
struct icss_iep *iep0;
struct icss_iep *iep1;
struct prueth_vlan_tbl *vlan_tbl;
struct net_device *hw_bridge_dev;
struct net_device *hsr_dev;
enum hsr_version hsr_prp_version;
u8 br_members;
u8 hsr_members;
struct notifier_block prueth_netdevice_nb;
struct notifier_block prueth_switchdev_nb;
struct notifier_block prueth_switchdev_bl_nb;
bool is_switch_mode;
bool is_hsr_offload_mode;
bool is_switchmode_supported;
unsigned char switch_id[MAX_PHYS_ITEM_ID_LEN];
int default_vlan;
/** @vtbl_lock: Lock for vtbl in shared memory */
spinlock_t vtbl_lock;
/** @stats_lock: Lock for reading icssg stats */
spinlock_t stats_lock;
struct icssg_firmwares icssg_emac_firmwares[PRUETH_NUM_MACS];
struct icssg_firmwares icssg_switch_firmwares[PRUETH_NUM_MACS];
struct icssg_firmwares icssg_hsr_firmwares[PRUETH_NUM_MACS];
struct icssg_firmwares icssg_prp_firmwares[PRUETH_NUM_MACS];
};
struct emac_tx_ts_response {
u32 reserved[2];
u32 cookie;
u32 lo_ts;
u32 hi_ts;
};
struct emac_tx_ts_response_sr1 {
__le32 lo_ts;
__le32 hi_ts;
__le32 reserved;
__le32 cookie;
};
/* get PRUSS SLICE number from prueth_emac */
static inline int prueth_emac_slice(struct prueth_emac *emac)
{
switch (emac->port_id) {
case PRUETH_PORT_MII0:
return ICSS_SLICE0;
case PRUETH_PORT_MII1:
return ICSS_SLICE1;
default:
return -EINVAL;
}
}
extern const struct ethtool_ops icssg_ethtool_ops;
extern const struct dev_pm_ops prueth_dev_pm_ops;
static inline u64 icssg_read_time(const void __iomem *addr)
{
u32 low, high;
do {
high = readl(addr + 4);
low = readl(addr);
} while (high != readl(addr + 4));
return low + ((u64)high << 32);
}
/* Classifier helpers */
void icssg_class_set_mac_addr(struct regmap *miig_rt, int slice, u8 *mac);
void icssg_class_set_host_mac_addr(struct regmap *miig_rt, const u8 *mac);
void icssg_class_disable(struct regmap *miig_rt, int slice);
void icssg_class_default(struct regmap *miig_rt, int slice, bool allmulti,
bool is_sr1);
void icssg_class_promiscuous_sr1(struct regmap *miig_rt, int slice);
void icssg_class_add_mcast_sr1(struct regmap *miig_rt, int slice,
struct net_device *ndev);
void icssg_ft1_set_mac_addr(struct regmap *miig_rt, int slice, u8 *mac_addr);
void icssg_ft3_hsr_configurations(struct regmap *miig_rt, int slice,
struct prueth *prueth);
/* config helpers */
void icssg_config_ipg(struct prueth_emac *emac);
int icssg_config(struct prueth *prueth, struct prueth_emac *emac,
int slice);
int icssg_set_port_state(struct prueth_emac *emac,
enum icssg_port_state_cmd state);
void icssg_config_set_speed(struct prueth_emac *emac);
void icssg_config_half_duplex(struct prueth_emac *emac);
void icssg_init_emac_mode(struct prueth *prueth);
void icssg_init_fw_offload_mode(struct prueth *prueth);
/* Buffer queue helpers */
int icssg_queue_pop(struct prueth *prueth, u8 queue);
void icssg_queue_push(struct prueth *prueth, int queue, u16 addr);
u32 icssg_queue_level(struct prueth *prueth, int queue);
int icssg_send_fdb_msg(struct prueth_emac *emac, struct mgmt_cmd *cmd,
struct mgmt_cmd_rsp *rsp);
int icssg_fdb_add_del(struct prueth_emac *emac, const unsigned char *addr,
u8 vid, u8 fid_c2, bool add);
int icssg_fdb_lookup(struct prueth_emac *emac, const unsigned char *addr,
u8 vid);
void icssg_vtbl_modify(struct prueth_emac *emac, u8 vid, u8 port_mask,
u8 untag_mask, bool add);
u16 icssg_get_pvid(struct prueth_emac *emac);
void icssg_set_pvid(struct prueth *prueth, u8 vid, u8 port);
int emac_fdb_flow_id_updated(struct prueth_emac *emac);
#define prueth_napi_to_tx_chn(pnapi) \
container_of(pnapi, struct prueth_tx_chn, napi_tx)
void icssg_stats_work_handler(struct work_struct *work);
void emac_update_hardware_stats(struct prueth_emac *emac);
int emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name);
/* Common functions */
void prueth_cleanup_rx_chns(struct prueth_emac *emac,
struct prueth_rx_chn *rx_chn,
int max_rflows);
void prueth_cleanup_tx_chns(struct prueth_emac *emac);
void prueth_ndev_del_tx_napi(struct prueth_emac *emac, int num);
void prueth_xmit_free(struct prueth_tx_chn *tx_chn,
struct cppi5_host_desc_t *desc);
int emac_tx_complete_packets(struct prueth_emac *emac, int chn,
int budget, bool *tdown);
int prueth_ndev_add_tx_napi(struct prueth_emac *emac);
int prueth_init_tx_chns(struct prueth_emac *emac);
int prueth_init_rx_chns(struct prueth_emac *emac,
struct prueth_rx_chn *rx_chn,
char *name, u32 max_rflows,
u32 max_desc_num);
int prueth_dma_rx_push_mapped(struct prueth_emac *emac,
struct prueth_rx_chn *rx_chn,
struct page *page, u32 buf_len);
unsigned int prueth_rxbuf_total_len(unsigned int len);
void emac_rx_timestamp(struct prueth_emac *emac,
struct sk_buff *skb, u32 *psdata);
enum netdev_tx icssg_ndo_start_xmit(struct sk_buff *skb, struct net_device *ndev);
irqreturn_t prueth_rx_irq(int irq, void *dev_id);
void prueth_cleanup_tx_ts(struct prueth_emac *emac);
int icssg_napi_rx_poll(struct napi_struct *napi_rx, int budget);
int prueth_prepare_rx_chan(struct prueth_emac *emac,
struct prueth_rx_chn *chn,
int buf_size);
void prueth_reset_tx_chan(struct prueth_emac *emac, int ch_num,
bool free_skb);
void prueth_reset_rx_chan(struct prueth_rx_chn *chn,
int num_flows, bool disable);
void icssg_ndo_tx_timeout(struct net_device *ndev, unsigned int txqueue);
int icssg_ndo_get_ts_config(struct net_device *ndev,
struct kernel_hwtstamp_config *config);
int icssg_ndo_set_ts_config(struct net_device *ndev,
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack);
void icssg_ndo_get_stats64(struct net_device *ndev,
struct rtnl_link_stats64 *stats);
int icssg_ndo_get_phys_port_name(struct net_device *ndev, char *name,
size_t len);
int prueth_node_port(struct device_node *eth_node);
int prueth_node_mac(struct device_node *eth_node);
void prueth_netdev_exit(struct prueth *prueth,
struct device_node *eth_node);
int prueth_get_cores(struct prueth *prueth, int slice, bool is_sr1);
void prueth_put_cores(struct prueth *prueth, int slice);
/* Revision specific helper */
u64 icssg_ts_to_ns(u32 hi_sw, u32 hi, u32 lo, u32 cycle_time_ns);
u32 emac_xmit_xdp_frame(struct prueth_emac *emac,
struct xdp_frame *xdpf,
unsigned int q_idx,
enum prueth_tx_buff_type buff_type);
void prueth_rx_cleanup(void *data, dma_addr_t desc_dma);
void prueth_tx_cleanup(void *data, dma_addr_t desc_dma);
int prueth_xsk_wakeup(struct net_device *ndev, u32 qid, u32 flags);
static inline bool prueth_xdp_is_enabled(struct prueth_emac *emac)
{
return !!READ_ONCE(emac->xdp_prog);
}
#endif /* __NET_TI_ICSSG_PRUETH_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0
/* ICSSG Buffer queue helpers
*
* Copyright (C) 2021 Texas Instruments Incorporated - https://www.ti.com
*/
#include <linux/regmap.h>
#include "icssg_prueth.h"
#define ICSSG_QUEUES_MAX 64
#define ICSSG_QUEUE_OFFSET 0xd00
#define ICSSG_QUEUE_PEEK_OFFSET 0xe00
#define ICSSG_QUEUE_CNT_OFFSET 0xe40
#define ICSSG_QUEUE_RESET_OFFSET 0xf40
int icssg_queue_pop(struct prueth *prueth, u8 queue)
{
u32 val, cnt;
if (queue >= ICSSG_QUEUES_MAX)
return -EINVAL;
regmap_read(prueth->miig_rt, ICSSG_QUEUE_CNT_OFFSET + 4 * queue, &cnt);
if (!cnt)
return -EINVAL;
regmap_read(prueth->miig_rt, ICSSG_QUEUE_OFFSET + 4 * queue, &val);
return val;
}
EXPORT_SYMBOL_GPL(icssg_queue_pop);
void icssg_queue_push(struct prueth *prueth, int queue, u16 addr)
{
if (queue >= ICSSG_QUEUES_MAX)
return;
regmap_write(prueth->miig_rt, ICSSG_QUEUE_OFFSET + 4 * queue, addr);
}
EXPORT_SYMBOL_GPL(icssg_queue_push);
u32 icssg_queue_level(struct prueth *prueth, int queue)
{
u32 reg;
if (queue >= ICSSG_QUEUES_MAX)
return 0;
regmap_read(prueth->miig_rt, ICSSG_QUEUE_CNT_OFFSET + 4 * queue, &reg);
return reg;
}
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments ICSSG Ethernet driver
*
* Copyright (C) 2018-2021 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#include "icssg_prueth.h"
#include "icssg_stats.h"
#include <linux/regmap.h>
#define ICSSG_TX_PACKET_OFFSET 0xA0
#define ICSSG_TX_BYTE_OFFSET 0xEC
static u32 stats_base[] = { 0x54c, /* Slice 0 stats start */
0xb18, /* Slice 1 stats start */
};
void emac_update_hardware_stats(struct prueth_emac *emac)
{
struct prueth *prueth = emac->prueth;
int slice = prueth_emac_slice(emac);
u32 base = stats_base[slice];
u32 tx_pkt_cnt = 0;
u32 val, reg;
int i;
spin_lock(&prueth->stats_lock);
for (i = 0; i < ARRAY_SIZE(icssg_all_miig_stats); i++) {
/* In MII mode TX lines are swapped inside ICSSG, so read Tx stats
* from slice1 for port0 and slice0 for port1 to get accurate Tx
* stats for a given port
*/
if (emac->phy_if == PHY_INTERFACE_MODE_MII &&
icssg_all_miig_stats[i].offset >= ICSSG_TX_PACKET_OFFSET &&
icssg_all_miig_stats[i].offset <= ICSSG_TX_BYTE_OFFSET)
base = stats_base[slice ^ 1];
regmap_read(prueth->miig_rt,
base + icssg_all_miig_stats[i].offset,
&val);
regmap_write(prueth->miig_rt,
base + icssg_all_miig_stats[i].offset,
val);
if (icssg_all_miig_stats[i].offset == ICSSG_TX_PACKET_OFFSET)
tx_pkt_cnt = val;
emac->stats[i] += val;
if (icssg_all_miig_stats[i].offset == ICSSG_TX_BYTE_OFFSET)
emac->stats[i] -= tx_pkt_cnt * 8;
}
if (prueth->pa_stats) {
for (i = 0; i < ARRAY_SIZE(icssg_all_pa_stats); i++) {
reg = icssg_all_pa_stats[i].offset +
slice * sizeof(u32);
regmap_read(prueth->pa_stats, reg, &val);
emac->pa_stats[i] += val;
}
}
spin_unlock(&prueth->stats_lock);
}
void icssg_stats_work_handler(struct work_struct *work)
{
struct prueth_emac *emac = container_of(work, struct prueth_emac,
stats_work.work);
emac_update_hardware_stats(emac);
queue_delayed_work(system_long_wq, &emac->stats_work,
msecs_to_jiffies((STATS_TIME_LIMIT_1G_MS * 1000) / emac->speed));
}
EXPORT_SYMBOL_GPL(icssg_stats_work_handler);
int emac_get_stat_by_name(struct prueth_emac *emac, char *stat_name)
{
int i;
for (i = 0; i < ARRAY_SIZE(icssg_all_miig_stats); i++) {
if (!strcmp(icssg_all_miig_stats[i].name, stat_name))
return emac->stats[icssg_all_miig_stats[i].offset / sizeof(u32)];
}
if (emac->prueth->pa_stats) {
for (i = 0; i < ARRAY_SIZE(icssg_all_pa_stats); i++) {
if (!strcmp(icssg_all_pa_stats[i].name, stat_name))
return emac->pa_stats[i];
}
}
netdev_err(emac->ndev, "Invalid stats %s\n", stat_name);
return -EINVAL;
}
@@ -0,0 +1,204 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Texas Instruments ICSSG Ethernet driver
*
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#ifndef __NET_TI_ICSSG_STATS_H
#define __NET_TI_ICSSG_STATS_H
#include "icssg_prueth.h"
#define STATS_TIME_LIMIT_1G_MS 25000 /* 25 seconds @ 1G */
struct miig_stats_regs {
/* Rx */
u32 rx_packets;
u32 rx_broadcast_frames;
u32 rx_multicast_frames;
u32 rx_crc_errors;
u32 rx_mii_error_frames;
u32 rx_odd_nibble_frames;
u32 rx_frame_max_size;
u32 rx_max_size_error_frames;
u32 rx_frame_min_size;
u32 rx_min_size_error_frames;
u32 rx_over_errors;
u32 rx_class0_hits;
u32 rx_class1_hits;
u32 rx_class2_hits;
u32 rx_class3_hits;
u32 rx_class4_hits;
u32 rx_class5_hits;
u32 rx_class6_hits;
u32 rx_class7_hits;
u32 rx_class8_hits;
u32 rx_class9_hits;
u32 rx_class10_hits;
u32 rx_class11_hits;
u32 rx_class12_hits;
u32 rx_class13_hits;
u32 rx_class14_hits;
u32 rx_class15_hits;
u32 rx_smd_frags;
u32 rx_bucket1_size;
u32 rx_bucket2_size;
u32 rx_bucket3_size;
u32 rx_bucket4_size;
u32 rx_64B_frames;
u32 rx_bucket1_frames;
u32 rx_bucket2_frames;
u32 rx_bucket3_frames;
u32 rx_bucket4_frames;
u32 rx_bucket5_frames;
u32 rx_bytes;
u32 rx_tx_total_bytes;
/* Tx */
u32 tx_packets;
u32 tx_broadcast_frames;
u32 tx_multicast_frames;
u32 tx_odd_nibble_frames;
u32 tx_underflow_errors;
u32 tx_frame_max_size;
u32 tx_max_size_error_frames;
u32 tx_frame_min_size;
u32 tx_min_size_error_frames;
u32 tx_bucket1_size;
u32 tx_bucket2_size;
u32 tx_bucket3_size;
u32 tx_bucket4_size;
u32 tx_64B_frames;
u32 tx_bucket1_frames;
u32 tx_bucket2_frames;
u32 tx_bucket3_frames;
u32 tx_bucket4_frames;
u32 tx_bucket5_frames;
u32 tx_bytes;
};
#define ICSSG_MIIG_STATS(field, stats_type) \
{ \
#field, \
offsetof(struct miig_stats_regs, field), \
stats_type \
}
struct icssg_miig_stats {
char name[ETH_GSTRING_LEN];
u32 offset;
bool standard_stats;
};
static const struct icssg_miig_stats icssg_all_miig_stats[] = {
/* Rx */
ICSSG_MIIG_STATS(rx_packets, true),
ICSSG_MIIG_STATS(rx_broadcast_frames, false),
ICSSG_MIIG_STATS(rx_multicast_frames, true),
ICSSG_MIIG_STATS(rx_crc_errors, true),
ICSSG_MIIG_STATS(rx_mii_error_frames, false),
ICSSG_MIIG_STATS(rx_odd_nibble_frames, false),
ICSSG_MIIG_STATS(rx_frame_max_size, true),
ICSSG_MIIG_STATS(rx_max_size_error_frames, false),
ICSSG_MIIG_STATS(rx_frame_min_size, true),
ICSSG_MIIG_STATS(rx_min_size_error_frames, false),
ICSSG_MIIG_STATS(rx_over_errors, true),
ICSSG_MIIG_STATS(rx_class0_hits, false),
ICSSG_MIIG_STATS(rx_class1_hits, false),
ICSSG_MIIG_STATS(rx_class2_hits, false),
ICSSG_MIIG_STATS(rx_class3_hits, false),
ICSSG_MIIG_STATS(rx_class4_hits, false),
ICSSG_MIIG_STATS(rx_class5_hits, false),
ICSSG_MIIG_STATS(rx_class6_hits, false),
ICSSG_MIIG_STATS(rx_class7_hits, false),
ICSSG_MIIG_STATS(rx_class8_hits, false),
ICSSG_MIIG_STATS(rx_class9_hits, false),
ICSSG_MIIG_STATS(rx_class10_hits, false),
ICSSG_MIIG_STATS(rx_class11_hits, false),
ICSSG_MIIG_STATS(rx_class12_hits, false),
ICSSG_MIIG_STATS(rx_class13_hits, false),
ICSSG_MIIG_STATS(rx_class14_hits, false),
ICSSG_MIIG_STATS(rx_class15_hits, false),
ICSSG_MIIG_STATS(rx_smd_frags, false),
ICSSG_MIIG_STATS(rx_bucket1_size, true),
ICSSG_MIIG_STATS(rx_bucket2_size, true),
ICSSG_MIIG_STATS(rx_bucket3_size, true),
ICSSG_MIIG_STATS(rx_bucket4_size, true),
ICSSG_MIIG_STATS(rx_64B_frames, true),
ICSSG_MIIG_STATS(rx_bucket1_frames, true),
ICSSG_MIIG_STATS(rx_bucket2_frames, true),
ICSSG_MIIG_STATS(rx_bucket3_frames, true),
ICSSG_MIIG_STATS(rx_bucket4_frames, true),
ICSSG_MIIG_STATS(rx_bucket5_frames, true),
ICSSG_MIIG_STATS(rx_bytes, true),
ICSSG_MIIG_STATS(rx_tx_total_bytes, false),
/* Tx */
ICSSG_MIIG_STATS(tx_packets, true),
ICSSG_MIIG_STATS(tx_broadcast_frames, false),
ICSSG_MIIG_STATS(tx_multicast_frames, false),
ICSSG_MIIG_STATS(tx_odd_nibble_frames, false),
ICSSG_MIIG_STATS(tx_underflow_errors, false),
ICSSG_MIIG_STATS(tx_frame_max_size, true),
ICSSG_MIIG_STATS(tx_max_size_error_frames, false),
ICSSG_MIIG_STATS(tx_frame_min_size, true),
ICSSG_MIIG_STATS(tx_min_size_error_frames, false),
ICSSG_MIIG_STATS(tx_bucket1_size, true),
ICSSG_MIIG_STATS(tx_bucket2_size, true),
ICSSG_MIIG_STATS(tx_bucket3_size, true),
ICSSG_MIIG_STATS(tx_bucket4_size, true),
ICSSG_MIIG_STATS(tx_64B_frames, true),
ICSSG_MIIG_STATS(tx_bucket1_frames, true),
ICSSG_MIIG_STATS(tx_bucket2_frames, true),
ICSSG_MIIG_STATS(tx_bucket3_frames, true),
ICSSG_MIIG_STATS(tx_bucket4_frames, true),
ICSSG_MIIG_STATS(tx_bucket5_frames, true),
ICSSG_MIIG_STATS(tx_bytes, true),
};
#define ICSSG_PA_STATS(field) \
{ \
#field, \
field, \
}
struct icssg_pa_stats {
char name[ETH_GSTRING_LEN];
u32 offset;
};
static const struct icssg_pa_stats icssg_all_pa_stats[] = {
ICSSG_PA_STATS(FW_RTU_PKT_DROP),
ICSSG_PA_STATS(FW_Q0_OVERFLOW),
ICSSG_PA_STATS(FW_Q1_OVERFLOW),
ICSSG_PA_STATS(FW_Q2_OVERFLOW),
ICSSG_PA_STATS(FW_Q3_OVERFLOW),
ICSSG_PA_STATS(FW_Q4_OVERFLOW),
ICSSG_PA_STATS(FW_Q5_OVERFLOW),
ICSSG_PA_STATS(FW_Q6_OVERFLOW),
ICSSG_PA_STATS(FW_Q7_OVERFLOW),
ICSSG_PA_STATS(FW_DROPPED_PKT),
ICSSG_PA_STATS(FW_RX_ERROR),
ICSSG_PA_STATS(FW_RX_DS_INVALID),
ICSSG_PA_STATS(FW_TX_DROPPED_PACKET),
ICSSG_PA_STATS(FW_TX_TS_DROPPED_PACKET),
ICSSG_PA_STATS(FW_INF_PORT_DISABLED),
ICSSG_PA_STATS(FW_INF_SAV),
ICSSG_PA_STATS(FW_INF_SA_DL),
ICSSG_PA_STATS(FW_INF_PORT_BLOCKED),
ICSSG_PA_STATS(FW_INF_DROP_TAGGED),
ICSSG_PA_STATS(FW_INF_DROP_PRIOTAGGED),
ICSSG_PA_STATS(FW_INF_DROP_NOTAG),
ICSSG_PA_STATS(FW_INF_DROP_NOTMEMBER),
ICSSG_PA_STATS(FW_RX_EOF_SHORT_FRMERR),
ICSSG_PA_STATS(FW_RX_B0_DROP_EARLY_EOF),
ICSSG_PA_STATS(FW_TX_JUMBO_FRM_CUTOFF),
ICSSG_PA_STATS(FW_RX_EXP_FRAG_Q_DROP),
ICSSG_PA_STATS(FW_RX_FIFO_OVERRUN),
ICSSG_PA_STATS(FW_CUT_THR_PKT),
ICSSG_PA_STATS(FW_HOST_RX_PKT_CNT),
ICSSG_PA_STATS(FW_HOST_TX_PKT_CNT),
ICSSG_PA_STATS(FW_HOST_EGRESS_Q_PRE_OVERFLOW),
ICSSG_PA_STATS(FW_HOST_EGRESS_Q_EXP_OVERFLOW),
};
#endif /* __NET_TI_ICSSG_STATS_H */
@@ -0,0 +1,270 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Texas Instruments ICSSG Ethernet driver
*
* Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#ifndef __NET_TI_ICSSG_SWITCH_MAP_H
#define __NET_TI_ICSSG_SWITCH_MAP_H
/************************* Ethernet Switch Constants *********************/
/* if bucket size is changed in firmware then this too should be changed
* because it directly impacts FDB ageing calculation
*/
#define NUMBER_OF_FDB_BUCKET_ENTRIES (4)
/* This is fixed in ICSSG */
#define SIZE_OF_FDB (2048)
#define FW_LINK_SPEED_1G (0x00)
#define FW_LINK_SPEED_100M (0x01)
#define FW_LINK_SPEED_10M (0x02)
#define FW_LINK_SPEED_HD (0x80)
/* Time after which FDB entries are checked for aged out values.
* Values are in nanoseconds
*/
#define FDB_AGEING_TIMEOUT_OFFSET 0x0014
/* Default VLAN tag for Host Port */
#define HOST_PORT_DF_VLAN_OFFSET 0x001C
/* Same as HOST_PORT_DF_VLAN_OFFSET */
#define EMAC_ICSSG_SWITCH_PORT0_DEFAULT_VLAN_OFFSET HOST_PORT_DF_VLAN_OFFSET
/* Default VLAN tag for P1 Port */
#define P1_PORT_DF_VLAN_OFFSET 0x0020
/* Same as P1_PORT_DF_VLAN_OFFSET */
#define EMAC_ICSSG_SWITCH_PORT1_DEFAULT_VLAN_OFFSET P1_PORT_DF_VLAN_OFFSET
/* default VLAN tag for P2 Port */
#define P2_PORT_DF_VLAN_OFFSET 0x0024
/* Same as P2_PORT_DF_VLAN_OFFSET */
#define EMAC_ICSSG_SWITCH_PORT2_DEFAULT_VLAN_OFFSET P2_PORT_DF_VLAN_OFFSET
/* VLAN-FID Table offset. 4096 VIDs. 2B per VID = 8KB = 0x2000 */
#define VLAN_STATIC_REG_TABLE_OFFSET 0x0100
/* VLAN-FID Table offset for EMAC */
#define EMAC_ICSSG_SWITCH_DEFAULT_VLAN_TABLE_OFFSET VLAN_STATIC_REG_TABLE_OFFSET
/* Packet descriptor Q reserved memory */
#define PORT_DESC0_HI 0x2104
/* Packet descriptor Q reserved memory */
#define PORT_DESC0_LO 0x2F6C
/* Packet descriptor Q reserved memory */
#define PORT_DESC1_HI 0x3DD4
/* Packet descriptor Q reserved memory */
#define PORT_DESC1_LO 0x4C3C
/* Packet descriptor Q reserved memory */
#define HOST_DESC0_HI 0x5AA4
/* Packet descriptor Q reserved memory */
#define HOST_DESC0_LO 0x5F0C
/* Packet descriptor Q reserved memory */
#define HOST_DESC1_HI 0x6374
/* Packet descriptor Q reserved memory */
#define HOST_DESC1_LO 0x67DC
/* Special packet descriptor Q reserved memory */
#define HOST_SPPD0 0x7AAC
/* Special acket descriptor Q reserved memory */
#define HOST_SPPD1 0x7EAC
/* IEP count cycle counter*/
#define TIMESYNC_FW_WC_CYCLECOUNT_OFFSET 0x83EC
/* IEP count hi roll over count */
#define TIMESYNC_FW_WC_HI_ROLLOVER_COUNT_OFFSET 0x83F4
/* IEP count hi sw counter */
#define TIMESYNC_FW_WC_COUNT_HI_SW_OFFSET_OFFSET 0x83F8
/* Set clock descriptor */
#define TIMESYNC_FW_WC_SETCLOCK_DESC_OFFSET 0x83FC
/* IEP count syncout reduction factor */
#define TIMESYNC_FW_WC_SYNCOUT_REDUCTION_FACTOR_OFFSET 0x843C
/* IEP count syncout reduction counter */
#define TIMESYNC_FW_WC_SYNCOUT_REDUCTION_COUNT_OFFSET 0x8440
/* IEP count syncout start time cycle counter */
#define TIMESYNC_FW_WC_SYNCOUT_START_TIME_CYCLECOUNT_OFFSET 0x8444
/* Control variable to generate SYNC1 */
#define TIMESYNC_FW_WC_ISOM_PIN_SIGNAL_EN_OFFSET 0x844C
/* SystemTime Sync0 periodicity */
#define TIMESYNC_FW_ST_SYNCOUT_PERIOD_OFFSET 0x8450
/* pktTxDelay for P1 = link speed dependent p1 mac delay + p1 phy delay */
#define TIMESYNC_FW_WC_PKTTXDELAY_P1_OFFSET 0x8454
/* pktTxDelay for P2 = link speed dependent p2 mac delay + p2 phy delay */
#define TIMESYNC_FW_WC_PKTTXDELAY_P2_OFFSET 0x8458
/* Set clock operation done signal for next task */
#define TIMESYNC_FW_SIG_PNFW_OFFSET 0x845C
/* Set clock operation done signal for next task */
#define TIMESYNC_FW_SIG_TIMESYNCFW_OFFSET 0x8460
/* New list is copied at this time */
#define TAS_CONFIG_CHANGE_TIME 0x000C
/* config change error counter */
#define TAS_CONFIG_CHANGE_ERROR_COUNTER 0x0014
/* TAS List update pending flag */
#define TAS_CONFIG_PENDING 0x0018
/* TAS list update trigger flag */
#define TAS_CONFIG_CHANGE 0x0019
/* List length for new TAS schedule */
#define TAS_ADMIN_LIST_LENGTH 0x001A
/* Currently active TAS list index */
#define TAS_ACTIVE_LIST_INDEX 0x001B
/* Cycle time for the new TAS schedule */
#define TAS_ADMIN_CYCLE_TIME 0x001C
/* Cycle counts remaining till the TAS list update */
#define TAS_CONFIG_CHANGE_CYCLE_COUNT 0x0020
/* Base Flow ID for sending Packets to Host for Slice0 */
#define PSI_L_REGULAR_FLOW_ID_BASE_OFFSET 0x0024
/* Same as PSI_L_REGULAR_FLOW_ID_BASE_OFFSET */
#define EMAC_ICSSG_SWITCH_PSI_L_REGULAR_FLOW_ID_BASE_OFFSET PSI_L_REGULAR_FLOW_ID_BASE_OFFSET
/* Base Flow ID for sending mgmt and Tx TS to Host for Slice0 */
#define PSI_L_MGMT_FLOW_ID_OFFSET 0x0026
/* Same as PSI_L_MGMT_FLOW_ID_OFFSET */
#define EMAC_ICSSG_SWITCH_PSI_L_MGMT_FLOW_ID_BASE_OFFSET PSI_L_MGMT_FLOW_ID_OFFSET
/* Queue number for Special Packets written here */
#define SPL_PKT_DEFAULT_PRIORITY 0x0028
/* Express Preemptible Queue Mask */
#define EXPRESS_PRE_EMPTIVE_Q_MASK 0x0029
/* Port1/Port2 Default Queue number for untagged Packets, only 1B is used */
#define QUEUE_NUM_UNTAGGED 0x002A
/* Stores the table used for priority regeneration. 1B per PCP/Queue */
#define PORT_Q_PRIORITY_REGEN_OFFSET 0x002C
/* For marking Packet as priority/express (this feature is disabled) or
* cut-through/S&F.
*/
#define EXPRESS_PRE_EMPTIVE_Q_MAP 0x0034
/* Stores the table used for priority mapping. 1B per PCP/Queue */
#define PORT_Q_PRIORITY_MAPPING_OFFSET 0x003C
/* Used to notify the FW of the current link speed */
#define PORT_LINK_SPEED_OFFSET 0x00A8
/* 2k memory pointer reserved for default writes by PRU0*/
#define DEFAULT_MSMC_Q_OFFSET 0x00AC
/* TAS gate mask for windows list0 */
#define TAS_GATE_MASK_LIST0 0x0100
/* TAS gate mask for windows list1 */
#define TAS_GATE_MASK_LIST1 0x0350
/* Memory to Enable/Disable Preemption on TX side */
#define PRE_EMPTION_ENABLE_TX 0x05A0
/* Active State of Preemption on TX side */
#define PRE_EMPTION_ACTIVE_TX 0x05A1
/* Memory to Enable/Disable Verify State Machine Preemption */
#define PRE_EMPTION_ENABLE_VERIFY 0x05A2
/* Verify Status of State Machine */
#define PRE_EMPTION_VERIFY_STATUS 0x05A3
/* Non Final Fragment Size supported by Link Partner */
#define PRE_EMPTION_ADD_FRAG_SIZE_REMOTE 0x05A4
/* Non Final Fragment Size supported by Firmware */
#define PRE_EMPTION_ADD_FRAG_SIZE_LOCAL 0x05A6
/* Time in ms the State machine waits for respond Packet */
#define PRE_EMPTION_VERIFY_TIME 0x05A8
/* Memory used for R30 related management commands */
#define MGR_R30_CMD_OFFSET 0x05AC
/* HW Buffer Pool0 base address */
#define BUFFER_POOL_0_ADDR_OFFSET 0x05BC
/* 16B for Host Egress MSMC Q (Pre-emptible) context */
#define HOST_RX_Q_PRE_CONTEXT_OFFSET 0x0684
/* Buffer for 8 FDB entries to be added by 'Add Multiple FDB entries IOCTL' */
#define FDB_CMD_BUFFER 0x0894
/* TAS queue max sdu length list */
#define TAS_QUEUE_MAX_SDU_LIST 0x08FA
/* Used by FW to generate random number with the SEED value */
#define HD_RAND_SEED_OFFSET 0x0934
/* 16B for Host Egress MSMC Q (Express) context */
#define HOST_RX_Q_EXP_CONTEXT_OFFSET 0x0940
/* Start of 32 bits PA_STAT counters */
#define PA_STAT_32b_START_OFFSET 0x0080
#define FW_RTU_PKT_DROP 0x0088
#define FW_Q0_OVERFLOW 0x0090
#define FW_Q1_OVERFLOW 0x0098
#define FW_Q2_OVERFLOW 0x00A0
#define FW_Q3_OVERFLOW 0x00A8
#define FW_Q4_OVERFLOW 0x00B0
#define FW_Q5_OVERFLOW 0x00B8
#define FW_Q6_OVERFLOW 0x00C0
#define FW_Q7_OVERFLOW 0x00C8
#define FW_DROPPED_PKT 0x00F8
#define FW_RX_ERROR 0x0100
#define FW_RX_DS_INVALID 0x0108
#define FW_TX_DROPPED_PACKET 0x0110
#define FW_TX_TS_DROPPED_PACKET 0x0118
#define FW_INF_PORT_DISABLED 0x0120
#define FW_INF_SAV 0x0128
#define FW_INF_SA_DL 0x0130
#define FW_INF_PORT_BLOCKED 0x0138
#define FW_INF_DROP_TAGGED 0x0140
#define FW_INF_DROP_PRIOTAGGED 0x0148
#define FW_INF_DROP_NOTAG 0x0150
#define FW_INF_DROP_NOTMEMBER 0x0158
#define FW_RX_EOF_SHORT_FRMERR 0x0188
#define FW_RX_B0_DROP_EARLY_EOF 0x0190
#define FW_TX_JUMBO_FRM_CUTOFF 0x0198
#define FW_RX_EXP_FRAG_Q_DROP 0x01A0
#define FW_RX_FIFO_OVERRUN 0x01A8
#define FW_CUT_THR_PKT 0x01B0
#define FW_HOST_RX_PKT_CNT 0x0248
#define FW_HOST_TX_PKT_CNT 0x0250
#define FW_HOST_EGRESS_Q_PRE_OVERFLOW 0x0258
#define FW_HOST_EGRESS_Q_EXP_OVERFLOW 0x0260
#endif /* __NET_TI_ICSSG_SWITCH_MAP_H */
@@ -0,0 +1,477 @@
// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments K3 ICSSG Ethernet Switchdev Driver
*
* Copyright (C) 2021 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/netdevice.h>
#include <linux/workqueue.h>
#include <net/switchdev.h>
#include "icssg_prueth.h"
#include "icssg_switchdev.h"
#include "icssg_mii_rt.h"
struct prueth_switchdev_event_work {
struct work_struct work;
struct switchdev_notifier_fdb_info fdb_info;
struct prueth_emac *emac;
unsigned long event;
};
static int prueth_switchdev_stp_state_set(struct prueth_emac *emac,
u8 state)
{
enum icssg_port_state_cmd emac_state;
int ret = 0;
switch (state) {
case BR_STATE_FORWARDING:
emac_state = ICSSG_EMAC_PORT_FORWARD;
break;
case BR_STATE_DISABLED:
emac_state = ICSSG_EMAC_PORT_DISABLE;
break;
case BR_STATE_LISTENING:
case BR_STATE_BLOCKING:
emac_state = ICSSG_EMAC_PORT_BLOCK;
break;
default:
return -EOPNOTSUPP;
}
icssg_set_port_state(emac, emac_state);
netdev_dbg(emac->ndev, "STP state: %u\n", emac_state);
return ret;
}
static int prueth_switchdev_attr_br_flags_set(struct prueth_emac *emac,
struct net_device *orig_dev,
struct switchdev_brport_flags brport_flags)
{
enum icssg_port_state_cmd emac_state;
if (brport_flags.mask & BR_MCAST_FLOOD)
emac_state = ICSSG_EMAC_PORT_MC_FLOODING_ENABLE;
else
emac_state = ICSSG_EMAC_PORT_MC_FLOODING_DISABLE;
netdev_dbg(emac->ndev, "BR_MCAST_FLOOD: %d port %u\n",
emac_state, emac->port_id);
icssg_set_port_state(emac, emac_state);
return 0;
}
static int prueth_switchdev_attr_br_flags_pre_set(struct net_device *netdev,
struct switchdev_brport_flags brport_flags)
{
if (brport_flags.mask & ~(BR_LEARNING | BR_MCAST_FLOOD))
return -EINVAL;
return 0;
}
static int prueth_switchdev_attr_set(struct net_device *ndev, const void *ctx,
const struct switchdev_attr *attr,
struct netlink_ext_ack *extack)
{
struct prueth_emac *emac = netdev_priv(ndev);
int ret;
netdev_dbg(ndev, "attr: id %u port: %u\n", attr->id, emac->port_id);
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
ret = prueth_switchdev_attr_br_flags_pre_set(ndev,
attr->u.brport_flags);
break;
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
ret = prueth_switchdev_stp_state_set(emac,
attr->u.stp_state);
netdev_dbg(ndev, "stp state: %u\n", attr->u.stp_state);
break;
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
ret = prueth_switchdev_attr_br_flags_set(emac, attr->orig_dev,
attr->u.brport_flags);
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static void prueth_switchdev_fdb_offload_notify(struct net_device *ndev,
struct switchdev_notifier_fdb_info *rcv)
{
struct switchdev_notifier_fdb_info info;
memset(&info, 0, sizeof(info));
info.addr = rcv->addr;
info.vid = rcv->vid;
info.offloaded = true;
call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
ndev, &info.info, NULL);
}
static void prueth_switchdev_event_work(struct work_struct *work)
{
struct prueth_switchdev_event_work *switchdev_work =
container_of(work, struct prueth_switchdev_event_work, work);
struct prueth_emac *emac = switchdev_work->emac;
struct switchdev_notifier_fdb_info *fdb;
int port_id = emac->port_id;
int ret;
rtnl_lock();
switch (switchdev_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
fdb = &switchdev_work->fdb_info;
netdev_dbg(emac->ndev, "prueth_fdb_add: MACID = %pM vid = %u flags = %u %u -- port %d\n",
fdb->addr, fdb->vid, fdb->added_by_user,
fdb->offloaded, port_id);
if (!fdb->added_by_user)
break;
if (!ether_addr_equal(emac->mac_addr, fdb->addr))
break;
ret = icssg_fdb_add_del(emac, fdb->addr, fdb->vid,
BIT(port_id), true);
if (!ret)
prueth_switchdev_fdb_offload_notify(emac->ndev, fdb);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb = &switchdev_work->fdb_info;
netdev_dbg(emac->ndev, "prueth_fdb_del: MACID = %pM vid = %u flags = %u %u -- port %d\n",
fdb->addr, fdb->vid, fdb->added_by_user,
fdb->offloaded, port_id);
if (!fdb->added_by_user)
break;
if (!ether_addr_equal(emac->mac_addr, fdb->addr))
break;
icssg_fdb_add_del(emac, fdb->addr, fdb->vid,
BIT(port_id), false);
break;
default:
break;
}
rtnl_unlock();
kfree(switchdev_work->fdb_info.addr);
kfree(switchdev_work);
dev_put(emac->ndev);
}
static int prueth_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
struct prueth_switchdev_event_work *switchdev_work;
struct switchdev_notifier_fdb_info *fdb_info = ptr;
struct prueth_emac *emac = netdev_priv(ndev);
int err;
if (!prueth_dev_check(ndev))
return NOTIFY_DONE;
if (event == SWITCHDEV_PORT_ATTR_SET) {
err = switchdev_handle_port_attr_set(ndev, ptr,
prueth_dev_check,
prueth_switchdev_attr_set);
return notifier_from_errno(err);
}
switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC);
if (WARN_ON(!switchdev_work))
return NOTIFY_BAD;
INIT_WORK(&switchdev_work->work, prueth_switchdev_event_work);
switchdev_work->emac = emac;
switchdev_work->event = event;
switch (event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
case SWITCHDEV_FDB_DEL_TO_DEVICE:
memcpy(&switchdev_work->fdb_info, ptr,
sizeof(switchdev_work->fdb_info));
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!switchdev_work->fdb_info.addr)
goto err_addr_alloc;
ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
fdb_info->addr);
dev_hold(ndev);
break;
default:
kfree(switchdev_work);
return NOTIFY_DONE;
}
queue_work(system_long_wq, &switchdev_work->work);
return NOTIFY_DONE;
err_addr_alloc:
kfree(switchdev_work);
return NOTIFY_BAD;
}
static int prueth_switchdev_vlan_add(struct prueth_emac *emac, bool untag, bool pvid,
u8 vid, struct net_device *orig_dev)
{
bool cpu_port = netif_is_bridge_master(orig_dev);
int untag_mask = 0;
int port_mask;
int ret = 0;
if (cpu_port)
port_mask = BIT(PRUETH_PORT_HOST);
else
port_mask = BIT(emac->port_id);
if (untag)
untag_mask = port_mask;
icssg_vtbl_modify(emac, vid, port_mask, untag_mask, true);
netdev_dbg(emac->ndev, "VID add vid:%u port_mask:%X untag_mask %X PVID %d\n",
vid, port_mask, untag_mask, pvid);
if (!pvid)
return ret;
icssg_set_pvid(emac->prueth, vid, emac->port_id);
return ret;
}
static int prueth_switchdev_vlan_del(struct prueth_emac *emac, u16 vid,
struct net_device *orig_dev)
{
bool cpu_port = netif_is_bridge_master(orig_dev);
int port_mask;
int ret = 0;
if (cpu_port)
port_mask = BIT(PRUETH_PORT_HOST);
else
port_mask = BIT(emac->port_id);
icssg_vtbl_modify(emac, vid, port_mask, 0, false);
if (cpu_port)
icssg_fdb_add_del(emac, emac->mac_addr, vid,
BIT(PRUETH_PORT_HOST), false);
if (vid == icssg_get_pvid(emac))
icssg_set_pvid(emac->prueth, 0, emac->port_id);
netdev_dbg(emac->ndev, "VID del vid:%u port_mask:%X\n",
vid, port_mask);
return ret;
}
static int prueth_switchdev_vlans_add(struct prueth_emac *emac,
const struct switchdev_obj_port_vlan *vlan)
{
bool untag = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
struct net_device *orig_dev = vlan->obj.orig_dev;
bool cpu_port = netif_is_bridge_master(orig_dev);
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
netdev_dbg(emac->ndev, "VID add vid:%u flags:%X\n",
vlan->vid, vlan->flags);
if (cpu_port && !(vlan->flags & BRIDGE_VLAN_INFO_BRENTRY))
return 0;
if (vlan->vid > 0xff)
return 0;
return prueth_switchdev_vlan_add(emac, untag, pvid, vlan->vid,
orig_dev);
}
static int prueth_switchdev_vlans_del(struct prueth_emac *emac,
const struct switchdev_obj_port_vlan *vlan)
{
if (vlan->vid > 0xff)
return 0;
return prueth_switchdev_vlan_del(emac, vlan->vid,
vlan->obj.orig_dev);
}
static int prueth_switchdev_mdb_add(struct prueth_emac *emac,
struct switchdev_obj_port_mdb *mdb)
{
struct net_device *orig_dev = mdb->obj.orig_dev;
u8 port_mask, fid_c2;
bool cpu_port;
int err;
cpu_port = netif_is_bridge_master(orig_dev);
if (cpu_port)
port_mask = BIT(PRUETH_PORT_HOST);
else
port_mask = BIT(emac->port_id);
fid_c2 = icssg_fdb_lookup(emac, mdb->addr, mdb->vid);
err = icssg_fdb_add_del(emac, mdb->addr, mdb->vid, fid_c2 | port_mask, true);
netdev_dbg(emac->ndev, "MDB add vid %u:%pM ports: %X\n",
mdb->vid, mdb->addr, port_mask);
return err;
}
static int prueth_switchdev_mdb_del(struct prueth_emac *emac,
struct switchdev_obj_port_mdb *mdb)
{
struct net_device *orig_dev = mdb->obj.orig_dev;
int del_mask, ret, fid_c2;
bool cpu_port;
cpu_port = netif_is_bridge_master(orig_dev);
if (cpu_port)
del_mask = BIT(PRUETH_PORT_HOST);
else
del_mask = BIT(emac->port_id);
fid_c2 = icssg_fdb_lookup(emac, mdb->addr, mdb->vid);
if (fid_c2 & ~del_mask)
ret = icssg_fdb_add_del(emac, mdb->addr, mdb->vid, fid_c2 & ~del_mask, true);
else
ret = icssg_fdb_add_del(emac, mdb->addr, mdb->vid, 0, false);
netdev_dbg(emac->ndev, "MDB del vid %u:%pM ports: %X\n",
mdb->vid, mdb->addr, del_mask);
return ret;
}
static int prueth_switchdev_obj_add(struct net_device *ndev, const void *ctx,
const struct switchdev_obj *obj,
struct netlink_ext_ack *extack)
{
struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
struct prueth_emac *emac = netdev_priv(ndev);
int err = 0;
netdev_dbg(ndev, "obj_add: id %u port: %u\n", obj->id, emac->port_id);
switch (obj->id) {
case SWITCHDEV_OBJ_ID_PORT_VLAN:
err = prueth_switchdev_vlans_add(emac, vlan);
break;
case SWITCHDEV_OBJ_ID_PORT_MDB:
case SWITCHDEV_OBJ_ID_HOST_MDB:
err = prueth_switchdev_mdb_add(emac, mdb);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
static int prueth_switchdev_obj_del(struct net_device *ndev, const void *ctx,
const struct switchdev_obj *obj)
{
struct switchdev_obj_port_vlan *vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
struct prueth_emac *emac = netdev_priv(ndev);
int err = 0;
netdev_dbg(ndev, "obj_del: id %u port: %u\n", obj->id, emac->port_id);
switch (obj->id) {
case SWITCHDEV_OBJ_ID_PORT_VLAN:
err = prueth_switchdev_vlans_del(emac, vlan);
break;
case SWITCHDEV_OBJ_ID_PORT_MDB:
case SWITCHDEV_OBJ_ID_HOST_MDB:
err = prueth_switchdev_mdb_del(emac, mdb);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
static int prueth_switchdev_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
int err;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = switchdev_handle_port_obj_add(dev, ptr,
prueth_dev_check,
prueth_switchdev_obj_add);
return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL:
err = switchdev_handle_port_obj_del(dev, ptr,
prueth_dev_check,
prueth_switchdev_obj_del);
return notifier_from_errno(err);
case SWITCHDEV_PORT_ATTR_SET:
err = switchdev_handle_port_attr_set(dev, ptr,
prueth_dev_check,
prueth_switchdev_attr_set);
return notifier_from_errno(err);
default:
break;
}
return NOTIFY_DONE;
}
int prueth_switchdev_register_notifiers(struct prueth *prueth)
{
int ret = 0;
prueth->prueth_switchdev_nb.notifier_call = &prueth_switchdev_event;
ret = register_switchdev_notifier(&prueth->prueth_switchdev_nb);
if (ret) {
dev_err(prueth->dev, "register switchdev notifier fail ret:%d\n",
ret);
return ret;
}
prueth->prueth_switchdev_bl_nb.notifier_call = &prueth_switchdev_blocking_event;
ret = register_switchdev_blocking_notifier(&prueth->prueth_switchdev_bl_nb);
if (ret) {
dev_err(prueth->dev, "register switchdev blocking notifier ret:%d\n",
ret);
unregister_switchdev_notifier(&prueth->prueth_switchdev_nb);
}
return ret;
}
void prueth_switchdev_unregister_notifiers(struct prueth *prueth)
{
unregister_switchdev_blocking_notifier(&prueth->prueth_switchdev_bl_nb);
unregister_switchdev_notifier(&prueth->prueth_switchdev_nb);
}
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2021 Texas Instruments Incorporated - https://www.ti.com/
*/
#ifndef __NET_TI_ICSSG_SWITCHDEV_H
#define __NET_TI_ICSSG_SWITCHDEV_H
#include "icssg_prueth.h"
int prueth_switchdev_register_notifiers(struct prueth *prueth);
void prueth_switchdev_unregister_notifiers(struct prueth *prueth);
bool prueth_dev_check(const struct net_device *ndev);
#endif /* __NET_TI_ICSSG_SWITCHDEV_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,280 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Texas Instruments ICSSM Ethernet driver
*
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#ifndef __NET_TI_PRUETH_H
#define __NET_TI_PRUETH_H
#include <linux/phy.h>
#include <linux/types.h>
#include <linux/pruss_driver.h>
#include <linux/remoteproc/pruss.h>
#include "icssm_switch.h"
#include "icssm_prueth_ptp.h"
#include "icssm_prueth_fdb_tbl.h"
/* ICSSM size of redundancy tag */
#define ICSSM_LRE_TAG_SIZE 6
/* PRUSS local memory map */
#define ICSS_LOCAL_SHARED_RAM 0x00010000
#define EMAC_MAX_PKTLEN (ETH_HLEN + VLAN_HLEN + ETH_DATA_LEN)
/* Below macro is for 1528 Byte Frame support, to Allow even with
* Redundancy tag
*/
#define EMAC_MAX_FRM_SUPPORT (ETH_HLEN + VLAN_HLEN + ETH_DATA_LEN + \
ICSSM_LRE_TAG_SIZE)
/* PRU Ethernet Type - Ethernet functionality (protocol
* implemented) provided by the PRU firmware being loaded.
*/
enum pruss_ethtype {
PRUSS_ETHTYPE_EMAC = 0,
PRUSS_ETHTYPE_HSR,
PRUSS_ETHTYPE_PRP,
PRUSS_ETHTYPE_SWITCH,
PRUSS_ETHTYPE_MAX,
};
#define PRUETH_IS_EMAC(p) ((p)->eth_type == PRUSS_ETHTYPE_EMAC)
#define PRUETH_IS_SWITCH(p) ((p)->eth_type == PRUSS_ETHTYPE_SWITCH)
/**
* struct prueth_queue_desc - Queue descriptor
* @rd_ptr: Read pointer, points to a buffer descriptor in Shared PRU RAM.
* @wr_ptr: Write pointer, points to a buffer descriptor in Shared PRU RAM.
* @busy_s: Slave queue busy flag, set by slave(us) to request access from
* master(PRU).
* @status: Bit field status register, Bits:
* 0: Master queue busy flag.
* 1: Packet has been placed in collision queue.
* 2: Packet has been discarded due to overflow.
* @max_fill_level: Maximum queue usage seen.
* @overflow_cnt: Count of queue overflows.
*
* Each port has up to 4 queues with variable length. The queue is processed
* as ring buffer with read and write pointers. Both pointers are address
* pointers and increment by 4 for each buffer descriptor position. Queue has
* a length defined in constants and a status.
*/
struct prueth_queue_desc {
u16 rd_ptr;
u16 wr_ptr;
u8 busy_s;
u8 status;
u8 max_fill_level;
u8 overflow_cnt;
};
/**
* struct prueth_queue_info - Information about a queue in memory
* @buffer_offset: buffer offset in OCMC RAM
* @queue_desc_offset: queue descriptor offset in Shared RAM
* @buffer_desc_offset: buffer descriptors offset in Shared RAM
* @buffer_desc_end: end address of buffer descriptors in Shared RAM
*/
struct prueth_queue_info {
u16 buffer_offset;
u16 queue_desc_offset;
u16 buffer_desc_offset;
u16 buffer_desc_end;
};
/**
* struct prueth_packet_info - Info about a packet in buffer
* @shadow: this packet is stored in the collision queue
* @port: port packet is on
* @length: length of packet
* @broadcast: this packet is a broadcast packet
* @error: this packet has an error
* @lookup_success: src mac found in FDB
* @flood: packet is to be flooded
* @timestamp: Specifies if timestamp is appended to the packet
*/
struct prueth_packet_info {
bool shadow;
unsigned int port;
unsigned int length;
bool broadcast;
bool error;
bool lookup_success;
bool flood;
bool timestamp;
};
/* In switch mode there are 3 real ports i.e. 3 mac addrs.
* however Linux sees only the host side port. The other 2 ports
* are the switch ports.
* In emac mode there are 2 real ports i.e. 2 mac addrs.
* Linux sees both the ports.
*/
enum prueth_port {
PRUETH_PORT_HOST = 0, /* host side port */
PRUETH_PORT_MII0, /* physical port MII 0 */
PRUETH_PORT_MII1, /* physical port MII 1 */
PRUETH_PORT_INVALID, /* Invalid prueth port */
};
enum prueth_mac {
PRUETH_MAC0 = 0,
PRUETH_MAC1,
PRUETH_NUM_MACS,
PRUETH_MAC_INVALID,
};
/* In both switch & emac modes there are 3 port queues
* EMAC mode:
* RX packets for both MII0 & MII1 ports come on
* QUEUE_HOST.
* TX packets for MII0 go on QUEUE_MII0, TX packets
* for MII1 go on QUEUE_MII1.
* Switch mode:
* Host port RX packets come on QUEUE_HOST
* TX packets might have to go on MII0 or MII1 or both.
* MII0 TX queue is QUEUE_MII0 and MII1 TX queue is
* QUEUE_MII1.
*/
enum prueth_port_queue_id {
PRUETH_PORT_QUEUE_HOST = 0,
PRUETH_PORT_QUEUE_MII0,
PRUETH_PORT_QUEUE_MII1,
PRUETH_PORT_QUEUE_MAX,
};
/* Each port queue has 4 queues and 1 collision queue */
enum prueth_queue_id {
PRUETH_QUEUE1 = 0,
PRUETH_QUEUE2,
PRUETH_QUEUE3,
PRUETH_QUEUE4,
PRUETH_COLQUEUE, /* collision queue */
};
/**
* struct prueth_firmware - PRU Ethernet FW data
* @fw_name: firmware names of firmware to run on PRU
*/
struct prueth_firmware {
const char *fw_name[PRUSS_ETHTYPE_MAX];
};
/* PRUeth memory range identifiers */
enum prueth_mem {
PRUETH_MEM_DRAM0 = 0,
PRUETH_MEM_DRAM1,
PRUETH_MEM_SHARED_RAM,
PRUETH_MEM_OCMC,
PRUETH_MEM_MAX,
};
enum pruss_device {
PRUSS_AM57XX = 0,
PRUSS_AM43XX,
PRUSS_AM33XX,
PRUSS_K2G
};
/**
* struct prueth_private_data - PRU Ethernet private data
* @driver_data: PRU Ethernet device name
* @fw_pru: firmware names to be used for PRUSS ethernet usecases
* @support_switch: boolean to indicate if switch is enabled
*/
struct prueth_private_data {
enum pruss_device driver_data;
const struct prueth_firmware fw_pru[PRUSS_NUM_PRUS];
bool support_switch;
};
struct prueth_emac_stats {
u64 tx_packets;
u64 tx_dropped;
u64 tx_bytes;
u64 rx_packets;
u64 rx_bytes;
u64 rx_length_errors;
u64 rx_over_errors;
};
/* data for each emac port */
struct prueth_emac {
struct prueth *prueth;
struct net_device *ndev;
struct napi_struct napi;
struct rproc *pru;
struct phy_device *phydev;
struct prueth_queue_desc __iomem *rx_queue_descs;
struct prueth_queue_desc __iomem *tx_queue_descs;
int link;
int speed;
int duplex;
int rx_irq;
enum prueth_port_queue_id tx_port_queue;
enum prueth_queue_id rx_queue_start;
enum prueth_queue_id rx_queue_end;
enum prueth_port port_id;
enum prueth_mem dram;
const char *phy_id;
u32 msg_enable;
u8 mac_addr[6];
unsigned char mc_filter_mask[ETH_ALEN]; /* for multicast filtering */
phy_interface_t phy_if;
/* spin lock used to protect
* during link configuration
*/
spinlock_t lock;
spinlock_t addr_lock; /* serialize access to VLAN/MC filter table */
struct hrtimer tx_hrtimer;
struct prueth_emac_stats stats;
int offload_fwd_mark;
};
struct prueth {
struct device *dev;
struct pruss *pruss;
struct rproc *pru0, *pru1;
struct pruss_mem_region mem[PRUETH_MEM_MAX];
struct gen_pool *sram_pool;
struct regmap *mii_rt;
struct icss_iep *iep;
const struct prueth_private_data *fw_data;
struct prueth_fw_offsets *fw_offsets;
struct device_node *eth_node[PRUETH_NUM_MACS];
struct prueth_emac *emac[PRUETH_NUM_MACS];
struct net_device *registered_netdevs[PRUETH_NUM_MACS];
struct net_device *hw_bridge_dev;
struct fdb_tbl *fdb_tbl;
struct notifier_block prueth_netdevice_nb;
struct notifier_block prueth_switchdev_nb;
struct notifier_block prueth_switchdev_bl_nb;
unsigned int eth_type;
size_t ocmc_ram_size;
u8 emac_configured;
u8 br_members;
};
extern const struct prueth_queue_desc queue_descs[][NUM_QUEUES];
void icssm_parse_packet_info(struct prueth *prueth, u32 buffer_descriptor,
struct prueth_packet_info *pkt_info);
int icssm_emac_rx_packet(struct prueth_emac *emac, u16 *bd_rd_ptr,
struct prueth_packet_info *pkt_info,
const struct prueth_queue_info *rxqueue);
void icssm_emac_mc_filter_bin_allow(struct prueth_emac *emac, u8 hash);
void icssm_emac_mc_filter_bin_disallow(struct prueth_emac *emac, u8 hash);
u8 icssm_emac_get_mc_hash(u8 *mac, u8 *mask);
#endif /* __NET_TI_PRUETH_H */
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2019-2021 Texas Instruments Incorporated - https://www.ti.com */
#ifndef __NET_TI_PRUSS_FDB_TBL_H
#define __NET_TI_PRUSS_FDB_TBL_H
#include <linux/kernel.h>
#include <linux/debugfs.h>
#include "icssm_prueth.h"
/* 4 bytes */
struct fdb_index_tbl_entry {
/* Bucket Table index of first Bucket with this MAC address */
u16 bucket_idx;
u16 bucket_entries; /* Number of entries in this bucket */
};
/* 4 * 256 = 1024 = 0x200 bytes */
struct fdb_index_array {
struct fdb_index_tbl_entry index_tbl_entry[FDB_INDEX_TBL_MAX_ENTRIES];
};
/* 10 bytes */
struct fdb_mac_tbl_entry {
u8 mac[ETH_ALEN];
u16 age;
u8 port; /* 0 based: 0=port1, 1=port2 */
union {
struct {
u8 is_static:1;
u8 active:1;
};
u8 flags;
};
};
/* 10 * 256 = 2560 = 0xa00 bytes */
struct fdb_mac_tbl_array {
struct fdb_mac_tbl_entry mac_tbl_entry[FDB_MAC_TBL_MAX_ENTRIES];
};
/* 1 byte */
struct fdb_stp_config {
u8 state; /* per-port STP state (defined in FW header) */
};
/* 1 byte */
struct fdb_flood_config {
u8 host_flood_enable:1;
u8 port1_flood_enable:1;
u8 port2_flood_enable:1;
};
/* 2 byte */
struct fdb_arbitration {
u8 host_lock;
u8 pru_locks;
};
struct fdb_tbl {
/* fdb index table */
struct fdb_index_array __iomem *index_a;
/* fdb MAC table */
struct fdb_mac_tbl_array __iomem *mac_tbl_a;
/* port 1 stp config */
struct fdb_stp_config __iomem *port1_stp_cfg;
/* port 2 stp config */
struct fdb_stp_config __iomem *port2_stp_cfg;
/* per-port flood enable */
struct fdb_flood_config __iomem *flood_enable_flags;
/* fdb locking mechanism */
struct fdb_arbitration __iomem *locks;
/* total number of entries in hash table */
u16 total_entries;
};
#endif
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com
*/
#ifndef PRUETH_PTP_H
#define PRUETH_PTP_H
#define RX_SYNC_TIMESTAMP_OFFSET_P1 0x8 /* 8 bytes */
#define RX_PDELAY_REQ_TIMESTAMP_OFFSET_P1 0x14 /* 12 bytes */
#define DISABLE_PTP_FRAME_FORWARDING_CTRL_OFFSET 0x14 /* 1 byte */
#define RX_PDELAY_RESP_TIMESTAMP_OFFSET_P1 0x20 /* 12 bytes */
#define RX_SYNC_TIMESTAMP_OFFSET_P2 0x2c /* 12 bytes */
#define RX_PDELAY_REQ_TIMESTAMP_OFFSET_P2 0x38 /* 12 bytes */
#define RX_PDELAY_RESP_TIMESTAMP_OFFSET_P2 0x44 /* 12 bytes */
#define TIMESYNC_DOMAIN_NUMBER_LIST 0x50 /* 2 bytes */
#define P1_SMA_LINE_DELAY_OFFSET 0x52 /* 4 bytes */
#define P2_SMA_LINE_DELAY_OFFSET 0x56 /* 4 bytes */
#define TIMESYNC_SECONDS_COUNT_OFFSET 0x5a /* 6 bytes */
#define TIMESYNC_TC_RCF_OFFSET 0x60 /* 4 bytes */
#define DUT_IS_MASTER_OFFSET 0x64 /* 1 byte */
#define MASTER_PORT_NUM_OFFSET 0x65 /* 1 byte */
#define SYNC_MASTER_MAC_OFFSET 0x66 /* 6 bytes */
#define TX_TS_NOTIFICATION_OFFSET_SYNC_P1 0x6c /* 1 byte */
#define TX_TS_NOTIFICATION_OFFSET_PDEL_REQ_P1 0x6d /* 1 byte */
#define TX_TS_NOTIFICATION_OFFSET_PDEL_RES_P1 0x6e /* 1 byte */
#define TX_TS_NOTIFICATION_OFFSET_SYNC_P2 0x6f /* 1 byte */
#define TX_TS_NOTIFICATION_OFFSET_PDEL_REQ_P2 0x70 /* 1 byte */
#define TX_TS_NOTIFICATION_OFFSET_PDEL_RES_P2 0x71 /* 1 byte */
#define TX_SYNC_TIMESTAMP_OFFSET_P1 0x72 /* 12 bytes */
#define TX_PDELAY_REQ_TIMESTAMP_OFFSET_P1 0x7e /* 12 bytes */
#define TX_PDELAY_RESP_TIMESTAMP_OFFSET_P1 0x8a /* 12 bytes */
#define TX_SYNC_TIMESTAMP_OFFSET_P2 0x96 /* 12 bytes */
#define TX_PDELAY_REQ_TIMESTAMP_OFFSET_P2 0xa2 /* 12 bytes */
#define TX_PDELAY_RESP_TIMESTAMP_OFFSET_P2 0xae /* 12 bytes */
#define TIMESYNC_CTRL_VAR_OFFSET 0xba /* 1 byte */
#define DISABLE_SWITCH_SYNC_RELAY_OFFSET 0xbb /* 1 byte */
#define MII_RX_CORRECTION_OFFSET 0xbc /* 2 bytes */
#define MII_TX_CORRECTION_OFFSET 0xbe /* 2 bytes */
#define TIMESYNC_CMP1_CMP_OFFSET 0xc0 /* 8 bytes */
#define TIMESYNC_SYNC0_CMP_OFFSET 0xc8 /* 8 bytes */
#define TIMESYNC_CMP1_PERIOD_OFFSET 0xd0 /* 4 bytes */
#define TIMESYNC_SYNC0_WIDTH_OFFSET 0xd4 /* 4 bytes */
#define SINGLE_STEP_IEP_OFFSET_P1 0xd8 /* 8 bytes */
#define SINGLE_STEP_SECONDS_OFFSET_P1 0xe0 /* 8 bytes */
#define SINGLE_STEP_IEP_OFFSET_P2 0xe8 /* 8 bytes */
#define SINGLE_STEP_SECONDS_OFFSET_P2 0xf0 /* 8 bytes */
#define LINK_LOCAL_FRAME_HAS_HSR_TAG 0xf8 /* 1 bytes */
#define PTP_PREV_TX_TIMESTAMP_P1 0xf9 /* 8 bytes */
#define PTP_PREV_TX_TIMESTAMP_P2 0x101 /* 8 bytes */
#define PTP_CLK_IDENTITY_OFFSET 0x109 /* 8 bytes */
#define PTP_SCRATCH_MEM 0x111 /* 16 byte */
#define PTP_IPV4_UDP_E2E_ENABLE 0x121 /* 1 byte */
enum {
PRUETH_PTP_SYNC,
PRUETH_PTP_DLY_REQ,
PRUETH_PTP_DLY_RESP,
PRUETH_PTP_TS_EVENTS,
};
#define PRUETH_PTP_TS_SIZE 12
#define PRUETH_PTP_TS_NOTIFY_SIZE 1
#define PRUETH_PTP_TS_NOTIFY_MASK 0xff
/* Bit definitions for TIMESYNC_CTRL */
#define TIMESYNC_CTRL_BG_ENABLE BIT(0)
#define TIMESYNC_CTRL_FORCED_2STEP BIT(1)
static inline u32 icssm_prueth_tx_ts_offs_get(u8 port, u8 event)
{
return TX_SYNC_TIMESTAMP_OFFSET_P1 + port *
PRUETH_PTP_TS_EVENTS * PRUETH_PTP_TS_SIZE +
event * PRUETH_PTP_TS_SIZE;
}
static inline u32 icssm_prueth_tx_ts_notify_offs_get(u8 port, u8 event)
{
return TX_TS_NOTIFICATION_OFFSET_SYNC_P1 +
PRUETH_PTP_TS_EVENTS * PRUETH_PTP_TS_NOTIFY_SIZE * port +
event * PRUETH_PTP_TS_NOTIFY_SIZE;
}
#endif /* PRUETH_PTP_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com
*/
#ifndef __NET_TI_PRUETH_SWITCH_H
#define __NET_TI_PRUETH_SWITCH_H
#include <net/switchdev.h>
#include "icssm_prueth.h"
#include "icssm_prueth_fdb_tbl.h"
#include "icssm_switchdev.h"
void icssm_prueth_sw_set_stp_state(struct prueth *prueth,
enum prueth_port port, u8 state);
u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth,
enum prueth_port port);
extern const struct prueth_queue_info sw_queue_infos[][4];
void icssm_prueth_sw_fdb_tbl_init(struct prueth *prueth);
int icssm_prueth_sw_init_fdb_table(struct prueth *prueth);
void icssm_prueth_sw_free_fdb_table(struct prueth *prueth);
int icssm_prueth_sw_do_purge_fdb(struct prueth_emac *emac);
void icssm_prueth_sw_fdb_add(struct prueth_emac *emac,
struct switchdev_notifier_fdb_info *fdb);
void icssm_prueth_sw_fdb_del(struct prueth_emac *emac,
struct switchdev_notifier_fdb_info *fdb);
int icssm_prueth_sw_learn_fdb(struct prueth_emac *emac, u8 *src_mac);
int icssm_prueth_sw_purge_fdb(struct prueth_emac *emac);
void icssm_prueth_sw_hostconfig(struct prueth *prueth);
int icssm_prueth_sw_emac_config(struct prueth_emac *emac);
int icssm_prueth_sw_boot_prus(struct prueth *prueth, struct net_device *ndev);
int icssm_prueth_sw_shutdown_prus(struct prueth_emac *emac,
struct net_device *ndev);
#endif /* __NET_TI_PRUETH_SWITCH_H */
@@ -0,0 +1,360 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2015-2021 Texas Instruments Incorporated - https://www.ti.com
*/
#ifndef __ICSS_SWITCH_H
#define __ICSS_SWITCH_H
/* Basic Switch Parameters
* Used to auto compute offset addresses on L3 OCMC RAM. Do not modify these
* without changing firmware accordingly
*/
#define SWITCH_BUFFER_SIZE (64 * 1024) /* L3 buffer */
#define ICSS_BLOCK_SIZE 32 /* data bytes per BD */
#define BD_SIZE 4 /* byte buffer descriptor */
#define NUM_QUEUES 4 /* Queues on Port 0/1/2 */
#define PORT_LINK_MASK 0x1
#define PORT_IS_HD_MASK 0x2
/* Physical Port queue size (number of BDs). Same for both ports */
#define QUEUE_1_SIZE 97 /* Network Management high */
#define QUEUE_2_SIZE 97 /* Network Management low */
#define QUEUE_3_SIZE 97 /* Protocol specific */
#define QUEUE_4_SIZE 97 /* NRT (IP,ARP, ICMP) */
/* Host queue size (number of BDs). Each BD points to data buffer of 32 bytes.
* HOST PORT QUEUES can buffer up to 4 full sized frames per queue
*/
#define HOST_QUEUE_1_SIZE 194 /* Protocol and VLAN priority 7 & 6 */
#define HOST_QUEUE_2_SIZE 194 /* Protocol mid */
#define HOST_QUEUE_3_SIZE 194 /* Protocol low */
#define HOST_QUEUE_4_SIZE 194 /* NRT (IP, ARP, ICMP) */
#define COL_QUEUE_SIZE 0
/* NRT Buffer descriptor definition
* Each buffer descriptor points to a max 32 byte block and has 32 bit in size
* to have atomic operation.
* PRU can address bytewise into memory.
* Definition of 32 bit descriptor is as follows
*
* Bits Name Meaning
* =============================================================================
* 0..7 Index points to index in buffer queue, max 256 x 32
* byte blocks can be addressed
* 6 LookupSuccess For switch, FDB lookup was successful (source
* MAC address found in FDB).
* For RED, NodeTable lookup was successful.
* 7 Flood Packet should be flooded (destination MAC
* address found in FDB). For switch only.
* 8..12 Block_length number of valid bytes in this specific block.
* Will be <=32 bytes on last block of packet
* 13 More "More" bit indicating that there are more blocks
* 14 Shadow indicates that "index" is pointing into shadow
* buffer
* 15 TimeStamp indicates that this packet has time stamp in
* separate buffer - only needed if PTP runs on
* host
* 16..17 Port different meaning for ingress and egress,
* Ingress: Port = 0 indicates phy port 1 and
* Port = 1 indicates phy port 2.
* Egress: 0 sends on phy port 1 and 1 sends on
* phy port 2. Port = 2 goes over MAC table
* look-up
* 18..28 Length 11 bit of total packet length which is put into
* first BD only so that host access only one BD
* 29 VlanTag indicates that packet has Length/Type field of
* 0x08100 with VLAN tag in following byte
* 30 Broadcast indicates that packet goes out on both physical
* ports, there will be two bd but only one buffer
* 31 Error indicates there was an error in the packet
*/
#define PRUETH_BD_START_FLAG_MASK BIT(0)
#define PRUETH_BD_START_FLAG_SHIFT 0
#define PRUETH_BD_HSR_FRAME_MASK BIT(4)
#define PRUETH_BD_HSR_FRAME_SHIFT 4
#define PRUETH_BD_SUP_HSR_FRAME_MASK BIT(5)
#define PRUETH_BD_SUP_HSR_FRAME_SHIFT 5
#define PRUETH_BD_LOOKUP_SUCCESS_MASK BIT(6)
#define PRUETH_BD_LOOKUP_SUCCESS_SHIFT 6
#define PRUETH_BD_SW_FLOOD_MASK BIT(7)
#define PRUETH_BD_SW_FLOOD_SHIFT 7
#define PRUETH_BD_SHADOW_MASK BIT(14)
#define PRUETH_BD_SHADOW_SHIFT 14
#define PRUETH_BD_TIMESTAMP_MASK BIT(15)
#define PRUETH_BD_TIMESTAMP_SHIFT 15
#define PRUETH_BD_PORT_MASK GENMASK(17, 16)
#define PRUETH_BD_PORT_SHIFT 16
#define PRUETH_BD_LENGTH_MASK GENMASK(28, 18)
#define PRUETH_BD_LENGTH_SHIFT 18
#define PRUETH_BD_BROADCAST_MASK BIT(30)
#define PRUETH_BD_BROADCAST_SHIFT 30
#define PRUETH_BD_ERROR_MASK BIT(31)
#define PRUETH_BD_ERROR_SHIFT 31
/* The following offsets indicate which sections of the memory are used
* for EMAC internal tasks
*/
#define DRAM_START_OFFSET 0x1E98
#define SRAM_START_OFFSET 0x400
/* General Purpose Statistics
* These are present on both PRU0 and PRU1 DRAM
*/
/* base statistics offset */
#define STATISTICS_OFFSET 0x1F00
#define STAT_SIZE 0x98
/* The following offsets indicate which sections of the memory are used
* for switch internal tasks
*/
#define SWITCH_SPECIFIC_DRAM0_START_SIZE 0x100
#define SWITCH_SPECIFIC_DRAM0_START_OFFSET 0x1F00
#define SWITCH_SPECIFIC_DRAM1_START_SIZE 0x300
#define SWITCH_SPECIFIC_DRAM1_START_OFFSET 0x1D00
/* Offset for storing
* 1. Storm Prevention Params
* 2. PHY Speed Offset
* 3. Port Status Offset
* These are present on both PRU0 and PRU1
*/
/* 4 bytes */
#define STORM_PREVENTION_OFFSET_BC (STATISTICS_OFFSET + STAT_SIZE)
/* 4 bytes */
#define PHY_SPEED_OFFSET (STATISTICS_OFFSET + STAT_SIZE + 4)
/* 1 byte */
#define PORT_STATUS_OFFSET (STATISTICS_OFFSET + STAT_SIZE + 8)
/* 1 byte */
#define COLLISION_COUNTER (STATISTICS_OFFSET + STAT_SIZE + 9)
/* 4 bytes */
#define RX_PKT_SIZE_OFFSET (STATISTICS_OFFSET + STAT_SIZE + 10)
/* 4 bytes */
#define PORT_CONTROL_ADDR (STATISTICS_OFFSET + STAT_SIZE + 14)
/* 6 bytes */
#define PORT_MAC_ADDR (STATISTICS_OFFSET + STAT_SIZE + 18)
/* 1 byte */
#define RX_INT_STATUS_OFFSET (STATISTICS_OFFSET + STAT_SIZE + 24)
/* 4 bytes */
#define STORM_PREVENTION_OFFSET_MC (STATISTICS_OFFSET + STAT_SIZE + 25)
/* 4 bytes */
#define STORM_PREVENTION_OFFSET_UC (STATISTICS_OFFSET + STAT_SIZE + 29)
/* 4 bytes ? */
#define STP_INVALID_STATE_OFFSET (STATISTICS_OFFSET + STAT_SIZE + 33)
/* DRAM1 Offsets for Switch */
/* 4 queue descriptors for port 0 (host receive) */
#define P0_QUEUE_DESC_OFFSET 0x1E7C
#define P1_QUEUE_DESC_OFFSET 0x1E9C
#define P2_QUEUE_DESC_OFFSET 0x1EBC
/* collision descriptor of port 0 */
#define P0_COL_QUEUE_DESC_OFFSET 0x1E64
#define P1_COL_QUEUE_DESC_OFFSET 0x1E6C
#define P2_COL_QUEUE_DESC_OFFSET 0x1E74
/* Collision Status Register
* P0: bit 0 is pending flag, bit 1..2 indicates which queue,
* P1: bit 8 is pending flag, 9..10 is queue number
* P2: bit 16 is pending flag, 17..18 is queue number, remaining bits are 0.
*/
#define COLLISION_STATUS_ADDR 0x1E60
#define INTERFACE_MAC_ADDR 0x1E58
#define P2_MAC_ADDR 0x1E50
#define P1_MAC_ADDR 0x1E48
#define QUEUE_SIZE_ADDR 0x1E30
#define QUEUE_OFFSET_ADDR 0x1E18
#define QUEUE_DESCRIPTOR_OFFSET_ADDR 0x1E00
#define COL_RX_CONTEXT_P2_OFFSET_ADDR (COL_RX_CONTEXT_P1_OFFSET_ADDR + 12)
#define COL_RX_CONTEXT_P1_OFFSET_ADDR (COL_RX_CONTEXT_P0_OFFSET_ADDR + 12)
#define COL_RX_CONTEXT_P0_OFFSET_ADDR (P2_Q4_RX_CONTEXT_OFFSET + 8)
/* Port 2 Rx Context */
#define P2_Q4_RX_CONTEXT_OFFSET (P2_Q3_RX_CONTEXT_OFFSET + 8)
#define P2_Q3_RX_CONTEXT_OFFSET (P2_Q2_RX_CONTEXT_OFFSET + 8)
#define P2_Q2_RX_CONTEXT_OFFSET (P2_Q1_RX_CONTEXT_OFFSET + 8)
#define P2_Q1_RX_CONTEXT_OFFSET RX_CONTEXT_P2_Q1_OFFSET_ADDR
#define RX_CONTEXT_P2_Q1_OFFSET_ADDR (P1_Q4_RX_CONTEXT_OFFSET + 8)
/* Port 1 Rx Context */
#define P1_Q4_RX_CONTEXT_OFFSET (P1_Q3_RX_CONTEXT_OFFSET + 8)
#define P1_Q3_RX_CONTEXT_OFFSET (P1_Q2_RX_CONTEXT_OFFSET + 8)
#define P1_Q2_RX_CONTEXT_OFFSET (P1_Q1_RX_CONTEXT_OFFSET + 8)
#define P1_Q1_RX_CONTEXT_OFFSET (RX_CONTEXT_P1_Q1_OFFSET_ADDR)
#define RX_CONTEXT_P1_Q1_OFFSET_ADDR (P0_Q4_RX_CONTEXT_OFFSET + 8)
/* Host Port Rx Context */
#define P0_Q4_RX_CONTEXT_OFFSET (P0_Q3_RX_CONTEXT_OFFSET + 8)
#define P0_Q3_RX_CONTEXT_OFFSET (P0_Q2_RX_CONTEXT_OFFSET + 8)
#define P0_Q2_RX_CONTEXT_OFFSET (P0_Q1_RX_CONTEXT_OFFSET + 8)
#define P0_Q1_RX_CONTEXT_OFFSET RX_CONTEXT_P0_Q1_OFFSET_ADDR
#define RX_CONTEXT_P0_Q1_OFFSET_ADDR (COL_TX_CONTEXT_P2_Q1_OFFSET_ADDR + 8)
/* Port 2 Tx Collision Context */
#define COL_TX_CONTEXT_P2_Q1_OFFSET_ADDR (COL_TX_CONTEXT_P1_Q1_OFFSET_ADDR + 8)
/* Port 1 Tx Collision Context */
#define COL_TX_CONTEXT_P1_Q1_OFFSET_ADDR (P2_Q4_TX_CONTEXT_OFFSET + 8)
/* Port 2 */
#define P2_Q4_TX_CONTEXT_OFFSET (P2_Q3_TX_CONTEXT_OFFSET + 8)
#define P2_Q3_TX_CONTEXT_OFFSET (P2_Q2_TX_CONTEXT_OFFSET + 8)
#define P2_Q2_TX_CONTEXT_OFFSET (P2_Q1_TX_CONTEXT_OFFSET + 8)
#define P2_Q1_TX_CONTEXT_OFFSET TX_CONTEXT_P2_Q1_OFFSET_ADDR
#define TX_CONTEXT_P2_Q1_OFFSET_ADDR (P1_Q4_TX_CONTEXT_OFFSET + 8)
/* Port 1 */
#define P1_Q4_TX_CONTEXT_OFFSET (P1_Q3_TX_CONTEXT_OFFSET + 8)
#define P1_Q3_TX_CONTEXT_OFFSET (P1_Q2_TX_CONTEXT_OFFSET + 8)
#define P1_Q2_TX_CONTEXT_OFFSET (P1_Q1_TX_CONTEXT_OFFSET + 8)
#define P1_Q1_TX_CONTEXT_OFFSET TX_CONTEXT_P1_Q1_OFFSET_ADDR
#define TX_CONTEXT_P1_Q1_OFFSET_ADDR SWITCH_SPECIFIC_DRAM1_START_OFFSET
/* DRAM Offsets for EMAC
* Present on Both DRAM0 and DRAM1
*/
/* 4 queue descriptors for port tx = 32 bytes */
#define TX_CONTEXT_Q1_OFFSET_ADDR (PORT_QUEUE_DESC_OFFSET + 32)
#define PORT_QUEUE_DESC_OFFSET (ICSS_EMAC_TTS_CYC_TX_SOF + 8)
/* EMAC Time Triggered Send Offsets */
#define ICSS_EMAC_TTS_CYC_TX_SOF (ICSS_EMAC_TTS_PREV_TX_SOF + 8)
#define ICSS_EMAC_TTS_PREV_TX_SOF \
(ICSS_EMAC_TTS_MISSED_CYCLE_CNT_OFFSET + 4)
#define ICSS_EMAC_TTS_MISSED_CYCLE_CNT_OFFSET (ICSS_EMAC_TTS_STATUS_OFFSET \
+ 4)
#define ICSS_EMAC_TTS_STATUS_OFFSET (ICSS_EMAC_TTS_CFG_TIME_OFFSET + 4)
#define ICSS_EMAC_TTS_CFG_TIME_OFFSET (ICSS_EMAC_TTS_CYCLE_PERIOD_OFFSET + 4)
#define ICSS_EMAC_TTS_CYCLE_PERIOD_OFFSET \
(ICSS_EMAC_TTS_CYCLE_START_OFFSET + 8)
#define ICSS_EMAC_TTS_CYCLE_START_OFFSET ICSS_EMAC_TTS_BASE_OFFSET
#define ICSS_EMAC_TTS_BASE_OFFSET DRAM_START_OFFSET
/* Shared RAM offsets for EMAC */
/* Queue Descriptors */
/* 4 queue descriptors for port 0 (host receive). 32 bytes */
#define HOST_QUEUE_DESC_OFFSET (HOST_QUEUE_SIZE_ADDR + 16)
/* table offset for queue size:
* 3 ports * 4 Queues * 1 byte offset = 12 bytes
*/
#define HOST_QUEUE_SIZE_ADDR (HOST_QUEUE_OFFSET_ADDR + 8)
/* table offset for queue:
* 4 Queues * 2 byte offset = 8 bytes
*/
#define HOST_QUEUE_OFFSET_ADDR (HOST_QUEUE_DESCRIPTOR_OFFSET_ADDR + 8)
/* table offset for Host queue descriptors:
* 1 ports * 4 Queues * 2 byte offset = 8 bytes
*/
#define HOST_QUEUE_DESCRIPTOR_OFFSET_ADDR (HOST_Q4_RX_CONTEXT_OFFSET + 8)
/* Host Port Rx Context */
#define HOST_Q4_RX_CONTEXT_OFFSET (HOST_Q3_RX_CONTEXT_OFFSET + 8)
#define HOST_Q3_RX_CONTEXT_OFFSET (HOST_Q2_RX_CONTEXT_OFFSET + 8)
#define HOST_Q2_RX_CONTEXT_OFFSET (HOST_Q1_RX_CONTEXT_OFFSET + 8)
#define HOST_Q1_RX_CONTEXT_OFFSET (EMAC_PROMISCUOUS_MODE_OFFSET + 4)
/* Promiscuous mode control */
#define EMAC_P1_PROMISCUOUS_BIT BIT(0)
#define EMAC_P2_PROMISCUOUS_BIT BIT(1)
#define EMAC_PROMISCUOUS_MODE_OFFSET (EMAC_RESERVED + 4)
#define EMAC_RESERVED EOF_48K_BUFFER_BD
/* allow for max 48k buffer which spans the descriptors up to 0x1800 6kB */
#define EOF_48K_BUFFER_BD (P0_BUFFER_DESC_OFFSET + HOST_BD_SIZE + \
PORT_BD_SIZE)
#define HOST_BD_SIZE ((HOST_QUEUE_1_SIZE + \
HOST_QUEUE_2_SIZE + HOST_QUEUE_3_SIZE + \
HOST_QUEUE_4_SIZE) * BD_SIZE)
#define PORT_BD_SIZE ((QUEUE_1_SIZE + QUEUE_2_SIZE + \
QUEUE_3_SIZE + QUEUE_4_SIZE) * 2 * BD_SIZE)
#define END_OF_BD_POOL (P2_Q4_BD_OFFSET + QUEUE_4_SIZE * BD_SIZE)
#define P2_Q4_BD_OFFSET (P2_Q3_BD_OFFSET + QUEUE_3_SIZE * BD_SIZE)
#define P2_Q3_BD_OFFSET (P2_Q2_BD_OFFSET + QUEUE_2_SIZE * BD_SIZE)
#define P2_Q2_BD_OFFSET (P2_Q1_BD_OFFSET + QUEUE_1_SIZE * BD_SIZE)
#define P2_Q1_BD_OFFSET (P1_Q4_BD_OFFSET + QUEUE_4_SIZE * BD_SIZE)
#define P1_Q4_BD_OFFSET (P1_Q3_BD_OFFSET + QUEUE_3_SIZE * BD_SIZE)
#define P1_Q3_BD_OFFSET (P1_Q2_BD_OFFSET + QUEUE_2_SIZE * BD_SIZE)
#define P1_Q2_BD_OFFSET (P1_Q1_BD_OFFSET + QUEUE_1_SIZE * BD_SIZE)
#define P1_Q1_BD_OFFSET (P0_Q4_BD_OFFSET + HOST_QUEUE_4_SIZE * BD_SIZE)
#define P0_Q4_BD_OFFSET (P0_Q3_BD_OFFSET + HOST_QUEUE_3_SIZE * BD_SIZE)
#define P0_Q3_BD_OFFSET (P0_Q2_BD_OFFSET + HOST_QUEUE_2_SIZE * BD_SIZE)
#define P0_Q2_BD_OFFSET (P0_Q1_BD_OFFSET + HOST_QUEUE_1_SIZE * BD_SIZE)
#define P0_Q1_BD_OFFSET P0_BUFFER_DESC_OFFSET
#define P0_BUFFER_DESC_OFFSET SRAM_START_OFFSET
/* Memory Usage of L3 OCMC RAM */
/* L3 64KB Memory - mainly buffer Pool */
#define END_OF_BUFFER_POOL (P2_Q4_BUFFER_OFFSET + QUEUE_4_SIZE * \
ICSS_BLOCK_SIZE)
#define P2_Q4_BUFFER_OFFSET (P2_Q3_BUFFER_OFFSET + QUEUE_3_SIZE * \
ICSS_BLOCK_SIZE)
#define P2_Q3_BUFFER_OFFSET (P2_Q2_BUFFER_OFFSET + QUEUE_2_SIZE * \
ICSS_BLOCK_SIZE)
#define P2_Q2_BUFFER_OFFSET (P2_Q1_BUFFER_OFFSET + QUEUE_1_SIZE * \
ICSS_BLOCK_SIZE)
#define P2_Q1_BUFFER_OFFSET (P1_Q4_BUFFER_OFFSET + QUEUE_4_SIZE * \
ICSS_BLOCK_SIZE)
#define P1_Q4_BUFFER_OFFSET (P1_Q3_BUFFER_OFFSET + QUEUE_3_SIZE * \
ICSS_BLOCK_SIZE)
#define P1_Q3_BUFFER_OFFSET (P1_Q2_BUFFER_OFFSET + QUEUE_2_SIZE * \
ICSS_BLOCK_SIZE)
#define P1_Q2_BUFFER_OFFSET (P1_Q1_BUFFER_OFFSET + QUEUE_1_SIZE * \
ICSS_BLOCK_SIZE)
#define P1_Q1_BUFFER_OFFSET (P0_Q4_BUFFER_OFFSET + HOST_QUEUE_4_SIZE * \
ICSS_BLOCK_SIZE)
#define P0_Q4_BUFFER_OFFSET (P0_Q3_BUFFER_OFFSET + HOST_QUEUE_3_SIZE * \
ICSS_BLOCK_SIZE)
#define P0_Q3_BUFFER_OFFSET (P0_Q2_BUFFER_OFFSET + HOST_QUEUE_2_SIZE * \
ICSS_BLOCK_SIZE)
#define P0_Q2_BUFFER_OFFSET (P0_Q1_BUFFER_OFFSET + HOST_QUEUE_1_SIZE * \
ICSS_BLOCK_SIZE)
#define P0_COL_BUFFER_OFFSET 0xEE00
#define P0_Q1_BUFFER_OFFSET 0x0000
#define V2_1_FDB_TBL_LOC PRUETH_MEM_SHARED_RAM
#define V2_1_FDB_TBL_OFFSET 0x2000
#define FDB_INDEX_TBL_MAX_ENTRIES 256
#define FDB_MAC_TBL_MAX_ENTRIES 256
#define FDB_INDEX_TBL_OFFSET V2_1_FDB_TBL_OFFSET
#define FDB_INDEX_TBL_SIZE (FDB_INDEX_TBL_MAX_ENTRIES * \
sizeof(struct fdb_index_tbl_entry))
#define FDB_MAC_TBL_OFFSET (FDB_INDEX_TBL_OFFSET + FDB_INDEX_TBL_SIZE)
#define FDB_MAC_TBL_SIZE (FDB_MAC_TBL_MAX_ENTRIES * \
sizeof(struct fdb_mac_tbl_entry))
#define FDB_PORT1_STP_CFG_OFFSET (FDB_MAC_TBL_OFFSET + FDB_MAC_TBL_SIZE)
#define FDB_PORT_STP_CFG_SIZE sizeof(struct fdb_stp_config)
#define FDB_PORT2_STP_CFG_OFFSET (FDB_PORT1_STP_CFG_OFFSET + \
FDB_PORT_STP_CFG_SIZE)
#define FDB_FLOOD_ENABLE_FLAGS_OFFSET (FDB_PORT2_STP_CFG_OFFSET + \
FDB_PORT_STP_CFG_SIZE)
#define FDB_FLOOD_ENABLE_FLAGS_SIZE sizeof(struct fdb_flood_config)
#define FDB_LOCKS_OFFSET (FDB_FLOOD_ENABLE_FLAGS_OFFSET + \
FDB_FLOOD_ENABLE_FLAGS_SIZE)
#endif /* __ICSS_SWITCH_H */
@@ -0,0 +1,333 @@
// SPDX-License-Identifier: GPL-2.0
/* Texas Instruments ICSSM Ethernet Driver
*
* Copyright (C) 2018-2022 Texas Instruments Incorporated - https://www.ti.com/
*
*/
#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/remoteproc.h>
#include <net/switchdev.h>
#include "icssm_prueth.h"
#include "icssm_prueth_switch.h"
#include "icssm_prueth_fdb_tbl.h"
/* switchev event work */
struct icssm_sw_event_work {
netdevice_tracker ndev_tracker;
struct work_struct work;
struct switchdev_notifier_fdb_info fdb_info;
struct prueth_emac *emac;
unsigned long event;
};
void icssm_prueth_sw_set_stp_state(struct prueth *prueth,
enum prueth_port port, u8 state)
{
struct fdb_tbl *t = prueth->fdb_tbl;
writeb(state, port - 1 ? (void __iomem *)&t->port2_stp_cfg->state :
(void __iomem *)&t->port1_stp_cfg->state);
}
u8 icssm_prueth_sw_get_stp_state(struct prueth *prueth, enum prueth_port port)
{
struct fdb_tbl *t = prueth->fdb_tbl;
u8 state;
state = readb(port - 1 ? (void __iomem *)&t->port2_stp_cfg->state :
(void __iomem *)&t->port1_stp_cfg->state);
return state;
}
static int icssm_prueth_sw_attr_set(struct net_device *ndev, const void *ctx,
const struct switchdev_attr *attr,
struct netlink_ext_ack *extack)
{
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
int err = 0;
u8 o_state;
/* Interface is not up */
if (!prueth->fdb_tbl)
return 0;
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
o_state = icssm_prueth_sw_get_stp_state(prueth, emac->port_id);
icssm_prueth_sw_set_stp_state(prueth, emac->port_id,
attr->u.stp_state);
if (o_state != attr->u.stp_state)
icssm_prueth_sw_purge_fdb(emac);
dev_dbg(prueth->dev, "attr set: stp state:%u port:%u\n",
attr->u.stp_state, emac->port_id);
break;
default:
err = -EOPNOTSUPP;
break;
}
return err;
}
static void icssm_prueth_sw_fdb_offload(struct net_device *ndev,
struct switchdev_notifier_fdb_info *rcv)
{
struct switchdev_notifier_fdb_info info;
info.addr = rcv->addr;
info.vid = rcv->vid;
call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, ndev, &info.info,
NULL);
}
/**
* icssm_sw_event_work - insert/delete fdb entry
*
* @work: work structure
*
*/
static void icssm_sw_event_work(struct work_struct *work)
{
struct icssm_sw_event_work *switchdev_work =
container_of(work, struct icssm_sw_event_work, work);
struct prueth_emac *emac = switchdev_work->emac;
struct switchdev_notifier_fdb_info *fdb;
struct prueth *prueth = emac->prueth;
int port = emac->port_id;
rtnl_lock();
/* Interface is not up */
if (!emac->prueth->fdb_tbl)
goto free;
switch (switchdev_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
fdb = &switchdev_work->fdb_info;
dev_dbg(prueth->dev,
"prueth fdb add: MACID = %pM vid = %u flags = %u -- port %d\n",
fdb->addr, fdb->vid, fdb->added_by_user, port);
if (!fdb->added_by_user)
break;
if (fdb->is_local)
break;
icssm_prueth_sw_fdb_add(emac, fdb);
icssm_prueth_sw_fdb_offload(emac->ndev, fdb);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb = &switchdev_work->fdb_info;
dev_dbg(prueth->dev,
"prueth fdb del: MACID = %pM vid = %u flags = %u -- port %d\n",
fdb->addr, fdb->vid, fdb->added_by_user, port);
if (fdb->is_local)
break;
icssm_prueth_sw_fdb_del(emac, fdb);
break;
default:
break;
}
free:
rtnl_unlock();
netdev_put(emac->ndev, &switchdev_work->ndev_tracker);
kfree(switchdev_work->fdb_info.addr);
kfree(switchdev_work);
}
/* called under rcu_read_lock() */
static int icssm_prueth_sw_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
struct switchdev_notifier_fdb_info *fdb_info = ptr;
struct prueth_emac *emac = netdev_priv(ndev);
struct icssm_sw_event_work *switchdev_work;
int err;
if (!icssm_prueth_sw_port_dev_check(ndev))
return NOTIFY_DONE;
if (event == SWITCHDEV_PORT_ATTR_SET) {
err = switchdev_handle_port_attr_set
(ndev, ptr, icssm_prueth_sw_port_dev_check,
icssm_prueth_sw_attr_set);
return notifier_from_errno(err);
}
switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC);
if (WARN_ON(!switchdev_work))
return NOTIFY_BAD;
INIT_WORK(&switchdev_work->work, icssm_sw_event_work);
switchdev_work->emac = emac;
switchdev_work->event = event;
switch (event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
case SWITCHDEV_FDB_DEL_TO_DEVICE:
memcpy(&switchdev_work->fdb_info, ptr,
sizeof(switchdev_work->fdb_info));
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!switchdev_work->fdb_info.addr)
goto err_addr_alloc;
ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
fdb_info->addr);
netdev_hold(ndev, &switchdev_work->ndev_tracker, GFP_ATOMIC);
break;
default:
kfree(switchdev_work);
return NOTIFY_DONE;
}
queue_work(system_long_wq, &switchdev_work->work);
return NOTIFY_DONE;
err_addr_alloc:
kfree(switchdev_work);
return NOTIFY_BAD;
}
static int icssm_prueth_switchdev_obj_add(struct net_device *ndev,
const void *ctx,
const struct switchdev_obj *obj,
struct netlink_ext_ack *extack)
{
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
int ret = 0;
u8 hash;
switch (obj->id) {
case SWITCHDEV_OBJ_ID_HOST_MDB:
dev_dbg(prueth->dev, "MDB add: %s: vid %u:%pM port: %x\n",
ndev->name, mdb->vid, mdb->addr, emac->port_id);
hash = icssm_emac_get_mc_hash(mdb->addr, emac->mc_filter_mask);
icssm_emac_mc_filter_bin_allow(emac, hash);
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static int icssm_prueth_switchdev_obj_del(struct net_device *ndev,
const void *ctx,
const struct switchdev_obj *obj)
{
struct switchdev_obj_port_mdb *mdb = SWITCHDEV_OBJ_PORT_MDB(obj);
struct prueth_emac *emac = netdev_priv(ndev);
struct prueth *prueth = emac->prueth;
struct netdev_hw_addr *ha;
u8 hash, tmp_hash;
int ret = 0;
u8 *mask;
switch (obj->id) {
case SWITCHDEV_OBJ_ID_HOST_MDB:
dev_dbg(prueth->dev, "MDB del: %s: vid %u:%pM port: %x\n",
ndev->name, mdb->vid, mdb->addr, emac->port_id);
if (prueth->hw_bridge_dev) {
mask = emac->mc_filter_mask;
hash = icssm_emac_get_mc_hash(mdb->addr, mask);
netdev_for_each_mc_addr(ha, prueth->hw_bridge_dev) {
tmp_hash = icssm_emac_get_mc_hash(ha->addr,
mask);
/* Another MC address is in the bin.
* Don't disable.
*/
if (tmp_hash == hash)
return 0;
}
icssm_emac_mc_filter_bin_disallow(emac, hash);
}
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
/* switchdev notifiers */
static int icssm_prueth_sw_blocking_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *ndev = switchdev_notifier_info_to_dev(ptr);
int err;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = switchdev_handle_port_obj_add
(ndev, ptr, icssm_prueth_sw_port_dev_check,
icssm_prueth_switchdev_obj_add);
return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL:
err = switchdev_handle_port_obj_del
(ndev, ptr, icssm_prueth_sw_port_dev_check,
icssm_prueth_switchdev_obj_del);
return notifier_from_errno(err);
case SWITCHDEV_PORT_ATTR_SET:
err = switchdev_handle_port_attr_set
(ndev, ptr, icssm_prueth_sw_port_dev_check,
icssm_prueth_sw_attr_set);
return notifier_from_errno(err);
default:
break;
}
return NOTIFY_DONE;
}
int icssm_prueth_sw_register_notifiers(struct prueth *prueth)
{
int ret = 0;
prueth->prueth_switchdev_nb.notifier_call =
&icssm_prueth_sw_switchdev_event;
ret = register_switchdev_notifier(&prueth->prueth_switchdev_nb);
if (ret) {
dev_err(prueth->dev,
"register switchdev notifier failed ret:%d\n", ret);
return ret;
}
prueth->prueth_switchdev_bl_nb.notifier_call =
&icssm_prueth_sw_blocking_event;
ret = register_switchdev_blocking_notifier
(&prueth->prueth_switchdev_bl_nb);
if (ret) {
dev_err(prueth->dev,
"register switchdev blocking notifier failed ret:%d\n",
ret);
unregister_switchdev_notifier(&prueth->prueth_switchdev_nb);
}
return ret;
}
void icssm_prueth_sw_unregister_notifiers(struct prueth *prueth)
{
unregister_switchdev_blocking_notifier(&prueth->prueth_switchdev_bl_nb);
unregister_switchdev_notifier(&prueth->prueth_switchdev_nb);
}
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2020-2021 Texas Instruments Incorporated - https://www.ti.com
*/
#ifndef __NET_TI_ICSSM_SWITCHDEV_H
#define __NET_TI_ICSSM_SWITCHDEV_H
#include "icssm_prueth.h"
int icssm_prueth_sw_register_notifiers(struct prueth *prueth);
void icssm_prueth_sw_unregister_notifiers(struct prueth *prueth);
bool icssm_prueth_sw_port_dev_check(const struct net_device *ndev);
#endif /* __NET_TI_ICSSM_SWITCHDEV_H */
@@ -0,0 +1,120 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (C) 2015-2021 Texas Instruments Incorporated - https://www.ti.com
*
* This file contains VLAN/Multicast filtering feature memory map
*
*/
#ifndef ICSS_VLAN_MULTICAST_FILTER_MM_H
#define ICSS_VLAN_MULTICAST_FILTER_MM_H
/* VLAN/Multicast filter defines & offsets,
* present on both PRU0 and PRU1 DRAM
*/
/* Feature enable/disable values for multicast filtering */
#define ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_DISABLED 0x00
#define ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_ENABLED 0x01
/* Feature enable/disable values for VLAN filtering */
#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_DISABLED 0x00
#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_ENABLED 0x01
/* Add/remove multicast mac id for filtering bin */
#define ICSS_EMAC_FW_MULTICAST_FILTER_HOST_RCV_ALLOWED 0x01
#define ICSS_EMAC_FW_MULTICAST_FILTER_HOST_RCV_NOT_ALLOWED 0x00
/* Default HASH value for the multicast filtering Mask */
#define ICSS_EMAC_FW_MULTICAST_FILTER_INIT_VAL 0xFF
/* Size requirements for Multicast filtering feature */
#define ICSS_EMAC_FW_MULTICAST_TABLE_SIZE_BYTES 256
#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES 6
#define ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_SIZE_BYTES 1
#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OVERRIDE_STATUS_SIZE_BYTES 1
#define ICSS_EMAC_FW_MULTICAST_FILTER_DROP_CNT_SIZE_BYTES 4
/* Size requirements for VLAN filtering feature : 4096 bits = 512 bytes */
#define ICSS_EMAC_FW_VLAN_FILTER_TABLE_SIZE_BYTES 512
#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_SIZE_BYTES 1
#define ICSS_EMAC_FW_VLAN_FILTER_DROP_CNT_SIZE_BYTES 4
/* Mask override set status */
#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OVERRIDE_SET 1
/* Mask override not set status */
#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OVERRIDE_NOT_SET 0
/* 6 bytes HASH Mask for the MAC */
#define ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET 0xF4
/* 0 -> multicast filtering disabled | 1 -> multicast filtering enabled */
#define ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET \
(ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OFFSET + \
ICSS_EMAC_FW_MULTICAST_FILTER_MASK_SIZE_BYTES)
/* Status indicating if the HASH override is done or not: 0: no, 1: yes */
#define ICSS_EMAC_FW_MULTICAST_FILTER_OVERRIDE_STATUS \
(ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_OFFSET + \
ICSS_EMAC_FW_MULTICAST_FILTER_CTRL_SIZE_BYTES)
/* Multicast drop statistics */
#define ICSS_EMAC_FW_MULTICAST_FILTER_DROP_CNT_OFFSET \
(ICSS_EMAC_FW_MULTICAST_FILTER_OVERRIDE_STATUS +\
ICSS_EMAC_FW_MULTICAST_FILTER_MASK_OVERRIDE_STATUS_SIZE_BYTES)
/* Multicast table */
#define ICSS_EMAC_FW_MULTICAST_FILTER_TABLE \
(ICSS_EMAC_FW_MULTICAST_FILTER_DROP_CNT_OFFSET +\
ICSS_EMAC_FW_MULTICAST_FILTER_DROP_CNT_SIZE_BYTES)
/* Multicast filter defines & offsets for LRE
*/
#define ICSS_LRE_FW_MULTICAST_TABLE_SEARCH_OP_CONTROL_BIT 0xE0
/* one byte field :
* 0 -> multicast filtering disabled
* 1 -> multicast filtering enabled
*/
#define ICSS_LRE_FW_MULTICAST_FILTER_MASK 0xE4
#define ICSS_LRE_FW_MULTICAST_FILTER_TABLE 0x100
/* VLAN table Offsets */
#define ICSS_EMAC_FW_VLAN_FLTR_TBL_BASE_ADDR 0x200
#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_BITMAP_OFFSET 0xEF
#define ICSS_EMAC_FW_VLAN_FILTER_DROP_CNT_OFFSET \
(ICSS_EMAC_FW_VLAN_FILTER_CTRL_BITMAP_OFFSET + \
ICSS_EMAC_FW_VLAN_FILTER_CTRL_SIZE_BYTES)
/* VLAN filter Control Bit maps */
/* one bit field, bit 0: | 0 : VLAN filter disabled (default),
* 1: VLAN filter enabled
*/
#define ICSS_EMAC_FW_VLAN_FILTER_CTRL_ENABLE_BIT 0
/* one bit field, bit 1: | 0 : untagged host rcv allowed (default),
* 1: untagged host rcv not allowed
*/
#define ICSS_EMAC_FW_VLAN_FILTER_UNTAG_HOST_RCV_ALLOW_CTRL_BIT 1
/* one bit field, bit 1: | 0 : priotag host rcv allowed (default),
* 1: priotag host rcv not allowed
*/
#define ICSS_EMAC_FW_VLAN_FILTER_PRIOTAG_HOST_RCV_ALLOW_CTRL_BIT 2
/* one bit field, bit 1: | 0 : skip sv vlan flow
* :1 : take sv vlan flow (not applicable for dual emac )
*/
#define ICSS_EMAC_FW_VLAN_FILTER_SV_VLAN_FLOW_HOST_RCV_ALLOW_CTRL_BIT 3
/* VLAN IDs */
#define ICSS_EMAC_FW_VLAN_FILTER_PRIOTAG_VID 0
#define ICSS_EMAC_FW_VLAN_FILTER_VID_MIN 0x0000
#define ICSS_EMAC_FW_VLAN_FILTER_VID_MAX 0x0FFF
/* VLAN Filtering Commands */
#define ICSS_EMAC_FW_VLAN_FILTER_ADD_VLAN_VID_CMD 0x00
#define ICSS_EMAC_FW_VLAN_FILTER_REMOVE_VLAN_VID_CMD 0x01
/* Switch defines for VLAN/MC filtering */
/* SRAM
* VLAN filter defines & offsets
*/
#define ICSS_LRE_FW_VLAN_FLTR_CTRL_BYTE 0x1FE
/* one bit field | 0 : VLAN filter disabled
* | 1 : VLAN filter enabled
*/
#define ICSS_LRE_FW_VLAN_FLTR_TBL_BASE_ADDR 0x200
#endif /* ICSS_MULTICAST_FILTER_MM_H */
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: GPL-2.0
/* TI K3 CPPI5 descriptors pool API
*
* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/genalloc.h>
#include <linux/kernel.h>
#include "k3-cppi-desc-pool.h"
struct k3_cppi_desc_pool {
struct device *dev;
dma_addr_t dma_addr;
void *cpumem; /* dma_alloc map */
size_t desc_size;
size_t mem_size;
size_t num_desc;
struct gen_pool *gen_pool;
void **desc_infos;
};
void k3_cppi_desc_pool_destroy(struct k3_cppi_desc_pool *pool)
{
if (!pool)
return;
WARN(gen_pool_size(pool->gen_pool) != gen_pool_avail(pool->gen_pool),
"k3_knav_desc_pool size %zu != avail %zu",
gen_pool_size(pool->gen_pool),
gen_pool_avail(pool->gen_pool));
if (pool->cpumem)
dma_free_coherent(pool->dev, pool->mem_size, pool->cpumem,
pool->dma_addr);
kfree(pool->desc_infos);
gen_pool_destroy(pool->gen_pool); /* frees pool->name */
kfree(pool);
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_destroy);
struct k3_cppi_desc_pool *
k3_cppi_desc_pool_create_name(struct device *dev, size_t size,
size_t desc_size,
const char *name)
{
struct k3_cppi_desc_pool *pool;
const char *pool_name = NULL;
int ret = -ENOMEM;
pool = kzalloc_obj(*pool);
if (!pool)
return ERR_PTR(ret);
pool->dev = dev;
pool->desc_size = roundup_pow_of_two(desc_size);
pool->num_desc = size;
pool->mem_size = pool->num_desc * pool->desc_size;
pool_name = kstrdup_const(name ? name : dev_name(pool->dev),
GFP_KERNEL);
if (!pool_name)
goto gen_pool_create_fail;
pool->gen_pool = gen_pool_create(ilog2(pool->desc_size), -1);
if (!pool->gen_pool) {
kfree_const(pool_name);
goto gen_pool_create_fail;
}
pool->gen_pool->name = pool_name;
pool->desc_infos = kzalloc_objs(*pool->desc_infos, pool->num_desc);
if (!pool->desc_infos)
goto gen_pool_desc_infos_alloc_fail;
pool->cpumem = dma_alloc_coherent(pool->dev, pool->mem_size,
&pool->dma_addr, GFP_KERNEL);
if (!pool->cpumem)
goto dma_alloc_fail;
ret = gen_pool_add_virt(pool->gen_pool, (unsigned long)pool->cpumem,
(phys_addr_t)pool->dma_addr, pool->mem_size,
-1);
if (ret < 0) {
dev_err(pool->dev, "pool add failed %d\n", ret);
goto gen_pool_add_virt_fail;
}
return pool;
gen_pool_add_virt_fail:
dma_free_coherent(pool->dev, pool->mem_size, pool->cpumem,
pool->dma_addr);
dma_alloc_fail:
kfree(pool->desc_infos);
gen_pool_desc_infos_alloc_fail:
gen_pool_destroy(pool->gen_pool); /* frees pool->name */
gen_pool_create_fail:
kfree(pool);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_create_name);
dma_addr_t k3_cppi_desc_pool_virt2dma(struct k3_cppi_desc_pool *pool,
void *addr)
{
return addr ? pool->dma_addr + (addr - pool->cpumem) : 0;
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_virt2dma);
void *k3_cppi_desc_pool_dma2virt(struct k3_cppi_desc_pool *pool, dma_addr_t dma)
{
return dma ? pool->cpumem + (dma - pool->dma_addr) : NULL;
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_dma2virt);
void *k3_cppi_desc_pool_alloc(struct k3_cppi_desc_pool *pool)
{
return (void *)gen_pool_alloc(pool->gen_pool, pool->desc_size);
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_alloc);
void k3_cppi_desc_pool_free(struct k3_cppi_desc_pool *pool, void *addr)
{
gen_pool_free(pool->gen_pool, (unsigned long)addr, pool->desc_size);
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_free);
size_t k3_cppi_desc_pool_avail(struct k3_cppi_desc_pool *pool)
{
return gen_pool_avail(pool->gen_pool) / pool->desc_size;
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_avail);
size_t k3_cppi_desc_pool_desc_size(const struct k3_cppi_desc_pool *pool)
{
return pool->desc_size;
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_desc_size);
void *k3_cppi_desc_pool_cpuaddr(const struct k3_cppi_desc_pool *pool)
{
return pool->cpumem;
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_cpuaddr);
void k3_cppi_desc_pool_desc_info_set(struct k3_cppi_desc_pool *pool,
int desc_idx, void *info)
{
pool->desc_infos[desc_idx] = info;
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_desc_info_set);
void *k3_cppi_desc_pool_desc_info(const struct k3_cppi_desc_pool *pool,
int desc_idx)
{
return pool->desc_infos[desc_idx];
}
EXPORT_SYMBOL_GPL(k3_cppi_desc_pool_desc_info);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TI K3 CPPI5 descriptors pool API");
@@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* TI K3 CPPI5 descriptors pool
*
* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
*/
#ifndef K3_CPPI_DESC_POOL_H_
#define K3_CPPI_DESC_POOL_H_
#include <linux/device.h>
#include <linux/types.h>
struct k3_cppi_desc_pool;
void k3_cppi_desc_pool_destroy(struct k3_cppi_desc_pool *pool);
struct k3_cppi_desc_pool *
k3_cppi_desc_pool_create_name(struct device *dev, size_t size,
size_t desc_size,
const char *name);
#define k3_cppi_desc_pool_create(dev, size, desc_size) \
k3_cppi_desc_pool_create_name(dev, size, desc_size, NULL)
dma_addr_t
k3_cppi_desc_pool_virt2dma(struct k3_cppi_desc_pool *pool, void *addr);
void *
k3_cppi_desc_pool_dma2virt(struct k3_cppi_desc_pool *pool, dma_addr_t dma);
void *k3_cppi_desc_pool_alloc(struct k3_cppi_desc_pool *pool);
void k3_cppi_desc_pool_free(struct k3_cppi_desc_pool *pool, void *addr);
size_t k3_cppi_desc_pool_avail(struct k3_cppi_desc_pool *pool);
size_t k3_cppi_desc_pool_desc_size(const struct k3_cppi_desc_pool *pool);
void *k3_cppi_desc_pool_cpuaddr(const struct k3_cppi_desc_pool *pool);
void k3_cppi_desc_pool_desc_info_set(struct k3_cppi_desc_pool *pool,
int desc_idx, void *info);
void *k3_cppi_desc_pool_desc_info(const struct k3_cppi_desc_pool *pool,
int desc_idx);
#endif /* K3_CPPI_DESC_POOL_H_ */
+251
View File
@@ -0,0 +1,251 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* NetCP driver local header
*
* Copyright (C) 2014 Texas Instruments Incorporated
* Authors: Sandeep Nair <sandeep_n@ti.com>
* Sandeep Paulraj <s-paulraj@ti.com>
* Cyril Chemparathy <cyril@ti.com>
* Santosh Shilimkar <santosh.shilimkar@ti.com>
* Wingman Kwok <w-kwok2@ti.com>
* Murali Karicheri <m-karicheri2@ti.com>
*/
#ifndef __NETCP_H__
#define __NETCP_H__
#include <linux/netdevice.h>
#include <linux/soc/ti/knav_dma.h>
#include <linux/u64_stats_sync.h>
/* Maximum Ethernet frame size supported by Keystone switch */
#define NETCP_MAX_FRAME_SIZE 9504
#define SGMII_LINK_MAC_MAC_AUTONEG 0
#define SGMII_LINK_MAC_PHY 1
#define SGMII_LINK_MAC_MAC_FORCED 2
#define SGMII_LINK_MAC_FIBER 3
#define SGMII_LINK_MAC_PHY_NO_MDIO 4
#define RGMII_LINK_MAC_PHY 5
#define RGMII_LINK_MAC_PHY_NO_MDIO 7
#define XGMII_LINK_MAC_PHY 10
#define XGMII_LINK_MAC_MAC_FORCED 11
struct netcp_device;
struct netcp_tx_pipe {
struct netcp_device *netcp_device;
void *dma_queue;
unsigned int dma_queue_id;
/* To port for packet forwarded to switch. Used only by ethss */
u8 switch_to_port;
#define SWITCH_TO_PORT_IN_TAGINFO BIT(0)
u8 flags;
void *dma_channel;
const char *dma_chan_name;
};
#define ADDR_NEW BIT(0)
#define ADDR_VALID BIT(1)
enum netcp_addr_type {
ADDR_ANY,
ADDR_DEV,
ADDR_UCAST,
ADDR_MCAST,
ADDR_BCAST
};
struct netcp_addr {
struct netcp_intf *netcp;
unsigned char addr[ETH_ALEN];
enum netcp_addr_type type;
unsigned int flags;
struct list_head node;
};
struct netcp_stats {
struct u64_stats_sync syncp_rx ____cacheline_aligned_in_smp;
u64_stats_t rx_packets;
u64_stats_t rx_bytes;
u32 rx_errors;
u32 rx_dropped;
struct u64_stats_sync syncp_tx ____cacheline_aligned_in_smp;
u64_stats_t tx_packets;
u64_stats_t tx_bytes;
u32 tx_errors;
u32 tx_dropped;
};
struct netcp_intf {
struct device *dev;
struct device *ndev_dev;
struct net_device *ndev;
bool big_endian;
unsigned int tx_compl_qid;
void *tx_pool;
struct list_head txhook_list_head;
unsigned int tx_pause_threshold;
void *tx_compl_q;
unsigned int tx_resume_threshold;
void *rx_queue;
void *rx_pool;
struct list_head rxhook_list_head;
unsigned int rx_queue_id;
void *rx_fdq[KNAV_DMA_FDQ_PER_CHAN];
struct napi_struct rx_napi;
struct napi_struct tx_napi;
#define ETH_SW_CAN_REMOVE_ETH_FCS BIT(0)
u32 hw_cap;
/* 64-bit netcp stats */
struct netcp_stats stats;
void *rx_channel;
const char *dma_chan_name;
u32 rx_pool_size;
u32 rx_pool_region_id;
u32 tx_pool_size;
u32 tx_pool_region_id;
struct list_head module_head;
struct list_head interface_list;
struct list_head addr_list;
bool netdev_registered;
bool primary_module_attached;
/* Lock used for protecting Rx/Tx hook list management */
spinlock_t lock;
struct netcp_device *netcp_device;
struct device_node *node_interface;
/* DMA configuration data */
u32 msg_enable;
u32 rx_queue_depths[KNAV_DMA_FDQ_PER_CHAN];
};
#define NETCP_PSDATA_LEN KNAV_DMA_NUM_PS_WORDS
struct netcp_packet {
struct sk_buff *skb;
__le32 *epib;
u32 *psdata;
u32 eflags;
unsigned int psdata_len;
struct netcp_intf *netcp;
struct netcp_tx_pipe *tx_pipe;
bool rxtstamp_complete;
void *ts_context;
void (*txtstamp)(void *ctx, struct sk_buff *skb);
};
static inline u32 *netcp_push_psdata(struct netcp_packet *p_info,
unsigned int bytes)
{
u32 *buf;
unsigned int words;
if ((bytes & 0x03) != 0)
return NULL;
words = bytes >> 2;
if ((p_info->psdata_len + words) > NETCP_PSDATA_LEN)
return NULL;
p_info->psdata_len += words;
buf = &p_info->psdata[NETCP_PSDATA_LEN - p_info->psdata_len];
return buf;
}
static inline int netcp_align_psdata(struct netcp_packet *p_info,
unsigned int byte_align)
{
int padding;
switch (byte_align) {
case 0:
padding = -EINVAL;
break;
case 1:
case 2:
case 4:
padding = 0;
break;
case 8:
padding = (p_info->psdata_len << 2) % 8;
break;
case 16:
padding = (p_info->psdata_len << 2) % 16;
break;
default:
padding = (p_info->psdata_len << 2) % byte_align;
break;
}
return padding;
}
struct netcp_module {
const char *name;
struct module *owner;
bool primary;
/* probe/remove: called once per NETCP instance */
int (*probe)(struct netcp_device *netcp_device,
struct device *device, struct device_node *node,
void **inst_priv);
int (*remove)(struct netcp_device *netcp_device, void *inst_priv);
/* attach/release: called once per network interface */
int (*attach)(void *inst_priv, struct net_device *ndev,
struct device_node *node, void **intf_priv);
int (*release)(void *intf_priv);
int (*open)(void *intf_priv, struct net_device *ndev);
int (*close)(void *intf_priv, struct net_device *ndev);
int (*add_addr)(void *intf_priv, struct netcp_addr *naddr);
int (*del_addr)(void *intf_priv, struct netcp_addr *naddr);
int (*add_vid)(void *intf_priv, int vid);
int (*del_vid)(void *intf_priv, int vid);
int (*ioctl)(void *intf_priv, struct ifreq *req, int cmd);
int (*set_rx_mode)(void *intf_priv, bool promisc);
int (*hwtstamp_get)(void *intf_priv,
struct kernel_hwtstamp_config *cfg);
int (*hwtstamp_set)(void *intf_priv,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack);
/* used internally */
struct list_head module_list;
struct list_head interface_list;
};
int netcp_register_module(struct netcp_module *module);
void netcp_unregister_module(struct netcp_module *module);
void *netcp_module_get_intf_data(struct netcp_module *module,
struct netcp_intf *intf);
int netcp_txpipe_init(struct netcp_tx_pipe *tx_pipe,
struct netcp_device *netcp_device,
const char *dma_chan_name, unsigned int dma_queue_id);
int netcp_txpipe_open(struct netcp_tx_pipe *tx_pipe);
int netcp_txpipe_close(struct netcp_tx_pipe *tx_pipe);
typedef int netcp_hook_rtn(int order, void *data, struct netcp_packet *packet);
int netcp_register_txhook(struct netcp_intf *netcp_priv, int order,
netcp_hook_rtn *hook_rtn, void *hook_data);
int netcp_unregister_txhook(struct netcp_intf *netcp_priv, int order,
netcp_hook_rtn *hook_rtn, void *hook_data);
int netcp_register_rxhook(struct netcp_intf *netcp_priv, int order,
netcp_hook_rtn *hook_rtn, void *hook_data);
int netcp_unregister_rxhook(struct netcp_intf *netcp_priv, int order,
netcp_hook_rtn *hook_rtn, void *hook_data);
/* SGMII functions */
int netcp_sgmii_reset(void __iomem *sgmii_ofs, int port);
bool netcp_sgmii_rtreset(void __iomem *sgmii_ofs, int port, bool set);
int netcp_sgmii_get_port_link(void __iomem *sgmii_ofs, int port);
int netcp_sgmii_config(void __iomem *sgmii_ofs, int port, u32 interface);
/* XGBE SERDES init functions */
int netcp_xgbe_serdes_init(void __iomem *serdes_regs, void __iomem *xgbe_regs);
#endif /* __NETCP_H__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: GPL-2.0
/*
* SGMI module initialisation
*
* Copyright (C) 2014 Texas Instruments Incorporated
* Authors: Sandeep Nair <sandeep_n@ti.com>
* Sandeep Paulraj <s-paulraj@ti.com>
* Wingman Kwok <w-kwok2@ti.com>
*
*/
#include "netcp.h"
#define SGMII_SRESET_RESET BIT(0)
#define SGMII_SRESET_RTRESET BIT(1)
#define SGMII_REG_STATUS_LOCK BIT(4)
#define SGMII_REG_STATUS_LINK BIT(0)
#define SGMII_REG_STATUS_AUTONEG BIT(2)
#define SGMII_REG_CONTROL_AUTONEG BIT(0)
#define SGMII23_OFFSET(x) ((x - 2) * 0x100)
#define SGMII_OFFSET(x) ((x <= 1) ? (x * 0x100) : (SGMII23_OFFSET(x)))
/* SGMII registers */
#define SGMII_SRESET_REG(x) (SGMII_OFFSET(x) + 0x004)
#define SGMII_CTL_REG(x) (SGMII_OFFSET(x) + 0x010)
#define SGMII_STATUS_REG(x) (SGMII_OFFSET(x) + 0x014)
#define SGMII_MRADV_REG(x) (SGMII_OFFSET(x) + 0x018)
static void sgmii_write_reg(void __iomem *base, int reg, u32 val)
{
writel(val, base + reg);
}
static u32 sgmii_read_reg(void __iomem *base, int reg)
{
return readl(base + reg);
}
static void sgmii_write_reg_bit(void __iomem *base, int reg, u32 val)
{
writel((readl(base + reg) | val), base + reg);
}
/* port is 0 based */
int netcp_sgmii_reset(void __iomem *sgmii_ofs, int port)
{
/* Soft reset */
sgmii_write_reg_bit(sgmii_ofs, SGMII_SRESET_REG(port),
SGMII_SRESET_RESET);
while ((sgmii_read_reg(sgmii_ofs, SGMII_SRESET_REG(port)) &
SGMII_SRESET_RESET) != 0x0)
;
return 0;
}
/* port is 0 based */
bool netcp_sgmii_rtreset(void __iomem *sgmii_ofs, int port, bool set)
{
u32 reg;
bool oldval;
/* Initiate a soft reset */
reg = sgmii_read_reg(sgmii_ofs, SGMII_SRESET_REG(port));
oldval = (reg & SGMII_SRESET_RTRESET) != 0x0;
if (set)
reg |= SGMII_SRESET_RTRESET;
else
reg &= ~SGMII_SRESET_RTRESET;
sgmii_write_reg(sgmii_ofs, SGMII_SRESET_REG(port), reg);
wmb();
return oldval;
}
int netcp_sgmii_get_port_link(void __iomem *sgmii_ofs, int port)
{
u32 status = 0, link = 0;
status = sgmii_read_reg(sgmii_ofs, SGMII_STATUS_REG(port));
if ((status & SGMII_REG_STATUS_LINK) != 0)
link = 1;
return link;
}
int netcp_sgmii_config(void __iomem *sgmii_ofs, int port, u32 interface)
{
unsigned int i, status, mask;
u32 mr_adv_ability;
u32 control;
switch (interface) {
case SGMII_LINK_MAC_MAC_AUTONEG:
mr_adv_ability = 0x9801;
control = 0x21;
break;
case SGMII_LINK_MAC_PHY:
case SGMII_LINK_MAC_PHY_NO_MDIO:
mr_adv_ability = 1;
control = 1;
break;
case SGMII_LINK_MAC_MAC_FORCED:
mr_adv_ability = 0x9801;
control = 0x20;
break;
case SGMII_LINK_MAC_FIBER:
mr_adv_ability = 0x20;
control = 0x1;
break;
default:
WARN_ONCE(1, "Invalid sgmii interface: %d\n", interface);
return -EINVAL;
}
sgmii_write_reg(sgmii_ofs, SGMII_CTL_REG(port), 0);
/* Wait for the SerDes pll to lock */
for (i = 0; i < 1000; i++) {
usleep_range(1000, 2000);
status = sgmii_read_reg(sgmii_ofs, SGMII_STATUS_REG(port));
if ((status & SGMII_REG_STATUS_LOCK) != 0)
break;
}
if ((status & SGMII_REG_STATUS_LOCK) == 0)
pr_err("serdes PLL not locked\n");
sgmii_write_reg(sgmii_ofs, SGMII_MRADV_REG(port), mr_adv_ability);
sgmii_write_reg(sgmii_ofs, SGMII_CTL_REG(port), control);
mask = SGMII_REG_STATUS_LINK;
if (control & SGMII_REG_CONTROL_AUTONEG)
mask |= SGMII_REG_STATUS_AUTONEG;
for (i = 0; i < 1000; i++) {
usleep_range(200, 500);
status = sgmii_read_reg(sgmii_ofs, SGMII_STATUS_REG(port));
if ((status & mask) == mask)
break;
}
return 0;
}
@@ -0,0 +1,494 @@
// SPDX-License-Identifier: GPL-2.0
/*
* XGE PCSR module initialisation
*
* Copyright (C) 2014 Texas Instruments Incorporated
* Authors: Sandeep Nair <sandeep_n@ti.com>
* WingMan Kwok <w-kwok2@ti.com>
*
*/
#include "netcp.h"
/* XGBE registers */
#define XGBE_CTRL_OFFSET 0x0c
#define XGBE_SGMII_1_OFFSET 0x0114
#define XGBE_SGMII_2_OFFSET 0x0214
/* PCS-R registers */
#define PCSR_CPU_CTRL_OFFSET 0x1fd0
#define POR_EN BIT(29)
#define reg_rmw(addr, value, mask) \
writel(((readl(addr) & (~(mask))) | \
(value & (mask))), (addr))
/* bit mask of width w at offset s */
#define MASK_WID_SH(w, s) (((1 << w) - 1) << s)
/* shift value v to offset s */
#define VAL_SH(v, s) (v << s)
#define PHY_A(serdes) 0
struct serdes_cfg {
u32 ofs;
u32 val;
u32 mask;
};
static struct serdes_cfg cfg_phyb_1p25g_156p25mhz_cmu0[] = {
{0x0000, 0x00800002, 0x00ff00ff},
{0x0014, 0x00003838, 0x0000ffff},
{0x0060, 0x1c44e438, 0xffffffff},
{0x0064, 0x00c18400, 0x00ffffff},
{0x0068, 0x17078200, 0xffffff00},
{0x006c, 0x00000014, 0x000000ff},
{0x0078, 0x0000c000, 0x0000ff00},
{0x0000, 0x00000003, 0x000000ff},
};
static struct serdes_cfg cfg_phyb_10p3125g_156p25mhz_cmu1[] = {
{0x0c00, 0x00030002, 0x00ff00ff},
{0x0c14, 0x00005252, 0x0000ffff},
{0x0c28, 0x80000000, 0xff000000},
{0x0c2c, 0x000000f6, 0x000000ff},
{0x0c3c, 0x04000405, 0xff00ffff},
{0x0c40, 0xc0800000, 0xffff0000},
{0x0c44, 0x5a202062, 0xffffffff},
{0x0c48, 0x40040424, 0xffffffff},
{0x0c4c, 0x00004002, 0x0000ffff},
{0x0c50, 0x19001c00, 0xff00ff00},
{0x0c54, 0x00002100, 0x0000ff00},
{0x0c58, 0x00000060, 0x000000ff},
{0x0c60, 0x80131e7c, 0xffffffff},
{0x0c64, 0x8400cb02, 0xff00ffff},
{0x0c68, 0x17078200, 0xffffff00},
{0x0c6c, 0x00000016, 0x000000ff},
{0x0c74, 0x00000400, 0x0000ff00},
{0x0c78, 0x0000c000, 0x0000ff00},
{0x0c00, 0x00000003, 0x000000ff},
};
static struct serdes_cfg cfg_phyb_10p3125g_16bit_lane[] = {
{0x0204, 0x00000080, 0x000000ff},
{0x0208, 0x0000920d, 0x0000ffff},
{0x0204, 0xfc000000, 0xff000000},
{0x0208, 0x00009104, 0x0000ffff},
{0x0210, 0x1a000000, 0xff000000},
{0x0214, 0x00006b58, 0x00ffffff},
{0x0218, 0x75800084, 0xffff00ff},
{0x022c, 0x00300000, 0x00ff0000},
{0x0230, 0x00003800, 0x0000ff00},
{0x024c, 0x008f0000, 0x00ff0000},
{0x0250, 0x30000000, 0xff000000},
{0x0260, 0x00000002, 0x000000ff},
{0x0264, 0x00000057, 0x000000ff},
{0x0268, 0x00575700, 0x00ffff00},
{0x0278, 0xff000000, 0xff000000},
{0x0280, 0x00500050, 0x00ff00ff},
{0x0284, 0x00001f15, 0x0000ffff},
{0x028c, 0x00006f00, 0x0000ff00},
{0x0294, 0x00000000, 0xffffff00},
{0x0298, 0x00002640, 0xff00ffff},
{0x029c, 0x00000003, 0x000000ff},
{0x02a4, 0x00000f13, 0x0000ffff},
{0x02a8, 0x0001b600, 0x00ffff00},
{0x0380, 0x00000030, 0x000000ff},
{0x03c0, 0x00000200, 0x0000ff00},
{0x03cc, 0x00000018, 0x000000ff},
{0x03cc, 0x00000000, 0x000000ff},
};
static struct serdes_cfg cfg_phyb_10p3125g_comlane[] = {
{0x0a00, 0x00000800, 0x0000ff00},
{0x0a84, 0x00000000, 0x000000ff},
{0x0a8c, 0x00130000, 0x00ff0000},
{0x0a90, 0x77a00000, 0xffff0000},
{0x0a94, 0x00007777, 0x0000ffff},
{0x0b08, 0x000f0000, 0xffff0000},
{0x0b0c, 0x000f0000, 0x00ffffff},
{0x0b10, 0xbe000000, 0xff000000},
{0x0b14, 0x000000ff, 0x000000ff},
{0x0b18, 0x00000014, 0x000000ff},
{0x0b5c, 0x981b0000, 0xffff0000},
{0x0b64, 0x00001100, 0x0000ff00},
{0x0b78, 0x00000c00, 0x0000ff00},
{0x0abc, 0xff000000, 0xff000000},
{0x0ac0, 0x0000008b, 0x000000ff},
};
static struct serdes_cfg cfg_cm_c1_c2[] = {
{0x0208, 0x00000000, 0x00000f00},
{0x0208, 0x00000000, 0x0000001f},
{0x0204, 0x00000000, 0x00040000},
{0x0208, 0x000000a0, 0x000000e0},
};
static void netcp_xgbe_serdes_cmu_init(void __iomem *serdes_regs)
{
int i;
/* cmu0 setup */
for (i = 0; i < ARRAY_SIZE(cfg_phyb_1p25g_156p25mhz_cmu0); i++) {
reg_rmw(serdes_regs + cfg_phyb_1p25g_156p25mhz_cmu0[i].ofs,
cfg_phyb_1p25g_156p25mhz_cmu0[i].val,
cfg_phyb_1p25g_156p25mhz_cmu0[i].mask);
}
/* cmu1 setup */
for (i = 0; i < ARRAY_SIZE(cfg_phyb_10p3125g_156p25mhz_cmu1); i++) {
reg_rmw(serdes_regs + cfg_phyb_10p3125g_156p25mhz_cmu1[i].ofs,
cfg_phyb_10p3125g_156p25mhz_cmu1[i].val,
cfg_phyb_10p3125g_156p25mhz_cmu1[i].mask);
}
}
/* lane is 0 based */
static void netcp_xgbe_serdes_lane_config(
void __iomem *serdes_regs, int lane)
{
int i;
/* lane setup */
for (i = 0; i < ARRAY_SIZE(cfg_phyb_10p3125g_16bit_lane); i++) {
reg_rmw(serdes_regs +
cfg_phyb_10p3125g_16bit_lane[i].ofs +
(0x200 * lane),
cfg_phyb_10p3125g_16bit_lane[i].val,
cfg_phyb_10p3125g_16bit_lane[i].mask);
}
/* disable auto negotiation*/
reg_rmw(serdes_regs + (0x200 * lane) + 0x0380,
0x00000000, 0x00000010);
/* disable link training */
reg_rmw(serdes_regs + (0x200 * lane) + 0x03c0,
0x00000000, 0x00000200);
}
static void netcp_xgbe_serdes_com_enable(void __iomem *serdes_regs)
{
int i;
for (i = 0; i < ARRAY_SIZE(cfg_phyb_10p3125g_comlane); i++) {
reg_rmw(serdes_regs + cfg_phyb_10p3125g_comlane[i].ofs,
cfg_phyb_10p3125g_comlane[i].val,
cfg_phyb_10p3125g_comlane[i].mask);
}
}
static void netcp_xgbe_serdes_lane_enable(
void __iomem *serdes_regs, int lane)
{
/* Set Lane Control Rate */
writel(0xe0e9e038, serdes_regs + 0x1fe0 + (4 * lane));
}
static void netcp_xgbe_serdes_phyb_rst_clr(void __iomem *serdes_regs)
{
reg_rmw(serdes_regs + 0x0a00, 0x0000001f, 0x000000ff);
}
static void netcp_xgbe_serdes_pll_disable(void __iomem *serdes_regs)
{
writel(0x88000000, serdes_regs + 0x1ff4);
}
static void netcp_xgbe_serdes_pll_enable(void __iomem *serdes_regs)
{
netcp_xgbe_serdes_phyb_rst_clr(serdes_regs);
writel(0xee000000, serdes_regs + 0x1ff4);
}
static int netcp_xgbe_wait_pll_locked(void __iomem *sw_regs)
{
unsigned long timeout;
int ret = 0;
u32 val_1, val_0;
timeout = jiffies + msecs_to_jiffies(500);
do {
val_0 = (readl(sw_regs + XGBE_SGMII_1_OFFSET) & BIT(4));
val_1 = (readl(sw_regs + XGBE_SGMII_2_OFFSET) & BIT(4));
if (val_1 && val_0)
return 0;
if (time_after(jiffies, timeout)) {
ret = -ETIMEDOUT;
break;
}
cpu_relax();
} while (true);
pr_err("XGBE serdes not locked: time out.\n");
return ret;
}
static void netcp_xgbe_serdes_enable_xgmii_port(void __iomem *sw_regs)
{
writel(0x03, sw_regs + XGBE_CTRL_OFFSET);
}
static u32 netcp_xgbe_serdes_read_tbus_val(void __iomem *serdes_regs)
{
u32 tmp;
if (PHY_A(serdes_regs)) {
tmp = (readl(serdes_regs + 0x0ec) >> 24) & 0x0ff;
tmp |= ((readl(serdes_regs + 0x0fc) >> 16) & 0x00f00);
} else {
tmp = (readl(serdes_regs + 0x0f8) >> 16) & 0x0fff;
}
return tmp;
}
static void netcp_xgbe_serdes_write_tbus_addr(void __iomem *serdes_regs,
int select, int ofs)
{
if (PHY_A(serdes_regs)) {
reg_rmw(serdes_regs + 0x0008, ((select << 5) + ofs) << 24,
~0x00ffffff);
return;
}
/* For 2 lane Phy-B, lane0 is actually lane1 */
switch (select) {
case 1:
select = 2;
break;
case 2:
select = 3;
break;
default:
return;
}
reg_rmw(serdes_regs + 0x00fc, ((select << 8) + ofs) << 16, ~0xf800ffff);
}
static u32 netcp_xgbe_serdes_read_select_tbus(void __iomem *serdes_regs,
int select, int ofs)
{
/* Set tbus address */
netcp_xgbe_serdes_write_tbus_addr(serdes_regs, select, ofs);
/* Get TBUS Value */
return netcp_xgbe_serdes_read_tbus_val(serdes_regs);
}
static void netcp_xgbe_serdes_reset_cdr(void __iomem *serdes_regs,
void __iomem *sig_detect_reg, int lane)
{
u32 tmp, dlpf, tbus;
/*Get the DLPF values */
tmp = netcp_xgbe_serdes_read_select_tbus(
serdes_regs, lane + 1, 5);
dlpf = tmp >> 2;
if (dlpf < 400 || dlpf > 700) {
reg_rmw(sig_detect_reg, VAL_SH(2, 1), MASK_WID_SH(2, 1));
mdelay(1);
reg_rmw(sig_detect_reg, VAL_SH(0, 1), MASK_WID_SH(2, 1));
} else {
tbus = netcp_xgbe_serdes_read_select_tbus(serdes_regs, lane +
1, 0xe);
pr_debug("XGBE: CDR centered, DLPF: %4d,%d,%d.\n",
tmp >> 2, tmp & 3, (tbus >> 2) & 3);
}
}
/* Call every 100 ms */
static int netcp_xgbe_check_link_status(void __iomem *serdes_regs,
void __iomem *sw_regs, u32 lanes,
u32 *current_state, u32 *lane_down)
{
void __iomem *pcsr_base = sw_regs + 0x0600;
void __iomem *sig_detect_reg;
u32 pcsr_rx_stat, blk_lock, blk_errs;
int loss, i, status = 1;
for (i = 0; i < lanes; i++) {
/* Get the Loss bit */
loss = readl(serdes_regs + 0x1fc0 + 0x20 + (i * 0x04)) & 0x1;
/* Get Block Errors and Block Lock bits */
pcsr_rx_stat = readl(pcsr_base + 0x0c + (i * 0x80));
blk_lock = (pcsr_rx_stat >> 30) & 0x1;
blk_errs = (pcsr_rx_stat >> 16) & 0x0ff;
/* Get Signal Detect Overlay Address */
sig_detect_reg = serdes_regs + (i * 0x200) + 0x200 + 0x04;
/* If Block errors maxed out, attempt recovery! */
if (blk_errs == 0x0ff)
blk_lock = 0;
switch (current_state[i]) {
case 0:
/* if good link lock the signal detect ON! */
if (!loss && blk_lock) {
pr_debug("XGBE PCSR Linked Lane: %d\n", i);
reg_rmw(sig_detect_reg, VAL_SH(3, 1),
MASK_WID_SH(2, 1));
current_state[i] = 1;
} else if (!blk_lock) {
/* if no lock, then reset CDR */
pr_debug("XGBE PCSR Recover Lane: %d\n", i);
netcp_xgbe_serdes_reset_cdr(serdes_regs,
sig_detect_reg, i);
}
break;
case 1:
if (!blk_lock) {
/* Link Lost? */
lane_down[i] = 1;
current_state[i] = 2;
}
break;
case 2:
if (blk_lock)
/* Nope just noise */
current_state[i] = 1;
else {
/* Lost the block lock, reset CDR if it is
* not centered and go back to sync state
*/
netcp_xgbe_serdes_reset_cdr(serdes_regs,
sig_detect_reg, i);
current_state[i] = 0;
}
break;
default:
pr_err("XGBE: unknown current_state[%d] %d\n",
i, current_state[i]);
break;
}
if (blk_errs > 0) {
/* Reset the Error counts! */
reg_rmw(pcsr_base + 0x08 + (i * 0x80), VAL_SH(0x19, 0),
MASK_WID_SH(8, 0));
reg_rmw(pcsr_base + 0x08 + (i * 0x80), VAL_SH(0x00, 0),
MASK_WID_SH(8, 0));
}
status &= (current_state[i] == 1);
}
return status;
}
static int netcp_xgbe_serdes_check_lane(void __iomem *serdes_regs,
void __iomem *sw_regs)
{
u32 current_state[2] = {0, 0};
int retries = 0, link_up;
u32 lane_down[2];
do {
lane_down[0] = 0;
lane_down[1] = 0;
link_up = netcp_xgbe_check_link_status(serdes_regs, sw_regs, 2,
current_state,
lane_down);
/* if we did not get link up then wait 100ms before calling
* it again
*/
if (link_up)
break;
if (lane_down[0])
pr_debug("XGBE: detected link down on lane 0\n");
if (lane_down[1])
pr_debug("XGBE: detected link down on lane 1\n");
if (++retries > 1) {
pr_debug("XGBE: timeout waiting for serdes link up\n");
return -ETIMEDOUT;
}
mdelay(100);
} while (!link_up);
pr_debug("XGBE: PCSR link is up\n");
return 0;
}
static void netcp_xgbe_serdes_setup_cm_c1_c2(void __iomem *serdes_regs,
int lane, int cm, int c1, int c2)
{
int i;
for (i = 0; i < ARRAY_SIZE(cfg_cm_c1_c2); i++) {
reg_rmw(serdes_regs + cfg_cm_c1_c2[i].ofs + (0x200 * lane),
cfg_cm_c1_c2[i].val,
cfg_cm_c1_c2[i].mask);
}
}
static void netcp_xgbe_reset_serdes(void __iomem *serdes_regs)
{
/* Toggle the POR_EN bit in CONFIG.CPU_CTRL */
/* enable POR_EN bit */
reg_rmw(serdes_regs + PCSR_CPU_CTRL_OFFSET, POR_EN, POR_EN);
usleep_range(10, 100);
/* disable POR_EN bit */
reg_rmw(serdes_regs + PCSR_CPU_CTRL_OFFSET, 0, POR_EN);
usleep_range(10, 100);
}
static int netcp_xgbe_serdes_config(void __iomem *serdes_regs,
void __iomem *sw_regs)
{
u32 ret, i;
netcp_xgbe_serdes_pll_disable(serdes_regs);
netcp_xgbe_serdes_cmu_init(serdes_regs);
for (i = 0; i < 2; i++)
netcp_xgbe_serdes_lane_config(serdes_regs, i);
netcp_xgbe_serdes_com_enable(serdes_regs);
/* This is EVM + RTM-BOC specific */
for (i = 0; i < 2; i++)
netcp_xgbe_serdes_setup_cm_c1_c2(serdes_regs, i, 0, 0, 5);
netcp_xgbe_serdes_pll_enable(serdes_regs);
for (i = 0; i < 2; i++)
netcp_xgbe_serdes_lane_enable(serdes_regs, i);
/* SB PLL Status Poll */
ret = netcp_xgbe_wait_pll_locked(sw_regs);
if (ret)
return ret;
netcp_xgbe_serdes_enable_xgmii_port(sw_regs);
netcp_xgbe_serdes_check_lane(serdes_regs, sw_regs);
return ret;
}
int netcp_xgbe_serdes_init(void __iomem *serdes_regs, void __iomem *xgbe_regs)
{
u32 val;
/* read COMLANE bits 4:0 */
val = readl(serdes_regs + 0xa00);
if (val & 0x1f) {
pr_debug("XGBE: serdes already in operation - reset\n");
netcp_xgbe_reset_serdes(serdes_regs);
}
return netcp_xgbe_serdes_config(serdes_regs, xgbe_regs);
}
File diff suppressed because it is too large Load Diff
+544
View File
@@ -0,0 +1,544 @@
#ifndef TLAN_H
#define TLAN_H
/********************************************************************
*
* Linux ThunderLAN Driver
*
* tlan.h
* by James Banks
*
* (C) 1997-1998 Caldera, Inc.
* (C) 1999-2001 Torben Mathiasen
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
*
* Dec 10, 1999 Torben Mathiasen <torben.mathiasen@compaq.com>
* New Maintainer
*
********************************************************************/
#include <linux/io.h>
#include <linux/types.h>
#include <linux/netdevice.h>
/*****************************************************************
* TLan Definitions
*
****************************************************************/
#define TLAN_MIN_FRAME_SIZE 64
#define TLAN_MAX_FRAME_SIZE 1600
#define TLAN_NUM_RX_LISTS 32
#define TLAN_NUM_TX_LISTS 64
#define TLAN_IGNORE 0
#define TLAN_RECORD 1
#define TLAN_DBG(lvl, format, args...) \
do { \
if (debug&lvl) \
printk(KERN_DEBUG "TLAN: " format, ##args); \
} while (0)
#define TLAN_DEBUG_GNRL 0x0001
#define TLAN_DEBUG_TX 0x0002
#define TLAN_DEBUG_RX 0x0004
#define TLAN_DEBUG_LIST 0x0008
#define TLAN_DEBUG_PROBE 0x0010
#define TX_TIMEOUT (10*HZ) /* We need time for auto-neg */
#define MAX_TLAN_BOARDS 8 /* Max number of boards installed
at a time */
/*****************************************************************
* Device Identification Definitions
*
****************************************************************/
#define PCI_DEVICE_ID_NETELLIGENT_10_T2 0xB012
#define PCI_DEVICE_ID_NETELLIGENT_10_100_WS_5100 0xB030
#ifndef PCI_DEVICE_ID_OLICOM_OC2183
#define PCI_DEVICE_ID_OLICOM_OC2183 0x0013
#endif
#ifndef PCI_DEVICE_ID_OLICOM_OC2325
#define PCI_DEVICE_ID_OLICOM_OC2325 0x0012
#endif
#ifndef PCI_DEVICE_ID_OLICOM_OC2326
#define PCI_DEVICE_ID_OLICOM_OC2326 0x0014
#endif
struct tlan_adapter_entry {
u16 vendor_id;
u16 device_id;
char *device_label;
u32 flags;
u16 addr_ofs;
};
#define TLAN_ADAPTER_NONE 0x00000000
#define TLAN_ADAPTER_UNMANAGED_PHY 0x00000001
#define TLAN_ADAPTER_BIT_RATE_PHY 0x00000002
#define TLAN_ADAPTER_USE_INTERN_10 0x00000004
#define TLAN_ADAPTER_ACTIVITY_LED 0x00000008
#define TLAN_SPEED_DEFAULT 0
#define TLAN_SPEED_10 10
#define TLAN_SPEED_100 100
#define TLAN_DUPLEX_DEFAULT 0
#define TLAN_DUPLEX_HALF 1
#define TLAN_DUPLEX_FULL 2
/*****************************************************************
* EISA Definitions
*
****************************************************************/
#define EISA_ID 0xc80 /* EISA ID Registers */
#define EISA_ID0 0xc80 /* EISA ID Register 0 */
#define EISA_ID1 0xc81 /* EISA ID Register 1 */
#define EISA_ID2 0xc82 /* EISA ID Register 2 */
#define EISA_ID3 0xc83 /* EISA ID Register 3 */
#define EISA_CR 0xc84 /* EISA Control Register */
#define EISA_REG0 0xc88 /* EISA Configuration Register 0 */
#define EISA_REG1 0xc89 /* EISA Configuration Register 1 */
#define EISA_REG2 0xc8a /* EISA Configuration Register 2 */
#define EISA_REG3 0xc8f /* EISA Configuration Register 3 */
#define EISA_APROM 0xc90 /* Ethernet Address PROM */
/*****************************************************************
* Rx/Tx List Definitions
*
****************************************************************/
#define TLAN_BUFFERS_PER_LIST 10
#define TLAN_LAST_BUFFER 0x80000000
#define TLAN_CSTAT_UNUSED 0x8000
#define TLAN_CSTAT_FRM_CMP 0x4000
#define TLAN_CSTAT_READY 0x3000
#define TLAN_CSTAT_EOC 0x0800
#define TLAN_CSTAT_RX_ERROR 0x0400
#define TLAN_CSTAT_PASS_CRC 0x0200
#define TLAN_CSTAT_DP_PR 0x0100
struct tlan_buffer {
u32 count;
u32 address;
};
struct tlan_list {
u32 forward;
u16 c_stat;
u16 frame_size;
struct tlan_buffer buffer[TLAN_BUFFERS_PER_LIST];
};
typedef u8 TLanBuffer[TLAN_MAX_FRAME_SIZE];
/*****************************************************************
* PHY definitions
*
****************************************************************/
#define TLAN_PHY_MAX_ADDR 0x1F
#define TLAN_PHY_NONE 0x20
/*****************************************************************
* TLAN Private Information Structure
*
****************************************************************/
struct tlan_priv {
struct net_device *next_device;
struct pci_dev *pci_dev;
struct net_device *dev;
void *dma_storage;
dma_addr_t dma_storage_dma;
unsigned int dma_size;
u8 *pad_buffer;
struct tlan_list *rx_list;
dma_addr_t rx_list_dma;
u8 *rx_buffer;
dma_addr_t rx_buffer_dma;
u32 rx_head;
u32 rx_tail;
u32 rx_eoc_count;
struct tlan_list *tx_list;
dma_addr_t tx_list_dma;
u8 *tx_buffer;
dma_addr_t tx_buffer_dma;
u32 tx_head;
u32 tx_in_progress;
u32 tx_tail;
u32 tx_busy_count;
u32 phy_online;
u32 timer_set_at;
u32 timer_type;
struct timer_list timer;
struct timer_list media_timer;
struct board *adapter;
u32 adapter_rev;
u32 aui;
u32 debug;
u32 duplex;
u32 phy[2];
u32 phy_num;
u32 speed;
u8 tlan_rev;
u8 tlan_full_duplex;
spinlock_t lock;
struct work_struct tlan_tqueue;
};
/*****************************************************************
* TLan Driver Timer Definitions
*
****************************************************************/
#define TLAN_TIMER_ACTIVITY 2
#define TLAN_TIMER_PHY_PDOWN 3
#define TLAN_TIMER_PHY_PUP 4
#define TLAN_TIMER_PHY_RESET 5
#define TLAN_TIMER_PHY_START_LINK 6
#define TLAN_TIMER_PHY_FINISH_AN 7
#define TLAN_TIMER_FINISH_RESET 8
#define TLAN_TIMER_ACT_DELAY (HZ/10)
/*****************************************************************
* TLan Driver Eeprom Definitions
*
****************************************************************/
#define TLAN_EEPROM_ACK 0
#define TLAN_EEPROM_STOP 1
#define TLAN_EEPROM_SIZE 256
/*****************************************************************
* Host Register Offsets and Contents
*
****************************************************************/
#define TLAN_HOST_CMD 0x00
#define TLAN_HC_GO 0x80000000
#define TLAN_HC_STOP 0x40000000
#define TLAN_HC_ACK 0x20000000
#define TLAN_HC_CS_MASK 0x1FE00000
#define TLAN_HC_EOC 0x00100000
#define TLAN_HC_RT 0x00080000
#define TLAN_HC_NES 0x00040000
#define TLAN_HC_AD_RST 0x00008000
#define TLAN_HC_LD_TMR 0x00004000
#define TLAN_HC_LD_THR 0x00002000
#define TLAN_HC_REQ_INT 0x00001000
#define TLAN_HC_INT_OFF 0x00000800
#define TLAN_HC_INT_ON 0x00000400
#define TLAN_HC_AC_MASK 0x000000FF
#define TLAN_CH_PARM 0x04
#define TLAN_DIO_ADR 0x08
#define TLAN_DA_ADR_INC 0x8000
#define TLAN_DA_RAM_ADR 0x4000
#define TLAN_HOST_INT 0x0A
#define TLAN_HI_IV_MASK 0x1FE0
#define TLAN_HI_IT_MASK 0x001C
#define TLAN_DIO_DATA 0x0C
/* ThunderLAN Internal Register DIO Offsets */
#define TLAN_NET_CMD 0x00
#define TLAN_NET_CMD_NRESET 0x80
#define TLAN_NET_CMD_NWRAP 0x40
#define TLAN_NET_CMD_CSF 0x20
#define TLAN_NET_CMD_CAF 0x10
#define TLAN_NET_CMD_NOBRX 0x08
#define TLAN_NET_CMD_DUPLEX 0x04
#define TLAN_NET_CMD_TRFRAM 0x02
#define TLAN_NET_CMD_TXPACE 0x01
#define TLAN_NET_SIO 0x01
#define TLAN_NET_SIO_MINTEN 0x80
#define TLAN_NET_SIO_ECLOK 0x40
#define TLAN_NET_SIO_ETXEN 0x20
#define TLAN_NET_SIO_EDATA 0x10
#define TLAN_NET_SIO_NMRST 0x08
#define TLAN_NET_SIO_MCLK 0x04
#define TLAN_NET_SIO_MTXEN 0x02
#define TLAN_NET_SIO_MDATA 0x01
#define TLAN_NET_STS 0x02
#define TLAN_NET_STS_MIRQ 0x80
#define TLAN_NET_STS_HBEAT 0x40
#define TLAN_NET_STS_TXSTOP 0x20
#define TLAN_NET_STS_RXSTOP 0x10
#define TLAN_NET_STS_RSRVD 0x0F
#define TLAN_NET_MASK 0x03
#define TLAN_NET_MASK_MASK7 0x80
#define TLAN_NET_MASK_MASK6 0x40
#define TLAN_NET_MASK_MASK5 0x20
#define TLAN_NET_MASK_MASK4 0x10
#define TLAN_NET_MASK_RSRVD 0x0F
#define TLAN_NET_CONFIG 0x04
#define TLAN_NET_CFG_RCLK 0x8000
#define TLAN_NET_CFG_TCLK 0x4000
#define TLAN_NET_CFG_BIT 0x2000
#define TLAN_NET_CFG_RXCRC 0x1000
#define TLAN_NET_CFG_PEF 0x0800
#define TLAN_NET_CFG_1FRAG 0x0400
#define TLAN_NET_CFG_1CHAN 0x0200
#define TLAN_NET_CFG_MTEST 0x0100
#define TLAN_NET_CFG_PHY_EN 0x0080
#define TLAN_NET_CFG_MSMASK 0x007F
#define TLAN_MAN_TEST 0x06
#define TLAN_DEF_VENDOR_ID 0x08
#define TLAN_DEF_DEVICE_ID 0x0A
#define TLAN_DEF_REVISION 0x0C
#define TLAN_DEF_SUBCLASS 0x0D
#define TLAN_DEF_MIN_LAT 0x0E
#define TLAN_DEF_MAX_LAT 0x0F
#define TLAN_AREG_0 0x10
#define TLAN_AREG_1 0x16
#define TLAN_AREG_2 0x1C
#define TLAN_AREG_3 0x22
#define TLAN_HASH_1 0x28
#define TLAN_HASH_2 0x2C
#define TLAN_GOOD_TX_FRMS 0x30
#define TLAN_TX_UNDERUNS 0x33
#define TLAN_GOOD_RX_FRMS 0x34
#define TLAN_RX_OVERRUNS 0x37
#define TLAN_DEFERRED_TX 0x38
#define TLAN_CRC_ERRORS 0x3A
#define TLAN_CODE_ERRORS 0x3B
#define TLAN_MULTICOL_FRMS 0x3C
#define TLAN_SINGLECOL_FRMS 0x3E
#define TLAN_EXCESSCOL_FRMS 0x40
#define TLAN_LATE_COLS 0x41
#define TLAN_CARRIER_LOSS 0x42
#define TLAN_ACOMMIT 0x43
#define TLAN_LED_REG 0x44
#define TLAN_LED_ACT 0x10
#define TLAN_LED_LINK 0x01
#define TLAN_BSIZE_REG 0x45
#define TLAN_MAX_RX 0x46
#define TLAN_INT_DIS 0x48
#define TLAN_ID_TX_EOC 0x04
#define TLAN_ID_RX_EOF 0x02
#define TLAN_ID_RX_EOC 0x01
/* ThunderLAN Interrupt Codes */
#define TLAN_INT_NUMBER_OF_INTS 8
#define TLAN_INT_NONE 0x0000
#define TLAN_INT_TX_EOF 0x0001
#define TLAN_INT_STAT_OVERFLOW 0x0002
#define TLAN_INT_RX_EOF 0x0003
#define TLAN_INT_DUMMY 0x0004
#define TLAN_INT_TX_EOC 0x0005
#define TLAN_INT_STATUS_CHECK 0x0006
#define TLAN_INT_RX_EOC 0x0007
/* ThunderLAN MII Registers */
/* Generic MII/PHY Registers */
#define MII_GEN_CTL 0x00
#define MII_GC_RESET 0x8000
#define MII_GC_LOOPBK 0x4000
#define MII_GC_SPEEDSEL 0x2000
#define MII_GC_AUTOENB 0x1000
#define MII_GC_PDOWN 0x0800
#define MII_GC_ISOLATE 0x0400
#define MII_GC_AUTORSRT 0x0200
#define MII_GC_DUPLEX 0x0100
#define MII_GC_COLTEST 0x0080
#define MII_GC_RESERVED 0x007F
#define MII_GEN_STS 0x01
#define MII_GS_100BT4 0x8000
#define MII_GS_100BTXFD 0x4000
#define MII_GS_100BTXHD 0x2000
#define MII_GS_10BTFD 0x1000
#define MII_GS_10BTHD 0x0800
#define MII_GS_RESERVED 0x07C0
#define MII_GS_AUTOCMPLT 0x0020
#define MII_GS_RFLT 0x0010
#define MII_GS_AUTONEG 0x0008
#define MII_GS_LINK 0x0004
#define MII_GS_JABBER 0x0002
#define MII_GS_EXTCAP 0x0001
#define MII_GEN_ID_HI 0x02
#define MII_GEN_ID_LO 0x03
#define MII_GIL_OUI 0xFC00
#define MII_GIL_MODEL 0x03F0
#define MII_GIL_REVISION 0x000F
#define MII_AN_ADV 0x04
#define MII_AN_LPA 0x05
#define MII_AN_EXP 0x06
/* ThunderLAN Specific MII/PHY Registers */
#define TLAN_TLPHY_ID 0x10
#define TLAN_TLPHY_CTL 0x11
#define TLAN_TC_IGLINK 0x8000
#define TLAN_TC_SWAPOL 0x4000
#define TLAN_TC_AUISEL 0x2000
#define TLAN_TC_SQEEN 0x1000
#define TLAN_TC_MTEST 0x0800
#define TLAN_TC_RESERVED 0x07F8
#define TLAN_TC_NFEW 0x0004
#define TLAN_TC_INTEN 0x0002
#define TLAN_TC_TINT 0x0001
#define TLAN_TLPHY_STS 0x12
#define TLAN_TS_MINT 0x8000
#define TLAN_TS_PHOK 0x4000
#define TLAN_TS_POLOK 0x2000
#define TLAN_TS_TPENERGY 0x1000
#define TLAN_TS_RESERVED 0x0FFF
#define TLAN_TLPHY_PAR 0x19
#define TLAN_PHY_CIM_STAT 0x0020
#define TLAN_PHY_SPEED_100 0x0040
#define TLAN_PHY_DUPLEX_FULL 0x0080
#define TLAN_PHY_AN_EN_STAT 0x0400
/* National Sem. & Level1 PHY id's */
#define NAT_SEM_ID1 0x2000
#define NAT_SEM_ID2 0x5C01
#define LEVEL1_ID1 0x7810
#define LEVEL1_ID2 0x0000
#define CIRC_INC(a, b) if (++a >= b) a = 0
/* Routines to access internal registers. */
static inline u8 tlan_dio_read8(u16 base_addr, u16 internal_addr)
{
outw(internal_addr, base_addr + TLAN_DIO_ADR);
return inb((base_addr + TLAN_DIO_DATA) + (internal_addr & 0x3));
}
static inline u16 tlan_dio_read16(u16 base_addr, u16 internal_addr)
{
outw(internal_addr, base_addr + TLAN_DIO_ADR);
return inw((base_addr + TLAN_DIO_DATA) + (internal_addr & 0x2));
}
static inline u32 tlan_dio_read32(u16 base_addr, u16 internal_addr)
{
outw(internal_addr, base_addr + TLAN_DIO_ADR);
return inl(base_addr + TLAN_DIO_DATA);
}
static inline void tlan_dio_write8(u16 base_addr, u16 internal_addr, u8 data)
{
outw(internal_addr, base_addr + TLAN_DIO_ADR);
outb(data, base_addr + TLAN_DIO_DATA + (internal_addr & 0x3));
}
static inline void tlan_dio_write16(u16 base_addr, u16 internal_addr, u16 data)
{
outw(internal_addr, base_addr + TLAN_DIO_ADR);
outw(data, base_addr + TLAN_DIO_DATA + (internal_addr & 0x2));
}
static inline void tlan_dio_write32(u16 base_addr, u16 internal_addr, u32 data)
{
outw(internal_addr, base_addr + TLAN_DIO_ADR);
outl(data, base_addr + TLAN_DIO_DATA + (internal_addr & 0x2));
}
#define tlan_clear_bit(bit, port) outb_p(inb_p(port) & ~bit, port)
#define tlan_get_bit(bit, port) ((int) (inb_p(port) & bit))
#define tlan_set_bit(bit, port) outb_p(inb_p(port) | bit, port)
/*
* given 6 bytes, view them as 8 6-bit numbers and return the XOR of those
* the code below is about seven times as fast as the original code
*
* The original code was:
*
* u32 xor(u32 a, u32 b) { return ((a && !b ) || (! a && b )); }
*
* #define XOR8(a, b, c, d, e, f, g, h) \
* xor(a, xor(b, xor(c, xor(d, xor(e, xor(f, xor(g, h)) ) ) ) ) )
* #define DA(a, bit) (( (u8) a[bit/8] ) & ( (u8) (1 << bit%8)) )
*
* hash = XOR8(DA(a,0), DA(a, 6), DA(a,12), DA(a,18), DA(a,24),
* DA(a,30), DA(a,36), DA(a,42));
* hash |= XOR8(DA(a,1), DA(a, 7), DA(a,13), DA(a,19), DA(a,25),
* DA(a,31), DA(a,37), DA(a,43)) << 1;
* hash |= XOR8(DA(a,2), DA(a, 8), DA(a,14), DA(a,20), DA(a,26),
* DA(a,32), DA(a,38), DA(a,44)) << 2;
* hash |= XOR8(DA(a,3), DA(a, 9), DA(a,15), DA(a,21), DA(a,27),
* DA(a,33), DA(a,39), DA(a,45)) << 3;
* hash |= XOR8(DA(a,4), DA(a,10), DA(a,16), DA(a,22), DA(a,28),
* DA(a,34), DA(a,40), DA(a,46)) << 4;
* hash |= XOR8(DA(a,5), DA(a,11), DA(a,17), DA(a,23), DA(a,29),
* DA(a,35), DA(a,41), DA(a,47)) << 5;
*
*/
static inline u32 tlan_hash_func(const u8 *a)
{
u8 hash;
hash = (a[0]^a[3]); /* & 077 */
hash ^= ((a[0]^a[3])>>6); /* & 003 */
hash ^= ((a[1]^a[4])<<2); /* & 074 */
hash ^= ((a[1]^a[4])>>4); /* & 017 */
hash ^= ((a[2]^a[5])<<4); /* & 060 */
hash ^= ((a[2]^a[5])>>2); /* & 077 */
return hash & 077;
}
#endif