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,29 @@
# SPDX-License-Identifier: GPL-2.0-only
config NET_DSA_MV88E6XXX
tristate "Marvell 88E6xxx Ethernet switch fabric support"
depends on NET_DSA
select IRQ_DOMAIN
select NET_DSA_TAG_EDSA
select NET_DSA_TAG_DSA
help
This driver adds support for most of the Marvell 88E6xxx models of
Ethernet switch chips, except 88E6060.
config NET_DSA_MV88E6XXX_PTP
bool "PTP support for Marvell 88E6xxx"
default n
depends on (NET_DSA_MV88E6XXX = y && PTP_1588_CLOCK = y) || \
(NET_DSA_MV88E6XXX = m && PTP_1588_CLOCK)
help
Say Y to enable PTP hardware timestamping on Marvell 88E6xxx switch
chips that support it.
config NET_DSA_MV88E6XXX_LEDS
bool "LED support for Marvell 88E6xxx"
default y
depends on NET_DSA_MV88E6XXX
depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_MV88E6XXX
depends on LEDS_TRIGGERS
help
This enabled support for controlling the LEDs attached to the
Marvell 88E6xxx switch chips.
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_MV88E6XXX) += mv88e6xxx.o
mv88e6xxx-objs := chip.o
mv88e6xxx-objs += devlink.o
mv88e6xxx-objs += global1.o
mv88e6xxx-objs += global1_atu.o
mv88e6xxx-objs += global1_vtu.o
mv88e6xxx-objs += global2.o
mv88e6xxx-objs += global2_avb.o
mv88e6xxx-objs += global2_scratch.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += hwtstamp.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_LEDS) += leds.o
mv88e6xxx-objs += pcs-6185.o
mv88e6xxx-objs += pcs-6352.o
mv88e6xxx-objs += pcs-639x.o
mv88e6xxx-objs += phy.o
mv88e6xxx-objs += port.o
mv88e6xxx-objs += port_hidden.o
mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += ptp.o
mv88e6xxx-objs += serdes.o
mv88e6xxx-objs += smi.o
mv88e6xxx-objs += switchdev.o
mv88e6xxx-objs += trace.o
mv88e6xxx-objs += tcflower.o
mv88e6xxx-objs += tcam.o
# for tracing framework to find trace.h
CFLAGS_trace.o := -I$(src)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,893 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx Ethernet switch single-chip definition
*
* Copyright (c) 2008 Marvell Semiconductor
*/
#ifndef _MV88E6XXX_CHIP_H
#define _MV88E6XXX_CHIP_H
#include <linux/idr.h>
#include <linux/if_vlan.h>
#include <linux/irq.h>
#include <linux/gpio/consumer.h>
#include <linux/kthread.h>
#include <linux/leds.h>
#include <linux/phy.h>
#include <linux/property.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/timecounter.h>
#include <net/dsa.h>
#define EDSA_HLEN 8
#define MV88E6XXX_N_FID 4096
#define MV88E6XXX_N_SID 64
#define MV88E6XXX_FID_STANDALONE 0
#define MV88E6XXX_FID_BRIDGED 1
/* PVT limits for 4-bit port and 5-bit switch */
#define MV88E6XXX_MAX_PVT_SWITCHES 32
#define MV88E6XXX_MAX_PVT_PORTS 16
#define MV88E6XXX_MAX_PVT_ENTRIES \
(MV88E6XXX_MAX_PVT_SWITCHES * MV88E6XXX_MAX_PVT_PORTS)
#define MV88E6XXX_MAX_GPIO 16
enum mv88e6xxx_egress_mode {
MV88E6XXX_EGRESS_MODE_UNMODIFIED,
MV88E6XXX_EGRESS_MODE_UNTAGGED,
MV88E6XXX_EGRESS_MODE_TAGGED,
MV88E6XXX_EGRESS_MODE_ETHERTYPE,
};
enum mv88e6xxx_egress_direction {
MV88E6XXX_EGRESS_DIR_INGRESS,
MV88E6XXX_EGRESS_DIR_EGRESS,
};
enum mv88e6xxx_frame_mode {
MV88E6XXX_FRAME_MODE_NORMAL,
MV88E6XXX_FRAME_MODE_DSA,
MV88E6XXX_FRAME_MODE_PROVIDER,
MV88E6XXX_FRAME_MODE_ETHERTYPE,
};
/* List of supported models */
enum mv88e6xxx_model {
MV88E6020,
MV88E6071,
MV88E6085,
MV88E6095,
MV88E6097,
MV88E6123,
MV88E6131,
MV88E6141,
MV88E6161,
MV88E6165,
MV88E6171,
MV88E6172,
MV88E6175,
MV88E6176,
MV88E6185,
MV88E6190,
MV88E6190X,
MV88E6191,
MV88E6191X,
MV88E6193X,
MV88E6220,
MV88E6240,
MV88E6250,
MV88E6290,
MV88E6320,
MV88E6321,
MV88E6341,
MV88E6350,
MV88E6351,
MV88E6352,
MV88E6361,
MV88E6390,
MV88E6390X,
MV88E6393X,
};
enum mv88e6xxx_family {
MV88E6XXX_FAMILY_NONE,
MV88E6XXX_FAMILY_6065, /* 6031 6035 6061 6065 */
MV88E6XXX_FAMILY_6095, /* 6092 6095 */
MV88E6XXX_FAMILY_6097, /* 6046 6085 6096 6097 */
MV88E6XXX_FAMILY_6165, /* 6123 6161 6165 */
MV88E6XXX_FAMILY_6185, /* 6108 6121 6122 6131 6152 6155 6182 6185 */
MV88E6XXX_FAMILY_6250, /* 6220 6250 6020 6071 */
MV88E6XXX_FAMILY_6320, /* 6320 6321 */
MV88E6XXX_FAMILY_6341, /* 6141 6341 */
MV88E6XXX_FAMILY_6351, /* 6171 6175 6350 6351 */
MV88E6XXX_FAMILY_6352, /* 6172 6176 6240 6352 */
MV88E6XXX_FAMILY_6390, /* 6190 6190X 6191 6290 6390 6390X */
MV88E6XXX_FAMILY_6393, /* 6191X 6193X 6361 6393X */
};
/**
* enum mv88e6xxx_edsa_support - Ethertype DSA tag support level
* @MV88E6XXX_EDSA_UNSUPPORTED: Device has no support for EDSA tags
* @MV88E6XXX_EDSA_UNDOCUMENTED: Documentation indicates that
* egressing FORWARD frames with an EDSA
* tag is reserved for future use, but
* empirical data shows that this mode
* is supported.
* @MV88E6XXX_EDSA_SUPPORTED: EDSA tags are fully supported.
*/
enum mv88e6xxx_edsa_support {
MV88E6XXX_EDSA_UNSUPPORTED = 0,
MV88E6XXX_EDSA_UNDOCUMENTED,
MV88E6XXX_EDSA_SUPPORTED,
};
struct mv88e6xxx_ops;
struct mv88e6xxx_info {
enum mv88e6xxx_family family;
u16 prod_num;
const char *name;
unsigned int num_databases;
unsigned int num_macs;
unsigned int num_ports;
unsigned int num_internal_phys;
unsigned int num_gpio;
unsigned int num_tcam_entries;
unsigned int max_vid;
unsigned int max_sid;
unsigned int port_base_addr;
unsigned int phy_base_addr;
unsigned int global1_addr;
unsigned int global2_addr;
unsigned int tcam_addr;
unsigned int age_time_coeff;
unsigned int g1_irqs;
unsigned int g2_irqs;
int stats_type;
bool pvt;
/* Mark certain ports as invalid. This is required for example for the
* MV88E6220 (which is in general a MV88E6250 with 7 ports) but the
* ports 2-4 are not routet to pins.
*/
unsigned int invalid_port_mask;
/* Multi-chip Addressing Mode.
* Some chips respond to only 2 registers of its own SMI device address
* when it is non-zero, and use indirect access to internal registers.
*/
bool multi_chip;
/* Dual-chip Addressing Mode
* Some chips respond to only half of the 32 SMI addresses,
* allowing two to coexist on the same SMI interface.
*/
bool dual_chip;
enum mv88e6xxx_edsa_support edsa_support;
/* Mask for FromPort and ToPort value of PortVec used in ATU Move
* operation. 0 means that the ATU Move operation is not supported.
*/
u8 atu_move_port_mask;
const struct mv88e6xxx_ops *ops;
/* Supports PTP */
bool ptp_support;
/* Internal PHY start index. 0 means that internal PHYs range starts at
* port 0, 1 means internal PHYs range starts at port 1, etc
*/
unsigned int internal_phys_offset;
};
struct mv88e6xxx_atu_entry {
u8 state;
bool trunk;
u16 portvec;
u8 mac[ETH_ALEN];
};
struct mv88e6xxx_vtu_entry {
u16 vid;
u16 fid;
u8 sid;
bool valid;
bool policy;
u8 member[DSA_MAX_PORTS];
u8 state[DSA_MAX_PORTS]; /* Older silicon has no STU */
};
struct mv88e6xxx_stu_entry {
u8 sid;
bool valid;
u8 state[DSA_MAX_PORTS];
};
struct mv88e6xxx_bus_ops;
struct mv88e6xxx_irq_ops;
struct mv88e6xxx_gpio_ops;
struct mv88e6xxx_avb_ops;
struct mv88e6xxx_ptp_ops;
struct mv88e6xxx_pcs_ops;
struct mv88e6xxx_cc_coeffs;
struct mv88e6xxx_tcam_ops;
struct mv88e6xxx_irq {
u16 masked;
struct irq_chip chip;
struct irq_domain *domain;
int nirqs;
};
/* state flags for mv88e6xxx_port_hwtstamp::state */
enum {
MV88E6XXX_HWTSTAMP_ENABLED,
MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS,
};
struct mv88e6xxx_port_hwtstamp {
/* Port index */
int port_id;
/* Timestamping state */
unsigned long state;
/* Resources for receive timestamping */
struct sk_buff_head rx_queue;
struct sk_buff_head rx_queue2;
/* Resources for transmit timestamping */
unsigned long tx_tstamp_start;
struct sk_buff *tx_skb;
u16 tx_seq_id;
/* Current timestamp configuration */
struct kernel_hwtstamp_config tstamp_config;
};
enum mv88e6xxx_policy_mapping {
MV88E6XXX_POLICY_MAPPING_DA,
MV88E6XXX_POLICY_MAPPING_SA,
MV88E6XXX_POLICY_MAPPING_VTU,
MV88E6XXX_POLICY_MAPPING_ETYPE,
MV88E6XXX_POLICY_MAPPING_PPPOE,
MV88E6XXX_POLICY_MAPPING_VBAS,
MV88E6XXX_POLICY_MAPPING_OPT82,
MV88E6XXX_POLICY_MAPPING_UDP,
};
enum mv88e6xxx_policy_action {
MV88E6XXX_POLICY_ACTION_NORMAL,
MV88E6XXX_POLICY_ACTION_MIRROR,
MV88E6XXX_POLICY_ACTION_TRAP,
MV88E6XXX_POLICY_ACTION_DISCARD,
};
struct mv88e6xxx_policy {
enum mv88e6xxx_policy_mapping mapping;
enum mv88e6xxx_policy_action action;
struct ethtool_rx_flow_spec fs;
u8 addr[ETH_ALEN];
int port;
u16 vid;
};
struct mv88e6xxx_vlan {
u16 vid;
bool valid;
};
struct mv88e6xxx_port {
struct mv88e6xxx_chip *chip;
int port;
struct fwnode_handle *fwnode;
struct mv88e6xxx_vlan bridge_pvid;
u64 serdes_stats[2];
u64 atu_member_violation;
u64 atu_miss_violation;
u64 atu_full_violation;
u64 vtu_member_violation;
u64 vtu_miss_violation;
phy_interface_t interface;
u8 cmode;
bool mirror_ingress;
bool mirror_egress;
struct devlink_region *region;
void *pcs_private;
/* LED related information */
bool fiber;
struct led_classdev led0;
struct led_classdev led1;
/* MacAuth Bypass control flag */
bool mab;
};
enum mv88e6xxx_region_id {
MV88E6XXX_REGION_GLOBAL1 = 0,
MV88E6XXX_REGION_GLOBAL2,
MV88E6XXX_REGION_ATU,
MV88E6XXX_REGION_VTU,
MV88E6XXX_REGION_STU,
MV88E6XXX_REGION_PVT,
_MV88E6XXX_REGION_MAX,
};
struct mv88e6xxx_region_priv {
enum mv88e6xxx_region_id id;
};
struct mv88e6xxx_mst {
struct list_head node;
refcount_t refcnt;
struct net_device *br;
u16 msti;
struct mv88e6xxx_stu_entry stu;
};
#define STATS_TYPE_PORT BIT(0)
#define STATS_TYPE_BANK0 BIT(1)
#define STATS_TYPE_BANK1 BIT(2)
struct mv88e6xxx_hw_stat {
char string[ETH_GSTRING_LEN];
size_t size;
int reg;
int type;
};
struct mv88e6xxx_tcam {
struct list_head entries;
};
struct mv88e6xxx_chip {
const struct mv88e6xxx_info *info;
/* Currently configured tagging protocol */
enum dsa_tag_protocol tag_protocol;
/* The dsa_switch this private structure is related to */
struct dsa_switch *ds;
/* The device this structure is associated to */
struct device *dev;
/* This mutex protects the access to the switch registers */
struct mutex reg_lock;
/* The MII bus and the address on the bus that is used to
* communication with the switch
*/
const struct mv88e6xxx_bus_ops *smi_ops;
struct mii_bus *bus;
int sw_addr;
/* Handles automatic disabling and re-enabling of the PHY
* polling unit.
*/
const struct mv88e6xxx_bus_ops *phy_ops;
struct mutex ppu_mutex;
int ppu_disabled;
struct work_struct ppu_work;
struct timer_list ppu_timer;
/* This mutex serialises access to the statistics unit.
* Hold this mutex over snapshot + dump sequences.
*/
struct mutex stats_mutex;
/* A switch may have a GPIO line tied to its reset pin. Parse
* this from the device tree, and use it before performing
* switch soft reset.
*/
struct gpio_desc *reset;
/* set to size of eeprom if supported by the switch */
u32 eeprom_len;
/* List of mdio busses */
struct list_head mdios;
/* Policy Control List IDs and rules */
struct idr policies;
/* There can be two interrupt controllers, which are chained
* off a GPIO as interrupt source
*/
struct mv88e6xxx_irq g1_irq;
struct mv88e6xxx_irq g2_irq;
int irq;
char irq_name[64];
int device_irq;
char device_irq_name[64];
int watchdog_irq;
char watchdog_irq_name[64];
int atu_prob_irq;
char atu_prob_irq_name[64];
int vtu_prob_irq;
char vtu_prob_irq_name[64];
struct kthread_worker *kworker;
struct kthread_delayed_work irq_poll_work;
/* GPIO resources */
u8 gpio_data[2];
/* This cyclecounter abstracts the switch PTP time.
* reg_lock must be held for any operation that read()s.
*/
struct cyclecounter tstamp_cc;
struct timecounter tstamp_tc;
struct delayed_work overflow_work;
const struct mv88e6xxx_cc_coeffs *cc_coeffs;
struct ptp_clock *ptp_clock;
struct ptp_clock_info ptp_clock_info;
struct delayed_work tai_event_work;
struct ptp_pin_desc pin_config[MV88E6XXX_MAX_GPIO];
u16 enable_count;
/* Current ingress and egress monitor ports */
int egress_dest_port;
int ingress_dest_port;
/* Per-port timestamping resources. */
struct mv88e6xxx_port_hwtstamp port_hwtstamp[DSA_MAX_PORTS];
/* Array of port structures. */
struct mv88e6xxx_port ports[DSA_MAX_PORTS];
/* devlink regions */
struct devlink_region *regions[_MV88E6XXX_REGION_MAX];
/* Bridge MST to SID mappings */
struct list_head msts;
/* FID map */
DECLARE_BITMAP(fid_bitmap, MV88E6XXX_N_FID);
/* TCAM entries */
struct mv88e6xxx_tcam tcam;
/* Global2 scratch register config data3 */
u8 g2_scratch_config3;
};
#define TCAM_MATCH_SIZE 96
struct mv88e6xxx_tcam_key {
u16 spv;
u16 spv_mask;
u8 frame_data[TCAM_MATCH_SIZE];
u8 frame_mask[TCAM_MATCH_SIZE];
};
struct mv88e6xxx_tcam_action {
u8 dpv_mode;
u16 dpv;
};
struct mv88e6xxx_tcam_entry {
struct list_head list;
unsigned long cookie;
u32 prio;
u8 hw_idx;
struct mv88e6xxx_tcam_key key;
struct mv88e6xxx_tcam_action action;
};
struct mv88e6xxx_bus_ops {
int (*read)(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val);
int (*write)(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val);
int (*init)(struct mv88e6xxx_chip *chip);
};
struct mv88e6xxx_mdio_bus {
struct mii_bus *bus;
struct mv88e6xxx_chip *chip;
struct list_head list;
bool external;
};
struct mv88e6xxx_ops {
/* Switch Setup Errata, called early in the switch setup to
* allow any errata actions to be performed
*/
int (*setup_errata)(struct mv88e6xxx_chip *chip);
int (*ieee_pri_map)(struct mv88e6xxx_chip *chip);
int (*ip_pri_map)(struct mv88e6xxx_chip *chip);
/* Ingress Rate Limit unit (IRL) operations */
int (*irl_init_all)(struct mv88e6xxx_chip *chip, int port);
int (*get_eeprom)(struct mv88e6xxx_chip *chip,
struct ethtool_eeprom *eeprom, u8 *data);
int (*set_eeprom)(struct mv88e6xxx_chip *chip,
struct ethtool_eeprom *eeprom, u8 *data);
int (*set_switch_mac)(struct mv88e6xxx_chip *chip, u8 *addr);
int (*phy_read)(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int reg, u16 *val);
int (*phy_write)(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int reg, u16 val);
int (*phy_read_c45)(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int devad, int reg, u16 *val);
int (*phy_write_c45)(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int devad, int reg, u16 val);
/* Priority Override Table operations */
int (*pot_clear)(struct mv88e6xxx_chip *chip);
/* PHY Polling Unit (PPU) operations */
int (*ppu_enable)(struct mv88e6xxx_chip *chip);
int (*ppu_disable)(struct mv88e6xxx_chip *chip);
/* Additional handlers to run before and after hard reset, to make sure
* that the switch and EEPROM are in a good state.
*/
int (*hardware_reset_pre)(struct mv88e6xxx_chip *chip);
int (*hardware_reset_post)(struct mv88e6xxx_chip *chip);
/* Switch Software Reset */
int (*reset)(struct mv88e6xxx_chip *chip);
/* RGMII Receive/Transmit Timing Control
* Add delay on PHY_INTERFACE_MODE_RGMII_*ID, no delay otherwise.
*/
int (*port_set_rgmii_delay)(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
#define LINK_FORCED_DOWN 0
#define LINK_FORCED_UP 1
#define LINK_UNFORCED -2
/* Port's MAC link state
* Use LINK_FORCED_UP or LINK_FORCED_DOWN to force link up or down,
* or LINK_UNFORCED for normal link detection.
*/
int (*port_set_link)(struct mv88e6xxx_chip *chip, int port, int link);
/* Synchronise the port link state with that of the SERDES
*/
int (*port_sync_link)(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup);
#define PAUSE_ON 1
#define PAUSE_OFF 0
/* Enable/disable sending Pause */
int (*port_set_pause)(struct mv88e6xxx_chip *chip, int port,
int pause);
#define SPEED_UNFORCED -2
#define DUPLEX_UNFORCED -2
/* Port's MAC speed (in Mbps) and MAC duplex mode
*
* Depending on the chip, 10, 100, 200, 1000, 2500, 10000 are valid.
* Use SPEED_UNFORCED for normal detection.
*
* Use DUPLEX_HALF or DUPLEX_FULL to force half or full duplex,
* or DUPLEX_UNFORCED for normal duplex detection.
*/
int (*port_set_speed_duplex)(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int (*port_tag_remap)(struct mv88e6xxx_chip *chip, int port);
int (*port_set_policy)(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_policy_mapping mapping,
enum mv88e6xxx_policy_action action);
int (*port_set_frame_mode)(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_frame_mode mode);
int (*port_set_ucast_flood)(struct mv88e6xxx_chip *chip, int port,
bool unicast);
int (*port_set_mcast_flood)(struct mv88e6xxx_chip *chip, int port,
bool multicast);
int (*port_set_ether_type)(struct mv88e6xxx_chip *chip, int port,
u16 etype);
int (*port_set_jumbo_size)(struct mv88e6xxx_chip *chip, int port,
size_t size);
int (*port_egress_rate_limiting)(struct mv88e6xxx_chip *chip, int port);
int (*port_pause_limit)(struct mv88e6xxx_chip *chip, int port, u8 in,
u8 out);
int (*port_disable_learn_limit)(struct mv88e6xxx_chip *chip, int port);
int (*port_disable_pri_override)(struct mv88e6xxx_chip *chip, int port);
int (*port_setup_message_port)(struct mv88e6xxx_chip *chip, int port);
/* CMODE control what PHY mode the MAC will use, eg. SGMII, RGMII, etc.
* Some chips allow this to be configured on specific ports.
*/
int (*port_set_cmode)(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int (*port_get_cmode)(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
/* LED control */
int (*port_setup_leds)(struct mv88e6xxx_chip *chip, int port);
/* Some devices have a per port register indicating what is
* the upstream port this port should forward to.
*/
int (*port_set_upstream_port)(struct mv88e6xxx_chip *chip, int port,
int upstream_port);
/* Snapshot the statistics for a port. The statistics can then
* be read back a leisure but still with a consistent view.
*/
int (*stats_snapshot)(struct mv88e6xxx_chip *chip, int port);
/* Set the histogram mode for statistics, when the control registers
* are separated out of the STATS_OP register.
*/
int (*stats_set_histogram)(struct mv88e6xxx_chip *chip);
/* Return the number of strings describing statistics */
int (*stats_get_sset_count)(struct mv88e6xxx_chip *chip);
void (*stats_get_strings)(struct mv88e6xxx_chip *chip, uint8_t **data);
size_t (*stats_get_stat)(struct mv88e6xxx_chip *chip, int port,
const struct mv88e6xxx_hw_stat *stat,
uint64_t *data);
int (*set_cpu_port)(struct mv88e6xxx_chip *chip, int port);
int (*set_egress_port)(struct mv88e6xxx_chip *chip,
enum mv88e6xxx_egress_direction direction,
int port);
#define MV88E6XXX_CASCADE_PORT_NONE 0xe
#define MV88E6XXX_CASCADE_PORT_MULTIPLE 0xf
int (*set_cascade_port)(struct mv88e6xxx_chip *chip, int port);
const struct mv88e6xxx_irq_ops *watchdog_ops;
int (*mgmt_rsvd2cpu)(struct mv88e6xxx_chip *chip);
/* SERDES lane mapping */
int (*serdes_get_lane)(struct mv88e6xxx_chip *chip, int port);
/* SERDES interrupt handling */
unsigned int (*serdes_irq_mapping)(struct mv88e6xxx_chip *chip,
int port);
/* Statistics from the SERDES interface */
int (*serdes_get_sset_count)(struct mv88e6xxx_chip *chip, int port);
int (*serdes_get_strings)(struct mv88e6xxx_chip *chip, int port,
uint8_t **data);
size_t (*serdes_get_stats)(struct mv88e6xxx_chip *chip, int port,
uint64_t *data);
/* SERDES registers for ethtool */
int (*serdes_get_regs_len)(struct mv88e6xxx_chip *chip, int port);
void (*serdes_get_regs)(struct mv88e6xxx_chip *chip, int port,
void *_p);
/* Address Translation Unit operations */
int (*atu_get_hash)(struct mv88e6xxx_chip *chip, u8 *hash);
int (*atu_set_hash)(struct mv88e6xxx_chip *chip, u8 hash);
/* VLAN Translation Unit operations */
int (*vtu_getnext)(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
int (*vtu_loadpurge)(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
/* Spanning Tree Unit operations */
int (*stu_getnext)(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry);
int (*stu_loadpurge)(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry);
/* GPIO operations */
const struct mv88e6xxx_gpio_ops *gpio_ops;
/* Interface to the AVB/PTP registers */
const struct mv88e6xxx_avb_ops *avb_ops;
/* Remote Management Unit operations */
int (*rmu_disable)(struct mv88e6xxx_chip *chip);
/* Precision Time Protocol operations */
const struct mv88e6xxx_ptp_ops *ptp_ops;
/* Phylink */
void (*phylink_get_caps)(struct mv88e6xxx_chip *chip, int port,
struct phylink_config *config);
const struct mv88e6xxx_pcs_ops *pcs_ops;
/* Max Frame Size */
int (*set_max_frame_size)(struct mv88e6xxx_chip *chip, int mtu);
int (*port_enable_tcam)(struct mv88e6xxx_chip *chip, int port);
/* Ternary Content Addressable Memory operations */
const struct mv88e6xxx_tcam_ops *tcam_ops;
};
struct mv88e6xxx_irq_ops {
/* Action to be performed when the interrupt happens */
int (*irq_action)(struct mv88e6xxx_chip *chip, int irq);
/* Setup the hardware to generate the interrupt */
int (*irq_setup)(struct mv88e6xxx_chip *chip);
/* Reset the hardware to stop generating the interrupt */
void (*irq_free)(struct mv88e6xxx_chip *chip);
};
struct mv88e6xxx_gpio_ops {
/* Get/set data on GPIO pin */
int (*get_data)(struct mv88e6xxx_chip *chip, unsigned int pin);
int (*set_data)(struct mv88e6xxx_chip *chip, unsigned int pin,
int value);
/* get/set GPIO direction */
int (*get_dir)(struct mv88e6xxx_chip *chip, unsigned int pin);
int (*set_dir)(struct mv88e6xxx_chip *chip, unsigned int pin,
bool input);
/* get/set GPIO pin control */
int (*get_pctl)(struct mv88e6xxx_chip *chip, unsigned int pin,
int *func);
int (*set_pctl)(struct mv88e6xxx_chip *chip, unsigned int pin,
int func);
};
struct mv88e6xxx_avb_ops {
/* Access port-scoped Precision Time Protocol registers */
int (*port_ptp_read)(struct mv88e6xxx_chip *chip, int port, int addr,
u16 *data, int len);
int (*port_ptp_write)(struct mv88e6xxx_chip *chip, int port, int addr,
u16 data);
/* Access global Precision Time Protocol registers */
int (*ptp_read)(struct mv88e6xxx_chip *chip, int addr, u16 *data,
int len);
int (*ptp_write)(struct mv88e6xxx_chip *chip, int addr, u16 data);
/* Access global Time Application Interface registers */
int (*tai_read)(struct mv88e6xxx_chip *chip, int addr, u16 *data,
int len);
int (*tai_write)(struct mv88e6xxx_chip *chip, int addr, u16 data);
};
struct mv88e6xxx_ptp_ops {
u64 (*clock_read)(struct cyclecounter *cc);
int (*ptp_enable)(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on);
int (*ptp_verify)(struct ptp_clock_info *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan);
void (*event_work)(struct work_struct *ugly);
int (*port_enable)(struct mv88e6xxx_chip *chip, int port);
int (*port_disable)(struct mv88e6xxx_chip *chip, int port);
int (*global_enable)(struct mv88e6xxx_chip *chip);
int (*global_disable)(struct mv88e6xxx_chip *chip);
int (*set_ptp_cpu_port)(struct mv88e6xxx_chip *chip, int port);
int n_ext_ts;
int arr0_sts_reg;
int arr1_sts_reg;
int dep_sts_reg;
u32 rx_filters;
};
struct mv88e6xxx_pcs_ops {
int (*pcs_init)(struct mv88e6xxx_chip *chip, int port);
void (*pcs_teardown)(struct mv88e6xxx_chip *chip, int port);
struct phylink_pcs *(*pcs_select)(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
};
struct mv88e6xxx_tcam_ops {
int (*entry_add)(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_tcam_entry *entry, u8 idx);
int (*flush_tcam)(struct mv88e6xxx_chip *chip);
};
static inline bool mv88e6xxx_has_stu(struct mv88e6xxx_chip *chip)
{
return chip->info->max_sid > 0 &&
chip->info->ops->stu_loadpurge &&
chip->info->ops->stu_getnext;
}
static inline bool mv88e6xxx_has_pvt(struct mv88e6xxx_chip *chip)
{
return chip->info->pvt;
}
static inline bool mv88e6xxx_has_lag(struct mv88e6xxx_chip *chip)
{
return !!chip->info->global2_addr;
}
static inline bool mv88e6xxx_has_tcam(struct mv88e6xxx_chip *chip)
{
return !!chip->info->tcam_addr;
}
static inline unsigned int mv88e6xxx_num_databases(struct mv88e6xxx_chip *chip)
{
return chip->info->num_databases;
}
static inline unsigned int mv88e6xxx_num_macs(struct mv88e6xxx_chip *chip)
{
return chip->info->num_macs;
}
static inline unsigned int mv88e6xxx_num_ports(struct mv88e6xxx_chip *chip)
{
return chip->info->num_ports;
}
static inline unsigned int mv88e6xxx_max_vid(struct mv88e6xxx_chip *chip)
{
return chip->info->max_vid;
}
static inline unsigned int mv88e6xxx_max_sid(struct mv88e6xxx_chip *chip)
{
return chip->info->max_sid;
}
static inline u16 mv88e6xxx_port_mask(struct mv88e6xxx_chip *chip)
{
return GENMASK((s32)mv88e6xxx_num_ports(chip) - 1, 0);
}
static inline unsigned int mv88e6xxx_num_gpio(struct mv88e6xxx_chip *chip)
{
return chip->info->num_gpio;
}
static inline bool mv88e6xxx_is_invalid_port(struct mv88e6xxx_chip *chip, int port)
{
return (chip->info->invalid_port_mask & BIT(port)) != 0;
}
static inline void mv88e6xxx_port_set_mab(struct mv88e6xxx_chip *chip,
int port, bool mab)
{
chip->ports[port].mab = mab;
}
int mv88e6xxx_read(struct mv88e6xxx_chip *chip, int addr, int reg, u16 *val);
int mv88e6xxx_write(struct mv88e6xxx_chip *chip, int addr, int reg, u16 val);
int mv88e6xxx_wait_mask(struct mv88e6xxx_chip *chip, int addr, int reg,
u16 mask, u16 val);
int mv88e6xxx_wait_bit(struct mv88e6xxx_chip *chip, int addr, int reg,
int bit, int val);
struct mii_bus *mv88e6xxx_default_mdio_bus(struct mv88e6xxx_chip *chip);
static inline void mv88e6xxx_reg_lock(struct mv88e6xxx_chip *chip)
{
mutex_lock(&chip->reg_lock);
}
static inline void mv88e6xxx_reg_unlock(struct mv88e6xxx_chip *chip)
{
mutex_unlock(&chip->reg_lock);
}
int mv88e6xxx_vtu_walk(struct mv88e6xxx_chip *chip,
int (*cb)(struct mv88e6xxx_chip *chip,
const struct mv88e6xxx_vtu_entry *entry,
void *priv),
void *priv);
#endif /* _MV88E6XXX_CHIP_H */
@@ -0,0 +1,815 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <net/dsa.h>
#include "chip.h"
#include "devlink.h"
#include "global1.h"
#include "global2.h"
#include "port.h"
static int mv88e6xxx_atu_get_hash(struct mv88e6xxx_chip *chip, u8 *hash)
{
if (chip->info->ops->atu_get_hash)
return chip->info->ops->atu_get_hash(chip, hash);
return -EOPNOTSUPP;
}
static int mv88e6xxx_atu_set_hash(struct mv88e6xxx_chip *chip, u8 hash)
{
if (chip->info->ops->atu_set_hash)
return chip->info->ops->atu_set_hash(chip, hash);
return -EOPNOTSUPP;
}
enum mv88e6xxx_devlink_param_id {
MV88E6XXX_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX,
MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH,
};
int mv88e6xxx_devlink_param_get(struct dsa_switch *ds, u32 id,
struct devlink_param_gset_ctx *ctx)
{
struct mv88e6xxx_chip *chip = ds->priv;
int err;
mv88e6xxx_reg_lock(chip);
switch (id) {
case MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH:
err = mv88e6xxx_atu_get_hash(chip, &ctx->val.vu8);
break;
default:
err = -EOPNOTSUPP;
break;
}
mv88e6xxx_reg_unlock(chip);
return err;
}
int mv88e6xxx_devlink_param_set(struct dsa_switch *ds, u32 id,
struct devlink_param_gset_ctx *ctx)
{
struct mv88e6xxx_chip *chip = ds->priv;
int err;
mv88e6xxx_reg_lock(chip);
switch (id) {
case MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH:
err = mv88e6xxx_atu_set_hash(chip, ctx->val.vu8);
break;
default:
err = -EOPNOTSUPP;
break;
}
mv88e6xxx_reg_unlock(chip);
return err;
}
static const struct devlink_param mv88e6xxx_devlink_params[] = {
DSA_DEVLINK_PARAM_DRIVER(MV88E6XXX_DEVLINK_PARAM_ID_ATU_HASH,
"ATU_hash", DEVLINK_PARAM_TYPE_U8,
BIT(DEVLINK_PARAM_CMODE_RUNTIME)),
};
int mv88e6xxx_setup_devlink_params(struct dsa_switch *ds)
{
return dsa_devlink_params_register(ds, mv88e6xxx_devlink_params,
ARRAY_SIZE(mv88e6xxx_devlink_params));
}
void mv88e6xxx_teardown_devlink_params(struct dsa_switch *ds)
{
dsa_devlink_params_unregister(ds, mv88e6xxx_devlink_params,
ARRAY_SIZE(mv88e6xxx_devlink_params));
}
enum mv88e6xxx_devlink_resource_id {
MV88E6XXX_RESOURCE_ID_NONE, /* DEVLINK_RESOURCE_ID_PARENT_TOP */
MV88E6XXX_RESOURCE_ID_ATU,
MV88E6XXX_RESOURCE_ID_ATU_BIN_0,
MV88E6XXX_RESOURCE_ID_ATU_BIN_1,
MV88E6XXX_RESOURCE_ID_ATU_BIN_2,
MV88E6XXX_RESOURCE_ID_ATU_BIN_3,
};
static u64 mv88e6xxx_devlink_atu_bin_get(struct mv88e6xxx_chip *chip,
u16 bin)
{
u16 occupancy = 0;
int err;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_g2_atu_stats_set(chip, MV88E6XXX_G2_ATU_STATS_MODE_ALL,
bin);
if (err) {
dev_err(chip->dev, "failed to set ATU stats kind/bin\n");
goto unlock;
}
err = mv88e6xxx_g1_atu_get_next(chip, 0);
if (err) {
dev_err(chip->dev, "failed to perform ATU get next\n");
goto unlock;
}
err = mv88e6xxx_g2_atu_stats_get(chip, &occupancy);
if (err) {
dev_err(chip->dev, "failed to get ATU stats\n");
goto unlock;
}
occupancy &= MV88E6XXX_G2_ATU_STATS_MASK;
unlock:
mv88e6xxx_reg_unlock(chip);
return occupancy;
}
static u64 mv88e6xxx_devlink_atu_bin_0_get(void *priv)
{
struct mv88e6xxx_chip *chip = priv;
return mv88e6xxx_devlink_atu_bin_get(chip,
MV88E6XXX_G2_ATU_STATS_BIN_0);
}
static u64 mv88e6xxx_devlink_atu_bin_1_get(void *priv)
{
struct mv88e6xxx_chip *chip = priv;
return mv88e6xxx_devlink_atu_bin_get(chip,
MV88E6XXX_G2_ATU_STATS_BIN_1);
}
static u64 mv88e6xxx_devlink_atu_bin_2_get(void *priv)
{
struct mv88e6xxx_chip *chip = priv;
return mv88e6xxx_devlink_atu_bin_get(chip,
MV88E6XXX_G2_ATU_STATS_BIN_2);
}
static u64 mv88e6xxx_devlink_atu_bin_3_get(void *priv)
{
struct mv88e6xxx_chip *chip = priv;
return mv88e6xxx_devlink_atu_bin_get(chip,
MV88E6XXX_G2_ATU_STATS_BIN_3);
}
static u64 mv88e6xxx_devlink_atu_get(void *priv)
{
return mv88e6xxx_devlink_atu_bin_0_get(priv) +
mv88e6xxx_devlink_atu_bin_1_get(priv) +
mv88e6xxx_devlink_atu_bin_2_get(priv) +
mv88e6xxx_devlink_atu_bin_3_get(priv);
}
int mv88e6xxx_setup_devlink_resources(struct dsa_switch *ds)
{
struct devlink_resource_size_params size_params;
struct mv88e6xxx_chip *chip = ds->priv;
int err;
devlink_resource_size_params_init(&size_params,
mv88e6xxx_num_macs(chip),
mv88e6xxx_num_macs(chip),
1, DEVLINK_RESOURCE_UNIT_ENTRY);
err = dsa_devlink_resource_register(ds, "ATU",
mv88e6xxx_num_macs(chip),
MV88E6XXX_RESOURCE_ID_ATU,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&size_params);
if (err)
goto out;
devlink_resource_size_params_init(&size_params,
mv88e6xxx_num_macs(chip) / 4,
mv88e6xxx_num_macs(chip) / 4,
1, DEVLINK_RESOURCE_UNIT_ENTRY);
err = dsa_devlink_resource_register(ds, "ATU_bin_0",
mv88e6xxx_num_macs(chip) / 4,
MV88E6XXX_RESOURCE_ID_ATU_BIN_0,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&size_params);
if (err)
goto out;
err = dsa_devlink_resource_register(ds, "ATU_bin_1",
mv88e6xxx_num_macs(chip) / 4,
MV88E6XXX_RESOURCE_ID_ATU_BIN_1,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&size_params);
if (err)
goto out;
err = dsa_devlink_resource_register(ds, "ATU_bin_2",
mv88e6xxx_num_macs(chip) / 4,
MV88E6XXX_RESOURCE_ID_ATU_BIN_2,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&size_params);
if (err)
goto out;
err = dsa_devlink_resource_register(ds, "ATU_bin_3",
mv88e6xxx_num_macs(chip) / 4,
MV88E6XXX_RESOURCE_ID_ATU_BIN_3,
DEVLINK_RESOURCE_ID_PARENT_TOP,
&size_params);
if (err)
goto out;
dsa_devlink_resource_occ_get_register(ds,
MV88E6XXX_RESOURCE_ID_ATU,
mv88e6xxx_devlink_atu_get,
chip);
dsa_devlink_resource_occ_get_register(ds,
MV88E6XXX_RESOURCE_ID_ATU_BIN_0,
mv88e6xxx_devlink_atu_bin_0_get,
chip);
dsa_devlink_resource_occ_get_register(ds,
MV88E6XXX_RESOURCE_ID_ATU_BIN_1,
mv88e6xxx_devlink_atu_bin_1_get,
chip);
dsa_devlink_resource_occ_get_register(ds,
MV88E6XXX_RESOURCE_ID_ATU_BIN_2,
mv88e6xxx_devlink_atu_bin_2_get,
chip);
dsa_devlink_resource_occ_get_register(ds,
MV88E6XXX_RESOURCE_ID_ATU_BIN_3,
mv88e6xxx_devlink_atu_bin_3_get,
chip);
return 0;
out:
dsa_devlink_resources_unregister(ds);
return err;
}
static int mv88e6xxx_region_global_snapshot(struct devlink *dl,
const struct devlink_region_ops *ops,
struct netlink_ext_ack *extack,
u8 **data)
{
struct mv88e6xxx_region_priv *region_priv = ops->priv;
struct dsa_switch *ds = dsa_devlink_to_ds(dl);
struct mv88e6xxx_chip *chip = ds->priv;
u16 *registers;
int i, err;
registers = kmalloc_array(32, sizeof(u16), GFP_KERNEL);
if (!registers)
return -ENOMEM;
mv88e6xxx_reg_lock(chip);
for (i = 0; i < 32; i++) {
switch (region_priv->id) {
case MV88E6XXX_REGION_GLOBAL1:
err = mv88e6xxx_g1_read(chip, i, &registers[i]);
break;
case MV88E6XXX_REGION_GLOBAL2:
err = mv88e6xxx_g2_read(chip, i, &registers[i]);
break;
default:
err = -EOPNOTSUPP;
}
if (err) {
kfree(registers);
goto out;
}
}
*data = (u8 *)registers;
out:
mv88e6xxx_reg_unlock(chip);
return err;
}
/* The ATU entry varies between mv88e6xxx chipset generations. Define
* a generic format which covers all the current and hopefully future
* mv88e6xxx generations
*/
struct mv88e6xxx_devlink_atu_entry {
/* The FID is scattered over multiple registers. */
u16 fid;
u16 atu_op;
u16 atu_data;
u16 atu_01;
u16 atu_23;
u16 atu_45;
};
static int mv88e6xxx_region_atu_snapshot_fid(struct mv88e6xxx_chip *chip,
int fid,
struct mv88e6xxx_devlink_atu_entry *table,
int *count)
{
u16 atu_op, atu_data, atu_01, atu_23, atu_45;
struct mv88e6xxx_atu_entry addr;
int err;
addr.state = 0;
eth_broadcast_addr(addr.mac);
do {
err = mv88e6xxx_g1_atu_getnext(chip, fid, &addr);
if (err)
return err;
if (!addr.state)
break;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_OP, &atu_op);
if (err)
return err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_DATA, &atu_data);
if (err)
return err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC01, &atu_01);
if (err)
return err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC23, &atu_23);
if (err)
return err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC45, &atu_45);
if (err)
return err;
table[*count].fid = fid;
table[*count].atu_op = atu_op;
table[*count].atu_data = atu_data;
table[*count].atu_01 = atu_01;
table[*count].atu_23 = atu_23;
table[*count].atu_45 = atu_45;
(*count)++;
} while (!is_broadcast_ether_addr(addr.mac));
return 0;
}
static int mv88e6xxx_region_atu_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 mv88e6xxx_devlink_atu_entry *table;
struct mv88e6xxx_chip *chip = ds->priv;
int fid = -1, err = 0, count = 0;
table = kzalloc_objs(struct mv88e6xxx_devlink_atu_entry,
mv88e6xxx_num_databases(chip));
if (!table)
return -ENOMEM;
mv88e6xxx_reg_lock(chip);
while (1) {
fid = find_next_bit(chip->fid_bitmap, MV88E6XXX_N_FID, fid + 1);
if (fid == MV88E6XXX_N_FID)
break;
err = mv88e6xxx_region_atu_snapshot_fid(chip, fid, table,
&count);
if (err) {
kfree(table);
goto out;
}
}
*data = (u8 *)table;
out:
mv88e6xxx_reg_unlock(chip);
return err;
}
/**
* struct mv88e6xxx_devlink_vtu_entry - Devlink VTU entry
* @fid: Global1/2: FID and VLAN policy.
* @sid: Global1/3: SID, unknown filters and learning.
* @op: Global1/5: FID (old chipsets).
* @vid: Global1/6: VID, valid, and page.
* @data: Global1/7-9: Membership data and priority override.
* @resvd: Reserved. Also happens to align the size to 16B.
*
* The VTU entry format varies between chipset generations, the
* descriptions above represent the superset of all possible
* information, not all fields are valid on all devices. Since this is
* a low-level debug interface, copy all data verbatim and defer
* parsing to the consumer.
*/
struct mv88e6xxx_devlink_vtu_entry {
u16 fid;
u16 sid;
u16 op;
u16 vid;
u16 data[3];
u16 resvd;
};
static int mv88e6xxx_region_vtu_snapshot(struct devlink *dl,
const struct devlink_region_ops *ops,
struct netlink_ext_ack *extack,
u8 **data)
{
struct mv88e6xxx_devlink_vtu_entry *table, *entry;
struct dsa_switch *ds = dsa_devlink_to_ds(dl);
struct mv88e6xxx_chip *chip = ds->priv;
struct mv88e6xxx_vtu_entry vlan;
int err;
table = kzalloc_objs(struct mv88e6xxx_devlink_vtu_entry,
mv88e6xxx_max_vid(chip) + 1);
if (!table)
return -ENOMEM;
entry = table;
vlan.vid = mv88e6xxx_max_vid(chip);
vlan.valid = false;
mv88e6xxx_reg_lock(chip);
do {
err = mv88e6xxx_g1_vtu_getnext(chip, &vlan);
if (err)
break;
if (!vlan.valid)
break;
err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_FID,
&entry->fid);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID,
&entry->sid);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_OP,
&entry->op);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID,
&entry->vid);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1,
&entry->data[0]);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA2,
&entry->data[1]);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA3,
&entry->data[2]);
if (err)
break;
entry++;
} while (vlan.vid < mv88e6xxx_max_vid(chip));
mv88e6xxx_reg_unlock(chip);
if (err) {
kfree(table);
return err;
}
*data = (u8 *)table;
return 0;
}
/**
* struct mv88e6xxx_devlink_stu_entry - Devlink STU entry
* @sid: Global1/3: SID, unknown filters and learning.
* @vid: Global1/6: Valid bit.
* @data: Global1/7-9: Membership data and priority override.
* @resvd: Reserved. In case we forgot something.
*
* The STU entry format varies between chipset generations. Peridot
* and Amethyst packs the STU data into Global1/7-8. Older silicon
* spreads the information across all three VTU data registers -
* inheriting the layout of even older hardware that had no STU at
* all. Since this is a low-level debug interface, copy all data
* verbatim and defer parsing to the consumer.
*/
struct mv88e6xxx_devlink_stu_entry {
u16 sid;
u16 vid;
u16 data[3];
u16 resvd;
};
static int mv88e6xxx_region_stu_snapshot(struct devlink *dl,
const struct devlink_region_ops *ops,
struct netlink_ext_ack *extack,
u8 **data)
{
struct mv88e6xxx_devlink_stu_entry *table, *entry;
struct dsa_switch *ds = dsa_devlink_to_ds(dl);
struct mv88e6xxx_chip *chip = ds->priv;
struct mv88e6xxx_stu_entry stu;
int err;
table = kzalloc_objs(struct mv88e6xxx_devlink_stu_entry,
mv88e6xxx_max_sid(chip) + 1);
if (!table)
return -ENOMEM;
entry = table;
stu.sid = mv88e6xxx_max_sid(chip);
stu.valid = false;
mv88e6xxx_reg_lock(chip);
do {
err = mv88e6xxx_g1_stu_getnext(chip, &stu);
if (err)
break;
if (!stu.valid)
break;
err = err ? : mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID,
&entry->sid);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID,
&entry->vid);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1,
&entry->data[0]);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA2,
&entry->data[1]);
err = err ? : mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA3,
&entry->data[2]);
if (err)
break;
entry++;
} while (stu.sid < mv88e6xxx_max_sid(chip));
mv88e6xxx_reg_unlock(chip);
if (err) {
kfree(table);
return err;
}
*data = (u8 *)table;
return 0;
}
static int mv88e6xxx_region_pvt_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 mv88e6xxx_chip *chip = ds->priv;
int dev, port, err;
u16 *pvt, *cur;
pvt = kcalloc(MV88E6XXX_MAX_PVT_ENTRIES, sizeof(*pvt), GFP_KERNEL);
if (!pvt)
return -ENOMEM;
mv88e6xxx_reg_lock(chip);
cur = pvt;
for (dev = 0; dev < MV88E6XXX_MAX_PVT_SWITCHES; dev++) {
for (port = 0; port < MV88E6XXX_MAX_PVT_PORTS; port++) {
err = mv88e6xxx_g2_pvt_read(chip, dev, port, cur);
if (err)
break;
cur++;
}
}
mv88e6xxx_reg_unlock(chip);
if (err) {
kfree(pvt);
return err;
}
*data = (u8 *)pvt;
return 0;
}
static int mv88e6xxx_region_port_snapshot(struct devlink_port *devlink_port,
const struct devlink_port_region_ops *ops,
struct netlink_ext_ack *extack,
u8 **data)
{
struct dsa_switch *ds = dsa_devlink_port_to_ds(devlink_port);
int port = dsa_devlink_port_to_port(devlink_port);
struct mv88e6xxx_chip *chip = ds->priv;
u16 *registers;
int i, err;
registers = kmalloc_array(32, sizeof(u16), GFP_KERNEL);
if (!registers)
return -ENOMEM;
mv88e6xxx_reg_lock(chip);
for (i = 0; i < 32; i++) {
err = mv88e6xxx_port_read(chip, port, i, &registers[i]);
if (err) {
kfree(registers);
goto out;
}
}
*data = (u8 *)registers;
out:
mv88e6xxx_reg_unlock(chip);
return err;
}
static struct mv88e6xxx_region_priv mv88e6xxx_region_global1_priv = {
.id = MV88E6XXX_REGION_GLOBAL1,
};
static const struct devlink_region_ops mv88e6xxx_region_global1_ops = {
.name = "global1",
.snapshot = mv88e6xxx_region_global_snapshot,
.destructor = kfree,
.priv = &mv88e6xxx_region_global1_priv,
};
static struct mv88e6xxx_region_priv mv88e6xxx_region_global2_priv = {
.id = MV88E6XXX_REGION_GLOBAL2,
};
static const struct devlink_region_ops mv88e6xxx_region_global2_ops = {
.name = "global2",
.snapshot = mv88e6xxx_region_global_snapshot,
.destructor = kfree,
.priv = &mv88e6xxx_region_global2_priv,
};
static const struct devlink_region_ops mv88e6xxx_region_atu_ops = {
.name = "atu",
.snapshot = mv88e6xxx_region_atu_snapshot,
.destructor = kfree,
};
static const struct devlink_region_ops mv88e6xxx_region_vtu_ops = {
.name = "vtu",
.snapshot = mv88e6xxx_region_vtu_snapshot,
.destructor = kfree,
};
static const struct devlink_region_ops mv88e6xxx_region_stu_ops = {
.name = "stu",
.snapshot = mv88e6xxx_region_stu_snapshot,
.destructor = kfree,
};
static const struct devlink_region_ops mv88e6xxx_region_pvt_ops = {
.name = "pvt",
.snapshot = mv88e6xxx_region_pvt_snapshot,
.destructor = kfree,
};
static const struct devlink_port_region_ops mv88e6xxx_region_port_ops = {
.name = "port",
.snapshot = mv88e6xxx_region_port_snapshot,
.destructor = kfree,
};
struct mv88e6xxx_region {
const struct devlink_region_ops *ops;
u64 size;
bool (*cond)(struct mv88e6xxx_chip *chip);
};
static const struct mv88e6xxx_region mv88e6xxx_regions[] = {
[MV88E6XXX_REGION_GLOBAL1] = {
.ops = &mv88e6xxx_region_global1_ops,
.size = 32 * sizeof(u16)
},
[MV88E6XXX_REGION_GLOBAL2] = {
.ops = &mv88e6xxx_region_global2_ops,
.size = 32 * sizeof(u16) },
[MV88E6XXX_REGION_ATU] = {
.ops = &mv88e6xxx_region_atu_ops
/* calculated at runtime */
},
[MV88E6XXX_REGION_VTU] = {
.ops = &mv88e6xxx_region_vtu_ops
/* calculated at runtime */
},
[MV88E6XXX_REGION_STU] = {
.ops = &mv88e6xxx_region_stu_ops,
.cond = mv88e6xxx_has_stu,
/* calculated at runtime */
},
[MV88E6XXX_REGION_PVT] = {
.ops = &mv88e6xxx_region_pvt_ops,
.size = MV88E6XXX_MAX_PVT_ENTRIES * sizeof(u16),
.cond = mv88e6xxx_has_pvt,
},
};
void mv88e6xxx_teardown_devlink_regions_global(struct dsa_switch *ds)
{
struct mv88e6xxx_chip *chip = ds->priv;
int i;
for (i = 0; i < ARRAY_SIZE(mv88e6xxx_regions); i++)
if (chip->regions[i])
dsa_devlink_region_destroy(chip->regions[i]);
}
void mv88e6xxx_teardown_devlink_regions_port(struct dsa_switch *ds, int port)
{
struct mv88e6xxx_chip *chip = ds->priv;
dsa_devlink_region_destroy(chip->ports[port].region);
}
int mv88e6xxx_setup_devlink_regions_port(struct dsa_switch *ds, int port)
{
struct mv88e6xxx_chip *chip = ds->priv;
struct devlink_region *region;
region = dsa_devlink_port_region_create(ds,
port,
&mv88e6xxx_region_port_ops, 1,
32 * sizeof(u16));
if (IS_ERR(region))
return PTR_ERR(region);
chip->ports[port].region = region;
return 0;
}
int mv88e6xxx_setup_devlink_regions_global(struct dsa_switch *ds)
{
bool (*cond)(struct mv88e6xxx_chip *chip);
struct mv88e6xxx_chip *chip = ds->priv;
const struct devlink_region_ops *ops;
struct devlink_region *region;
u64 size;
int i, j;
for (i = 0; i < ARRAY_SIZE(mv88e6xxx_regions); i++) {
ops = mv88e6xxx_regions[i].ops;
size = mv88e6xxx_regions[i].size;
cond = mv88e6xxx_regions[i].cond;
if (cond && !cond(chip))
continue;
switch (i) {
case MV88E6XXX_REGION_ATU:
size = mv88e6xxx_num_databases(chip) *
sizeof(struct mv88e6xxx_devlink_atu_entry);
break;
case MV88E6XXX_REGION_VTU:
size = (mv88e6xxx_max_vid(chip) + 1) *
sizeof(struct mv88e6xxx_devlink_vtu_entry);
break;
case MV88E6XXX_REGION_STU:
size = (mv88e6xxx_max_sid(chip) + 1) *
sizeof(struct mv88e6xxx_devlink_stu_entry);
break;
}
region = dsa_devlink_region_create(ds, ops, 1, size);
if (IS_ERR(region))
goto out;
chip->regions[i] = region;
}
return 0;
out:
for (j = 0; j < i; j++)
dsa_devlink_region_destroy(chip->regions[j]);
return PTR_ERR(region);
}
int mv88e6xxx_devlink_info_get(struct dsa_switch *ds,
struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
struct mv88e6xxx_chip *chip = ds->priv;
return devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
chip->info->name);
}
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Marvell 88E6xxx Switch devlink support. */
#ifndef _MV88E6XXX_DEVLINK_H
#define _MV88E6XXX_DEVLINK_H
int mv88e6xxx_setup_devlink_params(struct dsa_switch *ds);
void mv88e6xxx_teardown_devlink_params(struct dsa_switch *ds);
int mv88e6xxx_setup_devlink_resources(struct dsa_switch *ds);
int mv88e6xxx_devlink_param_get(struct dsa_switch *ds, u32 id,
struct devlink_param_gset_ctx *ctx);
int mv88e6xxx_devlink_param_set(struct dsa_switch *ds, u32 id,
struct devlink_param_gset_ctx *ctx);
int mv88e6xxx_setup_devlink_regions_global(struct dsa_switch *ds);
void mv88e6xxx_teardown_devlink_regions_global(struct dsa_switch *ds);
int mv88e6xxx_setup_devlink_regions_port(struct dsa_switch *ds, int port);
void mv88e6xxx_teardown_devlink_regions_port(struct dsa_switch *ds, int port);
int mv88e6xxx_devlink_info_get(struct dsa_switch *ds,
struct devlink_info_req *req,
struct netlink_ext_ack *extack);
#endif /* _MV88E6XXX_DEVLINK_H */
@@ -0,0 +1,679 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch Global (1) Registers support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2016-2017 Savoir-faire Linux Inc.
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*/
#include <linux/bitfield.h>
#include "chip.h"
#include "global1.h"
int mv88e6xxx_g1_read(struct mv88e6xxx_chip *chip, int reg, u16 *val)
{
int addr = chip->info->global1_addr;
return mv88e6xxx_read(chip, addr, reg, val);
}
int mv88e6xxx_g1_write(struct mv88e6xxx_chip *chip, int reg, u16 val)
{
int addr = chip->info->global1_addr;
return mv88e6xxx_write(chip, addr, reg, val);
}
int mv88e6xxx_g1_wait_bit(struct mv88e6xxx_chip *chip, int reg, int
bit, int val)
{
return mv88e6xxx_wait_bit(chip, chip->info->global1_addr, reg,
bit, val);
}
int mv88e6xxx_g1_wait_mask(struct mv88e6xxx_chip *chip, int reg,
u16 mask, u16 val)
{
return mv88e6xxx_wait_mask(chip, chip->info->global1_addr, reg,
mask, val);
}
/* Offset 0x00: Switch Global Status Register */
static int mv88e6185_g1_wait_ppu_disabled(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g1_wait_mask(chip, MV88E6XXX_G1_STS,
MV88E6185_G1_STS_PPU_STATE_MASK,
MV88E6185_G1_STS_PPU_STATE_DISABLED);
}
static int mv88e6185_g1_wait_ppu_polling(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g1_wait_mask(chip, MV88E6XXX_G1_STS,
MV88E6185_G1_STS_PPU_STATE_MASK,
MV88E6185_G1_STS_PPU_STATE_POLLING);
}
static int mv88e6352_g1_wait_ppu_polling(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6352_G1_STS_PPU_STATE);
return mv88e6xxx_g1_wait_bit(chip, MV88E6XXX_G1_STS, bit, 1);
}
static int mv88e6xxx_g1_wait_init_ready(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6XXX_G1_STS_INIT_READY);
/* Wait up to 1 second for the switch to be ready. The InitReady bit 11
* is set to a one when all units inside the device (ATU, VTU, etc.)
* have finished their initialization and are ready to accept frames.
*/
return mv88e6xxx_g1_wait_bit(chip, MV88E6XXX_G1_STS, bit, 1);
}
static int mv88e6250_g1_eeprom_reload(struct mv88e6xxx_chip *chip)
{
/* MV88E6185_G1_CTL1_RELOAD_EEPROM is also valid for 88E6250 */
int bit = __bf_shf(MV88E6185_G1_CTL1_RELOAD_EEPROM);
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &val);
if (err)
return err;
val |= MV88E6185_G1_CTL1_RELOAD_EEPROM;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, val);
if (err)
return err;
return mv88e6xxx_g1_wait_bit(chip, MV88E6XXX_G1_CTL1, bit, 0);
}
/* Returns 0 when done, -EBUSY when waiting, other negative codes on error */
static int mv88e6xxx_g1_is_eeprom_done(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STS, &val);
if (err < 0) {
dev_err(chip->dev, "Error reading status");
return err;
}
/* If the switch is still resetting, it may not
* respond on the bus, and so MDIO read returns
* 0xffff. Differentiate between that, and waiting for
* the EEPROM to be done by bit 0 being set.
*/
if (val == 0xffff || !(val & BIT(MV88E6XXX_G1_STS_IRQ_EEPROM_DONE)))
return -EBUSY;
return 0;
}
/* As the EEInt (EEPROM done) flag clears on read if the status register, this
* function must be called directly after a hard reset or EEPROM ReLoad request,
* or the done condition may have been missed
*/
int mv88e6xxx_g1_wait_eeprom_done(struct mv88e6xxx_chip *chip)
{
const unsigned long timeout = jiffies + 1 * HZ;
int ret;
/* Wait up to 1 second for the switch to finish reading the
* EEPROM.
*/
while (time_before(jiffies, timeout)) {
ret = mv88e6xxx_g1_is_eeprom_done(chip);
if (ret != -EBUSY)
return ret;
}
dev_err(chip->dev, "Timeout waiting for EEPROM done");
return -ETIMEDOUT;
}
int mv88e6250_g1_wait_eeprom_done_prereset(struct mv88e6xxx_chip *chip)
{
int ret;
ret = mv88e6xxx_g1_is_eeprom_done(chip);
if (ret != -EBUSY)
return ret;
/* Pre-reset, we don't know the state of the switch - when
* mv88e6xxx_g1_is_eeprom_done() returns -EBUSY, that may be because
* the switch is actually busy reading the EEPROM, or because
* MV88E6XXX_G1_STS_IRQ_EEPROM_DONE has been cleared by an unrelated
* status register read already.
*
* To account for the latter case, trigger another EEPROM reload for
* another chance at seeing the done flag.
*/
ret = mv88e6250_g1_eeprom_reload(chip);
if (ret)
return ret;
return mv88e6xxx_g1_wait_eeprom_done(chip);
}
/* Offset 0x01: Switch MAC Address Register Bytes 0 & 1
* Offset 0x02: Switch MAC Address Register Bytes 2 & 3
* Offset 0x03: Switch MAC Address Register Bytes 4 & 5
*/
int mv88e6xxx_g1_set_switch_mac(struct mv88e6xxx_chip *chip, u8 *addr)
{
u16 reg;
int err;
reg = (addr[0] << 8) | addr[1];
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_MAC_01, reg);
if (err)
return err;
reg = (addr[2] << 8) | addr[3];
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_MAC_23, reg);
if (err)
return err;
reg = (addr[4] << 8) | addr[5];
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_MAC_45, reg);
if (err)
return err;
return 0;
}
/* Offset 0x04: Switch Global Control Register */
int mv88e6185_g1_reset(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
/* Set the SWReset bit 15 along with the PPUEn bit 14, to also restart
* the PPU, including re-doing PHY detection and initialization
*/
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &val);
if (err)
return err;
val |= MV88E6XXX_G1_CTL1_SW_RESET;
val |= MV88E6XXX_G1_CTL1_PPU_ENABLE;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, val);
if (err)
return err;
err = mv88e6xxx_g1_wait_init_ready(chip);
if (err)
return err;
return mv88e6185_g1_wait_ppu_polling(chip);
}
int mv88e6250_g1_reset(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
/* Set the SWReset bit 15 */
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &val);
if (err)
return err;
val |= MV88E6XXX_G1_CTL1_SW_RESET;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, val);
if (err)
return err;
return mv88e6xxx_g1_wait_init_ready(chip);
}
int mv88e6352_g1_reset(struct mv88e6xxx_chip *chip)
{
int err;
err = mv88e6250_g1_reset(chip);
if (err)
return err;
return mv88e6352_g1_wait_ppu_polling(chip);
}
int mv88e6185_g1_ppu_enable(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &val);
if (err)
return err;
val |= MV88E6XXX_G1_CTL1_PPU_ENABLE;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, val);
if (err)
return err;
return mv88e6185_g1_wait_ppu_polling(chip);
}
int mv88e6185_g1_ppu_disable(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &val);
if (err)
return err;
val &= ~MV88E6XXX_G1_CTL1_PPU_ENABLE;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, val);
if (err)
return err;
return mv88e6185_g1_wait_ppu_disabled(chip);
}
int mv88e6185_g1_set_max_frame_size(struct mv88e6xxx_chip *chip, int mtu)
{
u16 val;
int err;
mtu += ETH_HLEN + ETH_FCS_LEN;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL1, &val);
if (err)
return err;
val &= ~MV88E6185_G1_CTL1_MAX_FRAME_1632;
if (mtu > 1518)
val |= MV88E6185_G1_CTL1_MAX_FRAME_1632;
return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL1, val);
}
/* Offset 0x10: IP-PRI Mapping Register 0
* Offset 0x11: IP-PRI Mapping Register 1
* Offset 0x12: IP-PRI Mapping Register 2
* Offset 0x13: IP-PRI Mapping Register 3
* Offset 0x14: IP-PRI Mapping Register 4
* Offset 0x15: IP-PRI Mapping Register 5
* Offset 0x16: IP-PRI Mapping Register 6
* Offset 0x17: IP-PRI Mapping Register 7
*/
int mv88e6085_g1_ip_pri_map(struct mv88e6xxx_chip *chip)
{
int err;
/* Reset the IP TOS/DiffServ/Traffic priorities to defaults */
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IP_PRI_0, 0x0000);
if (err)
return err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IP_PRI_1, 0x0000);
if (err)
return err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IP_PRI_2, 0x5555);
if (err)
return err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IP_PRI_3, 0x5555);
if (err)
return err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IP_PRI_4, 0xaaaa);
if (err)
return err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IP_PRI_5, 0xaaaa);
if (err)
return err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IP_PRI_6, 0xffff);
if (err)
return err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IP_PRI_7, 0xffff);
if (err)
return err;
return 0;
}
/* Offset 0x18: IEEE-PRI Register */
int mv88e6085_g1_ieee_pri_map(struct mv88e6xxx_chip *chip)
{
/* Reset the IEEE Tag priorities to defaults */
return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IEEE_PRI, 0xfa41);
}
int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip)
{
/* Reset the IEEE Tag priorities to defaults */
return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_IEEE_PRI, 0xfa50);
}
/* Offset 0x1a: Monitor Control */
/* Offset 0x1a: Monitor & MGMT Control on some devices */
int mv88e6095_g1_set_egress_port(struct mv88e6xxx_chip *chip,
enum mv88e6xxx_egress_direction direction,
int port)
{
u16 reg;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6185_G1_MONITOR_CTL, &reg);
if (err)
return err;
switch (direction) {
case MV88E6XXX_EGRESS_DIR_INGRESS:
reg &= ~MV88E6185_G1_MONITOR_CTL_INGRESS_DEST_MASK;
reg |= port <<
__bf_shf(MV88E6185_G1_MONITOR_CTL_INGRESS_DEST_MASK);
break;
case MV88E6XXX_EGRESS_DIR_EGRESS:
reg &= ~MV88E6185_G1_MONITOR_CTL_EGRESS_DEST_MASK;
reg |= port <<
__bf_shf(MV88E6185_G1_MONITOR_CTL_EGRESS_DEST_MASK);
break;
default:
return -EINVAL;
}
return mv88e6xxx_g1_write(chip, MV88E6185_G1_MONITOR_CTL, reg);
}
/* Older generations also call this the ARP destination. It has been
* generalized in more modern devices such that more than ARP can
* egress it
*/
int mv88e6095_g1_set_cpu_port(struct mv88e6xxx_chip *chip, int port)
{
u16 reg;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6185_G1_MONITOR_CTL, &reg);
if (err)
return err;
reg &= ~MV88E6185_G1_MONITOR_CTL_ARP_DEST_MASK;
reg |= port << __bf_shf(MV88E6185_G1_MONITOR_CTL_ARP_DEST_MASK);
return mv88e6xxx_g1_write(chip, MV88E6185_G1_MONITOR_CTL, reg);
}
static int mv88e6390_g1_monitor_write(struct mv88e6xxx_chip *chip,
u16 pointer, u8 data)
{
u16 reg;
reg = MV88E6390_G1_MONITOR_MGMT_CTL_UPDATE | pointer | data;
return mv88e6xxx_g1_write(chip, MV88E6390_G1_MONITOR_MGMT_CTL, reg);
}
int mv88e6390_g1_set_egress_port(struct mv88e6xxx_chip *chip,
enum mv88e6xxx_egress_direction direction,
int port)
{
u16 ptr;
switch (direction) {
case MV88E6XXX_EGRESS_DIR_INGRESS:
ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_INGRESS_DEST;
break;
case MV88E6XXX_EGRESS_DIR_EGRESS:
ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_EGRESS_DEST;
break;
default:
return -EINVAL;
}
return mv88e6390_g1_monitor_write(chip, ptr, port);
}
int mv88e6390_g1_set_cpu_port(struct mv88e6xxx_chip *chip, int port)
{
u16 ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST;
/* Use the default high priority for management frames sent to
* the CPU.
*/
port |= MV88E6390_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST_MGMTPRI;
return mv88e6390_g1_monitor_write(chip, ptr, port);
}
int mv88e6390_g1_set_ptp_cpu_port(struct mv88e6xxx_chip *chip, int port)
{
u16 ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_PTP_CPU_DEST;
/* Use the default high priority for PTP frames sent to
* the CPU.
*/
port |= MV88E6390_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST_MGMTPRI;
return mv88e6390_g1_monitor_write(chip, ptr, port);
}
int mv88e6390_g1_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip)
{
u16 ptr;
int err;
/* 01:80:c2:00:00:00-01:80:c2:00:00:07 are Management */
ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200000XLO;
err = mv88e6390_g1_monitor_write(chip, ptr, 0xff);
if (err)
return err;
/* 01:80:c2:00:00:08-01:80:c2:00:00:0f are Management */
ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200000XHI;
err = mv88e6390_g1_monitor_write(chip, ptr, 0xff);
if (err)
return err;
/* 01:80:c2:00:00:20-01:80:c2:00:00:27 are Management */
ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200002XLO;
err = mv88e6390_g1_monitor_write(chip, ptr, 0xff);
if (err)
return err;
/* 01:80:c2:00:00:28-01:80:c2:00:00:2f are Management */
ptr = MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200002XHI;
err = mv88e6390_g1_monitor_write(chip, ptr, 0xff);
if (err)
return err;
return 0;
}
/* Offset 0x1c: Global Control 2 */
static int mv88e6xxx_g1_ctl2_mask(struct mv88e6xxx_chip *chip, u16 mask,
u16 val)
{
u16 reg;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_CTL2, &reg);
if (err)
return err;
reg &= ~mask;
reg |= val & mask;
return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_CTL2, reg);
}
int mv88e6185_g1_set_cascade_port(struct mv88e6xxx_chip *chip, int port)
{
const u16 mask = MV88E6185_G1_CTL2_CASCADE_PORT_MASK;
return mv88e6xxx_g1_ctl2_mask(chip, mask, port << __bf_shf(mask));
}
int mv88e6085_g1_rmu_disable(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g1_ctl2_mask(chip, MV88E6085_G1_CTL2_P10RM |
MV88E6085_G1_CTL2_RM_ENABLE, 0);
}
int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g1_ctl2_mask(chip, MV88E6352_G1_CTL2_RMU_MODE_MASK,
MV88E6352_G1_CTL2_RMU_MODE_DISABLED);
}
int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g1_ctl2_mask(chip, MV88E6390_G1_CTL2_RMU_MODE_MASK,
MV88E6390_G1_CTL2_RMU_MODE_DISABLED);
}
int mv88e6390_g1_stats_set_histogram(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g1_ctl2_mask(chip, MV88E6390_G1_CTL2_HIST_MODE_MASK,
MV88E6390_G1_CTL2_HIST_MODE_RX);
}
int mv88e6xxx_g1_set_device_number(struct mv88e6xxx_chip *chip, int index)
{
return mv88e6xxx_g1_ctl2_mask(chip,
MV88E6XXX_G1_CTL2_DEVICE_NUMBER_MASK,
index);
}
/* Offset 0x1d: Statistics Operation 2 */
static int mv88e6xxx_g1_stats_wait(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6XXX_G1_STATS_OP_BUSY);
return mv88e6xxx_g1_wait_bit(chip, MV88E6XXX_G1_STATS_OP, bit, 0);
}
int mv88e6095_g1_stats_set_histogram(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STATS_OP, &val);
if (err)
return err;
val |= MV88E6XXX_G1_STATS_OP_HIST_RX;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_STATS_OP, val);
return err;
}
int mv88e6xxx_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port)
{
int err;
/* Snapshot the hardware statistics counters for this port. */
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_STATS_OP,
MV88E6XXX_G1_STATS_OP_BUSY |
MV88E6XXX_G1_STATS_OP_CAPTURE_PORT |
MV88E6XXX_G1_STATS_OP_HIST_RX | port);
if (err)
return err;
/* Wait for the snapshotting to complete. */
return mv88e6xxx_g1_stats_wait(chip);
}
int mv88e6320_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port)
{
port = (port + 1) << 5;
return mv88e6xxx_g1_stats_snapshot(chip, port);
}
int mv88e6390_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port)
{
int err;
port = (port + 1) << 5;
/* Snapshot the hardware statistics counters for this port. */
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_STATS_OP,
MV88E6XXX_G1_STATS_OP_BUSY |
MV88E6XXX_G1_STATS_OP_CAPTURE_PORT | port);
if (err)
return err;
/* Wait for the snapshotting to complete. */
return mv88e6xxx_g1_stats_wait(chip);
}
void mv88e6xxx_g1_stats_read(struct mv88e6xxx_chip *chip, int stat, u32 *val)
{
u32 value;
u16 reg;
int err;
*val = 0;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_STATS_OP,
MV88E6XXX_G1_STATS_OP_BUSY |
MV88E6XXX_G1_STATS_OP_READ_CAPTURED | stat);
if (err)
return;
err = mv88e6xxx_g1_stats_wait(chip);
if (err)
return;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STATS_COUNTER_32, &reg);
if (err)
return;
value = reg << 16;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STATS_COUNTER_01, &reg);
if (err)
return;
*val = value | reg;
}
int mv88e6xxx_g1_stats_clear(struct mv88e6xxx_chip *chip)
{
int err;
u16 val;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_STATS_OP, &val);
if (err)
return err;
/* Keep the histogram mode bits */
val &= MV88E6XXX_G1_STATS_OP_HIST_RX_TX;
val |= MV88E6XXX_G1_STATS_OP_BUSY | MV88E6XXX_G1_STATS_OP_FLUSH_ALL;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_STATS_OP, val);
if (err)
return err;
/* Wait for the flush to complete. */
return mv88e6xxx_g1_stats_wait(chip);
}
@@ -0,0 +1,368 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx Switch Global (1) Registers support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2016-2017 Savoir-faire Linux Inc.
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*/
#ifndef _MV88E6XXX_GLOBAL1_H
#define _MV88E6XXX_GLOBAL1_H
#include "chip.h"
/* Offset 0x00: Switch Global Status Register */
#define MV88E6XXX_G1_STS 0x00
#define MV88E6352_G1_STS_PPU_STATE 0x8000
#define MV88E6185_G1_STS_PPU_STATE_MASK 0xc000
#define MV88E6185_G1_STS_PPU_STATE_DISABLED_RST 0x0000
#define MV88E6185_G1_STS_PPU_STATE_INITIALIZING 0x4000
#define MV88E6185_G1_STS_PPU_STATE_DISABLED 0x8000
#define MV88E6185_G1_STS_PPU_STATE_POLLING 0xc000
#define MV88E6XXX_G1_STS_INIT_READY 0x0800
#define MV88E6393X_G1_STS_IRQ_DEVICE_2 9
#define MV88E6XXX_G1_STS_IRQ_AVB 8
#define MV88E6XXX_G1_STS_IRQ_DEVICE 7
#define MV88E6XXX_G1_STS_IRQ_STATS 6
#define MV88E6XXX_G1_STS_IRQ_VTU_PROB 5
#define MV88E6XXX_G1_STS_IRQ_VTU_DONE 4
#define MV88E6XXX_G1_STS_IRQ_ATU_PROB 3
#define MV88E6XXX_G1_STS_IRQ_ATU_DONE 2
#define MV88E6XXX_G1_STS_IRQ_TCAM_DONE 1
#define MV88E6XXX_G1_STS_IRQ_EEPROM_DONE 0
/* Offset 0x01: Switch MAC Address Register Bytes 0 & 1
* Offset 0x02: Switch MAC Address Register Bytes 2 & 3
* Offset 0x03: Switch MAC Address Register Bytes 4 & 5
*/
#define MV88E6XXX_G1_MAC_01 0x01
#define MV88E6XXX_G1_MAC_23 0x02
#define MV88E6XXX_G1_MAC_45 0x03
/* Offset 0x01: ATU FID Register */
#define MV88E6352_G1_ATU_FID 0x01
/* Offset 0x02: VTU FID Register */
#define MV88E6352_G1_VTU_FID 0x02
#define MV88E6352_G1_VTU_FID_VID_POLICY 0x1000
#define MV88E6352_G1_VTU_FID_MASK 0x0fff
/* Offset 0x03: VTU SID Register */
#define MV88E6352_G1_VTU_SID 0x03
#define MV88E6352_G1_VTU_SID_MASK 0x3f
/* Offset 0x04: Switch Global Control Register */
#define MV88E6XXX_G1_CTL1 0x04
#define MV88E6XXX_G1_CTL1_SW_RESET 0x8000
#define MV88E6XXX_G1_CTL1_PPU_ENABLE 0x4000
#define MV88E6352_G1_CTL1_DISCARD_EXCESS 0x2000
#define MV88E6185_G1_CTL1_SCHED_PRIO 0x0800
#define MV88E6185_G1_CTL1_MAX_FRAME_1632 0x0400
#define MV88E6185_G1_CTL1_RELOAD_EEPROM 0x0200
#define MV88E6393X_G1_CTL1_DEVICE2_EN 0x0200
#define MV88E6XXX_G1_CTL1_DEVICE_EN 0x0080
#define MV88E6XXX_G1_CTL1_STATS_DONE_EN 0x0040
#define MV88E6XXX_G1_CTL1_VTU_PROBLEM_EN 0x0020
#define MV88E6XXX_G1_CTL1_VTU_DONE_EN 0x0010
#define MV88E6XXX_G1_CTL1_ATU_PROBLEM_EN 0x0008
#define MV88E6XXX_G1_CTL1_ATU_DONE_EN 0x0004
#define MV88E6XXX_G1_CTL1_TCAM_EN 0x0002
#define MV88E6XXX_G1_CTL1_EEPROM_DONE_EN 0x0001
/* Offset 0x05: VTU Operation Register */
#define MV88E6XXX_G1_VTU_OP 0x05
#define MV88E6XXX_G1_VTU_OP_BUSY 0x8000
#define MV88E6XXX_G1_VTU_OP_MASK 0x7000
#define MV88E6XXX_G1_VTU_OP_FLUSH_ALL 0x1000
#define MV88E6XXX_G1_VTU_OP_NOOP 0x2000
#define MV88E6XXX_G1_VTU_OP_VTU_LOAD_PURGE 0x3000
#define MV88E6XXX_G1_VTU_OP_VTU_GET_NEXT 0x4000
#define MV88E6XXX_G1_VTU_OP_STU_LOAD_PURGE 0x5000
#define MV88E6XXX_G1_VTU_OP_STU_GET_NEXT 0x6000
#define MV88E6XXX_G1_VTU_OP_GET_CLR_VIOLATION 0x7000
#define MV88E6XXX_G1_VTU_OP_MEMBER_VIOLATION BIT(6)
#define MV88E6XXX_G1_VTU_OP_MISS_VIOLATION BIT(5)
#define MV88E6XXX_G1_VTU_OP_SPID_MASK 0xf
/* Offset 0x06: VTU VID Register */
#define MV88E6XXX_G1_VTU_VID 0x06
#define MV88E6XXX_G1_VTU_VID_MASK 0x0fff
#define MV88E6390_G1_VTU_VID_PAGE 0x2000
#define MV88E6XXX_G1_VTU_VID_VALID 0x1000
/* Offset 0x07: VTU/STU Data Register 1
* Offset 0x08: VTU/STU Data Register 2
* Offset 0x09: VTU/STU Data Register 3
*/
#define MV88E6XXX_G1_VTU_DATA1 0x07
#define MV88E6XXX_G1_VTU_DATA2 0x08
#define MV88E6XXX_G1_VTU_DATA3 0x09
#define MV88E6XXX_G1_VTU_STU_DATA_MASK 0x0003
#define MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNMODIFIED 0x0000
#define MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNTAGGED 0x0001
#define MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_TAGGED 0x0002
#define MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_NON_MEMBER 0x0003
#define MV88E6XXX_G1_STU_DATA_PORT_STATE_DISABLED 0x0000
#define MV88E6XXX_G1_STU_DATA_PORT_STATE_BLOCKING 0x0001
#define MV88E6XXX_G1_STU_DATA_PORT_STATE_LEARNING 0x0002
#define MV88E6XXX_G1_STU_DATA_PORT_STATE_FORWARDING 0x0003
/* Offset 0x0A: ATU Control Register */
#define MV88E6XXX_G1_ATU_CTL 0x0a
#define MV88E6XXX_G1_ATU_CTL_LEARN2ALL 0x0008
#define MV88E6161_G1_ATU_CTL_HASH_MASK 0x0003
/* Offset 0x0B: ATU Operation Register */
#define MV88E6XXX_G1_ATU_OP 0x0b
#define MV88E6XXX_G1_ATU_OP_BUSY 0x8000
#define MV88E6XXX_G1_ATU_OP_MASK 0x7000
#define MV88E6XXX_G1_ATU_OP_NOOP 0x0000
#define MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_ALL 0x1000
#define MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_NON_STATIC 0x2000
#define MV88E6XXX_G1_ATU_OP_LOAD_DB 0x3000
#define MV88E6XXX_G1_ATU_OP_GET_NEXT_DB 0x4000
#define MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_ALL_DB 0x5000
#define MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_NON_STATIC_DB 0x6000
#define MV88E6XXX_G1_ATU_OP_GET_CLR_VIOLATION 0x7000
#define MV88E6XXX_G1_ATU_OP_AGE_OUT_VIOLATION BIT(7)
#define MV88E6XXX_G1_ATU_OP_MEMBER_VIOLATION BIT(6)
#define MV88E6XXX_G1_ATU_OP_MISS_VIOLATION BIT(5)
#define MV88E6XXX_G1_ATU_OP_FULL_VIOLATION BIT(4)
/* Offset 0x0C: ATU Data Register */
#define MV88E6XXX_G1_ATU_DATA 0x0c
#define MV88E6XXX_G1_ATU_DATA_TRUNK 0x8000
#define MV88E6XXX_G1_ATU_DATA_TRUNK_ID_MASK 0x00f0
#define MV88E6XXX_G1_ATU_DATA_PORT_VECTOR_MASK 0x3ff0
#define MV88E6XXX_G1_ATU_DATA_STATE_MASK 0x000f
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_UNUSED 0x0000
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_1_OLDEST 0x0001
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_2 0x0002
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_3 0x0003
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_4 0x0004
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_5 0x0005
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_6 0x0006
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_AGE_7_NEWEST 0x0007
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_POLICY 0x0008
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_POLICY_PO 0x0009
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL 0x000a
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_AVB_NRL_PO 0x000b
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_DA_MGMT 0x000c
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_DA_MGMT_PO 0x000d
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC 0x000e
#define MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC_PO 0x000f
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_UNUSED 0x0000
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_POLICY 0x0004
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_AVB_NRL 0x0005
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_DA_MGMT 0x0006
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC 0x0007
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_POLICY_PO 0x000c
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_AVB_NRL_PO 0x000d
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_DA_MGMT_PO 0x000e
#define MV88E6XXX_G1_ATU_DATA_STATE_MC_STATIC_PO 0x000f
/* Offset 0x0D: ATU MAC Address Register Bytes 0 & 1
* Offset 0x0E: ATU MAC Address Register Bytes 2 & 3
* Offset 0x0F: ATU MAC Address Register Bytes 4 & 5
*/
#define MV88E6XXX_G1_ATU_MAC01 0x0d
#define MV88E6XXX_G1_ATU_MAC23 0x0e
#define MV88E6XXX_G1_ATU_MAC45 0x0f
/* Offset 0x10: IP-PRI Mapping Register 0
* Offset 0x11: IP-PRI Mapping Register 1
* Offset 0x12: IP-PRI Mapping Register 2
* Offset 0x13: IP-PRI Mapping Register 3
* Offset 0x14: IP-PRI Mapping Register 4
* Offset 0x15: IP-PRI Mapping Register 5
* Offset 0x16: IP-PRI Mapping Register 6
* Offset 0x17: IP-PRI Mapping Register 7
*/
#define MV88E6XXX_G1_IP_PRI_0 0x10
#define MV88E6XXX_G1_IP_PRI_1 0x11
#define MV88E6XXX_G1_IP_PRI_2 0x12
#define MV88E6XXX_G1_IP_PRI_3 0x13
#define MV88E6XXX_G1_IP_PRI_4 0x14
#define MV88E6XXX_G1_IP_PRI_5 0x15
#define MV88E6XXX_G1_IP_PRI_6 0x16
#define MV88E6XXX_G1_IP_PRI_7 0x17
/* Offset 0x18: IEEE-PRI Register */
#define MV88E6XXX_G1_IEEE_PRI 0x18
/* Offset 0x19: Core Tag Type */
#define MV88E6185_G1_CORE_TAG_TYPE 0x19
/* Offset 0x1A: Monitor Control */
#define MV88E6185_G1_MONITOR_CTL 0x1a
#define MV88E6185_G1_MONITOR_CTL_INGRESS_DEST_MASK 0xf000
#define MV88E6185_G1_MONITOR_CTL_EGRESS_DEST_MASK 0x0f00
#define MV88E6185_G1_MONITOR_CTL_ARP_DEST_MASK 0x00f0
#define MV88E6352_G1_MONITOR_CTL_CPU_DEST_MASK 0x00f0
#define MV88E6352_G1_MONITOR_CTL_MIRROR_DEST_MASK 0x000f
/* Offset 0x1A: Monitor & MGMT Control Register */
#define MV88E6390_G1_MONITOR_MGMT_CTL 0x1a
#define MV88E6390_G1_MONITOR_MGMT_CTL_UPDATE 0x8000
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_MASK 0x3f00
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200000XLO 0x0000
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200000XHI 0x0100
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200002XLO 0x0200
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_0180C200002XHI 0x0300
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_INGRESS_DEST 0x2000
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_EGRESS_DEST 0x2100
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST 0x3000
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_PTP_CPU_DEST 0x3200
#define MV88E6390_G1_MONITOR_MGMT_CTL_PTR_CPU_DEST_MGMTPRI 0x00e0
#define MV88E6390_G1_MONITOR_MGMT_CTL_DATA_MASK 0x00ff
/* Offset 0x1C: Global Control 2 */
#define MV88E6XXX_G1_CTL2 0x1c
#define MV88E6185_G1_CTL2_CASCADE_PORT_MASK 0xf000
#define MV88E6185_G1_CTL2_CASCADE_PORT_NONE 0xe000
#define MV88E6185_G1_CTL2_CASCADE_PORT_MULTI 0xf000
#define MV88E6352_G1_CTL2_HEADER_TYPE_MASK 0xc000
#define MV88E6352_G1_CTL2_HEADER_TYPE_ORIG 0x0000
#define MV88E6352_G1_CTL2_HEADER_TYPE_MGMT 0x4000
#define MV88E6390_G1_CTL2_HEADER_TYPE_LAG 0x8000
#define MV88E6352_G1_CTL2_RMU_MODE_MASK 0x3000
#define MV88E6352_G1_CTL2_RMU_MODE_DISABLED 0x0000
#define MV88E6352_G1_CTL2_RMU_MODE_PORT_4 0x1000
#define MV88E6352_G1_CTL2_RMU_MODE_PORT_5 0x2000
#define MV88E6352_G1_CTL2_RMU_MODE_PORT_6 0x3000
#define MV88E6085_G1_CTL2_DA_CHECK 0x4000
#define MV88E6085_G1_CTL2_P10RM 0x2000
#define MV88E6085_G1_CTL2_RM_ENABLE 0x1000
#define MV88E6352_G1_CTL2_DA_CHECK 0x0800
#define MV88E6390_G1_CTL2_RMU_MODE_MASK 0x0700
#define MV88E6390_G1_CTL2_RMU_MODE_PORT_0 0x0000
#define MV88E6390_G1_CTL2_RMU_MODE_PORT_1 0x0100
#define MV88E6390_G1_CTL2_RMU_MODE_PORT_9 0x0200
#define MV88E6390_G1_CTL2_RMU_MODE_PORT_10 0x0300
#define MV88E6390_G1_CTL2_RMU_MODE_ALL_DSA 0x0600
#define MV88E6390_G1_CTL2_RMU_MODE_DISABLED 0x0700
#define MV88E6390_G1_CTL2_HIST_MODE_MASK 0x00c0
#define MV88E6390_G1_CTL2_HIST_MODE_RX 0x0040
#define MV88E6390_G1_CTL2_HIST_MODE_TX 0x0080
#define MV88E6352_G1_CTL2_CTR_MODE_MASK 0x0060
#define MV88E6390_G1_CTL2_CTR_MODE 0x0020
#define MV88E6XXX_G1_CTL2_DEVICE_NUMBER_MASK 0x001f
/* Offset 0x1D: Stats Operation Register */
#define MV88E6XXX_G1_STATS_OP 0x1d
#define MV88E6XXX_G1_STATS_OP_BUSY 0x8000
#define MV88E6XXX_G1_STATS_OP_NOP 0x0000
#define MV88E6XXX_G1_STATS_OP_FLUSH_ALL 0x1000
#define MV88E6XXX_G1_STATS_OP_FLUSH_PORT 0x2000
#define MV88E6XXX_G1_STATS_OP_READ_CAPTURED 0x4000
#define MV88E6XXX_G1_STATS_OP_CAPTURE_PORT 0x5000
#define MV88E6XXX_G1_STATS_OP_HIST_RX 0x0400
#define MV88E6XXX_G1_STATS_OP_HIST_TX 0x0800
#define MV88E6XXX_G1_STATS_OP_HIST_RX_TX 0x0c00
#define MV88E6XXX_G1_STATS_OP_BANK_1_BIT_9 0x0200
#define MV88E6XXX_G1_STATS_OP_BANK_1_BIT_10 0x0400
/* Offset 0x1E: Stats Counter Register Bytes 3 & 2
* Offset 0x1F: Stats Counter Register Bytes 1 & 0
*/
#define MV88E6XXX_G1_STATS_COUNTER_32 0x1e
#define MV88E6XXX_G1_STATS_COUNTER_01 0x1f
int mv88e6xxx_g1_read(struct mv88e6xxx_chip *chip, int reg, u16 *val);
int mv88e6xxx_g1_write(struct mv88e6xxx_chip *chip, int reg, u16 val);
int mv88e6xxx_g1_wait_bit(struct mv88e6xxx_chip *chip, int reg, int
bit, int val);
int mv88e6xxx_g1_wait_mask(struct mv88e6xxx_chip *chip, int reg,
u16 mask, u16 val);
int mv88e6xxx_g1_set_switch_mac(struct mv88e6xxx_chip *chip, u8 *addr);
int mv88e6185_g1_reset(struct mv88e6xxx_chip *chip);
int mv88e6352_g1_reset(struct mv88e6xxx_chip *chip);
int mv88e6250_g1_reset(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g1_wait_eeprom_done(struct mv88e6xxx_chip *chip);
int mv88e6250_g1_wait_eeprom_done_prereset(struct mv88e6xxx_chip *chip);
int mv88e6185_g1_ppu_enable(struct mv88e6xxx_chip *chip);
int mv88e6185_g1_ppu_disable(struct mv88e6xxx_chip *chip);
int mv88e6185_g1_set_max_frame_size(struct mv88e6xxx_chip *chip, int mtu);
int mv88e6xxx_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port);
int mv88e6320_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_g1_stats_snapshot(struct mv88e6xxx_chip *chip, int port);
int mv88e6095_g1_stats_set_histogram(struct mv88e6xxx_chip *chip);
int mv88e6390_g1_stats_set_histogram(struct mv88e6xxx_chip *chip);
void mv88e6xxx_g1_stats_read(struct mv88e6xxx_chip *chip, int stat, u32 *val);
int mv88e6xxx_g1_stats_clear(struct mv88e6xxx_chip *chip);
int mv88e6095_g1_set_egress_port(struct mv88e6xxx_chip *chip,
enum mv88e6xxx_egress_direction direction,
int port);
int mv88e6390_g1_set_egress_port(struct mv88e6xxx_chip *chip,
enum mv88e6xxx_egress_direction direction,
int port);
int mv88e6095_g1_set_cpu_port(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_g1_set_cpu_port(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_g1_set_ptp_cpu_port(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_g1_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip);
int mv88e6085_g1_ip_pri_map(struct mv88e6xxx_chip *chip);
int mv88e6085_g1_ieee_pri_map(struct mv88e6xxx_chip *chip);
int mv88e6250_g1_ieee_pri_map(struct mv88e6xxx_chip *chip);
int mv88e6185_g1_set_cascade_port(struct mv88e6xxx_chip *chip, int port);
int mv88e6085_g1_rmu_disable(struct mv88e6xxx_chip *chip);
int mv88e6352_g1_rmu_disable(struct mv88e6xxx_chip *chip);
int mv88e6390_g1_rmu_disable(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g1_set_device_number(struct mv88e6xxx_chip *chip, int index);
int mv88e6xxx_g1_atu_set_learn2all(struct mv88e6xxx_chip *chip, bool learn2all);
int mv88e6xxx_g1_atu_set_age_time(struct mv88e6xxx_chip *chip,
unsigned int msecs);
int mv88e6xxx_g1_atu_getnext(struct mv88e6xxx_chip *chip, u16 fid,
struct mv88e6xxx_atu_entry *entry);
int mv88e6xxx_g1_atu_loadpurge(struct mv88e6xxx_chip *chip, u16 fid,
struct mv88e6xxx_atu_entry *entry);
int mv88e6xxx_g1_atu_flush(struct mv88e6xxx_chip *chip, u16 fid, bool all);
int mv88e6xxx_g1_atu_remove(struct mv88e6xxx_chip *chip, u16 fid, int port,
bool all);
int mv88e6xxx_g1_atu_prob_irq_setup(struct mv88e6xxx_chip *chip);
void mv88e6xxx_g1_atu_prob_irq_free(struct mv88e6xxx_chip *chip);
int mv88e6165_g1_atu_get_hash(struct mv88e6xxx_chip *chip, u8 *hash);
int mv88e6165_g1_atu_set_hash(struct mv88e6xxx_chip *chip, u8 hash);
int mv88e6xxx_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
int mv88e6185_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
int mv88e6185_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
int mv88e6352_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
int mv88e6352_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
int mv88e6390_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
int mv88e6390_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry);
int mv88e6xxx_g1_vtu_flush(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g1_stu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry);
int mv88e6352_g1_stu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry);
int mv88e6352_g1_stu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry);
int mv88e6390_g1_stu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry);
int mv88e6390_g1_stu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry);
int mv88e6xxx_g1_vtu_prob_irq_setup(struct mv88e6xxx_chip *chip);
void mv88e6xxx_g1_vtu_prob_irq_free(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g1_atu_get_next(struct mv88e6xxx_chip *chip, u16 fid);
#endif /* _MV88E6XXX_GLOBAL1_H */
@@ -0,0 +1,501 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Address Translation Unit (ATU) support
*
* Copyright (c) 2008 Marvell Semiconductor
* Copyright (c) 2017 Savoir-faire Linux, Inc.
*/
#include <linux/bitfield.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include "chip.h"
#include "global1.h"
#include "switchdev.h"
#include "trace.h"
/* Offset 0x01: ATU FID Register */
static int mv88e6xxx_g1_atu_fid_write(struct mv88e6xxx_chip *chip, u16 fid)
{
return mv88e6xxx_g1_write(chip, MV88E6352_G1_ATU_FID, fid & 0xfff);
}
/* Offset 0x0A: ATU Control Register */
int mv88e6xxx_g1_atu_set_learn2all(struct mv88e6xxx_chip *chip, bool learn2all)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_CTL, &val);
if (err)
return err;
if (learn2all)
val |= MV88E6XXX_G1_ATU_CTL_LEARN2ALL;
else
val &= ~MV88E6XXX_G1_ATU_CTL_LEARN2ALL;
return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_CTL, val);
}
int mv88e6xxx_g1_atu_set_age_time(struct mv88e6xxx_chip *chip,
unsigned int msecs)
{
const unsigned int coeff = chip->info->age_time_coeff;
const unsigned int min = 0x01 * coeff;
const unsigned int max = 0xff * coeff;
u8 age_time;
u16 val;
int err;
if (msecs < min || msecs > max)
return -ERANGE;
/* Round to nearest multiple of coeff */
age_time = (msecs + coeff / 2) / coeff;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_CTL, &val);
if (err)
return err;
/* AgeTime is 11:4 bits */
val &= ~0xff0;
val |= age_time << 4;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_CTL, val);
if (err)
return err;
dev_dbg(chip->dev, "AgeTime set to 0x%02x (%d ms)\n", age_time,
age_time * coeff);
return 0;
}
int mv88e6165_g1_atu_get_hash(struct mv88e6xxx_chip *chip, u8 *hash)
{
int err;
u16 val;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_CTL, &val);
if (err)
return err;
*hash = val & MV88E6161_G1_ATU_CTL_HASH_MASK;
return 0;
}
int mv88e6165_g1_atu_set_hash(struct mv88e6xxx_chip *chip, u8 hash)
{
int err;
u16 val;
if (hash & ~MV88E6161_G1_ATU_CTL_HASH_MASK)
return -EINVAL;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_CTL, &val);
if (err)
return err;
val &= ~MV88E6161_G1_ATU_CTL_HASH_MASK;
val |= hash;
return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_CTL, val);
}
/* Offset 0x0B: ATU Operation Register */
static int mv88e6xxx_g1_atu_op_wait(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6XXX_G1_ATU_OP_BUSY);
return mv88e6xxx_g1_wait_bit(chip, MV88E6XXX_G1_ATU_OP, bit, 0);
}
static int mv88e6xxx_g1_read_atu_violation(struct mv88e6xxx_chip *chip)
{
int err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_OP,
MV88E6XXX_G1_ATU_OP_BUSY |
MV88E6XXX_G1_ATU_OP_GET_CLR_VIOLATION);
if (err)
return err;
return mv88e6xxx_g1_atu_op_wait(chip);
}
static int mv88e6xxx_g1_atu_op(struct mv88e6xxx_chip *chip, u16 fid, u16 op)
{
u16 val;
int err;
/* FID bits are dispatched all around gradually as more are supported */
if (mv88e6xxx_num_databases(chip) > 256) {
err = mv88e6xxx_g1_atu_fid_write(chip, fid);
if (err)
return err;
} else {
if (mv88e6xxx_num_databases(chip) > 64) {
/* ATU DBNum[7:4] are located in ATU Control 15:12 */
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_CTL,
&val);
if (err)
return err;
val = (val & 0x0fff) | ((fid << 8) & 0xf000);
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_CTL,
val);
if (err)
return err;
} else if (mv88e6xxx_num_databases(chip) > 16) {
/* ATU DBNum[5:4] are located in ATU Operation 9:8 */
op |= (fid & 0x30) << 4;
}
/* ATU DBNum[3:0] are located in ATU Operation 3:0 */
op |= fid & 0xf;
}
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_OP,
MV88E6XXX_G1_ATU_OP_BUSY | op);
if (err)
return err;
return mv88e6xxx_g1_atu_op_wait(chip);
}
int mv88e6xxx_g1_atu_get_next(struct mv88e6xxx_chip *chip, u16 fid)
{
return mv88e6xxx_g1_atu_op(chip, fid, MV88E6XXX_G1_ATU_OP_GET_NEXT_DB);
}
static int mv88e6xxx_g1_atu_fid_read(struct mv88e6xxx_chip *chip, u16 *fid)
{
u16 val = 0, upper = 0, op = 0;
int err = -EOPNOTSUPP;
if (mv88e6xxx_num_databases(chip) > 256) {
err = mv88e6xxx_g1_read(chip, MV88E6352_G1_ATU_FID, &val);
val &= 0xfff;
if (err)
return err;
} else {
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_OP, &op);
if (err)
return err;
if (mv88e6xxx_num_databases(chip) > 64) {
/* ATU DBNum[7:4] are located in ATU Control 15:12 */
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_CTL,
&upper);
if (err)
return err;
upper = (upper >> 8) & 0x00f0;
} else if (mv88e6xxx_num_databases(chip) > 16) {
/* ATU DBNum[5:4] are located in ATU Operation 9:8 */
upper = (op >> 4) & 0x30;
}
/* ATU DBNum[3:0] are located in ATU Operation 3:0 */
val = (op & 0xf) | upper;
}
*fid = val;
return err;
}
/* Offset 0x0C: ATU Data Register */
static int mv88e6xxx_g1_atu_data_read(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_atu_entry *entry)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_DATA, &val);
if (err)
return err;
entry->state = val & 0xf;
if (entry->state) {
entry->trunk = !!(val & MV88E6XXX_G1_ATU_DATA_TRUNK);
entry->portvec = (val >> 4) & mv88e6xxx_port_mask(chip);
}
return 0;
}
static int mv88e6xxx_g1_atu_data_write(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_atu_entry *entry)
{
u16 data = entry->state & 0xf;
if (entry->state) {
if (entry->trunk)
data |= MV88E6XXX_G1_ATU_DATA_TRUNK;
data |= (entry->portvec & mv88e6xxx_port_mask(chip)) << 4;
}
return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_DATA, data);
}
/* Offset 0x0D: ATU MAC Address Register Bytes 0 & 1
* Offset 0x0E: ATU MAC Address Register Bytes 2 & 3
* Offset 0x0F: ATU MAC Address Register Bytes 4 & 5
*/
static int mv88e6xxx_g1_atu_mac_read(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_atu_entry *entry)
{
u16 val;
int i, err;
for (i = 0; i < 3; i++) {
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_MAC01 + i, &val);
if (err)
return err;
entry->mac[i * 2] = val >> 8;
entry->mac[i * 2 + 1] = val & 0xff;
}
return 0;
}
static int mv88e6xxx_g1_atu_mac_write(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_atu_entry *entry)
{
u16 val;
int i, err;
for (i = 0; i < 3; i++) {
val = (entry->mac[i * 2] << 8) | entry->mac[i * 2 + 1];
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_ATU_MAC01 + i, val);
if (err)
return err;
}
return 0;
}
/* Address Translation Unit operations */
int mv88e6xxx_g1_atu_getnext(struct mv88e6xxx_chip *chip, u16 fid,
struct mv88e6xxx_atu_entry *entry)
{
int err;
err = mv88e6xxx_g1_atu_op_wait(chip);
if (err)
return err;
/* Write the MAC address to iterate from only once */
if (!entry->state) {
err = mv88e6xxx_g1_atu_mac_write(chip, entry);
if (err)
return err;
}
err = mv88e6xxx_g1_atu_op(chip, fid, MV88E6XXX_G1_ATU_OP_GET_NEXT_DB);
if (err)
return err;
err = mv88e6xxx_g1_atu_data_read(chip, entry);
if (err)
return err;
return mv88e6xxx_g1_atu_mac_read(chip, entry);
}
int mv88e6xxx_g1_atu_loadpurge(struct mv88e6xxx_chip *chip, u16 fid,
struct mv88e6xxx_atu_entry *entry)
{
int err;
err = mv88e6xxx_g1_atu_op_wait(chip);
if (err)
return err;
err = mv88e6xxx_g1_atu_mac_write(chip, entry);
if (err)
return err;
err = mv88e6xxx_g1_atu_data_write(chip, entry);
if (err)
return err;
return mv88e6xxx_g1_atu_op(chip, fid, MV88E6XXX_G1_ATU_OP_LOAD_DB);
}
static int mv88e6xxx_g1_atu_flushmove(struct mv88e6xxx_chip *chip, u16 fid,
struct mv88e6xxx_atu_entry *entry,
bool all)
{
u16 op;
int err;
err = mv88e6xxx_g1_atu_op_wait(chip);
if (err)
return err;
err = mv88e6xxx_g1_atu_data_write(chip, entry);
if (err)
return err;
/* Flush/Move all or non-static entries from all or a given database */
if (all && fid)
op = MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_ALL_DB;
else if (fid)
op = MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_NON_STATIC_DB;
else if (all)
op = MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_ALL;
else
op = MV88E6XXX_G1_ATU_OP_FLUSH_MOVE_NON_STATIC;
return mv88e6xxx_g1_atu_op(chip, fid, op);
}
int mv88e6xxx_g1_atu_flush(struct mv88e6xxx_chip *chip, u16 fid, bool all)
{
struct mv88e6xxx_atu_entry entry = {
.state = 0, /* Null EntryState means Flush */
};
return mv88e6xxx_g1_atu_flushmove(chip, fid, &entry, all);
}
static int mv88e6xxx_g1_atu_move(struct mv88e6xxx_chip *chip, u16 fid,
int from_port, int to_port, bool all)
{
struct mv88e6xxx_atu_entry entry = { 0 };
unsigned long mask;
int shift;
if (!chip->info->atu_move_port_mask)
return -EOPNOTSUPP;
mask = chip->info->atu_move_port_mask;
shift = bitmap_weight(&mask, 16);
entry.state = 0xf; /* Full EntryState means Move */
entry.portvec = from_port & mask;
entry.portvec |= (to_port & mask) << shift;
return mv88e6xxx_g1_atu_flushmove(chip, fid, &entry, all);
}
int mv88e6xxx_g1_atu_remove(struct mv88e6xxx_chip *chip, u16 fid, int port,
bool all)
{
int from_port = port;
int to_port = chip->info->atu_move_port_mask;
return mv88e6xxx_g1_atu_move(chip, fid, from_port, to_port, all);
}
static irqreturn_t mv88e6xxx_g1_atu_prob_irq_thread_fn(int irq, void *dev_id)
{
struct mv88e6xxx_chip *chip = dev_id;
struct mv88e6xxx_atu_entry entry;
int err, spid;
u16 val, fid;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_g1_read_atu_violation(chip);
if (err)
goto out_unlock;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_ATU_OP, &val);
if (err)
goto out_unlock;
err = mv88e6xxx_g1_atu_fid_read(chip, &fid);
if (err)
goto out_unlock;
err = mv88e6xxx_g1_atu_data_read(chip, &entry);
if (err)
goto out_unlock;
err = mv88e6xxx_g1_atu_mac_read(chip, &entry);
if (err)
goto out_unlock;
mv88e6xxx_reg_unlock(chip);
spid = entry.state;
if (val & MV88E6XXX_G1_ATU_OP_MEMBER_VIOLATION) {
trace_mv88e6xxx_atu_member_violation(chip->dev, spid,
entry.portvec, entry.mac,
fid);
chip->ports[spid].atu_member_violation++;
}
if (val & MV88E6XXX_G1_ATU_OP_MISS_VIOLATION) {
trace_mv88e6xxx_atu_miss_violation(chip->dev, spid,
entry.portvec, entry.mac,
fid);
chip->ports[spid].atu_miss_violation++;
if (fid != MV88E6XXX_FID_STANDALONE && chip->ports[spid].mab) {
err = mv88e6xxx_handle_miss_violation(chip, spid,
&entry, fid);
if (err)
goto out;
}
}
if (val & MV88E6XXX_G1_ATU_OP_FULL_VIOLATION) {
trace_mv88e6xxx_atu_full_violation(chip->dev, spid,
entry.portvec, entry.mac,
fid);
if (spid < ARRAY_SIZE(chip->ports))
chip->ports[spid].atu_full_violation++;
}
return IRQ_HANDLED;
out_unlock:
mv88e6xxx_reg_unlock(chip);
out:
dev_err(chip->dev, "ATU problem: error %d while handling interrupt\n",
err);
return IRQ_HANDLED;
}
int mv88e6xxx_g1_atu_prob_irq_setup(struct mv88e6xxx_chip *chip)
{
int err;
chip->atu_prob_irq = irq_find_mapping(chip->g1_irq.domain,
MV88E6XXX_G1_STS_IRQ_ATU_PROB);
if (chip->atu_prob_irq < 0)
return chip->atu_prob_irq;
snprintf(chip->atu_prob_irq_name, sizeof(chip->atu_prob_irq_name),
"mv88e6xxx-%s-g1-atu-prob", dev_name(chip->dev));
err = request_threaded_irq(chip->atu_prob_irq, NULL,
mv88e6xxx_g1_atu_prob_irq_thread_fn,
IRQF_ONESHOT, chip->atu_prob_irq_name,
chip);
if (err)
irq_dispose_mapping(chip->atu_prob_irq);
return err;
}
void mv88e6xxx_g1_atu_prob_irq_free(struct mv88e6xxx_chip *chip)
{
free_irq(chip->atu_prob_irq, chip);
irq_dispose_mapping(chip->atu_prob_irq);
}
@@ -0,0 +1,683 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx VLAN [Spanning Tree] Translation Unit (VTU [STU]) support
*
* Copyright (c) 2008 Marvell Semiconductor
* Copyright (c) 2015 CMC Electronics, Inc.
* Copyright (c) 2017 Savoir-faire Linux, Inc.
*/
#include <linux/bitfield.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include "chip.h"
#include "global1.h"
#include "trace.h"
/* Offset 0x02: VTU FID Register */
static int mv88e6xxx_g1_vtu_fid_read(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_FID, &val);
if (err)
return err;
entry->fid = val & MV88E6352_G1_VTU_FID_MASK;
entry->policy = !!(val & MV88E6352_G1_VTU_FID_VID_POLICY);
return 0;
}
static int mv88e6xxx_g1_vtu_fid_write(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
u16 val = entry->fid & MV88E6352_G1_VTU_FID_MASK;
if (entry->policy)
val |= MV88E6352_G1_VTU_FID_VID_POLICY;
return mv88e6xxx_g1_write(chip, MV88E6352_G1_VTU_FID, val);
}
/* Offset 0x03: VTU SID Register */
static int mv88e6xxx_g1_vtu_sid_read(struct mv88e6xxx_chip *chip, u8 *sid)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6352_G1_VTU_SID, &val);
if (err)
return err;
*sid = val & MV88E6352_G1_VTU_SID_MASK;
return 0;
}
static int mv88e6xxx_g1_vtu_sid_write(struct mv88e6xxx_chip *chip, u8 sid)
{
u16 val = sid & MV88E6352_G1_VTU_SID_MASK;
return mv88e6xxx_g1_write(chip, MV88E6352_G1_VTU_SID, val);
}
/* Offset 0x05: VTU Operation Register */
static int mv88e6xxx_g1_vtu_op_wait(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6XXX_G1_VTU_OP_BUSY);
return mv88e6xxx_g1_wait_bit(chip, MV88E6XXX_G1_VTU_OP, bit, 0);
}
static int mv88e6xxx_g1_vtu_op(struct mv88e6xxx_chip *chip, u16 op)
{
int err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_VTU_OP,
MV88E6XXX_G1_VTU_OP_BUSY | op);
if (err)
return err;
return mv88e6xxx_g1_vtu_op_wait(chip);
}
/* Offset 0x06: VTU VID Register */
static int mv88e6xxx_g1_vtu_vid_read(struct mv88e6xxx_chip *chip,
bool *valid, u16 *vid)
{
u16 val;
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_VID, &val);
if (err)
return err;
if (vid) {
*vid = val & 0xfff;
if (val & MV88E6390_G1_VTU_VID_PAGE)
*vid |= 0x1000;
}
if (valid)
*valid = !!(val & MV88E6XXX_G1_VTU_VID_VALID);
return 0;
}
static int mv88e6xxx_g1_vtu_vid_write(struct mv88e6xxx_chip *chip,
bool valid, u16 vid)
{
u16 val = vid & 0xfff;
if (vid & 0x1000)
val |= MV88E6390_G1_VTU_VID_PAGE;
if (valid)
val |= MV88E6XXX_G1_VTU_VID_VALID;
return mv88e6xxx_g1_write(chip, MV88E6XXX_G1_VTU_VID, val);
}
/* Offset 0x07: VTU/STU Data Register 1
* Offset 0x08: VTU/STU Data Register 2
* Offset 0x09: VTU/STU Data Register 3
*/
static int mv88e6185_g1_vtu_stu_data_read(struct mv88e6xxx_chip *chip,
u16 *regs)
{
int i;
/* Read all 3 VTU/STU Data registers */
for (i = 0; i < 3; ++i) {
u16 *reg = &regs[i];
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1 + i, reg);
if (err)
return err;
}
return 0;
}
static int mv88e6185_g1_vtu_data_read(struct mv88e6xxx_chip *chip,
u8 *member, u8 *state)
{
u16 regs[3];
int err;
int i;
err = mv88e6185_g1_vtu_stu_data_read(chip, regs);
if (err)
return err;
/* Extract MemberTag data */
for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
unsigned int member_offset = (i % 4) * 4;
unsigned int state_offset = member_offset + 2;
if (member)
member[i] = (regs[i / 4] >> member_offset) & 0x3;
if (state)
state[i] = (regs[i / 4] >> state_offset) & 0x3;
}
return 0;
}
static int mv88e6185_g1_vtu_data_write(struct mv88e6xxx_chip *chip,
u8 *member, u8 *state)
{
u16 regs[3] = { 0 };
int i;
/* Insert MemberTag and PortState data */
for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
unsigned int member_offset = (i % 4) * 4;
unsigned int state_offset = member_offset + 2;
if (member)
regs[i / 4] |= (member[i] & 0x3) << member_offset;
if (state)
regs[i / 4] |= (state[i] & 0x3) << state_offset;
}
/* Write all 3 VTU/STU Data registers */
for (i = 0; i < 3; ++i) {
u16 reg = regs[i];
int err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_VTU_DATA1 + i, reg);
if (err)
return err;
}
return 0;
}
static int mv88e6390_g1_vtu_data_read(struct mv88e6xxx_chip *chip, u8 *data)
{
u16 regs[2];
int i;
/* Read the 2 VTU/STU Data registers */
for (i = 0; i < 2; ++i) {
u16 *reg = &regs[i];
int err;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_DATA1 + i, reg);
if (err)
return err;
}
/* Extract data */
for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
unsigned int offset = (i % 8) * 2;
data[i] = (regs[i / 8] >> offset) & 0x3;
}
return 0;
}
static int mv88e6390_g1_vtu_data_write(struct mv88e6xxx_chip *chip, u8 *data)
{
u16 regs[2] = { 0 };
int i;
/* Insert data */
for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
unsigned int offset = (i % 8) * 2;
regs[i / 8] |= (data[i] & 0x3) << offset;
}
/* Write the 2 VTU/STU Data registers */
for (i = 0; i < 2; ++i) {
u16 reg = regs[i];
int err;
err = mv88e6xxx_g1_write(chip, MV88E6XXX_G1_VTU_DATA1 + i, reg);
if (err)
return err;
}
return 0;
}
/* VLAN Translation Unit Operations */
int mv88e6xxx_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
int err;
err = mv88e6xxx_g1_vtu_op_wait(chip);
if (err)
return err;
/* To get the next higher active VID, the VTU GetNext operation can be
* started again without setting the VID registers since it already
* contains the last VID.
*
* To save a few hardware accesses and abstract this to the caller,
* write the VID only once, when the entry is given as invalid.
*/
if (!entry->valid) {
err = mv88e6xxx_g1_vtu_vid_write(chip, false, entry->vid);
if (err)
return err;
}
err = mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_VTU_GET_NEXT);
if (err)
return err;
return mv88e6xxx_g1_vtu_vid_read(chip, &entry->valid, &entry->vid);
}
int mv88e6185_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
u16 val;
int err;
err = mv88e6xxx_g1_vtu_getnext(chip, entry);
if (err)
return err;
if (entry->valid) {
err = mv88e6185_g1_vtu_data_read(chip, entry->member, entry->state);
if (err)
return err;
/* VTU DBNum[3:0] are located in VTU Operation 3:0
* VTU DBNum[7:4] ([5:4] for 6250) are located in VTU Operation 11:8 (9:8)
*/
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_OP, &val);
if (err)
return err;
entry->fid = val & 0x000f;
entry->fid |= (val & 0x0f00) >> 4;
entry->fid &= mv88e6xxx_num_databases(chip) - 1;
}
return 0;
}
int mv88e6352_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
int err;
/* Fetch VLAN MemberTag data from the VTU */
err = mv88e6xxx_g1_vtu_getnext(chip, entry);
if (err)
return err;
if (entry->valid) {
err = mv88e6185_g1_vtu_data_read(chip, entry->member, NULL);
if (err)
return err;
err = mv88e6xxx_g1_vtu_fid_read(chip, entry);
if (err)
return err;
err = mv88e6xxx_g1_vtu_sid_read(chip, &entry->sid);
if (err)
return err;
}
return 0;
}
int mv88e6390_g1_vtu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
int err;
/* Fetch VLAN MemberTag data from the VTU */
err = mv88e6xxx_g1_vtu_getnext(chip, entry);
if (err)
return err;
if (entry->valid) {
err = mv88e6390_g1_vtu_data_read(chip, entry->member);
if (err)
return err;
err = mv88e6xxx_g1_vtu_fid_read(chip, entry);
if (err)
return err;
err = mv88e6xxx_g1_vtu_sid_read(chip, &entry->sid);
if (err)
return err;
}
return 0;
}
int mv88e6185_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
u16 op = MV88E6XXX_G1_VTU_OP_VTU_LOAD_PURGE;
int err;
err = mv88e6xxx_g1_vtu_op_wait(chip);
if (err)
return err;
err = mv88e6xxx_g1_vtu_vid_write(chip, entry->valid, entry->vid);
if (err)
return err;
if (entry->valid) {
err = mv88e6185_g1_vtu_data_write(chip, entry->member, entry->state);
if (err)
return err;
/* VTU DBNum[3:0] are located in VTU Operation 3:0
* VTU DBNum[7:4] are located in VTU Operation 11:8
*
* For the 6250/6220, the latter are really [5:4] and
* 9:8, but in those cases bits 7:6 of entry->fid are
* 0 since they have num_databases = 64.
*/
op |= entry->fid & 0x000f;
op |= (entry->fid & 0x00f0) << 4;
}
return mv88e6xxx_g1_vtu_op(chip, op);
}
int mv88e6352_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
int err;
err = mv88e6xxx_g1_vtu_op_wait(chip);
if (err)
return err;
err = mv88e6xxx_g1_vtu_vid_write(chip, entry->valid, entry->vid);
if (err)
return err;
if (entry->valid) {
/* Write MemberTag data */
err = mv88e6185_g1_vtu_data_write(chip, entry->member, NULL);
if (err)
return err;
err = mv88e6xxx_g1_vtu_fid_write(chip, entry);
if (err)
return err;
err = mv88e6xxx_g1_vtu_sid_write(chip, entry->sid);
if (err)
return err;
}
/* Load/Purge VTU entry */
return mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_VTU_LOAD_PURGE);
}
int mv88e6390_g1_vtu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_vtu_entry *entry)
{
int err;
err = mv88e6xxx_g1_vtu_op_wait(chip);
if (err)
return err;
err = mv88e6xxx_g1_vtu_vid_write(chip, entry->valid, entry->vid);
if (err)
return err;
if (entry->valid) {
/* Write MemberTag data */
err = mv88e6390_g1_vtu_data_write(chip, entry->member);
if (err)
return err;
err = mv88e6xxx_g1_vtu_fid_write(chip, entry);
if (err)
return err;
err = mv88e6xxx_g1_vtu_sid_write(chip, entry->sid);
if (err)
return err;
}
/* Load/Purge VTU entry */
return mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_VTU_LOAD_PURGE);
}
int mv88e6xxx_g1_vtu_flush(struct mv88e6xxx_chip *chip)
{
int err;
/* As part of the VTU flush, refresh FID map */
bitmap_zero(chip->fid_bitmap, MV88E6XXX_N_FID);
err = mv88e6xxx_g1_vtu_op_wait(chip);
if (err)
return err;
return mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_FLUSH_ALL);
}
/* Spanning Tree Unit Operations */
int mv88e6xxx_g1_stu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry)
{
int err;
err = mv88e6xxx_g1_vtu_op_wait(chip);
if (err)
return err;
/* To get the next higher active SID, the STU GetNext operation can be
* started again without setting the SID registers since it already
* contains the last SID.
*
* To save a few hardware accesses and abstract this to the caller,
* write the SID only once, when the entry is given as invalid.
*/
if (!entry->valid) {
err = mv88e6xxx_g1_vtu_sid_write(chip, entry->sid);
if (err)
return err;
}
err = mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_STU_GET_NEXT);
if (err)
return err;
err = mv88e6xxx_g1_vtu_vid_read(chip, &entry->valid, NULL);
if (err)
return err;
if (entry->valid) {
err = mv88e6xxx_g1_vtu_sid_read(chip, &entry->sid);
if (err)
return err;
}
return 0;
}
int mv88e6352_g1_stu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry)
{
int err;
err = mv88e6xxx_g1_stu_getnext(chip, entry);
if (err)
return err;
if (!entry->valid)
return 0;
return mv88e6185_g1_vtu_data_read(chip, NULL, entry->state);
}
int mv88e6390_g1_stu_getnext(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry)
{
int err;
err = mv88e6xxx_g1_stu_getnext(chip, entry);
if (err)
return err;
if (!entry->valid)
return 0;
return mv88e6390_g1_vtu_data_read(chip, entry->state);
}
int mv88e6352_g1_stu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry)
{
int err;
err = mv88e6xxx_g1_vtu_op_wait(chip);
if (err)
return err;
err = mv88e6xxx_g1_vtu_vid_write(chip, entry->valid, 0);
if (err)
return err;
err = mv88e6xxx_g1_vtu_sid_write(chip, entry->sid);
if (err)
return err;
if (entry->valid) {
err = mv88e6185_g1_vtu_data_write(chip, NULL, entry->state);
if (err)
return err;
}
/* Load/Purge STU entry */
return mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_STU_LOAD_PURGE);
}
int mv88e6390_g1_stu_loadpurge(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_stu_entry *entry)
{
int err;
err = mv88e6xxx_g1_vtu_op_wait(chip);
if (err)
return err;
err = mv88e6xxx_g1_vtu_vid_write(chip, entry->valid, 0);
if (err)
return err;
err = mv88e6xxx_g1_vtu_sid_write(chip, entry->sid);
if (err)
return err;
if (entry->valid) {
err = mv88e6390_g1_vtu_data_write(chip, entry->state);
if (err)
return err;
}
/* Load/Purge STU entry */
return mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_STU_LOAD_PURGE);
}
/* VTU Violation Management */
static irqreturn_t mv88e6xxx_g1_vtu_prob_irq_thread_fn(int irq, void *dev_id)
{
struct mv88e6xxx_chip *chip = dev_id;
u16 val, vid;
int spid;
int err;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_g1_vtu_op(chip, MV88E6XXX_G1_VTU_OP_GET_CLR_VIOLATION);
if (err)
goto out;
err = mv88e6xxx_g1_read(chip, MV88E6XXX_G1_VTU_OP, &val);
if (err)
goto out;
err = mv88e6xxx_g1_vtu_vid_read(chip, NULL, &vid);
if (err)
goto out;
spid = val & MV88E6XXX_G1_VTU_OP_SPID_MASK;
if (val & MV88E6XXX_G1_VTU_OP_MEMBER_VIOLATION) {
trace_mv88e6xxx_vtu_member_violation(chip->dev, spid, vid);
chip->ports[spid].vtu_member_violation++;
}
if (val & MV88E6XXX_G1_VTU_OP_MISS_VIOLATION) {
trace_mv88e6xxx_vtu_miss_violation(chip->dev, spid, vid);
chip->ports[spid].vtu_miss_violation++;
}
mv88e6xxx_reg_unlock(chip);
return IRQ_HANDLED;
out:
mv88e6xxx_reg_unlock(chip);
dev_err(chip->dev, "VTU problem: error %d while handling interrupt\n",
err);
return IRQ_HANDLED;
}
int mv88e6xxx_g1_vtu_prob_irq_setup(struct mv88e6xxx_chip *chip)
{
int err;
chip->vtu_prob_irq = irq_find_mapping(chip->g1_irq.domain,
MV88E6XXX_G1_STS_IRQ_VTU_PROB);
if (chip->vtu_prob_irq < 0)
return chip->vtu_prob_irq;
snprintf(chip->vtu_prob_irq_name, sizeof(chip->vtu_prob_irq_name),
"mv88e6xxx-%s-g1-vtu-prob", dev_name(chip->dev));
err = request_threaded_irq(chip->vtu_prob_irq, NULL,
mv88e6xxx_g1_vtu_prob_irq_thread_fn,
IRQF_ONESHOT, chip->vtu_prob_irq_name,
chip);
if (err)
irq_dispose_mapping(chip->vtu_prob_irq);
return err;
}
void mv88e6xxx_g1_vtu_prob_irq_free(struct mv88e6xxx_chip *chip)
{
free_irq(chip->vtu_prob_irq, chip);
irq_dispose_mapping(chip->vtu_prob_irq);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,390 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx Switch Global 2 Registers support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2016-2017 Savoir-faire Linux Inc.
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*/
#ifndef _MV88E6XXX_GLOBAL2_H
#define _MV88E6XXX_GLOBAL2_H
#include "chip.h"
/* Offset 0x00: Interrupt Source Register */
#define MV88E6XXX_G2_INT_SRC 0x00
#define MV88E6XXX_G2_INT_SRC_WDOG 0x8000
#define MV88E6XXX_G2_INT_SRC_JAM_LIMIT 0x4000
#define MV88E6XXX_G2_INT_SRC_DUPLEX_MISMATCH 0x2000
#define MV88E6XXX_G2_INT_SRC_WAKE_EVENT 0x1000
#define MV88E6352_G2_INT_SRC_SERDES 0x0800
#define MV88E6352_G2_INT_SRC_PHY 0x001f
#define MV88E6390_G2_INT_SRC_PHY 0x07fe
#define MV88E6XXX_G2_INT_SOURCE_WATCHDOG 15
/* Offset 0x01: Interrupt Mask Register */
#define MV88E6XXX_G2_INT_MASK 0x01
#define MV88E6XXX_G2_INT_MASK_WDOG 0x8000
#define MV88E6XXX_G2_INT_MASK_JAM_LIMIT 0x4000
#define MV88E6XXX_G2_INT_MASK_DUPLEX_MISMATCH 0x2000
#define MV88E6XXX_G2_INT_MASK_WAKE_EVENT 0x1000
#define MV88E6352_G2_INT_MASK_SERDES 0x0800
#define MV88E6352_G2_INT_MASK_PHY 0x001f
#define MV88E6390_G2_INT_MASK_PHY 0x07fe
/* Offset 0x02: MGMT Enable Register 2x */
#define MV88E6XXX_G2_MGMT_EN_2X 0x02
/* Offset 0x02: MAC LINK change IRQ Register for MV88E6393X */
#define MV88E6393X_G2_MACLINK_INT_SRC 0x02
/* Offset 0x03: MGMT Enable Register 0x */
#define MV88E6XXX_G2_MGMT_EN_0X 0x03
/* Offset 0x03: MAC LINK change IRQ Mask Register for MV88E6393X */
#define MV88E6393X_G2_MACLINK_INT_MASK 0x03
/* Offset 0x04: Flow Control Delay Register */
#define MV88E6XXX_G2_FLOW_CTL 0x04
/* Offset 0x05: Switch Management Register */
#define MV88E6XXX_G2_SWITCH_MGMT 0x05
#define MV88E6XXX_G2_SWITCH_MGMT_USE_DOUBLE_TAG_DATA 0x8000
#define MV88E6XXX_G2_SWITCH_MGMT_PREVENT_LOOPS 0x4000
#define MV88E6XXX_G2_SWITCH_MGMT_FLOW_CTL_MSG 0x2000
#define MV88E6XXX_G2_SWITCH_MGMT_FORCE_FLOW_CTL_PRI 0x0080
#define MV88E6XXX_G2_SWITCH_MGMT_RSVD2CPU 0x0008
#define MV88E6393X_G2_EGRESS_MONITOR_DEST 0x05
/* Offset 0x06: Device Mapping Table Register */
#define MV88E6XXX_G2_DEVICE_MAPPING 0x06
#define MV88E6XXX_G2_DEVICE_MAPPING_UPDATE 0x8000
#define MV88E6XXX_G2_DEVICE_MAPPING_DEV_MASK 0x1f00
#define MV88E6352_G2_DEVICE_MAPPING_PORT_MASK 0x000f
#define MV88E6390_G2_DEVICE_MAPPING_PORT_MASK 0x001f
/* Offset 0x07: Trunk Mask Table Register */
#define MV88E6XXX_G2_TRUNK_MASK 0x07
#define MV88E6XXX_G2_TRUNK_MASK_UPDATE 0x8000
#define MV88E6XXX_G2_TRUNK_MASK_NUM_MASK 0x7000
#define MV88E6XXX_G2_TRUNK_MASK_HASH 0x0800
/* Offset 0x08: Trunk Mapping Table Register */
#define MV88E6XXX_G2_TRUNK_MAPPING 0x08
#define MV88E6XXX_G2_TRUNK_MAPPING_UPDATE 0x8000
#define MV88E6XXX_G2_TRUNK_MAPPING_ID_MASK 0x7800
/* Offset 0x09: Ingress Rate Command Register */
#define MV88E6XXX_G2_IRL_CMD 0x09
#define MV88E6XXX_G2_IRL_CMD_BUSY 0x8000
#define MV88E6352_G2_IRL_CMD_OP_MASK 0x7000
#define MV88E6352_G2_IRL_CMD_OP_NOOP 0x0000
#define MV88E6352_G2_IRL_CMD_OP_INIT_ALL 0x1000
#define MV88E6352_G2_IRL_CMD_OP_INIT_RES 0x2000
#define MV88E6352_G2_IRL_CMD_OP_WRITE_REG 0x3000
#define MV88E6352_G2_IRL_CMD_OP_READ_REG 0x4000
#define MV88E6390_G2_IRL_CMD_OP_MASK 0x6000
#define MV88E6390_G2_IRL_CMD_OP_READ_REG 0x0000
#define MV88E6390_G2_IRL_CMD_OP_INIT_ALL 0x2000
#define MV88E6390_G2_IRL_CMD_OP_INIT_RES 0x4000
#define MV88E6390_G2_IRL_CMD_OP_WRITE_REG 0x6000
#define MV88E6352_G2_IRL_CMD_PORT_MASK 0x0f00
#define MV88E6390_G2_IRL_CMD_PORT_MASK 0x1f00
#define MV88E6XXX_G2_IRL_CMD_RES_MASK 0x00e0
#define MV88E6XXX_G2_IRL_CMD_REG_MASK 0x000f
/* Offset 0x0A: Ingress Rate Data Register */
#define MV88E6XXX_G2_IRL_DATA 0x0a
#define MV88E6XXX_G2_IRL_DATA_MASK 0xffff
/* Offset 0x0B: Cross-chip Port VLAN Register */
#define MV88E6XXX_G2_PVT_ADDR 0x0b
#define MV88E6XXX_G2_PVT_ADDR_BUSY 0x8000
#define MV88E6XXX_G2_PVT_ADDR_OP_MASK 0x7000
#define MV88E6XXX_G2_PVT_ADDR_OP_INIT_ONES 0x1000
#define MV88E6XXX_G2_PVT_ADDR_OP_WRITE_PVLAN 0x3000
#define MV88E6XXX_G2_PVT_ADDR_OP_READ 0x4000
#define MV88E6XXX_G2_PVT_ADDR_PTR_MASK 0x01ff
#define MV88E6XXX_G2_PVT_ADDR_DEV_TRUNK 0x1f
/* Offset 0x0C: Cross-chip Port VLAN Data Register */
#define MV88E6XXX_G2_PVT_DATA 0x0c
#define MV88E6XXX_G2_PVT_DATA_MASK 0x7f
/* Offset 0x0D: Switch MAC/WoL/WoF Register */
#define MV88E6XXX_G2_SWITCH_MAC 0x0d
#define MV88E6XXX_G2_SWITCH_MAC_UPDATE 0x8000
#define MV88E6XXX_G2_SWITCH_MAC_PTR_MASK 0x1f00
#define MV88E6XXX_G2_SWITCH_MAC_DATA_MASK 0x00ff
/* Offset 0x0E: ATU Stats Register */
#define MV88E6XXX_G2_ATU_STATS 0x0e
#define MV88E6XXX_G2_ATU_STATS_BIN_0 (0x0 << 14)
#define MV88E6XXX_G2_ATU_STATS_BIN_1 (0x1 << 14)
#define MV88E6XXX_G2_ATU_STATS_BIN_2 (0x2 << 14)
#define MV88E6XXX_G2_ATU_STATS_BIN_3 (0x3 << 14)
#define MV88E6XXX_G2_ATU_STATS_MODE_ALL (0x0 << 12)
#define MV88E6XXX_G2_ATU_STATS_MODE_ALL_DYNAMIC (0x1 << 12)
#define MV88E6XXX_G2_ATU_STATS_MODE_FID_ALL (0x2 << 12)
#define MV88E6XXX_G2_ATU_STATS_MODE_FID_ALL_DYNAMIC (0x3 << 12)
#define MV88E6XXX_G2_ATU_STATS_MASK 0x0fff
/* Offset 0x0F: Priority Override Table */
#define MV88E6XXX_G2_PRIO_OVERRIDE 0x0f
#define MV88E6XXX_G2_PRIO_OVERRIDE_UPDATE 0x8000
#define MV88E6XXX_G2_PRIO_OVERRIDE_FPRISET 0x1000
#define MV88E6XXX_G2_PRIO_OVERRIDE_PTR_MASK 0x0f00
#define MV88E6352_G2_PRIO_OVERRIDE_QPRIAVBEN 0x0080
#define MV88E6352_G2_PRIO_OVERRIDE_DATAAVB_MASK 0x0030
#define MV88E6XXX_G2_PRIO_OVERRIDE_QFPRIEN 0x0008
#define MV88E6XXX_G2_PRIO_OVERRIDE_DATA_MASK 0x0007
/* Offset 0x14: EEPROM Command */
#define MV88E6XXX_G2_EEPROM_CMD 0x14
#define MV88E6XXX_G2_EEPROM_CMD_BUSY 0x8000
#define MV88E6XXX_G2_EEPROM_CMD_OP_MASK 0x7000
#define MV88E6XXX_G2_EEPROM_CMD_OP_WRITE 0x3000
#define MV88E6XXX_G2_EEPROM_CMD_OP_READ 0x4000
#define MV88E6XXX_G2_EEPROM_CMD_OP_LOAD 0x6000
#define MV88E6XXX_G2_EEPROM_CMD_RUNNING 0x0800
#define MV88E6XXX_G2_EEPROM_CMD_WRITE_EN 0x0400
#define MV88E6352_G2_EEPROM_CMD_ADDR_MASK 0x00ff
#define MV88E6390_G2_EEPROM_CMD_DATA_MASK 0x00ff
/* Offset 0x15: EEPROM Data */
#define MV88E6352_G2_EEPROM_DATA 0x15
#define MV88E6352_G2_EEPROM_DATA_MASK 0xffff
/* Offset 0x15: EEPROM Addr */
#define MV88E6390_G2_EEPROM_ADDR 0x15
#define MV88E6390_G2_EEPROM_ADDR_MASK 0xffff
/* Offset 0x16: AVB Command Register */
#define MV88E6352_G2_AVB_CMD 0x16
#define MV88E6352_G2_AVB_CMD_BUSY 0x8000
#define MV88E6352_G2_AVB_CMD_OP_READ 0x4000
#define MV88E6352_G2_AVB_CMD_OP_READ_INCR 0x6000
#define MV88E6352_G2_AVB_CMD_OP_WRITE 0x3000
#define MV88E6390_G2_AVB_CMD_OP_READ 0x0000
#define MV88E6390_G2_AVB_CMD_OP_READ_INCR 0x4000
#define MV88E6390_G2_AVB_CMD_OP_WRITE 0x6000
#define MV88E6352_G2_AVB_CMD_PORT_MASK 0x0f00
#define MV88E6352_G2_AVB_CMD_PORT_TAIGLOBAL 0xe
#define MV88E6165_G2_AVB_CMD_PORT_PTPGLOBAL 0xf
#define MV88E6352_G2_AVB_CMD_PORT_PTPGLOBAL 0xf
#define MV88E6390_G2_AVB_CMD_PORT_MASK 0x1f00
#define MV88E6390_G2_AVB_CMD_PORT_TAIGLOBAL 0x1e
#define MV88E6390_G2_AVB_CMD_PORT_PTPGLOBAL 0x1f
#define MV88E6352_G2_AVB_CMD_BLOCK_PTP 0
#define MV88E6352_G2_AVB_CMD_BLOCK_AVB 1
#define MV88E6352_G2_AVB_CMD_BLOCK_QAV 2
#define MV88E6352_G2_AVB_CMD_BLOCK_QVB 3
#define MV88E6352_G2_AVB_CMD_BLOCK_MASK 0x00e0
#define MV88E6352_G2_AVB_CMD_ADDR_MASK 0x001f
/* Offset 0x17: AVB Data Register */
#define MV88E6352_G2_AVB_DATA 0x17
/* Offset 0x18: SMI PHY Command Register */
#define MV88E6XXX_G2_SMI_PHY_CMD 0x18
#define MV88E6XXX_G2_SMI_PHY_CMD_BUSY 0x8000
#define MV88E6390_G2_SMI_PHY_CMD_FUNC_MASK 0x6000
#define MV88E6390_G2_SMI_PHY_CMD_FUNC_INTERNAL 0x0000
#define MV88E6390_G2_SMI_PHY_CMD_FUNC_EXTERNAL 0x2000
#define MV88E6390_G2_SMI_PHY_CMD_FUNC_SETUP 0x4000
#define MV88E6XXX_G2_SMI_PHY_CMD_MODE_MASK 0x1000
#define MV88E6XXX_G2_SMI_PHY_CMD_MODE_45 0x0000
#define MV88E6XXX_G2_SMI_PHY_CMD_MODE_22 0x1000
#define MV88E6XXX_G2_SMI_PHY_CMD_OP_MASK 0x0c00
#define MV88E6XXX_G2_SMI_PHY_CMD_OP_22_WRITE_DATA 0x0400
#define MV88E6XXX_G2_SMI_PHY_CMD_OP_22_READ_DATA 0x0800
#define MV88E6XXX_G2_SMI_PHY_CMD_OP_45_WRITE_ADDR 0x0000
#define MV88E6XXX_G2_SMI_PHY_CMD_OP_45_WRITE_DATA 0x0400
#define MV88E6XXX_G2_SMI_PHY_CMD_OP_45_READ_DATA_INC 0x0800
#define MV88E6XXX_G2_SMI_PHY_CMD_OP_45_READ_DATA 0x0c00
#define MV88E6XXX_G2_SMI_PHY_CMD_DEV_ADDR_MASK 0x03e0
#define MV88E6XXX_G2_SMI_PHY_CMD_REG_ADDR_MASK 0x001f
#define MV88E6XXX_G2_SMI_PHY_CMD_SETUP_PTR_MASK 0x03ff
/* Offset 0x19: SMI PHY Data Register */
#define MV88E6XXX_G2_SMI_PHY_DATA 0x19
/* Offset 0x1A: Scratch and Misc. Register */
#define MV88E6XXX_G2_SCRATCH_MISC_MISC 0x1a
#define MV88E6XXX_G2_SCRATCH_MISC_UPDATE 0x8000
#define MV88E6XXX_G2_SCRATCH_MISC_PTR_MASK 0x7f00
#define MV88E6XXX_G2_SCRATCH_MISC_DATA_MASK 0x00ff
/* Offset 0x1B: Watch Dog Control Register */
#define MV88E6250_G2_WDOG_CTL 0x1b
#define MV88E6250_G2_WDOG_CTL_QC_HISTORY 0x0100
#define MV88E6250_G2_WDOG_CTL_QC_EVENT 0x0080
#define MV88E6250_G2_WDOG_CTL_QC_ENABLE 0x0040
#define MV88E6250_G2_WDOG_CTL_EGRESS_HISTORY 0x0020
#define MV88E6250_G2_WDOG_CTL_EGRESS_EVENT 0x0010
#define MV88E6250_G2_WDOG_CTL_EGRESS_ENABLE 0x0008
#define MV88E6250_G2_WDOG_CTL_FORCE_IRQ 0x0004
#define MV88E6250_G2_WDOG_CTL_HISTORY 0x0002
#define MV88E6250_G2_WDOG_CTL_SWRESET 0x0001
/* Offset 0x1B: Watch Dog Control Register */
#define MV88E6352_G2_WDOG_CTL 0x1b
#define MV88E6352_G2_WDOG_CTL_EGRESS_EVENT 0x0080
#define MV88E6352_G2_WDOG_CTL_RMU_TIMEOUT 0x0040
#define MV88E6352_G2_WDOG_CTL_QC_ENABLE 0x0020
#define MV88E6352_G2_WDOG_CTL_EGRESS_HISTORY 0x0010
#define MV88E6352_G2_WDOG_CTL_EGRESS_ENABLE 0x0008
#define MV88E6352_G2_WDOG_CTL_FORCE_IRQ 0x0004
#define MV88E6352_G2_WDOG_CTL_HISTORY 0x0002
#define MV88E6352_G2_WDOG_CTL_SWRESET 0x0001
/* Offset 0x1B: Watch Dog Control Register */
#define MV88E6390_G2_WDOG_CTL 0x1b
#define MV88E6390_G2_WDOG_CTL_UPDATE 0x8000
#define MV88E6390_G2_WDOG_CTL_PTR_MASK 0x7f00
#define MV88E6390_G2_WDOG_CTL_PTR_INT_SOURCE 0x0000
#define MV88E6390_G2_WDOG_CTL_PTR_INT_STS 0x1000
#define MV88E6390_G2_WDOG_CTL_PTR_INT_ENABLE 0x1100
#define MV88E6390_G2_WDOG_CTL_PTR_EVENT 0x1200
#define MV88E6390_G2_WDOG_CTL_PTR_HISTORY 0x1300
#define MV88E6390_G2_WDOG_CTL_DATA_MASK 0x00ff
#define MV88E6390_G2_WDOG_CTL_CUT_THROUGH 0x0008
#define MV88E6390_G2_WDOG_CTL_QUEUE_CONTROLLER 0x0004
#define MV88E6390_G2_WDOG_CTL_EGRESS 0x0002
#define MV88E6390_G2_WDOG_CTL_FORCE_IRQ 0x0001
/* Offset 0x1C: QoS Weights Register */
#define MV88E6XXX_G2_QOS_WEIGHTS 0x1c
#define MV88E6XXX_G2_QOS_WEIGHTS_UPDATE 0x8000
#define MV88E6352_G2_QOS_WEIGHTS_PTR_MASK 0x3f00
#define MV88E6390_G2_QOS_WEIGHTS_PTR_MASK 0x7f00
#define MV88E6XXX_G2_QOS_WEIGHTS_DATA_MASK 0x00ff
/* Offset 0x1D: Misc Register */
#define MV88E6XXX_G2_MISC 0x1d
#define MV88E6XXX_G2_MISC_5_BIT_PORT 0x4000
#define MV88E6352_G2_NOEGR_POLICY 0x2000
#define MV88E6390_G2_LAG_ID_4 0x2000
/* Scratch/Misc registers accessed through MV88E6XXX_G2_SCRATCH_MISC */
/* Offset 0x02: Misc Configuration */
#define MV88E6352_G2_SCRATCH_MISC_CFG 0x02
#define MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI 0x80
/* Offset 0x60-0x61: GPIO Configuration */
#define MV88E6352_G2_SCRATCH_GPIO_CFG0 0x60
#define MV88E6352_G2_SCRATCH_GPIO_CFG1 0x61
/* Offset 0x62-0x63: GPIO Direction */
#define MV88E6352_G2_SCRATCH_GPIO_DIR0 0x62
#define MV88E6352_G2_SCRATCH_GPIO_DIR1 0x63
#define MV88E6352_G2_SCRATCH_GPIO_DIR_OUT 0
#define MV88E6352_G2_SCRATCH_GPIO_DIR_IN 1
/* Offset 0x64-0x65: GPIO Data */
#define MV88E6352_G2_SCRATCH_GPIO_DATA0 0x64
#define MV88E6352_G2_SCRATCH_GPIO_DATA1 0x65
/* Offset 0x68-0x6F: GPIO Pin Control */
#define MV88E6352_G2_SCRATCH_GPIO_PCTL0 0x68
#define MV88E6352_G2_SCRATCH_GPIO_PCTL1 0x69
#define MV88E6352_G2_SCRATCH_GPIO_PCTL2 0x6A
#define MV88E6352_G2_SCRATCH_GPIO_PCTL3 0x6B
#define MV88E6352_G2_SCRATCH_GPIO_PCTL4 0x6C
#define MV88E6352_G2_SCRATCH_GPIO_PCTL5 0x6D
#define MV88E6352_G2_SCRATCH_GPIO_PCTL6 0x6E
#define MV88E6352_G2_SCRATCH_GPIO_PCTL7 0x6F
#define MV88E6352_G2_SCRATCH_CONFIG_DATA0 0x70
#define MV88E6352_G2_SCRATCH_CONFIG_DATA1 0x71
#define MV88E6352_G2_SCRATCH_CONFIG_DATA1_NO_CPU BIT(2)
#define MV88E6352_G2_SCRATCH_CONFIG_DATA2 0x72
#define MV88E6352_G2_SCRATCH_CONFIG_DATA2_P0_MODE_MASK 0xf
#define MV88E6352_G2_SCRATCH_CONFIG_DATA3 0x73
#define MV88E6352_G2_SCRATCH_CONFIG_DATA3_S_SEL BIT(1)
#define MV88E6352_G2_SCRATCH_GPIO_PCTL_GPIO 0
#define MV88E6352_G2_SCRATCH_GPIO_PCTL_TRIG 1
#define MV88E6352_G2_SCRATCH_GPIO_PCTL_EVREQ 2
int mv88e6xxx_g2_read(struct mv88e6xxx_chip *chip, int reg, u16 *val);
int mv88e6xxx_g2_write(struct mv88e6xxx_chip *chip, int reg, u16 val);
int mv88e6xxx_g2_wait_bit(struct mv88e6xxx_chip *chip, int reg,
int bit, int val);
int mv88e6352_g2_irl_init_all(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_g2_irl_init_all(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_g2_smi_phy_read_c22(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int reg, u16 *val);
int mv88e6xxx_g2_smi_phy_write_c22(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int reg, u16 val);
int mv88e6xxx_g2_smi_phy_read_c45(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int devad, int reg, u16 *val);
int mv88e6xxx_g2_smi_phy_write_c45(struct mv88e6xxx_chip *chip,
struct mii_bus *bus,
int addr, int devad, int reg, u16 val);
int mv88e6xxx_g2_set_switch_mac(struct mv88e6xxx_chip *chip, u8 *addr);
int mv88e6xxx_g2_get_eeprom8(struct mv88e6xxx_chip *chip,
struct ethtool_eeprom *eeprom, u8 *data);
int mv88e6xxx_g2_set_eeprom8(struct mv88e6xxx_chip *chip,
struct ethtool_eeprom *eeprom, u8 *data);
int mv88e6xxx_g2_get_eeprom16(struct mv88e6xxx_chip *chip,
struct ethtool_eeprom *eeprom, u8 *data);
int mv88e6xxx_g2_set_eeprom16(struct mv88e6xxx_chip *chip,
struct ethtool_eeprom *eeprom, u8 *data);
int mv88e6xxx_g2_pvt_read(struct mv88e6xxx_chip *chip, int src_dev,
int src_port, u16 *data);
int mv88e6xxx_g2_pvt_write(struct mv88e6xxx_chip *chip, int src_dev,
int src_port, u16 data);
int mv88e6xxx_g2_misc_4_bit_port(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g2_irq_setup(struct mv88e6xxx_chip *chip);
void mv88e6xxx_g2_irq_free(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g2_irq_mdio_setup(struct mv88e6xxx_chip *chip,
struct mii_bus *bus);
void mv88e6xxx_g2_irq_mdio_free(struct mv88e6xxx_chip *chip,
struct mii_bus *bus);
int mv88e6185_g2_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip);
int mv88e6352_g2_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g2_pot_clear(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g2_trunk_mask_write(struct mv88e6xxx_chip *chip, int num,
bool hash, u16 mask);
int mv88e6xxx_g2_trunk_mapping_write(struct mv88e6xxx_chip *chip, int id,
u16 map);
int mv88e6xxx_g2_trunk_clear(struct mv88e6xxx_chip *chip);
int mv88e6xxx_g2_device_mapping_write(struct mv88e6xxx_chip *chip, int target,
int port);
int mv88e6xxx_g2_eeprom_wait(struct mv88e6xxx_chip *chip);
extern const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops;
extern const struct mv88e6xxx_irq_ops mv88e6250_watchdog_ops;
extern const struct mv88e6xxx_irq_ops mv88e6390_watchdog_ops;
extern const struct mv88e6xxx_irq_ops mv88e6393x_watchdog_ops;
extern const struct mv88e6xxx_avb_ops mv88e6165_avb_ops;
extern const struct mv88e6xxx_avb_ops mv88e6352_avb_ops;
extern const struct mv88e6xxx_avb_ops mv88e6390_avb_ops;
extern const struct mv88e6xxx_gpio_ops mv88e6352_gpio_ops;
int mv88e6390_g2_scratch_gpio_set_smi(struct mv88e6xxx_chip *chip,
bool external);
int mv88e6393x_g2_scratch_gpio_set_smi(struct mv88e6xxx_chip *chip,
bool external);
int mv88e6352_g2_cache_global_scratch_config3(struct mv88e6xxx_chip *chip);
int mv88e6352_g2_scratch_port_has_serdes(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_g2_atu_stats_set(struct mv88e6xxx_chip *chip, u16 kind, u16 bin);
int mv88e6xxx_g2_atu_stats_get(struct mv88e6xxx_chip *chip, u16 *stats);
#endif /* _MV88E6XXX_GLOBAL2_H */
@@ -0,0 +1,239 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch Global 2 Registers support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2016-2017 Savoir-faire Linux Inc.
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*
* Copyright (c) 2017 National Instruments
* Brandon Streiff <brandon.streiff@ni.com>
*/
#include <linux/bitfield.h>
#include "global2.h"
/* Offset 0x16: AVB Command Register
* Offset 0x17: AVB Data Register
*
* There are two different versions of this register interface:
* "6352": 3-bit "op" field, 4-bit "port" field.
* "6390": 2-bit "op" field, 5-bit "port" field.
*
* The "op" codes are different between the two, as well as the special
* port fields for global PTP and TAI configuration.
*/
/* mv88e6xxx_g2_avb_read -- Read one or multiple 16-bit words.
* The hardware supports snapshotting up to four contiguous registers.
*/
static int mv88e6xxx_g2_avb_wait(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6352_G2_AVB_CMD_BUSY);
return mv88e6xxx_g2_wait_bit(chip, MV88E6352_G2_AVB_CMD, bit, 0);
}
static int mv88e6xxx_g2_avb_read(struct mv88e6xxx_chip *chip, u16 readop,
u16 *data, int len)
{
int err;
int i;
err = mv88e6xxx_g2_avb_wait(chip);
if (err)
return err;
/* Hardware can only snapshot four words. */
if (len > 4)
return -E2BIG;
err = mv88e6xxx_g2_write(chip, MV88E6352_G2_AVB_CMD,
MV88E6352_G2_AVB_CMD_BUSY | readop);
if (err)
return err;
err = mv88e6xxx_g2_avb_wait(chip);
if (err)
return err;
for (i = 0; i < len; ++i) {
err = mv88e6xxx_g2_read(chip, MV88E6352_G2_AVB_DATA,
&data[i]);
if (err)
return err;
}
return 0;
}
/* mv88e6xxx_g2_avb_write -- Write one 16-bit word. */
static int mv88e6xxx_g2_avb_write(struct mv88e6xxx_chip *chip, u16 writeop,
u16 data)
{
int err;
err = mv88e6xxx_g2_avb_wait(chip);
if (err)
return err;
err = mv88e6xxx_g2_write(chip, MV88E6352_G2_AVB_DATA, data);
if (err)
return err;
err = mv88e6xxx_g2_write(chip, MV88E6352_G2_AVB_CMD,
MV88E6352_G2_AVB_CMD_BUSY | writeop);
return mv88e6xxx_g2_avb_wait(chip);
}
static int mv88e6352_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip,
int port, int addr, u16 *data,
int len)
{
u16 readop = (len == 1 ? MV88E6352_G2_AVB_CMD_OP_READ :
MV88E6352_G2_AVB_CMD_OP_READ_INCR) |
(port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) |
addr;
return mv88e6xxx_g2_avb_read(chip, readop, data, len);
}
static int mv88e6352_g2_avb_port_ptp_write(struct mv88e6xxx_chip *chip,
int port, int addr, u16 data)
{
u16 writeop = MV88E6352_G2_AVB_CMD_OP_WRITE | (port << 8) |
(MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) | addr;
return mv88e6xxx_g2_avb_write(chip, writeop, data);
}
static int mv88e6352_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data, int len)
{
return mv88e6352_g2_avb_port_ptp_read(chip,
MV88E6352_G2_AVB_CMD_PORT_PTPGLOBAL,
addr, data, len);
}
static int mv88e6352_g2_avb_ptp_write(struct mv88e6xxx_chip *chip, int addr,
u16 data)
{
return mv88e6352_g2_avb_port_ptp_write(chip,
MV88E6352_G2_AVB_CMD_PORT_PTPGLOBAL,
addr, data);
}
static int mv88e6352_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data, int len)
{
return mv88e6352_g2_avb_port_ptp_read(chip,
MV88E6352_G2_AVB_CMD_PORT_TAIGLOBAL,
addr, data, len);
}
static int mv88e6352_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
u16 data)
{
return mv88e6352_g2_avb_port_ptp_write(chip,
MV88E6352_G2_AVB_CMD_PORT_TAIGLOBAL,
addr, data);
}
const struct mv88e6xxx_avb_ops mv88e6352_avb_ops = {
.port_ptp_read = mv88e6352_g2_avb_port_ptp_read,
.port_ptp_write = mv88e6352_g2_avb_port_ptp_write,
.ptp_read = mv88e6352_g2_avb_ptp_read,
.ptp_write = mv88e6352_g2_avb_ptp_write,
.tai_read = mv88e6352_g2_avb_tai_read,
.tai_write = mv88e6352_g2_avb_tai_write,
};
static int mv88e6165_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data, int len)
{
return mv88e6352_g2_avb_port_ptp_read(chip,
MV88E6165_G2_AVB_CMD_PORT_PTPGLOBAL,
addr, data, len);
}
static int mv88e6165_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
u16 data)
{
return mv88e6352_g2_avb_port_ptp_write(chip,
MV88E6165_G2_AVB_CMD_PORT_PTPGLOBAL,
addr, data);
}
const struct mv88e6xxx_avb_ops mv88e6165_avb_ops = {
.port_ptp_read = mv88e6352_g2_avb_port_ptp_read,
.port_ptp_write = mv88e6352_g2_avb_port_ptp_write,
.ptp_read = mv88e6352_g2_avb_ptp_read,
.ptp_write = mv88e6352_g2_avb_ptp_write,
.tai_read = mv88e6165_g2_avb_tai_read,
.tai_write = mv88e6165_g2_avb_tai_write,
};
static int mv88e6390_g2_avb_port_ptp_read(struct mv88e6xxx_chip *chip,
int port, int addr, u16 *data,
int len)
{
u16 readop = (len == 1 ? MV88E6390_G2_AVB_CMD_OP_READ :
MV88E6390_G2_AVB_CMD_OP_READ_INCR) |
(port << 8) | (MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) |
addr;
return mv88e6xxx_g2_avb_read(chip, readop, data, len);
}
static int mv88e6390_g2_avb_port_ptp_write(struct mv88e6xxx_chip *chip,
int port, int addr, u16 data)
{
u16 writeop = MV88E6390_G2_AVB_CMD_OP_WRITE | (port << 8) |
(MV88E6352_G2_AVB_CMD_BLOCK_PTP << 5) | addr;
return mv88e6xxx_g2_avb_write(chip, writeop, data);
}
static int mv88e6390_g2_avb_ptp_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data, int len)
{
return mv88e6390_g2_avb_port_ptp_read(chip,
MV88E6390_G2_AVB_CMD_PORT_PTPGLOBAL,
addr, data, len);
}
static int mv88e6390_g2_avb_ptp_write(struct mv88e6xxx_chip *chip, int addr,
u16 data)
{
return mv88e6390_g2_avb_port_ptp_write(chip,
MV88E6390_G2_AVB_CMD_PORT_PTPGLOBAL,
addr, data);
}
static int mv88e6390_g2_avb_tai_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data, int len)
{
return mv88e6390_g2_avb_port_ptp_read(chip,
MV88E6390_G2_AVB_CMD_PORT_TAIGLOBAL,
addr, data, len);
}
static int mv88e6390_g2_avb_tai_write(struct mv88e6xxx_chip *chip, int addr,
u16 data)
{
return mv88e6390_g2_avb_port_ptp_write(chip,
MV88E6390_G2_AVB_CMD_PORT_TAIGLOBAL,
addr, data);
}
const struct mv88e6xxx_avb_ops mv88e6390_avb_ops = {
.port_ptp_read = mv88e6390_g2_avb_port_ptp_read,
.port_ptp_write = mv88e6390_g2_avb_port_ptp_write,
.ptp_read = mv88e6390_g2_avb_ptp_read,
.ptp_write = mv88e6390_g2_avb_ptp_write,
.tai_read = mv88e6390_g2_avb_tai_read,
.tai_write = mv88e6390_g2_avb_tai_write,
};
@@ -0,0 +1,370 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch Global 2 Scratch & Misc Registers support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 National Instruments
* Brandon Streiff <brandon.streiff@ni.com>
*/
#include "chip.h"
#include "global2.h"
/* Offset 0x1A: Scratch and Misc. Register */
static int mv88e6xxx_g2_scratch_read(struct mv88e6xxx_chip *chip, int reg,
u8 *data)
{
u16 value;
int err;
err = mv88e6xxx_g2_write(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC,
reg << 8);
if (err)
return err;
err = mv88e6xxx_g2_read(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC, &value);
if (err)
return err;
*data = (value & MV88E6XXX_G2_SCRATCH_MISC_DATA_MASK);
return 0;
}
static int mv88e6xxx_g2_scratch_write(struct mv88e6xxx_chip *chip, int reg,
u8 data)
{
u16 value = (reg << 8) | data;
return mv88e6xxx_g2_write(chip, MV88E6XXX_G2_SCRATCH_MISC_MISC,
MV88E6XXX_G2_SCRATCH_MISC_UPDATE | value);
}
/**
* mv88e6xxx_g2_scratch_get_bit - get a bit
* @chip: chip private data
* @base_reg: base of scratch bits
* @offset: index of bit within the register
* @set: is bit set?
*/
static int mv88e6xxx_g2_scratch_get_bit(struct mv88e6xxx_chip *chip,
int base_reg, unsigned int offset,
int *set)
{
int reg = base_reg + (offset / 8);
u8 mask = (1 << (offset & 0x7));
u8 val;
int err;
err = mv88e6xxx_g2_scratch_read(chip, reg, &val);
if (err)
return err;
*set = !!(mask & val);
return 0;
}
/**
* mv88e6xxx_g2_scratch_set_bit - set (or clear) a bit
* @chip: chip private data
* @base_reg: base of scratch bits
* @offset: index of bit within the register
* @set: should this bit be set?
*
* Helper function for dealing with the direction and data registers.
*/
static int mv88e6xxx_g2_scratch_set_bit(struct mv88e6xxx_chip *chip,
int base_reg, unsigned int offset,
int set)
{
int reg = base_reg + (offset / 8);
u8 mask = (1 << (offset & 0x7));
u8 val;
int err;
err = mv88e6xxx_g2_scratch_read(chip, reg, &val);
if (err)
return err;
if (set)
val |= mask;
else
val &= ~mask;
return mv88e6xxx_g2_scratch_write(chip, reg, val);
}
/**
* mv88e6352_g2_scratch_gpio_get_data - get data on gpio pin
* @chip: chip private data
* @pin: gpio index
*
* Return: 0 for low, 1 for high, negative error
*/
static int mv88e6352_g2_scratch_gpio_get_data(struct mv88e6xxx_chip *chip,
unsigned int pin)
{
int val = 0;
int err;
err = mv88e6xxx_g2_scratch_get_bit(chip,
MV88E6352_G2_SCRATCH_GPIO_DATA0,
pin, &val);
if (err)
return err;
return val;
}
/**
* mv88e6352_g2_scratch_gpio_set_data - set data on gpio pin
* @chip: chip private data
* @pin: gpio index
* @value: value to set
*/
static int mv88e6352_g2_scratch_gpio_set_data(struct mv88e6xxx_chip *chip,
unsigned int pin, int value)
{
u8 mask = (1 << (pin & 0x7));
int offset = (pin / 8);
int reg;
reg = MV88E6352_G2_SCRATCH_GPIO_DATA0 + offset;
if (value)
chip->gpio_data[offset] |= mask;
else
chip->gpio_data[offset] &= ~mask;
return mv88e6xxx_g2_scratch_write(chip, reg, chip->gpio_data[offset]);
}
/**
* mv88e6352_g2_scratch_gpio_get_dir - get direction of gpio pin
* @chip: chip private data
* @pin: gpio index
*
* Return: 0 for output, 1 for input.
*/
static int mv88e6352_g2_scratch_gpio_get_dir(struct mv88e6xxx_chip *chip,
unsigned int pin)
{
int val = 0;
int err;
err = mv88e6xxx_g2_scratch_get_bit(chip,
MV88E6352_G2_SCRATCH_GPIO_DIR0,
pin, &val);
if (err)
return err;
return val;
}
/**
* mv88e6352_g2_scratch_gpio_set_dir - set direction of gpio pin
* @chip: chip private data
* @pin: gpio index
* @input: should the gpio be an input, or an output?
*/
static int mv88e6352_g2_scratch_gpio_set_dir(struct mv88e6xxx_chip *chip,
unsigned int pin, bool input)
{
int value = (input ? MV88E6352_G2_SCRATCH_GPIO_DIR_IN :
MV88E6352_G2_SCRATCH_GPIO_DIR_OUT);
return mv88e6xxx_g2_scratch_set_bit(chip,
MV88E6352_G2_SCRATCH_GPIO_DIR0,
pin, value);
}
/**
* mv88e6352_g2_scratch_gpio_get_pctl - get pin control setting
* @chip: chip private data
* @pin: gpio index
* @func: function number
*
* Note that the function numbers themselves may vary by chipset.
*/
static int mv88e6352_g2_scratch_gpio_get_pctl(struct mv88e6xxx_chip *chip,
unsigned int pin, int *func)
{
int reg = MV88E6352_G2_SCRATCH_GPIO_PCTL0 + (pin / 2);
int offset = (pin & 0x1) ? 4 : 0;
u8 mask = (0x7 << offset);
int err;
u8 val;
err = mv88e6xxx_g2_scratch_read(chip, reg, &val);
if (err)
return err;
*func = (val & mask) >> offset;
return 0;
}
/**
* mv88e6352_g2_scratch_gpio_set_pctl - set pin control setting
* @chip: chip private data
* @pin: gpio index
* @func: function number
*/
static int mv88e6352_g2_scratch_gpio_set_pctl(struct mv88e6xxx_chip *chip,
unsigned int pin, int func)
{
int reg = MV88E6352_G2_SCRATCH_GPIO_PCTL0 + (pin / 2);
int offset = (pin & 0x1) ? 4 : 0;
u8 mask = (0x7 << offset);
int err;
u8 val;
err = mv88e6xxx_g2_scratch_read(chip, reg, &val);
if (err)
return err;
val = (val & ~mask) | ((func & mask) << offset);
return mv88e6xxx_g2_scratch_write(chip, reg, val);
}
const struct mv88e6xxx_gpio_ops mv88e6352_gpio_ops = {
.get_data = mv88e6352_g2_scratch_gpio_get_data,
.set_data = mv88e6352_g2_scratch_gpio_set_data,
.get_dir = mv88e6352_g2_scratch_gpio_get_dir,
.set_dir = mv88e6352_g2_scratch_gpio_set_dir,
.get_pctl = mv88e6352_g2_scratch_gpio_get_pctl,
.set_pctl = mv88e6352_g2_scratch_gpio_set_pctl,
};
/**
* mv88e6390_g2_scratch_gpio_set_smi - set gpio muxing for external smi
* @chip: chip private data
* @external: set mux for external smi, or free for gpio usage
*
* Some mv88e6xxx models have GPIO pins that may be configured as
* an external SMI interface, or they may be made free for other
* GPIO uses.
*/
int mv88e6390_g2_scratch_gpio_set_smi(struct mv88e6xxx_chip *chip,
bool external)
{
int misc_cfg = MV88E6352_G2_SCRATCH_MISC_CFG;
int config_data1 = MV88E6352_G2_SCRATCH_CONFIG_DATA1;
int config_data2 = MV88E6352_G2_SCRATCH_CONFIG_DATA2;
bool no_cpu;
u8 p0_mode;
int err;
u8 val;
err = mv88e6xxx_g2_scratch_read(chip, config_data2, &val);
if (err)
return err;
p0_mode = val & MV88E6352_G2_SCRATCH_CONFIG_DATA2_P0_MODE_MASK;
if (p0_mode == 0x01 || p0_mode == 0x02)
return -EBUSY;
err = mv88e6xxx_g2_scratch_read(chip, config_data1, &val);
if (err)
return err;
no_cpu = !!(val & MV88E6352_G2_SCRATCH_CONFIG_DATA1_NO_CPU);
err = mv88e6xxx_g2_scratch_read(chip, misc_cfg, &val);
if (err)
return err;
/* NO_CPU being 0 inverts the meaning of the bit */
if (!no_cpu)
external = !external;
if (external)
val |= MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI;
else
val &= ~MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI;
return mv88e6xxx_g2_scratch_write(chip, misc_cfg, val);
}
/**
* mv88e6393x_g2_scratch_gpio_set_smi - set gpio muxing for external smi
* @chip: chip private data
* @external: set mux for external smi, or free for gpio usage
*
* MV88E6191X/6193X/6393X GPIO pins 9 and 10 can be configured as an
* external SMI interface or as regular GPIO-s.
*
* They however have a different register layout then the existing
* function.
*/
int mv88e6393x_g2_scratch_gpio_set_smi(struct mv88e6xxx_chip *chip,
bool external)
{
int misc_cfg = MV88E6352_G2_SCRATCH_MISC_CFG;
int err;
u8 val;
err = mv88e6xxx_g2_scratch_read(chip, misc_cfg, &val);
if (err)
return err;
if (external)
val &= ~MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI;
else
val |= MV88E6352_G2_SCRATCH_MISC_CFG_NORMALSMI;
return mv88e6xxx_g2_scratch_write(chip, misc_cfg, val);
}
/**
* mv88e6352_g2_cache_global_scratch_config3 - caches G2 CONFIG3 value
* @chip: chip private data
*
* Reads and stores config3 value of global2 scratch registers, which
* can be used to determine if the port is attached to a serdes. The
* value does not change once the switch is released from reset and
* represents the value of the pin strapping.
*
* Return: negative error number if the register read fails; otherwise, 0
*/
int mv88e6352_g2_cache_global_scratch_config3(struct mv88e6xxx_chip *chip)
{
u8 config3;
int err;
err = mv88e6xxx_g2_scratch_read(chip, MV88E6352_G2_SCRATCH_CONFIG_DATA3,
&config3);
if (err)
return err;
chip->g2_scratch_config3 = config3;
return 0;
}
/**
* mv88e6352_g2_scratch_port_has_serdes - indicate if a port has serdes
* @chip: chip private data
* @port: port number to check for serdes
*
* Indicates whether the port may have a serdes attached according to the
* pin strapping, which is cached at reset (scratch config3). Returns 0 if
* the port is not configured to have a serdes, and 1 if the port is
* configured to have a serdes attached.
*/
int mv88e6352_g2_scratch_port_has_serdes(struct mv88e6xxx_chip *chip, int port)
{
u8 p;
if (chip->g2_scratch_config3 & MV88E6352_G2_SCRATCH_CONFIG_DATA3_S_SEL)
p = 5;
else
p = 4;
return port == p;
}
@@ -0,0 +1,611 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch hardware timestamping support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 National Instruments
* Erik Hons <erik.hons@ni.com>
* Brandon Streiff <brandon.streiff@ni.com>
* Dane Wagner <dane.wagner@ni.com>
*/
#include "chip.h"
#include "global2.h"
#include "hwtstamp.h"
#include "ptp.h"
#include <linux/ptp_classify.h>
#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
static int mv88e6xxx_port_ptp_read(struct mv88e6xxx_chip *chip, int port,
int addr, u16 *data, int len)
{
if (!chip->info->ops->avb_ops->port_ptp_read)
return -EOPNOTSUPP;
return chip->info->ops->avb_ops->port_ptp_read(chip, port, addr,
data, len);
}
static int mv88e6xxx_port_ptp_write(struct mv88e6xxx_chip *chip, int port,
int addr, u16 data)
{
if (!chip->info->ops->avb_ops->port_ptp_write)
return -EOPNOTSUPP;
return chip->info->ops->avb_ops->port_ptp_write(chip, port, addr,
data);
}
static int mv88e6xxx_ptp_write(struct mv88e6xxx_chip *chip, int addr,
u16 data)
{
if (!chip->info->ops->avb_ops->ptp_write)
return -EOPNOTSUPP;
return chip->info->ops->avb_ops->ptp_write(chip, addr, data);
}
static int mv88e6xxx_ptp_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data)
{
if (!chip->info->ops->avb_ops->ptp_read)
return -EOPNOTSUPP;
return chip->info->ops->avb_ops->ptp_read(chip, addr, data, 1);
}
/* TX_TSTAMP_TIMEOUT: This limits the time spent polling for a TX
* timestamp. When working properly, hardware will produce a timestamp
* within 1ms. Software may enounter delays due to MDIO contention, so
* the timeout is set accordingly.
*/
#define TX_TSTAMP_TIMEOUT msecs_to_jiffies(40)
int mv88e6xxx_get_ts_info(struct dsa_switch *ds, int port,
struct kernel_ethtool_ts_info *info)
{
const struct mv88e6xxx_ptp_ops *ptp_ops;
struct mv88e6xxx_chip *chip;
chip = ds->priv;
ptp_ops = chip->info->ops->ptp_ops;
if (!chip->info->ptp_support)
return -EOPNOTSUPP;
info->so_timestamping =
SOF_TIMESTAMPING_TX_HARDWARE |
SOF_TIMESTAMPING_RX_HARDWARE |
SOF_TIMESTAMPING_RAW_HARDWARE;
info->phc_index = ptp_clock_index(chip->ptp_clock);
info->tx_types =
(1 << HWTSTAMP_TX_OFF) |
(1 << HWTSTAMP_TX_ON);
info->rx_filters = ptp_ops->rx_filters;
return 0;
}
static int mv88e6xxx_set_hwtstamp_config(struct mv88e6xxx_chip *chip, int port,
struct kernel_hwtstamp_config *config)
{
const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port];
bool tstamp_enable = false;
/* Prevent the TX/RX paths from trying to interact with the
* timestamp hardware while we reconfigure it.
*/
clear_bit_unlock(MV88E6XXX_HWTSTAMP_ENABLED, &ps->state);
switch (config->tx_type) {
case HWTSTAMP_TX_OFF:
tstamp_enable = false;
break;
case HWTSTAMP_TX_ON:
tstamp_enable = true;
break;
default:
return -ERANGE;
}
/* The switch supports timestamping both L2 and L4; one cannot be
* disabled independently of the other.
*/
if (!(BIT(config->rx_filter) & ptp_ops->rx_filters)) {
config->rx_filter = HWTSTAMP_FILTER_NONE;
dev_dbg(chip->dev, "Unsupported rx_filter %d\n",
config->rx_filter);
return -ERANGE;
}
switch (config->rx_filter) {
case HWTSTAMP_FILTER_NONE:
tstamp_enable = false;
break;
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
break;
case HWTSTAMP_FILTER_ALL:
default:
config->rx_filter = HWTSTAMP_FILTER_NONE;
return -ERANGE;
}
mv88e6xxx_reg_lock(chip);
if (tstamp_enable) {
chip->enable_count += 1;
if (chip->enable_count == 1 && ptp_ops->global_enable)
ptp_ops->global_enable(chip);
if (ptp_ops->port_enable)
ptp_ops->port_enable(chip, port);
} else {
if (ptp_ops->port_disable)
ptp_ops->port_disable(chip, port);
chip->enable_count -= 1;
if (chip->enable_count == 0 && ptp_ops->global_disable)
ptp_ops->global_disable(chip);
}
mv88e6xxx_reg_unlock(chip);
/* Once hardware has been configured, enable timestamp checks
* in the RX/TX paths.
*/
if (tstamp_enable)
set_bit(MV88E6XXX_HWTSTAMP_ENABLED, &ps->state);
return 0;
}
int mv88e6xxx_port_hwtstamp_set(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack)
{
struct mv88e6xxx_chip *chip = ds->priv;
struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port];
int err;
if (!chip->info->ptp_support)
return -EOPNOTSUPP;
err = mv88e6xxx_set_hwtstamp_config(chip, port, config);
if (err)
return err;
/* Save the chosen configuration to be returned later. */
ps->tstamp_config = *config;
return 0;
}
int mv88e6xxx_port_hwtstamp_get(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *config)
{
struct mv88e6xxx_chip *chip = ds->priv;
struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port];
if (!chip->info->ptp_support)
return -EOPNOTSUPP;
*config = ps->tstamp_config;
return 0;
}
/* Returns a pointer to the PTP header if the caller should time stamp,
* or NULL if the caller should not.
*/
static struct ptp_header *mv88e6xxx_should_tstamp(struct mv88e6xxx_chip *chip,
int port, struct sk_buff *skb,
unsigned int type)
{
struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port];
struct ptp_header *hdr;
if (!chip->info->ptp_support)
return NULL;
hdr = ptp_parse_header(skb, type);
if (!hdr)
return NULL;
if (!test_bit(MV88E6XXX_HWTSTAMP_ENABLED, &ps->state))
return NULL;
return hdr;
}
static int mv88e6xxx_ts_valid(u16 status)
{
if (!(status & MV88E6XXX_PTP_TS_VALID))
return 0;
if (status & MV88E6XXX_PTP_TS_STATUS_MASK)
return 0;
return 1;
}
static int seq_match(struct sk_buff *skb, u16 ts_seqid)
{
unsigned int type = SKB_PTP_TYPE(skb);
struct ptp_header *hdr;
hdr = ptp_parse_header(skb, type);
return ts_seqid == ntohs(hdr->sequence_id);
}
static void mv88e6xxx_get_rxts(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_port_hwtstamp *ps,
struct sk_buff *skb, u16 reg,
struct sk_buff_head *rxq)
{
u16 buf[4] = { 0 }, status, seq_id;
struct skb_shared_hwtstamps *shwt;
struct sk_buff_head received;
u64 ns, timelo, timehi;
unsigned long flags;
int err;
/* The latched timestamp belongs to one of the received frames. */
__skb_queue_head_init(&received);
spin_lock_irqsave(&rxq->lock, flags);
skb_queue_splice_tail_init(rxq, &received);
spin_unlock_irqrestore(&rxq->lock, flags);
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_port_ptp_read(chip, ps->port_id,
reg, buf, ARRAY_SIZE(buf));
mv88e6xxx_reg_unlock(chip);
if (err)
pr_err("failed to get the receive time stamp\n");
status = buf[0];
timelo = buf[1];
timehi = buf[2];
seq_id = buf[3];
if (status & MV88E6XXX_PTP_TS_VALID) {
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_port_ptp_write(chip, ps->port_id, reg, 0);
mv88e6xxx_reg_unlock(chip);
if (err)
pr_err("failed to clear the receive status\n");
}
/* Since the device can only handle one time stamp at a time,
* we purge any extra frames from the queue.
*/
for ( ; skb; skb = __skb_dequeue(&received)) {
if (mv88e6xxx_ts_valid(status) && seq_match(skb, seq_id)) {
ns = timehi << 16 | timelo;
mv88e6xxx_reg_lock(chip);
ns = timecounter_cyc2time(&chip->tstamp_tc, ns);
mv88e6xxx_reg_unlock(chip);
shwt = skb_hwtstamps(skb);
memset(shwt, 0, sizeof(*shwt));
shwt->hwtstamp = ns_to_ktime(ns);
status &= ~MV88E6XXX_PTP_TS_VALID;
}
netif_rx(skb);
}
}
static void mv88e6xxx_rxtstamp_work(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_port_hwtstamp *ps)
{
const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
struct sk_buff *skb;
skb = skb_dequeue(&ps->rx_queue);
if (skb)
mv88e6xxx_get_rxts(chip, ps, skb, ptp_ops->arr0_sts_reg,
&ps->rx_queue);
skb = skb_dequeue(&ps->rx_queue2);
if (skb)
mv88e6xxx_get_rxts(chip, ps, skb, ptp_ops->arr1_sts_reg,
&ps->rx_queue2);
}
static int is_pdelay_resp(const struct ptp_header *hdr)
{
return (hdr->tsmt & 0xf) == 3;
}
bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
struct sk_buff *skb, unsigned int type)
{
struct mv88e6xxx_port_hwtstamp *ps;
struct mv88e6xxx_chip *chip;
struct ptp_header *hdr;
chip = ds->priv;
ps = &chip->port_hwtstamp[port];
if (ps->tstamp_config.rx_filter != HWTSTAMP_FILTER_PTP_V2_EVENT)
return false;
hdr = mv88e6xxx_should_tstamp(chip, port, skb, type);
if (!hdr)
return false;
SKB_PTP_TYPE(skb) = type;
if (is_pdelay_resp(hdr))
skb_queue_tail(&ps->rx_queue2, skb);
else
skb_queue_tail(&ps->rx_queue, skb);
ptp_schedule_worker(chip->ptp_clock, 0);
return true;
}
static int mv88e6xxx_txtstamp_work(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_port_hwtstamp *ps)
{
const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
struct skb_shared_hwtstamps shhwtstamps;
u16 departure_block[4], status;
struct sk_buff *tmp_skb;
u32 time_raw;
int err;
u64 ns;
if (!ps->tx_skb)
return 0;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_port_ptp_read(chip, ps->port_id,
ptp_ops->dep_sts_reg,
departure_block,
ARRAY_SIZE(departure_block));
mv88e6xxx_reg_unlock(chip);
if (err)
goto free_and_clear_skb;
if (!(departure_block[0] & MV88E6XXX_PTP_TS_VALID)) {
if (time_is_before_jiffies(ps->tx_tstamp_start +
TX_TSTAMP_TIMEOUT)) {
dev_warn(chip->dev, "p%d: clearing tx timestamp hang\n",
ps->port_id);
goto free_and_clear_skb;
}
/* The timestamp should be available quickly, while getting it
* is high priority and time bounded to only 10ms. A poll is
* warranted so restart the work.
*/
return 1;
}
/* We have the timestamp; go ahead and clear valid now */
mv88e6xxx_reg_lock(chip);
mv88e6xxx_port_ptp_write(chip, ps->port_id, ptp_ops->dep_sts_reg, 0);
mv88e6xxx_reg_unlock(chip);
status = departure_block[0] & MV88E6XXX_PTP_TS_STATUS_MASK;
if (status != MV88E6XXX_PTP_TS_STATUS_NORMAL) {
dev_warn(chip->dev, "p%d: tx timestamp overrun\n", ps->port_id);
goto free_and_clear_skb;
}
if (departure_block[3] != ps->tx_seq_id) {
dev_warn(chip->dev, "p%d: unexpected seq. id\n", ps->port_id);
goto free_and_clear_skb;
}
memset(&shhwtstamps, 0, sizeof(shhwtstamps));
time_raw = ((u32)departure_block[2] << 16) | departure_block[1];
mv88e6xxx_reg_lock(chip);
ns = timecounter_cyc2time(&chip->tstamp_tc, time_raw);
mv88e6xxx_reg_unlock(chip);
shhwtstamps.hwtstamp = ns_to_ktime(ns);
dev_dbg(chip->dev,
"p%d: txtstamp %llx status 0x%04x skb ID 0x%04x hw ID 0x%04x\n",
ps->port_id, ktime_to_ns(shhwtstamps.hwtstamp),
departure_block[0], ps->tx_seq_id, departure_block[3]);
/* skb_complete_tx_timestamp() will free up the client to make
* another timestamp-able transmit. We have to be ready for it
* -- by clearing the ps->tx_skb "flag" -- beforehand.
*/
tmp_skb = ps->tx_skb;
ps->tx_skb = NULL;
clear_bit_unlock(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, &ps->state);
skb_complete_tx_timestamp(tmp_skb, &shhwtstamps);
return 0;
free_and_clear_skb:
dev_kfree_skb_any(ps->tx_skb);
ps->tx_skb = NULL;
clear_bit_unlock(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, &ps->state);
return 0;
}
long mv88e6xxx_hwtstamp_work(struct ptp_clock_info *ptp)
{
struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
struct dsa_switch *ds = chip->ds;
struct mv88e6xxx_port_hwtstamp *ps;
int i, restart = 0;
for (i = 0; i < ds->num_ports; i++) {
if (!dsa_is_user_port(ds, i))
continue;
ps = &chip->port_hwtstamp[i];
if (test_bit(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS, &ps->state))
restart |= mv88e6xxx_txtstamp_work(chip, ps);
mv88e6xxx_rxtstamp_work(chip, ps);
}
return restart ? 1 : -1;
}
void mv88e6xxx_port_txtstamp(struct dsa_switch *ds, int port,
struct sk_buff *skb)
{
struct mv88e6xxx_chip *chip = ds->priv;
struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port];
struct ptp_header *hdr;
struct sk_buff *clone;
unsigned int type;
type = ptp_classify_raw(skb);
if (type == PTP_CLASS_NONE)
return;
hdr = mv88e6xxx_should_tstamp(chip, port, skb, type);
if (!hdr)
return;
clone = skb_clone_sk(skb);
if (!clone)
return;
if (test_and_set_bit_lock(MV88E6XXX_HWTSTAMP_TX_IN_PROGRESS,
&ps->state)) {
kfree_skb(clone);
return;
}
ps->tx_skb = clone;
ps->tx_tstamp_start = jiffies;
ps->tx_seq_id = be16_to_cpu(hdr->sequence_id);
ptp_schedule_worker(chip->ptp_clock, 0);
}
int mv88e6165_global_disable(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
err = mv88e6xxx_ptp_read(chip, MV88E6165_PTP_CFG, &val);
if (err)
return err;
val |= MV88E6165_PTP_CFG_DISABLE_PTP;
return mv88e6xxx_ptp_write(chip, MV88E6165_PTP_CFG, val);
}
int mv88e6165_global_enable(struct mv88e6xxx_chip *chip)
{
u16 val;
int err;
err = mv88e6xxx_ptp_read(chip, MV88E6165_PTP_CFG, &val);
if (err)
return err;
val &= ~(MV88E6165_PTP_CFG_DISABLE_PTP | MV88E6165_PTP_CFG_TSPEC_MASK);
return mv88e6xxx_ptp_write(chip, MV88E6165_PTP_CFG, val);
}
int mv88e6352_hwtstamp_port_disable(struct mv88e6xxx_chip *chip, int port)
{
return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP);
}
int mv88e6352_hwtstamp_port_enable(struct mv88e6xxx_chip *chip, int port)
{
return mv88e6xxx_port_ptp_write(chip, port, MV88E6XXX_PORT_PTP_CFG0,
MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH);
}
static int mv88e6xxx_hwtstamp_port_setup(struct mv88e6xxx_chip *chip, int port)
{
const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
struct mv88e6xxx_port_hwtstamp *ps = &chip->port_hwtstamp[port];
ps->port_id = port;
skb_queue_head_init(&ps->rx_queue);
skb_queue_head_init(&ps->rx_queue2);
if (ptp_ops->port_disable)
return ptp_ops->port_disable(chip, port);
return 0;
}
int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip)
{
const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
int err;
int i;
/* Disable timestamping on all ports. */
for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
err = mv88e6xxx_hwtstamp_port_setup(chip, i);
if (err)
return err;
}
/* Disable PTP globally */
if (ptp_ops->global_disable) {
err = ptp_ops->global_disable(chip);
if (err)
return err;
}
/* Set the ethertype of L2 PTP messages */
err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_ETHERTYPE, ETH_P_1588);
if (err)
return err;
/* MV88E6XXX_PTP_MSG_TYPE is a mask of PTP message types to
* timestamp. This affects all ports that have timestamping enabled,
* but the timestamp config is per-port; thus we configure all events
* here and only support the HWTSTAMP_FILTER_*_EVENT filter types.
*/
err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_MSGTYPE,
MV88E6XXX_PTP_MSGTYPE_ALL_EVENT);
if (err)
return err;
/* Use ARRIVAL1 for peer delay response messages. */
err = mv88e6xxx_ptp_write(chip, MV88E6XXX_PTP_TS_ARRIVAL_PTR,
MV88E6XXX_PTP_MSGTYPE_PDLAY_RES);
if (err)
return err;
/* 88E6341 devices default to timestamping at the PHY, but this has
* a hardware issue that results in unreliable timestamps. Force
* these devices to timestamp at the MAC.
*/
if (chip->info->family == MV88E6XXX_FAMILY_6341) {
u16 val = MV88E6341_PTP_CFG_UPDATE |
MV88E6341_PTP_CFG_MODE_IDX |
MV88E6341_PTP_CFG_MODE_TS_AT_MAC;
err = mv88e6xxx_ptp_write(chip, MV88E6341_PTP_CFG, val);
if (err)
return err;
}
return 0;
}
void mv88e6xxx_hwtstamp_free(struct mv88e6xxx_chip *chip)
{
}
@@ -0,0 +1,181 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx Switch hardware timestamping support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 National Instruments
* Erik Hons <erik.hons@ni.com>
* Brandon Streiff <brandon.streiff@ni.com>
* Dane Wagner <dane.wagner@ni.com>
*/
#ifndef _MV88E6XXX_HWTSTAMP_H
#define _MV88E6XXX_HWTSTAMP_H
#include "chip.h"
/* Global 6352 PTP registers */
/* Offset 0x00: PTP EtherType */
#define MV88E6XXX_PTP_ETHERTYPE 0x00
/* Offset 0x01: Message Type Timestamp Enables */
#define MV88E6XXX_PTP_MSGTYPE 0x01
#define MV88E6XXX_PTP_MSGTYPE_SYNC 0x0001
#define MV88E6XXX_PTP_MSGTYPE_DELAY_REQ 0x0002
#define MV88E6XXX_PTP_MSGTYPE_PDLAY_REQ 0x0004
#define MV88E6XXX_PTP_MSGTYPE_PDLAY_RES 0x0008
#define MV88E6XXX_PTP_MSGTYPE_ALL_EVENT 0x000f
/* Offset 0x02: Timestamp Arrival Capture Pointers */
#define MV88E6XXX_PTP_TS_ARRIVAL_PTR 0x02
/* Offset 0x05: PTP Global Configuration */
#define MV88E6165_PTP_CFG 0x05
#define MV88E6165_PTP_CFG_TSPEC_MASK 0xf000
#define MV88E6165_PTP_CFG_DISABLE_TS_OVERWRITE BIT(1)
#define MV88E6165_PTP_CFG_DISABLE_PTP BIT(0)
/* Offset 0x07: PTP Global Configuration */
#define MV88E6341_PTP_CFG 0x07
#define MV88E6341_PTP_CFG_UPDATE 0x8000
#define MV88E6341_PTP_CFG_IDX_MASK 0x7f00
#define MV88E6341_PTP_CFG_DATA_MASK 0x00ff
#define MV88E6341_PTP_CFG_MODE_IDX 0x0
#define MV88E6341_PTP_CFG_MODE_TS_AT_PHY 0x00
#define MV88E6341_PTP_CFG_MODE_TS_AT_MAC 0x80
/* Offset 0x08: PTP Interrupt Status */
#define MV88E6XXX_PTP_IRQ_STATUS 0x08
/* Per-Port 6352 PTP Registers */
/* Offset 0x00: PTP Configuration 0 */
#define MV88E6XXX_PORT_PTP_CFG0 0x00
#define MV88E6XXX_PORT_PTP_CFG0_TSPEC_SHIFT 12
#define MV88E6XXX_PORT_PTP_CFG0_TSPEC_MASK 0xf000
#define MV88E6XXX_PORT_PTP_CFG0_TSPEC_1588 0x0000
#define MV88E6XXX_PORT_PTP_CFG0_TSPEC_8021AS 0x1000
#define MV88E6XXX_PORT_PTP_CFG0_DISABLE_TSPEC_MATCH 0x0800
#define MV88E6XXX_PORT_PTP_CFG0_DISABLE_OVERWRITE 0x0002
#define MV88E6XXX_PORT_PTP_CFG0_DISABLE_PTP 0x0001
/* Offset 0x01: PTP Configuration 1 */
#define MV88E6XXX_PORT_PTP_CFG1 0x01
/* Offset 0x02: PTP Configuration 2 */
#define MV88E6XXX_PORT_PTP_CFG2 0x02
#define MV88E6XXX_PORT_PTP_CFG2_EMBED_ARRIVAL 0x1000
#define MV88E6XXX_PORT_PTP_CFG2_DEP_IRQ_EN 0x0002
#define MV88E6XXX_PORT_PTP_CFG2_ARR_IRQ_EN 0x0001
/* Offset 0x03: PTP LED Configuration */
#define MV88E6XXX_PORT_PTP_LED_CFG 0x03
/* Offset 0x08: PTP Arrival 0 Status */
#define MV88E6XXX_PORT_PTP_ARR0_STS 0x08
/* Offset 0x09/0x0A: PTP Arrival 0 Time */
#define MV88E6XXX_PORT_PTP_ARR0_TIME_LO 0x09
#define MV88E6XXX_PORT_PTP_ARR0_TIME_HI 0x0a
/* Offset 0x0B: PTP Arrival 0 Sequence ID */
#define MV88E6XXX_PORT_PTP_ARR0_SEQID 0x0b
/* Offset 0x0C: PTP Arrival 1 Status */
#define MV88E6XXX_PORT_PTP_ARR1_STS 0x0c
/* Offset 0x0D/0x0E: PTP Arrival 1 Time */
#define MV88E6XXX_PORT_PTP_ARR1_TIME_LO 0x0d
#define MV88E6XXX_PORT_PTP_ARR1_TIME_HI 0x0e
/* Offset 0x0F: PTP Arrival 1 Sequence ID */
#define MV88E6XXX_PORT_PTP_ARR1_SEQID 0x0f
/* Offset 0x10: PTP Departure Status */
#define MV88E6XXX_PORT_PTP_DEP_STS 0x10
/* Offset 0x11/0x12: PTP Deperture Time */
#define MV88E6XXX_PORT_PTP_DEP_TIME_LO 0x11
#define MV88E6XXX_PORT_PTP_DEP_TIME_HI 0x12
/* Offset 0x13: PTP Departure Sequence ID */
#define MV88E6XXX_PORT_PTP_DEP_SEQID 0x13
/* Status fields for arrival and depature timestamp status registers */
#define MV88E6XXX_PTP_TS_STATUS_MASK 0x0006
#define MV88E6XXX_PTP_TS_STATUS_NORMAL 0x0000
#define MV88E6XXX_PTP_TS_STATUS_OVERWITTEN 0x0002
#define MV88E6XXX_PTP_TS_STATUS_DISCARDED 0x0004
#define MV88E6XXX_PTP_TS_VALID 0x0001
#ifdef CONFIG_NET_DSA_MV88E6XXX_PTP
int mv88e6xxx_port_hwtstamp_set(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack);
int mv88e6xxx_port_hwtstamp_get(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *cfg);
bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
struct sk_buff *clone, unsigned int type);
void mv88e6xxx_port_txtstamp(struct dsa_switch *ds, int port,
struct sk_buff *skb);
int mv88e6xxx_get_ts_info(struct dsa_switch *ds, int port,
struct kernel_ethtool_ts_info *info);
long mv88e6xxx_hwtstamp_work(struct ptp_clock_info *ptp);
int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip);
void mv88e6xxx_hwtstamp_free(struct mv88e6xxx_chip *chip);
int mv88e6352_hwtstamp_port_enable(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_hwtstamp_port_disable(struct mv88e6xxx_chip *chip, int port);
int mv88e6165_global_enable(struct mv88e6xxx_chip *chip);
int mv88e6165_global_disable(struct mv88e6xxx_chip *chip);
#else /* !CONFIG_NET_DSA_MV88E6XXX_PTP */
static inline int
mv88e6xxx_port_hwtstamp_set(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *config,
struct netlink_ext_ack *extack)
{
return -EOPNOTSUPP;
}
static inline int
mv88e6xxx_port_hwtstamp_get(struct dsa_switch *ds, int port,
struct kernel_hwtstamp_config *config)
{
return -EOPNOTSUPP;
}
static inline bool mv88e6xxx_port_rxtstamp(struct dsa_switch *ds, int port,
struct sk_buff *clone,
unsigned int type)
{
return false;
}
static inline void mv88e6xxx_port_txtstamp(struct dsa_switch *ds, int port,
struct sk_buff *skb)
{
}
static inline int mv88e6xxx_get_ts_info(struct dsa_switch *ds, int port,
struct kernel_ethtool_ts_info *info)
{
return -EOPNOTSUPP;
}
static inline int mv88e6xxx_hwtstamp_setup(struct mv88e6xxx_chip *chip)
{
return 0;
}
static inline void mv88e6xxx_hwtstamp_free(struct mv88e6xxx_chip *chip)
{
}
#endif /* CONFIG_NET_DSA_MV88E6XXX_PTP */
#endif /* _MV88E6XXX_HWTSTAMP_H */
@@ -0,0 +1,848 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/bitfield.h>
#include <linux/leds.h>
#include <linux/property.h>
#include "chip.h"
#include "global2.h"
#include "port.h"
/* Offset 0x16: LED control */
static int mv88e6xxx_port_led_write(struct mv88e6xxx_chip *chip, int port, u16 reg)
{
reg |= MV88E6XXX_PORT_LED_CONTROL_UPDATE;
return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_LED_CONTROL, reg);
}
static int mv88e6xxx_port_led_read(struct mv88e6xxx_chip *chip, int port,
u16 ptr, u16 *val)
{
int err;
err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_LED_CONTROL, ptr);
if (err)
return err;
err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_LED_CONTROL, val);
*val &= 0x3ff;
return err;
}
static int mv88e6xxx_led_brightness_set(struct mv88e6xxx_port *p, int led,
int brightness)
{
u16 reg;
int err;
err = mv88e6xxx_port_led_read(p->chip, p->port,
MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
&reg);
if (err)
return err;
if (led == 1)
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
else
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
if (brightness) {
/* Selector 0x0f == Force LED ON */
if (led == 1)
reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELF;
else
reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELF;
} else {
/* Selector 0x0e == Force LED OFF */
if (led == 1)
reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELE;
else
reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELE;
}
reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
return mv88e6xxx_port_led_write(p->chip, p->port, reg);
}
static int mv88e6xxx_led0_brightness_set_blocking(struct led_classdev *ldev,
enum led_brightness brightness)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
int err;
mv88e6xxx_reg_lock(p->chip);
err = mv88e6xxx_led_brightness_set(p, 0, brightness);
mv88e6xxx_reg_unlock(p->chip);
return err;
}
static int mv88e6xxx_led1_brightness_set_blocking(struct led_classdev *ldev,
enum led_brightness brightness)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
int err;
mv88e6xxx_reg_lock(p->chip);
err = mv88e6xxx_led_brightness_set(p, 1, brightness);
mv88e6xxx_reg_unlock(p->chip);
return err;
}
struct mv88e6xxx_led_hwconfig {
int led;
u8 portmask;
unsigned long rules;
bool fiber;
bool blink_activity;
u16 selector;
};
/* The following is a lookup table to check what rules we can support on a
* certain LED given restrictions such as that some rules only work with fiber
* (SFP) connections and some blink on activity by default.
*/
#define MV88E6XXX_PORTS_0_3 (BIT(0) | BIT(1) | BIT(2) | BIT(3))
#define MV88E6XXX_PORTS_4_5 (BIT(4) | BIT(5))
#define MV88E6XXX_PORT_4 BIT(4)
#define MV88E6XXX_PORT_5 BIT(5)
/* Entries are listed in selector order.
*
* These configurations vary across different switch families, list
* different tables per-family here.
*/
static const struct mv88e6xxx_led_hwconfig mv88e6352_led_hwconfigs[] = {
{
.led = 0,
.portmask = MV88E6XXX_PORT_4,
.rules = BIT(TRIGGER_NETDEV_LINK),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL0,
},
{
.led = 1,
.portmask = MV88E6XXX_PORT_5,
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL0,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_4_5,
.rules = BIT(TRIGGER_NETDEV_LINK_100),
.blink_activity = true,
.fiber = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_4_5,
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
.blink_activity = true,
.fiber = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_4_5,
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
.blink_activity = true,
.fiber = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_4_5,
.rules = BIT(TRIGGER_NETDEV_LINK_100),
.blink_activity = true,
.fiber = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL3,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_1000),
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_4_5,
.rules = BIT(TRIGGER_NETDEV_LINK),
.fiber = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3,
},
{
.led = 1,
.portmask = MV88E6XXX_PORT_4,
.rules = BIT(TRIGGER_NETDEV_LINK),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL4,
},
{
.led = 1,
.portmask = MV88E6XXX_PORT_5,
.rules = BIT(TRIGGER_NETDEV_LINK),
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL5,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6,
},
{
.led = 0,
.portmask = MV88E6XXX_PORT_4,
.rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6,
},
{
.led = 1,
.portmask = MV88E6XXX_PORT_5,
.rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL7,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000),
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL7,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK),
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL8,
},
{
.led = 0,
.portmask = MV88E6XXX_PORT_5,
.rules = BIT(TRIGGER_NETDEV_LINK),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_10),
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL9,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_100),
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL9,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_10),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SELA,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_100),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SELA,
},
{
.led = 0,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
.selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SELB,
},
{
.led = 1,
.portmask = MV88E6XXX_PORTS_0_3,
.rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000),
.blink_activity = true,
.selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SELB,
},
};
/* mv88e6xxx_led_match_selector() - look up the appropriate LED mode selector
* @p: port state container
* @led: LED number, 0 or 1
* @blink_activity: blink the LED (usually blink on indicated activity)
* @fiber: the link is connected to fiber such as SFP
* @rules: LED status flags from the LED classdev core
* @selector: fill in the selector in this parameter with an OR operation
*/
static int mv88e6xxx_led_match_selector(struct mv88e6xxx_port *p, int led, bool blink_activity,
bool fiber, unsigned long rules, u16 *selector)
{
const struct mv88e6xxx_led_hwconfig *conf;
int i;
/* No rules means we turn the LED off */
if (!rules) {
if (led == 1)
*selector |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELE;
else
*selector |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELE;
return 0;
}
/* TODO: these rules are for MV88E6352, when adding other families,
* think about making sure you select the table that match the
* specific switch family.
*/
for (i = 0; i < ARRAY_SIZE(mv88e6352_led_hwconfigs); i++) {
conf = &mv88e6352_led_hwconfigs[i];
if (conf->led != led)
continue;
if (!(conf->portmask & BIT(p->port)))
continue;
if (conf->blink_activity != blink_activity)
continue;
if (conf->fiber != fiber)
continue;
if (conf->rules == rules) {
dev_dbg(p->chip->dev, "port%d LED %d set selector %04x for rules %08lx\n",
p->port, led, conf->selector, rules);
*selector |= conf->selector;
return 0;
}
}
return -EOPNOTSUPP;
}
/* mv88e6xxx_led_match_selector() - find Linux netdev rules from a selector value
* @p: port state container
* @selector: the selector value from the LED actity register
* @led: LED number, 0 or 1
* @rules: Linux netdev activity rules found from selector
*/
static int
mv88e6xxx_led_match_rule(struct mv88e6xxx_port *p, u16 selector, int led, unsigned long *rules)
{
const struct mv88e6xxx_led_hwconfig *conf;
int i;
/* Find the selector in the table, we just look for the right selector
* and ignore if the activity has special properties such as blinking
* or is fiber-only.
*/
for (i = 0; i < ARRAY_SIZE(mv88e6352_led_hwconfigs); i++) {
conf = &mv88e6352_led_hwconfigs[i];
if (conf->led != led)
continue;
if (!(conf->portmask & BIT(p->port)))
continue;
if (conf->selector == selector) {
dev_dbg(p->chip->dev, "port%d LED %d has selector %04x, rules %08lx\n",
p->port, led, selector, conf->rules);
*rules = conf->rules;
return 0;
}
}
return -EINVAL;
}
/* mv88e6xxx_led_get_selector() - get the appropriate LED mode selector
* @p: port state container
* @led: LED number, 0 or 1
* @fiber: the link is connected to fiber such as SFP
* @rules: LED status flags from the LED classdev core
* @selector: fill in the selector in this parameter with an OR operation
*/
static int mv88e6xxx_led_get_selector(struct mv88e6xxx_port *p, int led,
bool fiber, unsigned long rules, u16 *selector)
{
int err;
/* What happens here is that we first try to locate a trigger with solid
* indicator (such as LED is on for a 1000 link) else we try a second
* sweep to find something suitable with a trigger that will blink on
* activity.
*/
err = mv88e6xxx_led_match_selector(p, led, false, fiber, rules, selector);
if (err)
return mv88e6xxx_led_match_selector(p, led, true, fiber, rules, selector);
return 0;
}
/* Sets up the hardware blinking period */
static int mv88e6xxx_led_set_blinking_period(struct mv88e6xxx_port *p, int led,
unsigned long delay_on, unsigned long delay_off)
{
unsigned long period;
u16 reg;
period = delay_on + delay_off;
reg = 0;
switch (period) {
case 21:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_21MS;
break;
case 42:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_42MS;
break;
case 84:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_84MS;
break;
case 168:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_168MS;
break;
case 336:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_336MS;
break;
case 672:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_672MS;
break;
default:
/* Fall back to software blinking */
return -EINVAL;
}
/* This is essentially PWM duty cycle: how long time of the period
* will the LED be on. Zero isn't great in most cases.
*/
switch (delay_on) {
case 0:
/* This is usually pretty useless and will make the LED look OFF */
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_NONE;
break;
case 21:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS;
break;
case 42:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_42MS;
break;
case 84:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_84MS;
break;
case 168:
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_168MS;
break;
default:
/* Just use something non-zero */
reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS;
break;
}
/* Set up blink rate */
reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_STRETCH_BLINK;
return mv88e6xxx_port_led_write(p->chip, p->port, reg);
}
static int mv88e6xxx_led_blink_set(struct mv88e6xxx_port *p, int led,
unsigned long *delay_on, unsigned long *delay_off)
{
u16 reg;
int err;
/* Choose a sensible default 336 ms (~3 Hz) */
if ((*delay_on == 0) && (*delay_off == 0)) {
*delay_on = 168;
*delay_off = 168;
}
/* No off delay is just on */
if (*delay_off == 0)
return mv88e6xxx_led_brightness_set(p, led, 1);
err = mv88e6xxx_led_set_blinking_period(p, led, *delay_on, *delay_off);
if (err)
return err;
err = mv88e6xxx_port_led_read(p->chip, p->port,
MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
&reg);
if (err)
return err;
if (led == 1)
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
else
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
/* This will select the forced blinking status */
if (led == 1)
reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELD;
else
reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELD;
reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
return mv88e6xxx_port_led_write(p->chip, p->port, reg);
}
static int mv88e6xxx_led0_blink_set(struct led_classdev *ldev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
int err;
mv88e6xxx_reg_lock(p->chip);
err = mv88e6xxx_led_blink_set(p, 0, delay_on, delay_off);
mv88e6xxx_reg_unlock(p->chip);
return err;
}
static int mv88e6xxx_led1_blink_set(struct led_classdev *ldev,
unsigned long *delay_on,
unsigned long *delay_off)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
int err;
mv88e6xxx_reg_lock(p->chip);
err = mv88e6xxx_led_blink_set(p, 1, delay_on, delay_off);
mv88e6xxx_reg_unlock(p->chip);
return err;
}
static int
mv88e6xxx_led0_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
u16 selector = 0;
return mv88e6xxx_led_get_selector(p, 0, p->fiber, rules, &selector);
}
static int
mv88e6xxx_led1_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
u16 selector = 0;
return mv88e6xxx_led_get_selector(p, 1, p->fiber, rules, &selector);
}
static int mv88e6xxx_led_hw_control_set(struct mv88e6xxx_port *p,
int led, unsigned long rules)
{
u16 reg;
int err;
err = mv88e6xxx_port_led_read(p->chip, p->port,
MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL,
&reg);
if (err)
return err;
if (led == 1)
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
else
reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
err = mv88e6xxx_led_get_selector(p, led, p->fiber, rules, &reg);
if (err)
return err;
reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL;
if (led == 0)
dev_dbg(p->chip->dev, "LED 0 hw control on port %d trigger selector 0x%02x\n",
p->port,
(unsigned int)(reg & MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK));
else
dev_dbg(p->chip->dev, "LED 1 hw control on port %d trigger selector 0x%02x\n",
p->port,
(unsigned int)(reg & MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK) >> 4);
return mv88e6xxx_port_led_write(p->chip, p->port, reg);
}
static int
mv88e6xxx_led_hw_control_get(struct mv88e6xxx_port *p, int led, unsigned long *rules)
{
u16 val;
int err;
mv88e6xxx_reg_lock(p->chip);
err = mv88e6xxx_port_led_read(p->chip, p->port,
MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL, &val);
mv88e6xxx_reg_unlock(p->chip);
if (err)
return err;
/* Mask out the selector bits for this port */
if (led == 1) {
val &= MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK;
/* It's forced blinking/OFF/ON */
if (val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELD ||
val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELE ||
val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELF) {
*rules = 0;
return 0;
}
} else {
val &= MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK;
/* It's forced blinking/OFF/ON */
if (val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELD ||
val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELE ||
val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELF) {
*rules = 0;
return 0;
}
}
err = mv88e6xxx_led_match_rule(p, val, led, rules);
if (!err)
return 0;
dev_dbg(p->chip->dev, "couldn't find matching selector for %04x\n", val);
*rules = 0;
return 0;
}
static int
mv88e6xxx_led0_hw_control_set(struct led_classdev *ldev, unsigned long rules)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
int err;
mv88e6xxx_reg_lock(p->chip);
err = mv88e6xxx_led_hw_control_set(p, 0, rules);
mv88e6xxx_reg_unlock(p->chip);
return err;
}
static int
mv88e6xxx_led1_hw_control_set(struct led_classdev *ldev, unsigned long rules)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
int err;
mv88e6xxx_reg_lock(p->chip);
err = mv88e6xxx_led_hw_control_set(p, 1, rules);
mv88e6xxx_reg_unlock(p->chip);
return err;
}
static int
mv88e6xxx_led0_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
return mv88e6xxx_led_hw_control_get(p, 0, rules);
}
static int
mv88e6xxx_led1_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
return mv88e6xxx_led_hw_control_get(p, 1, rules);
}
static struct device *mv88e6xxx_led_hw_control_get_device(struct mv88e6xxx_port *p)
{
struct dsa_port *dp;
dp = dsa_to_port(p->chip->ds, p->port);
if (!dp)
return NULL;
if (dp->user)
return &dp->user->dev;
return NULL;
}
static struct device *
mv88e6xxx_led0_hw_control_get_device(struct led_classdev *ldev)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
return mv88e6xxx_led_hw_control_get_device(p);
}
static struct device *
mv88e6xxx_led1_hw_control_get_device(struct led_classdev *ldev)
{
struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
return mv88e6xxx_led_hw_control_get_device(p);
}
int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port)
{
struct fwnode_handle *led = NULL, *leds = NULL;
struct led_init_data init_data = { };
enum led_default_state state;
struct mv88e6xxx_port *p;
struct led_classdev *l;
struct device *dev;
u32 led_num;
int ret;
/* LEDs are on ports 1,2,3,4, 5 and 6 (index 0..5), no more */
if (port > 5)
return -EOPNOTSUPP;
p = &chip->ports[port];
if (!p->fwnode)
return 0;
dev = chip->dev;
leds = fwnode_get_named_child_node(p->fwnode, "leds");
if (!leds) {
dev_dbg(dev, "No Leds node specified in device tree for port %d!\n",
port);
return 0;
}
fwnode_for_each_child_node(leds, led) {
/* Reg represent the led number of the port, max 2
* LEDs can be connected to each port, in some designs
* only one LED is connected.
*/
if (fwnode_property_read_u32(led, "reg", &led_num))
continue;
if (led_num > 1) {
dev_err(dev, "invalid LED specified port %d\n", port);
ret = -EINVAL;
goto err_put_led;
}
if (led_num == 0)
l = &p->led0;
else
l = &p->led1;
state = led_init_default_state_get(led);
switch (state) {
case LEDS_DEFSTATE_ON:
l->brightness = 1;
mv88e6xxx_led_brightness_set(p, led_num, 1);
break;
case LEDS_DEFSTATE_KEEP:
break;
default:
l->brightness = 0;
mv88e6xxx_led_brightness_set(p, led_num, 0);
}
l->max_brightness = 1;
if (led_num == 0) {
l->brightness_set_blocking = mv88e6xxx_led0_brightness_set_blocking;
l->blink_set = mv88e6xxx_led0_blink_set;
l->hw_control_is_supported = mv88e6xxx_led0_hw_control_is_supported;
l->hw_control_set = mv88e6xxx_led0_hw_control_set;
l->hw_control_get = mv88e6xxx_led0_hw_control_get;
l->hw_control_get_device = mv88e6xxx_led0_hw_control_get_device;
} else {
l->brightness_set_blocking = mv88e6xxx_led1_brightness_set_blocking;
l->blink_set = mv88e6xxx_led1_blink_set;
l->hw_control_is_supported = mv88e6xxx_led1_hw_control_is_supported;
l->hw_control_set = mv88e6xxx_led1_hw_control_set;
l->hw_control_get = mv88e6xxx_led1_hw_control_get;
l->hw_control_get_device = mv88e6xxx_led1_hw_control_get_device;
}
l->hw_control_trigger = "netdev";
init_data.default_label = ":port";
init_data.fwnode = led;
init_data.devname_mandatory = true;
init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d:0%d", chip->info->name,
port, led_num);
if (!init_data.devicename) {
ret = -ENOMEM;
goto err_put_led;
}
ret = devm_led_classdev_register_ext(dev, l, &init_data);
kfree(init_data.devicename);
if (ret) {
dev_err(dev, "Failed to init LED %d for port %d", led_num, port);
goto err_put_led;
}
}
fwnode_handle_put(leds);
return 0;
err_put_led:
fwnode_handle_put(led);
fwnode_handle_put(leds);
return ret;
}
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6185 family SERDES PCS support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/phylink.h>
#include "global2.h"
#include "port.h"
#include "serdes.h"
struct mv88e6185_pcs {
struct phylink_pcs phylink_pcs;
unsigned int irq;
char name[64];
struct mv88e6xxx_chip *chip;
int port;
};
static struct mv88e6185_pcs *pcs_to_mv88e6185_pcs(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mv88e6185_pcs, phylink_pcs);
}
static irqreturn_t mv88e6185_pcs_handle_irq(int irq, void *dev_id)
{
struct mv88e6185_pcs *mpcs = dev_id;
struct mv88e6xxx_chip *chip;
irqreturn_t ret = IRQ_NONE;
bool link_up;
u16 status;
int port;
int err;
chip = mpcs->chip;
port = mpcs->port;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &status);
mv88e6xxx_reg_unlock(chip);
if (!err) {
link_up = !!(status & MV88E6XXX_PORT_STS_LINK);
phylink_pcs_change(&mpcs->phylink_pcs, link_up);
ret = IRQ_HANDLED;
}
return ret;
}
static void mv88e6185_pcs_get_state(struct phylink_pcs *pcs,
unsigned int neg_mode,
struct phylink_link_state *state)
{
struct mv88e6185_pcs *mpcs = pcs_to_mv88e6185_pcs(pcs);
struct mv88e6xxx_chip *chip = mpcs->chip;
int port = mpcs->port;
u16 status;
int err;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &status);
mv88e6xxx_reg_unlock(chip);
if (err)
status = 0;
state->link = !!(status & MV88E6XXX_PORT_STS_LINK);
if (state->link) {
state->duplex = status & MV88E6XXX_PORT_STS_DUPLEX ?
DUPLEX_FULL : DUPLEX_HALF;
switch (status & MV88E6XXX_PORT_STS_SPEED_MASK) {
case MV88E6XXX_PORT_STS_SPEED_1000:
state->speed = SPEED_1000;
break;
case MV88E6XXX_PORT_STS_SPEED_100:
state->speed = SPEED_100;
break;
case MV88E6XXX_PORT_STS_SPEED_10:
state->speed = SPEED_10;
break;
default:
state->link = false;
break;
}
}
}
static int mv88e6185_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
return 0;
}
static void mv88e6185_pcs_an_restart(struct phylink_pcs *pcs)
{
}
static const struct phylink_pcs_ops mv88e6185_phylink_pcs_ops = {
.pcs_get_state = mv88e6185_pcs_get_state,
.pcs_config = mv88e6185_pcs_config,
.pcs_an_restart = mv88e6185_pcs_an_restart,
};
static int mv88e6185_pcs_init(struct mv88e6xxx_chip *chip, int port)
{
struct mv88e6185_pcs *mpcs;
struct device *dev;
unsigned int irq;
int err;
/* There are no configurable serdes lanes on this switch chip, so
* we use the static cmode configuration to determine whether we
* have a PCS or not.
*/
if (chip->ports[port].cmode != MV88E6185_PORT_STS_CMODE_SERDES &&
chip->ports[port].cmode != MV88E6185_PORT_STS_CMODE_1000BASE_X)
return 0;
dev = chip->dev;
mpcs = kzalloc_obj(*mpcs);
if (!mpcs)
return -ENOMEM;
mpcs->chip = chip;
mpcs->port = port;
mpcs->phylink_pcs.ops = &mv88e6185_phylink_pcs_ops;
irq = mv88e6xxx_serdes_irq_mapping(chip, port);
if (irq) {
snprintf(mpcs->name, sizeof(mpcs->name),
"mv88e6xxx-%s-serdes-%d", dev_name(dev), port);
err = request_threaded_irq(irq, NULL, mv88e6185_pcs_handle_irq,
IRQF_ONESHOT, mpcs->name, mpcs);
if (err) {
kfree(mpcs);
return err;
}
mpcs->irq = irq;
} else {
mpcs->phylink_pcs.poll = true;
}
chip->ports[port].pcs_private = &mpcs->phylink_pcs;
return 0;
}
static void mv88e6185_pcs_teardown(struct mv88e6xxx_chip *chip, int port)
{
struct mv88e6185_pcs *mpcs;
mpcs = chip->ports[port].pcs_private;
if (!mpcs)
return;
if (mpcs->irq)
free_irq(mpcs->irq, mpcs);
kfree(mpcs);
chip->ports[port].pcs_private = NULL;
}
static struct phylink_pcs *mv88e6185_pcs_select(struct mv88e6xxx_chip *chip,
int port,
phy_interface_t interface)
{
return chip->ports[port].pcs_private;
}
const struct mv88e6xxx_pcs_ops mv88e6185_pcs_ops = {
.pcs_init = mv88e6185_pcs_init,
.pcs_teardown = mv88e6185_pcs_teardown,
.pcs_select = mv88e6185_pcs_select,
};
@@ -0,0 +1,388 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6352 family SERDES PCS support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/phylink.h>
#include "global2.h"
#include "port.h"
#include "serdes.h"
/* Definitions from drivers/net/phy/marvell.c, which would be good to reuse. */
#define MII_M1011_PHY_STATUS 17
#define MII_M1011_IMASK 18
#define MII_M1011_IMASK_LINK_CHANGE BIT(10)
#define MII_M1011_IEVENT 19
#define MII_M1011_IEVENT_LINK_CHANGE BIT(10)
#define MII_MARVELL_PHY_PAGE 22
#define MII_MARVELL_FIBER_PAGE 1
struct marvell_c22_pcs {
struct mdio_device mdio;
struct phylink_pcs phylink_pcs;
unsigned int irq;
char name[64];
bool (*link_check)(struct marvell_c22_pcs *mpcs);
struct mv88e6xxx_port *port;
};
static struct marvell_c22_pcs *pcs_to_marvell_c22_pcs(struct phylink_pcs *pcs)
{
return container_of(pcs, struct marvell_c22_pcs, phylink_pcs);
}
static int marvell_c22_pcs_set_fiber_page(struct marvell_c22_pcs *mpcs)
{
u16 page;
int err;
mutex_lock(&mpcs->mdio.bus->mdio_lock);
err = __mdiodev_read(&mpcs->mdio, MII_MARVELL_PHY_PAGE);
if (err < 0) {
dev_err(mpcs->mdio.dev.parent,
"%s: can't read Serdes page register: %pe\n",
mpcs->name, ERR_PTR(err));
return err;
}
page = err;
err = __mdiodev_write(&mpcs->mdio, MII_MARVELL_PHY_PAGE,
MII_MARVELL_FIBER_PAGE);
if (err) {
dev_err(mpcs->mdio.dev.parent,
"%s: can't set Serdes page register: %pe\n",
mpcs->name, ERR_PTR(err));
return err;
}
return page;
}
static int marvell_c22_pcs_restore_page(struct marvell_c22_pcs *mpcs,
int oldpage, int ret)
{
int err;
if (oldpage >= 0) {
err = __mdiodev_write(&mpcs->mdio, MII_MARVELL_PHY_PAGE,
oldpage);
if (err)
dev_err(mpcs->mdio.dev.parent,
"%s: can't restore Serdes page register: %pe\n",
mpcs->name, ERR_PTR(err));
if (!err || ret < 0)
err = ret;
} else {
err = oldpage;
}
mutex_unlock(&mpcs->mdio.bus->mdio_lock);
return err;
}
static irqreturn_t marvell_c22_pcs_handle_irq(int irq, void *dev_id)
{
struct marvell_c22_pcs *mpcs = dev_id;
irqreturn_t status = IRQ_NONE;
int err, oldpage;
oldpage = marvell_c22_pcs_set_fiber_page(mpcs);
if (oldpage < 0)
goto fail;
err = __mdiodev_read(&mpcs->mdio, MII_M1011_IEVENT);
if (err >= 0 && err & MII_M1011_IEVENT_LINK_CHANGE) {
phylink_pcs_change(&mpcs->phylink_pcs, true);
status = IRQ_HANDLED;
}
fail:
marvell_c22_pcs_restore_page(mpcs, oldpage, 0);
return status;
}
static int marvell_c22_pcs_modify(struct marvell_c22_pcs *mpcs, u8 reg,
u16 mask, u16 val)
{
int oldpage, err = 0;
oldpage = marvell_c22_pcs_set_fiber_page(mpcs);
if (oldpage >= 0)
err = __mdiodev_modify(&mpcs->mdio, reg, mask, val);
return marvell_c22_pcs_restore_page(mpcs, oldpage, err);
}
static int marvell_c22_pcs_power(struct marvell_c22_pcs *mpcs,
bool on)
{
u16 val = on ? 0 : BMCR_PDOWN;
return marvell_c22_pcs_modify(mpcs, MII_BMCR, BMCR_PDOWN, val);
}
static int marvell_c22_pcs_control_irq(struct marvell_c22_pcs *mpcs,
bool enable)
{
u16 val = enable ? MII_M1011_IMASK_LINK_CHANGE : 0;
return marvell_c22_pcs_modify(mpcs, MII_M1011_IMASK,
MII_M1011_IMASK_LINK_CHANGE, val);
}
static int marvell_c22_pcs_enable(struct phylink_pcs *pcs)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
int err;
err = marvell_c22_pcs_power(mpcs, true);
if (err)
return err;
return marvell_c22_pcs_control_irq(mpcs, !!mpcs->irq);
}
static void marvell_c22_pcs_disable(struct phylink_pcs *pcs)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
marvell_c22_pcs_control_irq(mpcs, false);
marvell_c22_pcs_power(mpcs, false);
}
static void marvell_c22_pcs_get_state(struct phylink_pcs *pcs,
unsigned int neg_mode,
struct phylink_link_state *state)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
int oldpage, bmsr, lpa, status;
state->link = false;
if (mpcs->link_check && !mpcs->link_check(mpcs))
return;
oldpage = marvell_c22_pcs_set_fiber_page(mpcs);
if (oldpage >= 0) {
bmsr = __mdiodev_read(&mpcs->mdio, MII_BMSR);
lpa = __mdiodev_read(&mpcs->mdio, MII_LPA);
status = __mdiodev_read(&mpcs->mdio, MII_M1011_PHY_STATUS);
}
if (marvell_c22_pcs_restore_page(mpcs, oldpage, 0) >= 0 &&
bmsr >= 0 && lpa >= 0 && status >= 0)
mv88e6xxx_pcs_decode_state(mpcs->mdio.dev.parent, bmsr, lpa,
status, state);
}
static int marvell_c22_pcs_config(struct phylink_pcs *pcs,
unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
int oldpage, adv, err, ret = 0;
u16 bmcr;
adv = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
if (adv < 0)
return 0;
bmcr = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED ? BMCR_ANENABLE : 0;
oldpage = marvell_c22_pcs_set_fiber_page(mpcs);
if (oldpage < 0)
goto restore;
err = __mdiodev_modify_changed(&mpcs->mdio, MII_ADVERTISE, 0xffff, adv);
ret = err;
if (err < 0)
goto restore;
err = __mdiodev_modify_changed(&mpcs->mdio, MII_BMCR, BMCR_ANENABLE,
bmcr);
if (err < 0) {
ret = err;
goto restore;
}
/* If the ANENABLE bit was changed, the PHY will restart negotiation,
* so we don't need to flag a change to trigger its own restart.
*/
if (err)
ret = 0;
restore:
return marvell_c22_pcs_restore_page(mpcs, oldpage, ret);
}
static void marvell_c22_pcs_an_restart(struct phylink_pcs *pcs)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
marvell_c22_pcs_modify(mpcs, MII_BMCR, BMCR_ANRESTART, BMCR_ANRESTART);
}
static void marvell_c22_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
phy_interface_t interface, int speed,
int duplex)
{
struct marvell_c22_pcs *mpcs = pcs_to_marvell_c22_pcs(pcs);
u16 bmcr;
int err;
if (phylink_autoneg_inband(mode))
return;
bmcr = mii_bmcr_encode_fixed(speed, duplex);
err = marvell_c22_pcs_modify(mpcs, MII_BMCR, BMCR_SPEED100 |
BMCR_FULLDPLX | BMCR_SPEED1000, bmcr);
if (err)
dev_err(mpcs->mdio.dev.parent,
"%s: failed to configure mpcs: %pe\n", mpcs->name,
ERR_PTR(err));
}
static const struct phylink_pcs_ops marvell_c22_pcs_ops = {
.pcs_enable = marvell_c22_pcs_enable,
.pcs_disable = marvell_c22_pcs_disable,
.pcs_get_state = marvell_c22_pcs_get_state,
.pcs_config = marvell_c22_pcs_config,
.pcs_an_restart = marvell_c22_pcs_an_restart,
.pcs_link_up = marvell_c22_pcs_link_up,
};
static struct marvell_c22_pcs *marvell_c22_pcs_alloc(struct device *dev,
struct mii_bus *bus,
unsigned int addr)
{
struct marvell_c22_pcs *mpcs;
mpcs = kzalloc_obj(*mpcs);
if (!mpcs)
return NULL;
mpcs->mdio.dev.parent = dev;
mpcs->mdio.bus = bus;
mpcs->mdio.addr = addr;
mpcs->phylink_pcs.ops = &marvell_c22_pcs_ops;
return mpcs;
}
static int marvell_c22_pcs_setup_irq(struct marvell_c22_pcs *mpcs,
unsigned int irq)
{
int err;
mpcs->phylink_pcs.poll = !irq;
mpcs->irq = irq;
if (irq) {
err = request_threaded_irq(irq, NULL,
marvell_c22_pcs_handle_irq,
IRQF_ONESHOT, mpcs->name, mpcs);
if (err)
return err;
}
return 0;
}
/* mv88e6352 specifics */
static bool mv88e6352_pcs_link_check(struct marvell_c22_pcs *mpcs)
{
struct mv88e6xxx_port *port = mpcs->port;
struct mv88e6xxx_chip *chip = port->chip;
u8 cmode;
/* Port 4 can be in auto-media mode. Check that the port is
* associated with the mpcs.
*/
mv88e6xxx_reg_lock(chip);
chip->info->ops->port_get_cmode(chip, port->port, &cmode);
mv88e6xxx_reg_unlock(chip);
return cmode == MV88E6XXX_PORT_STS_CMODE_100BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII;
}
static int mv88e6352_pcs_init(struct mv88e6xxx_chip *chip, int port)
{
struct marvell_c22_pcs *mpcs;
struct mii_bus *bus;
struct device *dev;
unsigned int irq;
int lane, err;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return 0;
irq = mv88e6xxx_serdes_irq_mapping(chip, port);
bus = mv88e6xxx_default_mdio_bus(chip);
dev = chip->dev;
mpcs = marvell_c22_pcs_alloc(dev, bus, lane);
if (!mpcs)
return -ENOMEM;
snprintf(mpcs->name, sizeof(mpcs->name),
"mv88e6xxx-%s-serdes-%d", dev_name(dev), port);
mpcs->link_check = mv88e6352_pcs_link_check;
mpcs->port = &chip->ports[port];
err = marvell_c22_pcs_setup_irq(mpcs, irq);
if (err) {
kfree(mpcs);
return err;
}
chip->ports[port].pcs_private = &mpcs->phylink_pcs;
return 0;
}
static void mv88e6352_pcs_teardown(struct mv88e6xxx_chip *chip, int port)
{
struct marvell_c22_pcs *mpcs;
struct phylink_pcs *pcs;
pcs = chip->ports[port].pcs_private;
if (!pcs)
return;
mpcs = pcs_to_marvell_c22_pcs(pcs);
if (mpcs->irq)
free_irq(mpcs->irq, mpcs);
kfree(mpcs);
chip->ports[port].pcs_private = NULL;
}
static struct phylink_pcs *mv88e6352_pcs_select(struct mv88e6xxx_chip *chip,
int port,
phy_interface_t interface)
{
return chip->ports[port].pcs_private;
}
const struct mv88e6xxx_pcs_ops mv88e6352_pcs_ops = {
.pcs_init = mv88e6352_pcs_init,
.pcs_teardown = mv88e6352_pcs_teardown,
.pcs_select = mv88e6352_pcs_select,
};
@@ -0,0 +1,970 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6352 family SERDES PCS support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/mii.h>
#include <linux/string_choices.h>
#include "chip.h"
#include "global2.h"
#include "phy.h"
#include "port.h"
#include "serdes.h"
struct mv88e639x_pcs {
struct mdio_device mdio;
struct phylink_pcs sgmii_pcs;
struct phylink_pcs xg_pcs;
bool erratum_3_14;
bool supports_5g;
phy_interface_t interface;
unsigned int irq;
char name[64];
irqreturn_t (*handle_irq)(struct mv88e639x_pcs *mpcs);
};
static int mv88e639x_read(struct mv88e639x_pcs *mpcs, u16 regnum, u16 *val)
{
int err;
err = mdiodev_c45_read(&mpcs->mdio, MDIO_MMD_PHYXS, regnum);
if (err < 0)
return err;
*val = err;
return 0;
}
static int mv88e639x_write(struct mv88e639x_pcs *mpcs, u16 regnum, u16 val)
{
return mdiodev_c45_write(&mpcs->mdio, MDIO_MMD_PHYXS, regnum, val);
}
static int mv88e639x_modify(struct mv88e639x_pcs *mpcs, u16 regnum, u16 mask,
u16 val)
{
return mdiodev_c45_modify(&mpcs->mdio, MDIO_MMD_PHYXS, regnum, mask,
val);
}
static int mv88e639x_modify_changed(struct mv88e639x_pcs *mpcs, u16 regnum,
u16 mask, u16 set)
{
return mdiodev_c45_modify_changed(&mpcs->mdio, MDIO_MMD_PHYXS, regnum,
mask, set);
}
static struct mv88e639x_pcs *
mv88e639x_pcs_alloc(struct device *dev, struct mii_bus *bus, unsigned int addr,
int port)
{
struct mv88e639x_pcs *mpcs;
mpcs = kzalloc_obj(*mpcs);
if (!mpcs)
return NULL;
mpcs->mdio.dev.parent = dev;
mpcs->mdio.bus = bus;
mpcs->mdio.addr = addr;
snprintf(mpcs->name, sizeof(mpcs->name),
"mv88e6xxx-%s-serdes-%d", dev_name(dev), port);
return mpcs;
}
static irqreturn_t mv88e639x_pcs_handle_irq(int irq, void *dev_id)
{
struct mv88e639x_pcs *mpcs = dev_id;
irqreturn_t (*handler)(struct mv88e639x_pcs *);
handler = READ_ONCE(mpcs->handle_irq);
if (!handler)
return IRQ_NONE;
return handler(mpcs);
}
static int mv88e639x_pcs_setup_irq(struct mv88e639x_pcs *mpcs,
struct mv88e6xxx_chip *chip, int port)
{
unsigned int irq;
irq = mv88e6xxx_serdes_irq_mapping(chip, port);
if (!irq) {
/* Use polling mode */
mpcs->sgmii_pcs.poll = true;
mpcs->xg_pcs.poll = true;
return 0;
}
mpcs->irq = irq;
return request_threaded_irq(irq, NULL, mv88e639x_pcs_handle_irq,
IRQF_ONESHOT, mpcs->name, mpcs);
}
static void mv88e639x_pcs_teardown(struct mv88e6xxx_chip *chip, int port)
{
struct mv88e639x_pcs *mpcs = chip->ports[port].pcs_private;
if (!mpcs)
return;
if (mpcs->irq)
free_irq(mpcs->irq, mpcs);
kfree(mpcs);
chip->ports[port].pcs_private = NULL;
}
static struct mv88e639x_pcs *sgmii_pcs_to_mv88e639x_pcs(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mv88e639x_pcs, sgmii_pcs);
}
static irqreturn_t mv88e639x_sgmii_handle_irq(struct mv88e639x_pcs *mpcs)
{
u16 int_status;
int err;
err = mv88e639x_read(mpcs, MV88E6390_SGMII_INT_STATUS, &int_status);
if (err)
return IRQ_NONE;
if (int_status & (MV88E6390_SGMII_INT_LINK_DOWN |
MV88E6390_SGMII_INT_LINK_UP)) {
phylink_pcs_change(&mpcs->sgmii_pcs,
int_status & MV88E6390_SGMII_INT_LINK_UP);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int mv88e639x_sgmii_pcs_control_irq(struct mv88e639x_pcs *mpcs,
bool enable)
{
u16 val = 0;
if (enable)
val |= MV88E6390_SGMII_INT_LINK_DOWN |
MV88E6390_SGMII_INT_LINK_UP;
return mv88e639x_modify(mpcs, MV88E6390_SGMII_INT_ENABLE,
MV88E6390_SGMII_INT_LINK_DOWN |
MV88E6390_SGMII_INT_LINK_UP, val);
}
static int mv88e639x_sgmii_pcs_control_pwr(struct mv88e639x_pcs *mpcs,
bool enable)
{
u16 mask, val;
if (enable) {
mask = BMCR_RESET | BMCR_LOOPBACK | BMCR_PDOWN;
val = 0;
} else {
mask = val = BMCR_PDOWN;
}
return mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR, mask, val);
}
static int mv88e639x_sgmii_pcs_enable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
/* power enable done in post_config */
mpcs->handle_irq = mv88e639x_sgmii_handle_irq;
return mv88e639x_sgmii_pcs_control_irq(mpcs, !!mpcs->irq);
}
static void mv88e639x_sgmii_pcs_disable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
mv88e639x_sgmii_pcs_control_irq(mpcs, false);
mv88e639x_sgmii_pcs_control_pwr(mpcs, false);
}
static void mv88e639x_sgmii_pcs_pre_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
mv88e639x_sgmii_pcs_control_pwr(mpcs, false);
}
static int mv88e6390_erratum_3_14(struct mv88e639x_pcs *mpcs)
{
static const int lanes[] = { MV88E6390_PORT9_LANE0, MV88E6390_PORT9_LANE1,
MV88E6390_PORT9_LANE2, MV88E6390_PORT9_LANE3,
MV88E6390_PORT10_LANE0, MV88E6390_PORT10_LANE1,
MV88E6390_PORT10_LANE2, MV88E6390_PORT10_LANE3 };
int err, i;
/* 88e6190x and 88e6390x errata 3.14:
* After chip reset, SERDES reconfiguration or SERDES core
* Software Reset, the SERDES lanes may not be properly aligned
* resulting in CRC errors
*/
for (i = 0; i < ARRAY_SIZE(lanes); i++) {
err = mdiobus_c45_write(mpcs->mdio.bus, lanes[i],
MDIO_MMD_PHYXS,
0xf054, 0x400C);
if (err)
return err;
err = mdiobus_c45_write(mpcs->mdio.bus, lanes[i],
MDIO_MMD_PHYXS,
0xf054, 0x4000);
if (err)
return err;
}
return 0;
}
static int mv88e639x_sgmii_pcs_post_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
int err;
mv88e639x_sgmii_pcs_control_pwr(mpcs, true);
if (mpcs->erratum_3_14) {
err = mv88e6390_erratum_3_14(mpcs);
if (err)
dev_err(mpcs->mdio.dev.parent,
"failed to apply erratum 3.14: %pe\n",
ERR_PTR(err));
}
return 0;
}
static void mv88e639x_sgmii_pcs_get_state(struct phylink_pcs *pcs,
unsigned int neg_mode,
struct phylink_link_state *state)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
u16 bmsr, lpa, status;
int err;
err = mv88e639x_read(mpcs, MV88E6390_SGMII_BMSR, &bmsr);
if (err) {
dev_err(mpcs->mdio.dev.parent,
"can't read Serdes PHY %s: %pe\n",
"BMSR", ERR_PTR(err));
state->link = false;
return;
}
err = mv88e639x_read(mpcs, MV88E6390_SGMII_LPA, &lpa);
if (err) {
dev_err(mpcs->mdio.dev.parent,
"can't read Serdes PHY %s: %pe\n",
"LPA", ERR_PTR(err));
state->link = false;
return;
}
err = mv88e639x_read(mpcs, MV88E6390_SGMII_PHY_STATUS, &status);
if (err) {
dev_err(mpcs->mdio.dev.parent,
"can't read Serdes PHY %s: %pe\n",
"status", ERR_PTR(err));
state->link = false;
return;
}
mv88e6xxx_pcs_decode_state(mpcs->mdio.dev.parent, bmsr, lpa, status,
state);
}
static int mv88e639x_sgmii_pcs_config(struct phylink_pcs *pcs,
unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
u16 val, bmcr;
bool changed;
int adv, err;
adv = phylink_mii_c22_pcs_encode_advertisement(interface, advertising);
if (adv < 0)
return 0;
mpcs->interface = interface;
err = mv88e639x_modify_changed(mpcs, MV88E6390_SGMII_ADVERTISE,
0xffff, adv);
if (err < 0)
return err;
changed = err > 0;
err = mv88e639x_read(mpcs, MV88E6390_SGMII_BMCR, &val);
if (err)
return err;
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
bmcr = val | BMCR_ANENABLE;
else
bmcr = val & ~BMCR_ANENABLE;
/* setting ANENABLE triggers a restart of negotiation */
if (bmcr == val)
return changed;
return mv88e639x_write(mpcs, MV88E6390_SGMII_BMCR, bmcr);
}
static void mv88e639x_sgmii_pcs_an_restart(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR,
BMCR_ANRESTART, BMCR_ANRESTART);
}
static void mv88e639x_sgmii_pcs_link_up(struct phylink_pcs *pcs,
unsigned int mode,
phy_interface_t interface,
int speed, int duplex)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
u16 bmcr;
int err;
if (phylink_autoneg_inband(mode))
return;
bmcr = mii_bmcr_encode_fixed(speed, duplex);
err = mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR,
BMCR_SPEED1000 | BMCR_SPEED100 | BMCR_FULLDPLX,
bmcr);
if (err)
dev_err(mpcs->mdio.dev.parent,
"can't access Serdes PHY %s: %pe\n",
"BMCR", ERR_PTR(err));
}
static const struct phylink_pcs_ops mv88e639x_sgmii_pcs_ops = {
.pcs_enable = mv88e639x_sgmii_pcs_enable,
.pcs_disable = mv88e639x_sgmii_pcs_disable,
.pcs_pre_config = mv88e639x_sgmii_pcs_pre_config,
.pcs_post_config = mv88e639x_sgmii_pcs_post_config,
.pcs_get_state = mv88e639x_sgmii_pcs_get_state,
.pcs_an_restart = mv88e639x_sgmii_pcs_an_restart,
.pcs_config = mv88e639x_sgmii_pcs_config,
.pcs_link_up = mv88e639x_sgmii_pcs_link_up,
};
static struct mv88e639x_pcs *xg_pcs_to_mv88e639x_pcs(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mv88e639x_pcs, xg_pcs);
}
static int mv88e639x_xg_pcs_enable(struct mv88e639x_pcs *mpcs)
{
return mv88e639x_modify(mpcs, MV88E6390_10G_CTRL1,
MDIO_CTRL1_RESET | MDIO_PCS_CTRL1_LOOPBACK |
MDIO_CTRL1_LPOWER, 0);
}
static void mv88e639x_xg_pcs_disable(struct mv88e639x_pcs *mpcs)
{
mv88e639x_modify(mpcs, MV88E6390_10G_CTRL1, MDIO_CTRL1_LPOWER,
MDIO_CTRL1_LPOWER);
}
static void mv88e639x_xg_pcs_get_state(struct phylink_pcs *pcs,
unsigned int neg_mode,
struct phylink_link_state *state)
{
struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs);
u16 status;
int err;
state->link = false;
err = mv88e639x_read(mpcs, MV88E6390_10G_STAT1, &status);
if (err) {
dev_err(mpcs->mdio.dev.parent,
"can't read Serdes PHY %s: %pe\n",
"STAT1", ERR_PTR(err));
return;
}
state->link = !!(status & MDIO_STAT1_LSTATUS);
if (state->link) {
switch (state->interface) {
case PHY_INTERFACE_MODE_5GBASER:
state->speed = SPEED_5000;
break;
case PHY_INTERFACE_MODE_10GBASER:
case PHY_INTERFACE_MODE_RXAUI:
case PHY_INTERFACE_MODE_XAUI:
state->speed = SPEED_10000;
break;
default:
state->link = false;
return;
}
state->duplex = DUPLEX_FULL;
}
}
static int mv88e639x_xg_pcs_config(struct phylink_pcs *pcs,
unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
return 0;
}
static struct phylink_pcs *
mv88e639x_pcs_select(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode)
{
struct mv88e639x_pcs *mpcs;
mpcs = chip->ports[port].pcs_private;
if (!mpcs)
return NULL;
switch (mode) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
return &mpcs->sgmii_pcs;
case PHY_INTERFACE_MODE_5GBASER:
if (!mpcs->supports_5g)
return NULL;
fallthrough;
case PHY_INTERFACE_MODE_10GBASER:
case PHY_INTERFACE_MODE_XAUI:
case PHY_INTERFACE_MODE_RXAUI:
case PHY_INTERFACE_MODE_USXGMII:
return &mpcs->xg_pcs;
default:
return NULL;
}
}
/* Marvell 88E6390 Specific support */
static irqreturn_t mv88e6390_xg_handle_irq(struct mv88e639x_pcs *mpcs)
{
u16 int_status;
int err;
err = mv88e639x_read(mpcs, MV88E6390_10G_INT_STATUS, &int_status);
if (err)
return IRQ_NONE;
if (int_status & (MV88E6390_10G_INT_LINK_DOWN |
MV88E6390_10G_INT_LINK_UP)) {
phylink_pcs_change(&mpcs->xg_pcs,
int_status & MV88E6390_10G_INT_LINK_UP);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int mv88e6390_xg_control_irq(struct mv88e639x_pcs *mpcs, bool enable)
{
u16 val = 0;
if (enable)
val = MV88E6390_10G_INT_LINK_DOWN | MV88E6390_10G_INT_LINK_UP;
return mv88e639x_modify(mpcs, MV88E6390_10G_INT_ENABLE,
MV88E6390_10G_INT_LINK_DOWN |
MV88E6390_10G_INT_LINK_UP, val);
}
static int mv88e6390_xg_pcs_enable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs);
int err;
err = mv88e639x_xg_pcs_enable(mpcs);
if (err)
return err;
mpcs->handle_irq = mv88e6390_xg_handle_irq;
return mv88e6390_xg_control_irq(mpcs, !!mpcs->irq);
}
static void mv88e6390_xg_pcs_disable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs);
mv88e6390_xg_control_irq(mpcs, false);
mv88e639x_xg_pcs_disable(mpcs);
}
static const struct phylink_pcs_ops mv88e6390_xg_pcs_ops = {
.pcs_enable = mv88e6390_xg_pcs_enable,
.pcs_disable = mv88e6390_xg_pcs_disable,
.pcs_get_state = mv88e639x_xg_pcs_get_state,
.pcs_config = mv88e639x_xg_pcs_config,
};
static int mv88e6390_pcs_enable_checker(struct mv88e639x_pcs *mpcs)
{
return mv88e639x_modify(mpcs, MV88E6390_PG_CONTROL,
MV88E6390_PG_CONTROL_ENABLE_PC,
MV88E6390_PG_CONTROL_ENABLE_PC);
}
static int mv88e6390_pcs_init(struct mv88e6xxx_chip *chip, int port)
{
struct mv88e639x_pcs *mpcs;
struct mii_bus *bus;
struct device *dev;
int lane, err;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return 0;
bus = mv88e6xxx_default_mdio_bus(chip);
dev = chip->dev;
mpcs = mv88e639x_pcs_alloc(dev, bus, lane, port);
if (!mpcs)
return -ENOMEM;
mpcs->sgmii_pcs.ops = &mv88e639x_sgmii_pcs_ops;
mpcs->xg_pcs.ops = &mv88e6390_xg_pcs_ops;
if (chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6190X ||
chip->info->prod_num == MV88E6XXX_PORT_SWITCH_ID_PROD_6390X)
mpcs->erratum_3_14 = true;
err = mv88e639x_pcs_setup_irq(mpcs, chip, port);
if (err)
goto err_free;
/* 6390 and 6390x has the checker, 6393x doesn't appear to? */
/* This is to enable gathering the statistics. Maybe this
* should call out to a helper? Or we could do this at init time.
*/
err = mv88e6390_pcs_enable_checker(mpcs);
if (err)
goto err_free;
chip->ports[port].pcs_private = mpcs;
return 0;
err_free:
kfree(mpcs);
return err;
}
const struct mv88e6xxx_pcs_ops mv88e6390_pcs_ops = {
.pcs_init = mv88e6390_pcs_init,
.pcs_teardown = mv88e639x_pcs_teardown,
.pcs_select = mv88e639x_pcs_select,
};
/* Marvell 88E6393X Specific support */
static int mv88e6393x_power_lane(struct mv88e639x_pcs *mpcs, bool enable)
{
u16 val = MV88E6393X_SERDES_CTRL1_TX_PDOWN |
MV88E6393X_SERDES_CTRL1_RX_PDOWN;
return mv88e639x_modify(mpcs, MV88E6393X_SERDES_CTRL1, val,
enable ? 0 : val);
}
/* mv88e6393x family errata 4.6:
* Cannot clear PwrDn bit on SERDES if device is configured CPU_MGD mode or
* P0_mode is configured for [x]MII.
* Workaround: Set SERDES register 4.F002 bit 5=0 and bit 15=1.
*
* It seems that after this workaround the SERDES is automatically powered up
* (the bit is cleared), so power it down.
*/
static int mv88e6393x_erratum_4_6(struct mv88e639x_pcs *mpcs)
{
int err;
err = mv88e639x_modify(mpcs, MV88E6393X_SERDES_POC,
MV88E6393X_SERDES_POC_PDOWN |
MV88E6393X_SERDES_POC_RESET,
MV88E6393X_SERDES_POC_RESET);
if (err)
return err;
err = mv88e639x_modify(mpcs, MV88E6390_SGMII_BMCR,
BMCR_PDOWN, BMCR_PDOWN);
if (err)
return err;
err = mv88e639x_sgmii_pcs_control_pwr(mpcs, false);
if (err)
return err;
return mv88e6393x_power_lane(mpcs, false);
}
/* mv88e6393x family errata 4.8:
* When a SERDES port is operating in 1000BASE-X or SGMII mode link may not
* come up after hardware reset or software reset of SERDES core. Workaround
* is to write SERDES register 4.F074.14=1 for only those modes and 0 in all
* other modes.
*/
static int mv88e6393x_erratum_4_8(struct mv88e639x_pcs *mpcs)
{
u16 reg, poc;
int err;
err = mv88e639x_read(mpcs, MV88E6393X_SERDES_POC, &poc);
if (err)
return err;
poc &= MV88E6393X_SERDES_POC_PCS_MASK;
if (poc == MV88E6393X_SERDES_POC_PCS_1000BASEX ||
poc == MV88E6393X_SERDES_POC_PCS_SGMII_PHY ||
poc == MV88E6393X_SERDES_POC_PCS_SGMII_MAC)
reg = MV88E6393X_ERRATA_4_8_BIT;
else
reg = 0;
return mv88e639x_modify(mpcs, MV88E6393X_ERRATA_4_8_REG,
MV88E6393X_ERRATA_4_8_BIT, reg);
}
/* mv88e6393x family errata 5.2:
* For optimal signal integrity the following sequence should be applied to
* SERDES operating in 10G mode. These registers only apply to 10G operation
* and have no effect on other speeds.
*/
static int mv88e6393x_erratum_5_2(struct mv88e639x_pcs *mpcs)
{
static const struct {
u16 dev, reg, val, mask;
} fixes[] = {
{ MDIO_MMD_VEND1, 0x8093, 0xcb5a, 0xffff },
{ MDIO_MMD_VEND1, 0x8171, 0x7088, 0xffff },
{ MDIO_MMD_VEND1, 0x80c9, 0x311a, 0xffff },
{ MDIO_MMD_VEND1, 0x80a2, 0x8000, 0xff7f },
{ MDIO_MMD_VEND1, 0x80a9, 0x0000, 0xfff0 },
{ MDIO_MMD_VEND1, 0x80a3, 0x0000, 0xf8ff },
{ MDIO_MMD_PHYXS, MV88E6393X_SERDES_POC,
MV88E6393X_SERDES_POC_RESET, MV88E6393X_SERDES_POC_RESET },
};
int err, i;
for (i = 0; i < ARRAY_SIZE(fixes); ++i) {
err = mdiodev_c45_modify(&mpcs->mdio, fixes[i].dev,
fixes[i].reg, fixes[i].mask,
fixes[i].val);
if (err)
return err;
}
return 0;
}
/* Inband AN is broken on Amethyst in 2500base-x mode when set by standard
* mechanism (via cmode).
* We can get around this by configuring the PCS mode to 1000base-x and then
* writing value 0x58 to register 1e.8000. (This must be done while SerDes
* receiver and transmitter are disabled, which is, when this function is
* called.)
* It seem that when we do this configuration to 2500base-x mode (by changing
* PCS mode to 1000base-x and frequency to 3.125 GHz from 1.25 GHz) and then
* configure to sgmii or 1000base-x, the device thinks that it already has
* SerDes at 1.25 GHz and does not change the 1e.8000 register, leaving SerDes
* at 3.125 GHz.
* To avoid this, change PCS mode back to 2500base-x when disabling SerDes from
* 2500base-x mode.
*/
static int mv88e6393x_fix_2500basex_an(struct mv88e639x_pcs *mpcs, bool on)
{
u16 reg;
int err;
if (on)
reg = MV88E6393X_SERDES_POC_PCS_1000BASEX |
MV88E6393X_SERDES_POC_AN;
else
reg = MV88E6393X_SERDES_POC_PCS_2500BASEX;
reg |= MV88E6393X_SERDES_POC_RESET;
err = mv88e639x_modify(mpcs, MV88E6393X_SERDES_POC,
MV88E6393X_SERDES_POC_PCS_MASK |
MV88E6393X_SERDES_POC_AN |
MV88E6393X_SERDES_POC_RESET, reg);
if (err)
return err;
return mdiodev_c45_write(&mpcs->mdio, MDIO_MMD_VEND1, 0x8000, 0x58);
}
static int mv88e6393x_sgmii_apply_2500basex_an(struct mv88e639x_pcs *mpcs,
phy_interface_t interface,
bool enable)
{
int err;
if (interface != PHY_INTERFACE_MODE_2500BASEX)
return 0;
err = mv88e6393x_fix_2500basex_an(mpcs, enable);
if (err)
dev_err(mpcs->mdio.dev.parent,
"failed to %s 2500basex fix: %pe\n",
str_enable_disable(enable), ERR_PTR(err));
return err;
}
static void mv88e6393x_sgmii_pcs_disable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
mv88e639x_sgmii_pcs_disable(pcs);
mv88e6393x_power_lane(mpcs, false);
mv88e6393x_sgmii_apply_2500basex_an(mpcs, mpcs->interface, false);
}
static void mv88e6393x_sgmii_pcs_pre_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
mv88e639x_sgmii_pcs_pre_config(pcs, interface);
mv88e6393x_power_lane(mpcs, false);
mv88e6393x_sgmii_apply_2500basex_an(mpcs, mpcs->interface, false);
}
static int mv88e6393x_sgmii_pcs_post_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
struct mv88e639x_pcs *mpcs = sgmii_pcs_to_mv88e639x_pcs(pcs);
int err;
err = mv88e6393x_erratum_4_8(mpcs);
if (err)
return err;
err = mv88e6393x_sgmii_apply_2500basex_an(mpcs, interface, true);
if (err)
return err;
err = mv88e6393x_power_lane(mpcs, true);
if (err)
return err;
return mv88e639x_sgmii_pcs_post_config(pcs, interface);
}
static const struct phylink_pcs_ops mv88e6393x_sgmii_pcs_ops = {
.pcs_enable = mv88e639x_sgmii_pcs_enable,
.pcs_disable = mv88e6393x_sgmii_pcs_disable,
.pcs_pre_config = mv88e6393x_sgmii_pcs_pre_config,
.pcs_post_config = mv88e6393x_sgmii_pcs_post_config,
.pcs_get_state = mv88e639x_sgmii_pcs_get_state,
.pcs_an_restart = mv88e639x_sgmii_pcs_an_restart,
.pcs_config = mv88e639x_sgmii_pcs_config,
.pcs_link_up = mv88e639x_sgmii_pcs_link_up,
};
static irqreturn_t mv88e6393x_xg_handle_irq(struct mv88e639x_pcs *mpcs)
{
u16 int_status, stat1;
bool link_down;
int err;
err = mv88e639x_read(mpcs, MV88E6393X_10G_INT_STATUS, &int_status);
if (err)
return IRQ_NONE;
if (int_status & MV88E6393X_10G_INT_LINK_CHANGE) {
err = mv88e639x_read(mpcs, MV88E6390_10G_STAT1, &stat1);
if (err)
return IRQ_NONE;
link_down = !(stat1 & MDIO_STAT1_LSTATUS);
phylink_pcs_change(&mpcs->xg_pcs, !link_down);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int mv88e6393x_xg_control_irq(struct mv88e639x_pcs *mpcs, bool enable)
{
u16 val = 0;
if (enable)
val = MV88E6393X_10G_INT_LINK_CHANGE;
return mv88e639x_modify(mpcs, MV88E6393X_10G_INT_ENABLE,
MV88E6393X_10G_INT_LINK_CHANGE, val);
}
static int mv88e6393x_xg_pcs_enable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs);
mpcs->handle_irq = mv88e6393x_xg_handle_irq;
return mv88e6393x_xg_control_irq(mpcs, !!mpcs->irq);
}
static void mv88e6393x_xg_pcs_disable(struct phylink_pcs *pcs)
{
struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs);
mv88e6393x_xg_control_irq(mpcs, false);
mv88e639x_xg_pcs_disable(mpcs);
mv88e6393x_power_lane(mpcs, false);
}
/* The PCS has to be powered down while CMODE is changed */
static void mv88e6393x_xg_pcs_pre_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs);
mv88e639x_xg_pcs_disable(mpcs);
mv88e6393x_power_lane(mpcs, false);
}
static int mv88e6393x_xg_pcs_post_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs);
int err;
if (interface == PHY_INTERFACE_MODE_10GBASER ||
interface == PHY_INTERFACE_MODE_USXGMII) {
err = mv88e6393x_erratum_5_2(mpcs);
if (err)
return err;
}
err = mv88e6393x_power_lane(mpcs, true);
if (err)
return err;
return mv88e639x_xg_pcs_enable(mpcs);
}
static void mv88e6393x_xg_pcs_get_state(struct phylink_pcs *pcs,
unsigned int neg_mode,
struct phylink_link_state *state)
{
struct mv88e639x_pcs *mpcs = xg_pcs_to_mv88e639x_pcs(pcs);
u16 status, lp_status;
int err;
if (state->interface != PHY_INTERFACE_MODE_USXGMII)
return mv88e639x_xg_pcs_get_state(pcs, neg_mode, state);
state->link = false;
err = mv88e639x_read(mpcs, MV88E6390_USXGMII_PHY_STATUS, &status);
err = err ? : mv88e639x_read(mpcs, MV88E6390_USXGMII_LP_STATUS, &lp_status);
if (err) {
dev_err(mpcs->mdio.dev.parent,
"can't read USXGMII status: %pe\n", ERR_PTR(err));
return;
}
state->link = !!(status & MDIO_USXGMII_LINK);
state->an_complete = state->link;
phylink_decode_usxgmii_word(state, lp_status);
}
static const struct phylink_pcs_ops mv88e6393x_xg_pcs_ops = {
.pcs_enable = mv88e6393x_xg_pcs_enable,
.pcs_disable = mv88e6393x_xg_pcs_disable,
.pcs_pre_config = mv88e6393x_xg_pcs_pre_config,
.pcs_post_config = mv88e6393x_xg_pcs_post_config,
.pcs_get_state = mv88e6393x_xg_pcs_get_state,
.pcs_config = mv88e639x_xg_pcs_config,
};
static int mv88e6393x_pcs_init(struct mv88e6xxx_chip *chip, int port)
{
struct mv88e639x_pcs *mpcs;
struct mii_bus *bus;
struct device *dev;
int lane, err;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return 0;
bus = mv88e6xxx_default_mdio_bus(chip);
dev = chip->dev;
mpcs = mv88e639x_pcs_alloc(dev, bus, lane, port);
if (!mpcs)
return -ENOMEM;
mpcs->sgmii_pcs.ops = &mv88e6393x_sgmii_pcs_ops;
mpcs->xg_pcs.ops = &mv88e6393x_xg_pcs_ops;
mpcs->supports_5g = true;
err = mv88e6393x_erratum_4_6(mpcs);
if (err)
goto err_free;
err = mv88e639x_pcs_setup_irq(mpcs, chip, port);
if (err)
goto err_free;
chip->ports[port].pcs_private = mpcs;
return 0;
err_free:
kfree(mpcs);
return err;
}
const struct mv88e6xxx_pcs_ops mv88e6393x_pcs_ops = {
.pcs_init = mv88e6393x_pcs_init,
.pcs_teardown = mv88e639x_pcs_teardown,
.pcs_select = mv88e639x_pcs_select,
};
+281
View File
@@ -0,0 +1,281 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88e6xxx Ethernet switch PHY and PPU support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/mdio.h>
#include <linux/module.h>
#include "chip.h"
#include "phy.h"
int mv88e6165_phy_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 *val)
{
return mv88e6xxx_read(chip, addr, reg, val);
}
int mv88e6165_phy_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 val)
{
return mv88e6xxx_write(chip, addr, reg, val);
}
int mv88e6xxx_phy_read(struct mv88e6xxx_chip *chip, int phy, int reg, u16 *val)
{
int addr = phy; /* PHY devices addresses start at 0x0 */
struct mii_bus *bus;
bus = mv88e6xxx_default_mdio_bus(chip);
if (!bus)
return -EOPNOTSUPP;
if (!chip->info->ops->phy_read)
return -EOPNOTSUPP;
return chip->info->ops->phy_read(chip, bus, addr, reg, val);
}
int mv88e6xxx_phy_write(struct mv88e6xxx_chip *chip, int phy, int reg, u16 val)
{
int addr = phy; /* PHY devices addresses start at 0x0 */
struct mii_bus *bus;
bus = mv88e6xxx_default_mdio_bus(chip);
if (!bus)
return -EOPNOTSUPP;
if (!chip->info->ops->phy_write)
return -EOPNOTSUPP;
return chip->info->ops->phy_write(chip, bus, addr, reg, val);
}
int mv88e6xxx_phy_read_c45(struct mv88e6xxx_chip *chip, int phy, int devad,
int reg, u16 *val)
{
int addr = phy; /* PHY devices addresses start at 0x0 */
struct mii_bus *bus;
bus = mv88e6xxx_default_mdio_bus(chip);
if (!bus)
return -EOPNOTSUPP;
if (!chip->info->ops->phy_read_c45)
return -EOPNOTSUPP;
return chip->info->ops->phy_read_c45(chip, bus, addr, devad, reg, val);
}
int mv88e6xxx_phy_write_c45(struct mv88e6xxx_chip *chip, int phy, int devad,
int reg, u16 val)
{
int addr = phy; /* PHY devices addresses start at 0x0 */
struct mii_bus *bus;
bus = mv88e6xxx_default_mdio_bus(chip);
if (!bus)
return -EOPNOTSUPP;
if (!chip->info->ops->phy_write_c45)
return -EOPNOTSUPP;
return chip->info->ops->phy_write_c45(chip, bus, addr, devad, reg, val);
}
static int mv88e6xxx_phy_page_get(struct mv88e6xxx_chip *chip, int phy, u8 page)
{
return mv88e6xxx_phy_write(chip, phy, MV88E6XXX_PHY_PAGE, page);
}
static void mv88e6xxx_phy_page_put(struct mv88e6xxx_chip *chip, int phy)
{
int err;
/* Restore PHY page Copper 0x0 for access via the registered
* MDIO bus
*/
err = mv88e6xxx_phy_write(chip, phy, MV88E6XXX_PHY_PAGE,
MV88E6XXX_PHY_PAGE_COPPER);
if (unlikely(err)) {
dev_err(chip->dev,
"failed to restore PHY %d page Copper (%d)\n",
phy, err);
}
}
int mv88e6xxx_phy_page_read(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 *val)
{
int err;
/* There is no paging for registers 22 */
if (reg == MV88E6XXX_PHY_PAGE)
return -EINVAL;
err = mv88e6xxx_phy_page_get(chip, phy, page);
if (!err) {
err = mv88e6xxx_phy_read(chip, phy, reg, val);
mv88e6xxx_phy_page_put(chip, phy);
}
return err;
}
int mv88e6xxx_phy_page_write(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 val)
{
int err;
/* There is no paging for registers 22 */
if (reg == MV88E6XXX_PHY_PAGE)
return -EINVAL;
err = mv88e6xxx_phy_page_get(chip, phy, page);
if (!err) {
err = mv88e6xxx_phy_write(chip, phy, MV88E6XXX_PHY_PAGE, page);
if (!err)
err = mv88e6xxx_phy_write(chip, phy, reg, val);
mv88e6xxx_phy_page_put(chip, phy);
}
return err;
}
static int mv88e6xxx_phy_ppu_disable(struct mv88e6xxx_chip *chip)
{
if (!chip->info->ops->ppu_disable)
return 0;
return chip->info->ops->ppu_disable(chip);
}
static int mv88e6xxx_phy_ppu_enable(struct mv88e6xxx_chip *chip)
{
if (!chip->info->ops->ppu_enable)
return 0;
return chip->info->ops->ppu_enable(chip);
}
static void mv88e6xxx_phy_ppu_reenable_work(struct work_struct *ugly)
{
struct mv88e6xxx_chip *chip;
chip = container_of(ugly, struct mv88e6xxx_chip, ppu_work);
mv88e6xxx_reg_lock(chip);
if (mutex_trylock(&chip->ppu_mutex)) {
if (mv88e6xxx_phy_ppu_enable(chip) == 0)
chip->ppu_disabled = 0;
mutex_unlock(&chip->ppu_mutex);
}
mv88e6xxx_reg_unlock(chip);
}
static void mv88e6xxx_phy_ppu_reenable_timer(struct timer_list *t)
{
struct mv88e6xxx_chip *chip = timer_container_of(chip, t, ppu_timer);
schedule_work(&chip->ppu_work);
}
static int mv88e6xxx_phy_ppu_access_get(struct mv88e6xxx_chip *chip)
{
int ret;
mutex_lock(&chip->ppu_mutex);
/* If the PHY polling unit is enabled, disable it so that
* we can access the PHY registers. If it was already
* disabled, cancel the timer that is going to re-enable
* it.
*/
if (!chip->ppu_disabled) {
ret = mv88e6xxx_phy_ppu_disable(chip);
if (ret < 0) {
mutex_unlock(&chip->ppu_mutex);
return ret;
}
chip->ppu_disabled = 1;
} else {
timer_delete(&chip->ppu_timer);
ret = 0;
}
return ret;
}
static void mv88e6xxx_phy_ppu_access_put(struct mv88e6xxx_chip *chip)
{
/* Schedule a timer to re-enable the PHY polling unit. */
mod_timer(&chip->ppu_timer, jiffies + msecs_to_jiffies(10));
mutex_unlock(&chip->ppu_mutex);
}
static void mv88e6xxx_phy_ppu_state_init(struct mv88e6xxx_chip *chip)
{
mutex_init(&chip->ppu_mutex);
INIT_WORK(&chip->ppu_work, mv88e6xxx_phy_ppu_reenable_work);
timer_setup(&chip->ppu_timer, mv88e6xxx_phy_ppu_reenable_timer, 0);
}
static void mv88e6xxx_phy_ppu_state_destroy(struct mv88e6xxx_chip *chip)
{
mutex_lock(&chip->ppu_mutex);
timer_delete_sync(&chip->ppu_timer);
cancel_work_sync(&chip->ppu_work);
mutex_unlock(&chip->ppu_mutex);
}
int mv88e6185_phy_ppu_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 *val)
{
int err;
err = mv88e6xxx_phy_ppu_access_get(chip);
if (!err) {
err = mv88e6xxx_read(chip, addr, reg, val);
mv88e6xxx_phy_ppu_access_put(chip);
}
return err;
}
int mv88e6185_phy_ppu_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 val)
{
int err;
err = mv88e6xxx_phy_ppu_access_get(chip);
if (!err) {
err = mv88e6xxx_write(chip, addr, reg, val);
mv88e6xxx_phy_ppu_access_put(chip);
}
return err;
}
void mv88e6xxx_phy_init(struct mv88e6xxx_chip *chip)
{
if (chip->info->ops->ppu_enable && chip->info->ops->ppu_disable)
mv88e6xxx_phy_ppu_state_init(chip);
}
void mv88e6xxx_phy_destroy(struct mv88e6xxx_chip *chip)
{
if (chip->info->ops->ppu_enable && chip->info->ops->ppu_disable)
mv88e6xxx_phy_ppu_state_destroy(chip);
}
int mv88e6xxx_phy_setup(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_phy_ppu_enable(chip);
}
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx PHY access
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*/
#ifndef _MV88E6XXX_PHY_H
#define _MV88E6XXX_PHY_H
#define MV88E6XXX_PHY_PAGE 0x16
#define MV88E6XXX_PHY_PAGE_COPPER 0x00
/* PHY Registers accesses implementations */
int mv88e6165_phy_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 *val);
int mv88e6165_phy_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 val);
int mv88e6185_phy_ppu_read(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 *val);
int mv88e6185_phy_ppu_write(struct mv88e6xxx_chip *chip, struct mii_bus *bus,
int addr, int reg, u16 val);
/* Generic PHY operations */
int mv88e6xxx_phy_read(struct mv88e6xxx_chip *chip, int phy,
int reg, u16 *val);
int mv88e6xxx_phy_write(struct mv88e6xxx_chip *chip, int phy,
int reg, u16 val);
int mv88e6xxx_phy_read_c45(struct mv88e6xxx_chip *chip, int phy, int devad,
int reg, u16 *val);
int mv88e6xxx_phy_write_c45(struct mv88e6xxx_chip *chip, int phy, int devad,
int reg, u16 val);
int mv88e6xxx_phy_page_read(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 *val);
int mv88e6xxx_phy_page_write(struct mv88e6xxx_chip *chip, int phy,
u8 page, int reg, u16 val);
void mv88e6xxx_phy_init(struct mv88e6xxx_chip *chip);
void mv88e6xxx_phy_destroy(struct mv88e6xxx_chip *chip);
int mv88e6xxx_phy_setup(struct mv88e6xxx_chip *chip);
#endif /*_MV88E6XXX_PHY_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,609 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx Switch Port Registers support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2016-2017 Savoir-faire Linux Inc.
* Vivien Didelot <vivien.didelot@savoirfairelinux.com>
*/
#ifndef _MV88E6XXX_PORT_H
#define _MV88E6XXX_PORT_H
#include "chip.h"
/* Offset 0x00: Port Status Register */
#define MV88E6XXX_PORT_STS 0x00
#define MV88E6XXX_PORT_STS_PAUSE_EN 0x8000
#define MV88E6XXX_PORT_STS_MY_PAUSE 0x4000
#define MV88E6XXX_PORT_STS_HD_FLOW 0x2000
#define MV88E6XXX_PORT_STS_PHY_DETECT 0x1000
#define MV88E6250_PORT_STS_LINK 0x1000
#define MV88E6250_PORT_STS_PORTMODE_MASK 0x0f00
#define MV88E6250_PORT_STS_PORTMODE_PHY_10_HALF 0x0800
#define MV88E6250_PORT_STS_PORTMODE_PHY_100_HALF 0x0900
#define MV88E6250_PORT_STS_PORTMODE_PHY_10_FULL 0x0a00
#define MV88E6250_PORT_STS_PORTMODE_PHY_100_FULL 0x0b00
/* - Modes with PHY suffix use output instead of input clock
* - Modes without RMII or RGMII use MII
* - Modes without speed do not have a fixed speed specified in the manual
* ("DC to x MHz" - variable clock support?)
*/
#define MV88E6250_PORT_STS_PORTMODE_MII_DISABLED 0x0000
#define MV88E6250_PORT_STS_PORTMODE_MII_100_RGMII 0x0100
#define MV88E6250_PORT_STS_PORTMODE_MII_DUAL_100_RMII_FULL_PHY 0x0200
#define MV88E6250_PORT_STS_PORTMODE_MII_200_RMII_FULL_PHY 0x0400
#define MV88E6250_PORT_STS_PORTMODE_MII_DUAL_100_RMII_FULL 0x0600
#define MV88E6250_PORT_STS_PORTMODE_MII_10_100_RMII_FULL 0x0700
#define MV88E6250_PORT_STS_PORTMODE_MII_HALF 0x0800
#define MV88E6250_PORT_STS_PORTMODE_MII_10_100_RMII_HALF_PHY 0x0900
#define MV88E6250_PORT_STS_PORTMODE_MII_FULL 0x0a00
#define MV88E6250_PORT_STS_PORTMODE_MII_10_100_RMII_FULL_PHY 0x0b00
#define MV88E6250_PORT_STS_PORTMODE_MII_10_HALF_PHY 0x0c00
#define MV88E6250_PORT_STS_PORTMODE_MII_100_HALF_PHY 0x0d00
#define MV88E6250_PORT_STS_PORTMODE_MII_10_FULL_PHY 0x0e00
#define MV88E6250_PORT_STS_PORTMODE_MII_100_FULL_PHY 0x0f00
#define MV88E6XXX_PORT_STS_LINK 0x0800
#define MV88E6XXX_PORT_STS_DUPLEX 0x0400
#define MV88E6XXX_PORT_STS_SPEED_MASK 0x0300
#define MV88E6XXX_PORT_STS_SPEED_10 0x0000
#define MV88E6XXX_PORT_STS_SPEED_100 0x0100
#define MV88E6XXX_PORT_STS_SPEED_1000 0x0200
#define MV88E6XXX_PORT_STS_SPEED_10000 0x0300
#define MV88E6352_PORT_STS_EEE 0x0040
#define MV88E6165_PORT_STS_AM_DIS 0x0040
#define MV88E6185_PORT_STS_MGMII 0x0040
#define MV88E6XXX_PORT_STS_TX_PAUSED 0x0020
#define MV88E6XXX_PORT_STS_FLOW_CTL 0x0010
#define MV88E6XXX_PORT_STS_CMODE_MASK 0x000f
#define MV88E6XXX_PORT_STS_CMODE_MII_PHY 0x0001
#define MV88E6XXX_PORT_STS_CMODE_MII 0x0002
#define MV88E6XXX_PORT_STS_CMODE_GMII 0x0003
#define MV88E6XXX_PORT_STS_CMODE_RMII_PHY 0x0004
#define MV88E6XXX_PORT_STS_CMODE_RMII 0x0005
#define MV88E6XXX_PORT_STS_CMODE_RGMII 0x0007
#define MV88E6XXX_PORT_STS_CMODE_100BASEX 0x0008
#define MV88E6XXX_PORT_STS_CMODE_1000BASEX 0x0009
#define MV88E6XXX_PORT_STS_CMODE_SGMII 0x000a
#define MV88E6XXX_PORT_STS_CMODE_2500BASEX 0x000b
#define MV88E6XXX_PORT_STS_CMODE_XAUI 0x000c
#define MV88E6XXX_PORT_STS_CMODE_RXAUI 0x000d
#define MV88E6393X_PORT_STS_CMODE_5GBASER 0x000c
#define MV88E6393X_PORT_STS_CMODE_10GBASER 0x000d
#define MV88E6393X_PORT_STS_CMODE_USXGMII 0x000e
#define MV88E6185_PORT_STS_CDUPLEX 0x0008
#define MV88E6185_PORT_STS_CMODE_MASK 0x0007
#define MV88E6185_PORT_STS_CMODE_GMII_FD 0x0000
#define MV88E6185_PORT_STS_CMODE_MII_100_FD_PS 0x0001
#define MV88E6185_PORT_STS_CMODE_MII_100 0x0002
#define MV88E6185_PORT_STS_CMODE_MII_10 0x0003
#define MV88E6185_PORT_STS_CMODE_SERDES 0x0004
#define MV88E6185_PORT_STS_CMODE_1000BASE_X 0x0005
#define MV88E6185_PORT_STS_CMODE_PHY 0x0006
#define MV88E6185_PORT_STS_CMODE_DISABLED 0x0007
/* Offset 0x01: MAC (or PCS or Physical) Control Register */
#define MV88E6XXX_PORT_MAC_CTL 0x01
#define MV88E6XXX_PORT_MAC_CTL_RGMII_DELAY_RXCLK 0x8000
#define MV88E6XXX_PORT_MAC_CTL_RGMII_DELAY_TXCLK 0x4000
#define MV88E6185_PORT_MAC_CTL_SYNC_OK 0x4000
#define MV88E6390_PORT_MAC_CTL_FORCE_SPEED 0x2000
#define MV88E6390_PORT_MAC_CTL_ALTSPEED 0x1000
#define MV88E6352_PORT_MAC_CTL_200BASE 0x1000
#define MV88E6XXX_PORT_MAC_CTL_EEE 0x0200
#define MV88E6XXX_PORT_MAC_CTL_FORCE_EEE 0x0100
#define MV88E6185_PORT_MAC_CTL_AN_EN 0x0400
#define MV88E6185_PORT_MAC_CTL_AN_RESTART 0x0200
#define MV88E6185_PORT_MAC_CTL_AN_DONE 0x0100
#define MV88E6XXX_PORT_MAC_CTL_FC 0x0080
#define MV88E6XXX_PORT_MAC_CTL_FORCE_FC 0x0040
#define MV88E6XXX_PORT_MAC_CTL_LINK_UP 0x0020
#define MV88E6XXX_PORT_MAC_CTL_FORCE_LINK 0x0010
#define MV88E6XXX_PORT_MAC_CTL_DUPLEX_FULL 0x0008
#define MV88E6XXX_PORT_MAC_CTL_FORCE_DUPLEX 0x0004
#define MV88E6XXX_PORT_MAC_CTL_SPEED_MASK 0x0003
#define MV88E6XXX_PORT_MAC_CTL_SPEED_10 0x0000
#define MV88E6XXX_PORT_MAC_CTL_SPEED_100 0x0001
#define MV88E6065_PORT_MAC_CTL_SPEED_200 0x0002
#define MV88E6XXX_PORT_MAC_CTL_SPEED_1000 0x0002
#define MV88E6390_PORT_MAC_CTL_SPEED_10000 0x0003
#define MV88E6XXX_PORT_MAC_CTL_SPEED_UNFORCED 0x0003
/* Offset 0x02: Jamming Control Register */
#define MV88E6097_PORT_JAM_CTL 0x02
#define MV88E6097_PORT_JAM_CTL_LIMIT_OUT_MASK 0xff00
#define MV88E6097_PORT_JAM_CTL_LIMIT_IN_MASK 0x00ff
/* Offset 0x02: Flow Control Register */
#define MV88E6390_PORT_FLOW_CTL 0x02
#define MV88E6390_PORT_FLOW_CTL_UPDATE 0x8000
#define MV88E6390_PORT_FLOW_CTL_PTR_MASK 0x7f00
#define MV88E6390_PORT_FLOW_CTL_LIMIT_IN 0x0000
#define MV88E6390_PORT_FLOW_CTL_LIMIT_OUT 0x0100
#define MV88E6390_PORT_FLOW_CTL_DATA_MASK 0x00ff
/* Offset 0x03: Switch Identifier Register */
#define MV88E6XXX_PORT_SWITCH_ID 0x03
#define MV88E6XXX_PORT_SWITCH_ID_PROD_MASK 0xfff0
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6020 0x0200
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6071 0x0710
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6085 0x04a0
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6095 0x0950
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6097 0x0990
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6190X 0x0a00
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6390X 0x0a10
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6131 0x1060
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6320 0x1150
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6123 0x1210
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6161 0x1610
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6165 0x1650
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6171 0x1710
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6172 0x1720
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6175 0x1750
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6176 0x1760
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6190 0x1900
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6191 0x1910
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6191X 0x1920
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6193X 0x1930
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6185 0x1a70
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6220 0x2200
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6240 0x2400
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6250 0x2500
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6361 0x2610
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6290 0x2900
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6321 0x3100
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6141 0x3400
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6341 0x3410
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6352 0x3520
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6350 0x3710
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6351 0x3750
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6390 0x3900
#define MV88E6XXX_PORT_SWITCH_ID_PROD_6393X 0x3930
#define MV88E6XXX_PORT_SWITCH_ID_REV_MASK 0x000f
/* Offset 0x04: Port Control Register */
#define MV88E6XXX_PORT_CTL0 0x04
#define MV88E6XXX_PORT_CTL0_USE_CORE_TAG 0x8000
#define MV88E6XXX_PORT_CTL0_SA_FILT_MASK 0xc000
#define MV88E6XXX_PORT_CTL0_SA_FILT_DISABLED 0x0000
#define MV88E6XXX_PORT_CTL0_SA_FILT_DROP_ON_LOCK 0x4000
#define MV88E6XXX_PORT_CTL0_SA_FILT_DROP_ON_UNLOCK 0x8000
#define MV88E6XXX_PORT_CTL0_SA_FILT_DROP_ON_CPU 0xc000
#define MV88E6XXX_PORT_CTL0_EGRESS_MODE_MASK 0x3000
#define MV88E6XXX_PORT_CTL0_EGRESS_MODE_UNMODIFIED 0x0000
#define MV88E6XXX_PORT_CTL0_EGRESS_MODE_UNTAGGED 0x1000
#define MV88E6XXX_PORT_CTL0_EGRESS_MODE_TAGGED 0x2000
#define MV88E6XXX_PORT_CTL0_EGRESS_MODE_ETHER_TYPE_DSA 0x3000
#define MV88E6XXX_PORT_CTL0_HEADER 0x0800
#define MV88E6XXX_PORT_CTL0_IGMP_MLD_SNOOP 0x0400
#define MV88E6XXX_PORT_CTL0_DOUBLE_TAG 0x0200
#define MV88E6XXX_PORT_CTL0_FRAME_MODE_MASK 0x0300
#define MV88E6XXX_PORT_CTL0_FRAME_MODE_NORMAL 0x0000
#define MV88E6XXX_PORT_CTL0_FRAME_MODE_DSA 0x0100
#define MV88E6XXX_PORT_CTL0_FRAME_MODE_PROVIDER 0x0200
#define MV88E6XXX_PORT_CTL0_FRAME_MODE_ETHER_TYPE_DSA 0x0300
#define MV88E6XXX_PORT_CTL0_DSA_TAG 0x0100
#define MV88E6XXX_PORT_CTL0_VLAN_TUNNEL 0x0080
#define MV88E6XXX_PORT_CTL0_TAG_IF_BOTH 0x0040
#define MV88E6185_PORT_CTL0_USE_IP 0x0020
#define MV88E6185_PORT_CTL0_USE_TAG 0x0010
#define MV88E6185_PORT_CTL0_FORWARD_UNKNOWN 0x0004
#define MV88E6352_PORT_CTL0_EGRESS_FLOODS_UC 0x0004
#define MV88E6352_PORT_CTL0_EGRESS_FLOODS_MC 0x0008
#define MV88E6XXX_PORT_CTL0_STATE_MASK 0x0003
#define MV88E6XXX_PORT_CTL0_STATE_DISABLED 0x0000
#define MV88E6XXX_PORT_CTL0_STATE_BLOCKING 0x0001
#define MV88E6XXX_PORT_CTL0_STATE_LEARNING 0x0002
#define MV88E6XXX_PORT_CTL0_STATE_FORWARDING 0x0003
/* Offset 0x05: Port Control 1 */
#define MV88E6XXX_PORT_CTL1 0x05
#define MV88E6XXX_PORT_CTL1_MESSAGE_PORT 0x8000
#define MV88E6XXX_PORT_CTL1_TRUNK_PORT 0x4000
#define MV88E6XXX_PORT_CTL1_TRUNK_ID_MASK 0x0f00
#define MV88E6XXX_PORT_CTL1_TRUNK_ID_SHIFT 8
#define MV88E6XXX_PORT_CTL1_FID_11_4_MASK 0x00ff
/* Offset 0x06: Port Based VLAN Map */
#define MV88E6XXX_PORT_BASE_VLAN 0x06
#define MV88E6XXX_PORT_BASE_VLAN_FID_3_0_MASK 0xf000
/* Offset 0x07: Default Port VLAN ID & Priority */
#define MV88E6XXX_PORT_DEFAULT_VLAN 0x07
#define MV88E6XXX_PORT_DEFAULT_VLAN_MASK 0x0fff
/* Offset 0x08: Port Control 2 Register */
#define MV88E6XXX_PORT_CTL2 0x08
#define MV88E6XXX_PORT_CTL2_IGNORE_FCS 0x8000
#define MV88E6XXX_PORT_CTL2_VTU_PRI_OVERRIDE 0x4000
#define MV88E6XXX_PORT_CTL2_SA_PRIO_OVERRIDE 0x2000
#define MV88E6XXX_PORT_CTL2_DA_PRIO_OVERRIDE 0x1000
#define MV88E6XXX_PORT_CTL2_JUMBO_MODE_MASK 0x3000
#define MV88E6XXX_PORT_CTL2_JUMBO_MODE_1522 0x0000
#define MV88E6XXX_PORT_CTL2_JUMBO_MODE_2048 0x1000
#define MV88E6XXX_PORT_CTL2_JUMBO_MODE_10240 0x2000
#define MV88E6XXX_PORT_CTL2_8021Q_MODE_MASK 0x0c00
#define MV88E6XXX_PORT_CTL2_8021Q_MODE_DISABLED 0x0000
#define MV88E6XXX_PORT_CTL2_8021Q_MODE_FALLBACK 0x0400
#define MV88E6XXX_PORT_CTL2_8021Q_MODE_CHECK 0x0800
#define MV88E6XXX_PORT_CTL2_8021Q_MODE_SECURE 0x0c00
#define MV88E6XXX_PORT_CTL2_DISCARD_TAGGED 0x0200
#define MV88E6XXX_PORT_CTL2_DISCARD_UNTAGGED 0x0100
#define MV88E6XXX_PORT_CTL2_MAP_DA 0x0080
#define MV88E6XXX_PORT_CTL2_DEFAULT_FORWARD 0x0040
#define MV88E6XXX_PORT_CTL2_EGRESS_MONITOR 0x0020
#define MV88E6XXX_PORT_CTL2_INGRESS_MONITOR 0x0010
#define MV88E6095_PORT_CTL2_CPU_PORT_MASK 0x000f
/* Offset 0x09: Egress Rate Control */
#define MV88E6XXX_PORT_EGRESS_RATE_CTL1 0x09
/* Offset 0x0A: Egress Rate Control 2 */
#define MV88E6XXX_PORT_EGRESS_RATE_CTL2 0x0a
/* Offset 0x0B: Port Association Vector */
#define MV88E6XXX_PORT_ASSOC_VECTOR 0x0b
#define MV88E6XXX_PORT_ASSOC_VECTOR_HOLD_AT_1 0x8000
#define MV88E6XXX_PORT_ASSOC_VECTOR_INT_AGE_OUT 0x4000
#define MV88E6XXX_PORT_ASSOC_VECTOR_LOCKED_PORT 0x2000
#define MV88E6XXX_PORT_ASSOC_VECTOR_IGNORE_WRONG 0x1000
#define MV88E6XXX_PORT_ASSOC_VECTOR_REFRESH_LOCKED 0x0800
/* Offset 0x0C: Port ATU Control */
#define MV88E6XXX_PORT_ATU_CTL 0x0c
/* Offset 0x0D: Priority Override Register */
#define MV88E6XXX_PORT_PRI_OVERRIDE 0x0d
#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_MASK 0x0003
#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_48_BYTE 0x0001
#define MV88E6XXX_PORT_PRI_OVERRIDE_TCAM_MODE_96_BYTE 0x0002
/* Offset 0x0E: Policy Control Register */
#define MV88E6XXX_PORT_POLICY_CTL 0x0e
#define MV88E6XXX_PORT_POLICY_CTL_DA_MASK 0xc000
#define MV88E6XXX_PORT_POLICY_CTL_SA_MASK 0x3000
#define MV88E6XXX_PORT_POLICY_CTL_VTU_MASK 0x0c00
#define MV88E6XXX_PORT_POLICY_CTL_ETYPE_MASK 0x0300
#define MV88E6XXX_PORT_POLICY_CTL_PPPOE_MASK 0x00c0
#define MV88E6XXX_PORT_POLICY_CTL_VBAS_MASK 0x0030
#define MV88E6XXX_PORT_POLICY_CTL_OPT82_MASK 0x000c
#define MV88E6XXX_PORT_POLICY_CTL_UDP_MASK 0x0003
#define MV88E6XXX_PORT_POLICY_CTL_NORMAL 0x0000
#define MV88E6XXX_PORT_POLICY_CTL_MIRROR 0x0001
#define MV88E6XXX_PORT_POLICY_CTL_TRAP 0x0002
#define MV88E6XXX_PORT_POLICY_CTL_DISCARD 0x0003
/* Offset 0x0E: Policy & MGMT Control Register (FAMILY_6393X) */
#define MV88E6393X_PORT_POLICY_MGMT_CTL 0x0e
#define MV88E6393X_PORT_POLICY_MGMT_CTL_UPDATE 0x8000
#define MV88E6393X_PORT_POLICY_MGMT_CTL_PTR_MASK 0x3f00
#define MV88E6393X_PORT_POLICY_MGMT_CTL_DATA_MASK 0x00ff
#define MV88E6393X_PORT_POLICY_MGMT_CTL_PTR_01C280000000XLO 0x2000
#define MV88E6393X_PORT_POLICY_MGMT_CTL_PTR_01C280000000XHI 0x2100
#define MV88E6393X_PORT_POLICY_MGMT_CTL_PTR_01C280000002XLO 0x2400
#define MV88E6393X_PORT_POLICY_MGMT_CTL_PTR_01C280000002XHI 0x2500
#define MV88E6393X_PORT_POLICY_MGMT_CTL_PTR_INGRESS_DEST 0x3000
#define MV88E6393X_PORT_POLICY_MGMT_CTL_PTR_CPU_DEST 0x3800
#define MV88E6393X_PORT_POLICY_MGMT_CTL_CPU_DEST_MGMTPRI 0x00e0
/* Offset 0x0F: Port Special Ether Type */
#define MV88E6XXX_PORT_ETH_TYPE 0x0f
#define MV88E6XXX_PORT_ETH_TYPE_DEFAULT 0x9100
/* Offset 0x10: InDiscards Low Counter */
#define MV88E6XXX_PORT_IN_DISCARD_LO 0x10
/* Offset 0x10: Extended Port Control Command */
#define MV88E6393X_PORT_EPC_CMD 0x10
#define MV88E6393X_PORT_EPC_CMD_BUSY 0x8000
#define MV88E6393X_PORT_EPC_CMD_WRITE 0x3000
#define MV88E6393X_PORT_EPC_INDEX_PORT_ETYPE 0x02
/* Offset 0x11: Extended Port Control Data */
#define MV88E6393X_PORT_EPC_DATA 0x11
/* Offset 0x11: InDiscards High Counter */
#define MV88E6XXX_PORT_IN_DISCARD_HI 0x11
/* Offset 0x12: InFiltered Counter */
#define MV88E6XXX_PORT_IN_FILTERED 0x12
/* Offset 0x13: OutFiltered Counter */
#define MV88E6XXX_PORT_OUT_FILTERED 0x13
/* Offset 0x16: LED Control */
#define MV88E6XXX_PORT_LED_CONTROL 0x16
#define MV88E6XXX_PORT_LED_CONTROL_UPDATE BIT(15)
#define MV88E6XXX_PORT_LED_CONTROL_POINTER_MASK GENMASK(14, 12)
#define MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL (0x00 << 12) /* Control for LED 0 and 1 */
#define MV88E6XXX_PORT_LED_CONTROL_POINTER_STRETCH_BLINK (0x06 << 12) /* Stetch and Blink Rate */
#define MV88E6XXX_PORT_LED_CONTROL_POINTER_CNTL_SPECIAL (0x07 << 12) /* Control for the Port's Special LED */
#define MV88E6XXX_PORT_LED_CONTROL_DATA_MASK GENMASK(10, 0)
/* Selection masks valid for either port 1,2,3,4 or 5 */
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK GENMASK(3, 0)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK GENMASK(7, 4)
/* Selection control for LED 0 and 1, ports 5 and 6 only has LED 0
* Bits Function
* 0..3 LED 0 control selector on ports 1-5
* 4..7 LED 1 control selector on ports 1-4 on port 5 this controls LED 0 of port 6
*
* Sel Port LED Function for the 6352 family:
* 0 1-4 0 Link/Act/Speed by Blink Rate (off=no link, on=link, blink=activity, blink speed=link speed)
* 1-4 1 Port 2's Special LED
* 5-6 0 Port 5 Link/Act (off=no link, on=link, blink=activity)
* 5-6 1 Port 6 Link/Act (off=no link, on=link 1000, blink=activity)
* 1 1-4 0 100/1000 Link/Act (off=no link, on=100 or 1000 link, blink=activity)
* 1-4 1 10/100 Link Act (off=no link, on=10 or 100 link, blink=activity)
* 5-6 0 Fiber 100 Link/Act (off=no link, on=link 100, blink=activity)
* 5-6 1 Fiber 1000 Link/Act (off=no link, on=link 1000, blink=activity)
* 2 1-4 0 1000 Link/Act (off=no link, on=link 1000, blink=activity)
* 1-4 1 10/100 Link/Act (off=no link, on=10 or 100 link, blink=activity)
* 5-6 0 Fiber 1000 Link/Act (off=no link, on=link 1000, blink=activity)
* 5-6 1 Fiber 100 Link/Act (off=no link, on=link 100, blink=activity)
* 3 1-4 0 Link/Act (off=no link, on=link, blink=activity)
* 1-4 1 1000 Link (off=no link, on=1000 link)
* 5-6 0 Port 0's Special LED
* 5-6 1 Fiber Link (off=no link, on=link)
* 4 1-4 0 Port 0's Special LED
* 1-4 1 Port 1's Special LED
* 5-6 0 Port 1's Special LED
* 5-6 1 Port 5 Link/Act (off=no link, on=link, blink=activity)
* 5 1-4 0 Reserved
* 1-4 1 Reserved
* 5-6 0 Port 2's Special LED
* 5-6 1 Port 6 Link (off=no link, on=link)
* 6 1-4 0 Duplex/Collision (off=half-duplex,on=full-duplex,blink=collision)
* 1-4 1 10/1000 Link/Act (off=no link, on=10 or 1000 link, blink=activity)
* 5-6 0 Port 5 Duplex/Collision (off=half-duplex, on=full-duplex, blink=col)
* 5-6 1 Port 6 Duplex/Collision (off=half-duplex, on=full-duplex, blink=col)
* 7 1-4 0 10/1000 Link/Act (off=no link, on=10 or 1000 link, blink=activity)
* 1-4 1 10/1000 Link (off=no link, on=10 or 1000 link)
* 5-6 0 Port 5 Link/Act/Speed by Blink rate (off=no link, on=link, blink=activity, blink speed=link speed)
* 5-6 1 Port 6 Link/Act/Speed by Blink rate (off=no link, on=link, blink=activity, blink speed=link speed)
* 8 1-4 0 Link (off=no link, on=link)
* 1-4 1 Activity (off=no link, blink on=activity)
* 5-6 0 Port 6 Link/Act (off=no link, on=link, blink=activity)
* 5-6 1 Port 0's Special LED
* 9 1-4 0 10 Link (off=no link, on=10 link)
* 1-4 1 100 Link (off=no link, on=100 link)
* 5-6 0 Reserved
* 5-6 1 Port 1's Special LED
* a 1-4 0 10 Link/Act (off=no link, on=10 link, blink=activity)
* 1-4 1 100 Link/Act (off=no link, on=100 link, blink=activity)
* 5-6 0 Reserved
* 5-6 1 Port 2's Special LED
* b 1-4 0 100/1000 Link (off=no link, on=100 or 1000 link)
* 1-4 1 10/100 Link (off=no link, on=100 link, blink=activity)
* 5-6 0 Reserved
* 5-6 1 Reserved
* c * * PTP Act (blink on=PTP activity)
* d * * Force Blink
* e * * Force Off
* f * * Force On
*/
/* Select LED0 output */
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL0 0x0
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1 0x1
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2 0x2
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL3 0x3
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL4 0x4
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL5 0x5
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6 0x6
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL7 0x7
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8 0x8
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL9 0x9
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELA 0xa
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELB 0xb
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELC 0xc
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELD 0xd
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELE 0xe
#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELF 0xf
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL0 (0x0 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1 (0x1 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2 (0x2 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3 (0x3 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL4 (0x4 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL5 (0x5 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6 (0x6 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL7 (0x7 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL8 (0x8 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL9 (0x9 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELA (0xa << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELB (0xb << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELC (0xc << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELD (0xd << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELE (0xe << 4)
#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELF (0xf << 4)
/* Stretch and Blink Rate Control (Index 0x06 of LED Control) */
/* Pulse Stretch Selection for all LED's on this port */
#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_NONE (0 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS (1 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_42MS (2 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_84MS (3 << 4)
#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_168MS (4 << 4)
/* Blink Rate Selection for all LEDs on this port */
#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_21MS 0
#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_42MS 1
#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_84MS 2
#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_168MS 3
#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_336MS 4
#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_672MS 5
/* Control for Special LED (Index 0x7 of LED Control on Port0) */
#define MV88E6XXX_PORT_LED_CONTROL_0x07_P0_LAN_LINKACT_SHIFT 0 /* bits 6:0 LAN Link Activity LED */
/* Control for Special LED (Index 0x7 of LED Control on Port 1) */
#define MV88E6XXX_PORT_LED_CONTROL_0x07_P1_WAN_LINKACT_SHIFT 0 /* bits 6:0 WAN Link Activity LED */
/* Control for Special LED (Index 0x7 of LED Control on Port 2) */
#define MV88E6XXX_PORT_LED_CONTROL_0x07_P2_PTP_ACT 0 /* bits 6:0 PTP Activity */
/* Offset 0x18: IEEE Priority Mapping Table */
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE 0x18
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_UPDATE 0x8000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_MASK 0x7000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_INGRESS_PCP 0x0000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_EGRESS_GREEN_PCP 0x1000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_EGRESS_YELLOW_PCP 0x2000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_EGRESS_AVB_PCP 0x3000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_EGRESS_GREEN_DSCP 0x5000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_EGRESS_YELLOW_DSCP 0x6000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_EGRESS_AVB_DSCP 0x7000
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_PTR_MASK 0x0e00
#define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_DATA_MASK 0x01ff
/* Offset 0x18: Port IEEE Priority Remapping Registers (0-3) */
#define MV88E6095_PORT_IEEE_PRIO_REMAP_0123 0x18
/* Offset 0x19: Port IEEE Priority Remapping Registers (4-7) */
#define MV88E6095_PORT_IEEE_PRIO_REMAP_4567 0x19
/* Offset 0x1a: Magic undocumented errata register */
#define MV88E6XXX_PORT_RESERVED_1A 0x1a
#define MV88E6XXX_PORT_RESERVED_1A_BUSY 0x8000
#define MV88E6XXX_PORT_RESERVED_1A_WRITE 0x4000
#define MV88E6XXX_PORT_RESERVED_1A_READ 0x0000
#define MV88E6XXX_PORT_RESERVED_1A_PORT_SHIFT 5
#define MV88E6XXX_PORT_RESERVED_1A_BLOCK_SHIFT 10
#define MV88E6XXX_PORT_RESERVED_1A_CTRL_PORT 0x04
#define MV88E6XXX_PORT_RESERVED_1A_DATA_PORT 0x05
#define MV88E6341_PORT_RESERVED_1A_FORCE_CMODE 0x8000
#define MV88E6341_PORT_RESERVED_1A_SGMII_AN 0x2000
int mv88e6xxx_port_read(struct mv88e6xxx_chip *chip, int port, int reg,
u16 *val);
int mv88e6xxx_port_write(struct mv88e6xxx_chip *chip, int port, int reg,
u16 val);
int mv88e6xxx_port_wait_bit(struct mv88e6xxx_chip *chip, int port, int reg,
int bit, int val);
int mv88e6185_port_set_pause(struct mv88e6xxx_chip *chip, int port,
int pause);
int mv88e6320_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6352_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6390_port_set_rgmii_delay(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6xxx_port_set_link(struct mv88e6xxx_chip *chip, int port, int link);
int mv88e6xxx_port_sync_link(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup);
int mv88e6185_port_sync_link(struct mv88e6xxx_chip *chip, int port, unsigned int mode, bool isup);
int mv88e6185_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int mv88e6250_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int mv88e6320_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int mv88e6341_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int mv88e6352_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int mv88e6390_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int mv88e6390x_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int mv88e6393x_port_set_speed_duplex(struct mv88e6xxx_chip *chip, int port,
int speed, int duplex);
int mv88e6xxx_port_set_state(struct mv88e6xxx_chip *chip, int port, u8 state);
int mv88e6xxx_port_set_vlan_map(struct mv88e6xxx_chip *chip, int port, u16 map);
int mv88e6xxx_port_get_fid(struct mv88e6xxx_chip *chip, int port, u16 *fid);
int mv88e6xxx_port_set_fid(struct mv88e6xxx_chip *chip, int port, u16 fid);
int mv88e6xxx_port_get_pvid(struct mv88e6xxx_chip *chip, int port, u16 *pvid);
int mv88e6xxx_port_set_pvid(struct mv88e6xxx_chip *chip, int port, u16 pvid);
int mv88e6xxx_port_set_lock(struct mv88e6xxx_chip *chip, int port,
bool locked);
int mv88e6xxx_port_set_8021q_mode(struct mv88e6xxx_chip *chip, int port,
u16 mode);
int mv88e6095_port_tag_remap(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_port_tag_remap(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_port_set_egress_mode(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_egress_mode mode);
int mv88e6085_port_set_frame_mode(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_frame_mode mode);
int mv88e6351_port_set_frame_mode(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_frame_mode mode);
int mv88e6185_port_set_forward_unknown(struct mv88e6xxx_chip *chip,
int port, bool unicast);
int mv88e6185_port_set_default_forward(struct mv88e6xxx_chip *chip,
int port, bool multicast);
int mv88e6352_port_set_ucast_flood(struct mv88e6xxx_chip *chip, int port,
bool unicast);
int mv88e6352_port_set_mcast_flood(struct mv88e6xxx_chip *chip, int port,
bool multicast);
int mv88e6352_port_set_policy(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_policy_mapping mapping,
enum mv88e6xxx_policy_action action);
int mv88e6393x_port_set_policy(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_policy_mapping mapping,
enum mv88e6xxx_policy_action action);
int mv88e6351_port_set_ether_type(struct mv88e6xxx_chip *chip, int port,
u16 etype);
int mv88e6393x_set_egress_port(struct mv88e6xxx_chip *chip,
enum mv88e6xxx_egress_direction direction,
int port);
int mv88e6393x_port_set_upstream_port(struct mv88e6xxx_chip *chip, int port,
int upstream_port);
int mv88e6393x_port_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip);
int mv88e6393x_port_set_ether_type(struct mv88e6xxx_chip *chip, int port,
u16 etype);
int mv88e6xxx_port_set_message_port(struct mv88e6xxx_chip *chip, int port,
bool message_port);
int mv88e6xxx_port_set_trunk(struct mv88e6xxx_chip *chip, int port,
bool trunk, u8 id);
int mv88e6165_port_set_jumbo_size(struct mv88e6xxx_chip *chip, int port,
size_t size);
int mv88e6095_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port);
int mv88e6097_port_egress_rate_limiting(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_port_set_assoc_vector(struct mv88e6xxx_chip *chip, int port,
u16 pav);
int mv88e6097_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
u8 out);
int mv88e6390_port_pause_limit(struct mv88e6xxx_chip *chip, int port, u8 in,
u8 out);
int mv88e6341_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6390_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6390x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6393x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
phy_interface_t mode);
int mv88e6185_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
int mv88e6352_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
#ifdef CONFIG_NET_DSA_MV88E6XXX_LEDS
int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port);
#else
static inline int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip,
int port)
{
return 0;
}
#endif
int mv88e6xxx_port_drop_untagged(struct mv88e6xxx_chip *chip, int port,
bool drop_untagged);
int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port, bool map);
int mv88e6095_port_set_upstream_port(struct mv88e6xxx_chip *chip, int port,
int upstream_port);
int mv88e6xxx_port_set_mirror(struct mv88e6xxx_chip *chip, int port,
enum mv88e6xxx_egress_direction direction,
bool mirror);
int mv88e6xxx_port_disable_learn_limit(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_port_disable_pri_override(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_port_hidden_write(struct mv88e6xxx_chip *chip, int block,
int port, int reg, u16 val);
int mv88e6xxx_port_hidden_wait(struct mv88e6xxx_chip *chip);
int mv88e6xxx_port_hidden_read(struct mv88e6xxx_chip *chip, int block, int port,
int reg, u16 *val);
int mv88e6xxx_port_enable_tcam(struct mv88e6xxx_chip *chip, int port);
#endif /* _MV88E6XXX_PORT_H */
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch Hidden Registers support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2019 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/bitfield.h>
#include "chip.h"
#include "port.h"
/* The mv88e6390 and mv88e6341 have some hidden registers used for debug and
* development. The errata also makes use of them.
*/
int mv88e6xxx_port_hidden_write(struct mv88e6xxx_chip *chip, int block,
int port, int reg, u16 val)
{
u16 ctrl;
int err;
err = mv88e6xxx_port_write(chip, MV88E6XXX_PORT_RESERVED_1A_DATA_PORT,
MV88E6XXX_PORT_RESERVED_1A, val);
if (err)
return err;
ctrl = MV88E6XXX_PORT_RESERVED_1A_BUSY |
MV88E6XXX_PORT_RESERVED_1A_WRITE |
block << MV88E6XXX_PORT_RESERVED_1A_BLOCK_SHIFT |
port << MV88E6XXX_PORT_RESERVED_1A_PORT_SHIFT |
reg;
return mv88e6xxx_port_write(chip, MV88E6XXX_PORT_RESERVED_1A_CTRL_PORT,
MV88E6XXX_PORT_RESERVED_1A, ctrl);
}
int mv88e6xxx_port_hidden_wait(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6XXX_PORT_RESERVED_1A_BUSY);
return mv88e6xxx_port_wait_bit(chip,
MV88E6XXX_PORT_RESERVED_1A_CTRL_PORT,
MV88E6XXX_PORT_RESERVED_1A, bit, 0);
}
int mv88e6xxx_port_hidden_read(struct mv88e6xxx_chip *chip, int block, int port,
int reg, u16 *val)
{
u16 ctrl;
int err;
ctrl = MV88E6XXX_PORT_RESERVED_1A_BUSY |
MV88E6XXX_PORT_RESERVED_1A_READ |
block << MV88E6XXX_PORT_RESERVED_1A_BLOCK_SHIFT |
port << MV88E6XXX_PORT_RESERVED_1A_PORT_SHIFT |
reg;
err = mv88e6xxx_port_write(chip, MV88E6XXX_PORT_RESERVED_1A_CTRL_PORT,
MV88E6XXX_PORT_RESERVED_1A, ctrl);
if (err)
return err;
err = mv88e6xxx_port_hidden_wait(chip);
if (err)
return err;
return mv88e6xxx_port_read(chip, MV88E6XXX_PORT_RESERVED_1A_DATA_PORT,
MV88E6XXX_PORT_RESERVED_1A, val);
}
+565
View File
@@ -0,0 +1,565 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch PTP support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 National Instruments
* Erik Hons <erik.hons@ni.com>
* Brandon Streiff <brandon.streiff@ni.com>
* Dane Wagner <dane.wagner@ni.com>
*/
#include "chip.h"
#include "global1.h"
#include "global2.h"
#include "hwtstamp.h"
#include "ptp.h"
#define MV88E6XXX_MAX_ADJ_PPB 1000000
struct mv88e6xxx_cc_coeffs {
u32 cc_shift;
u32 cc_mult;
u32 cc_mult_num;
u32 cc_mult_dem;
};
/* Family MV88E6250:
* Raw timestamps are in units of 10-ns clock periods.
*
* clkadj = scaled_ppm * 10*2^28 / (10^6 * 2^16)
* simplifies to
* clkadj = scaled_ppm * 2^7 / 5^5
*/
#define MV88E6XXX_CC_10NS_SHIFT 28
static const struct mv88e6xxx_cc_coeffs mv88e6xxx_cc_10ns_coeffs = {
.cc_shift = MV88E6XXX_CC_10NS_SHIFT,
.cc_mult = 10 << MV88E6XXX_CC_10NS_SHIFT,
.cc_mult_num = 1 << 7,
.cc_mult_dem = 3125ULL,
};
/* Other families except MV88E6393X in internal clock mode:
* Raw timestamps are in units of 8-ns clock periods.
*
* clkadj = scaled_ppm * 8*2^28 / (10^6 * 2^16)
* simplifies to
* clkadj = scaled_ppm * 2^9 / 5^6
*/
#define MV88E6XXX_CC_8NS_SHIFT 28
static const struct mv88e6xxx_cc_coeffs mv88e6xxx_cc_8ns_coeffs = {
.cc_shift = MV88E6XXX_CC_8NS_SHIFT,
.cc_mult = 8 << MV88E6XXX_CC_8NS_SHIFT,
.cc_mult_num = 1 << 9,
.cc_mult_dem = 15625ULL
};
/* Family MV88E6393X using internal clock:
* Raw timestamps are in units of 4-ns clock periods.
*
* clkadj = scaled_ppm * 4*2^28 / (10^6 * 2^16)
* simplifies to
* clkadj = scaled_ppm * 2^8 / 5^6
*/
#define MV88E6XXX_CC_4NS_SHIFT 28
static const struct mv88e6xxx_cc_coeffs mv88e6xxx_cc_4ns_coeffs = {
.cc_shift = MV88E6XXX_CC_4NS_SHIFT,
.cc_mult = 4 << MV88E6XXX_CC_4NS_SHIFT,
.cc_mult_num = 1 << 8,
.cc_mult_dem = 15625ULL
};
#define TAI_EVENT_WORK_INTERVAL msecs_to_jiffies(100)
#define cc_to_chip(cc) container_of(cc, struct mv88e6xxx_chip, tstamp_cc)
#define dw_overflow_to_chip(dw) container_of(dw, struct mv88e6xxx_chip, \
overflow_work)
#define dw_tai_event_to_chip(dw) container_of(dw, struct mv88e6xxx_chip, \
tai_event_work)
static int mv88e6xxx_tai_read(struct mv88e6xxx_chip *chip, int addr,
u16 *data, int len)
{
if (!chip->info->ops->avb_ops->tai_read)
return -EOPNOTSUPP;
return chip->info->ops->avb_ops->tai_read(chip, addr, data, len);
}
static int mv88e6xxx_tai_write(struct mv88e6xxx_chip *chip, int addr, u16 data)
{
if (!chip->info->ops->avb_ops->tai_write)
return -EOPNOTSUPP;
return chip->info->ops->avb_ops->tai_write(chip, addr, data);
}
/* TODO: places where this are called should be using pinctrl */
static int mv88e6352_set_gpio_func(struct mv88e6xxx_chip *chip, int pin,
int func, int input)
{
int err;
if (!chip->info->ops->gpio_ops)
return -EOPNOTSUPP;
err = chip->info->ops->gpio_ops->set_dir(chip, pin, input);
if (err)
return err;
return chip->info->ops->gpio_ops->set_pctl(chip, pin, func);
}
static const struct mv88e6xxx_cc_coeffs *
mv88e6xxx_cc_coeff_get(struct mv88e6xxx_chip *chip)
{
u16 period_ps;
int err;
err = mv88e6xxx_tai_read(chip, MV88E6XXX_TAI_CLOCK_PERIOD, &period_ps, 1);
if (err) {
dev_err(chip->dev, "failed to read cycle counter period: %d\n",
err);
return ERR_PTR(err);
}
switch (period_ps) {
case 4000:
return &mv88e6xxx_cc_4ns_coeffs;
case 8000:
return &mv88e6xxx_cc_8ns_coeffs;
case 10000:
return &mv88e6xxx_cc_10ns_coeffs;
default:
dev_err(chip->dev, "unexpected cycle counter period of %u ps\n",
period_ps);
return ERR_PTR(-ENODEV);
}
}
static u64 mv88e6352_ptp_clock_read(struct cyclecounter *cc)
{
struct mv88e6xxx_chip *chip = cc_to_chip(cc);
u16 phc_time[2];
int err;
err = mv88e6xxx_tai_read(chip, MV88E6352_TAI_TIME_LO, phc_time,
ARRAY_SIZE(phc_time));
if (err)
return 0;
else
return ((u32)phc_time[1] << 16) | phc_time[0];
}
static u64 mv88e6165_ptp_clock_read(struct cyclecounter *cc)
{
struct mv88e6xxx_chip *chip = cc_to_chip(cc);
u16 phc_time[2];
int err;
err = mv88e6xxx_tai_read(chip, MV88E6165_PTP_GC_TIME_LO, phc_time,
ARRAY_SIZE(phc_time));
if (err)
return 0;
else
return ((u32)phc_time[1] << 16) | phc_time[0];
}
/* mv88e6352_config_eventcap - configure TAI event capture
* @rising: zero for falling-edge trigger, else rising-edge trigger
*
* This will also reset the capture sequence counter.
*/
static int mv88e6352_config_eventcap(struct mv88e6xxx_chip *chip, int rising)
{
u16 evcap_config;
int err;
evcap_config = MV88E6352_TAI_CFG_CAP_OVERWRITE |
MV88E6352_TAI_CFG_CAP_CTR_START;
if (!rising)
evcap_config |= MV88E6352_TAI_CFG_EVREQ_FALLING;
err = mv88e6xxx_tai_write(chip, MV88E6352_TAI_CFG, evcap_config);
if (err)
return err;
/* Write the capture config; this also clears the capture counter */
return mv88e6xxx_tai_write(chip, MV88E6352_TAI_EVENT_STATUS, 0);
}
static void mv88e6352_tai_event_work(struct work_struct *ugly)
{
struct delayed_work *dw = to_delayed_work(ugly);
struct mv88e6xxx_chip *chip = dw_tai_event_to_chip(dw);
struct ptp_clock_event ev;
u16 status[4];
u32 raw_ts;
int err;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_tai_read(chip, MV88E6352_TAI_EVENT_STATUS,
status, ARRAY_SIZE(status));
mv88e6xxx_reg_unlock(chip);
if (err) {
dev_err(chip->dev, "failed to read TAI status register\n");
return;
}
if (status[0] & MV88E6352_TAI_EVENT_STATUS_ERROR) {
dev_warn(chip->dev, "missed event capture\n");
return;
}
if (!(status[0] & MV88E6352_TAI_EVENT_STATUS_VALID))
goto out;
raw_ts = ((u32)status[2] << 16) | status[1];
/* Clear the valid bit so the next timestamp can come in */
status[0] &= ~MV88E6352_TAI_EVENT_STATUS_VALID;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_tai_write(chip, MV88E6352_TAI_EVENT_STATUS, status[0]);
mv88e6xxx_reg_unlock(chip);
if (err) {
dev_err(chip->dev, "failed to write TAI status register\n");
return;
}
/* This is an external timestamp */
ev.type = PTP_CLOCK_EXTTS;
/* We only have one timestamping channel. */
ev.index = 0;
mv88e6xxx_reg_lock(chip);
ev.timestamp = timecounter_cyc2time(&chip->tstamp_tc, raw_ts);
mv88e6xxx_reg_unlock(chip);
ptp_clock_event(chip->ptp_clock, &ev);
out:
schedule_delayed_work(&chip->tai_event_work, TAI_EVENT_WORK_INTERVAL);
}
static int mv88e6xxx_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
int neg_adj = 0;
u32 diff, mult;
u64 adj;
if (scaled_ppm < 0) {
neg_adj = 1;
scaled_ppm = -scaled_ppm;
}
mult = chip->cc_coeffs->cc_mult;
adj = chip->cc_coeffs->cc_mult_num;
adj *= scaled_ppm;
diff = div_u64(adj, chip->cc_coeffs->cc_mult_dem);
mv88e6xxx_reg_lock(chip);
timecounter_read(&chip->tstamp_tc);
chip->tstamp_cc.mult = neg_adj ? mult - diff : mult + diff;
mv88e6xxx_reg_unlock(chip);
return 0;
}
static int mv88e6xxx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
mv88e6xxx_reg_lock(chip);
timecounter_adjtime(&chip->tstamp_tc, delta);
mv88e6xxx_reg_unlock(chip);
return 0;
}
static int mv88e6xxx_ptp_gettime(struct ptp_clock_info *ptp,
struct timespec64 *ts)
{
struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
u64 ns;
mv88e6xxx_reg_lock(chip);
ns = timecounter_read(&chip->tstamp_tc);
mv88e6xxx_reg_unlock(chip);
*ts = ns_to_timespec64(ns);
return 0;
}
static int mv88e6xxx_ptp_settime(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
u64 ns;
ns = timespec64_to_ns(ts);
mv88e6xxx_reg_lock(chip);
timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc, ns);
mv88e6xxx_reg_unlock(chip);
return 0;
}
static int mv88e6352_ptp_enable_extts(struct mv88e6xxx_chip *chip,
struct ptp_clock_request *rq, int on)
{
int rising = (rq->extts.flags & PTP_RISING_EDGE);
int func;
int pin;
int err;
/* Reject requests to enable time stamping on both edges. */
if ((rq->extts.flags & PTP_STRICT_FLAGS) &&
(rq->extts.flags & PTP_ENABLE_FEATURE) &&
(rq->extts.flags & PTP_EXTTS_EDGES) == PTP_EXTTS_EDGES)
return -EOPNOTSUPP;
pin = ptp_find_pin(chip->ptp_clock, PTP_PF_EXTTS, rq->extts.index);
if (pin < 0)
return -EBUSY;
mv88e6xxx_reg_lock(chip);
if (on) {
func = MV88E6352_G2_SCRATCH_GPIO_PCTL_EVREQ;
err = mv88e6352_set_gpio_func(chip, pin, func, true);
if (err)
goto out;
schedule_delayed_work(&chip->tai_event_work,
TAI_EVENT_WORK_INTERVAL);
err = mv88e6352_config_eventcap(chip, rising);
} else {
func = MV88E6352_G2_SCRATCH_GPIO_PCTL_GPIO;
err = mv88e6352_set_gpio_func(chip, pin, func, true);
cancel_delayed_work_sync(&chip->tai_event_work);
}
out:
mv88e6xxx_reg_unlock(chip);
return err;
}
static int mv88e6352_ptp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
struct mv88e6xxx_chip *chip = ptp_to_chip(ptp);
switch (rq->type) {
case PTP_CLK_REQ_EXTTS:
return mv88e6352_ptp_enable_extts(chip, rq, on);
default:
return -EOPNOTSUPP;
}
}
static int mv88e6352_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan)
{
switch (func) {
case PTP_PF_NONE:
case PTP_PF_EXTTS:
break;
case PTP_PF_PEROUT:
case PTP_PF_PHYSYNC:
return -EOPNOTSUPP;
}
return 0;
}
const struct mv88e6xxx_ptp_ops mv88e6165_ptp_ops = {
.clock_read = mv88e6165_ptp_clock_read,
.global_enable = mv88e6165_global_enable,
.global_disable = mv88e6165_global_disable,
.arr0_sts_reg = MV88E6165_PORT_PTP_ARR0_STS,
.arr1_sts_reg = MV88E6165_PORT_PTP_ARR1_STS,
.dep_sts_reg = MV88E6165_PORT_PTP_DEP_STS,
.rx_filters = (1 << HWTSTAMP_FILTER_NONE) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ) |
(1 << HWTSTAMP_FILTER_PTP_V2_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ),
};
const struct mv88e6xxx_ptp_ops mv88e6352_ptp_ops = {
.clock_read = mv88e6352_ptp_clock_read,
.ptp_enable = mv88e6352_ptp_enable,
.ptp_verify = mv88e6352_ptp_verify,
.event_work = mv88e6352_tai_event_work,
.port_enable = mv88e6352_hwtstamp_port_enable,
.port_disable = mv88e6352_hwtstamp_port_disable,
.n_ext_ts = 1,
.arr0_sts_reg = MV88E6XXX_PORT_PTP_ARR0_STS,
.arr1_sts_reg = MV88E6XXX_PORT_PTP_ARR1_STS,
.dep_sts_reg = MV88E6XXX_PORT_PTP_DEP_STS,
.rx_filters = (1 << HWTSTAMP_FILTER_NONE) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ) |
(1 << HWTSTAMP_FILTER_PTP_V2_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ),
};
const struct mv88e6xxx_ptp_ops mv88e6390_ptp_ops = {
.clock_read = mv88e6352_ptp_clock_read,
.ptp_enable = mv88e6352_ptp_enable,
.ptp_verify = mv88e6352_ptp_verify,
.event_work = mv88e6352_tai_event_work,
.port_enable = mv88e6352_hwtstamp_port_enable,
.port_disable = mv88e6352_hwtstamp_port_disable,
.set_ptp_cpu_port = mv88e6390_g1_set_ptp_cpu_port,
.n_ext_ts = 1,
.arr0_sts_reg = MV88E6XXX_PORT_PTP_ARR0_STS,
.arr1_sts_reg = MV88E6XXX_PORT_PTP_ARR1_STS,
.dep_sts_reg = MV88E6XXX_PORT_PTP_DEP_STS,
.rx_filters = (1 << HWTSTAMP_FILTER_NONE) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ) |
(1 << HWTSTAMP_FILTER_PTP_V2_EVENT) |
(1 << HWTSTAMP_FILTER_PTP_V2_SYNC) |
(1 << HWTSTAMP_FILTER_PTP_V2_DELAY_REQ),
};
static u64 mv88e6xxx_ptp_clock_read(struct cyclecounter *cc)
{
struct mv88e6xxx_chip *chip = cc_to_chip(cc);
if (chip->info->ops->ptp_ops->clock_read)
return chip->info->ops->ptp_ops->clock_read(cc);
return 0;
}
/* With a 250MHz input clock, the 32-bit timestamp counter overflows in ~17.2
* seconds; this task forces periodic reads so that we don't miss any.
*/
#define MV88E6XXX_TAI_OVERFLOW_PERIOD (HZ * 8)
static void mv88e6xxx_ptp_overflow_check(struct work_struct *work)
{
struct delayed_work *dw = to_delayed_work(work);
struct mv88e6xxx_chip *chip = dw_overflow_to_chip(dw);
struct timespec64 ts;
mv88e6xxx_ptp_gettime(&chip->ptp_clock_info, &ts);
schedule_delayed_work(&chip->overflow_work,
MV88E6XXX_TAI_OVERFLOW_PERIOD);
}
int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip)
{
const struct mv88e6xxx_ptp_ops *ptp_ops = chip->info->ops->ptp_ops;
int i;
/* Set up the cycle counter */
chip->cc_coeffs = mv88e6xxx_cc_coeff_get(chip);
if (IS_ERR(chip->cc_coeffs))
return PTR_ERR(chip->cc_coeffs);
memset(&chip->tstamp_cc, 0, sizeof(chip->tstamp_cc));
chip->tstamp_cc.read = mv88e6xxx_ptp_clock_read;
chip->tstamp_cc.mask = CYCLECOUNTER_MASK(32);
chip->tstamp_cc.mult = chip->cc_coeffs->cc_mult;
chip->tstamp_cc.shift = chip->cc_coeffs->cc_shift;
timecounter_init(&chip->tstamp_tc, &chip->tstamp_cc,
ktime_to_ns(ktime_get_real()));
INIT_DELAYED_WORK(&chip->overflow_work, mv88e6xxx_ptp_overflow_check);
if (ptp_ops->event_work)
INIT_DELAYED_WORK(&chip->tai_event_work, ptp_ops->event_work);
chip->ptp_clock_info.owner = THIS_MODULE;
snprintf(chip->ptp_clock_info.name, sizeof(chip->ptp_clock_info.name),
"%s", dev_name(chip->dev));
chip->ptp_clock_info.n_ext_ts = ptp_ops->n_ext_ts;
chip->ptp_clock_info.n_per_out = 0;
chip->ptp_clock_info.n_pins = mv88e6xxx_num_gpio(chip);
chip->ptp_clock_info.pps = 0;
for (i = 0; i < chip->ptp_clock_info.n_pins; ++i) {
struct ptp_pin_desc *ppd = &chip->pin_config[i];
snprintf(ppd->name, sizeof(ppd->name), "mv88e6xxx_gpio%d", i);
ppd->index = i;
ppd->func = PTP_PF_NONE;
}
chip->ptp_clock_info.pin_config = chip->pin_config;
chip->ptp_clock_info.max_adj = MV88E6XXX_MAX_ADJ_PPB;
chip->ptp_clock_info.adjfine = mv88e6xxx_ptp_adjfine;
chip->ptp_clock_info.adjtime = mv88e6xxx_ptp_adjtime;
chip->ptp_clock_info.gettime64 = mv88e6xxx_ptp_gettime;
chip->ptp_clock_info.settime64 = mv88e6xxx_ptp_settime;
chip->ptp_clock_info.enable = ptp_ops->ptp_enable;
chip->ptp_clock_info.verify = ptp_ops->ptp_verify;
chip->ptp_clock_info.do_aux_work = mv88e6xxx_hwtstamp_work;
chip->ptp_clock_info.supported_extts_flags = PTP_RISING_EDGE |
PTP_FALLING_EDGE |
PTP_STRICT_FLAGS;
if (ptp_ops->set_ptp_cpu_port) {
struct dsa_port *dp;
int upstream = 0;
int err;
dsa_switch_for_each_user_port(dp, chip->ds) {
upstream = dsa_upstream_port(chip->ds, dp->index);
break;
}
err = ptp_ops->set_ptp_cpu_port(chip, upstream);
if (err) {
dev_err(chip->dev, "Failed to set PTP CPU destination port!\n");
return err;
}
}
chip->ptp_clock = ptp_clock_register(&chip->ptp_clock_info, chip->dev);
if (IS_ERR(chip->ptp_clock))
return PTR_ERR(chip->ptp_clock);
schedule_delayed_work(&chip->overflow_work,
MV88E6XXX_TAI_OVERFLOW_PERIOD);
return 0;
}
/* This must never be called holding the register lock */
void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip)
{
if (chip->ptp_clock) {
cancel_delayed_work_sync(&chip->overflow_work);
if (chip->info->ops->ptp_ops->event_work)
cancel_delayed_work_sync(&chip->tai_event_work);
ptp_clock_unregister(chip->ptp_clock);
chip->ptp_clock = NULL;
}
}
@@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx Switch PTP support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 National Instruments
* Erik Hons <erik.hons@ni.com>
* Brandon Streiff <brandon.streiff@ni.com>
* Dane Wagner <dane.wagner@ni.com>
*/
#ifndef _MV88E6XXX_PTP_H
#define _MV88E6XXX_PTP_H
#include "chip.h"
/* Offset 0x00: TAI Global Config */
#define MV88E6352_TAI_CFG 0x00
#define MV88E6352_TAI_CFG_CAP_OVERWRITE 0x8000
#define MV88E6352_TAI_CFG_CAP_CTR_START 0x4000
#define MV88E6352_TAI_CFG_EVREQ_FALLING 0x2000
#define MV88E6352_TAI_CFG_TRIG_ACTIVE_LO 0x1000
#define MV88E6352_TAI_CFG_IRL_ENABLE 0x0400
#define MV88E6352_TAI_CFG_TRIG_IRQ_EN 0x0200
#define MV88E6352_TAI_CFG_EVREQ_IRQ_EN 0x0100
#define MV88E6352_TAI_CFG_TRIG_LOCK 0x0080
#define MV88E6352_TAI_CFG_BLOCK_UPDATE 0x0008
#define MV88E6352_TAI_CFG_MULTI_PTP 0x0004
#define MV88E6352_TAI_CFG_TRIG_MODE_ONESHOT 0x0002
#define MV88E6352_TAI_CFG_TRIG_ENABLE 0x0001
/* Offset 0x01: Timestamp Clock Period (ps) */
#define MV88E6XXX_TAI_CLOCK_PERIOD 0x01
/* Offset 0x09: Event Status */
#define MV88E6352_TAI_EVENT_STATUS 0x09
#define MV88E6352_TAI_EVENT_STATUS_ERROR 0x0200
#define MV88E6352_TAI_EVENT_STATUS_VALID 0x0100
#define MV88E6352_TAI_EVENT_STATUS_CTR_MASK 0x00ff
/* Offset 0x0A/0x0B: Event Time Lo/Hi. Always read with Event Status. */
/* Offset 0x0E/0x0F: PTP Global Time */
#define MV88E6352_TAI_TIME_LO 0x0e
#define MV88E6352_TAI_TIME_HI 0x0f
/* 6165 Global Control Registers */
/* Offset 0x9/0xa: Global Time */
#define MV88E6165_PTP_GC_TIME_LO 0x09
#define MV88E6165_PTP_GC_TIME_HI 0x0A
/* 6165 Per Port Registers. The arrival and departure registers are a
* common block consisting of status, two time registers and the sequence ID
*/
/* Offset 0: Arrival Time 0 Status */
#define MV88E6165_PORT_PTP_ARR0_STS 0x00
/* Offset 0x04: PTP Arrival 1 Status */
#define MV88E6165_PORT_PTP_ARR1_STS 0x04
/* Offset 0x08: PTP Departure Status */
#define MV88E6165_PORT_PTP_DEP_STS 0x08
/* Offset 0x0d: Port Status */
#define MV88E6164_PORT_STATUS 0x0d
#ifdef CONFIG_NET_DSA_MV88E6XXX_PTP
int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip);
void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip);
#define ptp_to_chip(ptp) container_of(ptp, struct mv88e6xxx_chip, \
ptp_clock_info)
extern const struct mv88e6xxx_ptp_ops mv88e6165_ptp_ops;
extern const struct mv88e6xxx_ptp_ops mv88e6352_ptp_ops;
extern const struct mv88e6xxx_ptp_ops mv88e6390_ptp_ops;
#else /* !CONFIG_NET_DSA_MV88E6XXX_PTP */
static inline int mv88e6xxx_ptp_setup(struct mv88e6xxx_chip *chip)
{
return 0;
}
static inline void mv88e6xxx_ptp_free(struct mv88e6xxx_chip *chip)
{
}
static const struct mv88e6xxx_ptp_ops mv88e6165_ptp_ops = {};
static const struct mv88e6xxx_ptp_ops mv88e6352_ptp_ops = {};
static const struct mv88e6xxx_ptp_ops mv88e6390_ptp_ops = {};
#endif /* CONFIG_NET_DSA_MV88E6XXX_PTP */
#endif /* _MV88E6XXX_PTP_H */
@@ -0,0 +1,530 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx SERDES manipulation, via SMI bus
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
*/
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/mii.h>
#include "chip.h"
#include "global2.h"
#include "phy.h"
#include "port.h"
#include "serdes.h"
static int mv88e6352_serdes_read(struct mv88e6xxx_chip *chip, int lane,
int reg, u16 *val)
{
return mv88e6xxx_phy_page_read(chip, lane,
MV88E6352_SERDES_PAGE_FIBER,
reg, val);
}
static int mv88e6390_serdes_read(struct mv88e6xxx_chip *chip,
int lane, int device, int reg, u16 *val)
{
return mv88e6xxx_phy_read_c45(chip, lane, device, reg, val);
}
int mv88e6xxx_pcs_decode_state(struct device *dev, u16 bmsr, u16 lpa,
u16 status, struct phylink_link_state *state)
{
state->link = false;
/* If the BMSR reports that the link had failed, report this to
* phylink.
*/
if (!(bmsr & BMSR_LSTATUS))
return 0;
state->link = !!(status & MV88E6390_SGMII_PHY_STATUS_LINK);
state->an_complete = !!(bmsr & BMSR_ANEGCOMPLETE);
if (status & MV88E6390_SGMII_PHY_STATUS_SPD_DPL_VALID) {
/* The Spped and Duplex Resolved register is 1 if AN is enabled
* and complete, or if AN is disabled. So with disabled AN we
* still get here on link up.
*/
state->duplex = status &
MV88E6390_SGMII_PHY_STATUS_DUPLEX_FULL ?
DUPLEX_FULL : DUPLEX_HALF;
if (status & MV88E6390_SGMII_PHY_STATUS_TX_PAUSE)
state->pause |= MLO_PAUSE_TX;
if (status & MV88E6390_SGMII_PHY_STATUS_RX_PAUSE)
state->pause |= MLO_PAUSE_RX;
switch (status & MV88E6390_SGMII_PHY_STATUS_SPEED_MASK) {
case MV88E6390_SGMII_PHY_STATUS_SPEED_1000:
if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
state->speed = SPEED_2500;
else
state->speed = SPEED_1000;
break;
case MV88E6390_SGMII_PHY_STATUS_SPEED_100:
state->speed = SPEED_100;
break;
case MV88E6390_SGMII_PHY_STATUS_SPEED_10:
state->speed = SPEED_10;
break;
default:
dev_err(dev, "invalid PHY speed\n");
return -EINVAL;
}
} else if (state->link &&
state->interface != PHY_INTERFACE_MODE_SGMII) {
/* If Speed and Duplex Resolved register is 0 and link is up, it
* means that AN was enabled, but link partner had it disabled
* and the PHY invoked the Auto-Negotiation Bypass feature and
* linked anyway.
*/
state->duplex = DUPLEX_FULL;
if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
state->speed = SPEED_2500;
else
state->speed = SPEED_1000;
} else {
state->link = false;
}
if (state->interface == PHY_INTERFACE_MODE_2500BASEX)
mii_lpa_mod_linkmode_x(state->lp_advertising, lpa,
ETHTOOL_LINK_MODE_2500baseX_Full_BIT);
else if (state->interface == PHY_INTERFACE_MODE_1000BASEX)
mii_lpa_mod_linkmode_x(state->lp_advertising, lpa,
ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
return 0;
}
int mv88e6352_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
{
int has_serdes;
has_serdes = mv88e6352_g2_scratch_port_has_serdes(chip, port);
if (has_serdes == 0)
return -ENODEV;
return MV88E6352_ADDR_SERDES;
}
struct mv88e6352_serdes_hw_stat {
char string[ETH_GSTRING_LEN];
int sizeof_stat;
int reg;
};
static struct mv88e6352_serdes_hw_stat mv88e6352_serdes_hw_stats[] = {
{ "serdes_fibre_rx_error", 16, 21 },
{ "serdes_PRBS_error", 32, 24 },
};
int mv88e6352_serdes_get_sset_count(struct mv88e6xxx_chip *chip, int port)
{
int lane = -ENODEV;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return 0;
return ARRAY_SIZE(mv88e6352_serdes_hw_stats);
}
int mv88e6352_serdes_get_strings(struct mv88e6xxx_chip *chip, int port,
uint8_t **data)
{
struct mv88e6352_serdes_hw_stat *stat;
int lane = -ENODEV;
int i;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return 0;
for (i = 0; i < ARRAY_SIZE(mv88e6352_serdes_hw_stats); i++) {
stat = &mv88e6352_serdes_hw_stats[i];
ethtool_puts(data, stat->string);
}
return ARRAY_SIZE(mv88e6352_serdes_hw_stats);
}
static uint64_t mv88e6352_serdes_get_stat(struct mv88e6xxx_chip *chip, int lane,
struct mv88e6352_serdes_hw_stat *stat)
{
u64 val = 0;
u16 reg;
int err;
err = mv88e6352_serdes_read(chip, lane, stat->reg, &reg);
if (err) {
dev_err(chip->dev, "failed to read statistic\n");
return 0;
}
val = reg;
if (stat->sizeof_stat == 32) {
err = mv88e6352_serdes_read(chip, lane, stat->reg + 1, &reg);
if (err) {
dev_err(chip->dev, "failed to read statistic\n");
return 0;
}
val = val << 16 | reg;
}
return val;
}
size_t mv88e6352_serdes_get_stats(struct mv88e6xxx_chip *chip, int port,
uint64_t *data)
{
struct mv88e6xxx_port *mv88e6xxx_port = &chip->ports[port];
struct mv88e6352_serdes_hw_stat *stat;
int lane = -ENODEV;
u64 value;
int i;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return 0;
BUILD_BUG_ON(ARRAY_SIZE(mv88e6352_serdes_hw_stats) >
ARRAY_SIZE(mv88e6xxx_port->serdes_stats));
for (i = 0; i < ARRAY_SIZE(mv88e6352_serdes_hw_stats); i++) {
stat = &mv88e6352_serdes_hw_stats[i];
value = mv88e6352_serdes_get_stat(chip, lane, stat);
mv88e6xxx_port->serdes_stats[i] += value;
data[i] = mv88e6xxx_port->serdes_stats[i];
}
return ARRAY_SIZE(mv88e6352_serdes_hw_stats);
}
unsigned int mv88e6352_serdes_irq_mapping(struct mv88e6xxx_chip *chip, int port)
{
return irq_find_mapping(chip->g2_irq.domain, MV88E6352_SERDES_IRQ);
}
int mv88e6352_serdes_get_regs_len(struct mv88e6xxx_chip *chip, int port)
{
int lane = -ENODEV;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return 0;
return 32 * sizeof(u16);
}
void mv88e6352_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p)
{
int lane = -ENODEV;
u16 *p = _p;
u16 reg;
int err;
int i;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return;
for (i = 0 ; i < 32; i++) {
err = mv88e6352_serdes_read(chip, lane, i, &reg);
if (!err)
p[i] = reg;
}
}
int mv88e6321_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
{
int lane = -ENODEV;
u8 cmode;
if (port != 0 && port != 1)
return lane;
cmode = chip->ports[port].cmode;
if (cmode == MV88E6XXX_PORT_STS_CMODE_100BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII)
lane = port + MV88E6321_PORT0_LANE;
return lane;
}
int mv88e6341_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
{
u8 cmode = chip->ports[port].cmode;
int lane = -ENODEV;
switch (port) {
case 5:
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
lane = MV88E6341_PORT5_LANE;
break;
}
return lane;
}
int mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
{
u8 cmode = chip->ports[port].cmode;
int lane = -ENODEV;
switch (port) {
case 9:
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
lane = MV88E6390_PORT9_LANE0;
break;
case 10:
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
lane = MV88E6390_PORT10_LANE0;
break;
}
return lane;
}
int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
{
u8 cmode_port = chip->ports[port].cmode;
u8 cmode_port10 = chip->ports[10].cmode;
u8 cmode_port9 = chip->ports[9].cmode;
int lane = -ENODEV;
switch (port) {
case 2:
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX)
lane = MV88E6390_PORT9_LANE1;
break;
case 3:
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX)
lane = MV88E6390_PORT9_LANE2;
break;
case 4:
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX)
lane = MV88E6390_PORT9_LANE3;
break;
case 5:
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX)
lane = MV88E6390_PORT10_LANE1;
break;
case 6:
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX)
lane = MV88E6390_PORT10_LANE2;
break;
case 7:
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
if (cmode_port == MV88E6XXX_PORT_STS_CMODE_1000BASEX)
lane = MV88E6390_PORT10_LANE3;
break;
case 9:
if (cmode_port9 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_XAUI ||
cmode_port9 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
lane = MV88E6390_PORT9_LANE0;
break;
case 10:
if (cmode_port10 == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_XAUI ||
cmode_port10 == MV88E6XXX_PORT_STS_CMODE_RXAUI)
lane = MV88E6390_PORT10_LANE0;
break;
}
return lane;
}
/* Only Ports 0, 9 and 10 have SERDES lanes. Return the SERDES lane address
* a port is using else Returns -ENODEV.
*/
int mv88e6393x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port)
{
u8 cmode = chip->ports[port].cmode;
int lane = -ENODEV;
if (port != 0 && port != 9 && port != 10)
return -EOPNOTSUPP;
if (cmode == MV88E6XXX_PORT_STS_CMODE_1000BASEX ||
cmode == MV88E6XXX_PORT_STS_CMODE_SGMII ||
cmode == MV88E6XXX_PORT_STS_CMODE_2500BASEX ||
cmode == MV88E6393X_PORT_STS_CMODE_5GBASER ||
cmode == MV88E6393X_PORT_STS_CMODE_10GBASER ||
cmode == MV88E6393X_PORT_STS_CMODE_USXGMII)
lane = port;
return lane;
}
struct mv88e6390_serdes_hw_stat {
char string[ETH_GSTRING_LEN];
int reg;
};
static struct mv88e6390_serdes_hw_stat mv88e6390_serdes_hw_stats[] = {
{ "serdes_rx_pkts", 0xf021 },
{ "serdes_rx_bytes", 0xf024 },
{ "serdes_rx_pkts_error", 0xf027 },
};
int mv88e6390_serdes_get_sset_count(struct mv88e6xxx_chip *chip, int port)
{
if (mv88e6xxx_serdes_get_lane(chip, port) < 0)
return 0;
return ARRAY_SIZE(mv88e6390_serdes_hw_stats);
}
int mv88e6390_serdes_get_strings(struct mv88e6xxx_chip *chip, int port,
uint8_t **data)
{
struct mv88e6390_serdes_hw_stat *stat;
int i;
if (mv88e6xxx_serdes_get_lane(chip, port) < 0)
return 0;
for (i = 0; i < ARRAY_SIZE(mv88e6390_serdes_hw_stats); i++) {
stat = &mv88e6390_serdes_hw_stats[i];
ethtool_puts(data, stat->string);
}
return ARRAY_SIZE(mv88e6390_serdes_hw_stats);
}
static uint64_t mv88e6390_serdes_get_stat(struct mv88e6xxx_chip *chip, int lane,
struct mv88e6390_serdes_hw_stat *stat)
{
u16 reg[3];
int err, i;
for (i = 0; i < 3; i++) {
err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
stat->reg + i, &reg[i]);
if (err) {
dev_err(chip->dev, "failed to read statistic\n");
return 0;
}
}
return reg[0] | ((u64)reg[1] << 16) | ((u64)reg[2] << 32);
}
size_t mv88e6390_serdes_get_stats(struct mv88e6xxx_chip *chip, int port,
uint64_t *data)
{
struct mv88e6390_serdes_hw_stat *stat;
int lane;
int i;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return 0;
for (i = 0; i < ARRAY_SIZE(mv88e6390_serdes_hw_stats); i++) {
stat = &mv88e6390_serdes_hw_stats[i];
data[i] = mv88e6390_serdes_get_stat(chip, lane, stat);
}
return ARRAY_SIZE(mv88e6390_serdes_hw_stats);
}
unsigned int mv88e6390_serdes_irq_mapping(struct mv88e6xxx_chip *chip, int port)
{
return irq_find_mapping(chip->g2_irq.domain, port);
}
static const u16 mv88e6390_serdes_regs[] = {
/* SERDES common registers */
0xf00a, 0xf00b, 0xf00c,
0xf010, 0xf011, 0xf012, 0xf013,
0xf016, 0xf017, 0xf018,
0xf01b, 0xf01c, 0xf01d, 0xf01e, 0xf01f,
0xf020, 0xf021, 0xf022, 0xf023, 0xf024, 0xf025, 0xf026, 0xf027,
0xf028, 0xf029,
0xf030, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, 0xf037,
0xf038, 0xf039,
/* SGMII */
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007,
0x2008,
0x200f,
0xa000, 0xa001, 0xa002, 0xa003,
/* 10Gbase-X */
0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007,
0x1008,
0x100e, 0x100f,
0x1018, 0x1019,
0x9000, 0x9001, 0x9002, 0x9003, 0x9004,
0x9006,
0x9010, 0x9011, 0x9012, 0x9013, 0x9014, 0x9015, 0x9016,
/* 10Gbase-R */
0x1020, 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1026, 0x1027,
0x1028, 0x1029, 0x102a, 0x102b,
};
int mv88e6390_serdes_get_regs_len(struct mv88e6xxx_chip *chip, int port)
{
if (mv88e6xxx_serdes_get_lane(chip, port) < 0)
return 0;
return ARRAY_SIZE(mv88e6390_serdes_regs) * sizeof(u16);
}
void mv88e6390_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p)
{
u16 *p = _p;
int lane;
u16 reg;
int err;
int i;
lane = mv88e6xxx_serdes_get_lane(chip, port);
if (lane < 0)
return;
for (i = 0 ; i < ARRAY_SIZE(mv88e6390_serdes_regs); i++) {
err = mv88e6390_serdes_read(chip, lane, MDIO_MMD_PHYXS,
mv88e6390_serdes_regs[i], &reg);
if (!err)
p[i] = reg;
}
}
@@ -0,0 +1,169 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx SERDES manipulation, via SMI bus
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2016 Andrew Lunn <andrew@lunn.ch>
*/
#ifndef _MV88E6XXX_SERDES_H
#define _MV88E6XXX_SERDES_H
#include "chip.h"
struct phylink_link_state;
#define MV88E6321_PORT0_LANE 0x0c
#define MV88E6352_ADDR_SERDES 0x0f
#define MV88E6352_SERDES_PAGE_FIBER 0x01
#define MV88E6352_SERDES_IRQ 0x0b
#define MV88E6352_SERDES_INT_ENABLE 0x12
#define MV88E6352_SERDES_INT_SPEED_CHANGE BIT(14)
#define MV88E6352_SERDES_INT_DUPLEX_CHANGE BIT(13)
#define MV88E6352_SERDES_INT_PAGE_RX BIT(12)
#define MV88E6352_SERDES_INT_AN_COMPLETE BIT(11)
#define MV88E6352_SERDES_INT_LINK_CHANGE BIT(10)
#define MV88E6352_SERDES_INT_SYMBOL_ERROR BIT(9)
#define MV88E6352_SERDES_INT_FALSE_CARRIER BIT(8)
#define MV88E6352_SERDES_INT_FIFO_OVER_UNDER BIT(7)
#define MV88E6352_SERDES_INT_FIBRE_ENERGY BIT(4)
#define MV88E6352_SERDES_INT_STATUS 0x13
#define MV88E6341_PORT5_LANE 0x15
#define MV88E6390_PORT9_LANE0 0x09
#define MV88E6390_PORT9_LANE1 0x12
#define MV88E6390_PORT9_LANE2 0x13
#define MV88E6390_PORT9_LANE3 0x14
#define MV88E6390_PORT10_LANE0 0x0a
#define MV88E6390_PORT10_LANE1 0x15
#define MV88E6390_PORT10_LANE2 0x16
#define MV88E6390_PORT10_LANE3 0x17
/* 10GBASE-R and 10GBASE-X4/X2 */
#define MV88E6390_10G_CTRL1 (0x1000 + MDIO_CTRL1)
#define MV88E6390_10G_STAT1 (0x1000 + MDIO_STAT1)
#define MV88E6390_10G_INT_ENABLE 0x9001
#define MV88E6390_10G_INT_LINK_DOWN BIT(3)
#define MV88E6390_10G_INT_LINK_UP BIT(2)
#define MV88E6390_10G_INT_STATUS 0x9003
#define MV88E6393X_10G_INT_ENABLE 0x9000
#define MV88E6393X_10G_INT_LINK_CHANGE BIT(2)
#define MV88E6393X_10G_INT_STATUS 0x9001
/* USXGMII */
#define MV88E6390_USXGMII_LP_STATUS 0xf0a2
#define MV88E6390_USXGMII_PHY_STATUS 0xf0a6
/* 1000BASE-X and SGMII */
#define MV88E6390_SGMII_BMCR (0x2000 + MII_BMCR)
#define MV88E6390_SGMII_BMSR (0x2000 + MII_BMSR)
#define MV88E6390_SGMII_ADVERTISE (0x2000 + MII_ADVERTISE)
#define MV88E6390_SGMII_LPA (0x2000 + MII_LPA)
#define MV88E6390_SGMII_INT_ENABLE 0xa001
#define MV88E6390_SGMII_INT_SPEED_CHANGE BIT(14)
#define MV88E6390_SGMII_INT_DUPLEX_CHANGE BIT(13)
#define MV88E6390_SGMII_INT_PAGE_RX BIT(12)
#define MV88E6390_SGMII_INT_AN_COMPLETE BIT(11)
#define MV88E6390_SGMII_INT_LINK_DOWN BIT(10)
#define MV88E6390_SGMII_INT_LINK_UP BIT(9)
#define MV88E6390_SGMII_INT_SYMBOL_ERROR BIT(8)
#define MV88E6390_SGMII_INT_FALSE_CARRIER BIT(7)
#define MV88E6390_SGMII_INT_STATUS 0xa002
#define MV88E6390_SGMII_PHY_STATUS 0xa003
#define MV88E6390_SGMII_PHY_STATUS_SPEED_MASK GENMASK(15, 14)
#define MV88E6390_SGMII_PHY_STATUS_SPEED_1000 0x8000
#define MV88E6390_SGMII_PHY_STATUS_SPEED_100 0x4000
#define MV88E6390_SGMII_PHY_STATUS_SPEED_10 0x0000
#define MV88E6390_SGMII_PHY_STATUS_DUPLEX_FULL BIT(13)
#define MV88E6390_SGMII_PHY_STATUS_SPD_DPL_VALID BIT(11)
#define MV88E6390_SGMII_PHY_STATUS_LINK BIT(10)
#define MV88E6390_SGMII_PHY_STATUS_TX_PAUSE BIT(3)
#define MV88E6390_SGMII_PHY_STATUS_RX_PAUSE BIT(2)
/* Packet generator pad packet checker */
#define MV88E6390_PG_CONTROL 0xf010
#define MV88E6390_PG_CONTROL_ENABLE_PC BIT(0)
#define MV88E6393X_PORT0_LANE 0x00
#define MV88E6393X_PORT9_LANE 0x09
#define MV88E6393X_PORT10_LANE 0x0a
/* Port Operational Configuration */
#define MV88E6393X_SERDES_POC 0xf002
#define MV88E6393X_SERDES_POC_PCS_1000BASEX 0x0000
#define MV88E6393X_SERDES_POC_PCS_2500BASEX 0x0001
#define MV88E6393X_SERDES_POC_PCS_SGMII_PHY 0x0002
#define MV88E6393X_SERDES_POC_PCS_SGMII_MAC 0x0003
#define MV88E6393X_SERDES_POC_PCS_5GBASER 0x0004
#define MV88E6393X_SERDES_POC_PCS_10GBASER 0x0005
#define MV88E6393X_SERDES_POC_PCS_USXGMII_PHY 0x0006
#define MV88E6393X_SERDES_POC_PCS_USXGMII_MAC 0x0007
#define MV88E6393X_SERDES_POC_PCS_MASK 0x0007
#define MV88E6393X_SERDES_POC_RESET BIT(15)
#define MV88E6393X_SERDES_POC_PDOWN BIT(5)
#define MV88E6393X_SERDES_POC_AN BIT(3)
#define MV88E6393X_SERDES_CTRL1 0xf003
#define MV88E6393X_SERDES_CTRL1_TX_PDOWN BIT(9)
#define MV88E6393X_SERDES_CTRL1_RX_PDOWN BIT(8)
#define MV88E6393X_ERRATA_4_8_REG 0xF074
#define MV88E6393X_ERRATA_4_8_BIT BIT(14)
int mv88e6xxx_pcs_decode_state(struct device *dev, u16 bmsr, u16 lpa,
u16 status, struct phylink_link_state *state);
int mv88e6321_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6341_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6390x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
int mv88e6393x_serdes_get_lane(struct mv88e6xxx_chip *chip, int port);
unsigned int mv88e6352_serdes_irq_mapping(struct mv88e6xxx_chip *chip,
int port);
unsigned int mv88e6390_serdes_irq_mapping(struct mv88e6xxx_chip *chip,
int port);
int mv88e6352_serdes_get_sset_count(struct mv88e6xxx_chip *chip, int port);
int mv88e6352_serdes_get_strings(struct mv88e6xxx_chip *chip, int port,
uint8_t **data);
size_t mv88e6352_serdes_get_stats(struct mv88e6xxx_chip *chip, int port,
uint64_t *data);
int mv88e6390_serdes_get_sset_count(struct mv88e6xxx_chip *chip, int port);
int mv88e6390_serdes_get_strings(struct mv88e6xxx_chip *chip, int port,
uint8_t **data);
size_t mv88e6390_serdes_get_stats(struct mv88e6xxx_chip *chip, int port,
uint64_t *data);
int mv88e6352_serdes_get_regs_len(struct mv88e6xxx_chip *chip, int port);
void mv88e6352_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p);
int mv88e6390_serdes_get_regs_len(struct mv88e6xxx_chip *chip, int port);
void mv88e6390_serdes_get_regs(struct mv88e6xxx_chip *chip, int port, void *_p);
/* Return the (first) SERDES lane address a port is using, -errno otherwise. */
static inline int mv88e6xxx_serdes_get_lane(struct mv88e6xxx_chip *chip,
int port)
{
if (!chip->info->ops->serdes_get_lane)
return -EOPNOTSUPP;
return chip->info->ops->serdes_get_lane(chip, port);
}
static inline unsigned int
mv88e6xxx_serdes_irq_mapping(struct mv88e6xxx_chip *chip, int port)
{
if (!chip->info->ops->serdes_irq_mapping)
return 0;
return chip->info->ops->serdes_irq_mapping(chip, port);
}
extern const struct mv88e6xxx_pcs_ops mv88e6185_pcs_ops;
extern const struct mv88e6xxx_pcs_ops mv88e6352_pcs_ops;
extern const struct mv88e6xxx_pcs_ops mv88e6390_pcs_ops;
extern const struct mv88e6xxx_pcs_ops mv88e6393x_pcs_ops;
#endif
+190
View File
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx System Management Interface (SMI) support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2019 Vivien Didelot <vivien.didelot@gmail.com>
*/
#include "chip.h"
#include "smi.h"
/* The switch ADDR[4:1] configuration pins define the chip SMI device address
* (ADDR[0] is always zero, thus only even SMI addresses can be strapped).
*
* When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it
* is the only device connected to the SMI master. In this mode it responds to
* all 32 possible SMI addresses, and thus maps directly the internal devices.
*
* When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing
* multiple devices to share the SMI interface. In this mode it responds to only
* 2 registers, used to indirectly access the internal SMI devices.
*
* Some chips use a different scheme: Only the ADDR4 pin is used for
* configuration, and the device responds to 16 of the 32 SMI
* addresses, allowing two to coexist on the same SMI interface.
*/
static int mv88e6xxx_smi_direct_read(struct mv88e6xxx_chip *chip,
int dev, int reg, u16 *data)
{
int ret;
ret = mdiobus_read_nested(chip->bus, dev, reg);
if (ret < 0)
return ret;
*data = ret & 0xffff;
return 0;
}
static int mv88e6xxx_smi_direct_write(struct mv88e6xxx_chip *chip,
int dev, int reg, u16 data)
{
int ret;
ret = mdiobus_write_nested(chip->bus, dev, reg, data);
if (ret < 0)
return ret;
return 0;
}
static int mv88e6xxx_smi_direct_wait(struct mv88e6xxx_chip *chip,
int dev, int reg, int bit, int val)
{
const unsigned long timeout = jiffies + msecs_to_jiffies(50);
u16 data;
int err;
int i;
/* Even if the initial poll takes longer than 50ms, always do
* at least one more attempt.
*/
for (i = 0; time_before(jiffies, timeout) || (i < 2); i++) {
err = mv88e6xxx_smi_direct_read(chip, dev, reg, &data);
if (err)
return err;
if (!!(data & BIT(bit)) == !!val)
return 0;
if (i < 2)
cpu_relax();
else
usleep_range(1000, 2000);
}
return -ETIMEDOUT;
}
static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_direct_ops = {
.read = mv88e6xxx_smi_direct_read,
.write = mv88e6xxx_smi_direct_write,
};
static int mv88e6xxx_smi_dual_direct_read(struct mv88e6xxx_chip *chip,
int dev, int reg, u16 *data)
{
return mv88e6xxx_smi_direct_read(chip, chip->sw_addr + dev, reg, data);
}
static int mv88e6xxx_smi_dual_direct_write(struct mv88e6xxx_chip *chip,
int dev, int reg, u16 data)
{
return mv88e6xxx_smi_direct_write(chip, chip->sw_addr + dev, reg, data);
}
static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_dual_direct_ops = {
.read = mv88e6xxx_smi_dual_direct_read,
.write = mv88e6xxx_smi_dual_direct_write,
};
/* Offset 0x00: SMI Command Register
* Offset 0x01: SMI Data Register
*/
static int mv88e6xxx_smi_indirect_read(struct mv88e6xxx_chip *chip,
int dev, int reg, u16 *data)
{
int err;
err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr,
MV88E6XXX_SMI_CMD,
MV88E6XXX_SMI_CMD_BUSY |
MV88E6XXX_SMI_CMD_MODE_22 |
MV88E6XXX_SMI_CMD_OP_22_READ |
(dev << 5) | reg);
if (err)
return err;
err = mv88e6xxx_smi_direct_wait(chip, chip->sw_addr,
MV88E6XXX_SMI_CMD, 15, 0);
if (err)
return err;
return mv88e6xxx_smi_direct_read(chip, chip->sw_addr,
MV88E6XXX_SMI_DATA, data);
}
static int mv88e6xxx_smi_indirect_write(struct mv88e6xxx_chip *chip,
int dev, int reg, u16 data)
{
int err;
err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr,
MV88E6XXX_SMI_DATA, data);
if (err)
return err;
err = mv88e6xxx_smi_direct_write(chip, chip->sw_addr,
MV88E6XXX_SMI_CMD,
MV88E6XXX_SMI_CMD_BUSY |
MV88E6XXX_SMI_CMD_MODE_22 |
MV88E6XXX_SMI_CMD_OP_22_WRITE |
(dev << 5) | reg);
if (err)
return err;
return mv88e6xxx_smi_direct_wait(chip, chip->sw_addr,
MV88E6XXX_SMI_CMD, 15, 0);
}
static int mv88e6xxx_smi_indirect_init(struct mv88e6xxx_chip *chip)
{
/* Ensure that the chip starts out in the ready state. As both
* reads and writes always ensure this on return, they can
* safely depend on the chip not being busy on entry.
*/
return mv88e6xxx_smi_direct_wait(chip, chip->sw_addr,
MV88E6XXX_SMI_CMD, 15, 0);
}
static const struct mv88e6xxx_bus_ops mv88e6xxx_smi_indirect_ops = {
.read = mv88e6xxx_smi_indirect_read,
.write = mv88e6xxx_smi_indirect_write,
.init = mv88e6xxx_smi_indirect_init,
};
int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip,
struct mii_bus *bus, int sw_addr)
{
if (chip->info->dual_chip)
chip->smi_ops = &mv88e6xxx_smi_dual_direct_ops;
else if (sw_addr == 0)
chip->smi_ops = &mv88e6xxx_smi_direct_ops;
else if (chip->info->multi_chip)
chip->smi_ops = &mv88e6xxx_smi_indirect_ops;
else
return -EINVAL;
chip->bus = bus;
chip->sw_addr = sw_addr;
if (chip->smi_ops->init)
return chip->smi_ops->init(chip);
return 0;
}
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Marvell 88E6xxx System Management Interface (SMI) support
*
* Copyright (c) 2008 Marvell Semiconductor
*
* Copyright (c) 2019 Vivien Didelot <vivien.didelot@gmail.com>
*/
#ifndef _MV88E6XXX_SMI_H
#define _MV88E6XXX_SMI_H
#include "chip.h"
/* Offset 0x00: SMI Command Register */
#define MV88E6XXX_SMI_CMD 0x00
#define MV88E6XXX_SMI_CMD_BUSY 0x8000
#define MV88E6XXX_SMI_CMD_MODE_MASK 0x1000
#define MV88E6XXX_SMI_CMD_MODE_45 0x0000
#define MV88E6XXX_SMI_CMD_MODE_22 0x1000
#define MV88E6XXX_SMI_CMD_OP_MASK 0x0c00
#define MV88E6XXX_SMI_CMD_OP_22_WRITE 0x0400
#define MV88E6XXX_SMI_CMD_OP_22_READ 0x0800
#define MV88E6XXX_SMI_CMD_OP_45_WRITE_ADDR 0x0000
#define MV88E6XXX_SMI_CMD_OP_45_WRITE_DATA 0x0400
#define MV88E6XXX_SMI_CMD_OP_45_READ_DATA 0x0800
#define MV88E6XXX_SMI_CMD_OP_45_READ_DATA_INC 0x0c00
#define MV88E6XXX_SMI_CMD_DEV_ADDR_MASK 0x003e
#define MV88E6XXX_SMI_CMD_REG_ADDR_MASK 0x001f
/* Offset 0x01: SMI Data Register */
#define MV88E6XXX_SMI_DATA 0x01
int mv88e6xxx_smi_init(struct mv88e6xxx_chip *chip,
struct mii_bus *bus, int sw_addr);
static inline int mv88e6xxx_smi_read(struct mv88e6xxx_chip *chip,
int dev, int reg, u16 *data)
{
if (chip->smi_ops && chip->smi_ops->read)
return chip->smi_ops->read(chip, dev, reg, data);
return -EOPNOTSUPP;
}
static inline int mv88e6xxx_smi_write(struct mv88e6xxx_chip *chip,
int dev, int reg, u16 data)
{
if (chip->smi_ops && chip->smi_ops->write)
return chip->smi_ops->write(chip, dev, reg, data);
return -EOPNOTSUPP;
}
#endif /* _MV88E6XXX_SMI_H */
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* switchdev.c
*
* Authors:
* Hans J. Schultz <netdev@kapio-technology.com>
*
*/
#include <net/switchdev.h>
#include "chip.h"
#include "global1.h"
#include "switchdev.h"
struct mv88e6xxx_fid_search_ctx {
u16 fid_search;
u16 vid_found;
};
static int __mv88e6xxx_find_vid(struct mv88e6xxx_chip *chip,
const struct mv88e6xxx_vtu_entry *entry,
void *priv)
{
struct mv88e6xxx_fid_search_ctx *ctx = priv;
if (ctx->fid_search == entry->fid) {
ctx->vid_found = entry->vid;
return 1;
}
return 0;
}
static int mv88e6xxx_find_vid(struct mv88e6xxx_chip *chip, u16 fid, u16 *vid)
{
struct mv88e6xxx_fid_search_ctx ctx;
int err;
ctx.fid_search = fid;
mv88e6xxx_reg_lock(chip);
err = mv88e6xxx_vtu_walk(chip, __mv88e6xxx_find_vid, &ctx);
mv88e6xxx_reg_unlock(chip);
if (err < 0)
return err;
if (err == 1)
*vid = ctx.vid_found;
else
return -ENOENT;
return 0;
}
int mv88e6xxx_handle_miss_violation(struct mv88e6xxx_chip *chip, int port,
struct mv88e6xxx_atu_entry *entry, u16 fid)
{
struct switchdev_notifier_fdb_info info = {
.addr = entry->mac,
.locked = true,
};
struct net_device *brport;
struct dsa_port *dp;
u16 vid;
int err;
err = mv88e6xxx_find_vid(chip, fid, &vid);
if (err)
return err;
info.vid = vid;
dp = dsa_to_port(chip->ds, port);
rtnl_lock();
brport = dsa_port_to_bridge_port(dp);
if (!brport) {
rtnl_unlock();
return -ENODEV;
}
err = call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
brport, &info.info, NULL);
rtnl_unlock();
return err;
}
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
*
* switchdev.h
*
* Authors:
* Hans J. Schultz <netdev@kapio-technology.com>
*
*/
#ifndef _MV88E6XXX_SWITCHDEV_H_
#define _MV88E6XXX_SWITCHDEV_H_
#include "chip.h"
int mv88e6xxx_handle_miss_violation(struct mv88e6xxx_chip *chip, int port,
struct mv88e6xxx_atu_entry *entry,
u16 fid);
#endif /* _MV88E6XXX_SWITCHDEV_H_ */
@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch TCAM support
*
* Copyright (c) 2026 Luminex Network Intelligence
*/
#include "linux/list.h"
#include "chip.h"
#include "tcam.h"
/* TCAM operatation register */
#define MV88E6XXX_TCAM_OP 0x00
#define MV88E6XXX_TCAM_OP_BUSY 0x8000
#define MV88E6XXX_TCAM_OP_OP_MASK 0x7000
#define MV88E6XXX_TCAM_OP_OP_FLUSH_ALL 0x1000
#define MV88E6XXX_TCAM_OP_OP_FLUSH 0x2000
#define MV88E6XXX_TCAM_OP_OP_LOAD 0x3000
#define MV88E6XXX_TCAM_OP_OP_GET_NEXT 0x4000
#define MV88E6XXX_TCAM_OP_OP_READ 0x5000
/* TCAM extension register */
#define MV88E6XXX_TCAM_EXTENSION 0x01
/* TCAM keys register 1 */
#define MV88E6XXX_TCAM_KEYS1 0x02
#define MV88E6XXX_TCAM_KEYS1_FT_MASK 0xC000
#define MV88E6XXX_TCAM_KEYS1_SPV_MASK 0x0007
#define MV88E6XXX_TCAM_KEYS1_SPV_MASK_MASK 0x0700
/* TCAM keys register 2 */
#define MV88E6XXX_TCAM_KEYS2 0x03
#define MV88E6XXX_TCAM_KEYS2_SPV_MASK 0x00ff
#define MV88E6XXX_TCAM_KEYS2_SPV_MASK_MASK 0xff00
#define MV88E6XXX_TCAM_ING_ACT3 0x04
#define MV88E6XXX_TCAM_ING_ACT3_SF 0x0800
#define MV88E6XXX_TCAM_ING_ACT3_DPV_MASK 0x07ff
#define MV88E6XXX_TCAM_ING_ACT5 0x06
#define MV88E6XXX_TCAM_ING_ACT5_DPV_MODE_MASK 0xc000
static int mv88e6xxx_tcam_write(struct mv88e6xxx_chip *chip, int reg, u16 val)
{
return mv88e6xxx_write(chip, chip->info->tcam_addr, reg, val);
}
static int mv88e6xxx_tcam_wait(struct mv88e6xxx_chip *chip)
{
int bit = __bf_shf(MV88E6XXX_TCAM_OP_BUSY);
return mv88e6xxx_wait_bit(chip, chip->info->tcam_addr,
MV88E6XXX_TCAM_OP, bit, 0);
}
static int mv88e6xxx_tcam_read_page(struct mv88e6xxx_chip *chip, u8 page,
u8 entry)
{
u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_READ |
(page & 0x3) << 10 | entry;
int err;
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val);
if (err)
return err;
return mv88e6xxx_tcam_wait(chip);
}
static int mv88e6xxx_tcam_load_page(struct mv88e6xxx_chip *chip, u8 page,
u8 entry)
{
u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_LOAD |
(page & 0x3) << 10 | entry;
int err;
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val);
if (err)
return err;
return mv88e6xxx_tcam_wait(chip);
}
static int mv88e6xxx_tcam_flush_entry(struct mv88e6xxx_chip *chip, u8 entry)
{
u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_FLUSH | entry;
int err;
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val);
if (err)
return err;
return mv88e6xxx_tcam_wait(chip);
}
static int mv88e6xxx_tcam_flush_all(struct mv88e6xxx_chip *chip)
{
u16 val = MV88E6XXX_TCAM_OP_BUSY | MV88E6XXX_TCAM_OP_OP_FLUSH_ALL;
int err;
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_OP, val);
if (err)
return err;
return mv88e6xxx_tcam_wait(chip);
}
struct mv88e6xxx_tcam_entry *
mv88e6xxx_tcam_entry_find(struct mv88e6xxx_chip *chip, unsigned long cookie)
{
struct mv88e6xxx_tcam_entry *entry;
list_for_each_entry(entry, &chip->tcam.entries, list)
if (entry->cookie == cookie)
return entry;
return NULL;
}
/* insert tcam entry in ordered list and move existing entries if necessary */
static int mv88e6xxx_tcam_insert_entry(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_tcam_entry *entry)
{
struct mv88e6xxx_tcam_entry *elem;
struct list_head *hpos;
int err;
list_for_each_prev(hpos, &chip->tcam.entries) {
u8 move_idx;
elem = list_entry(hpos, struct mv88e6xxx_tcam_entry, list);
if (entry->prio >= elem->prio)
break;
move_idx = elem->hw_idx + 1;
err = mv88e6xxx_tcam_flush_entry(chip, move_idx);
if (err)
return err;
err = chip->info->ops->tcam_ops->entry_add(chip, elem,
move_idx);
if (err)
return err;
elem->hw_idx = move_idx;
}
if (list_is_head(hpos, &chip->tcam.entries)) {
entry->hw_idx = 0;
} else {
elem = list_entry(hpos, struct mv88e6xxx_tcam_entry, list);
entry->hw_idx = elem->hw_idx + 1;
}
list_add(&entry->list, hpos);
return 0;
}
int mv88e6xxx_tcam_entry_add(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_tcam_entry *entry)
{
int err;
struct mv88e6xxx_tcam_entry *last;
last = list_last_entry_or_null(&chip->tcam.entries,
struct mv88e6xxx_tcam_entry, list);
if (last && last->hw_idx == chip->info->num_tcam_entries - 1)
return -ENOSPC;
err = mv88e6xxx_tcam_insert_entry(chip, entry);
if (err)
return err;
err = mv88e6xxx_tcam_flush_entry(chip, entry->hw_idx);
if (err)
goto unlink_out;
err = chip->info->ops->tcam_ops->entry_add(chip, entry, entry->hw_idx);
if (err)
goto unlink_out;
return 0;
unlink_out:
list_del(&entry->list);
return err;
}
int mv88e6xxx_tcam_entry_del(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_tcam_entry *entry)
{
struct mv88e6xxx_tcam_entry *elem = entry;
u8 move_idx = entry->hw_idx;
int err;
err = mv88e6xxx_tcam_flush_entry(chip, entry->hw_idx);
if (err)
return err;
/* move entries that come after the deleted entry forward */
list_for_each_entry_continue(elem, &chip->tcam.entries, list) {
u8 tmp_idx = elem->hw_idx;
err = mv88e6xxx_tcam_flush_entry(chip, move_idx);
if (err)
break;
err = chip->info->ops->tcam_ops->entry_add(chip, elem,
move_idx);
if (err)
break;
elem->hw_idx = move_idx;
move_idx = tmp_idx;
/* flush the last entry after moving entries */
if (list_is_last(&elem->list, &chip->tcam.entries))
err = mv88e6xxx_tcam_flush_entry(chip, tmp_idx);
}
list_del(&entry->list);
kfree(entry);
return err;
}
static int mv88e6390_tcam_entry_add(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_tcam_entry *entry, u8 idx)
{
int err = 0;
int i;
u16 val, spv_mask, spv;
err = mv88e6xxx_tcam_read_page(chip, 2, idx);
if (err)
return err;
if (entry->action.dpv_mode != 0) {
val = MV88E6XXX_TCAM_ING_ACT3_SF |
(entry->action.dpv & MV88E6XXX_TCAM_ING_ACT3_DPV_MASK);
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_ING_ACT3, val);
if (err)
return err;
val = entry->action.dpv_mode << 14;
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_ING_ACT5, val);
if (err)
return err;
}
err = mv88e6xxx_tcam_load_page(chip, 2, idx);
if (err)
return err;
err = mv88e6xxx_tcam_read_page(chip, 1, idx);
if (err)
return err;
for (i = PAGE0_MATCH_SIZE;
i < PAGE0_MATCH_SIZE + PAGE1_MATCH_SIZE; i++) {
if (entry->key.frame_mask[i]) {
val = entry->key.frame_mask[i] << 8 |
entry->key.frame_data[i];
err = mv88e6xxx_tcam_write(chip,
i - PAGE0_MATCH_SIZE + 2,
val);
if (err)
return err;
}
}
err = mv88e6xxx_tcam_load_page(chip, 1, idx);
if (err)
return err;
err = mv88e6xxx_tcam_read_page(chip, 0, idx);
if (err)
return err;
for (i = 0; i < PAGE0_MATCH_SIZE; i++) {
if (entry->key.frame_mask[i]) {
val = entry->key.frame_mask[i] << 8 |
entry->key.frame_data[i];
err = mv88e6xxx_tcam_write(chip, i + 6, val);
if (err)
return err;
}
}
spv_mask = entry->key.spv_mask & mv88e6xxx_port_mask(chip);
spv = entry->key.spv & mv88e6xxx_port_mask(chip);
/* frame type mask bits must be set for a valid entry */
val = MV88E6XXX_TCAM_KEYS1_FT_MASK |
(spv_mask & MV88E6XXX_TCAM_KEYS1_SPV_MASK_MASK) |
((spv >> 8) & MV88E6XXX_TCAM_KEYS1_SPV_MASK);
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_KEYS1, val);
if (err)
return err;
val = ((spv_mask << 8) & MV88E6XXX_TCAM_KEYS2_SPV_MASK_MASK) |
(spv & MV88E6XXX_TCAM_KEYS2_SPV_MASK);
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_KEYS2, val);
if (err)
return err;
err = mv88e6xxx_tcam_load_page(chip, 0, idx);
if (err)
return err;
entry->hw_idx = idx;
return 0;
}
static int mv88e6393_tcam_entry_add(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_tcam_entry *entry, u8 idx)
{
int err;
/* select block 0 port 0, then adding an entry is the same as 6390 as
* other blocks aren't used at the moment
*/
err = mv88e6xxx_tcam_write(chip, MV88E6XXX_TCAM_EXTENSION, 0x00);
if (err)
return err;
return mv88e6390_tcam_entry_add(chip, entry, idx);
}
const struct mv88e6xxx_tcam_ops mv88e6390_tcam_ops = {
.entry_add = mv88e6390_tcam_entry_add,
.flush_tcam = mv88e6xxx_tcam_flush_all,
};
const struct mv88e6xxx_tcam_ops mv88e6393_tcam_ops = {
.entry_add = mv88e6393_tcam_entry_add,
.flush_tcam = mv88e6xxx_tcam_flush_all,
};
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2026 Luminex Network Intelligence
*/
#ifndef _MV88E6XXX_TCAM_H_
#define _MV88E6XXX_TCAM_H_
#define PAGE0_MATCH_SIZE 22
#define PAGE1_MATCH_SIZE 26
#define DPV_MODE_NOP 0x0
#define DPV_MODE_AND 0x1
#define DPV_MODE_OR 0x2
#define DPV_MODE_REPLACE 0x3
int mv88e6xxx_tcam_entry_add(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_tcam_entry *entry);
int mv88e6xxx_tcam_entry_del(struct mv88e6xxx_chip *chip,
struct mv88e6xxx_tcam_entry *entry);
struct mv88e6xxx_tcam_entry *
mv88e6xxx_tcam_entry_find(struct mv88e6xxx_chip *chip, unsigned long cookie);
#define mv88e6xxx_tcam_match_set(key, _offset, data, mask) \
do { \
typeof(_offset) (offset) = (_offset); \
BUILD_BUG_ON((offset) + sizeof((data)) > TCAM_MATCH_SIZE); \
__mv88e6xxx_tcam_match_set(key, offset, sizeof(data), \
(u8 *)&(data), (u8 *)&(mask)); \
} while (0)
static inline void __mv88e6xxx_tcam_match_set(struct mv88e6xxx_tcam_key *key,
unsigned int offset, size_t size,
u8 *data, u8 *mask)
{
memcpy(&key->frame_data[offset], data, size);
memcpy(&key->frame_mask[offset], mask, size);
}
extern const struct mv88e6xxx_tcam_ops mv88e6390_tcam_ops;
extern const struct mv88e6xxx_tcam_ops mv88e6393_tcam_ops;
#endif
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Marvell 88E6xxx Switch flower support
*
* Copyright (c) 2026 Luminex Network Intelligence
*/
#include "chip.h"
#include "tcflower.h"
#include "tcam.h"
#define MV88E6XXX_ETHTYPE_OFFSET 16
#define MV88E6XXX_IP_PROTO_OFFSET 27
#define MV88E6XXX_IPV4_SRC_OFFSET 30
#define MV88E6XXX_IPV4_DST_OFFSET 34
static int mv88e6xxx_flower_parse_key(struct mv88e6xxx_chip *chip,
struct netlink_ext_ack *extack,
struct flow_cls_offload *cls,
struct mv88e6xxx_tcam_key *key)
{
struct flow_rule *rule = flow_cls_offload_flow_rule(cls);
struct flow_dissector *dissector = rule->match.dissector;
u16 addr_type = 0;
if (dissector->used_keys &
~(BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS))) {
NL_SET_ERR_MSG_MOD(extack,
"Unsupported keys used");
return -EOPNOTSUPP;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
struct flow_match_control match;
flow_rule_match_control(rule, &match);
addr_type = match.key->addr_type;
if (flow_rule_has_control_flags(match.mask->flags,
cls->common.extack))
return -EOPNOTSUPP;
}
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
struct flow_match_basic match;
flow_rule_match_basic(rule, &match);
mv88e6xxx_tcam_match_set(key, MV88E6XXX_ETHTYPE_OFFSET,
match.key->n_proto,
match.mask->n_proto);
mv88e6xxx_tcam_match_set(key, MV88E6XXX_IP_PROTO_OFFSET,
match.key->ip_proto,
match.mask->ip_proto);
}
if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS) {
struct flow_match_ipv4_addrs match;
flow_rule_match_ipv4_addrs(cls->rule, &match);
mv88e6xxx_tcam_match_set(key, MV88E6XXX_IPV4_SRC_OFFSET,
match.key->src,
match.mask->src);
mv88e6xxx_tcam_match_set(key, MV88E6XXX_IPV4_DST_OFFSET,
match.key->dst,
match.mask->dst);
}
return 0;
}
int mv88e6xxx_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 mv88e6xxx_chip *chip = ds->priv;
struct mv88e6xxx_tcam_key key = { 0 };
const struct flow_action_entry *act;
unsigned long cookie = cls->cookie;
struct mv88e6xxx_tcam_entry *entry;
int err, i;
if (!mv88e6xxx_has_tcam(chip)) {
NL_SET_ERR_MSG_MOD(extack, "hardware offload not supported");
return -EOPNOTSUPP;
}
err = mv88e6xxx_flower_parse_key(chip, extack, cls, &key);
if (err)
return err;
mv88e6xxx_reg_lock(chip);
entry = mv88e6xxx_tcam_entry_find(chip, cookie);
if (entry) {
err = -EEXIST;
goto err_unlock;
}
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) {
err = -ENOMEM;
goto err_unlock;
}
entry->cookie = cookie;
entry->prio = cls->common.prio;
entry->key = key;
flow_action_for_each(i, act, &rule->action) {
switch (act->id) {
case FLOW_ACTION_TRAP: {
int cpu = dsa_upstream_port(ds, port);
entry->action.dpv_mode = DPV_MODE_REPLACE;
entry->action.dpv = BIT(cpu);
break;
}
default:
NL_SET_ERR_MSG_MOD(extack, "action not supported");
err = -EOPNOTSUPP;
goto err_free_entry;
}
}
entry->key.spv = BIT(port);
entry->key.spv_mask = mv88e6xxx_port_mask(chip);
err = mv88e6xxx_tcam_entry_add(chip, entry);
if (err)
goto err_free_entry;
mv88e6xxx_reg_unlock(chip);
return 0;
err_free_entry:
kfree(entry);
err_unlock:
mv88e6xxx_reg_unlock(chip);
return err;
}
int mv88e6xxx_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress)
{
struct mv88e6xxx_chip *chip = ds->priv;
struct mv88e6xxx_tcam_entry *entry;
int err = 0;
mv88e6xxx_reg_lock(chip);
entry = mv88e6xxx_tcam_entry_find(chip, cls->cookie);
if (entry)
err = mv88e6xxx_tcam_entry_del(chip, entry);
mv88e6xxx_reg_unlock(chip);
return err;
}
void mv88e6xxx_flower_teardown(struct mv88e6xxx_chip *chip)
{
struct mv88e6xxx_tcam_entry *pos, *n;
list_for_each_entry_safe(pos, n, &chip->tcam.entries, list) {
list_del(&pos->list);
kfree(pos);
}
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2026 Luminex Network Intelligence
*/
#ifndef _MV88E6XXX_TCFLOWER_H_
#define _MV88E6XXX_TCFLOWER_H_
int mv88e6xxx_cls_flower_add(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress);
int mv88e6xxx_cls_flower_del(struct dsa_switch *ds, int port,
struct flow_cls_offload *cls, bool ingress);
void mv88e6xxx_flower_teardown(struct mv88e6xxx_chip *chip);
#endif
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Copyright 2022 NXP
*/
#define CREATE_TRACE_POINTS
#include "trace.h"
@@ -0,0 +1,96 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright 2022 NXP
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM mv88e6xxx
#if !defined(_MV88E6XXX_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _MV88E6XXX_TRACE_H
#include <linux/device.h>
#include <linux/if_ether.h>
#include <linux/tracepoint.h>
DECLARE_EVENT_CLASS(mv88e6xxx_atu_violation,
TP_PROTO(const struct device *dev, int spid, u16 portvec,
const unsigned char *addr, u16 fid),
TP_ARGS(dev, spid, portvec, addr, fid),
TP_STRUCT__entry(
__string(name, dev_name(dev))
__field(int, spid)
__field(u16, portvec)
__array(unsigned char, addr, ETH_ALEN)
__field(u16, fid)
),
TP_fast_assign(
__assign_str(name);
__entry->spid = spid;
__entry->portvec = portvec;
memcpy(__entry->addr, addr, ETH_ALEN);
__entry->fid = fid;
),
TP_printk("dev %s spid %d portvec 0x%x addr %pM fid %u",
__get_str(name), __entry->spid, __entry->portvec,
__entry->addr, __entry->fid)
);
DEFINE_EVENT(mv88e6xxx_atu_violation, mv88e6xxx_atu_member_violation,
TP_PROTO(const struct device *dev, int spid, u16 portvec,
const unsigned char *addr, u16 fid),
TP_ARGS(dev, spid, portvec, addr, fid));
DEFINE_EVENT(mv88e6xxx_atu_violation, mv88e6xxx_atu_miss_violation,
TP_PROTO(const struct device *dev, int spid, u16 portvec,
const unsigned char *addr, u16 fid),
TP_ARGS(dev, spid, portvec, addr, fid));
DEFINE_EVENT(mv88e6xxx_atu_violation, mv88e6xxx_atu_full_violation,
TP_PROTO(const struct device *dev, int spid, u16 portvec,
const unsigned char *addr, u16 fid),
TP_ARGS(dev, spid, portvec, addr, fid));
DECLARE_EVENT_CLASS(mv88e6xxx_vtu_violation,
TP_PROTO(const struct device *dev, int spid, u16 vid),
TP_ARGS(dev, spid, vid),
TP_STRUCT__entry(
__string(name, dev_name(dev))
__field(int, spid)
__field(u16, vid)
),
TP_fast_assign(
__assign_str(name);
__entry->spid = spid;
__entry->vid = vid;
),
TP_printk("dev %s spid %d vid %u",
__get_str(name), __entry->spid, __entry->vid)
);
DEFINE_EVENT(mv88e6xxx_vtu_violation, mv88e6xxx_vtu_member_violation,
TP_PROTO(const struct device *dev, int spid, u16 vid),
TP_ARGS(dev, spid, vid));
DEFINE_EVENT(mv88e6xxx_vtu_violation, mv88e6xxx_vtu_miss_violation,
TP_PROTO(const struct device *dev, int spid, u16 vid),
TP_ARGS(dev, spid, vid));
#endif /* _MV88E6XXX_TRACE_H */
/* We don't want to use include/trace/events */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
/* This part must be outside protection */
#include <trace/define_trace.h>