diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/net/ethernet/microchip/sparx5/sparx5_port.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'drivers/net/ethernet/microchip/sparx5/sparx5_port.c')
-rw-r--r-- | drivers/net/ethernet/microchip/sparx5/sparx5_port.c | 1347 |
1 files changed, 1347 insertions, 0 deletions
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_port.c b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c new file mode 100644 index 000000000..3a1b1a1f5 --- /dev/null +++ b/drivers/net/ethernet/microchip/sparx5/sparx5_port.c @@ -0,0 +1,1347 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Microchip Sparx5 Switch driver + * + * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. + */ + +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <net/dcbnl.h> + +#include "sparx5_main_regs.h" +#include "sparx5_main.h" +#include "sparx5_port.h" + +#define SPX5_ETYPE_TAG_C 0x8100 +#define SPX5_ETYPE_TAG_S 0x88a8 + +#define SPX5_WAIT_US 1000 +#define SPX5_WAIT_MAX_US 2000 + +enum port_error { + SPX5_PERR_SPEED, + SPX5_PERR_IFTYPE, +}; + +#define PAUSE_DISCARD 0xC +#define ETH_MAXLEN (ETH_DATA_LEN + ETH_HLEN + ETH_FCS_LEN) + +static void decode_sgmii_word(u16 lp_abil, struct sparx5_port_status *status) +{ + status->an_complete = true; + if (!(lp_abil & LPA_SGMII_LINK)) { + status->link = false; + return; + } + + switch (lp_abil & LPA_SGMII_SPD_MASK) { + case LPA_SGMII_10: + status->speed = SPEED_10; + break; + case LPA_SGMII_100: + status->speed = SPEED_100; + break; + case LPA_SGMII_1000: + status->speed = SPEED_1000; + break; + default: + status->link = false; + return; + } + if (lp_abil & LPA_SGMII_FULL_DUPLEX) + status->duplex = DUPLEX_FULL; + else + status->duplex = DUPLEX_HALF; +} + +static void decode_cl37_word(u16 lp_abil, uint16_t ld_abil, struct sparx5_port_status *status) +{ + status->link = !(lp_abil & ADVERTISE_RFAULT) && status->link; + status->an_complete = true; + status->duplex = (ADVERTISE_1000XFULL & lp_abil) ? + DUPLEX_FULL : DUPLEX_UNKNOWN; // 1G HDX not supported + + if ((ld_abil & ADVERTISE_1000XPAUSE) && + (lp_abil & ADVERTISE_1000XPAUSE)) { + status->pause = MLO_PAUSE_RX | MLO_PAUSE_TX; + } else if ((ld_abil & ADVERTISE_1000XPSE_ASYM) && + (lp_abil & ADVERTISE_1000XPSE_ASYM)) { + status->pause |= (lp_abil & ADVERTISE_1000XPAUSE) ? + MLO_PAUSE_TX : 0; + status->pause |= (ld_abil & ADVERTISE_1000XPAUSE) ? + MLO_PAUSE_RX : 0; + } else { + status->pause = MLO_PAUSE_NONE; + } +} + +static int sparx5_get_dev2g5_status(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_status *status) +{ + u32 portno = port->portno; + u16 lp_adv, ld_adv; + u32 value; + + /* Get PCS Link down sticky */ + value = spx5_rd(sparx5, DEV2G5_PCS1G_STICKY(portno)); + status->link_down = DEV2G5_PCS1G_STICKY_LINK_DOWN_STICKY_GET(value); + if (status->link_down) /* Clear the sticky */ + spx5_wr(value, sparx5, DEV2G5_PCS1G_STICKY(portno)); + + /* Get both current Link and Sync status */ + value = spx5_rd(sparx5, DEV2G5_PCS1G_LINK_STATUS(portno)); + status->link = DEV2G5_PCS1G_LINK_STATUS_LINK_STATUS_GET(value) && + DEV2G5_PCS1G_LINK_STATUS_SYNC_STATUS_GET(value); + + if (port->conf.portmode == PHY_INTERFACE_MODE_1000BASEX) + status->speed = SPEED_1000; + else if (port->conf.portmode == PHY_INTERFACE_MODE_2500BASEX) + status->speed = SPEED_2500; + + status->duplex = DUPLEX_FULL; + + /* Get PCS ANEG status register */ + value = spx5_rd(sparx5, DEV2G5_PCS1G_ANEG_STATUS(portno)); + + /* Aneg complete provides more information */ + if (DEV2G5_PCS1G_ANEG_STATUS_ANEG_COMPLETE_GET(value)) { + lp_adv = DEV2G5_PCS1G_ANEG_STATUS_LP_ADV_ABILITY_GET(value); + if (port->conf.portmode == PHY_INTERFACE_MODE_SGMII) { + decode_sgmii_word(lp_adv, status); + } else { + value = spx5_rd(sparx5, DEV2G5_PCS1G_ANEG_CFG(portno)); + ld_adv = DEV2G5_PCS1G_ANEG_CFG_ADV_ABILITY_GET(value); + decode_cl37_word(lp_adv, ld_adv, status); + } + } + return 0; +} + +static int sparx5_get_sfi_status(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_status *status) +{ + bool high_speed_dev = sparx5_is_baser(port->conf.portmode); + u32 portno = port->portno; + u32 value, dev, tinst; + void __iomem *inst; + + if (!high_speed_dev) { + netdev_err(port->ndev, "error: low speed and SFI mode\n"); + return -EINVAL; + } + + dev = sparx5_to_high_dev(portno); + tinst = sparx5_port_dev_index(portno); + inst = spx5_inst_get(sparx5, dev, tinst); + + value = spx5_inst_rd(inst, DEV10G_MAC_TX_MONITOR_STICKY(0)); + if (value != DEV10G_MAC_TX_MONITOR_STICKY_IDLE_STATE_STICKY) { + /* The link is or has been down. Clear the sticky bit */ + status->link_down = 1; + spx5_inst_wr(0xffffffff, inst, DEV10G_MAC_TX_MONITOR_STICKY(0)); + value = spx5_inst_rd(inst, DEV10G_MAC_TX_MONITOR_STICKY(0)); + } + status->link = (value == DEV10G_MAC_TX_MONITOR_STICKY_IDLE_STATE_STICKY); + status->duplex = DUPLEX_FULL; + if (port->conf.portmode == PHY_INTERFACE_MODE_5GBASER) + status->speed = SPEED_5000; + else if (port->conf.portmode == PHY_INTERFACE_MODE_10GBASER) + status->speed = SPEED_10000; + else + status->speed = SPEED_25000; + + return 0; +} + +/* Get link status of 1000Base-X/in-band and SFI ports. + */ +int sparx5_get_port_status(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_status *status) +{ + memset(status, 0, sizeof(*status)); + status->speed = port->conf.speed; + if (port->conf.power_down) { + status->link = false; + return 0; + } + switch (port->conf.portmode) { + case PHY_INTERFACE_MODE_SGMII: + case PHY_INTERFACE_MODE_QSGMII: + case PHY_INTERFACE_MODE_1000BASEX: + case PHY_INTERFACE_MODE_2500BASEX: + return sparx5_get_dev2g5_status(sparx5, port, status); + case PHY_INTERFACE_MODE_5GBASER: + case PHY_INTERFACE_MODE_10GBASER: + case PHY_INTERFACE_MODE_25GBASER: + return sparx5_get_sfi_status(sparx5, port, status); + case PHY_INTERFACE_MODE_NA: + return 0; + default: + netdev_err(port->ndev, "Status not supported"); + return -ENODEV; + } + return 0; +} + +static int sparx5_port_error(struct sparx5_port *port, + struct sparx5_port_config *conf, + enum port_error errtype) +{ + switch (errtype) { + case SPX5_PERR_SPEED: + netdev_err(port->ndev, + "Interface does not support speed: %u: for %s\n", + conf->speed, phy_modes(conf->portmode)); + break; + case SPX5_PERR_IFTYPE: + netdev_err(port->ndev, + "Switch port does not support interface type: %s\n", + phy_modes(conf->portmode)); + break; + default: + netdev_err(port->ndev, + "Interface configuration error\n"); + } + + return -EINVAL; +} + +static int sparx5_port_verify_speed(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + if ((sparx5_port_is_2g5(port->portno) && + conf->speed > SPEED_2500) || + (sparx5_port_is_5g(port->portno) && + conf->speed > SPEED_5000) || + (sparx5_port_is_10g(port->portno) && + conf->speed > SPEED_10000)) + return sparx5_port_error(port, conf, SPX5_PERR_SPEED); + + switch (conf->portmode) { + case PHY_INTERFACE_MODE_NA: + return -EINVAL; + case PHY_INTERFACE_MODE_1000BASEX: + if (conf->speed != SPEED_1000 || + sparx5_port_is_2g5(port->portno)) + return sparx5_port_error(port, conf, SPX5_PERR_SPEED); + if (sparx5_port_is_2g5(port->portno)) + return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE); + break; + case PHY_INTERFACE_MODE_2500BASEX: + if (conf->speed != SPEED_2500 || + sparx5_port_is_2g5(port->portno)) + return sparx5_port_error(port, conf, SPX5_PERR_SPEED); + break; + case PHY_INTERFACE_MODE_QSGMII: + if (port->portno > 47) + return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE); + fallthrough; + case PHY_INTERFACE_MODE_SGMII: + if (conf->speed != SPEED_1000 && + conf->speed != SPEED_100 && + conf->speed != SPEED_10 && + conf->speed != SPEED_2500) + return sparx5_port_error(port, conf, SPX5_PERR_SPEED); + break; + case PHY_INTERFACE_MODE_5GBASER: + case PHY_INTERFACE_MODE_10GBASER: + case PHY_INTERFACE_MODE_25GBASER: + if ((conf->speed != SPEED_5000 && + conf->speed != SPEED_10000 && + conf->speed != SPEED_25000)) + return sparx5_port_error(port, conf, SPX5_PERR_SPEED); + break; + default: + return sparx5_port_error(port, conf, SPX5_PERR_IFTYPE); + } + return 0; +} + +static bool sparx5_dev_change(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + return sparx5_is_baser(port->conf.portmode) ^ + sparx5_is_baser(conf->portmode); +} + +static int sparx5_port_flush_poll(struct sparx5 *sparx5, u32 portno) +{ + u32 value, resource, prio, delay_cnt = 0; + bool poll_src = true; + char *mem = ""; + + /* Resource == 0: Memory tracked per source (SRC-MEM) + * Resource == 1: Frame references tracked per source (SRC-REF) + * Resource == 2: Memory tracked per destination (DST-MEM) + * Resource == 3: Frame references tracked per destination. (DST-REF) + */ + while (1) { + bool empty = true; + + for (resource = 0; resource < (poll_src ? 2 : 1); resource++) { + u32 base; + + base = (resource == 0 ? 2048 : 0) + SPX5_PRIOS * portno; + for (prio = 0; prio < SPX5_PRIOS; prio++) { + value = spx5_rd(sparx5, + QRES_RES_STAT(base + prio)); + if (value) { + mem = resource == 0 ? + "DST-MEM" : "SRC-MEM"; + empty = false; + } + } + } + + if (empty) + break; + + if (delay_cnt++ == 2000) { + dev_err(sparx5->dev, + "Flush timeout port %u. %s queue not empty\n", + portno, mem); + return -EINVAL; + } + + usleep_range(SPX5_WAIT_US, SPX5_WAIT_MAX_US); + } + return 0; +} + +static int sparx5_port_disable(struct sparx5 *sparx5, struct sparx5_port *port, bool high_spd_dev) +{ + u32 tinst = high_spd_dev ? + sparx5_port_dev_index(port->portno) : port->portno; + u32 dev = high_spd_dev ? + sparx5_to_high_dev(port->portno) : TARGET_DEV2G5; + void __iomem *devinst = spx5_inst_get(sparx5, dev, tinst); + u32 spd = port->conf.speed; + u32 spd_prm; + int err; + + if (high_spd_dev) { + /* 1: Reset the PCS Rx clock domain */ + spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_RX_RST, + DEV10G_DEV_RST_CTRL_PCS_RX_RST, + devinst, + DEV10G_DEV_RST_CTRL(0)); + + /* 2: Disable MAC frame reception */ + spx5_inst_rmw(0, + DEV10G_MAC_ENA_CFG_RX_ENA, + devinst, + DEV10G_MAC_ENA_CFG(0)); + } else { + /* 1: Reset the PCS Rx clock domain */ + spx5_inst_rmw(DEV2G5_DEV_RST_CTRL_PCS_RX_RST, + DEV2G5_DEV_RST_CTRL_PCS_RX_RST, + devinst, + DEV2G5_DEV_RST_CTRL(0)); + /* 2: Disable MAC frame reception */ + spx5_inst_rmw(0, + DEV2G5_MAC_ENA_CFG_RX_ENA, + devinst, + DEV2G5_MAC_ENA_CFG(0)); + } + /* 3: Disable traffic being sent to or from switch port->portno */ + spx5_rmw(0, + QFWD_SWITCH_PORT_MODE_PORT_ENA, + sparx5, + QFWD_SWITCH_PORT_MODE(port->portno)); + + /* 4: Disable dequeuing from the egress queues */ + spx5_rmw(HSCH_PORT_MODE_DEQUEUE_DIS, + HSCH_PORT_MODE_DEQUEUE_DIS, + sparx5, + HSCH_PORT_MODE(port->portno)); + + /* 5: Disable Flowcontrol */ + spx5_rmw(QSYS_PAUSE_CFG_PAUSE_STOP_SET(0xFFF - 1), + QSYS_PAUSE_CFG_PAUSE_STOP, + sparx5, + QSYS_PAUSE_CFG(port->portno)); + + spd_prm = spd == SPEED_10 ? 1000 : spd == SPEED_100 ? 100 : 10; + /* 6: Wait while the last frame is exiting the queues */ + usleep_range(8 * spd_prm, 10 * spd_prm); + + /* 7: Flush the queues accociated with the port->portno */ + spx5_rmw(HSCH_FLUSH_CTRL_FLUSH_PORT_SET(port->portno) | + HSCH_FLUSH_CTRL_FLUSH_DST_SET(1) | + HSCH_FLUSH_CTRL_FLUSH_SRC_SET(1) | + HSCH_FLUSH_CTRL_FLUSH_ENA_SET(1), + HSCH_FLUSH_CTRL_FLUSH_PORT | + HSCH_FLUSH_CTRL_FLUSH_DST | + HSCH_FLUSH_CTRL_FLUSH_SRC | + HSCH_FLUSH_CTRL_FLUSH_ENA, + sparx5, + HSCH_FLUSH_CTRL); + + /* 8: Enable dequeuing from the egress queues */ + spx5_rmw(0, + HSCH_PORT_MODE_DEQUEUE_DIS, + sparx5, + HSCH_PORT_MODE(port->portno)); + + /* 9: Wait until flushing is complete */ + err = sparx5_port_flush_poll(sparx5, port->portno); + if (err) + return err; + + /* 10: Reset the MAC clock domain */ + if (high_spd_dev) { + spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_TX_RST_SET(1) | + DEV10G_DEV_RST_CTRL_MAC_RX_RST_SET(1) | + DEV10G_DEV_RST_CTRL_MAC_TX_RST_SET(1), + DEV10G_DEV_RST_CTRL_PCS_TX_RST | + DEV10G_DEV_RST_CTRL_MAC_RX_RST | + DEV10G_DEV_RST_CTRL_MAC_TX_RST, + devinst, + DEV10G_DEV_RST_CTRL(0)); + + } else { + spx5_inst_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(3) | + DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(1) | + DEV2G5_DEV_RST_CTRL_PCS_RX_RST_SET(1) | + DEV2G5_DEV_RST_CTRL_MAC_TX_RST_SET(1) | + DEV2G5_DEV_RST_CTRL_MAC_RX_RST_SET(1), + DEV2G5_DEV_RST_CTRL_SPEED_SEL | + DEV2G5_DEV_RST_CTRL_PCS_TX_RST | + DEV2G5_DEV_RST_CTRL_PCS_RX_RST | + DEV2G5_DEV_RST_CTRL_MAC_TX_RST | + DEV2G5_DEV_RST_CTRL_MAC_RX_RST, + devinst, + DEV2G5_DEV_RST_CTRL(0)); + } + /* 11: Clear flushing */ + spx5_rmw(HSCH_FLUSH_CTRL_FLUSH_PORT_SET(port->portno) | + HSCH_FLUSH_CTRL_FLUSH_ENA_SET(0), + HSCH_FLUSH_CTRL_FLUSH_PORT | + HSCH_FLUSH_CTRL_FLUSH_ENA, + sparx5, + HSCH_FLUSH_CTRL); + + if (high_spd_dev) { + u32 pcs = sparx5_to_pcs_dev(port->portno); + void __iomem *pcsinst = spx5_inst_get(sparx5, pcs, tinst); + + /* 12: Disable 5G/10G/25 BaseR PCS */ + spx5_inst_rmw(PCS10G_BR_PCS_CFG_PCS_ENA_SET(0), + PCS10G_BR_PCS_CFG_PCS_ENA, + pcsinst, + PCS10G_BR_PCS_CFG(0)); + + if (sparx5_port_is_25g(port->portno)) + /* Disable 25G PCS */ + spx5_rmw(DEV25G_PCS25G_CFG_PCS25G_ENA_SET(0), + DEV25G_PCS25G_CFG_PCS25G_ENA, + sparx5, + DEV25G_PCS25G_CFG(tinst)); + } else { + /* 12: Disable 1G PCS */ + spx5_rmw(DEV2G5_PCS1G_CFG_PCS_ENA_SET(0), + DEV2G5_PCS1G_CFG_PCS_ENA, + sparx5, + DEV2G5_PCS1G_CFG(port->portno)); + } + + /* The port is now flushed and disabled */ + return 0; +} + +static int sparx5_port_fifo_sz(struct sparx5 *sparx5, + u32 portno, u32 speed) +{ + u32 sys_clk = sparx5_clk_period(sparx5->coreclock); + const u32 taxi_dist[SPX5_PORTS_ALL] = { + 6, 8, 10, 6, 8, 10, 6, 8, 10, 6, 8, 10, + 4, 4, 4, 4, + 11, 12, 13, 14, 15, 16, 17, 18, + 11, 12, 13, 14, 15, 16, 17, 18, + 11, 12, 13, 14, 15, 16, 17, 18, + 11, 12, 13, 14, 15, 16, 17, 18, + 4, 6, 8, 4, 6, 8, 6, 8, + 2, 2, 2, 2, 2, 2, 2, 4, 2 + }; + u32 mac_per = 6400, tmp1, tmp2, tmp3; + u32 fifo_width = 16; + u32 mac_width = 8; + u32 addition = 0; + + switch (speed) { + case SPEED_25000: + return 0; + case SPEED_10000: + mac_per = 6400; + mac_width = 8; + addition = 1; + break; + case SPEED_5000: + mac_per = 12800; + mac_width = 8; + addition = 0; + break; + case SPEED_2500: + mac_per = 3200; + mac_width = 1; + addition = 0; + break; + case SPEED_1000: + mac_per = 8000; + mac_width = 1; + addition = 0; + break; + case SPEED_100: + case SPEED_10: + return 1; + default: + break; + } + + tmp1 = 1000 * mac_width / fifo_width; + tmp2 = 3000 + ((12000 + 2 * taxi_dist[portno] * 1000) + * sys_clk / mac_per); + tmp3 = tmp1 * tmp2 / 1000; + return (tmp3 + 2000 + 999) / 1000 + addition; +} + +/* Configure port muxing: + * QSGMII: 4x2G5 devices + */ +static int sparx5_port_mux_set(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + u32 portno = port->portno; + u32 inst; + + if (port->conf.portmode == conf->portmode) + return 0; /* Nothing to do */ + + switch (conf->portmode) { + case PHY_INTERFACE_MODE_QSGMII: /* QSGMII: 4x2G5 devices. Mode Q' */ + inst = (portno - portno % 4) / 4; + spx5_rmw(BIT(inst), + BIT(inst), + sparx5, + PORT_CONF_QSGMII_ENA); + + if ((portno / 4 % 2) == 0) { + /* Affects d0-d3,d8-d11..d40-d43 */ + spx5_rmw(PORT_CONF_USGMII_CFG_BYPASS_SCRAM_SET(1) | + PORT_CONF_USGMII_CFG_BYPASS_DESCRAM_SET(1) | + PORT_CONF_USGMII_CFG_QUAD_MODE_SET(1), + PORT_CONF_USGMII_CFG_BYPASS_SCRAM | + PORT_CONF_USGMII_CFG_BYPASS_DESCRAM | + PORT_CONF_USGMII_CFG_QUAD_MODE, + sparx5, + PORT_CONF_USGMII_CFG((portno / 8))); + } + break; + default: + break; + } + return 0; +} + +static int sparx5_port_max_tags_set(struct sparx5 *sparx5, + struct sparx5_port *port) +{ + enum sparx5_port_max_tags max_tags = port->max_vlan_tags; + int tag_ct = max_tags == SPX5_PORT_MAX_TAGS_ONE ? 1 : + max_tags == SPX5_PORT_MAX_TAGS_TWO ? 2 : 0; + bool dtag = max_tags == SPX5_PORT_MAX_TAGS_TWO; + enum sparx5_vlan_port_type vlan_type = port->vlan_type; + bool dotag = max_tags != SPX5_PORT_MAX_TAGS_NONE; + u32 dev = sparx5_to_high_dev(port->portno); + u32 tinst = sparx5_port_dev_index(port->portno); + void __iomem *inst = spx5_inst_get(sparx5, dev, tinst); + u32 etype; + + etype = (vlan_type == SPX5_VLAN_PORT_TYPE_S_CUSTOM ? + port->custom_etype : + vlan_type == SPX5_VLAN_PORT_TYPE_C ? + SPX5_ETYPE_TAG_C : SPX5_ETYPE_TAG_S); + + spx5_wr(DEV2G5_MAC_TAGS_CFG_TAG_ID_SET(etype) | + DEV2G5_MAC_TAGS_CFG_PB_ENA_SET(dtag) | + DEV2G5_MAC_TAGS_CFG_VLAN_AWR_ENA_SET(dotag) | + DEV2G5_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA_SET(dotag), + sparx5, + DEV2G5_MAC_TAGS_CFG(port->portno)); + + if (sparx5_port_is_2g5(port->portno)) + return 0; + + spx5_inst_rmw(DEV10G_MAC_TAGS_CFG_TAG_ID_SET(etype) | + DEV10G_MAC_TAGS_CFG_TAG_ENA_SET(dotag), + DEV10G_MAC_TAGS_CFG_TAG_ID | + DEV10G_MAC_TAGS_CFG_TAG_ENA, + inst, + DEV10G_MAC_TAGS_CFG(0, 0)); + + spx5_inst_rmw(DEV10G_MAC_NUM_TAGS_CFG_NUM_TAGS_SET(tag_ct), + DEV10G_MAC_NUM_TAGS_CFG_NUM_TAGS, + inst, + DEV10G_MAC_NUM_TAGS_CFG(0)); + + spx5_inst_rmw(DEV10G_MAC_MAXLEN_CFG_MAX_LEN_TAG_CHK_SET(dotag), + DEV10G_MAC_MAXLEN_CFG_MAX_LEN_TAG_CHK, + inst, + DEV10G_MAC_MAXLEN_CFG(0)); + return 0; +} + +int sparx5_port_fwd_urg(struct sparx5 *sparx5, u32 speed) +{ + u32 clk_period_ps = 1600; /* 625Mhz for now */ + u32 urg = 672000; + + switch (speed) { + case SPEED_10: + case SPEED_100: + case SPEED_1000: + urg = 672000; + break; + case SPEED_2500: + urg = 270000; + break; + case SPEED_5000: + urg = 135000; + break; + case SPEED_10000: + urg = 67200; + break; + case SPEED_25000: + urg = 27000; + break; + } + return urg / clk_period_ps - 1; +} + +static u16 sparx5_wm_enc(u16 value) +{ + if (value >= 2048) + return 2048 + value / 16; + + return value; +} + +static int sparx5_port_fc_setup(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + bool fc_obey = conf->pause & MLO_PAUSE_RX ? 1 : 0; + u32 pause_stop = 0xFFF - 1; /* FC gen disabled */ + + if (conf->pause & MLO_PAUSE_TX) + pause_stop = sparx5_wm_enc(4 * (ETH_MAXLEN / + SPX5_BUFFER_CELL_SZ)); + + /* Set HDX flowcontrol */ + spx5_rmw(DSM_MAC_CFG_HDX_BACKPREASSURE_SET(conf->duplex == DUPLEX_HALF), + DSM_MAC_CFG_HDX_BACKPREASSURE, + sparx5, + DSM_MAC_CFG(port->portno)); + + /* Obey flowcontrol */ + spx5_rmw(DSM_RX_PAUSE_CFG_RX_PAUSE_EN_SET(fc_obey), + DSM_RX_PAUSE_CFG_RX_PAUSE_EN, + sparx5, + DSM_RX_PAUSE_CFG(port->portno)); + + /* Disable forward pressure */ + spx5_rmw(QSYS_FWD_PRESSURE_FWD_PRESSURE_DIS_SET(fc_obey), + QSYS_FWD_PRESSURE_FWD_PRESSURE_DIS, + sparx5, + QSYS_FWD_PRESSURE(port->portno)); + + /* Generate pause frames */ + spx5_rmw(QSYS_PAUSE_CFG_PAUSE_STOP_SET(pause_stop), + QSYS_PAUSE_CFG_PAUSE_STOP, + sparx5, + QSYS_PAUSE_CFG(port->portno)); + + return 0; +} + +static u16 sparx5_get_aneg_word(struct sparx5_port_config *conf) +{ + if (conf->portmode == PHY_INTERFACE_MODE_1000BASEX) /* cl-37 aneg */ + return (conf->pause_adv | ADVERTISE_LPACK | ADVERTISE_1000XFULL); + else + return 1; /* Enable SGMII Aneg */ +} + +int sparx5_serdes_set(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + int portmode, err, speed = conf->speed; + + if (conf->portmode == PHY_INTERFACE_MODE_QSGMII && + ((port->portno % 4) != 0)) { + return 0; + } + if (sparx5_is_baser(conf->portmode)) { + if (conf->portmode == PHY_INTERFACE_MODE_25GBASER) + speed = SPEED_25000; + else if (conf->portmode == PHY_INTERFACE_MODE_10GBASER) + speed = SPEED_10000; + else + speed = SPEED_5000; + } + + err = phy_set_media(port->serdes, conf->media); + if (err) + return err; + if (speed > 0) { + err = phy_set_speed(port->serdes, speed); + if (err) + return err; + } + if (conf->serdes_reset) { + err = phy_reset(port->serdes); + if (err) + return err; + } + + /* Configure SerDes with port parameters + * For BaseR, the serdes driver supports 10GGBASE-R and speed 5G/10G/25G + */ + portmode = conf->portmode; + if (sparx5_is_baser(conf->portmode)) + portmode = PHY_INTERFACE_MODE_10GBASER; + err = phy_set_mode_ext(port->serdes, PHY_MODE_ETHERNET, portmode); + if (err) + return err; + conf->serdes_reset = false; + return err; +} + +static int sparx5_port_pcs_low_set(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + bool sgmii = false, inband_aneg = false; + int err; + + if (port->conf.inband) { + if (conf->portmode == PHY_INTERFACE_MODE_SGMII || + conf->portmode == PHY_INTERFACE_MODE_QSGMII) + inband_aneg = true; /* Cisco-SGMII in-band-aneg */ + else if (conf->portmode == PHY_INTERFACE_MODE_1000BASEX && + conf->autoneg) + inband_aneg = true; /* Clause-37 in-band-aneg */ + + err = sparx5_serdes_set(sparx5, port, conf); + if (err) + return -EINVAL; + } else { + sgmii = true; /* Phy is connected to the MAC */ + } + + /* Choose SGMII or 1000BaseX/2500BaseX PCS mode */ + spx5_rmw(DEV2G5_PCS1G_MODE_CFG_SGMII_MODE_ENA_SET(sgmii), + DEV2G5_PCS1G_MODE_CFG_SGMII_MODE_ENA, + sparx5, + DEV2G5_PCS1G_MODE_CFG(port->portno)); + + /* Enable PCS */ + spx5_wr(DEV2G5_PCS1G_CFG_PCS_ENA_SET(1), + sparx5, + DEV2G5_PCS1G_CFG(port->portno)); + + if (inband_aneg) { + u16 abil = sparx5_get_aneg_word(conf); + + /* Enable in-band aneg */ + spx5_wr(DEV2G5_PCS1G_ANEG_CFG_ADV_ABILITY_SET(abil) | + DEV2G5_PCS1G_ANEG_CFG_SW_RESOLVE_ENA_SET(1) | + DEV2G5_PCS1G_ANEG_CFG_ANEG_ENA_SET(1) | + DEV2G5_PCS1G_ANEG_CFG_ANEG_RESTART_ONE_SHOT_SET(1), + sparx5, + DEV2G5_PCS1G_ANEG_CFG(port->portno)); + } else { + spx5_wr(0, sparx5, DEV2G5_PCS1G_ANEG_CFG(port->portno)); + } + + /* Take PCS out of reset */ + spx5_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(2) | + DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(0) | + DEV2G5_DEV_RST_CTRL_PCS_RX_RST_SET(0), + DEV2G5_DEV_RST_CTRL_SPEED_SEL | + DEV2G5_DEV_RST_CTRL_PCS_TX_RST | + DEV2G5_DEV_RST_CTRL_PCS_RX_RST, + sparx5, + DEV2G5_DEV_RST_CTRL(port->portno)); + + return 0; +} + +static int sparx5_port_pcs_high_set(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + u32 clk_spd = conf->portmode == PHY_INTERFACE_MODE_5GBASER ? 1 : 0; + u32 pix = sparx5_port_dev_index(port->portno); + u32 dev = sparx5_to_high_dev(port->portno); + u32 pcs = sparx5_to_pcs_dev(port->portno); + void __iomem *devinst; + void __iomem *pcsinst; + int err; + + devinst = spx5_inst_get(sparx5, dev, pix); + pcsinst = spx5_inst_get(sparx5, pcs, pix); + + /* SFI : No in-band-aneg. Speeds 5G/10G/25G */ + err = sparx5_serdes_set(sparx5, port, conf); + if (err) + return -EINVAL; + if (conf->portmode == PHY_INTERFACE_MODE_25GBASER) { + /* Enable PCS for 25G device, speed 25G */ + spx5_rmw(DEV25G_PCS25G_CFG_PCS25G_ENA_SET(1), + DEV25G_PCS25G_CFG_PCS25G_ENA, + sparx5, + DEV25G_PCS25G_CFG(pix)); + } else { + /* Enable PCS for 5G/10G/25G devices, speed 5G/10G */ + spx5_inst_rmw(PCS10G_BR_PCS_CFG_PCS_ENA_SET(1), + PCS10G_BR_PCS_CFG_PCS_ENA, + pcsinst, + PCS10G_BR_PCS_CFG(0)); + } + + /* Enable 5G/10G/25G MAC module */ + spx5_inst_wr(DEV10G_MAC_ENA_CFG_RX_ENA_SET(1) | + DEV10G_MAC_ENA_CFG_TX_ENA_SET(1), + devinst, + DEV10G_MAC_ENA_CFG(0)); + + /* Take the device out of reset */ + spx5_inst_rmw(DEV10G_DEV_RST_CTRL_PCS_RX_RST_SET(0) | + DEV10G_DEV_RST_CTRL_PCS_TX_RST_SET(0) | + DEV10G_DEV_RST_CTRL_MAC_RX_RST_SET(0) | + DEV10G_DEV_RST_CTRL_MAC_TX_RST_SET(0) | + DEV10G_DEV_RST_CTRL_SPEED_SEL_SET(clk_spd), + DEV10G_DEV_RST_CTRL_PCS_RX_RST | + DEV10G_DEV_RST_CTRL_PCS_TX_RST | + DEV10G_DEV_RST_CTRL_MAC_RX_RST | + DEV10G_DEV_RST_CTRL_MAC_TX_RST | + DEV10G_DEV_RST_CTRL_SPEED_SEL, + devinst, + DEV10G_DEV_RST_CTRL(0)); + + return 0; +} + +/* Switch between 1G/2500 and 5G/10G/25G devices */ +static void sparx5_dev_switch(struct sparx5 *sparx5, int port, bool hsd) +{ + int bt_indx = BIT(sparx5_port_dev_index(port)); + + if (sparx5_port_is_5g(port)) { + spx5_rmw(hsd ? 0 : bt_indx, + bt_indx, + sparx5, + PORT_CONF_DEV5G_MODES); + } else if (sparx5_port_is_10g(port)) { + spx5_rmw(hsd ? 0 : bt_indx, + bt_indx, + sparx5, + PORT_CONF_DEV10G_MODES); + } else if (sparx5_port_is_25g(port)) { + spx5_rmw(hsd ? 0 : bt_indx, + bt_indx, + sparx5, + PORT_CONF_DEV25G_MODES); + } +} + +/* Configure speed/duplex dependent registers */ +static int sparx5_port_config_low_set(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + u32 clk_spd, gig_mode, tx_gap, hdx_gap_1, hdx_gap_2; + bool fdx = conf->duplex == DUPLEX_FULL; + int spd = conf->speed; + + clk_spd = spd == SPEED_10 ? 0 : spd == SPEED_100 ? 1 : 2; + gig_mode = spd == SPEED_1000 || spd == SPEED_2500; + tx_gap = spd == SPEED_1000 ? 4 : fdx ? 6 : 5; + hdx_gap_1 = spd == SPEED_1000 ? 0 : spd == SPEED_100 ? 1 : 2; + hdx_gap_2 = spd == SPEED_1000 ? 0 : spd == SPEED_100 ? 4 : 1; + + /* GIG/FDX mode */ + spx5_rmw(DEV2G5_MAC_MODE_CFG_GIGA_MODE_ENA_SET(gig_mode) | + DEV2G5_MAC_MODE_CFG_FDX_ENA_SET(fdx), + DEV2G5_MAC_MODE_CFG_GIGA_MODE_ENA | + DEV2G5_MAC_MODE_CFG_FDX_ENA, + sparx5, + DEV2G5_MAC_MODE_CFG(port->portno)); + + /* Set MAC IFG Gaps */ + spx5_wr(DEV2G5_MAC_IFG_CFG_TX_IFG_SET(tx_gap) | + DEV2G5_MAC_IFG_CFG_RX_IFG1_SET(hdx_gap_1) | + DEV2G5_MAC_IFG_CFG_RX_IFG2_SET(hdx_gap_2), + sparx5, + DEV2G5_MAC_IFG_CFG(port->portno)); + + /* Disabling frame aging when in HDX (due to HDX issue) */ + spx5_rmw(HSCH_PORT_MODE_AGE_DIS_SET(fdx == 0), + HSCH_PORT_MODE_AGE_DIS, + sparx5, + HSCH_PORT_MODE(port->portno)); + + /* Enable MAC module */ + spx5_wr(DEV2G5_MAC_ENA_CFG_RX_ENA | + DEV2G5_MAC_ENA_CFG_TX_ENA, + sparx5, + DEV2G5_MAC_ENA_CFG(port->portno)); + + /* Select speed and take MAC out of reset */ + spx5_rmw(DEV2G5_DEV_RST_CTRL_SPEED_SEL_SET(clk_spd) | + DEV2G5_DEV_RST_CTRL_MAC_TX_RST_SET(0) | + DEV2G5_DEV_RST_CTRL_MAC_RX_RST_SET(0), + DEV2G5_DEV_RST_CTRL_SPEED_SEL | + DEV2G5_DEV_RST_CTRL_MAC_TX_RST | + DEV2G5_DEV_RST_CTRL_MAC_RX_RST, + sparx5, + DEV2G5_DEV_RST_CTRL(port->portno)); + + return 0; +} + +int sparx5_port_pcs_set(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) + +{ + bool high_speed_dev = sparx5_is_baser(conf->portmode); + int err; + + if (sparx5_dev_change(sparx5, port, conf)) { + /* switch device */ + sparx5_dev_switch(sparx5, port->portno, high_speed_dev); + + /* Disable the not-in-use device */ + err = sparx5_port_disable(sparx5, port, !high_speed_dev); + if (err) + return err; + } + /* Disable the port before re-configuring */ + err = sparx5_port_disable(sparx5, port, high_speed_dev); + if (err) + return -EINVAL; + + if (high_speed_dev) + err = sparx5_port_pcs_high_set(sparx5, port, conf); + else + err = sparx5_port_pcs_low_set(sparx5, port, conf); + + if (err) + return -EINVAL; + + if (port->conf.inband) { + /* Enable/disable 1G counters in ASM */ + spx5_rmw(ASM_PORT_CFG_CSC_STAT_DIS_SET(high_speed_dev), + ASM_PORT_CFG_CSC_STAT_DIS, + sparx5, + ASM_PORT_CFG(port->portno)); + + /* Enable/disable 1G counters in DSM */ + spx5_rmw(DSM_BUF_CFG_CSC_STAT_DIS_SET(high_speed_dev), + DSM_BUF_CFG_CSC_STAT_DIS, + sparx5, + DSM_BUF_CFG(port->portno)); + } + + port->conf = *conf; + + return 0; +} + +int sparx5_port_config(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + bool high_speed_dev = sparx5_is_baser(conf->portmode); + int err, urgency, stop_wm; + + err = sparx5_port_verify_speed(sparx5, port, conf); + if (err) + return err; + + /* high speed device is already configured */ + if (!high_speed_dev) + sparx5_port_config_low_set(sparx5, port, conf); + + /* Configure flow control */ + err = sparx5_port_fc_setup(sparx5, port, conf); + if (err) + return err; + + /* Set the DSM stop watermark */ + stop_wm = sparx5_port_fifo_sz(sparx5, port->portno, conf->speed); + spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM_SET(stop_wm), + DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM, + sparx5, + DSM_DEV_TX_STOP_WM_CFG(port->portno)); + + /* Enable port in queue system */ + urgency = sparx5_port_fwd_urg(sparx5, conf->speed); + spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(1) | + QFWD_SWITCH_PORT_MODE_FWD_URGENCY_SET(urgency), + QFWD_SWITCH_PORT_MODE_PORT_ENA | + QFWD_SWITCH_PORT_MODE_FWD_URGENCY, + sparx5, + QFWD_SWITCH_PORT_MODE(port->portno)); + + /* Save the new values */ + port->conf = *conf; + + return 0; +} + +/* Initialize port config to default */ +int sparx5_port_init(struct sparx5 *sparx5, + struct sparx5_port *port, + struct sparx5_port_config *conf) +{ + u32 pause_start = sparx5_wm_enc(6 * (ETH_MAXLEN / SPX5_BUFFER_CELL_SZ)); + u32 atop = sparx5_wm_enc(20 * (ETH_MAXLEN / SPX5_BUFFER_CELL_SZ)); + u32 devhigh = sparx5_to_high_dev(port->portno); + u32 pix = sparx5_port_dev_index(port->portno); + u32 pcs = sparx5_to_pcs_dev(port->portno); + bool sd_pol = port->signd_active_high; + bool sd_sel = !port->signd_internal; + bool sd_ena = port->signd_enable; + u32 pause_stop = 0xFFF - 1; /* FC generate disabled */ + void __iomem *devinst; + void __iomem *pcsinst; + int err; + + devinst = spx5_inst_get(sparx5, devhigh, pix); + pcsinst = spx5_inst_get(sparx5, pcs, pix); + + /* Set the mux port mode */ + err = sparx5_port_mux_set(sparx5, port, conf); + if (err) + return err; + + /* Configure MAC vlan awareness */ + err = sparx5_port_max_tags_set(sparx5, port); + if (err) + return err; + + /* Set Max Length */ + spx5_rmw(DEV2G5_MAC_MAXLEN_CFG_MAX_LEN_SET(ETH_MAXLEN), + DEV2G5_MAC_MAXLEN_CFG_MAX_LEN, + sparx5, + DEV2G5_MAC_MAXLEN_CFG(port->portno)); + + /* 1G/2G5: Signal Detect configuration */ + spx5_wr(DEV2G5_PCS1G_SD_CFG_SD_POL_SET(sd_pol) | + DEV2G5_PCS1G_SD_CFG_SD_SEL_SET(sd_sel) | + DEV2G5_PCS1G_SD_CFG_SD_ENA_SET(sd_ena), + sparx5, + DEV2G5_PCS1G_SD_CFG(port->portno)); + + /* Set Pause WM hysteresis */ + spx5_rmw(QSYS_PAUSE_CFG_PAUSE_START_SET(pause_start) | + QSYS_PAUSE_CFG_PAUSE_STOP_SET(pause_stop) | + QSYS_PAUSE_CFG_PAUSE_ENA_SET(1), + QSYS_PAUSE_CFG_PAUSE_START | + QSYS_PAUSE_CFG_PAUSE_STOP | + QSYS_PAUSE_CFG_PAUSE_ENA, + sparx5, + QSYS_PAUSE_CFG(port->portno)); + + /* Port ATOP. Frames are tail dropped when this WM is hit */ + spx5_wr(QSYS_ATOP_ATOP_SET(atop), + sparx5, + QSYS_ATOP(port->portno)); + + /* Discard pause frame 01-80-C2-00-00-01 */ + spx5_wr(PAUSE_DISCARD, sparx5, ANA_CL_CAPTURE_BPDU_CFG(port->portno)); + + /* Discard SMAC multicast */ + spx5_rmw(ANA_CL_FILTER_CTRL_FILTER_SMAC_MC_DIS_SET(0), + ANA_CL_FILTER_CTRL_FILTER_SMAC_MC_DIS, + sparx5, ANA_CL_FILTER_CTRL(port->portno)); + + if (conf->portmode == PHY_INTERFACE_MODE_QSGMII || + conf->portmode == PHY_INTERFACE_MODE_SGMII) { + err = sparx5_serdes_set(sparx5, port, conf); + if (err) + return err; + + if (!sparx5_port_is_2g5(port->portno)) + /* Enable shadow device */ + spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV10G_SHADOW_ENA_SET(1), + DSM_DEV_TX_STOP_WM_CFG_DEV10G_SHADOW_ENA, + sparx5, + DSM_DEV_TX_STOP_WM_CFG(port->portno)); + + sparx5_dev_switch(sparx5, port->portno, false); + } + if (conf->portmode == PHY_INTERFACE_MODE_QSGMII) { + // All ports must be PCS enabled in QSGMII mode + spx5_rmw(DEV2G5_DEV_RST_CTRL_PCS_TX_RST_SET(0), + DEV2G5_DEV_RST_CTRL_PCS_TX_RST, + sparx5, + DEV2G5_DEV_RST_CTRL(port->portno)); + } + /* Default IFGs for 1G */ + spx5_wr(DEV2G5_MAC_IFG_CFG_TX_IFG_SET(6) | + DEV2G5_MAC_IFG_CFG_RX_IFG1_SET(0) | + DEV2G5_MAC_IFG_CFG_RX_IFG2_SET(0), + sparx5, + DEV2G5_MAC_IFG_CFG(port->portno)); + + if (sparx5_port_is_2g5(port->portno)) + return 0; /* Low speed device only - return */ + + /* Now setup the high speed device */ + if (conf->portmode == PHY_INTERFACE_MODE_NA) + conf->portmode = PHY_INTERFACE_MODE_10GBASER; + + if (sparx5_is_baser(conf->portmode)) + sparx5_dev_switch(sparx5, port->portno, true); + + /* Set Max Length */ + spx5_inst_rmw(DEV10G_MAC_MAXLEN_CFG_MAX_LEN_SET(ETH_MAXLEN), + DEV10G_MAC_MAXLEN_CFG_MAX_LEN, + devinst, + DEV10G_MAC_ENA_CFG(0)); + + /* Handle Signal Detect in 10G PCS */ + spx5_inst_wr(PCS10G_BR_PCS_SD_CFG_SD_POL_SET(sd_pol) | + PCS10G_BR_PCS_SD_CFG_SD_SEL_SET(sd_sel) | + PCS10G_BR_PCS_SD_CFG_SD_ENA_SET(sd_ena), + pcsinst, + PCS10G_BR_PCS_SD_CFG(0)); + + if (sparx5_port_is_25g(port->portno)) { + /* Handle Signal Detect in 25G PCS */ + spx5_wr(DEV25G_PCS25G_SD_CFG_SD_POL_SET(sd_pol) | + DEV25G_PCS25G_SD_CFG_SD_SEL_SET(sd_sel) | + DEV25G_PCS25G_SD_CFG_SD_ENA_SET(sd_ena), + sparx5, + DEV25G_PCS25G_SD_CFG(pix)); + } + + return 0; +} + +void sparx5_port_enable(struct sparx5_port *port, bool enable) +{ + struct sparx5 *sparx5 = port->sparx5; + + /* Enable port for frame transfer? */ + spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(enable), + QFWD_SWITCH_PORT_MODE_PORT_ENA, + sparx5, + QFWD_SWITCH_PORT_MODE(port->portno)); +} + +int sparx5_port_qos_set(struct sparx5_port *port, + struct sparx5_port_qos *qos) +{ + sparx5_port_qos_dscp_set(port, &qos->dscp); + sparx5_port_qos_pcp_set(port, &qos->pcp); + sparx5_port_qos_pcp_rewr_set(port, &qos->pcp_rewr); + sparx5_port_qos_dscp_rewr_set(port, &qos->dscp_rewr); + sparx5_port_qos_default_set(port, qos); + + return 0; +} + +int sparx5_port_qos_pcp_rewr_set(const struct sparx5_port *port, + struct sparx5_port_qos_pcp_rewr *qos) +{ + int i, mode = SPARX5_PORT_REW_TAG_CTRL_CLASSIFIED; + struct sparx5 *sparx5 = port->sparx5; + u8 pcp, dei; + + /* Use mapping table, with classified QoS as index, to map QoS and DP + * to tagged PCP and DEI, if PCP is trusted. Otherwise use classified + * PCP. Classified PCP equals frame PCP. + */ + if (qos->enable) + mode = SPARX5_PORT_REW_TAG_CTRL_MAPPED; + + spx5_rmw(REW_TAG_CTRL_TAG_PCP_CFG_SET(mode) | + REW_TAG_CTRL_TAG_DEI_CFG_SET(mode), + REW_TAG_CTRL_TAG_PCP_CFG | REW_TAG_CTRL_TAG_DEI_CFG, + port->sparx5, REW_TAG_CTRL(port->portno)); + + for (i = 0; i < ARRAY_SIZE(qos->map.map); i++) { + /* Extract PCP and DEI */ + pcp = qos->map.map[i]; + if (pcp > SPARX5_PORT_QOS_PCP_COUNT) + dei = 1; + else + dei = 0; + + /* Rewrite PCP and DEI, for each classified QoS class and DP + * level. This table is only used if tag ctrl mode is set to + * 'mapped'. + * + * 0:0nd - prio=0 and dp:0 => pcp=0 and dei=0 + * 0:0de - prio=0 and dp:1 => pcp=0 and dei=1 + */ + if (dei) { + spx5_rmw(REW_PCP_MAP_DE1_PCP_DE1_SET(pcp), + REW_PCP_MAP_DE1_PCP_DE1, sparx5, + REW_PCP_MAP_DE1(port->portno, i)); + + spx5_rmw(REW_DEI_MAP_DE1_DEI_DE1_SET(dei), + REW_DEI_MAP_DE1_DEI_DE1, port->sparx5, + REW_DEI_MAP_DE1(port->portno, i)); + } else { + spx5_rmw(REW_PCP_MAP_DE0_PCP_DE0_SET(pcp), + REW_PCP_MAP_DE0_PCP_DE0, sparx5, + REW_PCP_MAP_DE0(port->portno, i)); + + spx5_rmw(REW_DEI_MAP_DE0_DEI_DE0_SET(dei), + REW_DEI_MAP_DE0_DEI_DE0, port->sparx5, + REW_DEI_MAP_DE0(port->portno, i)); + } + } + + return 0; +} + +int sparx5_port_qos_pcp_set(const struct sparx5_port *port, + struct sparx5_port_qos_pcp *qos) +{ + struct sparx5 *sparx5 = port->sparx5; + u8 *pcp_itr = qos->map.map; + u8 pcp, dp; + int i; + + /* Enable/disable pcp and dp for qos classification. */ + spx5_rmw(ANA_CL_QOS_CFG_PCP_DEI_QOS_ENA_SET(qos->qos_enable) | + ANA_CL_QOS_CFG_PCP_DEI_DP_ENA_SET(qos->dp_enable), + ANA_CL_QOS_CFG_PCP_DEI_QOS_ENA | ANA_CL_QOS_CFG_PCP_DEI_DP_ENA, + sparx5, ANA_CL_QOS_CFG(port->portno)); + + /* Map each pcp and dei value to priority and dp */ + for (i = 0; i < ARRAY_SIZE(qos->map.map); i++) { + pcp = *(pcp_itr + i); + dp = (i < SPARX5_PORT_QOS_PCP_COUNT) ? 0 : 1; + spx5_rmw(ANA_CL_PCP_DEI_MAP_CFG_PCP_DEI_QOS_VAL_SET(pcp) | + ANA_CL_PCP_DEI_MAP_CFG_PCP_DEI_DP_VAL_SET(dp), + ANA_CL_PCP_DEI_MAP_CFG_PCP_DEI_QOS_VAL | + ANA_CL_PCP_DEI_MAP_CFG_PCP_DEI_DP_VAL, sparx5, + ANA_CL_PCP_DEI_MAP_CFG(port->portno, i)); + } + + return 0; +} + +void sparx5_port_qos_dscp_rewr_mode_set(const struct sparx5_port *port, + int mode) +{ + spx5_rmw(ANA_CL_QOS_CFG_DSCP_REWR_MODE_SEL_SET(mode), + ANA_CL_QOS_CFG_DSCP_REWR_MODE_SEL, port->sparx5, + ANA_CL_QOS_CFG(port->portno)); +} + +int sparx5_port_qos_dscp_rewr_set(const struct sparx5_port *port, + struct sparx5_port_qos_dscp_rewr *qos) +{ + struct sparx5 *sparx5 = port->sparx5; + bool rewr = false; + u16 dscp; + int i; + + /* On egress, rewrite DSCP value to either classified DSCP or frame + * DSCP. If enabled; classified DSCP, if disabled; frame DSCP. + */ + if (qos->enable) + rewr = true; + + spx5_rmw(REW_DSCP_MAP_DSCP_UPDATE_ENA_SET(rewr), + REW_DSCP_MAP_DSCP_UPDATE_ENA, sparx5, + REW_DSCP_MAP(port->portno)); + + /* On ingress, map each classified QoS class and DP to classified DSCP + * value. This mapping table is global for all ports. + */ + for (i = 0; i < ARRAY_SIZE(qos->map.map); i++) { + dscp = qos->map.map[i]; + spx5_rmw(ANA_CL_QOS_MAP_CFG_DSCP_REWR_VAL_SET(dscp), + ANA_CL_QOS_MAP_CFG_DSCP_REWR_VAL, sparx5, + ANA_CL_QOS_MAP_CFG(i)); + } + + return 0; +} + +int sparx5_port_qos_dscp_set(const struct sparx5_port *port, + struct sparx5_port_qos_dscp *qos) +{ + struct sparx5 *sparx5 = port->sparx5; + u8 *dscp = qos->map.map; + int i; + + /* Enable/disable dscp and dp for qos classification. + * Disable rewrite of dscp values for now. + */ + spx5_rmw(ANA_CL_QOS_CFG_DSCP_QOS_ENA_SET(qos->qos_enable) | + ANA_CL_QOS_CFG_DSCP_DP_ENA_SET(qos->dp_enable) | + ANA_CL_QOS_CFG_DSCP_KEEP_ENA_SET(1), + ANA_CL_QOS_CFG_DSCP_QOS_ENA | ANA_CL_QOS_CFG_DSCP_DP_ENA | + ANA_CL_QOS_CFG_DSCP_KEEP_ENA, sparx5, + ANA_CL_QOS_CFG(port->portno)); + + /* Map each dscp value to priority and dp */ + for (i = 0; i < ARRAY_SIZE(qos->map.map); i++) { + spx5_rmw(ANA_CL_DSCP_CFG_DSCP_QOS_VAL_SET(*(dscp + i)) | + ANA_CL_DSCP_CFG_DSCP_DP_VAL_SET(0), + ANA_CL_DSCP_CFG_DSCP_QOS_VAL | + ANA_CL_DSCP_CFG_DSCP_DP_VAL, sparx5, + ANA_CL_DSCP_CFG(i)); + } + + /* Set per-dscp trust */ + for (i = 0; i < ARRAY_SIZE(qos->map.map); i++) { + if (qos->qos_enable) { + spx5_rmw(ANA_CL_DSCP_CFG_DSCP_TRUST_ENA_SET(1), + ANA_CL_DSCP_CFG_DSCP_TRUST_ENA, sparx5, + ANA_CL_DSCP_CFG(i)); + } + } + + return 0; +} + +int sparx5_port_qos_default_set(const struct sparx5_port *port, + const struct sparx5_port_qos *qos) +{ + struct sparx5 *sparx5 = port->sparx5; + + /* Set default prio and dp level */ + spx5_rmw(ANA_CL_QOS_CFG_DEFAULT_QOS_VAL_SET(qos->default_prio) | + ANA_CL_QOS_CFG_DEFAULT_DP_VAL_SET(0), + ANA_CL_QOS_CFG_DEFAULT_QOS_VAL | + ANA_CL_QOS_CFG_DEFAULT_DP_VAL, + sparx5, ANA_CL_QOS_CFG(port->portno)); + + /* Set default pcp and dei for untagged frames */ + spx5_rmw(ANA_CL_VLAN_CTRL_PORT_PCP_SET(0) | + ANA_CL_VLAN_CTRL_PORT_DEI_SET(0), + ANA_CL_VLAN_CTRL_PORT_PCP | + ANA_CL_VLAN_CTRL_PORT_DEI, + sparx5, ANA_CL_VLAN_CTRL(port->portno)); + + return 0; +} |