Commit 0419c450 authored by David S. Miller's avatar David S. Miller

Merge branch 'stmmac-100GB-Enterprise-MAC-support'

Jose Abreu says:

====================
net: stmmac: 100GB Enterprise MAC support

Adds the support for Enterprise MAC IP version which allows operating
speeds up to 100GB.

Patch 1/4, adds the support in XPCS for XLGMII interface that is used in
this kind of Enterprise MAC IPs.

Patch 2/4, adds the XLGMII interface support in stmmac.

Patch 3/4, adds the HW specific support for Enterprise MAC.

We end in patch 4/4, by updating stmmac documentation to mention the
support for this new IP version.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 9f57db9b 2462a82c
......@@ -32,7 +32,8 @@ is also supported.
DesignWare(R) Cores Ethernet MAC 10/100/1000 Universal version 3.70a
(and older) and DesignWare(R) Cores Ethernet Quality-of-Service version 4.0
(and upper) have been used for developing this driver as well as
DesignWare(R) Cores XGMAC - 10G Ethernet MAC.
DesignWare(R) Cores XGMAC - 10G Ethernet MAC and DesignWare(R) Cores
Enterprise MAC - 100G Ethernet MAC.
This driver supports both the platform bus and PCI.
......@@ -48,6 +49,8 @@ Cores Ethernet Controllers and corresponding minimum and maximum versions:
+-------------------------------+--------------+--------------+--------------+
| XGMAC - 10G Ethernet MAC | 2.10a | N/A | XGMAC2+ |
+-------------------------------+--------------+--------------+--------------+
| XLGMAC - 100G Ethernet MAC | 2.00a | N/A | XLGMAC2+ |
+-------------------------------+--------------+--------------+--------------+
For questions related to hardware requirements, refer to the documentation
supplied with your Ethernet adapter. All hardware requirements listed apply
......@@ -57,7 +60,7 @@ Feature List
============
The following features are available in this driver:
- GMII/MII/RGMII/SGMII/RMII/XGMII Interface
- GMII/MII/RGMII/SGMII/RMII/XGMII/XLGMII Interface
- Half-Duplex / Full-Duplex Operation
- Energy Efficient Ethernet (EEE)
- IEEE 802.3x PAUSE Packets (Flow Control)
......
......@@ -34,6 +34,11 @@
#define DWMAC_CORE_5_00 0x50
#define DWMAC_CORE_5_10 0x51
#define DWXGMAC_CORE_2_10 0x21
#define DWXLGMAC_CORE_2_00 0x20
/* Device ID */
#define DWXGMAC_ID 0x76
#define DWXLGMAC_ID 0x27
#define STMMAC_CHAN0 0 /* Always supported and default for all chips */
......@@ -426,6 +431,12 @@ struct mac_link {
u32 speed5000;
u32 speed10000;
} xgmii;
struct {
u32 speed25000;
u32 speed40000;
u32 speed50000;
u32 speed100000;
} xlgmii;
};
struct mii_regs {
......@@ -459,6 +470,7 @@ struct mac_device_info {
unsigned int pcs;
unsigned int pmt;
unsigned int ps;
unsigned int xlgmac;
};
struct stmmac_rx_routing {
......@@ -470,6 +482,7 @@ int dwmac100_setup(struct stmmac_priv *priv);
int dwmac1000_setup(struct stmmac_priv *priv);
int dwmac4_setup(struct stmmac_priv *priv);
int dwxgmac2_setup(struct stmmac_priv *priv);
int dwxlgmac2_setup(struct stmmac_priv *priv);
void stmmac_set_mac_addr(void __iomem *ioaddr, u8 addr[6],
unsigned int high, unsigned int low);
......
......@@ -9,6 +9,7 @@
#include <linux/iopoll.h>
#include "stmmac.h"
#include "stmmac_ptp.h"
#include "dwxlgmac2.h"
#include "dwxgmac2.h"
static void dwxgmac2_core_init(struct mac_device_info *hw,
......@@ -1485,6 +1486,67 @@ const struct stmmac_ops dwxgmac210_ops = {
.fpe_configure = dwxgmac3_fpe_configure,
};
static void dwxlgmac2_rx_queue_enable(struct mac_device_info *hw, u8 mode,
u32 queue)
{
void __iomem *ioaddr = hw->pcsr;
u32 value;
value = readl(ioaddr + XLGMAC_RXQ_ENABLE_CTRL0) & ~XGMAC_RXQEN(queue);
if (mode == MTL_QUEUE_AVB)
value |= 0x1 << XGMAC_RXQEN_SHIFT(queue);
else if (mode == MTL_QUEUE_DCB)
value |= 0x2 << XGMAC_RXQEN_SHIFT(queue);
writel(value, ioaddr + XLGMAC_RXQ_ENABLE_CTRL0);
}
const struct stmmac_ops dwxlgmac2_ops = {
.core_init = dwxgmac2_core_init,
.set_mac = dwxgmac2_set_mac,
.rx_ipc = dwxgmac2_rx_ipc,
.rx_queue_enable = dwxlgmac2_rx_queue_enable,
.rx_queue_prio = dwxgmac2_rx_queue_prio,
.tx_queue_prio = dwxgmac2_tx_queue_prio,
.rx_queue_routing = NULL,
.prog_mtl_rx_algorithms = dwxgmac2_prog_mtl_rx_algorithms,
.prog_mtl_tx_algorithms = dwxgmac2_prog_mtl_tx_algorithms,
.set_mtl_tx_queue_weight = dwxgmac2_set_mtl_tx_queue_weight,
.map_mtl_to_dma = dwxgmac2_map_mtl_to_dma,
.config_cbs = dwxgmac2_config_cbs,
.dump_regs = dwxgmac2_dump_regs,
.host_irq_status = dwxgmac2_host_irq_status,
.host_mtl_irq_status = dwxgmac2_host_mtl_irq_status,
.flow_ctrl = dwxgmac2_flow_ctrl,
.pmt = dwxgmac2_pmt,
.set_umac_addr = dwxgmac2_set_umac_addr,
.get_umac_addr = dwxgmac2_get_umac_addr,
.set_eee_mode = dwxgmac2_set_eee_mode,
.reset_eee_mode = dwxgmac2_reset_eee_mode,
.set_eee_timer = dwxgmac2_set_eee_timer,
.set_eee_pls = dwxgmac2_set_eee_pls,
.pcs_ctrl_ane = NULL,
.pcs_rane = NULL,
.pcs_get_adv_lp = NULL,
.debug = NULL,
.set_filter = dwxgmac2_set_filter,
.safety_feat_config = dwxgmac3_safety_feat_config,
.safety_feat_irq_status = dwxgmac3_safety_feat_irq_status,
.safety_feat_dump = dwxgmac3_safety_feat_dump,
.set_mac_loopback = dwxgmac2_set_mac_loopback,
.rss_configure = dwxgmac2_rss_configure,
.update_vlan_hash = dwxgmac2_update_vlan_hash,
.rxp_config = dwxgmac3_rxp_config,
.get_mac_tx_timestamp = dwxgmac2_get_mac_tx_timestamp,
.flex_pps_config = dwxgmac2_flex_pps_config,
.sarc_configure = dwxgmac2_sarc_configure,
.enable_vlan = dwxgmac2_enable_vlan,
.config_l3_filter = dwxgmac2_config_l3_filter,
.config_l4_filter = dwxgmac2_config_l4_filter,
.set_arp_offload = dwxgmac2_set_arp_offload,
.est_configure = dwxgmac3_est_configure,
.fpe_configure = dwxgmac3_fpe_configure,
};
int dwxgmac2_setup(struct stmmac_priv *priv)
{
struct mac_device_info *mac = priv->hw;
......@@ -1521,3 +1583,40 @@ int dwxgmac2_setup(struct stmmac_priv *priv)
return 0;
}
int dwxlgmac2_setup(struct stmmac_priv *priv)
{
struct mac_device_info *mac = priv->hw;
dev_info(priv->device, "\tXLGMAC\n");
priv->dev->priv_flags |= IFF_UNICAST_FLT;
mac->pcsr = priv->ioaddr;
mac->multicast_filter_bins = priv->plat->multicast_filter_bins;
mac->unicast_filter_entries = priv->plat->unicast_filter_entries;
mac->mcast_bits_log2 = 0;
if (mac->multicast_filter_bins)
mac->mcast_bits_log2 = ilog2(mac->multicast_filter_bins);
mac->link.duplex = 0;
mac->link.speed1000 = XLGMAC_CONFIG_SS_1000;
mac->link.speed2500 = XLGMAC_CONFIG_SS_2500;
mac->link.xgmii.speed10000 = XLGMAC_CONFIG_SS_10G;
mac->link.xlgmii.speed25000 = XLGMAC_CONFIG_SS_25G;
mac->link.xlgmii.speed40000 = XLGMAC_CONFIG_SS_40G;
mac->link.xlgmii.speed50000 = XLGMAC_CONFIG_SS_50G;
mac->link.xlgmii.speed100000 = XLGMAC_CONFIG_SS_100G;
mac->link.speed_mask = XLGMAC_CONFIG_SS;
mac->mii.addr = XGMAC_MDIO_ADDR;
mac->mii.data = XGMAC_MDIO_DATA;
mac->mii.addr_shift = 16;
mac->mii.addr_mask = GENMASK(20, 16);
mac->mii.reg_shift = 0;
mac->mii.reg_mask = GENMASK(15, 0);
mac->mii.clk_csr_shift = 19;
mac->mii.clk_csr_mask = GENMASK(21, 19);
return 0;
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2020 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare XLGMAC definitions.
*/
#ifndef __STMMAC_DWXLGMAC2_H__
#define __STMMAC_DWXLGMAC2_H__
/* MAC Registers */
#define XLGMAC_CONFIG_SS GENMASK(30, 28)
#define XLGMAC_CONFIG_SS_SHIFT 28
#define XLGMAC_CONFIG_SS_40G (0x0 << XLGMAC_CONFIG_SS_SHIFT)
#define XLGMAC_CONFIG_SS_25G (0x1 << XLGMAC_CONFIG_SS_SHIFT)
#define XLGMAC_CONFIG_SS_50G (0x2 << XLGMAC_CONFIG_SS_SHIFT)
#define XLGMAC_CONFIG_SS_100G (0x3 << XLGMAC_CONFIG_SS_SHIFT)
#define XLGMAC_CONFIG_SS_10G (0x4 << XLGMAC_CONFIG_SS_SHIFT)
#define XLGMAC_CONFIG_SS_2500 (0x6 << XLGMAC_CONFIG_SS_SHIFT)
#define XLGMAC_CONFIG_SS_1000 (0x7 << XLGMAC_CONFIG_SS_SHIFT)
#define XLGMAC_RXQ_ENABLE_CTRL0 0x00000140
#endif /* __STMMAC_DWXLGMAC2_H__ */
......@@ -23,6 +23,18 @@ static u32 stmmac_get_id(struct stmmac_priv *priv, u32 id_reg)
return reg & GENMASK(7, 0);
}
static u32 stmmac_get_dev_id(struct stmmac_priv *priv, u32 id_reg)
{
u32 reg = readl(priv->ioaddr + id_reg);
if (!reg) {
dev_info(priv->device, "Version ID not available\n");
return 0x0;
}
return (reg & GENMASK(15, 8)) >> 8;
}
static void stmmac_dwmac_mode_quirk(struct stmmac_priv *priv)
{
struct mac_device_info *mac = priv->hw;
......@@ -69,11 +81,18 @@ static int stmmac_dwmac4_quirks(struct stmmac_priv *priv)
return 0;
}
static int stmmac_dwxlgmac_quirks(struct stmmac_priv *priv)
{
priv->hw->xlgmac = true;
return 0;
}
static const struct stmmac_hwif_entry {
bool gmac;
bool gmac4;
bool xgmac;
u32 min_id;
u32 dev_id;
const struct stmmac_regs_off regs;
const void *desc;
const void *dma;
......@@ -199,6 +218,7 @@ static const struct stmmac_hwif_entry {
.gmac4 = false,
.xgmac = true,
.min_id = DWXGMAC_CORE_2_10,
.dev_id = DWXGMAC_ID,
.regs = {
.ptp_off = PTP_XGMAC_OFFSET,
.mmc_off = MMC_XGMAC_OFFSET,
......@@ -212,6 +232,25 @@ static const struct stmmac_hwif_entry {
.mmc = &dwxgmac_mmc_ops,
.setup = dwxgmac2_setup,
.quirks = NULL,
}, {
.gmac = false,
.gmac4 = false,
.xgmac = true,
.min_id = DWXLGMAC_CORE_2_00,
.dev_id = DWXLGMAC_ID,
.regs = {
.ptp_off = PTP_XGMAC_OFFSET,
.mmc_off = MMC_XGMAC_OFFSET,
},
.desc = &dwxgmac210_desc_ops,
.dma = &dwxgmac210_dma_ops,
.mac = &dwxlgmac2_ops,
.hwtimestamp = &stmmac_ptp,
.mode = NULL,
.tc = &dwmac510_tc_ops,
.mmc = &dwxgmac_mmc_ops,
.setup = dwxlgmac2_setup,
.quirks = stmmac_dwxlgmac_quirks,
},
};
......@@ -223,13 +262,15 @@ int stmmac_hwif_init(struct stmmac_priv *priv)
const struct stmmac_hwif_entry *entry;
struct mac_device_info *mac;
bool needs_setup = true;
u32 id, dev_id = 0;
int i, ret;
u32 id;
if (needs_gmac) {
id = stmmac_get_id(priv, GMAC_VERSION);
} else if (needs_gmac4 || needs_xgmac) {
id = stmmac_get_id(priv, GMAC4_VERSION);
if (needs_xgmac)
dev_id = stmmac_get_dev_id(priv, GMAC4_VERSION);
} else {
id = 0;
}
......@@ -267,6 +308,8 @@ int stmmac_hwif_init(struct stmmac_priv *priv)
/* Use synopsys_id var because some setups can override this */
if (priv->synopsys_id < entry->min_id)
continue;
if (needs_xgmac && (dev_id ^ entry->dev_id))
continue;
/* Only use generic HW helpers if needed */
mac->desc = mac->desc ? : entry->desc;
......
......@@ -605,6 +605,7 @@ extern const struct stmmac_dma_ops dwmac410_dma_ops;
extern const struct stmmac_ops dwmac510_ops;
extern const struct stmmac_tc_ops dwmac510_tc_ops;
extern const struct stmmac_ops dwxgmac210_ops;
extern const struct stmmac_ops dwxlgmac2_ops;
extern const struct stmmac_dma_ops dwxgmac210_dma_ops;
extern const struct stmmac_desc_ops dwxgmac210_desc_ops;
extern const struct stmmac_mmc_ops dwmac_mmc_ops;
......
......@@ -849,6 +849,38 @@ static void stmmac_validate(struct phylink_config *config,
phylink_set(mac_supported, 10000baseKX4_Full);
phylink_set(mac_supported, 10000baseKR_Full);
}
if (!max_speed || (max_speed >= 25000)) {
phylink_set(mac_supported, 25000baseCR_Full);
phylink_set(mac_supported, 25000baseKR_Full);
phylink_set(mac_supported, 25000baseSR_Full);
}
if (!max_speed || (max_speed >= 40000)) {
phylink_set(mac_supported, 40000baseKR4_Full);
phylink_set(mac_supported, 40000baseCR4_Full);
phylink_set(mac_supported, 40000baseSR4_Full);
phylink_set(mac_supported, 40000baseLR4_Full);
}
if (!max_speed || (max_speed >= 50000)) {
phylink_set(mac_supported, 50000baseCR2_Full);
phylink_set(mac_supported, 50000baseKR2_Full);
phylink_set(mac_supported, 50000baseSR2_Full);
phylink_set(mac_supported, 50000baseKR_Full);
phylink_set(mac_supported, 50000baseSR_Full);
phylink_set(mac_supported, 50000baseCR_Full);
phylink_set(mac_supported, 50000baseLR_ER_FR_Full);
phylink_set(mac_supported, 50000baseDR_Full);
}
if (!max_speed || (max_speed >= 100000)) {
phylink_set(mac_supported, 100000baseKR4_Full);
phylink_set(mac_supported, 100000baseSR4_Full);
phylink_set(mac_supported, 100000baseCR4_Full);
phylink_set(mac_supported, 100000baseLR4_ER4_Full);
phylink_set(mac_supported, 100000baseKR2_Full);
phylink_set(mac_supported, 100000baseSR2_Full);
phylink_set(mac_supported, 100000baseCR2_Full);
phylink_set(mac_supported, 100000baseLR2_ER2_FR2_Full);
phylink_set(mac_supported, 100000baseDR2_Full);
}
}
/* Half-Duplex can only work with single queue */
......@@ -929,6 +961,32 @@ static void stmmac_mac_link_up(struct phylink_config *config,
default:
return;
}
} else if (interface == PHY_INTERFACE_MODE_XLGMII) {
switch (speed) {
case SPEED_100000:
ctrl |= priv->hw->link.xlgmii.speed100000;
break;
case SPEED_50000:
ctrl |= priv->hw->link.xlgmii.speed50000;
break;
case SPEED_40000:
ctrl |= priv->hw->link.xlgmii.speed40000;
break;
case SPEED_25000:
ctrl |= priv->hw->link.xlgmii.speed25000;
break;
case SPEED_10000:
ctrl |= priv->hw->link.xgmii.speed10000;
break;
case SPEED_2500:
ctrl |= priv->hw->link.speed2500;
break;
case SPEED_1000:
ctrl |= priv->hw->link.speed1000;
break;
default:
return;
}
} else {
switch (speed) {
case SPEED_2500:
......
......@@ -14,6 +14,7 @@
#define SYNOPSYS_XPCS_USXGMII_ID 0x7996ced0
#define SYNOPSYS_XPCS_10GKR_ID 0x7996ced0
#define SYNOPSYS_XPCS_XLGMII_ID 0x7996ced0
#define SYNOPSYS_XPCS_MASK 0xffffffff
/* Vendor regs access */
......@@ -74,6 +75,36 @@ static const int xpcs_10gkr_features[] = {
__ETHTOOL_LINK_MODE_MASK_NBITS,
};
static const int xpcs_xlgmii_features[] = {
ETHTOOL_LINK_MODE_Pause_BIT,
ETHTOOL_LINK_MODE_Asym_Pause_BIT,
ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT,
ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT,
ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT,
ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT,
ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT,
ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT,
ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT,
ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
ETHTOOL_LINK_MODE_50000baseDR_Full_BIT,
ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT,
ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT,
ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT,
ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT,
ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT,
ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT,
ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT,
ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT,
__ETHTOOL_LINK_MODE_MASK_NBITS,
};
static const phy_interface_t xpcs_usxgmii_interfaces[] = {
PHY_INTERFACE_MODE_USXGMII,
PHY_INTERFACE_MODE_MAX,
......@@ -84,6 +115,11 @@ static const phy_interface_t xpcs_10gkr_interfaces[] = {
PHY_INTERFACE_MODE_MAX,
};
static const phy_interface_t xpcs_xlgmii_interfaces[] = {
PHY_INTERFACE_MODE_XLGMII,
PHY_INTERFACE_MODE_MAX,
};
static struct xpcs_id {
u32 id;
u32 mask;
......@@ -100,6 +136,11 @@ static struct xpcs_id {
.mask = SYNOPSYS_XPCS_MASK,
.supported = xpcs_10gkr_features,
.interface = xpcs_10gkr_interfaces,
}, {
.id = SYNOPSYS_XPCS_XLGMII_ID,
.mask = SYNOPSYS_XPCS_MASK,
.supported = xpcs_xlgmii_features,
.interface = xpcs_xlgmii_interfaces,
},
};
......@@ -458,6 +499,60 @@ static void xpcs_resolve_lpa(struct mdio_xpcs_args *xpcs,
state->duplex = DUPLEX_FULL;
}
static int xpcs_get_max_xlgmii_speed(struct mdio_xpcs_args *xpcs,
struct phylink_link_state *state)
{
unsigned long *adv = state->advertising;
int speed = SPEED_UNKNOWN;
int bit;
for_each_set_bit(bit, adv, __ETHTOOL_LINK_MODE_MASK_NBITS) {
int new_speed = SPEED_UNKNOWN;
switch (bit) {
case ETHTOOL_LINK_MODE_25000baseCR_Full_BIT:
case ETHTOOL_LINK_MODE_25000baseKR_Full_BIT:
case ETHTOOL_LINK_MODE_25000baseSR_Full_BIT:
new_speed = SPEED_25000;
break;
case ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT:
case ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT:
case ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT:
case ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT:
new_speed = SPEED_40000;
break;
case ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT:
case ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT:
case ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT:
case ETHTOOL_LINK_MODE_50000baseKR_Full_BIT:
case ETHTOOL_LINK_MODE_50000baseSR_Full_BIT:
case ETHTOOL_LINK_MODE_50000baseCR_Full_BIT:
case ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT:
case ETHTOOL_LINK_MODE_50000baseDR_Full_BIT:
new_speed = SPEED_50000;
break;
case ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT:
case ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT:
case ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT:
case ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT:
case ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT:
case ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT:
case ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT:
case ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT:
case ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT:
new_speed = SPEED_100000;
break;
default:
continue;
}
if (new_speed > speed)
speed = new_speed;
}
return speed;
}
static void xpcs_resolve_pma(struct mdio_xpcs_args *xpcs,
struct phylink_link_state *state)
{
......@@ -468,6 +563,9 @@ static void xpcs_resolve_pma(struct mdio_xpcs_args *xpcs,
case PHY_INTERFACE_MODE_10GKR:
state->speed = SPEED_10000;
break;
case PHY_INTERFACE_MODE_XLGMII:
state->speed = xpcs_get_max_xlgmii_speed(xpcs, state);
break;
default:
state->speed = SPEED_UNKNOWN;
break;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment