Commit caa137ee authored by David S. Miller's avatar David S. Miller

Merge branch 'mv88e6xxx-Watchdog-support'

Andrew Lunn says:

====================
mv88e6xxx Watchdog support

The Marvell switches have an in built watchdog over some of the
internal state machine. The watchdog can be configured to raise an
interrupt on error. The problem the watchdog found is then logged to
the kernel log.

The older switches can automagically perform a software reset when the
watchdog triggers. This just resets the internal state machine, but
leaves the switch configuration unchanged.

The 6390 family of switches cannot both raise an interrupt and
automagically perform a software reset. So the interrupt handler has
to perform the switch reset, and then re-enable the watchdog
interrupts.

This has been tested using hacked together debugfs code which allows
the "force" bit to be set, so cause a watchdog interrupt.

v2: Remove g2_prefix
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 3b03cc07 61303736
......@@ -3111,6 +3111,7 @@ static const struct mv88e6xxx_ops mv88e6085_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.ppu_enable = mv88e6185_g1_ppu_enable,
.ppu_disable = mv88e6185_g1_ppu_disable,
......@@ -3179,6 +3180,7 @@ static const struct mv88e6xxx_ops mv88e6123_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3205,6 +3207,7 @@ static const struct mv88e6xxx_ops mv88e6131_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.ppu_enable = mv88e6185_g1_ppu_enable,
.ppu_disable = mv88e6185_g1_ppu_disable,
......@@ -3232,6 +3235,7 @@ static const struct mv88e6xxx_ops mv88e6161_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3250,6 +3254,7 @@ static const struct mv88e6xxx_ops mv88e6165_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3276,6 +3281,7 @@ static const struct mv88e6xxx_ops mv88e6171_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3304,6 +3310,7 @@ static const struct mv88e6xxx_ops mv88e6172_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3330,6 +3337,7 @@ static const struct mv88e6xxx_ops mv88e6175_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3358,6 +3366,7 @@ static const struct mv88e6xxx_ops mv88e6176_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3380,6 +3389,7 @@ static const struct mv88e6xxx_ops mv88e6185_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.ppu_enable = mv88e6185_g1_ppu_enable,
.ppu_disable = mv88e6185_g1_ppu_disable,
......@@ -3409,6 +3419,7 @@ static const struct mv88e6xxx_ops mv88e6190_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3436,6 +3447,7 @@ static const struct mv88e6xxx_ops mv88e6190x_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3463,6 +3475,7 @@ static const struct mv88e6xxx_ops mv88e6191_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3491,6 +3504,7 @@ static const struct mv88e6xxx_ops mv88e6240_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3519,6 +3533,7 @@ static const struct mv88e6xxx_ops mv88e6290_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3598,6 +3613,7 @@ static const struct mv88e6xxx_ops mv88e6350_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3624,6 +3640,7 @@ static const struct mv88e6xxx_ops mv88e6351_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3652,6 +3669,7 @@ static const struct mv88e6xxx_ops mv88e6352_ops = {
.stats_get_stats = mv88e6095_stats_get_stats,
.g1_set_cpu_port = mv88e6095_g1_set_cpu_port,
.g1_set_egress_port = mv88e6095_g1_set_egress_port,
.watchdog_ops = &mv88e6097_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6095_g2_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3680,6 +3698,7 @@ static const struct mv88e6xxx_ops mv88e6141_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3708,6 +3727,7 @@ static const struct mv88e6xxx_ops mv88e6341_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3738,6 +3758,7 @@ static const struct mv88e6xxx_ops mv88e6390_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3767,6 +3788,7 @@ static const struct mv88e6xxx_ops mv88e6390x_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......@@ -3794,6 +3816,7 @@ static const struct mv88e6xxx_ops mv88e6391_ops = {
.stats_get_stats = mv88e6390_stats_get_stats,
.g1_set_cpu_port = mv88e6390_g1_set_cpu_port,
.g1_set_egress_port = mv88e6390_g1_set_egress_port,
.watchdog_ops = &mv88e6390_watchdog_ops,
.mgmt_rsvd2cpu = mv88e6390_g1_mgmt_rsvd2cpu,
.reset = mv88e6352_g1_reset,
};
......
......@@ -649,6 +649,139 @@ int mv88e6xxx_g2_smi_phy_write(struct mv88e6xxx_chip *chip,
return mv88e6xxx_g2_smi_phy_write_c22(chip, addr, reg, val, external);
}
static int mv88e6097_watchdog_action(struct mv88e6xxx_chip *chip, int irq)
{
u16 reg;
mv88e6xxx_g2_read(chip, GLOBAL2_WDOG_CONTROL, &reg);
dev_info(chip->dev, "Watchdog event: 0x%04x", reg);
return IRQ_HANDLED;
}
static void mv88e6097_watchdog_free(struct mv88e6xxx_chip *chip)
{
u16 reg;
mv88e6xxx_g2_read(chip, GLOBAL2_WDOG_CONTROL, &reg);
reg &= ~(GLOBAL2_WDOG_CONTROL_EGRESS_ENABLE |
GLOBAL2_WDOG_CONTROL_QC_ENABLE);
mv88e6xxx_g2_write(chip, GLOBAL2_WDOG_CONTROL, reg);
}
static int mv88e6097_watchdog_setup(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g2_write(chip, GLOBAL2_WDOG_CONTROL,
GLOBAL2_WDOG_CONTROL_EGRESS_ENABLE |
GLOBAL2_WDOG_CONTROL_QC_ENABLE |
GLOBAL2_WDOG_CONTROL_SWRESET);
}
const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops = {
.irq_action = mv88e6097_watchdog_action,
.irq_setup = mv88e6097_watchdog_setup,
.irq_free = mv88e6097_watchdog_free,
};
static int mv88e6390_watchdog_setup(struct mv88e6xxx_chip *chip)
{
return mv88e6xxx_g2_update(chip, GLOBAL2_WDOG_CONTROL,
GLOBAL2_WDOG_INT_ENABLE |
GLOBAL2_WDOG_CUT_THROUGH |
GLOBAL2_WDOG_QUEUE_CONTROLLER |
GLOBAL2_WDOG_EGRESS |
GLOBAL2_WDOG_FORCE_IRQ);
}
static int mv88e6390_watchdog_action(struct mv88e6xxx_chip *chip, int irq)
{
int err;
u16 reg;
mv88e6xxx_g2_write(chip, GLOBAL2_WDOG_CONTROL, GLOBAL2_WDOG_EVENT);
err = mv88e6xxx_g2_read(chip, GLOBAL2_WDOG_CONTROL, &reg);
dev_info(chip->dev, "Watchdog event: 0x%04x",
reg & GLOBAL2_WDOG_DATA_MASK);
mv88e6xxx_g2_write(chip, GLOBAL2_WDOG_CONTROL, GLOBAL2_WDOG_HISTORY);
err = mv88e6xxx_g2_read(chip, GLOBAL2_WDOG_CONTROL, &reg);
dev_info(chip->dev, "Watchdog history: 0x%04x",
reg & GLOBAL2_WDOG_DATA_MASK);
/* Trigger a software reset to try to recover the switch */
if (chip->info->ops->reset)
chip->info->ops->reset(chip);
mv88e6390_watchdog_setup(chip);
return IRQ_HANDLED;
}
static void mv88e6390_watchdog_free(struct mv88e6xxx_chip *chip)
{
mv88e6xxx_g2_update(chip, GLOBAL2_WDOG_CONTROL,
GLOBAL2_WDOG_INT_ENABLE);
}
const struct mv88e6xxx_irq_ops mv88e6390_watchdog_ops = {
.irq_action = mv88e6390_watchdog_action,
.irq_setup = mv88e6390_watchdog_setup,
.irq_free = mv88e6390_watchdog_free,
};
static irqreturn_t mv88e6xxx_g2_watchdog_thread_fn(int irq, void *dev_id)
{
struct mv88e6xxx_chip *chip = dev_id;
irqreturn_t ret = IRQ_NONE;
mutex_lock(&chip->reg_lock);
if (chip->info->ops->watchdog_ops->irq_action)
ret = chip->info->ops->watchdog_ops->irq_action(chip, irq);
mutex_unlock(&chip->reg_lock);
return ret;
}
static void mv88e6xxx_g2_watchdog_free(struct mv88e6xxx_chip *chip)
{
mutex_lock(&chip->reg_lock);
if (chip->info->ops->watchdog_ops->irq_free)
chip->info->ops->watchdog_ops->irq_free(chip);
mutex_unlock(&chip->reg_lock);
free_irq(chip->watchdog_irq, chip);
irq_dispose_mapping(chip->watchdog_irq);
}
static int mv88e6xxx_g2_watchdog_setup(struct mv88e6xxx_chip *chip)
{
int err;
chip->watchdog_irq = irq_find_mapping(chip->g2_irq.domain,
GLOBAL2_INT_SOURCE_WATCHDOG);
if (chip->watchdog_irq < 0)
return chip->watchdog_irq;
err = request_threaded_irq(chip->watchdog_irq, NULL,
mv88e6xxx_g2_watchdog_thread_fn,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
"mv88e6xxx-watchdog", chip);
if (err)
return err;
mutex_lock(&chip->reg_lock);
if (chip->info->ops->watchdog_ops->irq_setup)
err = chip->info->ops->watchdog_ops->irq_setup(chip);
mutex_unlock(&chip->reg_lock);
return err;
}
static void mv88e6xxx_g2_irq_mask(struct irq_data *d)
{
struct mv88e6xxx_chip *chip = irq_data_get_irq_chip_data(d);
......@@ -737,6 +870,8 @@ void mv88e6xxx_g2_irq_free(struct mv88e6xxx_chip *chip)
{
int irq, virq;
mv88e6xxx_g2_watchdog_free(chip);
free_irq(chip->device_irq, chip);
irq_dispose_mapping(chip->device_irq);
......@@ -779,7 +914,7 @@ int mv88e6xxx_g2_irq_setup(struct mv88e6xxx_chip *chip)
if (err)
goto out;
return 0;
return mv88e6xxx_g2_watchdog_setup(chip);
out:
for (irq = 0; irq < 16; irq++) {
......
......@@ -46,6 +46,9 @@ int mv88e6xxx_g2_irq_setup(struct mv88e6xxx_chip *chip);
void mv88e6xxx_g2_irq_free(struct mv88e6xxx_chip *chip);
int mv88e6095_g2_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip);
extern const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops;
extern const struct mv88e6xxx_irq_ops mv88e6390_watchdog_ops;
#else /* !CONFIG_NET_DSA_MV88E6XXX_GLOBAL2 */
static inline int mv88e6xxx_g2_require(struct mv88e6xxx_chip *chip)
......@@ -125,6 +128,9 @@ static inline int mv88e6095_g2_mgmt_rsvd2cpu(struct mv88e6xxx_chip *chip)
return -EOPNOTSUPP;
}
static const struct mv88e6xxx_irq_ops mv88e6097_watchdog_ops = {};
static const struct mv88e6xxx_irq_ops mv88e6390_watchdog_ops = {};
#endif /* CONFIG_NET_DSA_MV88E6XXX_GLOBAL2 */
#endif /* _MV88E6XXX_GLOBAL2_H */
......@@ -339,6 +339,7 @@
#define GLOBAL_STATS_COUNTER_01 0x1f
#define GLOBAL2_INT_SOURCE 0x00
#define GLOBAL2_INT_SOURCE_WATCHDOG 15
#define GLOBAL2_INT_MASK 0x01
#define GLOBAL2_MGMT_EN_2X 0x02
#define GLOBAL2_MGMT_EN_0X 0x03
......@@ -415,6 +416,25 @@
#define GLOBAL2_SCRATCH_REGISTER_SHIFT 8
#define GLOBAL2_SCRATCH_VALUE_MASK 0xff
#define GLOBAL2_WDOG_CONTROL 0x1b
#define GLOBAL2_WDOG_CONTROL_EGRESS_EVENT BIT(7)
#define GLOBAL2_WDOG_CONTROL_RMU_TIMEOUT BIT(6)
#define GLOBAL2_WDOG_CONTROL_QC_ENABLE BIT(5)
#define GLOBAL2_WDOG_CONTROL_EGRESS_HISTORY BIT(4)
#define GLOBAL2_WDOG_CONTROL_EGRESS_ENABLE BIT(3)
#define GLOBAL2_WDOG_CONTROL_FORCE_IRQ BIT(2)
#define GLOBAL2_WDOG_CONTROL_HISTORY BIT(1)
#define GLOBAL2_WDOG_CONTROL_SWRESET BIT(0)
#define GLOBAL2_WDOG_UPDATE BIT(15)
#define GLOBAL2_WDOG_INT_SOURCE (0x00 << 8)
#define GLOBAL2_WDOG_INT_STATUS (0x10 << 8)
#define GLOBAL2_WDOG_INT_ENABLE (0x11 << 8)
#define GLOBAL2_WDOG_EVENT (0x12 << 8)
#define GLOBAL2_WDOG_HISTORY (0x13 << 8)
#define GLOBAL2_WDOG_DATA_MASK 0xff
#define GLOBAL2_WDOG_CUT_THROUGH BIT(3)
#define GLOBAL2_WDOG_QUEUE_CONTROLLER BIT(2)
#define GLOBAL2_WDOG_EGRESS BIT(1)
#define GLOBAL2_WDOG_FORCE_IRQ BIT(0)
#define GLOBAL2_QOS_WEIGHT 0x1c
#define GLOBAL2_MISC 0x1d
......@@ -666,6 +686,7 @@ enum mv88e6xxx_cap {
#define MV88E6XXX_FLAGS_FAMILY_6390 \
(MV88E6XXX_FLAG_EEE | \
MV88E6XXX_FLAG_GLOBAL2 | \
MV88E6XXX_FLAG_G2_INT | \
MV88E6XXX_FLAG_STU | \
MV88E6XXX_FLAG_VTU | \
MV88E6XXX_FLAGS_IRL | \
......@@ -706,6 +727,7 @@ struct mv88e6xxx_vtu_entry {
};
struct mv88e6xxx_bus_ops;
struct mv88e6xxx_irq_ops;
struct mv88e6xxx_irq {
u16 masked;
......@@ -766,6 +788,7 @@ struct mv88e6xxx_chip {
struct mv88e6xxx_irq g2_irq;
int irq;
int device_irq;
int watchdog_irq;
};
struct mv88e6xxx_bus_ops {
......@@ -879,11 +902,21 @@ struct mv88e6xxx_ops {
uint64_t *data);
int (*g1_set_cpu_port)(struct mv88e6xxx_chip *chip, int port);
int (*g1_set_egress_port)(struct mv88e6xxx_chip *chip, int port);
const struct mv88e6xxx_irq_ops *watchdog_ops;
/* Can be either in g1 or g2, so don't use a prefix */
int (*mgmt_rsvd2cpu)(struct mv88e6xxx_chip *chip);
};
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);
};
#define STATS_TYPE_PORT BIT(0)
#define STATS_TYPE_BANK0 BIT(1)
#define STATS_TYPE_BANK1 BIT(2)
......
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