Commit 4de97b0c authored by David S. Miller's avatar David S. Miller

Merge branch 'enetc-PCIe-MDIO'

Claudiu Manoil says:

====================
enetc: Add mdio bus driver for the PCIe MDIO endpoint

First patch fixes a sparse issue and cleans up accessors to avoid
casting to __iomem.  The second one cleans up the Makefile, to make
it easier to add new entries.

Third patch just registers the PCIe endpoint device containing
the MDIO registers as a standalone MDIO bus driver, to provide
an alternative way to control the MDIO bus.  The same code used
by the ENETC ports (eth controllers) to manage MDIO via local
registers applies and is reused.

Bindings are provided for the new MDIO node, similarly to ENETC
port nodes bindings.

Last patch enables the ENETC port 1 and its RGMII PHY on the
LS1028A QDS board, where the MDIO muxing configuration relies
on the MDIO support provided in the first patch.

Changes since v0:
v1 - fixed mdio bus allocation
v2 - cleaned up accessors to avoid casting
v3 - fixed spelling (mostly commit message)
v4 - fixed err path check blunder
v5 - fixed loadble module build, provided separate kbuild module
     for the driver
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 4a986617 8488d8e9
...@@ -11,7 +11,9 @@ Required properties: ...@@ -11,7 +11,9 @@ Required properties:
to parent node bindings. to parent node bindings.
- compatible : Should be "fsl,enetc". - compatible : Should be "fsl,enetc".
1) The ENETC external port is connected to a MDIO configurable phy: 1. The ENETC external port is connected to a MDIO configurable phy
1.1. Using the local ENETC Port MDIO interface
In this case, the ENETC node should include a "mdio" sub-node In this case, the ENETC node should include a "mdio" sub-node
that in turn should contain the "ethernet-phy" node describing the that in turn should contain the "ethernet-phy" node describing the
...@@ -47,8 +49,42 @@ Example: ...@@ -47,8 +49,42 @@ Example:
}; };
}; };
2) The ENETC port is an internal port or has a fixed-link external 1.2. Using the central MDIO PCIe endpoint device
connection:
In this case, the mdio node should be defined as another PCIe
endpoint node, at the same level with the ENETC port nodes.
Required properties:
- reg : Specifies PCIe Device Number and Function
Number of the ENETC endpoint device, according
to parent node bindings.
- compatible : Should be "fsl,enetc-mdio".
The remaining required mdio bus properties are standard, their bindings
already defined in Documentation/devicetree/bindings/net/mdio.txt.
Example:
ethernet@0,0 {
compatible = "fsl,enetc";
reg = <0x000000 0 0 0 0>;
phy-handle = <&sgmii_phy0>;
phy-connection-type = "sgmii";
};
mdio@0,3 {
compatible = "fsl,enetc-mdio";
reg = <0x000300 0 0 0 0>;
#address-cells = <1>;
#size-cells = <0>;
sgmii_phy0: ethernet-phy@2 {
reg = <0x2>;
};
};
2. The ENETC port is an internal port or has a fixed-link external
connection
In this case, the ENETC port node defines a fixed link connection, In this case, the ENETC port node defines a fixed link connection,
as specified by Documentation/devicetree/bindings/net/fixed-link.txt. as specified by Documentation/devicetree/bindings/net/fixed-link.txt.
......
...@@ -85,6 +85,26 @@ simple-audio-card,codec { ...@@ -85,6 +85,26 @@ simple-audio-card,codec {
system-clock-frequency = <25000000>; system-clock-frequency = <25000000>;
}; };
}; };
mdio-mux {
compatible = "mdio-mux-multiplexer";
mux-controls = <&mux 0>;
mdio-parent-bus = <&enetc_mdio_pf3>;
#address-cells=<1>;
#size-cells = <0>;
/* on-board RGMII PHY */
mdio@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
qds_phy1: ethernet-phy@5 {
/* Atheros 8035 */
reg = <5>;
};
};
};
}; };
&duart0 { &duart0 {
...@@ -164,6 +184,26 @@ sgtl5000: audio-codec@a { ...@@ -164,6 +184,26 @@ sgtl5000: audio-codec@a {
}; };
}; };
}; };
fpga@66 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,ls1028aqds-fpga", "fsl,fpga-qixis-i2c",
"simple-mfd";
reg = <0x66>;
mux: mux-controller {
compatible = "reg-mux";
#mux-control-cells = <1>;
mux-reg-masks = <0x54 0xf0>; /* 0: reg 0x54, bits 7:4 */
};
};
};
&enetc_port1 {
phy-handle = <&qds_phy1>;
phy-connection-type = "rgmii-id";
}; };
&sai1 { &sai1 {
......
...@@ -536,6 +536,12 @@ enetc_port1: ethernet@0,1 { ...@@ -536,6 +536,12 @@ enetc_port1: ethernet@0,1 {
compatible = "fsl,enetc"; compatible = "fsl,enetc";
reg = <0x000100 0 0 0 0>; reg = <0x000100 0 0 0 0>;
}; };
enetc_mdio_pf3: mdio@0,3 {
compatible = "fsl,enetc-mdio";
reg = <0x000300 0 0 0 0>;
#address-cells = <1>;
#size-cells = <0>;
};
ethernet@0,4 { ethernet@0,4 {
compatible = "fsl,enetc-ptp"; compatible = "fsl,enetc-ptp";
reg = <0x000400 0 0 0 0>; reg = <0x000400 0 0 0 0>;
......
...@@ -18,6 +18,15 @@ config FSL_ENETC_VF ...@@ -18,6 +18,15 @@ config FSL_ENETC_VF
If compiled as module (M), the module name is fsl-enetc-vf. If compiled as module (M), the module name is fsl-enetc-vf.
config FSL_ENETC_MDIO
tristate "ENETC MDIO driver"
depends on PCI && (ARCH_LAYERSCAPE || COMPILE_TEST)
help
This driver supports NXP ENETC Central MDIO controller as a PCIe
physical function (PF) device.
If compiled as module (M), the module name is fsl-enetc-mdio.
config FSL_ENETC_PTP_CLOCK config FSL_ENETC_PTP_CLOCK
tristate "ENETC PTP clock driver" tristate "ENETC PTP clock driver"
depends on PTP_1588_CLOCK_QORIQ && (FSL_ENETC || FSL_ENETC_VF) depends on PTP_1588_CLOCK_QORIQ && (FSL_ENETC || FSL_ENETC_VF)
......
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
common-objs := enetc.o enetc_cbdr.o enetc_ethtool.o
obj-$(CONFIG_FSL_ENETC) += fsl-enetc.o obj-$(CONFIG_FSL_ENETC) += fsl-enetc.o
fsl-enetc-$(CONFIG_FSL_ENETC) += enetc.o enetc_cbdr.o enetc_ethtool.o \ fsl-enetc-y := enetc_pf.o enetc_mdio.o $(common-objs)
enetc_mdio.o
fsl-enetc-$(CONFIG_PCI_IOV) += enetc_msg.o fsl-enetc-$(CONFIG_PCI_IOV) += enetc_msg.o
fsl-enetc-objs := enetc_pf.o $(fsl-enetc-y)
obj-$(CONFIG_FSL_ENETC_VF) += fsl-enetc-vf.o obj-$(CONFIG_FSL_ENETC_VF) += fsl-enetc-vf.o
fsl-enetc-vf-y := enetc_vf.o $(common-objs)
ifeq ($(CONFIG_FSL_ENETC)$(CONFIG_FSL_ENETC_VF), yy) obj-$(CONFIG_FSL_ENETC_MDIO) += fsl-enetc-mdio.o
fsl-enetc-vf-objs := enetc_vf.o fsl-enetc-mdio-y := enetc_pci_mdio.o enetc_mdio.o
else
fsl-enetc-vf-$(CONFIG_FSL_ENETC_VF) += enetc.o enetc_cbdr.o \
enetc_ethtool.o
fsl-enetc-vf-objs := enetc_vf.o $(fsl-enetc-vf-y)
endif
obj-$(CONFIG_FSL_ENETC_PTP_CLOCK) += fsl-enetc-ptp.o obj-$(CONFIG_FSL_ENETC_PTP_CLOCK) += fsl-enetc-ptp.o
fsl-enetc-ptp-$(CONFIG_FSL_ENETC_PTP_CLOCK) += enetc_ptp.o fsl-enetc-ptp-y := enetc_ptp.o
...@@ -6,18 +6,20 @@ ...@@ -6,18 +6,20 @@
#include <linux/iopoll.h> #include <linux/iopoll.h>
#include <linux/of.h> #include <linux/of.h>
#include "enetc_pf.h" #include "enetc_mdio.h"
struct enetc_mdio_regs { #define ENETC_MDIO_REG_OFFSET 0x1c00
u32 mdio_cfg; /* MDIO configuration and status */ #define ENETC_MDIO_CFG 0x0 /* MDIO configuration and status */
u32 mdio_ctl; /* MDIO control */ #define ENETC_MDIO_CTL 0x4 /* MDIO control */
u32 mdio_data; /* MDIO data */ #define ENETC_MDIO_DATA 0x8 /* MDIO data */
u32 mdio_addr; /* MDIO address */ #define ENETC_MDIO_ADDR 0xc /* MDIO address */
};
#define bus_to_enetc_regs(bus) (struct enetc_mdio_regs __iomem *)((bus)->priv) #define enetc_mdio_rd(hw, off) \
enetc_port_rd(hw, ENETC_##off + ENETC_MDIO_REG_OFFSET)
#define enetc_mdio_wr(hw, off, val) \
enetc_port_wr(hw, ENETC_##off + ENETC_MDIO_REG_OFFSET, val)
#define enetc_mdio_rd_reg(off) enetc_mdio_rd(hw, off)
#define ENETC_MDIO_REG_OFFSET 0x1c00
#define ENETC_MDC_DIV 258 #define ENETC_MDC_DIV 258
#define MDIO_CFG_CLKDIV(x) ((((x) >> 1) & 0xff) << 8) #define MDIO_CFG_CLKDIV(x) ((((x) >> 1) & 0xff) << 8)
...@@ -33,18 +35,18 @@ struct enetc_mdio_regs { ...@@ -33,18 +35,18 @@ struct enetc_mdio_regs {
#define MDIO_DATA(x) ((x) & 0xffff) #define MDIO_DATA(x) ((x) & 0xffff)
#define TIMEOUT 1000 #define TIMEOUT 1000
static int enetc_mdio_wait_complete(struct enetc_mdio_regs __iomem *regs) static int enetc_mdio_wait_complete(struct enetc_hw *hw)
{ {
u32 val; u32 val;
return readx_poll_timeout(enetc_rd_reg, &regs->mdio_cfg, val, return readx_poll_timeout(enetc_mdio_rd_reg, MDIO_CFG, val,
!(val & MDIO_CFG_BSY), 10, 10 * TIMEOUT); !(val & MDIO_CFG_BSY), 10, 10 * TIMEOUT);
} }
static int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value)
u16 value)
{ {
struct enetc_mdio_regs __iomem *regs = bus_to_enetc_regs(bus); struct enetc_mdio_priv *mdio_priv = bus->priv;
struct enetc_hw *hw = mdio_priv->hw;
u32 mdio_ctl, mdio_cfg; u32 mdio_ctl, mdio_cfg;
u16 dev_addr; u16 dev_addr;
int ret; int ret;
...@@ -59,38 +61,39 @@ static int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, ...@@ -59,38 +61,39 @@ static int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum,
mdio_cfg &= ~MDIO_CFG_ENC45; mdio_cfg &= ~MDIO_CFG_ENC45;
} }
enetc_wr_reg(&regs->mdio_cfg, mdio_cfg); enetc_mdio_wr(hw, MDIO_CFG, mdio_cfg);
ret = enetc_mdio_wait_complete(regs); ret = enetc_mdio_wait_complete(hw);
if (ret) if (ret)
return ret; return ret;
/* set port and dev addr */ /* set port and dev addr */
mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr); mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr);
enetc_wr_reg(&regs->mdio_ctl, mdio_ctl); enetc_mdio_wr(hw, MDIO_CTL, mdio_ctl);
/* set the register address */ /* set the register address */
if (regnum & MII_ADDR_C45) { if (regnum & MII_ADDR_C45) {
enetc_wr_reg(&regs->mdio_addr, regnum & 0xffff); enetc_mdio_wr(hw, MDIO_ADDR, regnum & 0xffff);
ret = enetc_mdio_wait_complete(regs); ret = enetc_mdio_wait_complete(hw);
if (ret) if (ret)
return ret; return ret;
} }
/* write the value */ /* write the value */
enetc_wr_reg(&regs->mdio_data, MDIO_DATA(value)); enetc_mdio_wr(hw, MDIO_DATA, MDIO_DATA(value));
ret = enetc_mdio_wait_complete(regs); ret = enetc_mdio_wait_complete(hw);
if (ret) if (ret)
return ret; return ret;
return 0; return 0;
} }
static int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum) int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
{ {
struct enetc_mdio_regs __iomem *regs = bus_to_enetc_regs(bus); struct enetc_mdio_priv *mdio_priv = bus->priv;
struct enetc_hw *hw = mdio_priv->hw;
u32 mdio_ctl, mdio_cfg; u32 mdio_ctl, mdio_cfg;
u16 dev_addr, value; u16 dev_addr, value;
int ret; int ret;
...@@ -104,41 +107,41 @@ static int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum) ...@@ -104,41 +107,41 @@ static int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
mdio_cfg &= ~MDIO_CFG_ENC45; mdio_cfg &= ~MDIO_CFG_ENC45;
} }
enetc_wr_reg(&regs->mdio_cfg, mdio_cfg); enetc_mdio_wr(hw, MDIO_CFG, mdio_cfg);
ret = enetc_mdio_wait_complete(regs); ret = enetc_mdio_wait_complete(hw);
if (ret) if (ret)
return ret; return ret;
/* set port and device addr */ /* set port and device addr */
mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr); mdio_ctl = MDIO_CTL_PORT_ADDR(phy_id) | MDIO_CTL_DEV_ADDR(dev_addr);
enetc_wr_reg(&regs->mdio_ctl, mdio_ctl); enetc_mdio_wr(hw, MDIO_CTL, mdio_ctl);
/* set the register address */ /* set the register address */
if (regnum & MII_ADDR_C45) { if (regnum & MII_ADDR_C45) {
enetc_wr_reg(&regs->mdio_addr, regnum & 0xffff); enetc_mdio_wr(hw, MDIO_ADDR, regnum & 0xffff);
ret = enetc_mdio_wait_complete(regs); ret = enetc_mdio_wait_complete(hw);
if (ret) if (ret)
return ret; return ret;
} }
/* initiate the read */ /* initiate the read */
enetc_wr_reg(&regs->mdio_ctl, mdio_ctl | MDIO_CTL_READ); enetc_mdio_wr(hw, MDIO_CTL, mdio_ctl | MDIO_CTL_READ);
ret = enetc_mdio_wait_complete(regs); ret = enetc_mdio_wait_complete(hw);
if (ret) if (ret)
return ret; return ret;
/* return all Fs if nothing was there */ /* return all Fs if nothing was there */
if (enetc_rd_reg(&regs->mdio_cfg) & MDIO_CFG_RD_ER) { if (enetc_mdio_rd(hw, MDIO_CFG) & MDIO_CFG_RD_ER) {
dev_dbg(&bus->dev, dev_dbg(&bus->dev,
"Error while reading PHY%d reg at %d.%hhu\n", "Error while reading PHY%d reg at %d.%hhu\n",
phy_id, dev_addr, regnum); phy_id, dev_addr, regnum);
return 0xffff; return 0xffff;
} }
value = enetc_rd_reg(&regs->mdio_data) & 0xffff; value = enetc_mdio_rd(hw, MDIO_DATA) & 0xffff;
return value; return value;
} }
...@@ -146,12 +149,12 @@ static int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum) ...@@ -146,12 +149,12 @@ static int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
int enetc_mdio_probe(struct enetc_pf *pf) int enetc_mdio_probe(struct enetc_pf *pf)
{ {
struct device *dev = &pf->si->pdev->dev; struct device *dev = &pf->si->pdev->dev;
struct enetc_mdio_regs __iomem *regs; struct enetc_mdio_priv *mdio_priv;
struct device_node *np; struct device_node *np;
struct mii_bus *bus; struct mii_bus *bus;
int ret; int err;
bus = mdiobus_alloc_size(sizeof(regs)); bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
if (!bus) if (!bus)
return -ENOMEM; return -ENOMEM;
...@@ -159,41 +162,31 @@ int enetc_mdio_probe(struct enetc_pf *pf) ...@@ -159,41 +162,31 @@ int enetc_mdio_probe(struct enetc_pf *pf)
bus->read = enetc_mdio_read; bus->read = enetc_mdio_read;
bus->write = enetc_mdio_write; bus->write = enetc_mdio_write;
bus->parent = dev; bus->parent = dev;
mdio_priv = bus->priv;
mdio_priv->hw = &pf->si->hw;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev)); snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
/* store the enetc mdio base address for this bus */
regs = pf->si->hw.port + ENETC_MDIO_REG_OFFSET;
bus->priv = regs;
np = of_get_child_by_name(dev->of_node, "mdio"); np = of_get_child_by_name(dev->of_node, "mdio");
if (!np) { if (!np) {
dev_err(dev, "MDIO node missing\n"); dev_err(dev, "MDIO node missing\n");
ret = -EINVAL; return -EINVAL;
goto err_registration;
} }
ret = of_mdiobus_register(bus, np); err = of_mdiobus_register(bus, np);
if (ret) { if (err) {
of_node_put(np); of_node_put(np);
dev_err(dev, "cannot register MDIO bus\n"); dev_err(dev, "cannot register MDIO bus\n");
goto err_registration; return err;
} }
of_node_put(np); of_node_put(np);
pf->mdio = bus; pf->mdio = bus;
return 0; return 0;
err_registration:
mdiobus_free(bus);
return ret;
} }
void enetc_mdio_remove(struct enetc_pf *pf) void enetc_mdio_remove(struct enetc_pf *pf)
{ {
if (pf->mdio) { if (pf->mdio)
mdiobus_unregister(pf->mdio); mdiobus_unregister(pf->mdio);
mdiobus_free(pf->mdio);
}
} }
/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
/* Copyright 2019 NXP */
#include <linux/phy.h>
#include "enetc_pf.h"
struct enetc_mdio_priv {
struct enetc_hw *hw;
};
int enetc_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 value);
int enetc_mdio_read(struct mii_bus *bus, int phy_id, int regnum);
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/* Copyright 2019 NXP */
#include <linux/of_mdio.h>
#include "enetc_mdio.h"
#define ENETC_MDIO_DEV_ID 0xee01
#define ENETC_MDIO_DEV_NAME "FSL PCIe IE Central MDIO"
#define ENETC_MDIO_BUS_NAME ENETC_MDIO_DEV_NAME " Bus"
#define ENETC_MDIO_DRV_NAME ENETC_MDIO_DEV_NAME " driver"
static int enetc_pci_mdio_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct enetc_mdio_priv *mdio_priv;
struct device *dev = &pdev->dev;
struct enetc_hw *hw;
struct mii_bus *bus;
int err;
hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL);
if (!hw)
return -ENOMEM;
bus = devm_mdiobus_alloc_size(dev, sizeof(*mdio_priv));
if (!bus)
return -ENOMEM;
bus->name = ENETC_MDIO_BUS_NAME;
bus->read = enetc_mdio_read;
bus->write = enetc_mdio_write;
bus->parent = dev;
mdio_priv = bus->priv;
mdio_priv->hw = hw;
snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
pcie_flr(pdev);
err = pci_enable_device_mem(pdev);
if (err) {
dev_err(dev, "device enable failed\n");
return err;
}
err = pci_request_region(pdev, 0, KBUILD_MODNAME);
if (err) {
dev_err(dev, "pci_request_region failed\n");
goto err_pci_mem_reg;
}
hw->port = pci_iomap(pdev, 0, 0);
if (!hw->port) {
err = -ENXIO;
dev_err(dev, "iomap failed\n");
goto err_ioremap;
}
err = of_mdiobus_register(bus, dev->of_node);
if (err)
goto err_mdiobus_reg;
pci_set_drvdata(pdev, bus);
return 0;
err_mdiobus_reg:
iounmap(mdio_priv->hw->port);
err_ioremap:
pci_release_mem_regions(pdev);
err_pci_mem_reg:
pci_disable_device(pdev);
return err;
}
static void enetc_pci_mdio_remove(struct pci_dev *pdev)
{
struct mii_bus *bus = pci_get_drvdata(pdev);
struct enetc_mdio_priv *mdio_priv;
mdiobus_unregister(bus);
mdio_priv = bus->priv;
iounmap(mdio_priv->hw->port);
pci_release_mem_regions(pdev);
pci_disable_device(pdev);
}
static const struct pci_device_id enetc_pci_mdio_id_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, ENETC_MDIO_DEV_ID) },
{ 0, } /* End of table. */
};
MODULE_DEVICE_TABLE(pci, enetc_pci_mdio_id_table);
static struct pci_driver enetc_pci_mdio_driver = {
.name = KBUILD_MODNAME,
.id_table = enetc_pci_mdio_id_table,
.probe = enetc_pci_mdio_probe,
.remove = enetc_pci_mdio_remove,
};
module_pci_driver(enetc_pci_mdio_driver);
MODULE_DESCRIPTION(ENETC_MDIO_DRV_NAME);
MODULE_LICENSE("Dual BSD/GPL");
...@@ -750,6 +750,7 @@ static int enetc_of_get_phy(struct enetc_ndev_priv *priv) ...@@ -750,6 +750,7 @@ static int enetc_of_get_phy(struct enetc_ndev_priv *priv)
{ {
struct enetc_pf *pf = enetc_si_priv(priv->si); struct enetc_pf *pf = enetc_si_priv(priv->si);
struct device_node *np = priv->dev->of_node; struct device_node *np = priv->dev->of_node;
struct device_node *mdio_np;
int err; int err;
if (!np) { if (!np) {
...@@ -773,7 +774,9 @@ static int enetc_of_get_phy(struct enetc_ndev_priv *priv) ...@@ -773,7 +774,9 @@ static int enetc_of_get_phy(struct enetc_ndev_priv *priv)
priv->phy_node = of_node_get(np); priv->phy_node = of_node_get(np);
} }
if (!of_phy_is_fixed_link(np)) { mdio_np = of_get_child_by_name(np, "mdio");
if (mdio_np) {
of_node_put(mdio_np);
err = enetc_mdio_probe(pf); err = enetc_mdio_probe(pf);
if (err) { if (err) {
of_node_put(priv->phy_node); of_node_put(priv->phy_node);
......
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