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
@@ -0,0 +1,51 @@
# SPDX-License-Identifier: GPL-2.0-only
config NET_DSA_SJA1105
tristate "NXP SJA1105 Ethernet switch family support"
depends on NET_DSA && SPI
depends on PTP_1588_CLOCK_OPTIONAL
select NET_DSA_TAG_SJA1105
select PCS_XPCS
select PACKING
select CRC32
help
This is the driver for the NXP SJA1105 (5-port) and SJA1110 (10-port)
automotive Ethernet switch family. These are managed over an SPI
interface. Probing is handled based on OF bindings and so is the
linkage to PHYLINK. The driver supports the following revisions:
- SJA1105E (Gen. 1, No TT-Ethernet)
- SJA1105T (Gen. 1, TT-Ethernet)
- SJA1105P (Gen. 2, No SGMII, No TT-Ethernet)
- SJA1105Q (Gen. 2, No SGMII, TT-Ethernet)
- SJA1105R (Gen. 2, SGMII, No TT-Ethernet)
- SJA1105S (Gen. 2, SGMII, TT-Ethernet)
- SJA1110A (Gen. 3, SGMII, TT-Ethernet, 100base-TX PHY, 10 ports)
- SJA1110B (Gen. 3, SGMII, TT-Ethernet, 100base-TX PHY, 9 ports)
- SJA1110C (Gen. 3, SGMII, TT-Ethernet, 100base-TX PHY, 7 ports)
- SJA1110D (Gen. 3, SGMII, TT-Ethernet, no 100base-TX PHY, 7 ports)
config NET_DSA_SJA1105_PTP
bool "Support for the PTP clock on the NXP SJA1105 Ethernet switch"
depends on NET_DSA_SJA1105
depends on PTP_1588_CLOCK
help
This enables support for timestamping and PTP clock manipulations in
the SJA1105 DSA driver.
config NET_DSA_SJA1105_TAS
bool "Support for the Time-Aware Scheduler on NXP SJA1105"
depends on NET_DSA_SJA1105 && NET_SCH_TAPRIO
depends on NET_SCH_TAPRIO=y || NET_DSA_SJA1105=m
depends on NET_DSA_SJA1105_PTP
help
This enables support for the TTEthernet-based egress scheduling
engine in the SJA1105 DSA driver, which is controlled using a
hardware offload of the tc-tqprio qdisc.
config NET_DSA_SJA1105_VL
bool "Support for Virtual Links on NXP SJA1105"
depends on NET_DSA_SJA1105_TAS
help
This enables support for flow classification using capable devices
(SJA1105T, SJA1105Q, SJA1105S). The following actions are supported:
- redirect, trap, drop
- time-based ingress policing, via the tc-gate action
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o
sja1105-objs := \
sja1105_spi.o \
sja1105_main.o \
sja1105_mdio.o \
sja1105_flower.o \
sja1105_ethtool.o \
sja1105_devlink.o \
sja1105_clocking.o \
sja1105_static_config.o \
sja1105_dynamic_config.o \
ifdef CONFIG_NET_DSA_SJA1105_PTP
sja1105-objs += sja1105_ptp.o
endif
ifdef CONFIG_NET_DSA_SJA1105_TAS
sja1105-objs += sja1105_tas.o
endif
ifdef CONFIG_NET_DSA_SJA1105_VL
sja1105-objs += sja1105_vl.o
endif
@@ -0,0 +1,426 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2018, Sensor-Technik Wiedemann GmbH
* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
*/
#ifndef _SJA1105_H
#define _SJA1105_H
#include <linux/ptp_clock_kernel.h>
#include <linux/timecounter.h>
#include <linux/dsa/sja1105.h>
#include <linux/dsa/8021q.h>
#include <net/dsa.h>
#include <linux/mutex.h>
#include "sja1105_static_config.h"
#define SJA1105ET_FDB_BIN_SIZE 4
/* The hardware value is in multiples of 10 ms.
* The passed parameter is in multiples of 1 ms.
*/
#define SJA1105_AGEING_TIME_MS(ms) ((ms) / 10)
#define SJA1105_NUM_L2_POLICERS SJA1110_MAX_L2_POLICING_COUNT
/* Calculated assuming 1Gbps, where the clock has 125 MHz (8 ns period)
* To avoid floating point operations, we'll multiply the degrees by 10
* to get a "phase" and get 1 decimal point precision.
*/
#define SJA1105_RGMII_DELAY_PS_TO_PHASE(ps) \
(((ps) * 360) / 800)
#define SJA1105_RGMII_DELAY_PHASE_TO_PS(phase) \
((800 * (phase)) / 360)
#define SJA1105_RGMII_DELAY_PHASE_TO_HW(phase) \
(((phase) - 738) / 9)
#define SJA1105_RGMII_DELAY_PS_TO_HW(ps) \
SJA1105_RGMII_DELAY_PHASE_TO_HW(SJA1105_RGMII_DELAY_PS_TO_PHASE(ps))
/* Valid range in degrees is a value between 73.8 and 101.7
* in 0.9 degree increments
*/
#define SJA1105_RGMII_DELAY_MIN_PS \
SJA1105_RGMII_DELAY_PHASE_TO_PS(738)
#define SJA1105_RGMII_DELAY_MAX_PS \
SJA1105_RGMII_DELAY_PHASE_TO_PS(1017)
typedef enum {
SPI_READ = 0,
SPI_WRITE = 1,
} sja1105_spi_rw_mode_t;
#include "sja1105_tas.h"
#include "sja1105_ptp.h"
enum sja1105_stats_area {
MAC,
HL1,
HL2,
ETHER,
__MAX_SJA1105_STATS_AREA,
};
/* Keeps the different addresses between E/T and P/Q/R/S */
struct sja1105_regs {
u64 device_id;
u64 prod_id;
u64 status;
u64 port_control;
u64 rgu;
u64 vl_status;
u64 config;
u64 rmii_pll1;
u64 ptppinst;
u64 ptppindur;
u64 ptp_control;
u64 ptpclkval;
u64 ptpclkrate;
u64 ptpclkcorp;
u64 ptpsyncts;
u64 ptpschtm;
u64 ptpegr_ts[SJA1105_MAX_NUM_PORTS];
u64 pad_mii_tx[SJA1105_MAX_NUM_PORTS];
u64 pad_mii_rx[SJA1105_MAX_NUM_PORTS];
u64 pad_mii_id[SJA1105_MAX_NUM_PORTS];
u64 cgu_idiv[SJA1105_MAX_NUM_PORTS];
u64 mii_tx_clk[SJA1105_MAX_NUM_PORTS];
u64 mii_rx_clk[SJA1105_MAX_NUM_PORTS];
u64 mii_ext_tx_clk[SJA1105_MAX_NUM_PORTS];
u64 mii_ext_rx_clk[SJA1105_MAX_NUM_PORTS];
u64 rgmii_tx_clk[SJA1105_MAX_NUM_PORTS];
u64 rmii_ref_clk[SJA1105_MAX_NUM_PORTS];
u64 rmii_ext_tx_clk[SJA1105_MAX_NUM_PORTS];
u64 stats[__MAX_SJA1105_STATS_AREA][SJA1105_MAX_NUM_PORTS];
u64 mdio_100base_tx;
u64 mdio_100base_t1;
u64 pcs_base[SJA1105_MAX_NUM_PORTS];
};
struct sja1105_mdio_private {
struct sja1105_private *priv;
};
enum {
SJA1105_SPEED_AUTO,
SJA1105_SPEED_10MBPS,
SJA1105_SPEED_100MBPS,
SJA1105_SPEED_1000MBPS,
SJA1105_SPEED_2500MBPS,
SJA1105_SPEED_MAX,
};
enum sja1105_internal_phy_t {
SJA1105_NO_PHY = 0,
SJA1105_PHY_BASE_TX,
SJA1105_PHY_BASE_T1,
};
struct sja1105_info {
u64 device_id;
/* Needed for distinction between P and R, and between Q and S
* (since the parts with/without SGMII share the same
* switch core and device_id)
*/
u64 part_no;
/* E/T and P/Q/R/S have partial timestamps of different sizes.
* They must be reconstructed on both families anyway to get the full
* 64-bit values back.
*/
int ptp_ts_bits;
/* Also SPI commands are of different sizes to retrieve
* the egress timestamps.
*/
int ptpegr_ts_bytes;
int num_cbs_shapers;
int max_frame_mem;
int num_ports;
bool multiple_cascade_ports;
/* Every {port, TXQ} has its own CBS shaper */
bool fixed_cbs_mapping;
enum dsa_tag_protocol tag_proto;
const struct sja1105_dynamic_table_ops *dyn_ops;
const struct sja1105_table_ops *static_ops;
const struct sja1105_regs *regs;
bool can_limit_mcast_flood;
int (*reset_cmd)(struct dsa_switch *ds);
int (*setup_rgmii_delay)(const void *ctx, int port);
/* Prototypes from include/net/dsa.h */
int (*fdb_add_cmd)(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int (*fdb_del_cmd)(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
void (*ptp_cmd_packing)(u8 *buf, struct sja1105_ptp_cmd *cmd,
enum packing_op op);
bool (*rxtstamp)(struct dsa_switch *ds, int port, struct sk_buff *skb);
void (*txtstamp)(struct dsa_switch *ds, int port, struct sk_buff *skb);
int (*clocking_setup)(struct sja1105_private *priv);
int (*pcs_mdio_read_c45)(struct mii_bus *bus, int phy, int mmd,
int reg);
int (*pcs_mdio_write_c45)(struct mii_bus *bus, int phy, int mmd,
int reg, u16 val);
int (*disable_microcontroller)(struct sja1105_private *priv);
const char *name;
bool supports_mii[SJA1105_MAX_NUM_PORTS];
bool supports_rmii[SJA1105_MAX_NUM_PORTS];
bool supports_rgmii[SJA1105_MAX_NUM_PORTS];
bool supports_sgmii[SJA1105_MAX_NUM_PORTS];
bool supports_2500basex[SJA1105_MAX_NUM_PORTS];
enum sja1105_internal_phy_t internal_phy[SJA1105_MAX_NUM_PORTS];
const u64 port_speed[SJA1105_SPEED_MAX];
};
enum sja1105_key_type {
SJA1105_KEY_BCAST,
SJA1105_KEY_TC,
SJA1105_KEY_VLAN_UNAWARE_VL,
SJA1105_KEY_VLAN_AWARE_VL,
};
struct sja1105_key {
enum sja1105_key_type type;
union {
/* SJA1105_KEY_TC */
struct {
int pcp;
} tc;
/* SJA1105_KEY_VLAN_UNAWARE_VL */
/* SJA1105_KEY_VLAN_AWARE_VL */
struct {
u64 dmac;
u16 vid;
u16 pcp;
} vl;
};
};
enum sja1105_rule_type {
SJA1105_RULE_BCAST_POLICER,
SJA1105_RULE_TC_POLICER,
SJA1105_RULE_VL,
};
enum sja1105_vl_type {
SJA1105_VL_NONCRITICAL,
SJA1105_VL_RATE_CONSTRAINED,
SJA1105_VL_TIME_TRIGGERED,
};
struct sja1105_rule {
struct list_head list;
unsigned long cookie;
unsigned long port_mask;
struct sja1105_key key;
enum sja1105_rule_type type;
/* Action */
union {
/* SJA1105_RULE_BCAST_POLICER */
struct {
int sharindx;
} bcast_pol;
/* SJA1105_RULE_TC_POLICER */
struct {
int sharindx;
} tc_pol;
/* SJA1105_RULE_VL */
struct {
enum sja1105_vl_type type;
unsigned long destports;
int sharindx;
int maxlen;
int ipv;
u64 base_time;
u64 cycle_time;
int num_entries;
struct action_gate_entry *entries;
struct flow_stats stats;
} vl;
};
};
struct sja1105_flow_block {
struct list_head rules;
bool l2_policer_used[SJA1105_NUM_L2_POLICERS];
int num_virtual_links;
};
struct sja1105_private {
struct sja1105_static_config static_config;
int rgmii_rx_delay_ps[SJA1105_MAX_NUM_PORTS];
int rgmii_tx_delay_ps[SJA1105_MAX_NUM_PORTS];
phy_interface_t phy_mode[SJA1105_MAX_NUM_PORTS];
bool fixed_link[SJA1105_MAX_NUM_PORTS];
unsigned long ucast_egress_floods;
unsigned long bcast_egress_floods;
unsigned long hwts_tx_en;
unsigned long hwts_rx_en;
const struct sja1105_info *info;
size_t max_xfer_len;
struct spi_device *spidev;
struct dsa_switch *ds;
u16 bridge_pvid[SJA1105_MAX_NUM_PORTS];
u16 tag_8021q_pvid[SJA1105_MAX_NUM_PORTS];
struct sja1105_flow_block flow_block;
/* Serializes transmission of management frames so that
* the switch doesn't confuse them with one another.
*/
struct mutex mgmt_lock;
/* Serializes accesses to the FDB */
struct mutex fdb_lock;
/* PTP two-step TX timestamp ID, and its serialization lock */
spinlock_t ts_id_lock;
u8 ts_id;
/* Serializes access to the dynamic config interface */
struct mutex dynamic_config_lock;
struct devlink_region **regions;
struct sja1105_cbs_entry *cbs;
struct mii_bus *mdio_base_t1;
struct mii_bus *mdio_base_tx;
struct mii_bus *mdio_pcs;
struct phylink_pcs *pcs[SJA1105_MAX_NUM_PORTS];
struct sja1105_ptp_data ptp_data;
struct sja1105_tas_data tas_data;
};
#include "sja1105_dynamic_config.h"
struct sja1105_spi_message {
u64 access;
u64 read_count;
u64 address;
};
/* From sja1105_main.c */
enum sja1105_reset_reason {
SJA1105_VLAN_FILTERING = 0,
SJA1105_AGEING_TIME,
SJA1105_SCHEDULING,
SJA1105_BEST_EFFORT_POLICING,
SJA1105_VIRTUAL_LINKS,
};
int sja1105_static_config_reload(struct sja1105_private *priv,
enum sja1105_reset_reason reason);
int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled,
struct netlink_ext_ack *extack);
void sja1105_frame_memory_partitioning(struct sja1105_private *priv);
/* From sja1105_mdio.c */
int sja1105_mdiobus_register(struct dsa_switch *ds);
void sja1105_mdiobus_unregister(struct dsa_switch *ds);
int sja1105_pcs_mdio_read_c45(struct mii_bus *bus, int phy, int mmd, int reg);
int sja1105_pcs_mdio_write_c45(struct mii_bus *bus, int phy, int mmd, int reg,
u16 val);
int sja1110_pcs_mdio_read_c45(struct mii_bus *bus, int phy, int mmd, int reg);
int sja1110_pcs_mdio_write_c45(struct mii_bus *bus, int phy, int mmd, int reg,
u16 val);
/* From sja1105_devlink.c */
int sja1105_devlink_setup(struct dsa_switch *ds);
void sja1105_devlink_teardown(struct dsa_switch *ds);
int sja1105_devlink_info_get(struct dsa_switch *ds,
struct devlink_info_req *req,
struct netlink_ext_ack *extack);
/* From sja1105_spi.c */
int sja1105_xfer_buf(const struct sja1105_private *priv,
sja1105_spi_rw_mode_t rw, u64 reg_addr,
u8 *buf, size_t len);
int sja1105_xfer_u32(const struct sja1105_private *priv,
sja1105_spi_rw_mode_t rw, u64 reg_addr, u32 *value,
struct ptp_system_timestamp *ptp_sts);
int sja1105_xfer_u64(const struct sja1105_private *priv,
sja1105_spi_rw_mode_t rw, u64 reg_addr, u64 *value,
struct ptp_system_timestamp *ptp_sts);
int static_config_buf_prepare_for_upload(struct sja1105_private *priv,
void *config_buf, int buf_len);
int sja1105_static_config_upload(struct sja1105_private *priv);
int sja1105_inhibit_tx(const struct sja1105_private *priv,
unsigned long port_bitmap, bool tx_inhibited);
extern const struct sja1105_info sja1105e_info;
extern const struct sja1105_info sja1105t_info;
extern const struct sja1105_info sja1105p_info;
extern const struct sja1105_info sja1105q_info;
extern const struct sja1105_info sja1105r_info;
extern const struct sja1105_info sja1105s_info;
extern const struct sja1105_info sja1110a_info;
extern const struct sja1105_info sja1110b_info;
extern const struct sja1105_info sja1110c_info;
extern const struct sja1105_info sja1110d_info;
/* From sja1105_clocking.c */
typedef enum {
XMII_MAC = 0,
XMII_PHY = 1,
} sja1105_mii_role_t;
typedef enum {
XMII_MODE_MII = 0,
XMII_MODE_RMII = 1,
XMII_MODE_RGMII = 2,
XMII_MODE_SGMII = 3,
} sja1105_phy_interface_t;
int sja1105pqrs_setup_rgmii_delay(const void *ctx, int port);
int sja1110_setup_rgmii_delay(const void *ctx, int port);
int sja1105_clocking_setup_port(struct sja1105_private *priv, int port);
int sja1105_clocking_setup(struct sja1105_private *priv);
int sja1110_disable_microcontroller(struct sja1105_private *priv);
/* From sja1105_ethtool.c */
void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data);
void sja1105_get_strings(struct dsa_switch *ds, int port,
u32 stringset, u8 *data);
int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset);
/* From sja1105_dynamic_config.c */
int sja1105_dynamic_config_read(struct sja1105_private *priv,
enum sja1105_blk_idx blk_idx,
int index, void *entry);
int sja1105_dynamic_config_write(struct sja1105_private *priv,
enum sja1105_blk_idx blk_idx,
int index, void *entry, bool keep);
enum sja1105_iotag {
SJA1105_C_TAG = 0, /* Inner VLAN header */
SJA1105_S_TAG = 1, /* Outer VLAN header */
};
enum sja1110_vlan_type {
SJA1110_VLAN_INVALID = 0,
SJA1110_VLAN_C_TAG = 1, /* Single inner VLAN tag */
SJA1110_VLAN_S_TAG = 2, /* Single outer VLAN tag */
SJA1110_VLAN_D_TAG = 3, /* Double tagged, use outer tag for lookup */
};
enum sja1110_shaper_type {
SJA1110_LEAKY_BUCKET_SHAPER = 0,
SJA1110_CBS_SHAPER = 1,
};
u8 sja1105et_fdb_hash(struct sja1105_private *priv, const u8 *addr, u16 vid);
int sja1105et_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int sja1105et_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int sja1105pqrs_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
int sja1105pqrs_fdb_del(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid);
/* From sja1105_flower.c */
int sja1105_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress);
int sja1105_cls_flower_add(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress);
int sja1105_cls_flower_stats(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress);
void sja1105_flower_setup(struct dsa_switch *ds);
void sja1105_flower_teardown(struct dsa_switch *ds);
struct sja1105_rule *sja1105_rule_find(struct sja1105_private *priv,
unsigned long cookie);
#endif
@@ -0,0 +1,860 @@
// SPDX-License-Identifier: BSD-3-Clause
/* Copyright 2016-2018 NXP
* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
*/
#include <linux/packing.h>
#include "sja1105.h"
#define SJA1105_SIZE_CGU_CMD 4
#define SJA1110_BASE_MCSS_CLK SJA1110_CGU_ADDR(0x70)
#define SJA1110_BASE_TIMER_CLK SJA1110_CGU_ADDR(0x74)
/* Common structure for CFG_PAD_MIIx_RX and CFG_PAD_MIIx_TX */
struct sja1105_cfg_pad_mii {
u64 d32_os;
u64 d32_ih;
u64 d32_ipud;
u64 d10_ih;
u64 d10_os;
u64 d10_ipud;
u64 ctrl_os;
u64 ctrl_ih;
u64 ctrl_ipud;
u64 clk_os;
u64 clk_ih;
u64 clk_ipud;
};
struct sja1105_cfg_pad_mii_id {
u64 rxc_stable_ovr;
u64 rxc_delay;
u64 rxc_bypass;
u64 rxc_pd;
u64 txc_stable_ovr;
u64 txc_delay;
u64 txc_bypass;
u64 txc_pd;
};
/* UM10944 Table 82.
* IDIV_0_C to IDIV_4_C control registers
* (addr. 10000Bh to 10000Fh)
*/
struct sja1105_cgu_idiv {
u64 clksrc;
u64 autoblock;
u64 idiv;
u64 pd;
};
/* PLL_1_C control register
*
* SJA1105 E/T: UM10944 Table 81 (address 10000Ah)
* SJA1105 P/Q/R/S: UM11040 Table 116 (address 10000Ah)
*/
struct sja1105_cgu_pll_ctrl {
u64 pllclksrc;
u64 msel;
u64 autoblock;
u64 psel;
u64 direct;
u64 fbsel;
u64 bypass;
u64 pd;
};
struct sja1110_cgu_outclk {
u64 clksrc;
u64 autoblock;
u64 pd;
};
enum {
CLKSRC_MII0_TX_CLK = 0x00,
CLKSRC_MII0_RX_CLK = 0x01,
CLKSRC_MII1_TX_CLK = 0x02,
CLKSRC_MII1_RX_CLK = 0x03,
CLKSRC_MII2_TX_CLK = 0x04,
CLKSRC_MII2_RX_CLK = 0x05,
CLKSRC_MII3_TX_CLK = 0x06,
CLKSRC_MII3_RX_CLK = 0x07,
CLKSRC_MII4_TX_CLK = 0x08,
CLKSRC_MII4_RX_CLK = 0x09,
CLKSRC_PLL0 = 0x0B,
CLKSRC_PLL1 = 0x0E,
CLKSRC_IDIV0 = 0x11,
CLKSRC_IDIV1 = 0x12,
CLKSRC_IDIV2 = 0x13,
CLKSRC_IDIV3 = 0x14,
CLKSRC_IDIV4 = 0x15,
};
/* UM10944 Table 83.
* MIIx clock control registers 1 to 30
* (addresses 100013h to 100035h)
*/
struct sja1105_cgu_mii_ctrl {
u64 clksrc;
u64 autoblock;
u64 pd;
};
static void sja1105_cgu_idiv_packing(void *buf, struct sja1105_cgu_idiv *idiv,
enum packing_op op)
{
const int size = 4;
sja1105_packing(buf, &idiv->clksrc, 28, 24, size, op);
sja1105_packing(buf, &idiv->autoblock, 11, 11, size, op);
sja1105_packing(buf, &idiv->idiv, 5, 2, size, op);
sja1105_packing(buf, &idiv->pd, 0, 0, size, op);
}
static int sja1105_cgu_idiv_config(struct sja1105_private *priv, int port,
bool enabled, int factor)
{
const struct sja1105_regs *regs = priv->info->regs;
struct device *dev = priv->ds->dev;
struct sja1105_cgu_idiv idiv;
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
if (regs->cgu_idiv[port] == SJA1105_RSV_ADDR)
return 0;
if (enabled && factor != 1 && factor != 10) {
dev_err(dev, "idiv factor must be 1 or 10\n");
return -ERANGE;
}
/* Payload for packed_buf */
idiv.clksrc = 0x0A; /* 25MHz */
idiv.autoblock = 1; /* Block clk automatically */
idiv.idiv = factor - 1; /* Divide by 1 or 10 */
idiv.pd = enabled ? 0 : 1; /* Power down? */
sja1105_cgu_idiv_packing(packed_buf, &idiv, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->cgu_idiv[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static void
sja1105_cgu_mii_control_packing(void *buf, struct sja1105_cgu_mii_ctrl *cmd,
enum packing_op op)
{
const int size = 4;
sja1105_packing(buf, &cmd->clksrc, 28, 24, size, op);
sja1105_packing(buf, &cmd->autoblock, 11, 11, size, op);
sja1105_packing(buf, &cmd->pd, 0, 0, size, op);
}
static int sja1105_cgu_mii_tx_clk_config(struct sja1105_private *priv,
int port, sja1105_mii_role_t role)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cgu_mii_ctrl mii_tx_clk;
static const int mac_clk_sources[] = {
CLKSRC_MII0_TX_CLK,
CLKSRC_MII1_TX_CLK,
CLKSRC_MII2_TX_CLK,
CLKSRC_MII3_TX_CLK,
CLKSRC_MII4_TX_CLK,
};
static const int phy_clk_sources[] = {
CLKSRC_IDIV0,
CLKSRC_IDIV1,
CLKSRC_IDIV2,
CLKSRC_IDIV3,
CLKSRC_IDIV4,
};
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
int clksrc;
if (regs->mii_tx_clk[port] == SJA1105_RSV_ADDR)
return 0;
if (role == XMII_MAC)
clksrc = mac_clk_sources[port];
else
clksrc = phy_clk_sources[port];
/* Payload for packed_buf */
mii_tx_clk.clksrc = clksrc;
mii_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */
mii_tx_clk.pd = 0; /* Power Down off => enabled */
sja1105_cgu_mii_control_packing(packed_buf, &mii_tx_clk, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_tx_clk[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static int
sja1105_cgu_mii_rx_clk_config(struct sja1105_private *priv, int port)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cgu_mii_ctrl mii_rx_clk;
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
static const int clk_sources[] = {
CLKSRC_MII0_RX_CLK,
CLKSRC_MII1_RX_CLK,
CLKSRC_MII2_RX_CLK,
CLKSRC_MII3_RX_CLK,
CLKSRC_MII4_RX_CLK,
};
if (regs->mii_rx_clk[port] == SJA1105_RSV_ADDR)
return 0;
/* Payload for packed_buf */
mii_rx_clk.clksrc = clk_sources[port];
mii_rx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */
mii_rx_clk.pd = 0; /* Power Down off => enabled */
sja1105_cgu_mii_control_packing(packed_buf, &mii_rx_clk, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_rx_clk[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static int
sja1105_cgu_mii_ext_tx_clk_config(struct sja1105_private *priv, int port)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cgu_mii_ctrl mii_ext_tx_clk;
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
static const int clk_sources[] = {
CLKSRC_IDIV0,
CLKSRC_IDIV1,
CLKSRC_IDIV2,
CLKSRC_IDIV3,
CLKSRC_IDIV4,
};
if (regs->mii_ext_tx_clk[port] == SJA1105_RSV_ADDR)
return 0;
/* Payload for packed_buf */
mii_ext_tx_clk.clksrc = clk_sources[port];
mii_ext_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */
mii_ext_tx_clk.pd = 0; /* Power Down off => enabled */
sja1105_cgu_mii_control_packing(packed_buf, &mii_ext_tx_clk, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_ext_tx_clk[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static int
sja1105_cgu_mii_ext_rx_clk_config(struct sja1105_private *priv, int port)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cgu_mii_ctrl mii_ext_rx_clk;
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
static const int clk_sources[] = {
CLKSRC_IDIV0,
CLKSRC_IDIV1,
CLKSRC_IDIV2,
CLKSRC_IDIV3,
CLKSRC_IDIV4,
};
if (regs->mii_ext_rx_clk[port] == SJA1105_RSV_ADDR)
return 0;
/* Payload for packed_buf */
mii_ext_rx_clk.clksrc = clk_sources[port];
mii_ext_rx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */
mii_ext_rx_clk.pd = 0; /* Power Down off => enabled */
sja1105_cgu_mii_control_packing(packed_buf, &mii_ext_rx_clk, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_ext_rx_clk[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static int sja1105_mii_clocking_setup(struct sja1105_private *priv, int port,
sja1105_mii_role_t role)
{
struct device *dev = priv->ds->dev;
int rc;
dev_dbg(dev, "Configuring MII-%s clocking\n",
(role == XMII_MAC) ? "MAC" : "PHY");
/* If role is MAC, disable IDIV
* If role is PHY, enable IDIV and configure for 1/1 divider
*/
rc = sja1105_cgu_idiv_config(priv, port, (role == XMII_PHY), 1);
if (rc < 0)
return rc;
/* Configure CLKSRC of MII_TX_CLK_n
* * If role is MAC, select TX_CLK_n
* * If role is PHY, select IDIV_n
*/
rc = sja1105_cgu_mii_tx_clk_config(priv, port, role);
if (rc < 0)
return rc;
/* Configure CLKSRC of MII_RX_CLK_n
* Select RX_CLK_n
*/
rc = sja1105_cgu_mii_rx_clk_config(priv, port);
if (rc < 0)
return rc;
if (role == XMII_PHY) {
/* Per MII spec, the PHY (which is us) drives the TX_CLK pin */
/* Configure CLKSRC of EXT_TX_CLK_n
* Select IDIV_n
*/
rc = sja1105_cgu_mii_ext_tx_clk_config(priv, port);
if (rc < 0)
return rc;
/* Configure CLKSRC of EXT_RX_CLK_n
* Select IDIV_n
*/
rc = sja1105_cgu_mii_ext_rx_clk_config(priv, port);
if (rc < 0)
return rc;
}
return 0;
}
static void
sja1105_cgu_pll_control_packing(void *buf, struct sja1105_cgu_pll_ctrl *cmd,
enum packing_op op)
{
const int size = 4;
sja1105_packing(buf, &cmd->pllclksrc, 28, 24, size, op);
sja1105_packing(buf, &cmd->msel, 23, 16, size, op);
sja1105_packing(buf, &cmd->autoblock, 11, 11, size, op);
sja1105_packing(buf, &cmd->psel, 9, 8, size, op);
sja1105_packing(buf, &cmd->direct, 7, 7, size, op);
sja1105_packing(buf, &cmd->fbsel, 6, 6, size, op);
sja1105_packing(buf, &cmd->bypass, 1, 1, size, op);
sja1105_packing(buf, &cmd->pd, 0, 0, size, op);
}
static int sja1105_cgu_rgmii_tx_clk_config(struct sja1105_private *priv,
int port, u64 speed)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cgu_mii_ctrl txc;
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
int clksrc;
if (regs->rgmii_tx_clk[port] == SJA1105_RSV_ADDR)
return 0;
if (speed == priv->info->port_speed[SJA1105_SPEED_1000MBPS]) {
clksrc = CLKSRC_PLL0;
} else {
static const int clk_sources[] = {
CLKSRC_IDIV0,
CLKSRC_IDIV1,
CLKSRC_IDIV2,
CLKSRC_IDIV3,
CLKSRC_IDIV4,
};
clksrc = clk_sources[port];
}
/* RGMII: 125MHz for 1000, 25MHz for 100, 2.5MHz for 10 */
txc.clksrc = clksrc;
/* Autoblock clk while changing clksrc */
txc.autoblock = 1;
/* Power Down off => enabled */
txc.pd = 0;
sja1105_cgu_mii_control_packing(packed_buf, &txc, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->rgmii_tx_clk[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
/* AGU */
static void
sja1105_cfg_pad_mii_packing(void *buf, struct sja1105_cfg_pad_mii *cmd,
enum packing_op op)
{
const int size = 4;
sja1105_packing(buf, &cmd->d32_os, 28, 27, size, op);
sja1105_packing(buf, &cmd->d32_ih, 26, 26, size, op);
sja1105_packing(buf, &cmd->d32_ipud, 25, 24, size, op);
sja1105_packing(buf, &cmd->d10_os, 20, 19, size, op);
sja1105_packing(buf, &cmd->d10_ih, 18, 18, size, op);
sja1105_packing(buf, &cmd->d10_ipud, 17, 16, size, op);
sja1105_packing(buf, &cmd->ctrl_os, 12, 11, size, op);
sja1105_packing(buf, &cmd->ctrl_ih, 10, 10, size, op);
sja1105_packing(buf, &cmd->ctrl_ipud, 9, 8, size, op);
sja1105_packing(buf, &cmd->clk_os, 4, 3, size, op);
sja1105_packing(buf, &cmd->clk_ih, 2, 2, size, op);
sja1105_packing(buf, &cmd->clk_ipud, 1, 0, size, op);
}
static int sja1105_rgmii_cfg_pad_tx_config(struct sja1105_private *priv,
int port)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cfg_pad_mii pad_mii_tx = {0};
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
if (regs->pad_mii_tx[port] == SJA1105_RSV_ADDR)
return 0;
/* Payload */
pad_mii_tx.d32_os = 3; /* TXD[3:2] output stage: */
/* high noise/high speed */
pad_mii_tx.d10_os = 3; /* TXD[1:0] output stage: */
/* high noise/high speed */
pad_mii_tx.d32_ipud = 2; /* TXD[3:2] input stage: */
/* plain input (default) */
pad_mii_tx.d10_ipud = 2; /* TXD[1:0] input stage: */
/* plain input (default) */
pad_mii_tx.ctrl_os = 3; /* TX_CTL / TX_ER output stage */
pad_mii_tx.ctrl_ipud = 2; /* TX_CTL / TX_ER input stage (default) */
pad_mii_tx.clk_os = 3; /* TX_CLK output stage */
pad_mii_tx.clk_ih = 0; /* TX_CLK input hysteresis (default) */
pad_mii_tx.clk_ipud = 2; /* TX_CLK input stage (default) */
sja1105_cfg_pad_mii_packing(packed_buf, &pad_mii_tx, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_tx[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static int sja1105_cfg_pad_rx_config(struct sja1105_private *priv, int port)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cfg_pad_mii pad_mii_rx = {0};
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
if (regs->pad_mii_rx[port] == SJA1105_RSV_ADDR)
return 0;
/* Payload */
pad_mii_rx.d32_ih = 0; /* RXD[3:2] input stage hysteresis: */
/* non-Schmitt (default) */
pad_mii_rx.d32_ipud = 2; /* RXD[3:2] input weak pull-up/down */
/* plain input (default) */
pad_mii_rx.d10_ih = 0; /* RXD[1:0] input stage hysteresis: */
/* non-Schmitt (default) */
pad_mii_rx.d10_ipud = 2; /* RXD[1:0] input weak pull-up/down */
/* plain input (default) */
pad_mii_rx.ctrl_ih = 0; /* RX_DV/CRS_DV/RX_CTL and RX_ER */
/* input stage hysteresis: */
/* non-Schmitt (default) */
pad_mii_rx.ctrl_ipud = 3; /* RX_DV/CRS_DV/RX_CTL and RX_ER */
/* input stage weak pull-up/down: */
/* pull-down */
pad_mii_rx.clk_os = 2; /* RX_CLK/RXC output stage: */
/* medium noise/fast speed (default) */
pad_mii_rx.clk_ih = 0; /* RX_CLK/RXC input hysteresis: */
/* non-Schmitt (default) */
pad_mii_rx.clk_ipud = 2; /* RX_CLK/RXC input pull-up/down: */
/* plain input (default) */
sja1105_cfg_pad_mii_packing(packed_buf, &pad_mii_rx, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_rx[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static void
sja1105_cfg_pad_mii_id_packing(void *buf, struct sja1105_cfg_pad_mii_id *cmd,
enum packing_op op)
{
const int size = SJA1105_SIZE_CGU_CMD;
sja1105_packing(buf, &cmd->rxc_stable_ovr, 15, 15, size, op);
sja1105_packing(buf, &cmd->rxc_delay, 14, 10, size, op);
sja1105_packing(buf, &cmd->rxc_bypass, 9, 9, size, op);
sja1105_packing(buf, &cmd->rxc_pd, 8, 8, size, op);
sja1105_packing(buf, &cmd->txc_stable_ovr, 7, 7, size, op);
sja1105_packing(buf, &cmd->txc_delay, 6, 2, size, op);
sja1105_packing(buf, &cmd->txc_bypass, 1, 1, size, op);
sja1105_packing(buf, &cmd->txc_pd, 0, 0, size, op);
}
static void
sja1110_cfg_pad_mii_id_packing(void *buf, struct sja1105_cfg_pad_mii_id *cmd,
enum packing_op op)
{
const int size = SJA1105_SIZE_CGU_CMD;
u64 range = 4;
/* Fields RXC_RANGE and TXC_RANGE select the input frequency range:
* 0 = 2.5MHz
* 1 = 25MHz
* 2 = 50MHz
* 3 = 125MHz
* 4 = Automatically determined by port speed.
* There's no point in defining a structure different than the one for
* SJA1105, so just hardcode the frequency range to automatic, just as
* before.
*/
sja1105_packing(buf, &cmd->rxc_stable_ovr, 26, 26, size, op);
sja1105_packing(buf, &cmd->rxc_delay, 25, 21, size, op);
sja1105_packing(buf, &range, 20, 18, size, op);
sja1105_packing(buf, &cmd->rxc_bypass, 17, 17, size, op);
sja1105_packing(buf, &cmd->rxc_pd, 16, 16, size, op);
sja1105_packing(buf, &cmd->txc_stable_ovr, 10, 10, size, op);
sja1105_packing(buf, &cmd->txc_delay, 9, 5, size, op);
sja1105_packing(buf, &range, 4, 2, size, op);
sja1105_packing(buf, &cmd->txc_bypass, 1, 1, size, op);
sja1105_packing(buf, &cmd->txc_pd, 0, 0, size, op);
}
/* The RGMII delay setup procedure is 2-step and gets called upon each
* .phylink_mac_config. Both are strategic.
* The reason is that the RX Tunable Delay Line of the SJA1105 MAC has issues
* with recovering from a frequency change of the link partner's RGMII clock.
* The easiest way to recover from this is to temporarily power down the TDL,
* as it will re-lock at the new frequency afterwards.
*/
int sja1105pqrs_setup_rgmii_delay(const void *ctx, int port)
{
const struct sja1105_private *priv = ctx;
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cfg_pad_mii_id pad_mii_id = {0};
int rx_delay = priv->rgmii_rx_delay_ps[port];
int tx_delay = priv->rgmii_tx_delay_ps[port];
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
int rc;
if (rx_delay)
pad_mii_id.rxc_delay = SJA1105_RGMII_DELAY_PS_TO_HW(rx_delay);
if (tx_delay)
pad_mii_id.txc_delay = SJA1105_RGMII_DELAY_PS_TO_HW(tx_delay);
/* Stage 1: Turn the RGMII delay lines off. */
pad_mii_id.rxc_bypass = 1;
pad_mii_id.rxc_pd = 1;
pad_mii_id.txc_bypass = 1;
pad_mii_id.txc_pd = 1;
sja1105_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK);
rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_id[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
if (rc < 0)
return rc;
/* Stage 2: Turn the RGMII delay lines on. */
if (rx_delay) {
pad_mii_id.rxc_bypass = 0;
pad_mii_id.rxc_pd = 0;
}
if (tx_delay) {
pad_mii_id.txc_bypass = 0;
pad_mii_id.txc_pd = 0;
}
sja1105_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_id[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
int sja1110_setup_rgmii_delay(const void *ctx, int port)
{
const struct sja1105_private *priv = ctx;
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cfg_pad_mii_id pad_mii_id = {0};
int rx_delay = priv->rgmii_rx_delay_ps[port];
int tx_delay = priv->rgmii_tx_delay_ps[port];
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
pad_mii_id.rxc_pd = 1;
pad_mii_id.txc_pd = 1;
if (rx_delay) {
pad_mii_id.rxc_delay = SJA1105_RGMII_DELAY_PS_TO_HW(rx_delay);
/* The "BYPASS" bit in SJA1110 is actually a "don't bypass" */
pad_mii_id.rxc_bypass = 1;
pad_mii_id.rxc_pd = 0;
}
if (tx_delay) {
pad_mii_id.txc_delay = SJA1105_RGMII_DELAY_PS_TO_HW(tx_delay);
pad_mii_id.txc_bypass = 1;
pad_mii_id.txc_pd = 0;
}
sja1110_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_id[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static int sja1105_rgmii_clocking_setup(struct sja1105_private *priv, int port,
sja1105_mii_role_t role)
{
struct device *dev = priv->ds->dev;
struct sja1105_mac_config_entry *mac;
u64 speed;
int rc;
mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries;
speed = mac[port].speed;
dev_dbg(dev, "Configuring port %d RGMII at speed %lldMbps\n",
port, speed);
if (speed == priv->info->port_speed[SJA1105_SPEED_1000MBPS]) {
/* 1000Mbps, IDIV disabled (125 MHz) */
rc = sja1105_cgu_idiv_config(priv, port, false, 1);
} else if (speed == priv->info->port_speed[SJA1105_SPEED_100MBPS]) {
/* 100Mbps, IDIV enabled, divide by 1 (25 MHz) */
rc = sja1105_cgu_idiv_config(priv, port, true, 1);
} else if (speed == priv->info->port_speed[SJA1105_SPEED_10MBPS]) {
/* 10Mbps, IDIV enabled, divide by 10 (2.5 MHz) */
rc = sja1105_cgu_idiv_config(priv, port, true, 10);
} else if (speed == priv->info->port_speed[SJA1105_SPEED_AUTO]) {
/* Skip CGU configuration if there is no speed available
* (e.g. link is not established yet)
*/
dev_dbg(dev, "Speed not available, skipping CGU config\n");
return 0;
} else {
rc = -EINVAL;
}
if (rc < 0) {
dev_err(dev, "Failed to configure idiv\n");
return rc;
}
rc = sja1105_cgu_rgmii_tx_clk_config(priv, port, speed);
if (rc < 0) {
dev_err(dev, "Failed to configure RGMII Tx clock\n");
return rc;
}
rc = sja1105_rgmii_cfg_pad_tx_config(priv, port);
if (rc < 0) {
dev_err(dev, "Failed to configure Tx pad registers\n");
return rc;
}
if (!priv->info->setup_rgmii_delay)
return 0;
return priv->info->setup_rgmii_delay(priv, port);
}
static int sja1105_cgu_rmii_ref_clk_config(struct sja1105_private *priv,
int port)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cgu_mii_ctrl ref_clk;
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
static const int clk_sources[] = {
CLKSRC_MII0_TX_CLK,
CLKSRC_MII1_TX_CLK,
CLKSRC_MII2_TX_CLK,
CLKSRC_MII3_TX_CLK,
CLKSRC_MII4_TX_CLK,
};
if (regs->rmii_ref_clk[port] == SJA1105_RSV_ADDR)
return 0;
/* Payload for packed_buf */
ref_clk.clksrc = clk_sources[port];
ref_clk.autoblock = 1; /* Autoblock clk while changing clksrc */
ref_clk.pd = 0; /* Power Down off => enabled */
sja1105_cgu_mii_control_packing(packed_buf, &ref_clk, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_ref_clk[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static int
sja1105_cgu_rmii_ext_tx_clk_config(struct sja1105_private *priv, int port)
{
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_cgu_mii_ctrl ext_tx_clk;
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
if (regs->rmii_ext_tx_clk[port] == SJA1105_RSV_ADDR)
return 0;
/* Payload for packed_buf */
ext_tx_clk.clksrc = CLKSRC_PLL1;
ext_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */
ext_tx_clk.pd = 0; /* Power Down off => enabled */
sja1105_cgu_mii_control_packing(packed_buf, &ext_tx_clk, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_ext_tx_clk[port],
packed_buf, SJA1105_SIZE_CGU_CMD);
}
static int sja1105_cgu_rmii_pll_config(struct sja1105_private *priv)
{
const struct sja1105_regs *regs = priv->info->regs;
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
struct sja1105_cgu_pll_ctrl pll = {0};
struct device *dev = priv->ds->dev;
int rc;
if (regs->rmii_pll1 == SJA1105_RSV_ADDR)
return 0;
/* PLL1 must be enabled and output 50 Mhz.
* This is done by writing first 0x0A010941 to
* the PLL_1_C register and then deasserting
* power down (PD) 0x0A010940.
*/
/* Step 1: PLL1 setup for 50Mhz */
pll.pllclksrc = 0xA;
pll.msel = 0x1;
pll.autoblock = 0x1;
pll.psel = 0x1;
pll.direct = 0x0;
pll.fbsel = 0x1;
pll.bypass = 0x0;
pll.pd = 0x1;
sja1105_cgu_pll_control_packing(packed_buf, &pll, PACK);
rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_pll1, packed_buf,
SJA1105_SIZE_CGU_CMD);
if (rc < 0) {
dev_err(dev, "failed to configure PLL1 for 50MHz\n");
return rc;
}
/* Step 2: Enable PLL1 */
pll.pd = 0x0;
sja1105_cgu_pll_control_packing(packed_buf, &pll, PACK);
rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_pll1, packed_buf,
SJA1105_SIZE_CGU_CMD);
if (rc < 0) {
dev_err(dev, "failed to enable PLL1\n");
return rc;
}
return rc;
}
static int sja1105_rmii_clocking_setup(struct sja1105_private *priv, int port,
sja1105_mii_role_t role)
{
struct device *dev = priv->ds->dev;
int rc;
dev_dbg(dev, "Configuring RMII-%s clocking\n",
(role == XMII_MAC) ? "MAC" : "PHY");
/* AH1601.pdf chapter 2.5.1. Sources */
if (role == XMII_MAC) {
/* Configure and enable PLL1 for 50Mhz output */
rc = sja1105_cgu_rmii_pll_config(priv);
if (rc < 0)
return rc;
}
/* Disable IDIV for this port */
rc = sja1105_cgu_idiv_config(priv, port, false, 1);
if (rc < 0)
return rc;
/* Source to sink mappings */
rc = sja1105_cgu_rmii_ref_clk_config(priv, port);
if (rc < 0)
return rc;
if (role == XMII_MAC) {
rc = sja1105_cgu_rmii_ext_tx_clk_config(priv, port);
if (rc < 0)
return rc;
}
return 0;
}
int sja1105_clocking_setup_port(struct sja1105_private *priv, int port)
{
struct sja1105_xmii_params_entry *mii;
struct device *dev = priv->ds->dev;
sja1105_phy_interface_t phy_mode;
sja1105_mii_role_t role;
int rc;
mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries;
/* RGMII etc */
phy_mode = mii->xmii_mode[port];
/* MAC or PHY, for applicable types (not RGMII) */
role = mii->phy_mac[port];
switch (phy_mode) {
case XMII_MODE_MII:
rc = sja1105_mii_clocking_setup(priv, port, role);
break;
case XMII_MODE_RMII:
rc = sja1105_rmii_clocking_setup(priv, port, role);
break;
case XMII_MODE_RGMII:
rc = sja1105_rgmii_clocking_setup(priv, port, role);
break;
case XMII_MODE_SGMII:
/* Nothing to do in the CGU for SGMII */
rc = 0;
break;
default:
dev_err(dev, "Invalid interface mode specified: %d\n",
phy_mode);
return -EINVAL;
}
if (rc) {
dev_err(dev, "Clocking setup for port %d failed: %d\n",
port, rc);
return rc;
}
/* Internally pull down the RX_DV/CRS_DV/RX_CTL and RX_ER inputs */
return sja1105_cfg_pad_rx_config(priv, port);
}
int sja1105_clocking_setup(struct sja1105_private *priv)
{
struct dsa_switch *ds = priv->ds;
int port, rc;
for (port = 0; port < ds->num_ports; port++) {
rc = sja1105_clocking_setup_port(priv, port);
if (rc < 0)
return rc;
}
return 0;
}
static void
sja1110_cgu_outclk_packing(void *buf, struct sja1110_cgu_outclk *outclk,
enum packing_op op)
{
const int size = 4;
sja1105_packing(buf, &outclk->clksrc, 27, 24, size, op);
sja1105_packing(buf, &outclk->autoblock, 11, 11, size, op);
sja1105_packing(buf, &outclk->pd, 0, 0, size, op);
}
int sja1110_disable_microcontroller(struct sja1105_private *priv)
{
u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0};
struct sja1110_cgu_outclk outclk_6_c = {
.clksrc = 0x3,
.pd = true,
};
struct sja1110_cgu_outclk outclk_7_c = {
.clksrc = 0x5,
.pd = true,
};
int rc;
/* Power down the BASE_TIMER_CLK to disable the watchdog timer */
sja1110_cgu_outclk_packing(packed_buf, &outclk_7_c, PACK);
rc = sja1105_xfer_buf(priv, SPI_WRITE, SJA1110_BASE_TIMER_CLK,
packed_buf, SJA1105_SIZE_CGU_CMD);
if (rc)
return rc;
/* Power down the BASE_MCSS_CLOCK to gate the microcontroller off */
sja1110_cgu_outclk_packing(packed_buf, &outclk_6_c, PACK);
return sja1105_xfer_buf(priv, SPI_WRITE, SJA1110_BASE_MCSS_CLK,
packed_buf, SJA1105_SIZE_CGU_CMD);
}
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
* Copyright 2020 NXP
*/
#include "sja1105.h"
/* Since devlink regions have a fixed size and the static config has a variable
* size, we need to calculate the maximum possible static config size by
* creating a dummy config with all table entries populated to the max, and get
* its packed length. This is done dynamically as opposed to simply hardcoding
* a number, since currently not all static config tables are implemented, so
* we are avoiding a possible code desynchronization.
*/
static size_t sja1105_static_config_get_max_size(struct sja1105_private *priv)
{
struct sja1105_static_config config;
enum sja1105_blk_idx blk_idx;
int rc;
rc = sja1105_static_config_init(&config,
priv->info->static_ops,
priv->info->device_id);
if (rc)
return 0;
for (blk_idx = 0; blk_idx < BLK_IDX_MAX; blk_idx++) {
struct sja1105_table *table = &config.tables[blk_idx];
table->entry_count = table->ops->max_entry_count;
}
return sja1105_static_config_get_length(&config);
}
static int
sja1105_region_static_config_snapshot(struct devlink *dl,
const struct devlink_region_ops *ops,
struct netlink_ext_ack *extack,
u8 **data)
{
struct dsa_switch *ds = dsa_devlink_to_ds(dl);
struct sja1105_private *priv = ds->priv;
size_t max_len, len;
len = sja1105_static_config_get_length(&priv->static_config);
max_len = sja1105_static_config_get_max_size(priv);
*data = kcalloc(max_len, sizeof(u8), GFP_KERNEL);
if (!*data)
return -ENOMEM;
return static_config_buf_prepare_for_upload(priv, *data, len);
}
static struct devlink_region_ops sja1105_region_static_config_ops = {
.name = "static-config",
.snapshot = sja1105_region_static_config_snapshot,
.destructor = kfree,
};
enum sja1105_region_id {
SJA1105_REGION_STATIC_CONFIG = 0,
};
struct sja1105_region {
const struct devlink_region_ops *ops;
size_t (*get_size)(struct sja1105_private *priv);
};
static struct sja1105_region sja1105_regions[] = {
[SJA1105_REGION_STATIC_CONFIG] = {
.ops = &sja1105_region_static_config_ops,
.get_size = sja1105_static_config_get_max_size,
},
};
static int sja1105_setup_devlink_regions(struct dsa_switch *ds)
{
int i, num_regions = ARRAY_SIZE(sja1105_regions);
struct sja1105_private *priv = ds->priv;
const struct devlink_region_ops *ops;
struct devlink_region *region;
u64 size;
priv->regions = kzalloc_objs(struct devlink_region *, num_regions);
if (!priv->regions)
return -ENOMEM;
for (i = 0; i < num_regions; i++) {
size = sja1105_regions[i].get_size(priv);
ops = sja1105_regions[i].ops;
region = dsa_devlink_region_create(ds, ops, 1, size);
if (IS_ERR(region)) {
while (--i >= 0)
dsa_devlink_region_destroy(priv->regions[i]);
kfree(priv->regions);
return PTR_ERR(region);
}
priv->regions[i] = region;
}
return 0;
}
static void sja1105_teardown_devlink_regions(struct dsa_switch *ds)
{
int i, num_regions = ARRAY_SIZE(sja1105_regions);
struct sja1105_private *priv = ds->priv;
for (i = 0; i < num_regions; i++)
dsa_devlink_region_destroy(priv->regions[i]);
kfree(priv->regions);
}
int sja1105_devlink_info_get(struct dsa_switch *ds,
struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
struct sja1105_private *priv = ds->priv;
return devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
priv->info->name);
}
int sja1105_devlink_setup(struct dsa_switch *ds)
{
return sja1105_setup_devlink_regions(ds);
}
void sja1105_devlink_teardown(struct dsa_switch *ds)
{
sja1105_teardown_devlink_regions(ds);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com>
*/
#ifndef _SJA1105_DYNAMIC_CONFIG_H
#define _SJA1105_DYNAMIC_CONFIG_H
#include "sja1105.h"
#include <linux/packing.h>
/* Special index that can be used for sja1105_dynamic_config_read */
#define SJA1105_SEARCH -1
struct sja1105_dyn_cmd;
struct sja1105_dynamic_table_ops {
/* This returns size_t just to keep same prototype as the
* static config ops, of which we are reusing some functions.
*/
size_t (*entry_packing)(void *buf, void *entry_ptr, enum packing_op op);
void (*cmd_packing)(void *buf, struct sja1105_dyn_cmd *cmd,
enum packing_op op);
size_t max_entry_count;
size_t packed_size;
u64 addr;
u8 access;
};
struct sja1105_mgmt_entry {
u64 tsreg;
u64 takets;
u64 macaddr;
u64 destports;
u64 enfport;
u64 index;
};
extern const struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN];
extern const struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN];
extern const struct sja1105_dynamic_table_ops sja1110_dyn_ops[BLK_IDX_MAX_DYN];
#endif
@@ -0,0 +1,633 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
*/
#include "sja1105.h"
enum sja1105_counter_index {
__SJA1105_COUNTER_UNUSED,
/* MAC */
N_RUNT,
N_SOFERR,
N_ALIGNERR,
N_MIIERR,
TYPEERR,
SIZEERR,
TCTIMEOUT,
PRIORERR,
NOMASTER,
MEMOV,
MEMERR,
INVTYP,
INTCYOV,
DOMERR,
PCFBAGDROP,
SPCPRIOR,
AGEPRIOR,
PORTDROP,
LENDROP,
BAGDROP,
POLICEERR,
DRPNONA664ERR,
SPCERR,
AGEDRP,
/* HL1 */
N_N664ERR,
N_VLANERR,
N_UNRELEASED,
N_SIZEERR,
N_CRCERR,
N_VLNOTFOUND,
N_CTPOLERR,
N_POLERR,
N_RXFRM,
N_RXBYTE,
N_TXFRM,
N_TXBYTE,
/* HL2 */
N_QFULL,
N_PART_DROP,
N_EGR_DISABLED,
N_NOT_REACH,
__MAX_SJA1105ET_PORT_COUNTER,
/* P/Q/R/S only */
/* ETHER */
N_DROPS_NOLEARN = __MAX_SJA1105ET_PORT_COUNTER,
N_DROPS_NOROUTE,
N_DROPS_ILL_DTAG,
N_DROPS_DTAG,
N_DROPS_SOTAG,
N_DROPS_SITAG,
N_DROPS_UTAG,
N_TX_BYTES_1024_2047,
N_TX_BYTES_512_1023,
N_TX_BYTES_256_511,
N_TX_BYTES_128_255,
N_TX_BYTES_65_127,
N_TX_BYTES_64,
N_TX_MCAST,
N_TX_BCAST,
N_RX_BYTES_1024_2047,
N_RX_BYTES_512_1023,
N_RX_BYTES_256_511,
N_RX_BYTES_128_255,
N_RX_BYTES_65_127,
N_RX_BYTES_64,
N_RX_MCAST,
N_RX_BCAST,
__MAX_SJA1105PQRS_PORT_COUNTER,
};
struct sja1105_port_counter {
enum sja1105_stats_area area;
const char name[ETH_GSTRING_LEN];
int offset;
int start;
int end;
bool is_64bit;
};
static const struct sja1105_port_counter sja1105_port_counters[] = {
/* MAC-Level Diagnostic Counters */
[N_RUNT] = {
.area = MAC,
.name = "n_runt",
.offset = 0,
.start = 31,
.end = 24,
},
[N_SOFERR] = {
.area = MAC,
.name = "n_soferr",
.offset = 0x0,
.start = 23,
.end = 16,
},
[N_ALIGNERR] = {
.area = MAC,
.name = "n_alignerr",
.offset = 0x0,
.start = 15,
.end = 8,
},
[N_MIIERR] = {
.area = MAC,
.name = "n_miierr",
.offset = 0x0,
.start = 7,
.end = 0,
},
/* MAC-Level Diagnostic Flags */
[TYPEERR] = {
.area = MAC,
.name = "typeerr",
.offset = 0x1,
.start = 27,
.end = 27,
},
[SIZEERR] = {
.area = MAC,
.name = "sizeerr",
.offset = 0x1,
.start = 26,
.end = 26,
},
[TCTIMEOUT] = {
.area = MAC,
.name = "tctimeout",
.offset = 0x1,
.start = 25,
.end = 25,
},
[PRIORERR] = {
.area = MAC,
.name = "priorerr",
.offset = 0x1,
.start = 24,
.end = 24,
},
[NOMASTER] = {
.area = MAC,
.name = "nomaster",
.offset = 0x1,
.start = 23,
.end = 23,
},
[MEMOV] = {
.area = MAC,
.name = "memov",
.offset = 0x1,
.start = 22,
.end = 22,
},
[MEMERR] = {
.area = MAC,
.name = "memerr",
.offset = 0x1,
.start = 21,
.end = 21,
},
[INVTYP] = {
.area = MAC,
.name = "invtyp",
.offset = 0x1,
.start = 19,
.end = 19,
},
[INTCYOV] = {
.area = MAC,
.name = "intcyov",
.offset = 0x1,
.start = 18,
.end = 18,
},
[DOMERR] = {
.area = MAC,
.name = "domerr",
.offset = 0x1,
.start = 17,
.end = 17,
},
[PCFBAGDROP] = {
.area = MAC,
.name = "pcfbagdrop",
.offset = 0x1,
.start = 16,
.end = 16,
},
[SPCPRIOR] = {
.area = MAC,
.name = "spcprior",
.offset = 0x1,
.start = 15,
.end = 12,
},
[AGEPRIOR] = {
.area = MAC,
.name = "ageprior",
.offset = 0x1,
.start = 11,
.end = 8,
},
[PORTDROP] = {
.area = MAC,
.name = "portdrop",
.offset = 0x1,
.start = 6,
.end = 6,
},
[LENDROP] = {
.area = MAC,
.name = "lendrop",
.offset = 0x1,
.start = 5,
.end = 5,
},
[BAGDROP] = {
.area = MAC,
.name = "bagdrop",
.offset = 0x1,
.start = 4,
.end = 4,
},
[POLICEERR] = {
.area = MAC,
.name = "policeerr",
.offset = 0x1,
.start = 3,
.end = 3,
},
[DRPNONA664ERR] = {
.area = MAC,
.name = "drpnona664err",
.offset = 0x1,
.start = 2,
.end = 2,
},
[SPCERR] = {
.area = MAC,
.name = "spcerr",
.offset = 0x1,
.start = 1,
.end = 1,
},
[AGEDRP] = {
.area = MAC,
.name = "agedrp",
.offset = 0x1,
.start = 0,
.end = 0,
},
/* High-Level Diagnostic Counters */
[N_N664ERR] = {
.area = HL1,
.name = "n_n664err",
.offset = 0xF,
.start = 31,
.end = 0,
},
[N_VLANERR] = {
.area = HL1,
.name = "n_vlanerr",
.offset = 0xE,
.start = 31,
.end = 0,
},
[N_UNRELEASED] = {
.area = HL1,
.name = "n_unreleased",
.offset = 0xD,
.start = 31,
.end = 0,
},
[N_SIZEERR] = {
.area = HL1,
.name = "n_sizeerr",
.offset = 0xC,
.start = 31,
.end = 0,
},
[N_CRCERR] = {
.area = HL1,
.name = "n_crcerr",
.offset = 0xB,
.start = 31,
.end = 0,
},
[N_VLNOTFOUND] = {
.area = HL1,
.name = "n_vlnotfound",
.offset = 0xA,
.start = 31,
.end = 0,
},
[N_CTPOLERR] = {
.area = HL1,
.name = "n_ctpolerr",
.offset = 0x9,
.start = 31,
.end = 0,
},
[N_POLERR] = {
.area = HL1,
.name = "n_polerr",
.offset = 0x8,
.start = 31,
.end = 0,
},
[N_RXFRM] = {
.area = HL1,
.name = "n_rxfrm",
.offset = 0x6,
.start = 31,
.end = 0,
.is_64bit = true,
},
[N_RXBYTE] = {
.area = HL1,
.name = "n_rxbyte",
.offset = 0x4,
.start = 31,
.end = 0,
.is_64bit = true,
},
[N_TXFRM] = {
.area = HL1,
.name = "n_txfrm",
.offset = 0x2,
.start = 31,
.end = 0,
.is_64bit = true,
},
[N_TXBYTE] = {
.area = HL1,
.name = "n_txbyte",
.offset = 0x0,
.start = 31,
.end = 0,
.is_64bit = true,
},
[N_QFULL] = {
.area = HL2,
.name = "n_qfull",
.offset = 0x3,
.start = 31,
.end = 0,
},
[N_PART_DROP] = {
.area = HL2,
.name = "n_part_drop",
.offset = 0x2,
.start = 31,
.end = 0,
},
[N_EGR_DISABLED] = {
.area = HL2,
.name = "n_egr_disabled",
.offset = 0x1,
.start = 31,
.end = 0,
},
[N_NOT_REACH] = {
.area = HL2,
.name = "n_not_reach",
.offset = 0x0,
.start = 31,
.end = 0,
},
/* Ether Stats */
[N_DROPS_NOLEARN] = {
.area = ETHER,
.name = "n_drops_nolearn",
.offset = 0x16,
.start = 31,
.end = 0,
},
[N_DROPS_NOROUTE] = {
.area = ETHER,
.name = "n_drops_noroute",
.offset = 0x15,
.start = 31,
.end = 0,
},
[N_DROPS_ILL_DTAG] = {
.area = ETHER,
.name = "n_drops_ill_dtag",
.offset = 0x14,
.start = 31,
.end = 0,
},
[N_DROPS_DTAG] = {
.area = ETHER,
.name = "n_drops_dtag",
.offset = 0x13,
.start = 31,
.end = 0,
},
[N_DROPS_SOTAG] = {
.area = ETHER,
.name = "n_drops_sotag",
.offset = 0x12,
.start = 31,
.end = 0,
},
[N_DROPS_SITAG] = {
.area = ETHER,
.name = "n_drops_sitag",
.offset = 0x11,
.start = 31,
.end = 0,
},
[N_DROPS_UTAG] = {
.area = ETHER,
.name = "n_drops_utag",
.offset = 0x10,
.start = 31,
.end = 0,
},
[N_TX_BYTES_1024_2047] = {
.area = ETHER,
.name = "n_tx_bytes_1024_2047",
.offset = 0x0F,
.start = 31,
.end = 0,
},
[N_TX_BYTES_512_1023] = {
.area = ETHER,
.name = "n_tx_bytes_512_1023",
.offset = 0x0E,
.start = 31,
.end = 0,
},
[N_TX_BYTES_256_511] = {
.area = ETHER,
.name = "n_tx_bytes_256_511",
.offset = 0x0D,
.start = 31,
.end = 0,
},
[N_TX_BYTES_128_255] = {
.area = ETHER,
.name = "n_tx_bytes_128_255",
.offset = 0x0C,
.start = 31,
.end = 0,
},
[N_TX_BYTES_65_127] = {
.area = ETHER,
.name = "n_tx_bytes_65_127",
.offset = 0x0B,
.start = 31,
.end = 0,
},
[N_TX_BYTES_64] = {
.area = ETHER,
.name = "n_tx_bytes_64",
.offset = 0x0A,
.start = 31,
.end = 0,
},
[N_TX_MCAST] = {
.area = ETHER,
.name = "n_tx_mcast",
.offset = 0x09,
.start = 31,
.end = 0,
},
[N_TX_BCAST] = {
.area = ETHER,
.name = "n_tx_bcast",
.offset = 0x08,
.start = 31,
.end = 0,
},
[N_RX_BYTES_1024_2047] = {
.area = ETHER,
.name = "n_rx_bytes_1024_2047",
.offset = 0x07,
.start = 31,
.end = 0,
},
[N_RX_BYTES_512_1023] = {
.area = ETHER,
.name = "n_rx_bytes_512_1023",
.offset = 0x06,
.start = 31,
.end = 0,
},
[N_RX_BYTES_256_511] = {
.area = ETHER,
.name = "n_rx_bytes_256_511",
.offset = 0x05,
.start = 31,
.end = 0,
},
[N_RX_BYTES_128_255] = {
.area = ETHER,
.name = "n_rx_bytes_128_255",
.offset = 0x04,
.start = 31,
.end = 0,
},
[N_RX_BYTES_65_127] = {
.area = ETHER,
.name = "n_rx_bytes_65_127",
.offset = 0x03,
.start = 31,
.end = 0,
},
[N_RX_BYTES_64] = {
.area = ETHER,
.name = "n_rx_bytes_64",
.offset = 0x02,
.start = 31,
.end = 0,
},
[N_RX_MCAST] = {
.area = ETHER,
.name = "n_rx_mcast",
.offset = 0x01,
.start = 31,
.end = 0,
},
[N_RX_BCAST] = {
.area = ETHER,
.name = "n_rx_bcast",
.offset = 0x00,
.start = 31,
.end = 0,
},
};
static int sja1105_port_counter_read(struct sja1105_private *priv, int port,
enum sja1105_counter_index idx, u64 *ctr)
{
const struct sja1105_port_counter *c = &sja1105_port_counters[idx];
size_t size = c->is_64bit ? 8 : 4;
u8 buf[8] = {0};
u64 regs;
int rc;
regs = priv->info->regs->stats[c->area][port];
rc = sja1105_xfer_buf(priv, SPI_READ, regs + c->offset, buf, size);
if (rc)
return rc;
sja1105_unpack(buf, ctr, c->start, c->end, size);
return 0;
}
void sja1105_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data)
{
struct sja1105_private *priv = ds->priv;
enum sja1105_counter_index max_ctr, i;
int rc, k = 0;
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
max_ctr = __MAX_SJA1105ET_PORT_COUNTER;
else
max_ctr = __MAX_SJA1105PQRS_PORT_COUNTER;
for (i = 0; i < max_ctr; i++) {
if (!strlen(sja1105_port_counters[i].name))
continue;
rc = sja1105_port_counter_read(priv, port, i, &data[k++]);
if (rc) {
dev_err(ds->dev,
"Failed to read port %d counters: %d\n",
port, rc);
break;
}
}
}
void sja1105_get_strings(struct dsa_switch *ds, int port,
u32 stringset, u8 *data)
{
struct sja1105_private *priv = ds->priv;
enum sja1105_counter_index max_ctr, i;
if (stringset != ETH_SS_STATS)
return;
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
max_ctr = __MAX_SJA1105ET_PORT_COUNTER;
else
max_ctr = __MAX_SJA1105PQRS_PORT_COUNTER;
for (i = 0; i < max_ctr; i++) {
if (!strlen(sja1105_port_counters[i].name))
continue;
ethtool_puts(&data, sja1105_port_counters[i].name);
}
}
int sja1105_get_sset_count(struct dsa_switch *ds, int port, int sset)
{
struct sja1105_private *priv = ds->priv;
enum sja1105_counter_index max_ctr, i;
int sset_count = 0;
if (sset != ETH_SS_STATS)
return -EOPNOTSUPP;
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
max_ctr = __MAX_SJA1105ET_PORT_COUNTER;
else
max_ctr = __MAX_SJA1105PQRS_PORT_COUNTER;
for (i = 0; i < max_ctr; i++) {
if (!strlen(sja1105_port_counters[i].name))
continue;
sset_count++;
}
return sset_count;
}
@@ -0,0 +1,545 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2020 NXP
*/
#include "sja1105.h"
#include "sja1105_vl.h"
struct sja1105_rule *sja1105_rule_find(struct sja1105_private *priv,
unsigned long cookie)
{
struct sja1105_rule *rule;
list_for_each_entry(rule, &priv->flow_block.rules, list)
if (rule->cookie == cookie)
return rule;
return NULL;
}
static int sja1105_find_free_l2_policer(struct sja1105_private *priv)
{
int i;
for (i = 0; i < SJA1105_NUM_L2_POLICERS; i++)
if (!priv->flow_block.l2_policer_used[i])
return i;
return -1;
}
static int sja1105_setup_bcast_policer(struct sja1105_private *priv,
struct netlink_ext_ack *extack,
unsigned long cookie, int port,
u64 rate_bytes_per_sec,
u32 burst)
{
struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
struct sja1105_l2_policing_entry *policing;
struct dsa_switch *ds = priv->ds;
bool new_rule = false;
unsigned long p;
int rc;
if (!rule) {
rule = kzalloc_obj(*rule);
if (!rule)
return -ENOMEM;
rule->cookie = cookie;
rule->type = SJA1105_RULE_BCAST_POLICER;
rule->bcast_pol.sharindx = sja1105_find_free_l2_policer(priv);
rule->key.type = SJA1105_KEY_BCAST;
new_rule = true;
}
if (rule->bcast_pol.sharindx == -1) {
NL_SET_ERR_MSG_MOD(extack, "No more L2 policers free");
rc = -ENOSPC;
goto out;
}
policing = priv->static_config.tables[BLK_IDX_L2_POLICING].entries;
if (policing[(ds->num_ports * SJA1105_NUM_TC) + port].sharindx != port) {
NL_SET_ERR_MSG_MOD(extack,
"Port already has a broadcast policer");
rc = -EEXIST;
goto out;
}
rule->port_mask |= BIT(port);
/* Make the broadcast policers of all ports attached to this block
* point to the newly allocated policer
*/
for_each_set_bit(p, &rule->port_mask, SJA1105_MAX_NUM_PORTS) {
int bcast = (ds->num_ports * SJA1105_NUM_TC) + p;
policing[bcast].sharindx = rule->bcast_pol.sharindx;
}
policing[rule->bcast_pol.sharindx].rate = div_u64(rate_bytes_per_sec *
512, 1000000);
policing[rule->bcast_pol.sharindx].smax = burst;
/* TODO: support per-flow MTU */
policing[rule->bcast_pol.sharindx].maxlen = VLAN_ETH_FRAME_LEN +
ETH_FCS_LEN;
rc = sja1105_static_config_reload(priv, SJA1105_BEST_EFFORT_POLICING);
out:
if (rc == 0 && new_rule) {
priv->flow_block.l2_policer_used[rule->bcast_pol.sharindx] = true;
list_add(&rule->list, &priv->flow_block.rules);
} else if (new_rule) {
kfree(rule);
}
return rc;
}
static int sja1105_setup_tc_policer(struct sja1105_private *priv,
struct netlink_ext_ack *extack,
unsigned long cookie, int port, int tc,
u64 rate_bytes_per_sec,
u32 burst)
{
struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
struct sja1105_l2_policing_entry *policing;
bool new_rule = false;
unsigned long p;
int rc;
if (!rule) {
rule = kzalloc_obj(*rule);
if (!rule)
return -ENOMEM;
rule->cookie = cookie;
rule->type = SJA1105_RULE_TC_POLICER;
rule->tc_pol.sharindx = sja1105_find_free_l2_policer(priv);
rule->key.type = SJA1105_KEY_TC;
rule->key.tc.pcp = tc;
new_rule = true;
}
if (rule->tc_pol.sharindx == -1) {
NL_SET_ERR_MSG_MOD(extack, "No more L2 policers free");
rc = -ENOSPC;
goto out;
}
policing = priv->static_config.tables[BLK_IDX_L2_POLICING].entries;
if (policing[(port * SJA1105_NUM_TC) + tc].sharindx != port) {
NL_SET_ERR_MSG_MOD(extack,
"Port-TC pair already has an L2 policer");
rc = -EEXIST;
goto out;
}
rule->port_mask |= BIT(port);
/* Make the policers for traffic class @tc of all ports attached to
* this block point to the newly allocated policer
*/
for_each_set_bit(p, &rule->port_mask, SJA1105_MAX_NUM_PORTS) {
int index = (p * SJA1105_NUM_TC) + tc;
policing[index].sharindx = rule->tc_pol.sharindx;
}
policing[rule->tc_pol.sharindx].rate = div_u64(rate_bytes_per_sec *
512, 1000000);
policing[rule->tc_pol.sharindx].smax = burst;
/* TODO: support per-flow MTU */
policing[rule->tc_pol.sharindx].maxlen = VLAN_ETH_FRAME_LEN +
ETH_FCS_LEN;
rc = sja1105_static_config_reload(priv, SJA1105_BEST_EFFORT_POLICING);
out:
if (rc == 0 && new_rule) {
priv->flow_block.l2_policer_used[rule->tc_pol.sharindx] = true;
list_add(&rule->list, &priv->flow_block.rules);
} else if (new_rule) {
kfree(rule);
}
return rc;
}
static int sja1105_flower_policer(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack,
unsigned long cookie,
struct sja1105_key *key,
u64 rate_bytes_per_sec,
u32 burst)
{
switch (key->type) {
case SJA1105_KEY_BCAST:
return sja1105_setup_bcast_policer(priv, extack, cookie, port,
rate_bytes_per_sec, burst);
case SJA1105_KEY_TC:
return sja1105_setup_tc_policer(priv, extack, cookie, port,
key->tc.pcp, rate_bytes_per_sec,
burst);
default:
NL_SET_ERR_MSG_MOD(extack, "Unknown keys for policing");
return -EOPNOTSUPP;
}
}
static int sja1105_flower_parse_key(struct sja1105_private *priv,
struct netlink_ext_ack *extack,
struct flow_cls_offload *cls,
struct sja1105_key *key)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct flow_dissector *dissector = rule->match.dissector;
bool is_bcast_dmac = false;
u64 dmac = U64_MAX;
u16 vid = U16_MAX;
u16 pcp = U16_MAX;
if (dissector->used_keys &
~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
BIT_ULL(FLOW_DISSECTOR_KEY_VLAN) |
BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS))) {
NL_SET_ERR_MSG_MOD(extack,
"Unsupported keys used");
return -EOPNOTSUPP;
}
if (flow_rule_match_has_control_flags(rule, extack))
return -EOPNOTSUPP;
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
struct flow_match_basic match;
flow_rule_match_basic(rule, &match);
if (match.key->n_proto) {
NL_SET_ERR_MSG_MOD(extack,
"Matching on protocol not supported");
return -EOPNOTSUPP;
}
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
u8 bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
u8 null[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
struct flow_match_eth_addrs match;
flow_rule_match_eth_addrs(rule, &match);
if (!ether_addr_equal_masked(match.key->src, null,
match.mask->src)) {
NL_SET_ERR_MSG_MOD(extack,
"Matching on source MAC not supported");
return -EOPNOTSUPP;
}
if (!ether_addr_equal(match.mask->dst, bcast)) {
NL_SET_ERR_MSG_MOD(extack,
"Masked matching on MAC not supported");
return -EOPNOTSUPP;
}
dmac = ether_addr_to_u64(match.key->dst);
is_bcast_dmac = ether_addr_equal(match.key->dst, bcast);
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) {
struct flow_match_vlan match;
flow_rule_match_vlan(rule, &match);
if (match.mask->vlan_id &&
match.mask->vlan_id != VLAN_VID_MASK) {
NL_SET_ERR_MSG_MOD(extack,
"Masked matching on VID is not supported");
return -EOPNOTSUPP;
}
if (match.mask->vlan_priority &&
match.mask->vlan_priority != 0x7) {
NL_SET_ERR_MSG_MOD(extack,
"Masked matching on PCP is not supported");
return -EOPNOTSUPP;
}
if (match.mask->vlan_id)
vid = match.key->vlan_id;
if (match.mask->vlan_priority)
pcp = match.key->vlan_priority;
}
if (is_bcast_dmac && vid == U16_MAX && pcp == U16_MAX) {
key->type = SJA1105_KEY_BCAST;
return 0;
}
if (dmac == U64_MAX && vid == U16_MAX && pcp != U16_MAX) {
key->type = SJA1105_KEY_TC;
key->tc.pcp = pcp;
return 0;
}
if (dmac != U64_MAX && vid != U16_MAX && pcp != U16_MAX) {
key->type = SJA1105_KEY_VLAN_AWARE_VL;
key->vl.dmac = dmac;
key->vl.vid = vid;
key->vl.pcp = pcp;
return 0;
}
if (dmac != U64_MAX) {
key->type = SJA1105_KEY_VLAN_UNAWARE_VL;
key->vl.dmac = dmac;
return 0;
}
NL_SET_ERR_MSG_MOD(extack, "Not matching on any known key");
return -EOPNOTSUPP;
}
static int sja1105_policer_validate(const struct flow_action *action,
const struct flow_action_entry *act,
struct netlink_ext_ack *extack)
{
if (act->police.exceed.act_id != FLOW_ACTION_DROP) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when exceed action is not drop");
return -EOPNOTSUPP;
}
if (act->police.notexceed.act_id != FLOW_ACTION_PIPE &&
act->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when conform action is not pipe or ok");
return -EOPNOTSUPP;
}
if (act->police.notexceed.act_id == FLOW_ACTION_ACCEPT &&
!flow_action_is_last_entry(action, act)) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when conform action is ok, but action is not last");
return -EOPNOTSUPP;
}
if (act->police.peakrate_bytes_ps ||
act->police.avrate || act->police.overhead) {
NL_SET_ERR_MSG_MOD(extack,
"Offload not supported when peakrate/avrate/overhead is configured");
return -EOPNOTSUPP;
}
if (act->police.rate_pkt_ps) {
NL_SET_ERR_MSG_MOD(extack,
"QoS offload not support packets per second");
return -EOPNOTSUPP;
}
return 0;
}
int sja1105_cls_flower_add(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct netlink_ext_ack *extack = cls->common.extack;
struct sja1105_private *priv = ds->priv;
const struct flow_action_entry *act;
unsigned long cookie = cls->cookie;
bool routing_rule = false;
struct sja1105_key key;
bool gate_rule = false;
bool vl_rule = false;
int rc, i;
rc = sja1105_flower_parse_key(priv, extack, cls, &key);
if (rc)
return rc;
flow_action_for_each(i, act, &rule->action) {
switch (act->id) {
case FLOW_ACTION_POLICE:
rc = sja1105_policer_validate(&rule->action, act, extack);
if (rc)
goto out;
rc = sja1105_flower_policer(priv, port, extack, cookie,
&key,
act->police.rate_bytes_ps,
act->police.burst);
if (rc)
goto out;
break;
case FLOW_ACTION_TRAP: {
int cpu = dsa_upstream_port(ds, port);
routing_rule = true;
vl_rule = true;
rc = sja1105_vl_redirect(priv, port, extack, cookie,
&key, BIT(cpu), true);
if (rc)
goto out;
break;
}
case FLOW_ACTION_REDIRECT: {
struct dsa_port *to_dp;
to_dp = dsa_port_from_netdev(act->dev);
if (IS_ERR(to_dp) || to_dp->ds != ds) {
NL_SET_ERR_MSG_MOD(extack,
"Destination not a local switch port");
return -EOPNOTSUPP;
}
routing_rule = true;
vl_rule = true;
rc = sja1105_vl_redirect(priv, port, extack, cookie,
&key, BIT(to_dp->index), true);
if (rc)
goto out;
break;
}
case FLOW_ACTION_DROP:
vl_rule = true;
rc = sja1105_vl_redirect(priv, port, extack, cookie,
&key, 0, false);
if (rc)
goto out;
break;
case FLOW_ACTION_GATE:
gate_rule = true;
vl_rule = true;
rc = sja1105_vl_gate(priv, port, extack, cookie,
&key, act->hw_index,
act->gate.prio,
act->gate.basetime,
act->gate.cycletime,
act->gate.cycletimeext,
act->gate.num_entries,
act->gate.entries);
if (rc)
goto out;
break;
default:
NL_SET_ERR_MSG_MOD(extack,
"Action not supported");
rc = -EOPNOTSUPP;
goto out;
}
}
if (vl_rule && !rc) {
/* Delay scheduling configuration until DESTPORTS has been
* populated by all other actions.
*/
if (gate_rule) {
if (!routing_rule) {
NL_SET_ERR_MSG_MOD(extack,
"Can only offload gate action together with redirect or trap");
return -EOPNOTSUPP;
}
rc = sja1105_init_scheduling(priv);
if (rc)
goto out;
}
rc = sja1105_static_config_reload(priv, SJA1105_VIRTUAL_LINKS);
}
out:
return rc;
}
int sja1105_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_rule *rule = sja1105_rule_find(priv, cls->cookie);
struct sja1105_l2_policing_entry *policing;
int old_sharindx;
if (!rule)
return 0;
if (rule->type == SJA1105_RULE_VL)
return sja1105_vl_delete(priv, port, rule, cls->common.extack);
policing = priv->static_config.tables[BLK_IDX_L2_POLICING].entries;
if (rule->type == SJA1105_RULE_BCAST_POLICER) {
int bcast = (ds->num_ports * SJA1105_NUM_TC) + port;
old_sharindx = policing[bcast].sharindx;
policing[bcast].sharindx = port;
} else if (rule->type == SJA1105_RULE_TC_POLICER) {
int index = (port * SJA1105_NUM_TC) + rule->key.tc.pcp;
old_sharindx = policing[index].sharindx;
policing[index].sharindx = port;
} else {
return -EINVAL;
}
rule->port_mask &= ~BIT(port);
if (!rule->port_mask) {
priv->flow_block.l2_policer_used[old_sharindx] = false;
list_del(&rule->list);
kfree(rule);
}
return sja1105_static_config_reload(priv, SJA1105_BEST_EFFORT_POLICING);
}
int sja1105_cls_flower_stats(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_rule *rule = sja1105_rule_find(priv, cls->cookie);
int rc;
if (!rule)
return 0;
if (rule->type != SJA1105_RULE_VL)
return 0;
rc = sja1105_vl_stats(priv, port, rule, &cls->stats,
cls->common.extack);
if (rc)
return rc;
return 0;
}
void sja1105_flower_setup(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
int port;
INIT_LIST_HEAD(&priv->flow_block.rules);
for (port = 0; port < ds->num_ports; port++)
priv->flow_block.l2_policer_used[port] = true;
}
void sja1105_flower_teardown(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_rule *rule;
struct list_head *pos, *n;
list_for_each_safe(pos, n, &priv->flow_block.rules) {
rule = list_entry(pos, struct sja1105_rule, list);
list_del(&rule->list);
kfree(rule);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,507 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2021 NXP
*/
#include <linux/pcs/pcs-xpcs.h>
#include <linux/of_mdio.h>
#include "sja1105.h"
#define SJA1110_PCS_BANK_REG SJA1110_SPI_ADDR(0x3fc)
int sja1105_pcs_mdio_read_c45(struct mii_bus *bus, int phy, int mmd, int reg)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
u64 addr;
u32 tmp;
int rc;
addr = (mmd << 16) | reg;
if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
return 0xffff;
if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID1)
return NXP_SJA1105_XPCS_ID >> 16;
if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID2)
return NXP_SJA1105_XPCS_ID & GENMASK(15, 0);
rc = sja1105_xfer_u32(priv, SPI_READ, addr, &tmp, NULL);
if (rc < 0)
return rc;
return tmp & 0xffff;
}
int sja1105_pcs_mdio_write_c45(struct mii_bus *bus, int phy, int mmd,
int reg, u16 val)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
u64 addr;
u32 tmp;
addr = (mmd << 16) | reg;
tmp = val;
if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
return -EINVAL;
return sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
}
int sja1110_pcs_mdio_read_c45(struct mii_bus *bus, int phy, int mmd, int reg)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
const struct sja1105_regs *regs = priv->info->regs;
int offset, bank;
u64 addr;
u32 tmp;
int rc;
if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
return -ENODEV;
addr = (mmd << 16) | reg;
if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID1)
return NXP_SJA1110_XPCS_ID >> 16;
if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID2)
return NXP_SJA1110_XPCS_ID & GENMASK(15, 0);
bank = addr >> 8;
offset = addr & GENMASK(7, 0);
/* This addressing scheme reserves register 0xff for the bank address
* register, so that can never be addressed.
*/
if (WARN_ON(offset == 0xff))
return -ENODEV;
tmp = bank;
rc = sja1105_xfer_u32(priv, SPI_WRITE,
regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
&tmp, NULL);
if (rc < 0)
return rc;
rc = sja1105_xfer_u32(priv, SPI_READ, regs->pcs_base[phy] + offset,
&tmp, NULL);
if (rc < 0)
return rc;
return tmp & 0xffff;
}
int sja1110_pcs_mdio_write_c45(struct mii_bus *bus, int phy, int mmd, int reg,
u16 val)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
const struct sja1105_regs *regs = priv->info->regs;
int offset, bank;
u64 addr;
u32 tmp;
int rc;
if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
return -ENODEV;
addr = (mmd << 16) | reg;
bank = addr >> 8;
offset = addr & GENMASK(7, 0);
/* This addressing scheme reserves register 0xff for the bank address
* register, so that can never be addressed.
*/
if (WARN_ON(offset == 0xff))
return -ENODEV;
tmp = bank;
rc = sja1105_xfer_u32(priv, SPI_WRITE,
regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
&tmp, NULL);
if (rc < 0)
return rc;
tmp = val;
return sja1105_xfer_u32(priv, SPI_WRITE, regs->pcs_base[phy] + offset,
&tmp, NULL);
}
enum sja1105_mdio_opcode {
SJA1105_C45_ADDR = 0,
SJA1105_C22 = 1,
SJA1105_C45_DATA = 2,
SJA1105_C45_DATA_AUTOINC = 3,
};
static u64 sja1105_base_t1_encode_addr(struct sja1105_private *priv,
int phy, enum sja1105_mdio_opcode op,
int xad)
{
const struct sja1105_regs *regs = priv->info->regs;
return regs->mdio_100base_t1 | (phy << 7) | (op << 5) | (xad << 0);
}
static int sja1105_base_t1_mdio_read_c22(struct mii_bus *bus, int phy, int reg)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
u64 addr;
u32 tmp;
int rc;
addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C22, reg & 0x1f);
rc = sja1105_xfer_u32(priv, SPI_READ, addr, &tmp, NULL);
if (rc < 0)
return rc;
return tmp & 0xffff;
}
static int sja1105_base_t1_mdio_read_c45(struct mii_bus *bus, int phy,
int mmd, int reg)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
u64 addr;
u32 tmp;
int rc;
addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C45_ADDR, mmd);
rc = sja1105_xfer_u32(priv, SPI_WRITE, addr, &reg, NULL);
if (rc < 0)
return rc;
addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C45_DATA, mmd);
rc = sja1105_xfer_u32(priv, SPI_READ, addr, &tmp, NULL);
if (rc < 0)
return rc;
return tmp & 0xffff;
}
static int sja1105_base_t1_mdio_write_c22(struct mii_bus *bus, int phy, int reg,
u16 val)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
u64 addr;
u32 tmp;
addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C22, reg & 0x1f);
tmp = val & 0xffff;
return sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
}
static int sja1105_base_t1_mdio_write_c45(struct mii_bus *bus, int phy,
int mmd, int reg, u16 val)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
u64 addr;
u32 tmp;
int rc;
addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C45_ADDR, mmd);
rc = sja1105_xfer_u32(priv, SPI_WRITE, addr, &reg, NULL);
if (rc < 0)
return rc;
addr = sja1105_base_t1_encode_addr(priv, phy, SJA1105_C45_DATA, mmd);
tmp = val & 0xffff;
return sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
}
static int sja1105_base_tx_mdio_read(struct mii_bus *bus, int phy, int reg)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
const struct sja1105_regs *regs = priv->info->regs;
u32 tmp;
int rc;
rc = sja1105_xfer_u32(priv, SPI_READ, regs->mdio_100base_tx + reg,
&tmp, NULL);
if (rc < 0)
return rc;
return tmp & 0xffff;
}
static int sja1105_base_tx_mdio_write(struct mii_bus *bus, int phy, int reg,
u16 val)
{
struct sja1105_mdio_private *mdio_priv = bus->priv;
struct sja1105_private *priv = mdio_priv->priv;
const struct sja1105_regs *regs = priv->info->regs;
u32 tmp = val;
return sja1105_xfer_u32(priv, SPI_WRITE, regs->mdio_100base_tx + reg,
&tmp, NULL);
}
static int sja1105_mdiobus_base_tx_register(struct sja1105_private *priv,
struct device_node *mdio_node)
{
struct sja1105_mdio_private *mdio_priv;
struct device_node *np;
struct mii_bus *bus;
int rc = 0;
np = of_get_compatible_child(mdio_node, "nxp,sja1110-base-tx-mdio");
if (!np)
return 0;
if (!of_device_is_available(np))
goto out_put_np;
bus = mdiobus_alloc_size(sizeof(*mdio_priv));
if (!bus) {
rc = -ENOMEM;
goto out_put_np;
}
bus->name = "SJA1110 100base-TX MDIO bus";
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-base-tx",
dev_name(priv->ds->dev));
bus->read = sja1105_base_tx_mdio_read;
bus->write = sja1105_base_tx_mdio_write;
bus->parent = priv->ds->dev;
mdio_priv = bus->priv;
mdio_priv->priv = priv;
rc = of_mdiobus_register(bus, np);
if (rc) {
mdiobus_free(bus);
goto out_put_np;
}
priv->mdio_base_tx = bus;
out_put_np:
of_node_put(np);
return rc;
}
static void sja1105_mdiobus_base_tx_unregister(struct sja1105_private *priv)
{
if (!priv->mdio_base_tx)
return;
mdiobus_unregister(priv->mdio_base_tx);
mdiobus_free(priv->mdio_base_tx);
priv->mdio_base_tx = NULL;
}
static int sja1105_mdiobus_base_t1_register(struct sja1105_private *priv,
struct device_node *mdio_node)
{
struct sja1105_mdio_private *mdio_priv;
struct device_node *np;
struct mii_bus *bus;
int rc = 0;
np = of_get_compatible_child(mdio_node, "nxp,sja1110-base-t1-mdio");
if (!np)
return 0;
if (!of_device_is_available(np))
goto out_put_np;
bus = mdiobus_alloc_size(sizeof(*mdio_priv));
if (!bus) {
rc = -ENOMEM;
goto out_put_np;
}
bus->name = "SJA1110 100base-T1 MDIO bus";
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-base-t1",
dev_name(priv->ds->dev));
bus->read = sja1105_base_t1_mdio_read_c22;
bus->write = sja1105_base_t1_mdio_write_c22;
bus->read_c45 = sja1105_base_t1_mdio_read_c45;
bus->write_c45 = sja1105_base_t1_mdio_write_c45;
bus->parent = priv->ds->dev;
mdio_priv = bus->priv;
mdio_priv->priv = priv;
rc = of_mdiobus_register(bus, np);
if (rc) {
mdiobus_free(bus);
goto out_put_np;
}
priv->mdio_base_t1 = bus;
out_put_np:
of_node_put(np);
return rc;
}
static void sja1105_mdiobus_base_t1_unregister(struct sja1105_private *priv)
{
if (!priv->mdio_base_t1)
return;
mdiobus_unregister(priv->mdio_base_t1);
mdiobus_free(priv->mdio_base_t1);
priv->mdio_base_t1 = NULL;
}
static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
{
struct sja1105_mdio_private *mdio_priv;
struct dsa_switch *ds = priv->ds;
struct mii_bus *bus;
int rc = 0;
int port;
if (!priv->info->pcs_mdio_read_c45 || !priv->info->pcs_mdio_write_c45)
return 0;
bus = mdiobus_alloc_size(sizeof(*mdio_priv));
if (!bus)
return -ENOMEM;
bus->name = "SJA1105 PCS MDIO bus";
snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs",
dev_name(ds->dev));
bus->read_c45 = priv->info->pcs_mdio_read_c45;
bus->write_c45 = priv->info->pcs_mdio_write_c45;
bus->parent = ds->dev;
/* There is no PHY on this MDIO bus => mask out all PHY addresses
* from auto probing.
*/
bus->phy_mask = ~0;
mdio_priv = bus->priv;
mdio_priv->priv = priv;
rc = mdiobus_register(bus);
if (rc) {
mdiobus_free(bus);
return rc;
}
for (port = 0; port < ds->num_ports; port++) {
struct phylink_pcs *pcs;
if (dsa_is_unused_port(ds, port))
continue;
if (priv->phy_mode[port] != PHY_INTERFACE_MODE_SGMII &&
priv->phy_mode[port] != PHY_INTERFACE_MODE_2500BASEX)
continue;
pcs = xpcs_create_pcs_mdiodev(bus, port);
if (IS_ERR(pcs)) {
rc = PTR_ERR(pcs);
goto out_pcs_free;
}
priv->pcs[port] = pcs;
}
priv->mdio_pcs = bus;
return 0;
out_pcs_free:
for (port = 0; port < ds->num_ports; port++) {
if (priv->pcs[port]) {
xpcs_destroy_pcs(priv->pcs[port]);
priv->pcs[port] = NULL;
}
}
mdiobus_unregister(bus);
mdiobus_free(bus);
return rc;
}
static void sja1105_mdiobus_pcs_unregister(struct sja1105_private *priv)
{
struct dsa_switch *ds = priv->ds;
int port;
if (!priv->mdio_pcs)
return;
for (port = 0; port < ds->num_ports; port++) {
if (priv->pcs[port]) {
xpcs_destroy_pcs(priv->pcs[port]);
priv->pcs[port] = NULL;
}
}
mdiobus_unregister(priv->mdio_pcs);
mdiobus_free(priv->mdio_pcs);
priv->mdio_pcs = NULL;
}
int sja1105_mdiobus_register(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
const struct sja1105_regs *regs = priv->info->regs;
struct device_node *switch_node = ds->dev->of_node;
struct device_node *mdio_node;
int rc;
rc = sja1105_mdiobus_pcs_register(priv);
if (rc)
return rc;
mdio_node = of_get_available_child_by_name(switch_node, "mdios");
if (!mdio_node)
return 0;
if (regs->mdio_100base_tx != SJA1105_RSV_ADDR) {
rc = sja1105_mdiobus_base_tx_register(priv, mdio_node);
if (rc)
goto err_put_mdio_node;
}
if (regs->mdio_100base_t1 != SJA1105_RSV_ADDR) {
rc = sja1105_mdiobus_base_t1_register(priv, mdio_node);
if (rc)
goto err_free_base_tx_mdiobus;
}
of_node_put(mdio_node);
return 0;
err_free_base_tx_mdiobus:
sja1105_mdiobus_base_tx_unregister(priv);
err_put_mdio_node:
of_node_put(mdio_node);
sja1105_mdiobus_pcs_unregister(priv);
return rc;
}
void sja1105_mdiobus_unregister(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
sja1105_mdiobus_base_t1_unregister(priv);
sja1105_mdiobus_base_tx_unregister(priv);
sja1105_mdiobus_pcs_unregister(priv);
}
@@ -0,0 +1,970 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com>
*/
#include <linux/spi/spi.h>
#include "sja1105.h"
/* The adjfine API clamps ppb between [-32,768,000, 32,768,000], and
* therefore scaled_ppm between [-2,147,483,648, 2,147,483,647].
* Set the maximum supported ppb to a round value smaller than the maximum.
*
* Percentually speaking, this is a +/- 0.032x adjustment of the
* free-running counter (0.968x to 1.032x).
*/
#define SJA1105_MAX_ADJ_PPB 32000000
#define SJA1105_SIZE_PTP_CMD 4
/* PTPSYNCTS has no interrupt or update mechanism, because the intended
* hardware use case is for the timestamp to be collected synchronously,
* immediately after the CAS_MASTER SJA1105 switch has performed a CASSYNC
* one-shot toggle (no return to level) on the PTP_CLK pin. When used as a
* generic extts source, the PTPSYNCTS register needs polling and a comparison
* with the old value. The polling interval is configured as the Nyquist rate
* of a signal with 50% duty cycle and 1Hz frequency, which is sadly all that
* this hardware can do (but may be enough for some setups). Anything of higher
* frequency than 1 Hz will be lost, since there is no timestamp FIFO.
*/
#define SJA1105_EXTTS_INTERVAL (HZ / 6)
/* This range is actually +/- SJA1105_MAX_ADJ_PPB
* divided by 1000 (ppb -> ppm) and with a 16-bit
* "fractional" part (actually fixed point).
* |
* v
* Convert scaled_ppm from the +/- ((10^6) << 16) range
* into the +/- (1 << 31) range.
*
* This forgoes a "ppb" numeric representation (up to NSEC_PER_SEC)
* and defines the scaling factor between scaled_ppm and the actual
* frequency adjustments of the PHC.
*
* ptpclkrate = scaled_ppm * 2^31 / (10^6 * 2^16)
* simplifies to
* ptpclkrate = scaled_ppm * 2^9 / 5^6
*/
#define SJA1105_CC_MULT_NUM (1 << 9)
#define SJA1105_CC_MULT_DEM 15625
#define SJA1105_CC_MULT 0x80000000
enum sja1105_ptp_clk_mode {
PTP_ADD_MODE = 1,
PTP_SET_MODE = 0,
};
#define extts_to_data(t) \
container_of((t), struct sja1105_ptp_data, extts_timer)
#define ptp_caps_to_data(d) \
container_of((d), struct sja1105_ptp_data, caps)
#define ptp_data_to_sja1105(d) \
container_of((d), struct sja1105_private, ptp_data)
int sja1105_hwtstamp_set(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack)
{
struct sja1105_private *priv = ds->priv;
unsigned long hwts_tx_en, hwts_rx_en;
hwts_tx_en = priv->hwts_tx_en;
hwts_rx_en = priv->hwts_rx_en;
switch (config->tx_type) {
case HWTSTAMP_TX_OFF:
hwts_tx_en &= ~BIT(port);
break;
case HWTSTAMP_TX_ON:
hwts_tx_en |= BIT(port);
break;
default:
return -ERANGE;
}
switch (config->rx_filter) {
case HWTSTAMP_FILTER_NONE:
hwts_rx_en &= ~BIT(port);
break;
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
hwts_rx_en |= BIT(port);
break;
default:
return -ERANGE;
}
priv->hwts_tx_en = hwts_tx_en;
priv->hwts_rx_en = hwts_rx_en;
return 0;
}
int sja1105_hwtstamp_get(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *config)
{
struct sja1105_private *priv = ds->priv;
config->flags = 0;
if (priv->hwts_tx_en & BIT(port))
config->tx_type = HWTSTAMP_TX_ON;
else
config->tx_type = HWTSTAMP_TX_OFF;
if (priv->hwts_rx_en & BIT(port))
config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
else
config->rx_filter = HWTSTAMP_FILTER_NONE;
return 0;
}
int sja1105_get_ts_info(struct dsa_switch *ds, int port,
struct kernel_ethtool_ts_info *info)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
/* Called during cleanup */
if (!ptp_data->clock)
return -ENODEV;
info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
info->tx_types = (1 << HWTSTAMP_TX_OFF) |
(1 << HWTSTAMP_TX_ON);
info->rx_filters = (1 << HWTSTAMP_FILTER_NONE) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT);
info->phc_index = ptp_clock_index(ptp_data->clock);
return 0;
}
void sja1105et_ptp_cmd_packing(u8 *buf, struct sja1105_ptp_cmd *cmd,
enum packing_op op)
{
const int size = SJA1105_SIZE_PTP_CMD;
/* No need to keep this as part of the structure */
u64 valid = 1;
sja1105_packing(buf, &valid, 31, 31, size, op);
sja1105_packing(buf, &cmd->ptpstrtsch, 30, 30, size, op);
sja1105_packing(buf, &cmd->ptpstopsch, 29, 29, size, op);
sja1105_packing(buf, &cmd->startptpcp, 28, 28, size, op);
sja1105_packing(buf, &cmd->stopptpcp, 27, 27, size, op);
sja1105_packing(buf, &cmd->resptp, 2, 2, size, op);
sja1105_packing(buf, &cmd->corrclk4ts, 1, 1, size, op);
sja1105_packing(buf, &cmd->ptpclkadd, 0, 0, size, op);
}
void sja1105pqrs_ptp_cmd_packing(u8 *buf, struct sja1105_ptp_cmd *cmd,
enum packing_op op)
{
const int size = SJA1105_SIZE_PTP_CMD;
/* No need to keep this as part of the structure */
u64 valid = 1;
sja1105_packing(buf, &valid, 31, 31, size, op);
sja1105_packing(buf, &cmd->ptpstrtsch, 30, 30, size, op);
sja1105_packing(buf, &cmd->ptpstopsch, 29, 29, size, op);
sja1105_packing(buf, &cmd->startptpcp, 28, 28, size, op);
sja1105_packing(buf, &cmd->stopptpcp, 27, 27, size, op);
sja1105_packing(buf, &cmd->resptp, 3, 3, size, op);
sja1105_packing(buf, &cmd->corrclk4ts, 2, 2, size, op);
sja1105_packing(buf, &cmd->ptpclkadd, 0, 0, size, op);
}
int sja1105_ptp_commit(struct dsa_switch *ds, struct sja1105_ptp_cmd *cmd,
sja1105_spi_rw_mode_t rw)
{
const struct sja1105_private *priv = ds->priv;
const struct sja1105_regs *regs = priv->info->regs;
u8 buf[SJA1105_SIZE_PTP_CMD] = {0};
int rc;
if (rw == SPI_WRITE)
priv->info->ptp_cmd_packing(buf, cmd, PACK);
rc = sja1105_xfer_buf(priv, rw, regs->ptp_control, buf,
SJA1105_SIZE_PTP_CMD);
if (rw == SPI_READ)
priv->info->ptp_cmd_packing(buf, cmd, UNPACK);
return rc;
}
/* The switch returns partial timestamps (24 bits for SJA1105 E/T, which wrap
* around in 0.135 seconds, and 32 bits for P/Q/R/S, wrapping around in 34.35
* seconds).
*
* This receives the RX or TX MAC timestamps, provided by hardware as
* the lower bits of the cycle counter, sampled at the time the timestamp was
* collected.
*
* To reconstruct into a full 64-bit-wide timestamp, the cycle counter is
* read and the high-order bits are filled in.
*
* Must be called within one wraparound period of the partial timestamp since
* it was generated by the MAC.
*/
static u64 sja1105_tstamp_reconstruct(struct dsa_switch *ds, u64 now,
u64 ts_partial)
{
struct sja1105_private *priv = ds->priv;
u64 partial_tstamp_mask = CYCLECOUNTER_MASK(priv->info->ptp_ts_bits);
u64 ts_reconstructed;
ts_reconstructed = (now & ~partial_tstamp_mask) | ts_partial;
/* Check lower bits of current cycle counter against the timestamp.
* If the current cycle counter is lower than the partial timestamp,
* then wraparound surely occurred and must be accounted for.
*/
if ((now & partial_tstamp_mask) <= ts_partial)
ts_reconstructed -= (partial_tstamp_mask + 1);
return ts_reconstructed;
}
/* Reads the SPI interface for an egress timestamp generated by the switch
* for frames sent using management routes.
*
* SJA1105 E/T layout of the 4-byte SPI payload:
*
* 31 23 15 7 0
* | | | | |
* +-----+-----+-----+ ^
* ^ |
* | |
* 24-bit timestamp Update bit
*
*
* SJA1105 P/Q/R/S layout of the 8-byte SPI payload:
*
* 31 23 15 7 0 63 55 47 39 32
* | | | | | | | | | |
* ^ +-----+-----+-----+-----+
* | ^
* | |
* Update bit 32-bit timestamp
*
* Notice that the update bit is in the same place.
* To have common code for E/T and P/Q/R/S for reading the timestamp,
* we need to juggle with the offset and the bit indices.
*/
static int sja1105_ptpegr_ts_poll(struct dsa_switch *ds, int port, u64 *ts)
{
struct sja1105_private *priv = ds->priv;
const struct sja1105_regs *regs = priv->info->regs;
int tstamp_bit_start, tstamp_bit_end;
int timeout = 10;
u8 packed_buf[8];
u64 update;
int rc;
do {
rc = sja1105_xfer_buf(priv, SPI_READ, regs->ptpegr_ts[port],
packed_buf, priv->info->ptpegr_ts_bytes);
if (rc < 0)
return rc;
sja1105_unpack(packed_buf, &update, 0, 0,
priv->info->ptpegr_ts_bytes);
if (update)
break;
usleep_range(10, 50);
} while (--timeout);
if (!timeout)
return -ETIMEDOUT;
/* Point the end bit to the second 32-bit word on P/Q/R/S,
* no-op on E/T.
*/
tstamp_bit_end = (priv->info->ptpegr_ts_bytes - 4) * 8;
/* Shift the 24-bit timestamp on E/T to be collected from 31:8.
* No-op on P/Q/R/S.
*/
tstamp_bit_end += 32 - priv->info->ptp_ts_bits;
tstamp_bit_start = tstamp_bit_end + priv->info->ptp_ts_bits - 1;
*ts = 0;
sja1105_unpack(packed_buf, ts, tstamp_bit_start, tstamp_bit_end,
priv->info->ptpegr_ts_bytes);
return 0;
}
/* Caller must hold ptp_data->lock */
static int sja1105_ptpclkval_read(struct sja1105_private *priv, u64 *ticks,
struct ptp_system_timestamp *ptp_sts)
{
const struct sja1105_regs *regs = priv->info->regs;
return sja1105_xfer_u64(priv, SPI_READ, regs->ptpclkval, ticks,
ptp_sts);
}
/* Caller must hold ptp_data->lock */
static int sja1105_ptpclkval_write(struct sja1105_private *priv, u64 ticks,
struct ptp_system_timestamp *ptp_sts)
{
const struct sja1105_regs *regs = priv->info->regs;
return sja1105_xfer_u64(priv, SPI_WRITE, regs->ptpclkval, &ticks,
ptp_sts);
}
static void sja1105_extts_poll(struct sja1105_private *priv)
{
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
const struct sja1105_regs *regs = priv->info->regs;
struct ptp_clock_event event;
u64 ptpsyncts = 0;
int rc;
rc = sja1105_xfer_u64(priv, SPI_READ, regs->ptpsyncts, &ptpsyncts,
NULL);
if (rc < 0)
dev_err_ratelimited(priv->ds->dev,
"Failed to read PTPSYNCTS: %d\n", rc);
if (ptpsyncts && ptp_data->ptpsyncts != ptpsyncts) {
event.index = 0;
event.type = PTP_CLOCK_EXTTS;
event.timestamp = ns_to_ktime(sja1105_ticks_to_ns(ptpsyncts));
ptp_clock_event(ptp_data->clock, &event);
ptp_data->ptpsyncts = ptpsyncts;
}
}
static long sja1105_rxtstamp_work(struct ptp_clock_info *ptp)
{
struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
struct dsa_switch *ds = priv->ds;
struct sk_buff *skb;
mutex_lock(&ptp_data->lock);
while ((skb = skb_dequeue(&ptp_data->skb_rxtstamp_queue)) != NULL) {
struct skb_shared_hwtstamps *shwt = skb_hwtstamps(skb);
u64 ticks, ts;
int rc;
rc = sja1105_ptpclkval_read(priv, &ticks, NULL);
if (rc < 0) {
dev_err(ds->dev, "Failed to read PTP clock: %d\n", rc);
kfree_skb(skb);
continue;
}
*shwt = (struct skb_shared_hwtstamps) {0};
ts = SJA1105_SKB_CB(skb)->tstamp;
ts = sja1105_tstamp_reconstruct(ds, ticks, ts);
shwt->hwtstamp = ns_to_ktime(sja1105_ticks_to_ns(ts));
netif_rx(skb);
}
if (ptp_data->extts_enabled)
sja1105_extts_poll(priv);
mutex_unlock(&ptp_data->lock);
/* Don't restart */
return -1;
}
bool sja1105_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
if (!(priv->hwts_rx_en & BIT(port)))
return false;
/* We need to read the full PTP clock to reconstruct the Rx
* timestamp. For that we need a sleepable context.
*/
skb_queue_tail(&ptp_data->skb_rxtstamp_queue, skb);
ptp_schedule_worker(ptp_data->clock, 0);
return true;
}
bool sja1110_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
{
struct skb_shared_hwtstamps *shwt = skb_hwtstamps(skb);
u64 ts = SJA1105_SKB_CB(skb)->tstamp;
*shwt = (struct skb_shared_hwtstamps) {0};
shwt->hwtstamp = ns_to_ktime(sja1105_ticks_to_ns(ts));
/* Don't defer */
return false;
}
/* Called from dsa_skb_defer_rx_timestamp */
bool sja1105_port_rxtstamp(struct dsa_switch *ds, int port,
struct sk_buff *skb, unsigned int type)
{
struct sja1105_private *priv = ds->priv;
return priv->info->rxtstamp(ds, port, skb);
}
void sja1110_process_meta_tstamp(struct dsa_switch *ds, int port, u8 ts_id,
enum sja1110_meta_tstamp dir, u64 tstamp)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
struct sk_buff *skb, *skb_tmp, *skb_match = NULL;
struct skb_shared_hwtstamps shwt = {0};
/* We don't care about RX timestamps on the CPU port */
if (dir == SJA1110_META_TSTAMP_RX)
return;
spin_lock(&ptp_data->skb_txtstamp_queue.lock);
skb_queue_walk_safe(&ptp_data->skb_txtstamp_queue, skb, skb_tmp) {
if (SJA1105_SKB_CB(skb)->ts_id != ts_id)
continue;
__skb_unlink(skb, &ptp_data->skb_txtstamp_queue);
skb_match = skb;
break;
}
spin_unlock(&ptp_data->skb_txtstamp_queue.lock);
if (WARN_ON(!skb_match))
return;
shwt.hwtstamp = ns_to_ktime(sja1105_ticks_to_ns(tstamp));
skb_complete_tx_timestamp(skb_match, &shwt);
}
/* In addition to cloning the skb which is done by the common
* sja1105_port_txtstamp, we need to generate a timestamp ID and save the
* packet to the TX timestamping queue.
*/
void sja1110_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
{
struct sk_buff *clone = SJA1105_SKB_CB(skb)->clone;
struct sja1105_private *priv = ds->priv;
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
u8 ts_id;
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
spin_lock(&priv->ts_id_lock);
ts_id = priv->ts_id;
/* Deal automatically with 8-bit wraparound */
priv->ts_id++;
SJA1105_SKB_CB(clone)->ts_id = ts_id;
spin_unlock(&priv->ts_id_lock);
skb_queue_tail(&ptp_data->skb_txtstamp_queue, clone);
}
/* Called from dsa_skb_tx_timestamp. This callback is just to clone
* the skb and have it available in SJA1105_SKB_CB in the .port_deferred_xmit
* callback, where we will timestamp it synchronously.
*/
void sja1105_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb)
{
struct sja1105_private *priv = ds->priv;
struct sk_buff *clone;
if (!(priv->hwts_tx_en & BIT(port)))
return;
clone = skb_clone_sk(skb);
if (!clone)
return;
SJA1105_SKB_CB(skb)->clone = clone;
if (priv->info->txtstamp)
priv->info->txtstamp(ds, port, skb);
}
static int sja1105_ptp_reset(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
struct sja1105_ptp_cmd cmd = ptp_data->cmd;
int rc;
mutex_lock(&ptp_data->lock);
cmd.resptp = 1;
dev_dbg(ds->dev, "Resetting PTP clock\n");
rc = sja1105_ptp_commit(ds, &cmd, SPI_WRITE);
sja1105_tas_clockstep(priv->ds);
mutex_unlock(&ptp_data->lock);
return rc;
}
/* Caller must hold ptp_data->lock */
int __sja1105_ptp_gettimex(struct dsa_switch *ds, u64 *ns,
struct ptp_system_timestamp *ptp_sts)
{
struct sja1105_private *priv = ds->priv;
u64 ticks;
int rc;
rc = sja1105_ptpclkval_read(priv, &ticks, ptp_sts);
if (rc < 0) {
dev_err(ds->dev, "Failed to read PTP clock: %d\n", rc);
return rc;
}
*ns = sja1105_ticks_to_ns(ticks);
return 0;
}
static int sja1105_ptp_gettimex(struct ptp_clock_info *ptp,
struct timespec64 *ts,
struct ptp_system_timestamp *ptp_sts)
{
struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
u64 now = 0;
int rc;
mutex_lock(&ptp_data->lock);
rc = __sja1105_ptp_gettimex(priv->ds, &now, ptp_sts);
*ts = ns_to_timespec64(now);
mutex_unlock(&ptp_data->lock);
return rc;
}
/* Caller must hold ptp_data->lock */
static int sja1105_ptp_mode_set(struct sja1105_private *priv,
enum sja1105_ptp_clk_mode mode)
{
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
if (ptp_data->cmd.ptpclkadd == mode)
return 0;
ptp_data->cmd.ptpclkadd = mode;
return sja1105_ptp_commit(priv->ds, &ptp_data->cmd, SPI_WRITE);
}
/* Write to PTPCLKVAL while PTPCLKADD is 0 */
int __sja1105_ptp_settime(struct dsa_switch *ds, u64 ns,
struct ptp_system_timestamp *ptp_sts)
{
struct sja1105_private *priv = ds->priv;
u64 ticks = ns_to_sja1105_ticks(ns);
int rc;
rc = sja1105_ptp_mode_set(priv, PTP_SET_MODE);
if (rc < 0) {
dev_err(priv->ds->dev, "Failed to put PTPCLK in set mode\n");
return rc;
}
rc = sja1105_ptpclkval_write(priv, ticks, ptp_sts);
sja1105_tas_clockstep(priv->ds);
return rc;
}
static int sja1105_ptp_settime(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
u64 ns = timespec64_to_ns(ts);
int rc;
mutex_lock(&ptp_data->lock);
rc = __sja1105_ptp_settime(priv->ds, ns, NULL);
mutex_unlock(&ptp_data->lock);
return rc;
}
static int sja1105_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
const struct sja1105_regs *regs = priv->info->regs;
u32 clkrate32;
s64 clkrate;
int rc;
clkrate = (s64)scaled_ppm * SJA1105_CC_MULT_NUM;
clkrate = div_s64(clkrate, SJA1105_CC_MULT_DEM);
/* Take a +/- value and re-center it around 2^31. */
clkrate = SJA1105_CC_MULT + clkrate;
WARN_ON(abs(clkrate) >= GENMASK_ULL(31, 0));
clkrate32 = clkrate;
mutex_lock(&ptp_data->lock);
rc = sja1105_xfer_u32(priv, SPI_WRITE, regs->ptpclkrate, &clkrate32,
NULL);
sja1105_tas_adjfreq(priv->ds);
mutex_unlock(&ptp_data->lock);
return rc;
}
/* Write to PTPCLKVAL while PTPCLKADD is 1 */
int __sja1105_ptp_adjtime(struct dsa_switch *ds, s64 delta)
{
struct sja1105_private *priv = ds->priv;
s64 ticks = ns_to_sja1105_ticks(delta);
int rc;
rc = sja1105_ptp_mode_set(priv, PTP_ADD_MODE);
if (rc < 0) {
dev_err(priv->ds->dev, "Failed to put PTPCLK in add mode\n");
return rc;
}
rc = sja1105_ptpclkval_write(priv, ticks, NULL);
sja1105_tas_clockstep(priv->ds);
return rc;
}
static int sja1105_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
int rc;
mutex_lock(&ptp_data->lock);
rc = __sja1105_ptp_adjtime(priv->ds, delta);
mutex_unlock(&ptp_data->lock);
return rc;
}
static void sja1105_ptp_extts_setup_timer(struct sja1105_ptp_data *ptp_data)
{
unsigned long expires = ((jiffies / SJA1105_EXTTS_INTERVAL) + 1) *
SJA1105_EXTTS_INTERVAL;
mod_timer(&ptp_data->extts_timer, expires);
}
static void sja1105_ptp_extts_timer(struct timer_list *t)
{
struct sja1105_ptp_data *ptp_data = extts_to_data(t);
ptp_schedule_worker(ptp_data->clock, 0);
sja1105_ptp_extts_setup_timer(ptp_data);
}
static int sja1105_change_ptp_clk_pin_func(struct sja1105_private *priv,
enum ptp_pin_function func)
{
struct sja1105_avb_params_entry *avb;
enum ptp_pin_function old_func;
avb = priv->static_config.tables[BLK_IDX_AVB_PARAMS].entries;
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID ||
avb->cas_master)
old_func = PTP_PF_PEROUT;
else
old_func = PTP_PF_EXTTS;
if (func == old_func)
return 0;
avb->cas_master = (func == PTP_PF_PEROUT);
return sja1105_dynamic_config_write(priv, BLK_IDX_AVB_PARAMS, 0, avb,
true);
}
/* The PTP_CLK pin may be configured to toggle with a 50% duty cycle and a
* frequency f:
*
* NSEC_PER_SEC
* f = ----------------------
* (PTPPINDUR * 8 ns) * 2
*/
static int sja1105_per_out_enable(struct sja1105_private *priv,
struct ptp_perout_request *perout,
bool on)
{
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
const struct sja1105_regs *regs = priv->info->regs;
struct sja1105_ptp_cmd cmd = ptp_data->cmd;
int rc;
/* We only support one channel */
if (perout->index != 0)
return -EOPNOTSUPP;
mutex_lock(&ptp_data->lock);
rc = sja1105_change_ptp_clk_pin_func(priv, PTP_PF_PEROUT);
if (rc)
goto out;
if (on) {
struct timespec64 pin_duration_ts = {
.tv_sec = perout->period.sec,
.tv_nsec = perout->period.nsec,
};
struct timespec64 pin_start_ts = {
.tv_sec = perout->start.sec,
.tv_nsec = perout->start.nsec,
};
u64 pin_duration = timespec64_to_ns(&pin_duration_ts);
u64 pin_start = timespec64_to_ns(&pin_start_ts);
u32 pin_duration32;
u64 now;
/* ptppindur: 32 bit register which holds the interval between
* 2 edges on PTP_CLK. So check for truncation which happens
* at periods larger than around 68.7 seconds.
*/
pin_duration = max_t(u64, ns_to_sja1105_ticks(pin_duration / 2), 1);
if (pin_duration > U32_MAX) {
rc = -ERANGE;
goto out;
}
pin_duration32 = pin_duration;
/* ptppins: 64 bit register which needs to hold a PTP time
* larger than the current time, otherwise the startptpcp
* command won't do anything. So advance the current time
* by a number of periods in a way that won't alter the
* phase offset.
*/
rc = __sja1105_ptp_gettimex(priv->ds, &now, NULL);
if (rc < 0)
goto out;
pin_start = future_base_time(pin_start, pin_duration,
now + 1ull * NSEC_PER_SEC);
pin_start = ns_to_sja1105_ticks(pin_start);
rc = sja1105_xfer_u64(priv, SPI_WRITE, regs->ptppinst,
&pin_start, NULL);
if (rc < 0)
goto out;
rc = sja1105_xfer_u32(priv, SPI_WRITE, regs->ptppindur,
&pin_duration32, NULL);
if (rc < 0)
goto out;
}
if (on)
cmd.startptpcp = true;
else
cmd.stopptpcp = true;
rc = sja1105_ptp_commit(priv->ds, &cmd, SPI_WRITE);
out:
mutex_unlock(&ptp_data->lock);
return rc;
}
static int sja1105_extts_enable(struct sja1105_private *priv,
struct ptp_extts_request *extts,
bool on)
{
int rc;
/* We only support one channel */
if (extts->index != 0)
return -EOPNOTSUPP;
/* We can only enable time stamping on both edges, sadly. */
if ((extts->flags & PTP_STRICT_FLAGS) &&
(extts->flags & PTP_ENABLE_FEATURE) &&
(extts->flags & PTP_EXTTS_EDGES) != PTP_EXTTS_EDGES)
return -EOPNOTSUPP;
rc = sja1105_change_ptp_clk_pin_func(priv, PTP_PF_EXTTS);
if (rc)
return rc;
priv->ptp_data.extts_enabled = on;
if (on)
sja1105_ptp_extts_setup_timer(&priv->ptp_data);
else
timer_delete_sync(&priv->ptp_data.extts_timer);
return 0;
}
static int sja1105_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *req, int on)
{
struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
int rc = -EOPNOTSUPP;
if (req->type == PTP_CLK_REQ_PEROUT)
rc = sja1105_per_out_enable(priv, &req->perout, on);
else if (req->type == PTP_CLK_REQ_EXTTS)
rc = sja1105_extts_enable(priv, &req->extts, on);
return rc;
}
static int sja1105_ptp_verify_pin(struct ptp_clock_info *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan)
{
struct sja1105_ptp_data *ptp_data = ptp_caps_to_data(ptp);
struct sja1105_private *priv = ptp_data_to_sja1105(ptp_data);
if (chan != 0 || pin != 0)
return -1;
switch (func) {
case PTP_PF_NONE:
case PTP_PF_PEROUT:
break;
case PTP_PF_EXTTS:
if (priv->info->device_id == SJA1105E_DEVICE_ID ||
priv->info->device_id == SJA1105T_DEVICE_ID)
return -1;
break;
default:
return -1;
}
return 0;
}
static struct ptp_pin_desc sja1105_ptp_pin = {
.name = "ptp_clk",
.index = 0,
.func = PTP_PF_NONE,
};
int sja1105_ptp_clock_register(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
ptp_data->caps = (struct ptp_clock_info) {
.owner = THIS_MODULE,
.name = "SJA1105 PHC",
.adjfine = sja1105_ptp_adjfine,
.adjtime = sja1105_ptp_adjtime,
.gettimex64 = sja1105_ptp_gettimex,
.settime64 = sja1105_ptp_settime,
.enable = sja1105_ptp_enable,
.verify = sja1105_ptp_verify_pin,
.do_aux_work = sja1105_rxtstamp_work,
.max_adj = SJA1105_MAX_ADJ_PPB,
.pin_config = &sja1105_ptp_pin,
.n_pins = 1,
.n_ext_ts = 1,
.n_per_out = 1,
.supported_extts_flags = PTP_RISING_EDGE |
PTP_FALLING_EDGE |
PTP_STRICT_FLAGS,
};
/* Only used on SJA1105 */
skb_queue_head_init(&ptp_data->skb_rxtstamp_queue);
/* Only used on SJA1110 */
skb_queue_head_init(&ptp_data->skb_txtstamp_queue);
ptp_data->clock = ptp_clock_register(&ptp_data->caps, ds->dev);
if (IS_ERR_OR_NULL(ptp_data->clock))
return PTR_ERR(ptp_data->clock);
ptp_data->cmd.corrclk4ts = true;
ptp_data->cmd.ptpclkadd = PTP_SET_MODE;
timer_setup(&ptp_data->extts_timer, sja1105_ptp_extts_timer, 0);
return sja1105_ptp_reset(ds);
}
void sja1105_ptp_clock_unregister(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
if (IS_ERR_OR_NULL(ptp_data->clock))
return;
timer_delete_sync(&ptp_data->extts_timer);
ptp_cancel_worker_sync(ptp_data->clock);
skb_queue_purge(&ptp_data->skb_txtstamp_queue);
skb_queue_purge(&ptp_data->skb_rxtstamp_queue);
ptp_clock_unregister(ptp_data->clock);
ptp_data->clock = NULL;
}
void sja1105_ptp_txtstamp_skb(struct dsa_switch *ds, int port,
struct sk_buff *skb)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
struct skb_shared_hwtstamps shwt = {0};
u64 ticks, ts;
int rc;
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
mutex_lock(&ptp_data->lock);
rc = sja1105_ptpegr_ts_poll(ds, port, &ts);
if (rc < 0) {
dev_err(ds->dev, "timed out polling for tstamp\n");
kfree_skb(skb);
goto out;
}
rc = sja1105_ptpclkval_read(priv, &ticks, NULL);
if (rc < 0) {
dev_err(ds->dev, "Failed to read PTP clock: %d\n", rc);
kfree_skb(skb);
goto out;
}
ts = sja1105_tstamp_reconstruct(ds, ticks, ts);
shwt.hwtstamp = ns_to_ktime(sja1105_ticks_to_ns(ts));
skb_complete_tx_timestamp(skb, &shwt);
out:
mutex_unlock(&ptp_data->lock);
}
@@ -0,0 +1,210 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com>
*/
#ifndef _SJA1105_PTP_H
#define _SJA1105_PTP_H
#include <linux/timer.h>
#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_PTP)
/* Timestamps are in units of 8 ns clock ticks (equivalent to
* a fixed 125 MHz clock).
*/
#define SJA1105_TICK_NS 8
static inline s64 ns_to_sja1105_ticks(s64 ns)
{
return ns / SJA1105_TICK_NS;
}
static inline s64 sja1105_ticks_to_ns(s64 ticks)
{
return ticks * SJA1105_TICK_NS;
}
/* Calculate the first base_time in the future that satisfies this
* relationship:
*
* future_base_time = base_time + N x cycle_time >= now, or
*
* now - base_time
* N >= ---------------
* cycle_time
*
* Because N is an integer, the ceiling value of the above "a / b" ratio
* is in fact precisely the floor value of "(a + b - 1) / b", which is
* easier to calculate only having integer division tools.
*/
static inline s64 future_base_time(s64 base_time, s64 cycle_time, s64 now)
{
s64 a, b, n;
if (base_time >= now)
return base_time;
a = now - base_time;
b = cycle_time;
n = div_s64(a + b - 1, b);
return base_time + n * cycle_time;
}
/* This is not a preprocessor macro because the "ns" argument may or may not be
* s64 at caller side. This ensures it is properly type-cast before div_s64.
*/
static inline s64 ns_to_sja1105_delta(s64 ns)
{
return div_s64(ns, 200);
}
static inline s64 sja1105_delta_to_ns(s64 delta)
{
return delta * 200;
}
struct sja1105_ptp_cmd {
u64 startptpcp; /* start toggling PTP_CLK pin */
u64 stopptpcp; /* stop toggling PTP_CLK pin */
u64 ptpstrtsch; /* start schedule */
u64 ptpstopsch; /* stop schedule */
u64 resptp; /* reset */
u64 corrclk4ts; /* use the corrected clock for timestamps */
u64 ptpclkadd; /* enum sja1105_ptp_clk_mode */
};
struct sja1105_ptp_data {
struct timer_list extts_timer;
/* Used only on SJA1105 to reconstruct partial timestamps */
struct sk_buff_head skb_rxtstamp_queue;
/* Used on SJA1110 where meta frames are generated only for
* 2-step TX timestamps
*/
struct sk_buff_head skb_txtstamp_queue;
struct ptp_clock_info caps;
struct ptp_clock *clock;
struct sja1105_ptp_cmd cmd;
/* Serializes all operations on the PTP hardware clock */
struct mutex lock;
bool extts_enabled;
u64 ptpsyncts;
};
int sja1105_ptp_clock_register(struct dsa_switch *ds);
void sja1105_ptp_clock_unregister(struct dsa_switch *ds);
void sja1105et_ptp_cmd_packing(u8 *buf, struct sja1105_ptp_cmd *cmd,
enum packing_op op);
void sja1105pqrs_ptp_cmd_packing(u8 *buf, struct sja1105_ptp_cmd *cmd,
enum packing_op op);
int sja1105_get_ts_info(struct dsa_switch *ds, int port,
struct kernel_ethtool_ts_info *ts);
void sja1105_ptp_txtstamp_skb(struct dsa_switch *ds, int slot,
struct sk_buff *clone);
bool sja1105_port_rxtstamp(struct dsa_switch *ds, int port,
struct sk_buff *skb, unsigned int type);
void sja1105_port_txtstamp(struct dsa_switch *ds, int port,
struct sk_buff *skb);
int sja1105_hwtstamp_get(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *config);
int sja1105_hwtstamp_set(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack);
int __sja1105_ptp_gettimex(struct dsa_switch *ds, u64 *ns,
struct ptp_system_timestamp *sts);
int __sja1105_ptp_settime(struct dsa_switch *ds, u64 ns,
struct ptp_system_timestamp *ptp_sts);
int __sja1105_ptp_adjtime(struct dsa_switch *ds, s64 delta);
int sja1105_ptp_commit(struct dsa_switch *ds, struct sja1105_ptp_cmd *cmd,
sja1105_spi_rw_mode_t rw);
bool sja1105_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb);
bool sja1110_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb);
void sja1110_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb);
void sja1110_process_meta_tstamp(struct dsa_switch *ds, int port, u8 ts_id,
enum sja1110_meta_tstamp dir, u64 tstamp);
#else
struct sja1105_ptp_cmd;
/* Structures cannot be empty in C. Bah!
* Keep the mutex as the only element, which is a bit more difficult to
* refactor out of sja1105_main.c anyway.
*/
struct sja1105_ptp_data {
struct mutex lock;
};
static inline int sja1105_ptp_clock_register(struct dsa_switch *ds)
{
return 0;
}
static inline void sja1105_ptp_clock_unregister(struct dsa_switch *ds) { }
static inline void sja1105_ptp_txtstamp_skb(struct dsa_switch *ds, int slot,
struct sk_buff *clone)
{
}
static inline int __sja1105_ptp_gettimex(struct dsa_switch *ds, u64 *ns,
struct ptp_system_timestamp *sts)
{
return 0;
}
static inline int __sja1105_ptp_settime(struct dsa_switch *ds, u64 ns,
struct ptp_system_timestamp *ptp_sts)
{
return 0;
}
static inline int __sja1105_ptp_adjtime(struct dsa_switch *ds, s64 delta)
{
return 0;
}
static inline int sja1105_ptp_commit(struct dsa_switch *ds,
struct sja1105_ptp_cmd *cmd,
sja1105_spi_rw_mode_t rw)
{
return 0;
}
#define sja1105et_ptp_cmd_packing NULL
#define sja1105pqrs_ptp_cmd_packing NULL
#define sja1105_get_ts_info NULL
#define sja1105_port_rxtstamp NULL
#define sja1105_port_txtstamp NULL
#define sja1105_hwtstamp_get NULL
#define sja1105_hwtstamp_set NULL
#define sja1105_rxtstamp NULL
#define sja1110_rxtstamp NULL
#define sja1110_txtstamp NULL
#define sja1110_process_meta_tstamp NULL
#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_PTP) */
#endif /* _SJA1105_PTP_H */
@@ -0,0 +1,977 @@
// SPDX-License-Identifier: BSD-3-Clause
/* Copyright 2016-2018 NXP
* Copyright (c) 2018, Sensor-Technik Wiedemann GmbH
* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
*/
#include <linux/spi/spi.h>
#include <linux/packing.h>
#include "sja1105.h"
struct sja1105_chunk {
u8 *buf;
size_t len;
u64 reg_addr;
};
static void
sja1105_spi_message_pack(void *buf, const struct sja1105_spi_message *msg)
{
const int size = SJA1105_SIZE_SPI_MSG_HEADER;
memset(buf, 0, size);
sja1105_pack(buf, &msg->access, 31, 31, size);
sja1105_pack(buf, &msg->read_count, 30, 25, size);
sja1105_pack(buf, &msg->address, 24, 4, size);
}
/* If @rw is:
* - SPI_WRITE: creates and sends an SPI write message at absolute
* address reg_addr, taking @len bytes from *buf
* - SPI_READ: creates and sends an SPI read message from absolute
* address reg_addr, writing @len bytes into *buf
*/
static int sja1105_xfer(const struct sja1105_private *priv,
sja1105_spi_rw_mode_t rw, u64 reg_addr, u8 *buf,
size_t len, struct ptp_system_timestamp *ptp_sts)
{
u8 hdr_buf[SJA1105_SIZE_SPI_MSG_HEADER] = {0};
struct spi_device *spi = priv->spidev;
struct spi_transfer xfers[2] = {0};
struct spi_transfer *chunk_xfer;
struct spi_transfer *hdr_xfer;
struct sja1105_chunk chunk;
int num_chunks;
int rc, i = 0;
num_chunks = DIV_ROUND_UP(len, priv->max_xfer_len);
chunk.reg_addr = reg_addr;
chunk.buf = buf;
chunk.len = min_t(size_t, len, priv->max_xfer_len);
hdr_xfer = &xfers[0];
chunk_xfer = &xfers[1];
for (i = 0; i < num_chunks; i++) {
struct spi_transfer *ptp_sts_xfer;
struct sja1105_spi_message msg;
/* Populate the transfer's header buffer */
msg.address = chunk.reg_addr;
msg.access = rw;
if (rw == SPI_READ)
msg.read_count = chunk.len / 4;
else
/* Ignored */
msg.read_count = 0;
sja1105_spi_message_pack(hdr_buf, &msg);
hdr_xfer->tx_buf = hdr_buf;
hdr_xfer->len = SJA1105_SIZE_SPI_MSG_HEADER;
/* Populate the transfer's data buffer */
if (rw == SPI_READ)
chunk_xfer->rx_buf = chunk.buf;
else
chunk_xfer->tx_buf = chunk.buf;
chunk_xfer->len = chunk.len;
/* Request timestamping for the transfer. Instead of letting
* callers specify which byte they want to timestamp, we can
* make certain assumptions:
* - A read operation will request a software timestamp when
* what's being read is the PTP time. That is snapshotted by
* the switch hardware at the end of the command portion
* (hdr_xfer).
* - A write operation will request a software timestamp on
* actions that modify the PTP time. Taking clock stepping as
* an example, the switch writes the PTP time at the end of
* the data portion (chunk_xfer).
*/
if (rw == SPI_READ)
ptp_sts_xfer = hdr_xfer;
else
ptp_sts_xfer = chunk_xfer;
ptp_sts_xfer->ptp_sts_word_pre = ptp_sts_xfer->len - 1;
ptp_sts_xfer->ptp_sts_word_post = ptp_sts_xfer->len - 1;
ptp_sts_xfer->ptp_sts = ptp_sts;
/* Calculate next chunk */
chunk.buf += chunk.len;
chunk.reg_addr += chunk.len / 4;
chunk.len = min_t(size_t, (ptrdiff_t)(buf + len - chunk.buf),
priv->max_xfer_len);
rc = spi_sync_transfer(spi, xfers, 2);
if (rc < 0) {
dev_err(&spi->dev, "SPI transfer failed: %d\n", rc);
return rc;
}
}
return 0;
}
int sja1105_xfer_buf(const struct sja1105_private *priv,
sja1105_spi_rw_mode_t rw, u64 reg_addr,
u8 *buf, size_t len)
{
return sja1105_xfer(priv, rw, reg_addr, buf, len, NULL);
}
/* If @rw is:
* - SPI_WRITE: creates and sends an SPI write message at absolute
* address reg_addr
* - SPI_READ: creates and sends an SPI read message from absolute
* address reg_addr
*
* The u64 *value is unpacked, meaning that it's stored in the native
* CPU endianness and directly usable by software running on the core.
*/
int sja1105_xfer_u64(const struct sja1105_private *priv,
sja1105_spi_rw_mode_t rw, u64 reg_addr, u64 *value,
struct ptp_system_timestamp *ptp_sts)
{
u8 packed_buf[8];
int rc;
if (rw == SPI_WRITE)
sja1105_pack(packed_buf, value, 63, 0, 8);
rc = sja1105_xfer(priv, rw, reg_addr, packed_buf, 8, ptp_sts);
if (rw == SPI_READ)
sja1105_unpack(packed_buf, value, 63, 0, 8);
return rc;
}
/* Same as above, but transfers only a 4 byte word */
int sja1105_xfer_u32(const struct sja1105_private *priv,
sja1105_spi_rw_mode_t rw, u64 reg_addr, u32 *value,
struct ptp_system_timestamp *ptp_sts)
{
u8 packed_buf[4];
u64 tmp;
int rc;
if (rw == SPI_WRITE) {
/* The packing API only supports u64 as CPU word size,
* so we need to convert.
*/
tmp = *value;
sja1105_pack(packed_buf, &tmp, 31, 0, 4);
}
rc = sja1105_xfer(priv, rw, reg_addr, packed_buf, 4, ptp_sts);
if (rw == SPI_READ) {
sja1105_unpack(packed_buf, &tmp, 31, 0, 4);
*value = tmp;
}
return rc;
}
static int sja1105et_reset_cmd(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
const struct sja1105_regs *regs = priv->info->regs;
u32 cold_reset = BIT(3);
/* Cold reset */
return sja1105_xfer_u32(priv, SPI_WRITE, regs->rgu, &cold_reset, NULL);
}
static int sja1105pqrs_reset_cmd(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
const struct sja1105_regs *regs = priv->info->regs;
u32 cold_reset = BIT(2);
/* Cold reset */
return sja1105_xfer_u32(priv, SPI_WRITE, regs->rgu, &cold_reset, NULL);
}
static int sja1110_reset_cmd(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
const struct sja1105_regs *regs = priv->info->regs;
u32 switch_reset = BIT(20);
/* Only reset the switch core.
* A full cold reset would re-enable the BASE_MCSS_CLOCK PLL which
* would turn on the microcontroller, potentially letting it execute
* code which could interfere with our configuration.
*/
return sja1105_xfer_u32(priv, SPI_WRITE, regs->rgu, &switch_reset, NULL);
}
int sja1105_inhibit_tx(const struct sja1105_private *priv,
unsigned long port_bitmap, bool tx_inhibited)
{
const struct sja1105_regs *regs = priv->info->regs;
u32 inhibit_cmd;
int rc;
rc = sja1105_xfer_u32(priv, SPI_READ, regs->port_control,
&inhibit_cmd, NULL);
if (rc < 0)
return rc;
if (tx_inhibited)
inhibit_cmd |= port_bitmap;
else
inhibit_cmd &= ~port_bitmap;
return sja1105_xfer_u32(priv, SPI_WRITE, regs->port_control,
&inhibit_cmd, NULL);
}
struct sja1105_status {
u64 configs;
u64 crcchkl;
u64 ids;
u64 crcchkg;
};
/* This is not reading the entire General Status area, which is also
* divergent between E/T and P/Q/R/S, but only the relevant bits for
* ensuring that the static config upload procedure was successful.
*/
static void sja1105_status_unpack(void *buf, struct sja1105_status *status)
{
/* So that addition translates to 4 bytes */
u32 *p = buf;
/* device_id is missing from the buffer, but we don't
* want to diverge from the manual definition of the
* register addresses, so we'll back off one step with
* the register pointer, and never access p[0].
*/
p--;
sja1105_unpack(p + 0x1, &status->configs, 31, 31, 4);
sja1105_unpack(p + 0x1, &status->crcchkl, 30, 30, 4);
sja1105_unpack(p + 0x1, &status->ids, 29, 29, 4);
sja1105_unpack(p + 0x1, &status->crcchkg, 28, 28, 4);
}
static int sja1105_status_get(struct sja1105_private *priv,
struct sja1105_status *status)
{
const struct sja1105_regs *regs = priv->info->regs;
u8 packed_buf[4];
int rc;
rc = sja1105_xfer_buf(priv, SPI_READ, regs->status, packed_buf, 4);
if (rc < 0)
return rc;
sja1105_status_unpack(packed_buf, status);
return 0;
}
/* Not const because unpacking priv->static_config into buffers and preparing
* for upload requires the recalculation of table CRCs and updating the
* structures with these.
*/
int static_config_buf_prepare_for_upload(struct sja1105_private *priv,
void *config_buf, int buf_len)
{
struct sja1105_static_config *config = &priv->static_config;
struct sja1105_table_header final_header;
sja1105_config_valid_t valid;
char *final_header_ptr;
int crc_len;
valid = sja1105_static_config_check_valid(config,
priv->info->max_frame_mem);
if (valid != SJA1105_CONFIG_OK) {
dev_err(&priv->spidev->dev,
sja1105_static_config_error_msg[valid]);
return -EINVAL;
}
/* Write Device ID and config tables to config_buf */
sja1105_static_config_pack(config_buf, config);
/* Recalculate CRC of the last header (right now 0xDEADBEEF).
* Don't include the CRC field itself.
*/
crc_len = buf_len - 4;
/* Read the whole table header */
final_header_ptr = config_buf + buf_len - SJA1105_SIZE_TABLE_HEADER;
sja1105_table_header_packing(final_header_ptr, &final_header, UNPACK);
/* Modify */
final_header.crc = sja1105_crc32(config_buf, crc_len);
/* Rewrite */
sja1105_table_header_packing(final_header_ptr, &final_header, PACK);
return 0;
}
#define RETRIES 10
int sja1105_static_config_upload(struct sja1105_private *priv)
{
struct sja1105_static_config *config = &priv->static_config;
const struct sja1105_regs *regs = priv->info->regs;
struct device *dev = &priv->spidev->dev;
struct dsa_switch *ds = priv->ds;
struct sja1105_status status;
int rc, retries = RETRIES;
u8 *config_buf;
int buf_len;
buf_len = sja1105_static_config_get_length(config);
config_buf = kcalloc(buf_len, sizeof(char), GFP_KERNEL);
if (!config_buf)
return -ENOMEM;
rc = static_config_buf_prepare_for_upload(priv, config_buf, buf_len);
if (rc < 0) {
dev_err(dev, "Invalid config, cannot upload\n");
rc = -EINVAL;
goto out;
}
/* Prevent PHY jabbering during switch reset by inhibiting
* Tx on all ports and waiting for current packet to drain.
* Otherwise, the PHY will see an unterminated Ethernet packet.
*/
rc = sja1105_inhibit_tx(priv, GENMASK_ULL(ds->num_ports - 1, 0), true);
if (rc < 0) {
dev_err(dev, "Failed to inhibit Tx on ports\n");
rc = -ENXIO;
goto out;
}
/* Wait for an eventual egress packet to finish transmission
* (reach IFG). It is guaranteed that a second one will not
* follow, and that switch cold reset is thus safe
*/
usleep_range(500, 1000);
do {
/* Put the SJA1105 in programming mode */
rc = priv->info->reset_cmd(priv->ds);
if (rc < 0) {
dev_err(dev, "Failed to reset switch, retrying...\n");
continue;
}
/* Wait for the switch to come out of reset */
usleep_range(1000, 5000);
/* Upload the static config to the device */
rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->config,
config_buf, buf_len);
if (rc < 0) {
dev_err(dev, "Failed to upload config, retrying...\n");
continue;
}
/* Check that SJA1105 responded well to the config upload */
rc = sja1105_status_get(priv, &status);
if (rc < 0)
continue;
if (status.ids == 1) {
dev_err(dev, "Mismatch between hardware and static config "
"device id. Wrote 0x%llx, wants 0x%llx\n",
config->device_id, priv->info->device_id);
continue;
}
if (status.crcchkl == 1) {
dev_err(dev, "Switch reported invalid local CRC on "
"the uploaded config, retrying...\n");
continue;
}
if (status.crcchkg == 1) {
dev_err(dev, "Switch reported invalid global CRC on "
"the uploaded config, retrying...\n");
continue;
}
if (status.configs == 0) {
dev_err(dev, "Switch reported that configuration is "
"invalid, retrying...\n");
continue;
}
/* Success! */
break;
} while (--retries);
if (!retries) {
rc = -EIO;
dev_err(dev, "Failed to upload config to device, giving up\n");
goto out;
} else if (retries != RETRIES) {
dev_info(dev, "Succeeded after %d tried\n", RETRIES - retries);
}
out:
kfree(config_buf);
return rc;
}
static const struct sja1105_regs sja1105et_regs = {
.device_id = 0x0,
.prod_id = 0x100BC3,
.status = 0x1,
.port_control = 0x11,
.vl_status = 0x10000,
.config = 0x020000,
.rgu = 0x100440,
/* UM10944.pdf, Table 86, ACU Register overview */
.pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808},
.pad_mii_rx = {0x100801, 0x100803, 0x100805, 0x100807, 0x100809},
.rmii_pll1 = 0x10000A,
.cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F},
.stats[MAC] = {0x200, 0x202, 0x204, 0x206, 0x208},
.stats[HL1] = {0x400, 0x410, 0x420, 0x430, 0x440},
.stats[HL2] = {0x600, 0x610, 0x620, 0x630, 0x640},
/* UM10944.pdf, Table 78, CGU Register overview */
.mii_tx_clk = {0x100013, 0x10001A, 0x100021, 0x100028, 0x10002F},
.mii_rx_clk = {0x100014, 0x10001B, 0x100022, 0x100029, 0x100030},
.mii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034},
.mii_ext_rx_clk = {0x100019, 0x100020, 0x100027, 0x10002E, 0x100035},
.rgmii_tx_clk = {0x100016, 0x10001D, 0x100024, 0x10002B, 0x100032},
.rmii_ref_clk = {0x100015, 0x10001C, 0x100023, 0x10002A, 0x100031},
.rmii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034},
.ptpegr_ts = {0xC0, 0xC2, 0xC4, 0xC6, 0xC8},
.ptpschtm = 0x12, /* Spans 0x12 to 0x13 */
.ptppinst = 0x14,
.ptppindur = 0x16,
.ptp_control = 0x17,
.ptpclkval = 0x18, /* Spans 0x18 to 0x19 */
.ptpclkrate = 0x1A,
.ptpclkcorp = 0x1D,
.mdio_100base_tx = SJA1105_RSV_ADDR,
.mdio_100base_t1 = SJA1105_RSV_ADDR,
};
static const struct sja1105_regs sja1105pqrs_regs = {
.device_id = 0x0,
.prod_id = 0x100BC3,
.status = 0x1,
.port_control = 0x12,
.vl_status = 0x10000,
.config = 0x020000,
.rgu = 0x100440,
/* UM10944.pdf, Table 86, ACU Register overview */
.pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808},
.pad_mii_rx = {0x100801, 0x100803, 0x100805, 0x100807, 0x100809},
.pad_mii_id = {0x100810, 0x100811, 0x100812, 0x100813, 0x100814},
.rmii_pll1 = 0x10000A,
.cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F},
.stats[MAC] = {0x200, 0x202, 0x204, 0x206, 0x208},
.stats[HL1] = {0x400, 0x410, 0x420, 0x430, 0x440},
.stats[HL2] = {0x600, 0x610, 0x620, 0x630, 0x640},
.stats[ETHER] = {0x1400, 0x1418, 0x1430, 0x1448, 0x1460},
/* UM11040.pdf, Table 114 */
.mii_tx_clk = {0x100013, 0x100019, 0x10001F, 0x100025, 0x10002B},
.mii_rx_clk = {0x100014, 0x10001A, 0x100020, 0x100026, 0x10002C},
.mii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F},
.mii_ext_rx_clk = {0x100018, 0x10001E, 0x100024, 0x10002A, 0x100030},
.rgmii_tx_clk = {0x100016, 0x10001C, 0x100022, 0x100028, 0x10002E},
.rmii_ref_clk = {0x100015, 0x10001B, 0x100021, 0x100027, 0x10002D},
.rmii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F},
.ptpegr_ts = {0xC0, 0xC4, 0xC8, 0xCC, 0xD0},
.ptpschtm = 0x13, /* Spans 0x13 to 0x14 */
.ptppinst = 0x15,
.ptppindur = 0x17,
.ptp_control = 0x18,
.ptpclkval = 0x19,
.ptpclkrate = 0x1B,
.ptpclkcorp = 0x1E,
.ptpsyncts = 0x1F,
.mdio_100base_tx = SJA1105_RSV_ADDR,
.mdio_100base_t1 = SJA1105_RSV_ADDR,
};
static const struct sja1105_regs sja1110_regs = {
.device_id = SJA1110_SPI_ADDR(0x0),
.prod_id = SJA1110_ACU_ADDR(0xf00),
.status = SJA1110_SPI_ADDR(0x4),
.port_control = SJA1110_SPI_ADDR(0x50), /* actually INHIB_TX */
.vl_status = 0x10000,
.config = 0x020000,
.rgu = SJA1110_RGU_ADDR(0x100), /* Reset Control Register 0 */
/* Ports 2 and 3 are capable of xMII, but there isn't anything to
* configure in the CGU/ACU for them.
*/
.pad_mii_tx = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR},
.pad_mii_rx = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR},
.pad_mii_id = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1110_ACU_ADDR(0x18), SJA1110_ACU_ADDR(0x28),
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR},
.rmii_pll1 = SJA1105_RSV_ADDR,
.cgu_idiv = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
.stats[MAC] = {0x200, 0x202, 0x204, 0x206, 0x208, 0x20a,
0x20c, 0x20e, 0x210, 0x212, 0x214},
.stats[HL1] = {0x400, 0x410, 0x420, 0x430, 0x440, 0x450,
0x460, 0x470, 0x480, 0x490, 0x4a0},
.stats[HL2] = {0x600, 0x610, 0x620, 0x630, 0x640, 0x650,
0x660, 0x670, 0x680, 0x690, 0x6a0},
.stats[ETHER] = {0x1400, 0x1418, 0x1430, 0x1448, 0x1460, 0x1478,
0x1490, 0x14a8, 0x14c0, 0x14d8, 0x14f0},
.mii_tx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
.mii_rx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
.mii_ext_tx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
.mii_ext_rx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
.rgmii_tx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
.rmii_ref_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
.rmii_ext_tx_clk = {SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR},
.ptpschtm = SJA1110_SPI_ADDR(0x54),
.ptppinst = SJA1110_SPI_ADDR(0x5c),
.ptppindur = SJA1110_SPI_ADDR(0x64),
.ptp_control = SJA1110_SPI_ADDR(0x68),
.ptpclkval = SJA1110_SPI_ADDR(0x6c),
.ptpclkrate = SJA1110_SPI_ADDR(0x74),
.ptpclkcorp = SJA1110_SPI_ADDR(0x80),
.ptpsyncts = SJA1110_SPI_ADDR(0x84),
.mdio_100base_tx = 0x1c2400,
.mdio_100base_t1 = 0x1c1000,
.pcs_base = {SJA1105_RSV_ADDR, 0x1c1400, 0x1c1800, 0x1c1c00, 0x1c2000,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
};
const struct sja1105_info sja1105e_info = {
.device_id = SJA1105E_DEVICE_ID,
.part_no = SJA1105ET_PART_NO,
.static_ops = sja1105e_table_ops,
.dyn_ops = sja1105et_dyn_ops,
.tag_proto = DSA_TAG_PROTO_SJA1105,
.can_limit_mcast_flood = false,
.ptp_ts_bits = 24,
.ptpegr_ts_bytes = 4,
.max_frame_mem = SJA1105_MAX_FRAME_MEMORY,
.num_ports = SJA1105_NUM_PORTS,
.num_cbs_shapers = SJA1105ET_MAX_CBS_COUNT,
.reset_cmd = sja1105et_reset_cmd,
.fdb_add_cmd = sja1105et_fdb_add,
.fdb_del_cmd = sja1105et_fdb_del,
.ptp_cmd_packing = sja1105et_ptp_cmd_packing,
.rxtstamp = sja1105_rxtstamp,
.clocking_setup = sja1105_clocking_setup,
.regs = &sja1105et_regs,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 3,
[SJA1105_SPEED_100MBPS] = 2,
[SJA1105_SPEED_1000MBPS] = 1,
[SJA1105_SPEED_2500MBPS] = 0, /* Not supported */
},
.supports_mii = {true, true, true, true, true},
.supports_rmii = {true, true, true, true, true},
.supports_rgmii = {true, true, true, true, true},
.name = "SJA1105E",
};
const struct sja1105_info sja1105t_info = {
.device_id = SJA1105T_DEVICE_ID,
.part_no = SJA1105ET_PART_NO,
.static_ops = sja1105t_table_ops,
.dyn_ops = sja1105et_dyn_ops,
.tag_proto = DSA_TAG_PROTO_SJA1105,
.can_limit_mcast_flood = false,
.ptp_ts_bits = 24,
.ptpegr_ts_bytes = 4,
.max_frame_mem = SJA1105_MAX_FRAME_MEMORY,
.num_ports = SJA1105_NUM_PORTS,
.num_cbs_shapers = SJA1105ET_MAX_CBS_COUNT,
.reset_cmd = sja1105et_reset_cmd,
.fdb_add_cmd = sja1105et_fdb_add,
.fdb_del_cmd = sja1105et_fdb_del,
.ptp_cmd_packing = sja1105et_ptp_cmd_packing,
.rxtstamp = sja1105_rxtstamp,
.clocking_setup = sja1105_clocking_setup,
.regs = &sja1105et_regs,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 3,
[SJA1105_SPEED_100MBPS] = 2,
[SJA1105_SPEED_1000MBPS] = 1,
[SJA1105_SPEED_2500MBPS] = 0, /* Not supported */
},
.supports_mii = {true, true, true, true, true},
.supports_rmii = {true, true, true, true, true},
.supports_rgmii = {true, true, true, true, true},
.name = "SJA1105T",
};
const struct sja1105_info sja1105p_info = {
.device_id = SJA1105PR_DEVICE_ID,
.part_no = SJA1105P_PART_NO,
.static_ops = sja1105p_table_ops,
.dyn_ops = sja1105pqrs_dyn_ops,
.tag_proto = DSA_TAG_PROTO_SJA1105,
.can_limit_mcast_flood = true,
.ptp_ts_bits = 32,
.ptpegr_ts_bytes = 8,
.max_frame_mem = SJA1105_MAX_FRAME_MEMORY,
.num_ports = SJA1105_NUM_PORTS,
.num_cbs_shapers = SJA1105PQRS_MAX_CBS_COUNT,
.setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay,
.reset_cmd = sja1105pqrs_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1105_rxtstamp,
.clocking_setup = sja1105_clocking_setup,
.regs = &sja1105pqrs_regs,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 3,
[SJA1105_SPEED_100MBPS] = 2,
[SJA1105_SPEED_1000MBPS] = 1,
[SJA1105_SPEED_2500MBPS] = 0, /* Not supported */
},
.supports_mii = {true, true, true, true, true},
.supports_rmii = {true, true, true, true, true},
.supports_rgmii = {true, true, true, true, true},
.name = "SJA1105P",
};
const struct sja1105_info sja1105q_info = {
.device_id = SJA1105QS_DEVICE_ID,
.part_no = SJA1105Q_PART_NO,
.static_ops = sja1105q_table_ops,
.dyn_ops = sja1105pqrs_dyn_ops,
.tag_proto = DSA_TAG_PROTO_SJA1105,
.can_limit_mcast_flood = true,
.ptp_ts_bits = 32,
.ptpegr_ts_bytes = 8,
.max_frame_mem = SJA1105_MAX_FRAME_MEMORY,
.num_ports = SJA1105_NUM_PORTS,
.num_cbs_shapers = SJA1105PQRS_MAX_CBS_COUNT,
.setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay,
.reset_cmd = sja1105pqrs_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1105_rxtstamp,
.clocking_setup = sja1105_clocking_setup,
.regs = &sja1105pqrs_regs,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 3,
[SJA1105_SPEED_100MBPS] = 2,
[SJA1105_SPEED_1000MBPS] = 1,
[SJA1105_SPEED_2500MBPS] = 0, /* Not supported */
},
.supports_mii = {true, true, true, true, true},
.supports_rmii = {true, true, true, true, true},
.supports_rgmii = {true, true, true, true, true},
.name = "SJA1105Q",
};
const struct sja1105_info sja1105r_info = {
.device_id = SJA1105PR_DEVICE_ID,
.part_no = SJA1105R_PART_NO,
.static_ops = sja1105r_table_ops,
.dyn_ops = sja1105pqrs_dyn_ops,
.tag_proto = DSA_TAG_PROTO_SJA1105,
.can_limit_mcast_flood = true,
.ptp_ts_bits = 32,
.ptpegr_ts_bytes = 8,
.max_frame_mem = SJA1105_MAX_FRAME_MEMORY,
.num_ports = SJA1105_NUM_PORTS,
.num_cbs_shapers = SJA1105PQRS_MAX_CBS_COUNT,
.setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay,
.reset_cmd = sja1105pqrs_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1105_rxtstamp,
.clocking_setup = sja1105_clocking_setup,
.pcs_mdio_read_c45 = sja1105_pcs_mdio_read_c45,
.pcs_mdio_write_c45 = sja1105_pcs_mdio_write_c45,
.regs = &sja1105pqrs_regs,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 3,
[SJA1105_SPEED_100MBPS] = 2,
[SJA1105_SPEED_1000MBPS] = 1,
[SJA1105_SPEED_2500MBPS] = 0, /* Not supported */
},
.supports_mii = {true, true, true, true, true},
.supports_rmii = {true, true, true, true, true},
.supports_rgmii = {true, true, true, true, true},
.supports_sgmii = {false, false, false, false, true},
.name = "SJA1105R",
};
const struct sja1105_info sja1105s_info = {
.device_id = SJA1105QS_DEVICE_ID,
.part_no = SJA1105S_PART_NO,
.static_ops = sja1105s_table_ops,
.dyn_ops = sja1105pqrs_dyn_ops,
.regs = &sja1105pqrs_regs,
.tag_proto = DSA_TAG_PROTO_SJA1105,
.can_limit_mcast_flood = true,
.ptp_ts_bits = 32,
.ptpegr_ts_bytes = 8,
.max_frame_mem = SJA1105_MAX_FRAME_MEMORY,
.num_ports = SJA1105_NUM_PORTS,
.num_cbs_shapers = SJA1105PQRS_MAX_CBS_COUNT,
.setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay,
.reset_cmd = sja1105pqrs_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1105_rxtstamp,
.clocking_setup = sja1105_clocking_setup,
.pcs_mdio_read_c45 = sja1105_pcs_mdio_read_c45,
.pcs_mdio_write_c45 = sja1105_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 3,
[SJA1105_SPEED_100MBPS] = 2,
[SJA1105_SPEED_1000MBPS] = 1,
[SJA1105_SPEED_2500MBPS] = 0, /* Not supported */
},
.supports_mii = {true, true, true, true, true},
.supports_rmii = {true, true, true, true, true},
.supports_rgmii = {true, true, true, true, true},
.supports_sgmii = {false, false, false, false, true},
.name = "SJA1105S",
};
const struct sja1105_info sja1110a_info = {
.device_id = SJA1110_DEVICE_ID,
.part_no = SJA1110A_PART_NO,
.static_ops = sja1110_table_ops,
.dyn_ops = sja1110_dyn_ops,
.regs = &sja1110_regs,
.tag_proto = DSA_TAG_PROTO_SJA1110,
.can_limit_mcast_flood = true,
.multiple_cascade_ports = true,
.fixed_cbs_mapping = true,
.ptp_ts_bits = 32,
.ptpegr_ts_bytes = 8,
.max_frame_mem = SJA1110_MAX_FRAME_MEMORY,
.num_ports = SJA1110_NUM_PORTS,
.num_cbs_shapers = SJA1110_MAX_CBS_COUNT,
.setup_rgmii_delay = sja1110_setup_rgmii_delay,
.reset_cmd = sja1110_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1110_rxtstamp,
.txtstamp = sja1110_txtstamp,
.disable_microcontroller = sja1110_disable_microcontroller,
.pcs_mdio_read_c45 = sja1110_pcs_mdio_read_c45,
.pcs_mdio_write_c45 = sja1110_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 4,
[SJA1105_SPEED_100MBPS] = 3,
[SJA1105_SPEED_1000MBPS] = 2,
[SJA1105_SPEED_2500MBPS] = 1,
},
.supports_mii = {true, true, true, true, false,
true, true, true, true, true, true},
.supports_rmii = {false, false, true, true, false,
false, false, false, false, false, false},
.supports_rgmii = {false, false, true, true, false,
false, false, false, false, false, false},
.supports_sgmii = {false, true, true, true, true,
false, false, false, false, false, false},
.supports_2500basex = {false, false, false, true, true,
false, false, false, false, false, false},
.internal_phy = {SJA1105_NO_PHY, SJA1105_PHY_BASE_TX,
SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1},
.name = "SJA1110A",
};
const struct sja1105_info sja1110b_info = {
.device_id = SJA1110_DEVICE_ID,
.part_no = SJA1110B_PART_NO,
.static_ops = sja1110_table_ops,
.dyn_ops = sja1110_dyn_ops,
.regs = &sja1110_regs,
.tag_proto = DSA_TAG_PROTO_SJA1110,
.can_limit_mcast_flood = true,
.multiple_cascade_ports = true,
.fixed_cbs_mapping = true,
.ptp_ts_bits = 32,
.ptpegr_ts_bytes = 8,
.max_frame_mem = SJA1110_MAX_FRAME_MEMORY,
.num_ports = SJA1110_NUM_PORTS,
.num_cbs_shapers = SJA1110_MAX_CBS_COUNT,
.setup_rgmii_delay = sja1110_setup_rgmii_delay,
.reset_cmd = sja1110_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1110_rxtstamp,
.txtstamp = sja1110_txtstamp,
.disable_microcontroller = sja1110_disable_microcontroller,
.pcs_mdio_read_c45 = sja1110_pcs_mdio_read_c45,
.pcs_mdio_write_c45 = sja1110_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 4,
[SJA1105_SPEED_100MBPS] = 3,
[SJA1105_SPEED_1000MBPS] = 2,
[SJA1105_SPEED_2500MBPS] = 1,
},
.supports_mii = {true, true, true, true, false,
true, true, true, true, true, false},
.supports_rmii = {false, false, true, true, false,
false, false, false, false, false, false},
.supports_rgmii = {false, false, true, true, false,
false, false, false, false, false, false},
.supports_sgmii = {false, false, false, true, true,
false, false, false, false, false, false},
.supports_2500basex = {false, false, false, true, true,
false, false, false, false, false, false},
.internal_phy = {SJA1105_NO_PHY, SJA1105_PHY_BASE_TX,
SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_NO_PHY},
.name = "SJA1110B",
};
const struct sja1105_info sja1110c_info = {
.device_id = SJA1110_DEVICE_ID,
.part_no = SJA1110C_PART_NO,
.static_ops = sja1110_table_ops,
.dyn_ops = sja1110_dyn_ops,
.regs = &sja1110_regs,
.tag_proto = DSA_TAG_PROTO_SJA1110,
.can_limit_mcast_flood = true,
.multiple_cascade_ports = true,
.fixed_cbs_mapping = true,
.ptp_ts_bits = 32,
.ptpegr_ts_bytes = 8,
.max_frame_mem = SJA1110_MAX_FRAME_MEMORY,
.num_ports = SJA1110_NUM_PORTS,
.num_cbs_shapers = SJA1110_MAX_CBS_COUNT,
.setup_rgmii_delay = sja1110_setup_rgmii_delay,
.reset_cmd = sja1110_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1110_rxtstamp,
.txtstamp = sja1110_txtstamp,
.disable_microcontroller = sja1110_disable_microcontroller,
.pcs_mdio_read_c45 = sja1110_pcs_mdio_read_c45,
.pcs_mdio_write_c45 = sja1110_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 4,
[SJA1105_SPEED_100MBPS] = 3,
[SJA1105_SPEED_1000MBPS] = 2,
[SJA1105_SPEED_2500MBPS] = 1,
},
.supports_mii = {true, true, true, true, false,
true, true, true, false, false, false},
.supports_rmii = {false, false, true, true, false,
false, false, false, false, false, false},
.supports_rgmii = {false, false, true, true, false,
false, false, false, false, false, false},
.supports_sgmii = {false, false, false, false, true,
false, false, false, false, false, false},
.supports_2500basex = {false, false, false, false, true,
false, false, false, false, false, false},
.internal_phy = {SJA1105_NO_PHY, SJA1105_PHY_BASE_TX,
SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY},
.name = "SJA1110C",
};
const struct sja1105_info sja1110d_info = {
.device_id = SJA1110_DEVICE_ID,
.part_no = SJA1110D_PART_NO,
.static_ops = sja1110_table_ops,
.dyn_ops = sja1110_dyn_ops,
.regs = &sja1110_regs,
.tag_proto = DSA_TAG_PROTO_SJA1110,
.can_limit_mcast_flood = true,
.multiple_cascade_ports = true,
.fixed_cbs_mapping = true,
.ptp_ts_bits = 32,
.ptpegr_ts_bytes = 8,
.max_frame_mem = SJA1110_MAX_FRAME_MEMORY,
.num_ports = SJA1110_NUM_PORTS,
.num_cbs_shapers = SJA1110_MAX_CBS_COUNT,
.setup_rgmii_delay = sja1110_setup_rgmii_delay,
.reset_cmd = sja1110_reset_cmd,
.fdb_add_cmd = sja1105pqrs_fdb_add,
.fdb_del_cmd = sja1105pqrs_fdb_del,
.ptp_cmd_packing = sja1105pqrs_ptp_cmd_packing,
.rxtstamp = sja1110_rxtstamp,
.txtstamp = sja1110_txtstamp,
.disable_microcontroller = sja1110_disable_microcontroller,
.pcs_mdio_read_c45 = sja1110_pcs_mdio_read_c45,
.pcs_mdio_write_c45 = sja1110_pcs_mdio_write_c45,
.port_speed = {
[SJA1105_SPEED_AUTO] = 0,
[SJA1105_SPEED_10MBPS] = 4,
[SJA1105_SPEED_100MBPS] = 3,
[SJA1105_SPEED_1000MBPS] = 2,
[SJA1105_SPEED_2500MBPS] = 1,
},
.supports_mii = {true, false, true, false, false,
true, true, true, false, false, false},
.supports_rmii = {false, false, true, false, false,
false, false, false, false, false, false},
.supports_rgmii = {false, false, true, false, false,
false, false, false, false, false, false},
.supports_sgmii = {false, true, true, true, true,
false, false, false, false, false, false},
.supports_2500basex = {false, false, false, true, true,
false, false, false, false, false, false},
.internal_phy = {SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY, SJA1105_PHY_BASE_T1,
SJA1105_PHY_BASE_T1, SJA1105_PHY_BASE_T1,
SJA1105_NO_PHY, SJA1105_NO_PHY,
SJA1105_NO_PHY},
.name = "SJA1110D",
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,548 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright 2016-2018 NXP
* Copyright (c) 2018-2019, Vladimir Oltean <olteanv@gmail.com>
*/
#ifndef _SJA1105_STATIC_CONFIG_H
#define _SJA1105_STATIC_CONFIG_H
#include <linux/packing.h>
#include <linux/types.h>
#include <asm/types.h>
#define SJA1105_NUM_PORTS 5
#define SJA1110_NUM_PORTS 11
#define SJA1105_MAX_NUM_PORTS SJA1110_NUM_PORTS
#define SJA1105_NUM_TC 8
#define SJA1105_SIZE_SPI_MSG_HEADER 4
#define SJA1105_SIZE_SPI_MSG_MAXLEN (64 * 4)
#define SJA1105_SIZE_DEVICE_ID 4
#define SJA1105_SIZE_TABLE_HEADER 12
#define SJA1105_SIZE_SCHEDULE_ENTRY 8
#define SJA1110_SIZE_SCHEDULE_ENTRY 12
#define SJA1105_SIZE_SCHEDULE_ENTRY_POINTS_ENTRY 4
#define SJA1110_SIZE_SCHEDULE_ENTRY_POINTS_ENTRY 8
#define SJA1105_SIZE_VL_LOOKUP_ENTRY 12
#define SJA1105_SIZE_VL_POLICING_ENTRY 8
#define SJA1105_SIZE_VL_FORWARDING_ENTRY 4
#define SJA1105_SIZE_L2_POLICING_ENTRY 8
#define SJA1105_SIZE_VLAN_LOOKUP_ENTRY 8
#define SJA1110_SIZE_VLAN_LOOKUP_ENTRY 12
#define SJA1105_SIZE_L2_FORWARDING_ENTRY 8
#define SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY 12
#define SJA1105_SIZE_RETAGGING_ENTRY 8
#define SJA1105_SIZE_XMII_PARAMS_ENTRY 4
#define SJA1110_SIZE_XMII_PARAMS_ENTRY 8
#define SJA1105_SIZE_SCHEDULE_PARAMS_ENTRY 12
#define SJA1105_SIZE_SCHEDULE_ENTRY_POINTS_PARAMS_ENTRY 4
#define SJA1105_SIZE_VL_FORWARDING_PARAMS_ENTRY 12
#define SJA1105ET_SIZE_L2_LOOKUP_ENTRY 12
#define SJA1105ET_SIZE_MAC_CONFIG_ENTRY 28
#define SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY 4
#define SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY 40
#define SJA1105ET_SIZE_AVB_PARAMS_ENTRY 12
#define SJA1105ET_SIZE_CBS_ENTRY 16
#define SJA1105PQRS_SIZE_L2_LOOKUP_ENTRY 20
#define SJA1110_SIZE_L2_LOOKUP_ENTRY 24
#define SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY 32
#define SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY 16
#define SJA1110_SIZE_L2_LOOKUP_PARAMS_ENTRY 28
#define SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY 44
#define SJA1110_SIZE_GENERAL_PARAMS_ENTRY 56
#define SJA1105PQRS_SIZE_AVB_PARAMS_ENTRY 16
#define SJA1105PQRS_SIZE_CBS_ENTRY 20
#define SJA1110_SIZE_PCP_REMAPPING_ENTRY 4
/* UM10944.pdf Page 11, Table 2. Configuration Blocks */
enum {
BLKID_SCHEDULE = 0x00,
BLKID_SCHEDULE_ENTRY_POINTS = 0x01,
BLKID_VL_LOOKUP = 0x02,
BLKID_VL_POLICING = 0x03,
BLKID_VL_FORWARDING = 0x04,
BLKID_L2_LOOKUP = 0x05,
BLKID_L2_POLICING = 0x06,
BLKID_VLAN_LOOKUP = 0x07,
BLKID_L2_FORWARDING = 0x08,
BLKID_MAC_CONFIG = 0x09,
BLKID_SCHEDULE_PARAMS = 0x0A,
BLKID_SCHEDULE_ENTRY_POINTS_PARAMS = 0x0B,
BLKID_VL_FORWARDING_PARAMS = 0x0C,
BLKID_L2_LOOKUP_PARAMS = 0x0D,
BLKID_L2_FORWARDING_PARAMS = 0x0E,
BLKID_AVB_PARAMS = 0x10,
BLKID_GENERAL_PARAMS = 0x11,
BLKID_RETAGGING = 0x12,
BLKID_CBS = 0x13,
BLKID_PCP_REMAPPING = 0x1C,
BLKID_XMII_PARAMS = 0x4E,
};
enum sja1105_blk_idx {
BLK_IDX_SCHEDULE = 0,
BLK_IDX_SCHEDULE_ENTRY_POINTS,
BLK_IDX_VL_LOOKUP,
BLK_IDX_VL_POLICING,
BLK_IDX_VL_FORWARDING,
BLK_IDX_L2_LOOKUP,
BLK_IDX_L2_POLICING,
BLK_IDX_VLAN_LOOKUP,
BLK_IDX_L2_FORWARDING,
BLK_IDX_MAC_CONFIG,
BLK_IDX_SCHEDULE_PARAMS,
BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS,
BLK_IDX_VL_FORWARDING_PARAMS,
BLK_IDX_L2_LOOKUP_PARAMS,
BLK_IDX_L2_FORWARDING_PARAMS,
BLK_IDX_AVB_PARAMS,
BLK_IDX_GENERAL_PARAMS,
BLK_IDX_RETAGGING,
BLK_IDX_CBS,
BLK_IDX_XMII_PARAMS,
BLK_IDX_PCP_REMAPPING,
BLK_IDX_MAX,
/* Fake block indices that are only valid for dynamic access */
BLK_IDX_MGMT_ROUTE,
BLK_IDX_MAX_DYN,
BLK_IDX_INVAL = -1,
};
#define SJA1105_MAX_SCHEDULE_COUNT 1024
#define SJA1110_MAX_SCHEDULE_COUNT 4096
#define SJA1105_MAX_SCHEDULE_ENTRY_POINTS_COUNT 2048
#define SJA1105_MAX_VL_LOOKUP_COUNT 1024
#define SJA1110_MAX_VL_LOOKUP_COUNT 4096
#define SJA1105_MAX_VL_POLICING_COUNT 1024
#define SJA1110_MAX_VL_POLICING_COUNT 4096
#define SJA1105_MAX_VL_FORWARDING_COUNT 1024
#define SJA1110_MAX_VL_FORWARDING_COUNT 4096
#define SJA1105_MAX_L2_LOOKUP_COUNT 1024
#define SJA1105_MAX_L2_POLICING_COUNT 45
#define SJA1110_MAX_L2_POLICING_COUNT 110
#define SJA1105_MAX_VLAN_LOOKUP_COUNT 4096
#define SJA1105_MAX_L2_FORWARDING_COUNT 13
#define SJA1110_MAX_L2_FORWARDING_COUNT 19
#define SJA1105_MAX_MAC_CONFIG_COUNT 5
#define SJA1110_MAX_MAC_CONFIG_COUNT 11
#define SJA1105_MAX_SCHEDULE_PARAMS_COUNT 1
#define SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT 1
#define SJA1105_MAX_VL_FORWARDING_PARAMS_COUNT 1
#define SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT 1
#define SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT 1
#define SJA1105_MAX_GENERAL_PARAMS_COUNT 1
#define SJA1105_MAX_RETAGGING_COUNT 32
#define SJA1105_MAX_XMII_PARAMS_COUNT 1
#define SJA1105_MAX_AVB_PARAMS_COUNT 1
#define SJA1105ET_MAX_CBS_COUNT 10
#define SJA1105PQRS_MAX_CBS_COUNT 16
#define SJA1110_MAX_CBS_COUNT 80
#define SJA1110_MAX_PCP_REMAPPING_COUNT 11
#define SJA1105_MAX_FRAME_MEMORY 929
#define SJA1110_MAX_FRAME_MEMORY 1820
#define SJA1105_FRAME_MEMORY_RETAGGING_OVERHEAD 19
#define SJA1105_VL_FRAME_MEMORY 100
#define SJA1105E_DEVICE_ID 0x9C00000Cull
#define SJA1105T_DEVICE_ID 0x9E00030Eull
#define SJA1105PR_DEVICE_ID 0xAF00030Eull
#define SJA1105QS_DEVICE_ID 0xAE00030Eull
#define SJA1110_DEVICE_ID 0xB700030Full
#define SJA1105ET_PART_NO 0x9A83
#define SJA1105P_PART_NO 0x9A84
#define SJA1105Q_PART_NO 0x9A85
#define SJA1105R_PART_NO 0x9A86
#define SJA1105S_PART_NO 0x9A87
#define SJA1110A_PART_NO 0x1110
#define SJA1110B_PART_NO 0x1111
#define SJA1110C_PART_NO 0x1112
#define SJA1110D_PART_NO 0x1113
#define SJA1110_ACU 0x1c4400
#define SJA1110_RGU 0x1c6000
#define SJA1110_CGU 0x1c6400
#define SJA1110_SPI_ADDR(x) ((x) / 4)
#define SJA1110_ACU_ADDR(x) (SJA1110_ACU + SJA1110_SPI_ADDR(x))
#define SJA1110_CGU_ADDR(x) (SJA1110_CGU + SJA1110_SPI_ADDR(x))
#define SJA1110_RGU_ADDR(x) (SJA1110_RGU + SJA1110_SPI_ADDR(x))
#define SJA1105_RSV_ADDR 0xffffffffffffffffull
struct sja1105_schedule_entry {
u64 winstindex;
u64 winend;
u64 winst;
u64 destports;
u64 setvalid;
u64 txen;
u64 resmedia_en;
u64 resmedia;
u64 vlindex;
u64 delta;
};
struct sja1105_schedule_params_entry {
u64 subscheind[8];
};
struct sja1105_general_params_entry {
u64 vllupformat;
u64 mirr_ptacu;
u64 switchid;
u64 hostprio;
u64 mac_fltres1;
u64 mac_fltres0;
u64 mac_flt1;
u64 mac_flt0;
u64 incl_srcpt1;
u64 incl_srcpt0;
u64 send_meta1;
u64 send_meta0;
u64 casc_port;
u64 host_port;
u64 mirr_port;
u64 vlmarker;
u64 vlmask;
u64 tpid;
u64 ignore2stf;
u64 tpid2;
/* P/Q/R/S only */
u64 queue_ts;
u64 egrmirrvid;
u64 egrmirrpcp;
u64 egrmirrdei;
u64 replay_port;
/* SJA1110 only */
u64 tte_en;
u64 tdmaconfigidx;
u64 header_type;
};
struct sja1105_schedule_entry_points_entry {
u64 subschindx;
u64 delta;
u64 address;
};
struct sja1105_schedule_entry_points_params_entry {
u64 clksrc;
u64 actsubsch;
};
struct sja1105_vlan_lookup_entry {
u64 ving_mirr;
u64 vegr_mirr;
u64 vmemb_port;
u64 vlan_bc;
u64 tag_port;
u64 vlanid;
u64 type_entry; /* SJA1110 only */
};
struct sja1105_l2_lookup_entry {
u64 vlanid;
u64 macaddr;
u64 destports;
u64 enfport;
u64 index;
/* P/Q/R/S only */
u64 mask_iotag;
u64 mask_vlanid;
u64 mask_macaddr;
u64 mask_srcport;
u64 iotag;
u64 srcport;
u64 lockeds;
union {
/* LOCKEDS=1: Static FDB entries */
struct {
/* TSREG is deprecated in SJA1110, TRAP is supported only
* in SJA1110.
*/
u64 trap;
u64 tsreg;
u64 mirrvlan;
u64 takets;
u64 mirr;
u64 retag;
};
/* LOCKEDS=0: Dynamically learned FDB entries */
struct {
u64 touched;
u64 age;
};
};
};
struct sja1105_l2_lookup_params_entry {
u64 maxaddrp[SJA1105_MAX_NUM_PORTS]; /* P/Q/R/S only */
u64 start_dynspc; /* P/Q/R/S only */
u64 drpnolearn; /* P/Q/R/S only */
u64 use_static; /* P/Q/R/S only */
u64 owr_dyn; /* P/Q/R/S only */
u64 learn_once; /* P/Q/R/S only */
u64 maxage; /* Shared */
u64 dyn_tbsz; /* E/T only */
u64 poly; /* E/T only */
u64 shared_learn; /* Shared */
u64 no_enf_hostprt; /* Shared */
u64 no_mgmt_learn; /* Shared */
};
struct sja1105_l2_forwarding_entry {
u64 bc_domain;
u64 reach_port;
u64 fl_domain;
/* This is actually max(SJA1105_NUM_TC, SJA1105_MAX_NUM_PORTS) */
u64 vlan_pmap[SJA1105_MAX_NUM_PORTS];
bool type_egrpcp2outputq;
};
struct sja1105_l2_forwarding_params_entry {
u64 max_dynp;
u64 part_spc[8];
};
struct sja1105_l2_policing_entry {
u64 sharindx;
u64 smax;
u64 rate;
u64 maxlen;
u64 partition;
};
struct sja1105_avb_params_entry {
u64 cas_master;
u64 destmeta;
u64 srcmeta;
};
struct sja1105_mac_config_entry {
u64 top[8];
u64 base[8];
u64 enabled[8];
u64 ifg;
u64 speed;
u64 tp_delin;
u64 tp_delout;
u64 maxage;
u64 vlanprio;
u64 vlanid;
u64 ing_mirr;
u64 egr_mirr;
u64 drpnona664;
u64 drpdtag;
u64 drpuntag;
u64 retag;
u64 dyn_learn;
u64 egress;
u64 ingress;
};
struct sja1105_retagging_entry {
u64 egr_port;
u64 ing_port;
u64 vlan_ing;
u64 vlan_egr;
u64 do_not_learn;
u64 use_dest_ports;
u64 destports;
};
struct sja1105_cbs_entry {
u64 port; /* Not used for SJA1110 */
u64 prio; /* Not used for SJA1110 */
u64 credit_hi;
u64 credit_lo;
u64 send_slope;
u64 idle_slope;
};
struct sja1105_xmii_params_entry {
u64 phy_mac[SJA1105_MAX_NUM_PORTS];
u64 xmii_mode[SJA1105_MAX_NUM_PORTS];
/* The SJA1110 insists being a snowflake, and requires SGMII,
* 2500base-x and internal MII ports connected to the 100base-TX PHY to
* set this bit. We set it unconditionally from the high-level logic,
* and only sja1110_xmii_params_entry_packing writes it to the static
* config. I have no better name for it than "special".
*/
u64 special[SJA1105_MAX_NUM_PORTS];
};
struct sja1110_pcp_remapping_entry {
u64 egrpcp[SJA1105_NUM_TC];
};
enum {
SJA1105_VL_FORMAT_PSFP = 0,
SJA1105_VL_FORMAT_ARINC664 = 1,
};
struct sja1105_vl_lookup_entry {
u64 format;
u64 port;
union {
/* SJA1105_VL_FORMAT_PSFP */
struct {
u64 destports;
u64 iscritical;
u64 macaddr;
u64 vlanid;
u64 vlanprior;
};
/* SJA1105_VL_FORMAT_ARINC664 */
struct {
u64 egrmirr;
u64 ingrmirr;
u64 vlid;
};
};
/* Not part of hardware structure */
unsigned long flow_cookie;
};
struct sja1105_vl_policing_entry {
u64 type;
u64 maxlen;
u64 sharindx;
u64 bag;
u64 jitter;
};
struct sja1105_vl_forwarding_entry {
u64 type;
u64 priority;
u64 partition;
u64 destports;
};
struct sja1105_vl_forwarding_params_entry {
u64 partspc[8];
u64 debugen;
};
struct sja1105_table_header {
u64 block_id;
u64 len;
u64 crc;
};
struct sja1105_table_ops {
size_t (*packing)(void *buf, void *entry_ptr, enum packing_op op);
size_t unpacked_entry_size;
size_t packed_entry_size;
size_t max_entry_count;
};
struct sja1105_table {
const struct sja1105_table_ops *ops;
size_t entry_count;
void *entries;
};
struct sja1105_static_config {
u64 device_id;
struct sja1105_table tables[BLK_IDX_MAX];
};
extern const struct sja1105_table_ops sja1105e_table_ops[BLK_IDX_MAX];
extern const struct sja1105_table_ops sja1105t_table_ops[BLK_IDX_MAX];
extern const struct sja1105_table_ops sja1105p_table_ops[BLK_IDX_MAX];
extern const struct sja1105_table_ops sja1105q_table_ops[BLK_IDX_MAX];
extern const struct sja1105_table_ops sja1105r_table_ops[BLK_IDX_MAX];
extern const struct sja1105_table_ops sja1105s_table_ops[BLK_IDX_MAX];
extern const struct sja1105_table_ops sja1110_table_ops[BLK_IDX_MAX];
size_t sja1105_table_header_packing(void *buf, void *hdr, enum packing_op op);
void
sja1105_table_header_pack_with_crc(void *buf, struct sja1105_table_header *hdr);
size_t
sja1105_static_config_get_length(const struct sja1105_static_config *config);
typedef enum {
SJA1105_CONFIG_OK = 0,
SJA1105_TTETHERNET_NOT_SUPPORTED,
SJA1105_INCORRECT_TTETHERNET_CONFIGURATION,
SJA1105_INCORRECT_VIRTUAL_LINK_CONFIGURATION,
SJA1105_MISSING_L2_POLICING_TABLE,
SJA1105_MISSING_L2_FORWARDING_TABLE,
SJA1105_MISSING_L2_FORWARDING_PARAMS_TABLE,
SJA1105_MISSING_GENERAL_PARAMS_TABLE,
SJA1105_MISSING_VLAN_TABLE,
SJA1105_MISSING_XMII_TABLE,
SJA1105_MISSING_MAC_TABLE,
SJA1105_OVERCOMMITTED_FRAME_MEMORY,
} sja1105_config_valid_t;
extern const char *sja1105_static_config_error_msg[];
sja1105_config_valid_t
sja1105_static_config_check_valid(const struct sja1105_static_config *config,
int max_mem);
void
sja1105_static_config_pack(void *buf, struct sja1105_static_config *config);
int sja1105_static_config_init(struct sja1105_static_config *config,
const struct sja1105_table_ops *static_ops,
u64 device_id);
void sja1105_static_config_free(struct sja1105_static_config *config);
int sja1105_table_delete_entry(struct sja1105_table *table, int i);
int sja1105_table_resize(struct sja1105_table *table, size_t new_count);
u32 sja1105_crc32(const void *buf, size_t len);
void sja1105_pack(void *buf, const u64 *val, int start, int end, size_t len);
void sja1105_unpack(const void *buf, u64 *val, int start, int end, size_t len);
void sja1105_packing(void *buf, u64 *val, int start, int end,
size_t len, enum packing_op op);
/* Common implementations for the static and dynamic configs */
size_t sja1105pqrs_general_params_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_general_params_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105pqrs_l2_lookup_params_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_l2_lookup_params_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105_l2_forwarding_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_l2_forwarding_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105pqrs_l2_lookup_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105et_l2_lookup_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_l2_lookup_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105_vlan_lookup_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_vlan_lookup_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105_retagging_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_retagging_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105pqrs_mac_config_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_mac_config_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105pqrs_avb_params_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1105_vl_lookup_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_vl_lookup_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_vl_policing_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_xmii_params_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_l2_policing_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
size_t sja1110_l2_forwarding_params_entry_packing(void *buf, void *entry_ptr,
enum packing_op op);
#endif
@@ -0,0 +1,898 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com>
*/
#include "sja1105.h"
#define SJA1105_TAS_CLKSRC_DISABLED 0
#define SJA1105_TAS_CLKSRC_STANDALONE 1
#define SJA1105_TAS_CLKSRC_AS6802 2
#define SJA1105_TAS_CLKSRC_PTP 3
#define SJA1105_GATE_MASK GENMASK_ULL(SJA1105_NUM_TC - 1, 0)
#define work_to_sja1105_tas(d) \
container_of((d), struct sja1105_tas_data, tas_work)
#define tas_to_sja1105(d) \
container_of((d), struct sja1105_private, tas_data)
static int sja1105_tas_set_runtime_params(struct sja1105_private *priv)
{
struct sja1105_tas_data *tas_data = &priv->tas_data;
struct sja1105_gating_config *gating_cfg = &tas_data->gating_cfg;
struct dsa_switch *ds = priv->ds;
s64 earliest_base_time = S64_MAX;
s64 latest_base_time = 0;
s64 its_cycle_time = 0;
s64 max_cycle_time = 0;
int port;
tas_data->enabled = false;
for (port = 0; port < ds->num_ports; port++) {
const struct tc_taprio_qopt_offload *offload;
offload = tas_data->offload[port];
if (!offload)
continue;
tas_data->enabled = true;
if (max_cycle_time < offload->cycle_time)
max_cycle_time = offload->cycle_time;
if (latest_base_time < offload->base_time)
latest_base_time = offload->base_time;
if (earliest_base_time > offload->base_time) {
earliest_base_time = offload->base_time;
its_cycle_time = offload->cycle_time;
}
}
if (!list_empty(&gating_cfg->entries)) {
tas_data->enabled = true;
if (max_cycle_time < gating_cfg->cycle_time)
max_cycle_time = gating_cfg->cycle_time;
if (latest_base_time < gating_cfg->base_time)
latest_base_time = gating_cfg->base_time;
if (earliest_base_time > gating_cfg->base_time) {
earliest_base_time = gating_cfg->base_time;
its_cycle_time = gating_cfg->cycle_time;
}
}
if (!tas_data->enabled)
return 0;
/* Roll the earliest base time over until it is in a comparable
* time base with the latest, then compare their deltas.
* We want to enforce that all ports' base times are within
* SJA1105_TAS_MAX_DELTA 200ns cycles of one another.
*/
earliest_base_time = future_base_time(earliest_base_time,
its_cycle_time,
latest_base_time);
while (earliest_base_time > latest_base_time)
earliest_base_time -= its_cycle_time;
if (latest_base_time - earliest_base_time >
sja1105_delta_to_ns(SJA1105_TAS_MAX_DELTA)) {
dev_err(ds->dev,
"Base times too far apart: min %llu max %llu\n",
earliest_base_time, latest_base_time);
return -ERANGE;
}
tas_data->earliest_base_time = earliest_base_time;
tas_data->max_cycle_time = max_cycle_time;
dev_dbg(ds->dev, "earliest base time %lld ns\n", earliest_base_time);
dev_dbg(ds->dev, "latest base time %lld ns\n", latest_base_time);
dev_dbg(ds->dev, "longest cycle time %lld ns\n", max_cycle_time);
return 0;
}
/* Lo and behold: the egress scheduler from hell.
*
* At the hardware level, the Time-Aware Shaper holds a global linear arrray of
* all schedule entries for all ports. These are the Gate Control List (GCL)
* entries, let's call them "timeslots" for short. This linear array of
* timeslots is held in BLK_IDX_SCHEDULE.
*
* Then there are a maximum of 8 "execution threads" inside the switch, which
* iterate cyclically through the "schedule". Each "cycle" has an entry point
* and an exit point, both being timeslot indices in the schedule table. The
* hardware calls each cycle a "subschedule".
*
* Subschedule (cycle) i starts when
* ptpclkval >= ptpschtm + BLK_IDX_SCHEDULE_ENTRY_POINTS[i].delta.
*
* The hardware scheduler iterates BLK_IDX_SCHEDULE with a k ranging from
* k = BLK_IDX_SCHEDULE_ENTRY_POINTS[i].address to
* k = BLK_IDX_SCHEDULE_PARAMS.subscheind[i]
*
* For each schedule entry (timeslot) k, the engine executes the gate control
* list entry for the duration of BLK_IDX_SCHEDULE[k].delta.
*
* +---------+
* | | BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS
* +---------+
* |
* +-----------------+
* | .actsubsch
* BLK_IDX_SCHEDULE_ENTRY_POINTS v
* +-------+-------+
* |cycle 0|cycle 1|
* +-------+-------+
* | | | |
* +----------------+ | | +-------------------------------------+
* | .subschindx | | .subschindx |
* | | +---------------+ |
* | .address | .address | |
* | | | |
* | | | |
* | BLK_IDX_SCHEDULE v v |
* | +-------+-------+-------+-------+-------+------+ |
* | |entry 0|entry 1|entry 2|entry 3|entry 4|entry5| |
* | +-------+-------+-------+-------+-------+------+ |
* | ^ ^ ^ ^ |
* | | | | | |
* | +-------------------------+ | | | |
* | | +-------------------------------+ | | |
* | | | +-------------------+ | |
* | | | | | |
* | +---------------------------------------------------------------+ |
* | |subscheind[0]<=subscheind[1]<=subscheind[2]<=...<=subscheind[7]| |
* | +---------------------------------------------------------------+ |
* | ^ ^ BLK_IDX_SCHEDULE_PARAMS |
* | | | |
* +--------+ +-------------------------------------------+
*
* In the above picture there are two subschedules (cycles):
*
* - cycle 0: iterates the schedule table from 0 to 2 (and back)
* - cycle 1: iterates the schedule table from 3 to 5 (and back)
*
* All other possible execution threads must be marked as unused by making
* their "subschedule end index" (subscheind) equal to the last valid
* subschedule's end index (in this case 5).
*/
int sja1105_init_scheduling(struct sja1105_private *priv)
{
struct sja1105_schedule_entry_points_entry *schedule_entry_points;
struct sja1105_schedule_entry_points_params_entry
*schedule_entry_points_params;
struct sja1105_schedule_params_entry *schedule_params;
struct sja1105_tas_data *tas_data = &priv->tas_data;
struct sja1105_gating_config *gating_cfg = &tas_data->gating_cfg;
struct sja1105_schedule_entry *schedule;
struct dsa_switch *ds = priv->ds;
struct sja1105_table *table;
int schedule_start_idx;
s64 entry_point_delta;
int schedule_end_idx;
int num_entries = 0;
int num_cycles = 0;
int cycle = 0;
int i, k = 0;
int port, rc;
rc = sja1105_tas_set_runtime_params(priv);
if (rc < 0)
return rc;
/* Discard previous Schedule Table */
table = &priv->static_config.tables[BLK_IDX_SCHEDULE];
if (table->entry_count) {
kfree(table->entries);
table->entry_count = 0;
}
/* Discard previous Schedule Entry Points Parameters Table */
table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS];
if (table->entry_count) {
kfree(table->entries);
table->entry_count = 0;
}
/* Discard previous Schedule Parameters Table */
table = &priv->static_config.tables[BLK_IDX_SCHEDULE_PARAMS];
if (table->entry_count) {
kfree(table->entries);
table->entry_count = 0;
}
/* Discard previous Schedule Entry Points Table */
table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS];
if (table->entry_count) {
kfree(table->entries);
table->entry_count = 0;
}
/* Figure out the dimensioning of the problem */
for (port = 0; port < ds->num_ports; port++) {
if (tas_data->offload[port]) {
num_entries += tas_data->offload[port]->num_entries;
num_cycles++;
}
}
if (!list_empty(&gating_cfg->entries)) {
num_entries += gating_cfg->num_entries;
num_cycles++;
}
/* Nothing to do */
if (!num_cycles)
return 0;
/* Pre-allocate space in the static config tables */
/* Schedule Table */
table = &priv->static_config.tables[BLK_IDX_SCHEDULE];
table->entries = kcalloc(num_entries, table->ops->unpacked_entry_size,
GFP_KERNEL);
if (!table->entries)
return -ENOMEM;
table->entry_count = num_entries;
schedule = table->entries;
/* Schedule Points Parameters Table */
table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS];
table->entries = kcalloc(SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT,
table->ops->unpacked_entry_size, GFP_KERNEL);
if (!table->entries)
/* Previously allocated memory will be freed automatically in
* sja1105_static_config_free. This is true for all early
* returns below.
*/
return -ENOMEM;
table->entry_count = SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT;
schedule_entry_points_params = table->entries;
/* Schedule Parameters Table */
table = &priv->static_config.tables[BLK_IDX_SCHEDULE_PARAMS];
table->entries = kcalloc(SJA1105_MAX_SCHEDULE_PARAMS_COUNT,
table->ops->unpacked_entry_size, GFP_KERNEL);
if (!table->entries)
return -ENOMEM;
table->entry_count = SJA1105_MAX_SCHEDULE_PARAMS_COUNT;
schedule_params = table->entries;
/* Schedule Entry Points Table */
table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS];
table->entries = kcalloc(num_cycles, table->ops->unpacked_entry_size,
GFP_KERNEL);
if (!table->entries)
return -ENOMEM;
table->entry_count = num_cycles;
schedule_entry_points = table->entries;
/* Finally start populating the static config tables */
schedule_entry_points_params->clksrc = SJA1105_TAS_CLKSRC_PTP;
schedule_entry_points_params->actsubsch = num_cycles - 1;
for (port = 0; port < ds->num_ports; port++) {
const struct tc_taprio_qopt_offload *offload;
/* Relative base time */
s64 rbt;
offload = tas_data->offload[port];
if (!offload)
continue;
schedule_start_idx = k;
schedule_end_idx = k + offload->num_entries - 1;
/* This is the base time expressed as a number of TAS ticks
* relative to PTPSCHTM, which we'll (perhaps improperly) call
* the operational base time.
*/
rbt = future_base_time(offload->base_time,
offload->cycle_time,
tas_data->earliest_base_time);
rbt -= tas_data->earliest_base_time;
/* UM10944.pdf 4.2.2. Schedule Entry Points table says that
* delta cannot be zero, which is shitty. Advance all relative
* base times by 1 TAS delta, so that even the earliest base
* time becomes 1 in relative terms. Then start the operational
* base time (PTPSCHTM) one TAS delta earlier than planned.
*/
entry_point_delta = ns_to_sja1105_delta(rbt) + 1;
schedule_entry_points[cycle].subschindx = cycle;
schedule_entry_points[cycle].delta = entry_point_delta;
schedule_entry_points[cycle].address = schedule_start_idx;
/* The subschedule end indices need to be
* monotonically increasing.
*/
for (i = cycle; i < 8; i++)
schedule_params->subscheind[i] = schedule_end_idx;
for (i = 0; i < offload->num_entries; i++, k++) {
s64 delta_ns = offload->entries[i].interval;
schedule[k].delta = ns_to_sja1105_delta(delta_ns);
schedule[k].destports = BIT(port);
schedule[k].resmedia_en = true;
schedule[k].resmedia = SJA1105_GATE_MASK &
~offload->entries[i].gate_mask;
}
cycle++;
}
if (!list_empty(&gating_cfg->entries)) {
struct sja1105_gate_entry *e;
/* Relative base time */
s64 rbt;
schedule_start_idx = k;
schedule_end_idx = k + gating_cfg->num_entries - 1;
rbt = future_base_time(gating_cfg->base_time,
gating_cfg->cycle_time,
tas_data->earliest_base_time);
rbt -= tas_data->earliest_base_time;
entry_point_delta = ns_to_sja1105_delta(rbt) + 1;
schedule_entry_points[cycle].subschindx = cycle;
schedule_entry_points[cycle].delta = entry_point_delta;
schedule_entry_points[cycle].address = schedule_start_idx;
for (i = cycle; i < 8; i++)
schedule_params->subscheind[i] = schedule_end_idx;
list_for_each_entry(e, &gating_cfg->entries, list) {
schedule[k].delta = ns_to_sja1105_delta(e->interval);
schedule[k].destports = e->rule->vl.destports;
schedule[k].setvalid = true;
schedule[k].txen = true;
schedule[k].vlindex = e->rule->vl.sharindx;
schedule[k].winstindex = e->rule->vl.sharindx;
if (e->gate_state) /* Gate open */
schedule[k].winst = true;
else /* Gate closed */
schedule[k].winend = true;
k++;
}
}
return 0;
}
/* Be there 2 port subschedules, each executing an arbitrary number of gate
* open/close events cyclically.
* None of those gate events must ever occur at the exact same time, otherwise
* the switch is known to act in exotically strange ways.
* However the hardware doesn't bother performing these integrity checks.
* So here we are with the task of validating whether the new @admin offload
* has any conflict with the already established TAS configuration in
* tas_data->offload. We already know the other ports are in harmony with one
* another, otherwise we wouldn't have saved them.
* Each gate event executes periodically, with a period of @cycle_time and a
* phase given by its cycle's @base_time plus its offset within the cycle
* (which in turn is given by the length of the events prior to it).
* There are two aspects to possible collisions:
* - Collisions within one cycle's (actually the longest cycle's) time frame.
* For that, we need to compare the cartesian product of each possible
* occurrence of each event within one cycle time.
* - Collisions in the future. Events may not collide within one cycle time,
* but if two port schedules don't have the same periodicity (aka the cycle
* times aren't multiples of one another), they surely will some time in the
* future (actually they will collide an infinite amount of times).
*/
static bool
sja1105_tas_check_conflicts(struct sja1105_private *priv, int port,
const struct tc_taprio_qopt_offload *admin)
{
struct sja1105_tas_data *tas_data = &priv->tas_data;
const struct tc_taprio_qopt_offload *offload;
s64 max_cycle_time, min_cycle_time;
s64 delta1, delta2;
s64 rbt1, rbt2;
s64 stop_time;
s64 t1, t2;
int i, j;
s32 rem;
offload = tas_data->offload[port];
if (!offload)
return false;
/* Check if the two cycle times are multiples of one another.
* If they aren't, then they will surely collide.
*/
max_cycle_time = max(offload->cycle_time, admin->cycle_time);
min_cycle_time = min(offload->cycle_time, admin->cycle_time);
div_s64_rem(max_cycle_time, min_cycle_time, &rem);
if (rem)
return true;
/* Calculate the "reduced" base time of each of the two cycles
* (transposed back as close to 0 as possible) by dividing to
* the cycle time.
*/
div_s64_rem(offload->base_time, offload->cycle_time, &rem);
rbt1 = rem;
div_s64_rem(admin->base_time, admin->cycle_time, &rem);
rbt2 = rem;
stop_time = max_cycle_time + max(rbt1, rbt2);
/* delta1 is the relative base time of each GCL entry within
* the established ports' TAS config.
*/
for (i = 0, delta1 = 0;
i < offload->num_entries;
delta1 += offload->entries[i].interval, i++) {
/* delta2 is the relative base time of each GCL entry
* within the newly added TAS config.
*/
for (j = 0, delta2 = 0;
j < admin->num_entries;
delta2 += admin->entries[j].interval, j++) {
/* t1 follows all possible occurrences of the
* established ports' GCL entry i within the
* first cycle time.
*/
for (t1 = rbt1 + delta1;
t1 <= stop_time;
t1 += offload->cycle_time) {
/* t2 follows all possible occurrences
* of the newly added GCL entry j
* within the first cycle time.
*/
for (t2 = rbt2 + delta2;
t2 <= stop_time;
t2 += admin->cycle_time) {
if (t1 == t2) {
dev_warn(priv->ds->dev,
"GCL entry %d collides with entry %d of port %d\n",
j, i, port);
return true;
}
}
}
}
}
return false;
}
/* Check the tc-taprio configuration on @port for conflicts with the tc-gate
* global subschedule. If @port is -1, check it against all ports.
* To reuse the sja1105_tas_check_conflicts logic without refactoring it,
* convert the gating configuration to a dummy tc-taprio offload structure.
*/
bool sja1105_gating_check_conflicts(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack)
{
struct sja1105_gating_config *gating_cfg = &priv->tas_data.gating_cfg;
size_t num_entries = gating_cfg->num_entries;
struct tc_taprio_qopt_offload *dummy;
struct dsa_switch *ds = priv->ds;
struct sja1105_gate_entry *e;
bool conflict;
int i = 0;
if (list_empty(&gating_cfg->entries))
return false;
dummy = kzalloc_flex(*dummy, entries, num_entries);
if (!dummy) {
NL_SET_ERR_MSG_MOD(extack, "Failed to allocate memory");
return true;
}
dummy->num_entries = num_entries;
dummy->base_time = gating_cfg->base_time;
dummy->cycle_time = gating_cfg->cycle_time;
list_for_each_entry(e, &gating_cfg->entries, list)
dummy->entries[i++].interval = e->interval;
if (port != -1) {
conflict = sja1105_tas_check_conflicts(priv, port, dummy);
} else {
for (port = 0; port < ds->num_ports; port++) {
conflict = sja1105_tas_check_conflicts(priv, port,
dummy);
if (conflict)
break;
}
}
kfree(dummy);
return conflict;
}
int sja1105_setup_tc_taprio(struct dsa_switch *ds, int port,
struct tc_taprio_qopt_offload *admin)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_tas_data *tas_data = &priv->tas_data;
int other_port, rc, i;
/* Can't change an already configured port (must delete qdisc first).
* Can't delete the qdisc from an unconfigured port.
*/
if ((!!tas_data->offload[port] && admin->cmd == TAPRIO_CMD_REPLACE) ||
(!tas_data->offload[port] && admin->cmd == TAPRIO_CMD_DESTROY))
return -EINVAL;
if (admin->cmd == TAPRIO_CMD_DESTROY) {
taprio_offload_free(tas_data->offload[port]);
tas_data->offload[port] = NULL;
rc = sja1105_init_scheduling(priv);
if (rc < 0)
return rc;
return sja1105_static_config_reload(priv, SJA1105_SCHEDULING);
} else if (admin->cmd != TAPRIO_CMD_REPLACE) {
return -EOPNOTSUPP;
}
/* The cycle time extension is the amount of time the last cycle from
* the old OPER needs to be extended in order to phase-align with the
* base time of the ADMIN when that becomes the new OPER.
* But of course our switch needs to be reset to switch-over between
* the ADMIN and the OPER configs - so much for a seamless transition.
* So don't add insult over injury and just say we don't support cycle
* time extension.
*/
if (admin->cycle_time_extension)
return -ENOTSUPP;
for (i = 0; i < admin->num_entries; i++) {
s64 delta_ns = admin->entries[i].interval;
s64 delta_cycles = ns_to_sja1105_delta(delta_ns);
bool too_long, too_short;
too_long = (delta_cycles >= SJA1105_TAS_MAX_DELTA);
too_short = (delta_cycles == 0);
if (too_long || too_short) {
dev_err(priv->ds->dev,
"Interval %llu too %s for GCL entry %d\n",
delta_ns, too_long ? "long" : "short", i);
return -ERANGE;
}
}
for (other_port = 0; other_port < ds->num_ports; other_port++) {
if (other_port == port)
continue;
if (sja1105_tas_check_conflicts(priv, other_port, admin))
return -ERANGE;
}
if (sja1105_gating_check_conflicts(priv, port, NULL)) {
dev_err(ds->dev, "Conflict with tc-gate schedule\n");
return -ERANGE;
}
tas_data->offload[port] = taprio_offload_get(admin);
rc = sja1105_init_scheduling(priv);
if (rc < 0)
return rc;
return sja1105_static_config_reload(priv, SJA1105_SCHEDULING);
}
static int sja1105_tas_check_running(struct sja1105_private *priv)
{
struct sja1105_tas_data *tas_data = &priv->tas_data;
struct dsa_switch *ds = priv->ds;
struct sja1105_ptp_cmd cmd = {0};
int rc;
rc = sja1105_ptp_commit(ds, &cmd, SPI_READ);
if (rc < 0)
return rc;
if (cmd.ptpstrtsch == 1)
/* Schedule successfully started */
tas_data->state = SJA1105_TAS_STATE_RUNNING;
else if (cmd.ptpstopsch == 1)
/* Schedule is stopped */
tas_data->state = SJA1105_TAS_STATE_DISABLED;
else
/* Schedule is probably not configured with PTP clock source */
rc = -EINVAL;
return rc;
}
/* Write to PTPCLKCORP */
static int sja1105_tas_adjust_drift(struct sja1105_private *priv,
u64 correction)
{
const struct sja1105_regs *regs = priv->info->regs;
u32 ptpclkcorp = ns_to_sja1105_ticks(correction);
return sja1105_xfer_u32(priv, SPI_WRITE, regs->ptpclkcorp,
&ptpclkcorp, NULL);
}
/* Write to PTPSCHTM */
static int sja1105_tas_set_base_time(struct sja1105_private *priv,
u64 base_time)
{
const struct sja1105_regs *regs = priv->info->regs;
u64 ptpschtm = ns_to_sja1105_ticks(base_time);
return sja1105_xfer_u64(priv, SPI_WRITE, regs->ptpschtm,
&ptpschtm, NULL);
}
static int sja1105_tas_start(struct sja1105_private *priv)
{
struct sja1105_tas_data *tas_data = &priv->tas_data;
struct sja1105_ptp_cmd *cmd = &priv->ptp_data.cmd;
struct dsa_switch *ds = priv->ds;
int rc;
dev_dbg(ds->dev, "Starting the TAS\n");
if (tas_data->state == SJA1105_TAS_STATE_ENABLED_NOT_RUNNING ||
tas_data->state == SJA1105_TAS_STATE_RUNNING) {
dev_err(ds->dev, "TAS already started\n");
return -EINVAL;
}
cmd->ptpstrtsch = 1;
cmd->ptpstopsch = 0;
rc = sja1105_ptp_commit(ds, cmd, SPI_WRITE);
if (rc < 0)
return rc;
tas_data->state = SJA1105_TAS_STATE_ENABLED_NOT_RUNNING;
return 0;
}
static int sja1105_tas_stop(struct sja1105_private *priv)
{
struct sja1105_tas_data *tas_data = &priv->tas_data;
struct sja1105_ptp_cmd *cmd = &priv->ptp_data.cmd;
struct dsa_switch *ds = priv->ds;
int rc;
dev_dbg(ds->dev, "Stopping the TAS\n");
if (tas_data->state == SJA1105_TAS_STATE_DISABLED) {
dev_err(ds->dev, "TAS already disabled\n");
return -EINVAL;
}
cmd->ptpstopsch = 1;
cmd->ptpstrtsch = 0;
rc = sja1105_ptp_commit(ds, cmd, SPI_WRITE);
if (rc < 0)
return rc;
tas_data->state = SJA1105_TAS_STATE_DISABLED;
return 0;
}
/* The schedule engine and the PTP clock are driven by the same oscillator, and
* they run in parallel. But whilst the PTP clock can keep an absolute
* time-of-day, the schedule engine is only running in 'ticks' (25 ticks make
* up a delta, which is 200ns), and wrapping around at the end of each cycle.
* The schedule engine is started when the PTP clock reaches the PTPSCHTM time
* (in PTP domain).
* Because the PTP clock can be rate-corrected (accelerated or slowed down) by
* a software servo, and the schedule engine clock runs in parallel to the PTP
* clock, there is logic internal to the switch that periodically keeps the
* schedule engine from drifting away. The frequency with which this internal
* syntonization happens is the PTP clock correction period (PTPCLKCORP). It is
* a value also in the PTP clock domain, and is also rate-corrected.
* To be precise, during a correction period, there is logic to determine by
* how many scheduler clock ticks has the PTP clock drifted. At the end of each
* correction period/beginning of new one, the length of a delta is shrunk or
* expanded with an integer number of ticks, compared with the typical 25.
* So a delta lasts for 200ns (or 25 ticks) only on average.
* Sometimes it is longer, sometimes it is shorter. The internal syntonization
* logic can adjust for at most 5 ticks each 20 ticks.
*
* The first implication is that you should choose your schedule correction
* period to be an integer multiple of the schedule length. Preferably one.
* In case there are schedules of multiple ports active, then the correction
* period needs to be a multiple of them all. Given the restriction that the
* cycle times have to be multiples of one another anyway, this means the
* correction period can simply be the largest cycle time, hence the current
* choice. This way, the updates are always synchronous to the transmission
* cycle, and therefore predictable.
*
* The second implication is that at the beginning of a correction period, the
* first few deltas will be modulated in time, until the schedule engine is
* properly phase-aligned with the PTP clock. For this reason, you should place
* your best-effort traffic at the beginning of a cycle, and your
* time-triggered traffic afterwards.
*
* The third implication is that once the schedule engine is started, it can
* only adjust for so much drift within a correction period. In the servo you
* can only change the PTPCLKRATE, but not step the clock (PTPCLKADD). If you
* want to do the latter, you need to stop and restart the schedule engine,
* which is what the state machine handles.
*/
static void sja1105_tas_state_machine(struct work_struct *work)
{
struct sja1105_tas_data *tas_data = work_to_sja1105_tas(work);
struct sja1105_private *priv = tas_to_sja1105(tas_data);
struct sja1105_ptp_data *ptp_data = &priv->ptp_data;
struct timespec64 base_time_ts, now_ts;
struct dsa_switch *ds = priv->ds;
struct timespec64 diff;
s64 base_time, now;
int rc = 0;
mutex_lock(&ptp_data->lock);
switch (tas_data->state) {
case SJA1105_TAS_STATE_DISABLED:
/* Can't do anything at all if clock is still being stepped */
if (tas_data->last_op != SJA1105_PTP_ADJUSTFREQ)
break;
rc = sja1105_tas_adjust_drift(priv, tas_data->max_cycle_time);
if (rc < 0)
break;
rc = __sja1105_ptp_gettimex(ds, &now, NULL);
if (rc < 0)
break;
/* Plan to start the earliest schedule first. The others
* will be started in hardware, by way of their respective
* entry points delta.
* Try our best to avoid fringe cases (race condition between
* ptpschtm and ptpstrtsch) by pushing the oper_base_time at
* least one second in the future from now. This is not ideal,
* but this only needs to buy us time until the
* sja1105_tas_start command below gets executed.
*/
base_time = future_base_time(tas_data->earliest_base_time,
tas_data->max_cycle_time,
now + 1ull * NSEC_PER_SEC);
base_time -= sja1105_delta_to_ns(1);
rc = sja1105_tas_set_base_time(priv, base_time);
if (rc < 0)
break;
tas_data->oper_base_time = base_time;
rc = sja1105_tas_start(priv);
if (rc < 0)
break;
base_time_ts = ns_to_timespec64(base_time);
now_ts = ns_to_timespec64(now);
dev_dbg(ds->dev, "OPER base time %ptSp (now %ptSp)\n",
&base_time_ts, &now_ts);
break;
case SJA1105_TAS_STATE_ENABLED_NOT_RUNNING:
if (tas_data->last_op != SJA1105_PTP_ADJUSTFREQ) {
/* Clock was stepped.. bad news for TAS */
sja1105_tas_stop(priv);
break;
}
/* Check if TAS has actually started, by comparing the
* scheduled start time with the SJA1105 PTP clock
*/
rc = __sja1105_ptp_gettimex(ds, &now, NULL);
if (rc < 0)
break;
if (now < tas_data->oper_base_time) {
/* TAS has not started yet */
diff = ns_to_timespec64(tas_data->oper_base_time - now);
dev_dbg(ds->dev, "time to start: [%ptSp]", &diff);
break;
}
/* Time elapsed, what happened? */
rc = sja1105_tas_check_running(priv);
if (rc < 0)
break;
if (tas_data->state != SJA1105_TAS_STATE_RUNNING)
/* TAS has started */
dev_err(ds->dev,
"TAS not started despite time elapsed\n");
break;
case SJA1105_TAS_STATE_RUNNING:
/* Clock was stepped.. bad news for TAS */
if (tas_data->last_op != SJA1105_PTP_ADJUSTFREQ) {
sja1105_tas_stop(priv);
break;
}
rc = sja1105_tas_check_running(priv);
if (rc < 0)
break;
if (tas_data->state != SJA1105_TAS_STATE_RUNNING)
dev_err(ds->dev, "TAS surprisingly stopped\n");
break;
default:
if (net_ratelimit())
dev_err(ds->dev, "TAS in an invalid state (incorrect use of API)!\n");
}
if (rc && net_ratelimit())
dev_err(ds->dev, "An operation returned %d\n", rc);
mutex_unlock(&ptp_data->lock);
}
void sja1105_tas_clockstep(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_tas_data *tas_data = &priv->tas_data;
if (!tas_data->enabled)
return;
tas_data->last_op = SJA1105_PTP_CLOCKSTEP;
schedule_work(&tas_data->tas_work);
}
void sja1105_tas_adjfreq(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_tas_data *tas_data = &priv->tas_data;
if (!tas_data->enabled)
return;
/* No reason to schedule the workqueue, nothing changed */
if (tas_data->state == SJA1105_TAS_STATE_RUNNING)
return;
tas_data->last_op = SJA1105_PTP_ADJUSTFREQ;
schedule_work(&tas_data->tas_work);
}
void sja1105_tas_setup(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
struct sja1105_tas_data *tas_data = &priv->tas_data;
INIT_WORK(&tas_data->tas_work, sja1105_tas_state_machine);
tas_data->state = SJA1105_TAS_STATE_DISABLED;
tas_data->last_op = SJA1105_PTP_NONE;
INIT_LIST_HEAD(&tas_data->gating_cfg.entries);
}
void sja1105_tas_teardown(struct dsa_switch *ds)
{
struct sja1105_private *priv = ds->priv;
struct tc_taprio_qopt_offload *offload;
int port;
cancel_work_sync(&priv->tas_data.tas_work);
for (port = 0; port < ds->num_ports; port++) {
offload = priv->tas_data.offload[port];
if (!offload)
continue;
taprio_offload_free(offload);
}
}
@@ -0,0 +1,104 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com>
*/
#ifndef _SJA1105_TAS_H
#define _SJA1105_TAS_H
#include <net/pkt_sched.h>
#define SJA1105_TAS_MAX_DELTA BIT(18)
struct sja1105_private;
#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_TAS)
enum sja1105_tas_state {
SJA1105_TAS_STATE_DISABLED,
SJA1105_TAS_STATE_ENABLED_NOT_RUNNING,
SJA1105_TAS_STATE_RUNNING,
};
enum sja1105_ptp_op {
SJA1105_PTP_NONE,
SJA1105_PTP_CLOCKSTEP,
SJA1105_PTP_ADJUSTFREQ,
};
struct sja1105_gate_entry {
struct list_head list;
struct sja1105_rule *rule;
s64 interval;
u8 gate_state;
};
struct sja1105_gating_config {
u64 cycle_time;
s64 base_time;
int num_entries;
struct list_head entries;
};
struct sja1105_tas_data {
struct tc_taprio_qopt_offload *offload[SJA1105_MAX_NUM_PORTS];
struct sja1105_gating_config gating_cfg;
enum sja1105_tas_state state;
enum sja1105_ptp_op last_op;
struct work_struct tas_work;
s64 earliest_base_time;
s64 oper_base_time;
u64 max_cycle_time;
bool enabled;
};
int sja1105_setup_tc_taprio(struct dsa_switch *ds, int port,
struct tc_taprio_qopt_offload *admin);
void sja1105_tas_setup(struct dsa_switch *ds);
void sja1105_tas_teardown(struct dsa_switch *ds);
void sja1105_tas_clockstep(struct dsa_switch *ds);
void sja1105_tas_adjfreq(struct dsa_switch *ds);
bool sja1105_gating_check_conflicts(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack);
int sja1105_init_scheduling(struct sja1105_private *priv);
#else
/* C doesn't allow empty structures, bah! */
struct sja1105_tas_data {
u8 dummy;
};
static inline int sja1105_setup_tc_taprio(struct dsa_switch *ds, int port,
struct tc_taprio_qopt_offload *admin)
{
return -EOPNOTSUPP;
}
static inline void sja1105_tas_setup(struct dsa_switch *ds) { }
static inline void sja1105_tas_teardown(struct dsa_switch *ds) { }
static inline void sja1105_tas_clockstep(struct dsa_switch *ds) { }
static inline void sja1105_tas_adjfreq(struct dsa_switch *ds) { }
static inline bool
sja1105_gating_check_conflicts(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
{
return true;
}
static inline int sja1105_init_scheduling(struct sja1105_private *priv)
{
return 0;
}
#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_TAS) */
#endif /* _SJA1105_TAS_H */
@@ -0,0 +1,800 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2020 NXP
*/
#include <net/tc_act/tc_gate.h>
#include <linux/dsa/8021q.h>
#include "sja1105_vl.h"
#define SJA1105_SIZE_VL_STATUS 8
/* Insert into the global gate list, sorted by gate action time. */
static int sja1105_insert_gate_entry(struct sja1105_gating_config *gating_cfg,
struct sja1105_rule *rule,
u8 gate_state, s64 entry_time,
struct netlink_ext_ack *extack)
{
struct sja1105_gate_entry *e;
int rc;
e = kzalloc_obj(*e);
if (!e)
return -ENOMEM;
e->rule = rule;
e->gate_state = gate_state;
e->interval = entry_time;
if (list_empty(&gating_cfg->entries)) {
list_add(&e->list, &gating_cfg->entries);
} else {
struct sja1105_gate_entry *p;
list_for_each_entry(p, &gating_cfg->entries, list) {
if (p->interval == e->interval) {
NL_SET_ERR_MSG_MOD(extack,
"Gate conflict");
rc = -EBUSY;
goto err;
}
if (e->interval < p->interval)
break;
}
list_add(&e->list, p->list.prev);
}
gating_cfg->num_entries++;
return 0;
err:
kfree(e);
return rc;
}
/* The gate entries contain absolute times in their e->interval field. Convert
* that to proper intervals (i.e. "0, 5, 10, 15" to "5, 5, 5, 5").
*/
static void
sja1105_gating_cfg_time_to_interval(struct sja1105_gating_config *gating_cfg,
u64 cycle_time)
{
struct sja1105_gate_entry *last_e;
struct sja1105_gate_entry *e;
struct list_head *prev;
list_for_each_entry(e, &gating_cfg->entries, list) {
struct sja1105_gate_entry *p;
prev = e->list.prev;
if (prev == &gating_cfg->entries)
continue;
p = list_entry(prev, struct sja1105_gate_entry, list);
p->interval = e->interval - p->interval;
}
last_e = list_last_entry(&gating_cfg->entries,
struct sja1105_gate_entry, list);
last_e->interval = cycle_time - last_e->interval;
}
static void sja1105_free_gating_config(struct sja1105_gating_config *gating_cfg)
{
struct sja1105_gate_entry *e, *n;
list_for_each_entry_safe(e, n, &gating_cfg->entries, list) {
list_del(&e->list);
kfree(e);
}
}
static int sja1105_compose_gating_subschedule(struct sja1105_private *priv,
struct netlink_ext_ack *extack)
{
struct sja1105_gating_config *gating_cfg = &priv->tas_data.gating_cfg;
struct sja1105_rule *rule;
s64 max_cycle_time = 0;
s64 its_base_time = 0;
int i, rc = 0;
sja1105_free_gating_config(gating_cfg);
list_for_each_entry(rule, &priv->flow_block.rules, list) {
if (rule->type != SJA1105_RULE_VL)
continue;
if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
continue;
if (max_cycle_time < rule->vl.cycle_time) {
max_cycle_time = rule->vl.cycle_time;
its_base_time = rule->vl.base_time;
}
}
if (!max_cycle_time)
return 0;
dev_dbg(priv->ds->dev, "max_cycle_time %lld its_base_time %lld\n",
max_cycle_time, its_base_time);
gating_cfg->base_time = its_base_time;
gating_cfg->cycle_time = max_cycle_time;
gating_cfg->num_entries = 0;
list_for_each_entry(rule, &priv->flow_block.rules, list) {
s64 time;
s64 rbt;
if (rule->type != SJA1105_RULE_VL)
continue;
if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
continue;
/* Calculate the difference between this gating schedule's
* base time, and the base time of the gating schedule with the
* longest cycle time. We call it the relative base time (rbt).
*/
rbt = future_base_time(rule->vl.base_time, rule->vl.cycle_time,
its_base_time);
rbt -= its_base_time;
time = rbt;
for (i = 0; i < rule->vl.num_entries; i++) {
u8 gate_state = rule->vl.entries[i].gate_state;
s64 entry_time = time;
while (entry_time < max_cycle_time) {
rc = sja1105_insert_gate_entry(gating_cfg, rule,
gate_state,
entry_time,
extack);
if (rc)
goto err;
entry_time += rule->vl.cycle_time;
}
time += rule->vl.entries[i].interval;
}
}
sja1105_gating_cfg_time_to_interval(gating_cfg, max_cycle_time);
return 0;
err:
sja1105_free_gating_config(gating_cfg);
return rc;
}
/* The switch flow classification core implements TTEthernet, which 'thinks' in
* terms of Virtual Links (VL), a concept borrowed from ARINC 664 part 7.
* However it also has one other operating mode (VLLUPFORMAT=0) where it acts
* somewhat closer to a pre-standard implementation of IEEE 802.1Qci
* (Per-Stream Filtering and Policing), which is what the driver is going to be
* implementing.
*
* VL Lookup
* Key = {DMAC && VLANID +---------+ Key = { (DMAC[47:16] & VLMASK ==
* && VLAN PCP | | VLMARKER)
* && INGRESS PORT} +---------+ (both fixed)
* (exact match, | && DMAC[15:0] == VLID
* all specified in rule) | (specified in rule)
* v && INGRESS PORT }
* ------------
* 0 (PSFP) / \ 1 (ARINC664)
* +-----------/ VLLUPFORMAT \----------+
* | \ (fixed) / |
* | \ / |
* 0 (forwarding) v ------------ |
* ------------ |
* / \ 1 (QoS classification) |
* +---/ ISCRITICAL \-----------+ |
* | \ (per rule) / | |
* | \ / VLID taken from VLID taken from
* v ------------ index of rule contents of rule
* select that matched that matched
* DESTPORTS | |
* | +---------+--------+
* | |
* | v
* | VL Forwarding
* | (indexed by VLID)
* | +---------+
* | +--------------| |
* | | select TYPE +---------+
* | v
* | 0 (rate ------------ 1 (time
* | constrained) / \ triggered)
* | +------/ TYPE \------------+
* | | \ (per VLID) / |
* | v \ / v
* | VL Policing ------------ VL Policing
* | (indexed by VLID) (indexed by VLID)
* | +---------+ +---------+
* | | TYPE=0 | | TYPE=1 |
* | +---------+ +---------+
* | select SHARINDX select SHARINDX to
* | to rate-limit re-enter VL Forwarding
* | groups of VL's with new VLID for egress
* | to same quota |
* | | |
* | select MAXLEN -> exceed => drop select MAXLEN -> exceed => drop
* | | |
* | v v
* | VL Forwarding VL Forwarding
* | (indexed by SHARINDX) (indexed by SHARINDX)
* | +---------+ +---------+
* | | TYPE=0 | | TYPE=1 |
* | +---------+ +---------+
* | select PRIORITY, select PRIORITY,
* | PARTITION, DESTPORTS PARTITION, DESTPORTS
* | | |
* | v v
* | VL Policing VL Policing
* | (indexed by SHARINDX) (indexed by SHARINDX)
* | +---------+ +---------+
* | | TYPE=0 | | TYPE=1 |
* | +---------+ +---------+
* | | |
* | v |
* | select BAG, -> exceed => drop |
* | JITTER v
* | | ----------------------------------------------
* | | / Reception Window is open for this VL \
* | | / (the Schedule Table executes an entry i \
* | | / M <= i < N, for which these conditions hold): \ no
* | | +----/ \-+
* | | |yes \ WINST[M] == 1 && WINSTINDEX[M] == VLID / |
* | | | \ WINEND[N] == 1 && WINSTINDEX[N] == VLID / |
* | | | \ / |
* | | | \ (the VL window has opened and not yet closed)/ |
* | | | ---------------------------------------------- |
* | | v v
* | | dispatch to DESTPORTS when the Schedule Table drop
* | | executes an entry i with TXEN == 1 && VLINDEX == i
* v v
* dispatch immediately to DESTPORTS
*
* The per-port classification key is always composed of {DMAC, VID, PCP} and
* is non-maskable. This 'looks like' the NULL stream identification function
* from IEEE 802.1CB clause 6, except for the extra VLAN PCP. When the switch
* ports operate as VLAN-unaware, we do allow the user to not specify the VLAN
* ID and PCP, and then the port-based defaults will be used.
*
* In TTEthernet, routing is something that needs to be done manually for each
* Virtual Link. So the flow action must always include one of:
* a. 'redirect', 'trap' or 'drop': select the egress port list
* Additionally, the following actions may be applied on a Virtual Link,
* turning it into 'critical' traffic:
* b. 'police': turn it into a rate-constrained VL, with bandwidth limitation
* given by the maximum frame length, bandwidth allocation gap (BAG) and
* maximum jitter.
* c. 'gate': turn it into a time-triggered VL, which can be only be received
* and forwarded according to a given schedule.
*/
static bool sja1105_vl_key_lower(struct sja1105_vl_lookup_entry *a,
struct sja1105_vl_lookup_entry *b)
{
if (a->macaddr < b->macaddr)
return true;
if (a->macaddr > b->macaddr)
return false;
if (a->vlanid < b->vlanid)
return true;
if (a->vlanid > b->vlanid)
return false;
if (a->port < b->port)
return true;
if (a->port > b->port)
return false;
if (a->vlanprior < b->vlanprior)
return true;
if (a->vlanprior > b->vlanprior)
return false;
/* Keys are equal */
return false;
}
/* FIXME: this should change when the bridge upper of the port changes. */
static u16 sja1105_port_get_tag_8021q_vid(struct dsa_port *dp)
{
unsigned long bridge_num;
if (!dp->bridge)
return dsa_tag_8021q_standalone_vid(dp);
bridge_num = dsa_port_bridge_num_get(dp);
return dsa_tag_8021q_bridge_vid(bridge_num);
}
static int sja1105_init_virtual_links(struct sja1105_private *priv,
struct netlink_ext_ack *extack)
{
struct sja1105_vl_policing_entry *vl_policing;
struct sja1105_vl_forwarding_entry *vl_fwd;
struct sja1105_vl_lookup_entry *vl_lookup;
bool have_critical_virtual_links = false;
struct sja1105_table *table;
struct sja1105_rule *rule;
int num_virtual_links = 0;
int max_sharindx = 0;
int i, j, k;
/* Figure out the dimensioning of the problem */
list_for_each_entry(rule, &priv->flow_block.rules, list) {
if (rule->type != SJA1105_RULE_VL)
continue;
/* Each VL lookup entry matches on a single ingress port */
num_virtual_links += hweight_long(rule->port_mask);
if (rule->vl.type != SJA1105_VL_NONCRITICAL)
have_critical_virtual_links = true;
if (max_sharindx < rule->vl.sharindx)
max_sharindx = rule->vl.sharindx;
}
if (num_virtual_links > SJA1105_MAX_VL_LOOKUP_COUNT) {
NL_SET_ERR_MSG_MOD(extack, "Not enough VL entries available");
return -ENOSPC;
}
if (max_sharindx + 1 > SJA1105_MAX_VL_LOOKUP_COUNT) {
NL_SET_ERR_MSG_MOD(extack, "Policer index out of range");
return -ENOSPC;
}
max_sharindx = max_t(int, num_virtual_links, max_sharindx) + 1;
/* Discard previous VL Lookup Table */
table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
if (table->entry_count) {
kfree(table->entries);
table->entry_count = 0;
}
/* Discard previous VL Policing Table */
table = &priv->static_config.tables[BLK_IDX_VL_POLICING];
if (table->entry_count) {
kfree(table->entries);
table->entry_count = 0;
}
/* Discard previous VL Forwarding Table */
table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING];
if (table->entry_count) {
kfree(table->entries);
table->entry_count = 0;
}
/* Discard previous VL Forwarding Parameters Table */
table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING_PARAMS];
if (table->entry_count) {
kfree(table->entries);
table->entry_count = 0;
}
/* Nothing to do */
if (!num_virtual_links)
return 0;
/* Pre-allocate space in the static config tables */
/* VL Lookup Table */
table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
table->entries = kcalloc(num_virtual_links,
table->ops->unpacked_entry_size,
GFP_KERNEL);
if (!table->entries)
return -ENOMEM;
table->entry_count = num_virtual_links;
vl_lookup = table->entries;
k = 0;
list_for_each_entry(rule, &priv->flow_block.rules, list) {
unsigned long port;
if (rule->type != SJA1105_RULE_VL)
continue;
for_each_set_bit(port, &rule->port_mask, SJA1105_MAX_NUM_PORTS) {
vl_lookup[k].format = SJA1105_VL_FORMAT_PSFP;
vl_lookup[k].port = port;
vl_lookup[k].macaddr = rule->key.vl.dmac;
if (rule->key.type == SJA1105_KEY_VLAN_AWARE_VL) {
vl_lookup[k].vlanid = rule->key.vl.vid;
vl_lookup[k].vlanprior = rule->key.vl.pcp;
} else {
/* FIXME */
struct dsa_port *dp = dsa_to_port(priv->ds, port);
u16 vid = sja1105_port_get_tag_8021q_vid(dp);
vl_lookup[k].vlanid = vid;
vl_lookup[k].vlanprior = 0;
}
/* For critical VLs, the DESTPORTS mask is taken from
* the VL Forwarding Table, so no point in putting it
* in the VL Lookup Table
*/
if (rule->vl.type == SJA1105_VL_NONCRITICAL)
vl_lookup[k].destports = rule->vl.destports;
else
vl_lookup[k].iscritical = true;
vl_lookup[k].flow_cookie = rule->cookie;
k++;
}
}
/* UM10944.pdf chapter 4.2.3 VL Lookup table:
* "the entries in the VL Lookup table must be sorted in ascending
* order (i.e. the smallest value must be loaded first) according to
* the following sort order: MACADDR, VLANID, PORT, VLANPRIOR."
*/
for (i = 0; i < num_virtual_links; i++) {
struct sja1105_vl_lookup_entry *a = &vl_lookup[i];
for (j = i + 1; j < num_virtual_links; j++) {
struct sja1105_vl_lookup_entry *b = &vl_lookup[j];
if (sja1105_vl_key_lower(b, a)) {
struct sja1105_vl_lookup_entry tmp = *a;
*a = *b;
*b = tmp;
}
}
}
if (!have_critical_virtual_links)
return 0;
/* VL Policing Table */
table = &priv->static_config.tables[BLK_IDX_VL_POLICING];
table->entries = kcalloc(max_sharindx, table->ops->unpacked_entry_size,
GFP_KERNEL);
if (!table->entries)
return -ENOMEM;
table->entry_count = max_sharindx;
vl_policing = table->entries;
/* VL Forwarding Table */
table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING];
table->entries = kcalloc(max_sharindx, table->ops->unpacked_entry_size,
GFP_KERNEL);
if (!table->entries)
return -ENOMEM;
table->entry_count = max_sharindx;
vl_fwd = table->entries;
/* VL Forwarding Parameters Table */
table = &priv->static_config.tables[BLK_IDX_VL_FORWARDING_PARAMS];
table->entries = kcalloc(1, table->ops->unpacked_entry_size,
GFP_KERNEL);
if (!table->entries)
return -ENOMEM;
table->entry_count = 1;
for (i = 0; i < num_virtual_links; i++) {
unsigned long cookie = vl_lookup[i].flow_cookie;
struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
if (rule->vl.type == SJA1105_VL_NONCRITICAL)
continue;
if (rule->vl.type == SJA1105_VL_TIME_TRIGGERED) {
int sharindx = rule->vl.sharindx;
vl_policing[i].type = 1;
vl_policing[i].sharindx = sharindx;
vl_policing[i].maxlen = rule->vl.maxlen;
vl_policing[sharindx].type = 1;
vl_fwd[i].type = 1;
vl_fwd[sharindx].type = 1;
vl_fwd[sharindx].priority = rule->vl.ipv;
vl_fwd[sharindx].partition = 0;
vl_fwd[sharindx].destports = rule->vl.destports;
}
}
sja1105_frame_memory_partitioning(priv);
return 0;
}
int sja1105_vl_redirect(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack, unsigned long cookie,
struct sja1105_key *key, unsigned long destports,
bool append)
{
struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
struct dsa_port *dp = dsa_to_port(priv->ds, port);
bool vlan_aware = dsa_port_is_vlan_filtering(dp);
int rc;
if (!vlan_aware && key->type != SJA1105_KEY_VLAN_UNAWARE_VL) {
NL_SET_ERR_MSG_MOD(extack,
"Can only redirect based on DMAC");
return -EOPNOTSUPP;
} else if (vlan_aware && key->type != SJA1105_KEY_VLAN_AWARE_VL) {
NL_SET_ERR_MSG_MOD(extack,
"Can only redirect based on {DMAC, VID, PCP}");
return -EOPNOTSUPP;
}
if (!rule) {
rule = kzalloc_obj(*rule);
if (!rule)
return -ENOMEM;
rule->cookie = cookie;
rule->type = SJA1105_RULE_VL;
rule->key = *key;
list_add(&rule->list, &priv->flow_block.rules);
}
rule->port_mask |= BIT(port);
if (append)
rule->vl.destports |= destports;
else
rule->vl.destports = destports;
rc = sja1105_init_virtual_links(priv, extack);
if (rc) {
rule->port_mask &= ~BIT(port);
if (!rule->port_mask) {
list_del(&rule->list);
kfree(rule);
}
}
return rc;
}
int sja1105_vl_delete(struct sja1105_private *priv, int port,
struct sja1105_rule *rule, struct netlink_ext_ack *extack)
{
int rc;
rule->port_mask &= ~BIT(port);
if (!rule->port_mask) {
list_del(&rule->list);
kfree(rule);
}
rc = sja1105_compose_gating_subschedule(priv, extack);
if (rc)
return rc;
rc = sja1105_init_virtual_links(priv, extack);
if (rc)
return rc;
rc = sja1105_init_scheduling(priv);
if (rc < 0)
return rc;
return sja1105_static_config_reload(priv, SJA1105_VIRTUAL_LINKS);
}
int sja1105_vl_gate(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack, unsigned long cookie,
struct sja1105_key *key, u32 index, s32 prio,
u64 base_time, u64 cycle_time, u64 cycle_time_ext,
u32 num_entries, struct action_gate_entry *entries)
{
struct sja1105_rule *rule = sja1105_rule_find(priv, cookie);
struct dsa_port *dp = dsa_to_port(priv->ds, port);
bool vlan_aware = dsa_port_is_vlan_filtering(dp);
int ipv = -1;
int i, rc;
s32 rem;
if (cycle_time_ext) {
NL_SET_ERR_MSG_MOD(extack,
"Cycle time extension not supported");
return -EOPNOTSUPP;
}
div_s64_rem(base_time, sja1105_delta_to_ns(1), &rem);
if (rem) {
NL_SET_ERR_MSG_MOD(extack,
"Base time must be multiple of 200 ns");
return -ERANGE;
}
div_s64_rem(cycle_time, sja1105_delta_to_ns(1), &rem);
if (rem) {
NL_SET_ERR_MSG_MOD(extack,
"Cycle time must be multiple of 200 ns");
return -ERANGE;
}
if (!vlan_aware && key->type != SJA1105_KEY_VLAN_UNAWARE_VL) {
NL_SET_ERR_MSG_MOD(extack,
"Can only gate based on DMAC");
return -EOPNOTSUPP;
} else if (vlan_aware && key->type != SJA1105_KEY_VLAN_AWARE_VL) {
NL_SET_ERR_MSG_MOD(extack,
"Can only gate based on {DMAC, VID, PCP}");
return -EOPNOTSUPP;
}
if (!rule) {
rule = kzalloc_obj(*rule);
if (!rule)
return -ENOMEM;
list_add(&rule->list, &priv->flow_block.rules);
rule->cookie = cookie;
rule->type = SJA1105_RULE_VL;
rule->key = *key;
rule->vl.type = SJA1105_VL_TIME_TRIGGERED;
rule->vl.sharindx = index;
rule->vl.base_time = base_time;
rule->vl.cycle_time = cycle_time;
rule->vl.num_entries = num_entries;
rule->vl.entries = kzalloc_objs(struct action_gate_entry,
num_entries);
if (!rule->vl.entries) {
rc = -ENOMEM;
goto out;
}
for (i = 0; i < num_entries; i++) {
div_s64_rem(entries[i].interval,
sja1105_delta_to_ns(1), &rem);
if (rem) {
NL_SET_ERR_MSG_MOD(extack,
"Interval must be multiple of 200 ns");
rc = -ERANGE;
goto out;
}
if (!entries[i].interval) {
NL_SET_ERR_MSG_MOD(extack,
"Interval cannot be zero");
rc = -ERANGE;
goto out;
}
if (ns_to_sja1105_delta(entries[i].interval) >
SJA1105_TAS_MAX_DELTA) {
NL_SET_ERR_MSG_MOD(extack,
"Maximum interval is 52 ms");
rc = -ERANGE;
goto out;
}
if (entries[i].maxoctets != -1) {
NL_SET_ERR_MSG_MOD(extack,
"Cannot offload IntervalOctetMax");
rc = -EOPNOTSUPP;
goto out;
}
if (ipv == -1) {
ipv = entries[i].ipv;
} else if (ipv != entries[i].ipv) {
NL_SET_ERR_MSG_MOD(extack,
"Only support a single IPV per VL");
rc = -EOPNOTSUPP;
goto out;
}
rule->vl.entries[i] = entries[i];
}
if (ipv == -1) {
if (key->type == SJA1105_KEY_VLAN_AWARE_VL)
ipv = key->vl.pcp;
else
ipv = 0;
}
/* TODO: support per-flow MTU */
rule->vl.maxlen = VLAN_ETH_FRAME_LEN + ETH_FCS_LEN;
rule->vl.ipv = ipv;
}
rule->port_mask |= BIT(port);
rc = sja1105_compose_gating_subschedule(priv, extack);
if (rc)
goto out;
rc = sja1105_init_virtual_links(priv, extack);
if (rc)
goto out;
if (sja1105_gating_check_conflicts(priv, -1, extack)) {
NL_SET_ERR_MSG_MOD(extack, "Conflict with tc-taprio schedule");
rc = -ERANGE;
goto out;
}
out:
if (rc) {
rule->port_mask &= ~BIT(port);
if (!rule->port_mask) {
list_del(&rule->list);
kfree(rule->vl.entries);
kfree(rule);
}
}
return rc;
}
static int sja1105_find_vlid(struct sja1105_private *priv, int port,
struct sja1105_key *key)
{
struct sja1105_vl_lookup_entry *vl_lookup;
struct sja1105_table *table;
int i;
if (WARN_ON(key->type != SJA1105_KEY_VLAN_AWARE_VL &&
key->type != SJA1105_KEY_VLAN_UNAWARE_VL))
return -1;
table = &priv->static_config.tables[BLK_IDX_VL_LOOKUP];
vl_lookup = table->entries;
for (i = 0; i < table->entry_count; i++) {
if (key->type == SJA1105_KEY_VLAN_AWARE_VL) {
if (vl_lookup[i].port == port &&
vl_lookup[i].macaddr == key->vl.dmac &&
vl_lookup[i].vlanid == key->vl.vid &&
vl_lookup[i].vlanprior == key->vl.pcp)
return i;
} else {
if (vl_lookup[i].port == port &&
vl_lookup[i].macaddr == key->vl.dmac)
return i;
}
}
return -1;
}
int sja1105_vl_stats(struct sja1105_private *priv, int port,
struct sja1105_rule *rule, struct flow_stats *stats,
struct netlink_ext_ack *extack)
{
const struct sja1105_regs *regs = priv->info->regs;
u8 buf[SJA1105_SIZE_VL_STATUS] = {0};
u64 unreleased;
u64 timingerr;
u64 lengtherr;
int vlid, rc;
u64 pkts;
if (rule->vl.type != SJA1105_VL_TIME_TRIGGERED)
return 0;
vlid = sja1105_find_vlid(priv, port, &rule->key);
if (vlid < 0)
return 0;
rc = sja1105_xfer_buf(priv, SPI_READ, regs->vl_status + 2 * vlid, buf,
SJA1105_SIZE_VL_STATUS);
if (rc) {
NL_SET_ERR_MSG_MOD(extack, "SPI access failed");
return rc;
}
sja1105_unpack(buf, &timingerr, 31, 16, SJA1105_SIZE_VL_STATUS);
sja1105_unpack(buf, &unreleased, 15, 0, SJA1105_SIZE_VL_STATUS);
sja1105_unpack(buf, &lengtherr, 47, 32, SJA1105_SIZE_VL_STATUS);
pkts = timingerr + unreleased + lengtherr;
flow_stats_update(stats, 0, pkts - rule->vl.stats.pkts, 0,
jiffies, FLOW_ACTION_HW_STATS_IMMEDIATE);
rule->vl.stats.pkts = pkts;
rule->vl.stats.lastused = jiffies;
return 0;
}
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright 2020 NXP
*/
#ifndef _SJA1105_VL_H
#define _SJA1105_VL_H
#include "sja1105.h"
#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_VL)
int sja1105_vl_redirect(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack, unsigned long cookie,
struct sja1105_key *key, unsigned long destports,
bool append);
int sja1105_vl_delete(struct sja1105_private *priv, int port,
struct sja1105_rule *rule,
struct netlink_ext_ack *extack);
int sja1105_vl_gate(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack, unsigned long cookie,
struct sja1105_key *key, u32 index, s32 prio,
u64 base_time, u64 cycle_time, u64 cycle_time_ext,
u32 num_entries, struct action_gate_entry *entries);
int sja1105_vl_stats(struct sja1105_private *priv, int port,
struct sja1105_rule *rule, struct flow_stats *stats,
struct netlink_ext_ack *extack);
#else
static inline int sja1105_vl_redirect(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack,
unsigned long cookie,
struct sja1105_key *key,
unsigned long destports,
bool append)
{
NL_SET_ERR_MSG_MOD(extack, "Virtual Links not compiled in");
return -EOPNOTSUPP;
}
static inline int sja1105_vl_delete(struct sja1105_private *priv,
int port, struct sja1105_rule *rule,
struct netlink_ext_ack *extack)
{
NL_SET_ERR_MSG_MOD(extack, "Virtual Links not compiled in");
return -EOPNOTSUPP;
}
static inline int sja1105_vl_gate(struct sja1105_private *priv, int port,
struct netlink_ext_ack *extack,
unsigned long cookie,
struct sja1105_key *key, u32 index, s32 prio,
u64 base_time, u64 cycle_time,
u64 cycle_time_ext, u32 num_entries,
struct action_gate_entry *entries)
{
NL_SET_ERR_MSG_MOD(extack, "Virtual Links not compiled in");
return -EOPNOTSUPP;
}
static inline int sja1105_vl_stats(struct sja1105_private *priv, int port,
struct sja1105_rule *rule,
struct flow_stats *stats,
struct netlink_ext_ack *extack)
{
NL_SET_ERR_MSG_MOD(extack, "Virtual Links not compiled in");
return -EOPNOTSUPP;
}
#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_VL) */
#endif /* _SJA1105_VL_H */