linux/drivers/net/dsa/realtek/rtl8365mb.c

// SPDX-License-Identifier: GPL-2.0
/* Realtek SMI subdriver for the Realtek RTL8365MB-VC ethernet switch.
 *
 * Copyright (C) 2021 Alvin Šipraga <[email protected]>
 * Copyright (C) 2021 Michael Rasmussen <[email protected]>
 *
 * The RTL8365MB-VC is a 4+1 port 10/100/1000M switch controller. It includes 4
 * integrated PHYs for the user facing ports, and an extension interface which
 * can be connected to the CPU - or another PHY - via either MII, RMII, or
 * RGMII. The switch is configured via the Realtek Simple Management Interface
 * (SMI), which uses the MDIO/MDC lines.
 *
 * Below is a simplified block diagram of the chip and its relevant interfaces.
 *
 *                          .-----------------------------------.
 *                          |                                   |
 *         UTP <---------------> Giga PHY <-> PCS <-> P0 GMAC   |
 *         UTP <---------------> Giga PHY <-> PCS <-> P1 GMAC   |
 *         UTP <---------------> Giga PHY <-> PCS <-> P2 GMAC   |
 *         UTP <---------------> Giga PHY <-> PCS <-> P3 GMAC   |
 *                          |                                   |
 *     CPU/PHY <-MII/RMII/RGMII--->  Extension  <---> Extension |
 *                          |       interface 1        GMAC 1   |
 *                          |                                   |
 *     SMI driver/ <-MDC/SCL---> Management    ~~~~~~~~~~~~~~   |
 *        EEPROM   <-MDIO/SDA--> interface     ~REALTEK ~~~~~   |
 *                          |                  ~RTL8365MB ~~~   |
 *                          |                  ~GXXXC TAIWAN~   |
 *        GPIO <--------------> Reset          ~~~~~~~~~~~~~~   |
 *                          |                                   |
 *      Interrupt  <----------> Link UP/DOWN events             |
 *      controller          |                                   |
 *                          '-----------------------------------'
 *
 * The driver uses DSA to integrate the 4 user and 1 extension ports into the
 * kernel. Netdevices are created for the user ports, as are PHY devices for
 * their integrated PHYs. The device tree firmware should also specify the link
 * partner of the extension port - either via a fixed-link or other phy-handle.
 * See the device tree bindings for more detailed information. Note that the
 * driver has only been tested with a fixed-link, but in principle it should not
 * matter.
 *
 * NOTE: Currently, only the RGMII interface is implemented in this driver.
 *
 * The interrupt line is asserted on link UP/DOWN events. The driver creates a
 * custom irqchip to handle this interrupt and demultiplex the events by reading
 * the status registers via SMI. Interrupts are then propagated to the relevant
 * PHY device.
 *
 * The EEPROM contains initial register values which the chip will read over I2C
 * upon hardware reset. It is also possible to omit the EEPROM. In both cases,
 * the driver will manually reprogram some registers using jam tables to reach
 * an initial state defined by the vendor driver.
 *
 * This Linux driver is written based on an OS-agnostic vendor driver from
 * Realtek. The reference GPL-licensed sources can be found in the OpenWrt
 * source tree under the name rtl8367c. The vendor driver claims to support a
 * number of similar switch controllers from Realtek, but the only hardware we
 * have is the RTL8365MB-VC. Moreover, there does not seem to be any chip under
 * the name RTL8367C. Although one wishes that the 'C' stood for some kind of
 * common hardware revision, there exist examples of chips with the suffix -VC
 * which are explicitly not supported by the rtl8367c driver and which instead
 * require the rtl8367d vendor driver. With all this uncertainty, the driver has
 * been modestly named rtl8365mb. Future implementors may wish to rename things
 * accordingly.
 *
 * In the same family of chips, some carry up to 8 user ports and up to 2
 * extension ports. Where possible this driver tries to make things generic, but
 * more work must be done to support these configurations. According to
 * documentation from Realtek, the family should include the following chips:
 *
 *  - RTL8363NB
 *  - RTL8363NB-VB
 *  - RTL8363SC
 *  - RTL8363SC-VB
 *  - RTL8364NB
 *  - RTL8364NB-VB
 *  - RTL8365MB-VC
 *  - RTL8366SC
 *  - RTL8367RB-VB
 *  - RTL8367SB
 *  - RTL8367S
 *  - RTL8370MB
 *  - RTL8310SR
 *
 * Some of the register logic for these additional chips has been skipped over
 * while implementing this driver. It is therefore not possible to assume that
 * things will work out-of-the-box for other chips, and a careful review of the
 * vendor driver may be needed to expand support. The RTL8365MB-VC seems to be
 * one of the simpler chips.
 */

#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/mutex.h>
#include <linux/of_irq.h>
#include <linux/regmap.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>

#include "realtek.h"
#include "realtek-smi.h"
#include "realtek-mdio.h"
#include "rtl83xx.h"

/* Family-specific data and limits */
#define RTL8365MB_PHYADDRMAX
#define RTL8365MB_NUM_PHYREGS
#define RTL8365MB_PHYREGMAX
#define RTL8365MB_MAX_NUM_PORTS
#define RTL8365MB_MAX_NUM_EXTINTS
#define RTL8365MB_LEARN_LIMIT_MAX

/* Chip identification registers */
#define RTL8365MB_CHIP_ID_REG

#define RTL8365MB_CHIP_VER_REG

#define RTL8365MB_MAGIC_REG
#define RTL8365MB_MAGIC_VALUE

/* Chip reset register */
#define RTL8365MB_CHIP_RESET_REG
#define RTL8365MB_CHIP_RESET_SW_MASK
#define RTL8365MB_CHIP_RESET_HW_MASK

/* Interrupt polarity register */
#define RTL8365MB_INTR_POLARITY_REG
#define RTL8365MB_INTR_POLARITY_MASK
#define RTL8365MB_INTR_POLARITY_HIGH
#define RTL8365MB_INTR_POLARITY_LOW

/* Interrupt control/status register - enable/check specific interrupt types */
#define RTL8365MB_INTR_CTRL_REG
#define RTL8365MB_INTR_STATUS_REG
#define RTL8365MB_INTR_SLIENT_START_2_MASK
#define RTL8365MB_INTR_SLIENT_START_MASK
#define RTL8365MB_INTR_ACL_ACTION_MASK
#define RTL8365MB_INTR_CABLE_DIAG_FIN_MASK
#define RTL8365MB_INTR_INTERRUPT_8051_MASK
#define RTL8365MB_INTR_LOOP_DETECTION_MASK
#define RTL8365MB_INTR_GREEN_TIMER_MASK
#define RTL8365MB_INTR_SPECIAL_CONGEST_MASK
#define RTL8365MB_INTR_SPEED_CHANGE_MASK
#define RTL8365MB_INTR_LEARN_OVER_MASK
#define RTL8365MB_INTR_METER_EXCEEDED_MASK
#define RTL8365MB_INTR_LINK_CHANGE_MASK
#define RTL8365MB_INTR_ALL_MASK

/* Per-port interrupt type status registers */
#define RTL8365MB_PORT_LINKDOWN_IND_REG
#define RTL8365MB_PORT_LINKDOWN_IND_MASK

#define RTL8365MB_PORT_LINKUP_IND_REG
#define RTL8365MB_PORT_LINKUP_IND_MASK

/* PHY indirect access registers */
#define RTL8365MB_INDIRECT_ACCESS_CTRL_REG
#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_MASK
#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_READ
#define RTL8365MB_INDIRECT_ACCESS_CTRL_RW_WRITE
#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_MASK
#define RTL8365MB_INDIRECT_ACCESS_CTRL_CMD_VALUE
#define RTL8365MB_INDIRECT_ACCESS_STATUS_REG
#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_REG
#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_5_1_MASK
#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_PHYNUM_MASK
#define RTL8365MB_INDIRECT_ACCESS_ADDRESS_OCPADR_9_6_MASK
#define RTL8365MB_PHY_BASE
#define RTL8365MB_INDIRECT_ACCESS_WRITE_DATA_REG
#define RTL8365MB_INDIRECT_ACCESS_READ_DATA_REG

/* PHY OCP address prefix register */
#define RTL8365MB_GPHY_OCP_MSB_0_REG
#define RTL8365MB_GPHY_OCP_MSB_0_CFG_CPU_OCPADR_MASK
#define RTL8365MB_PHY_OCP_ADDR_PREFIX_MASK

/* The PHY OCP addresses of PHY registers 0~31 start here */
#define RTL8365MB_PHY_OCP_ADDR_PHYREG_BASE

/* External interface port mode values - used in DIGITAL_INTERFACE_SELECT */
#define RTL8365MB_EXT_PORT_MODE_DISABLE
#define RTL8365MB_EXT_PORT_MODE_RGMII
#define RTL8365MB_EXT_PORT_MODE_MII_MAC
#define RTL8365MB_EXT_PORT_MODE_MII_PHY
#define RTL8365MB_EXT_PORT_MODE_TMII_MAC
#define RTL8365MB_EXT_PORT_MODE_TMII_PHY
#define RTL8365MB_EXT_PORT_MODE_GMII
#define RTL8365MB_EXT_PORT_MODE_RMII_MAC
#define RTL8365MB_EXT_PORT_MODE_RMII_PHY
#define RTL8365MB_EXT_PORT_MODE_SGMII
#define RTL8365MB_EXT_PORT_MODE_HSGMII
#define RTL8365MB_EXT_PORT_MODE_1000X_100FX
#define RTL8365MB_EXT_PORT_MODE_1000X
#define RTL8365MB_EXT_PORT_MODE_100FX

/* External interface mode configuration registers 0~1 */
#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG0
#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG1
#define RTL8365MB_DIGITAL_INTERFACE_SELECT_REG(_extint)
#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_MASK(_extint)
#define RTL8365MB_DIGITAL_INTERFACE_SELECT_MODE_OFFSET(_extint)

/* External interface RGMII TX/RX delay configuration registers 0~2 */
#define RTL8365MB_EXT_RGMXF_REG0
#define RTL8365MB_EXT_RGMXF_REG1
#define RTL8365MB_EXT_RGMXF_REG2
#define RTL8365MB_EXT_RGMXF_REG(_extint)
#define RTL8365MB_EXT_RGMXF_RXDELAY_MASK
#define RTL8365MB_EXT_RGMXF_TXDELAY_MASK

/* External interface port speed values - used in DIGITAL_INTERFACE_FORCE */
#define RTL8365MB_PORT_SPEED_10M
#define RTL8365MB_PORT_SPEED_100M
#define RTL8365MB_PORT_SPEED_1000M

/* External interface force configuration registers 0~2 */
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG0
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG1
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG2
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_REG(_extint)
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_EN_MASK
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_NWAY_MASK
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_TXPAUSE_MASK
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_RXPAUSE_MASK
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_LINK_MASK
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_DUPLEX_MASK
#define RTL8365MB_DIGITAL_INTERFACE_FORCE_SPEED_MASK

/* CPU port mask register - controls which ports are treated as CPU ports */
#define RTL8365MB_CPU_PORT_MASK_REG
#define RTL8365MB_CPU_PORT_MASK_MASK

/* CPU control register */
#define RTL8365MB_CPU_CTRL_REG
#define RTL8365MB_CPU_CTRL_TRAP_PORT_EXT_MASK
#define RTL8365MB_CPU_CTRL_TAG_FORMAT_MASK
#define RTL8365MB_CPU_CTRL_RXBYTECOUNT_MASK
#define RTL8365MB_CPU_CTRL_TAG_POSITION_MASK
#define RTL8365MB_CPU_CTRL_TRAP_PORT_MASK
#define RTL8365MB_CPU_CTRL_INSERTMODE_MASK
#define RTL8365MB_CPU_CTRL_EN_MASK

/* Maximum packet length register */
#define RTL8365MB_CFG0_MAX_LEN_REG
#define RTL8365MB_CFG0_MAX_LEN_MASK
#define RTL8365MB_CFG0_MAX_LEN_MAX

/* Port learning limit registers */
#define RTL8365MB_LUT_PORT_LEARN_LIMIT_BASE
#define RTL8365MB_LUT_PORT_LEARN_LIMIT_REG(_physport)

/* Port isolation (forwarding mask) registers */
#define RTL8365MB_PORT_ISOLATION_REG_BASE
#define RTL8365MB_PORT_ISOLATION_REG(_physport)
#define RTL8365MB_PORT_ISOLATION_MASK

/* MSTP port state registers - indexed by tree instance */
#define RTL8365MB_MSTI_CTRL_BASE
#define RTL8365MB_MSTI_CTRL_REG(_msti, _physport)
#define RTL8365MB_MSTI_CTRL_PORT_STATE_OFFSET(_physport)
#define RTL8365MB_MSTI_CTRL_PORT_STATE_MASK(_physport)

/* MIB counter value registers */
#define RTL8365MB_MIB_COUNTER_BASE
#define RTL8365MB_MIB_COUNTER_REG(_x)

/* MIB counter address register */
#define RTL8365MB_MIB_ADDRESS_REG
#define RTL8365MB_MIB_ADDRESS_PORT_OFFSET
#define RTL8365MB_MIB_ADDRESS(_p, _x)

#define RTL8365MB_MIB_CTRL0_REG
#define RTL8365MB_MIB_CTRL0_RESET_MASK
#define RTL8365MB_MIB_CTRL0_BUSY_MASK

/* The DSA callback .get_stats64 runs in atomic context, so we are not allowed
 * to block. On the other hand, accessing MIB counters absolutely requires us to
 * block. The solution is thus to schedule work which polls the MIB counters
 * asynchronously and updates some private data, which the callback can then
 * fetch atomically. Three seconds should be a good enough polling interval.
 */
#define RTL8365MB_STATS_INTERVAL_JIFFIES

enum rtl8365mb_mib_counter_index {};

struct rtl8365mb_mib_counter {};

#define RTL8365MB_MAKE_MIB_COUNTER(_offset, _length, _name)

static struct rtl8365mb_mib_counter rtl8365mb_mib_counters[] =;

static_assert();

struct rtl8365mb_jam_tbl_entry {};

/* Lifted from the vendor driver sources */
static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_8365mb_vc[] =;

static const struct rtl8365mb_jam_tbl_entry rtl8365mb_init_jam_common[] =;

enum rtl8365mb_phy_interface_mode {};

/**
 * struct rtl8365mb_extint - external interface info
 * @port: the port with an external interface
 * @id: the external interface ID, which is either 0, 1, or 2
 * @supported_interfaces: a bitmask of supported PHY interface modes
 *
 * Represents a mapping: port -> { id, supported_interfaces }. To be embedded
 * in &struct rtl8365mb_chip_info for every port with an external interface.
 */
struct rtl8365mb_extint {};

/**
 * struct rtl8365mb_chip_info - static chip-specific info
 * @name: human-readable chip name
 * @chip_id: chip identifier
 * @chip_ver: chip silicon revision
 * @extints: available external interfaces
 * @jam_table: chip-specific initialization jam table
 * @jam_size: size of the chip's jam table
 *
 * These data are specific to a given chip in the family of switches supported
 * by this driver. When adding support for another chip in the family, a new
 * chip info should be added to the rtl8365mb_chip_infos array.
 */
struct rtl8365mb_chip_info {};

/* Chip info for each supported switch in the family */
#define PHY_INTF(_mode)
static const struct rtl8365mb_chip_info rtl8365mb_chip_infos[] =;

enum rtl8365mb_stp_state {};

enum rtl8365mb_cpu_insert {};

enum rtl8365mb_cpu_position {};

enum rtl8365mb_cpu_format {};

enum rtl8365mb_cpu_rxlen {};

/**
 * struct rtl8365mb_cpu - CPU port configuration
 * @enable: enable/disable hardware insertion of CPU tag in switch->CPU frames
 * @mask: port mask of ports that parse should parse CPU tags
 * @trap_port: forward trapped frames to this port
 * @insert: CPU tag insertion mode in switch->CPU frames
 * @position: position of CPU tag in frame
 * @rx_length: minimum CPU RX length
 * @format: CPU tag format
 *
 * Represents the CPU tagging and CPU port configuration of the switch. These
 * settings are configurable at runtime.
 */
struct rtl8365mb_cpu {};

/**
 * struct rtl8365mb_port - private per-port data
 * @priv: pointer to parent realtek_priv data
 * @index: DSA port index, same as dsa_port::index
 * @stats: link statistics populated by rtl8365mb_stats_poll, ready for atomic
 *         access via rtl8365mb_get_stats64
 * @stats_lock: protect the stats structure during read/update
 * @mib_work: delayed work for polling MIB counters
 */
struct rtl8365mb_port {};

/**
 * struct rtl8365mb - driver private data
 * @priv: pointer to parent realtek_priv data
 * @irq: registered IRQ or zero
 * @chip_info: chip-specific info about the attached switch
 * @cpu: CPU tagging and CPU port configuration for this chip
 * @mib_lock: prevent concurrent reads of MIB counters
 * @ports: per-port data
 *
 * Private data for this driver.
 */
struct rtl8365mb {};

static int rtl8365mb_phy_poll_busy(struct realtek_priv *priv)
{}

static int rtl8365mb_phy_ocp_prepare(struct realtek_priv *priv, int phy,
				     u32 ocp_addr)
{}

static int rtl8365mb_phy_ocp_read(struct realtek_priv *priv, int phy,
				  u32 ocp_addr, u16 *data)
{}

static int rtl8365mb_phy_ocp_write(struct realtek_priv *priv, int phy,
				   u32 ocp_addr, u16 data)
{}

static int rtl8365mb_phy_read(struct realtek_priv *priv, int phy, int regnum)
{}

static int rtl8365mb_phy_write(struct realtek_priv *priv, int phy, int regnum,
			       u16 val)
{}

static const struct rtl8365mb_extint *
rtl8365mb_get_port_extint(struct realtek_priv *priv, int port)
{}

static enum dsa_tag_protocol
rtl8365mb_get_tag_protocol(struct dsa_switch *ds, int port,
			   enum dsa_tag_protocol mp)
{}

static int rtl8365mb_ext_config_rgmii(struct realtek_priv *priv, int port,
				      phy_interface_t interface)
{}

static int rtl8365mb_ext_config_forcemode(struct realtek_priv *priv, int port,
					  bool link, int speed, int duplex,
					  bool tx_pause, bool rx_pause)
{}

static void rtl8365mb_phylink_get_caps(struct dsa_switch *ds, int port,
				       struct phylink_config *config)
{}

static void rtl8365mb_phylink_mac_config(struct phylink_config *config,
					 unsigned int mode,
					 const struct phylink_link_state *state)
{}

static void rtl8365mb_phylink_mac_link_down(struct phylink_config *config,
					    unsigned int mode,
					    phy_interface_t interface)
{}

static void rtl8365mb_phylink_mac_link_up(struct phylink_config *config,
					  struct phy_device *phydev,
					  unsigned int mode,
					  phy_interface_t interface,
					  int speed, int duplex, bool tx_pause,
					  bool rx_pause)
{}

static int rtl8365mb_port_change_mtu(struct dsa_switch *ds, int port,
				     int new_mtu)
{}

static int rtl8365mb_port_max_mtu(struct dsa_switch *ds, int port)
{}

static void rtl8365mb_port_stp_state_set(struct dsa_switch *ds, int port,
					 u8 state)
{}

static int rtl8365mb_port_set_learning(struct realtek_priv *priv, int port,
				       bool enable)
{}

static int rtl8365mb_port_set_isolation(struct realtek_priv *priv, int port,
					u32 mask)
{}

static int rtl8365mb_mib_counter_read(struct realtek_priv *priv, int port,
				      u32 offset, u32 length, u64 *mibvalue)
{}

static void rtl8365mb_get_ethtool_stats(struct dsa_switch *ds, int port, u64 *data)
{}

static void rtl8365mb_get_strings(struct dsa_switch *ds, int port, u32 stringset, u8 *data)
{}

static int rtl8365mb_get_sset_count(struct dsa_switch *ds, int port, int sset)
{}

static void rtl8365mb_get_phy_stats(struct dsa_switch *ds, int port,
				    struct ethtool_eth_phy_stats *phy_stats)
{}

static void rtl8365mb_get_mac_stats(struct dsa_switch *ds, int port,
				    struct ethtool_eth_mac_stats *mac_stats)
{}

static void rtl8365mb_get_ctrl_stats(struct dsa_switch *ds, int port,
				     struct ethtool_eth_ctrl_stats *ctrl_stats)
{}

static void rtl8365mb_stats_update(struct realtek_priv *priv, int port)
{}

static void rtl8365mb_stats_poll(struct work_struct *work)
{}

static void rtl8365mb_get_stats64(struct dsa_switch *ds, int port,
				  struct rtnl_link_stats64 *s)
{}

static void rtl8365mb_stats_setup(struct realtek_priv *priv)
{}

static void rtl8365mb_stats_teardown(struct realtek_priv *priv)
{}

static int rtl8365mb_get_and_clear_status_reg(struct realtek_priv *priv, u32 reg,
					      u32 *val)
{}

static irqreturn_t rtl8365mb_irq(int irq, void *data)
{}

static struct irq_chip rtl8365mb_irq_chip =;

static int rtl8365mb_irq_map(struct irq_domain *domain, unsigned int irq,
			     irq_hw_number_t hwirq)
{}

static void rtl8365mb_irq_unmap(struct irq_domain *d, unsigned int irq)
{}

static const struct irq_domain_ops rtl8365mb_irqdomain_ops =;

static int rtl8365mb_set_irq_enable(struct realtek_priv *priv, bool enable)
{}

static int rtl8365mb_irq_enable(struct realtek_priv *priv)
{}

static int rtl8365mb_irq_disable(struct realtek_priv *priv)
{}

static int rtl8365mb_irq_setup(struct realtek_priv *priv)
{}

static void rtl8365mb_irq_teardown(struct realtek_priv *priv)
{}

static int rtl8365mb_cpu_config(struct realtek_priv *priv)
{}

static int rtl8365mb_change_tag_protocol(struct dsa_switch *ds,
					 enum dsa_tag_protocol proto)
{}

static int rtl8365mb_switch_init(struct realtek_priv *priv)
{}

static int rtl8365mb_reset_chip(struct realtek_priv *priv)
{}

static int rtl8365mb_setup(struct dsa_switch *ds)
{}

static void rtl8365mb_teardown(struct dsa_switch *ds)
{}

static int rtl8365mb_get_chip_id_and_ver(struct regmap *map, u32 *id, u32 *ver)
{}

static int rtl8365mb_detect(struct realtek_priv *priv)
{}

static const struct phylink_mac_ops rtl8365mb_phylink_mac_ops =;

static const struct dsa_switch_ops rtl8365mb_switch_ops =;

static const struct realtek_ops rtl8365mb_ops =;

const struct realtek_variant rtl8365mb_variant =;

static const struct of_device_id rtl8365mb_of_match[] =;
MODULE_DEVICE_TABLE(of, rtl8365mb_of_match);

static struct platform_driver rtl8365mb_smi_driver =;

static struct mdio_driver rtl8365mb_mdio_driver =;

static int rtl8365mb_init(void)
{}
module_init();

static void __exit rtl8365mb_exit(void)
{}
module_exit(rtl8365mb_exit);

MODULE_AUTHOR();
MODULE_DESCRIPTION();
MODULE_LICENSE();
MODULE_IMPORT_NS();