Commit 22e74389 authored by Daniel Scheller's avatar Daniel Scheller Committed by Mauro Carvalho Chehab

media: ddbridge: bump ddbridge code to version 0.9.29

This huge patch bumps the ddbridge driver to version 0.9.29. Compared to
the vendor driver package, DD OctoNET including GTL link support, and all
DVB-C Modulator card support has been removed since this requires large
changes to the underlying DVB core API, which should eventually be done
separately, and, after that, the functionality/device support can be added
back rather easy.

While the diff is rather large, the bump is mostly a big refactor of all
data structures. Yet, the MSI support (message signaled interrupts) is
greatly improved, also all currently available CI single/duo bridge cards
are fully supported.

More changes compared to the upstream driver:
 - the DDB_USE_WORKER flag/define was removed, kernel worker functionality
   will be used.
 - coding style is properly fixed (zero complaints from checkpatch)
 - all (not much though) CamelCase has been fixed to kernel_case
 - (private) IOCTLs temporarily removed (which are mainly used to provide
   rarely-used FPGA update functionality)

Great care has been taken to keep all previous changes and fixes (e.g.
kernel logging via dev_*(), pointer annotations and such) intact.

Permission to reuse and mainline the driver code was formally granted by
Ralph Metzler <rjkm@metzlerbros.de>.
Signed-off-by: default avatarDaniel Scheller <d.scheller@gmx.net>
Tested-by: default avatarRichard Scobie <r.scobie@clear.net.nz>
Tested-by: default avatarJasmin Jessich <jasmin@anw.at>
Tested-by: default avatarDietmar Spingler <d_spingler@freenet.de>
Tested-by: default avatarManfred Knick <Manfred.Knick@t-online.de>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@s-opensource.com>
parent a96e5ab8
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* ddbridge.c: Digital Devices PCIe bridge driver
* ddbridge-i2c.c: Digital Devices bridge i2c driver
*
* Copyright (C) 2010-2011 Digital Devices GmbH
* Copyright (C) 2010-2017 Digital Devices GmbH
* Ralph Metzler <rjkm@metzlerbros.de>
* Marcus Metzler <mocm@metzlerbros.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 only, as published by the Free Software Foundation.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* To obtain the license, point your browser to
* http://www.gnu.org/copyleft/gpl.html
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
......@@ -34,30 +31,47 @@
#include <linux/vmalloc.h>
#include "ddbridge.h"
#include "ddbridge-regs.h"
#include "ddbridge-i2c.h"
#include "ddbridge-regs.h"
/******************************************************************************/
static int ddb_i2c_cmd(struct ddb_i2c *i2c, u32 adr, u32 cmd)
{
struct ddb *dev = i2c->dev;
long stat;
unsigned long stat;
u32 val;
i2c->done = 0;
ddbwritel((adr << 9) | cmd, i2c->regs + I2C_COMMAND);
stat = wait_event_timeout(i2c->wq, i2c->done == 1, HZ);
ddbwritel(dev, (adr << 9) | cmd, i2c->regs + I2C_COMMAND);
stat = wait_for_completion_timeout(&i2c->completion, HZ);
val = ddbreadl(dev, i2c->regs + I2C_COMMAND);
if (stat == 0) {
dev_err(&dev->pdev->dev, "I2C timeout\n");
{ /* MSI debugging*/
u32 istat = ddbreadl(INTERRUPT_STATUS);
dev_err(&dev->pdev->dev, "IRS %08x\n", istat);
ddbwritel(istat, INTERRUPT_ACK);
dev_err(dev->dev, "I2C timeout, card %d, port %d, link %u\n",
dev->nr, i2c->nr, i2c->link);
{
u32 istat = ddbreadl(dev, INTERRUPT_STATUS);
dev_err(dev->dev, "DDBridge IRS %08x\n", istat);
if (i2c->link) {
u32 listat = ddbreadl(dev,
DDB_LINK_TAG(i2c->link) |
INTERRUPT_STATUS);
dev_err(dev->dev, "DDBridge link %u IRS %08x\n",
i2c->link, listat);
}
if (istat & 1) {
ddbwritel(dev, istat & 1, INTERRUPT_ACK);
} else {
u32 mon = ddbreadl(dev,
i2c->regs + I2C_MONITOR);
dev_err(dev->dev, "I2C cmd=%08x mon=%08x\n",
val, mon);
}
}
return -EIO;
}
val = ddbreadl(i2c->regs+I2C_COMMAND);
if (val & 0x70000)
return -EIO;
return 0;
......@@ -66,48 +80,54 @@ static int ddb_i2c_cmd(struct ddb_i2c *i2c, u32 adr, u32 cmd)
static int ddb_i2c_master_xfer(struct i2c_adapter *adapter,
struct i2c_msg msg[], int num)
{
struct ddb_i2c *i2c = (struct ddb_i2c *)i2c_get_adapdata(adapter);
struct ddb_i2c *i2c = (struct ddb_i2c *) i2c_get_adapdata(adapter);
struct ddb *dev = i2c->dev;
u8 addr = 0;
if (num)
addr = msg[0].addr;
if (num == 2 && msg[1].flags & I2C_M_RD &&
!(msg[0].flags & I2C_M_RD)) {
memcpy_toio(dev->regs + I2C_TASKMEM_BASE + i2c->wbuf,
msg[0].buf, msg[0].len);
ddbwritel(msg[0].len|(msg[1].len << 16),
i2c->regs+I2C_TASKLENGTH);
if (!ddb_i2c_cmd(i2c, addr, 1)) {
memcpy_fromio(msg[1].buf,
dev->regs + I2C_TASKMEM_BASE + i2c->rbuf,
msg[1].len);
if (msg[0].len > i2c->bsize)
return -EIO;
switch (num) {
case 1:
if (msg[0].flags & I2C_M_RD) {
ddbwritel(dev, msg[0].len << 16,
i2c->regs + I2C_TASKLENGTH);
if (ddb_i2c_cmd(i2c, addr, 3))
break;
ddbcpyfrom(dev, msg[0].buf,
i2c->rbuf, msg[0].len);
return num;
}
}
if (num == 1 && !(msg[0].flags & I2C_M_RD)) {
ddbcpyto(I2C_TASKMEM_BASE + i2c->wbuf, msg[0].buf, msg[0].len);
ddbwritel(msg[0].len, i2c->regs + I2C_TASKLENGTH);
if (!ddb_i2c_cmd(i2c, addr, 2))
ddbcpyto(dev, i2c->wbuf, msg[0].buf, msg[0].len);
ddbwritel(dev, msg[0].len, i2c->regs + I2C_TASKLENGTH);
if (ddb_i2c_cmd(i2c, addr, 2))
break;
return num;
}
if (num == 1 && (msg[0].flags & I2C_M_RD)) {
ddbwritel(msg[0].len << 16, i2c->regs + I2C_TASKLENGTH);
if (!ddb_i2c_cmd(i2c, addr, 3)) {
ddbcpyfrom(msg[0].buf,
I2C_TASKMEM_BASE + i2c->rbuf, msg[0].len);
case 2:
if ((msg[0].flags & I2C_M_RD) == I2C_M_RD)
break;
if ((msg[1].flags & I2C_M_RD) != I2C_M_RD)
break;
if (msg[1].len > i2c->bsize)
break;
ddbcpyto(dev, i2c->wbuf, msg[0].buf, msg[0].len);
ddbwritel(dev, msg[0].len | (msg[1].len << 16),
i2c->regs + I2C_TASKLENGTH);
if (ddb_i2c_cmd(i2c, addr, 1))
break;
ddbcpyfrom(dev, msg[1].buf,
i2c->rbuf,
msg[1].len);
return num;
}
default:
break;
}
return -EIO;
}
static u32 ddb_i2c_functionality(struct i2c_adapter *adap)
{
return I2C_FUNC_SMBUS_EMUL;
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm ddb_i2c_algo = {
......@@ -119,32 +139,38 @@ void ddb_i2c_release(struct ddb *dev)
{
int i;
struct ddb_i2c *i2c;
struct i2c_adapter *adap;
for (i = 0; i < dev->info->port_num; i++) {
for (i = 0; i < dev->i2c_num; i++) {
i2c = &dev->i2c[i];
adap = &i2c->adap;
i2c_del_adapter(adap);
i2c_del_adapter(&i2c->adap);
}
}
int ddb_i2c_init(struct ddb *dev)
static void i2c_handler(unsigned long priv)
{
struct ddb_i2c *i2c = (struct ddb_i2c *) priv;
complete(&i2c->completion);
}
static int ddb_i2c_add(struct ddb *dev, struct ddb_i2c *i2c,
struct ddb_regmap *regmap, int link, int i, int num)
{
int i, j, stat = 0;
struct ddb_i2c *i2c;
struct i2c_adapter *adap;
for (i = 0; i < dev->info->port_num; i++) {
i2c = &dev->i2c[i];
i2c->dev = dev;
i2c->nr = i;
i2c->wbuf = i * (I2C_TASKMEM_SIZE / 4);
i2c->rbuf = i2c->wbuf + (I2C_TASKMEM_SIZE / 8);
i2c->regs = 0x80 + i * 0x20;
ddbwritel(I2C_SPEED_100, i2c->regs + I2C_TIMING);
ddbwritel((i2c->rbuf << 16) | i2c->wbuf,
i2c->dev = dev;
i2c->link = link;
i2c->bsize = regmap->i2c_buf->size;
i2c->wbuf = DDB_LINK_TAG(link) |
(regmap->i2c_buf->base + i2c->bsize * i);
i2c->rbuf = i2c->wbuf; /* + i2c->bsize / 2 */
i2c->regs = DDB_LINK_TAG(link) |
(regmap->i2c->base + regmap->i2c->size * i);
ddbwritel(dev, I2C_SPEED_100, i2c->regs + I2C_TIMING);
ddbwritel(dev, ((i2c->rbuf & 0xffff) << 16) | (i2c->wbuf & 0xffff),
i2c->regs + I2C_TASKADDRESS);
init_waitqueue_head(&i2c->wq);
init_completion(&i2c->completion);
adap = &i2c->adap;
i2c_set_adapdata(adap, i2c);
......@@ -155,19 +181,48 @@ int ddb_i2c_init(struct ddb *dev)
adap->class = I2C_CLASS_TV_ANALOG;
#endif
#endif
strcpy(adap->name, "ddbridge");
snprintf(adap->name, I2C_NAME_SIZE, "ddbridge_%02x.%x.%x",
dev->nr, i2c->link, i);
adap->algo = &ddb_i2c_algo;
adap->algo_data = (void *)i2c;
adap->dev.parent = &dev->pdev->dev;
stat = i2c_add_adapter(adap);
adap->dev.parent = dev->dev;
return i2c_add_adapter(adap);
}
int ddb_i2c_init(struct ddb *dev)
{
int stat = 0;
u32 i, j, num = 0, l, base;
struct ddb_i2c *i2c;
struct i2c_adapter *adap;
struct ddb_regmap *regmap;
for (l = 0; l < DDB_MAX_LINK; l++) {
if (!dev->link[l].info)
continue;
regmap = dev->link[l].info->regmap;
if (!regmap || !regmap->i2c)
continue;
base = regmap->irq_base_i2c;
for (i = 0; i < regmap->i2c->num; i++) {
if (!(dev->link[l].info->i2c_mask & (1 << i)))
continue;
i2c = &dev->i2c[num];
dev->handler_data[l][i + base] = (unsigned long) i2c;
dev->handler[l][i + base] = i2c_handler;
stat = ddb_i2c_add(dev, i2c, regmap, l, i, num);
if (stat)
break;
num++;
}
if (stat)
for (j = 0; j < i; j++) {
}
if (stat) {
for (j = 0; j < num; j++) {
i2c = &dev->i2c[j];
adap = &i2c->adap;
i2c_del_adapter(adap);
}
} else
dev->i2c_num = num;
return stat;
}
/*
* ddbridge.c: Digital Devices PCIe bridge driver
* ddbridge-i2c.c: Digital Devices bridge i2c driver
*
* Copyright (C) 2010-2011 Digital Devices GmbH
* Copyright (C) 2010-2017 Digital Devices GmbH
* Ralph Metzler <rjkm@metzlerbros.de>
* Marcus Metzler <mocm@metzlerbros.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 only, as published by the Free Software Foundation.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* To obtain the license, point your browser to
* http://www.gnu.org/copyleft/gpl.html
*/
#ifndef __DDBRIDGE_I2C_H__
......@@ -70,24 +69,26 @@ static int __maybe_unused i2c_read_regs(struct i2c_adapter *adapter,
return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1;
}
static int __maybe_unused i2c_read_reg(struct i2c_adapter *adapter, u8 adr,
u8 reg, u8 *val)
{
return i2c_read_regs(adapter, adr, reg, val, 1);
}
static int __maybe_unused i2c_read_reg16(struct i2c_adapter *adapter,
u8 adr, u16 reg, u8 *val)
static int __maybe_unused i2c_read_regs16(struct i2c_adapter *adapter,
u8 adr, u16 reg, u8 *val, u8 len)
{
u8 msg[2] = { reg >> 8, reg & 0xff };
struct i2c_msg msgs[2] = { { .addr = adr, .flags = 0,
.buf = msg, .len = 2 },
{ .addr = adr, .flags = I2C_M_RD,
.buf = val, .len = 1 } };
.buf = val, .len = len } };
return (i2c_transfer(adapter, msgs, 2) == 2) ? 0 : -1;
}
static int __maybe_unused i2c_write_reg16(struct i2c_adapter *adap,
u8 adr, u16 reg, u8 val)
{
u8 msg[3] = { reg >> 8, reg & 0xff, val };
return i2c_write(adap, adr, msg, 3);
}
static int __maybe_unused i2c_write_reg(struct i2c_adapter *adap,
u8 adr, u8 reg, u8 val)
{
......@@ -96,4 +97,16 @@ static int __maybe_unused i2c_write_reg(struct i2c_adapter *adap,
return i2c_write(adap, adr, msg, 2);
}
static int __maybe_unused i2c_read_reg16(struct i2c_adapter *adapter,
u8 adr, u16 reg, u8 *val)
{
return i2c_read_regs16(adapter, adr, reg, val, 1);
}
static int __maybe_unused i2c_read_reg(struct i2c_adapter *adapter,
u8 adr, u8 reg, u8 *val)
{
return i2c_read_regs(adapter, adr, reg, val, 1);
}
#endif /* __DDBRIDGE_I2C_H__ */
This diff is collapsed.
/*
* ddbridge-regs.h: Digital Devices PCIe bridge driver
*
* Copyright (C) 2010-2011 Digital Devices GmbH
* Copyright (C) 2010-2017 Digital Devices GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
......@@ -17,20 +17,26 @@
* http://www.gnu.org/copyleft/gpl.html
*/
/* DD-DVBBridgeV1.h 273 2010-09-17 05:03:16Z manfred */
/* ------------------------------------------------------------------------- */
/* SPI Controller */
/* Register Definitions */
#define SPI_CONTROL 0x10
#define SPI_DATA 0x14
#define CUR_REGISTERMAP_VERSION 0x10000
/* ------------------------------------------------------------------------- */
/* GPIO */
#define HARDWARE_VERSION 0x00
#define REGISTERMAP_VERSION 0x04
#define GPIO_OUTPUT 0x20
#define GPIO_INPUT 0x24
#define GPIO_DIRECTION 0x28
/* ------------------------------------------------------------------------- */
/* SPI Controller */
/* MDIO */
#define SPI_CONTROL 0x10
#define SPI_DATA 0x14
#define MDIO_CTRL 0x20
#define MDIO_ADR 0x24
#define MDIO_REG 0x28
#define MDIO_VAL 0x2C
/* ------------------------------------------------------------------------- */
......@@ -38,14 +44,14 @@
/* ------------------------------------------------------------------------- */
/* Interrupt controller */
/* How many MSI's are available depends on HW (Min 2 max 8) */
/* How many are usable also depends on Host platform */
/* Interrupt controller
* How many MSI's are available depends on HW (Min 2 max 8)
* How many are usable also depends on Host platform
*/
#define INTERRUPT_BASE (0x40)
#define INTERRUPT_ENABLE (INTERRUPT_BASE + 0x00)
#define MSI0_ENABLE (INTERRUPT_BASE + 0x00)
#define MSI1_ENABLE (INTERRUPT_BASE + 0x04)
#define MSI2_ENABLE (INTERRUPT_BASE + 0x08)
#define MSI3_ENABLE (INTERRUPT_BASE + 0x0C)
......@@ -57,59 +63,31 @@
#define INTERRUPT_STATUS (INTERRUPT_BASE + 0x20)
#define INTERRUPT_ACK (INTERRUPT_BASE + 0x20)
#define INTMASK_I2C1 (0x00000001)
#define INTMASK_I2C2 (0x00000002)
#define INTMASK_I2C3 (0x00000004)
#define INTMASK_I2C4 (0x00000008)
#define INTMASK_CIRQ1 (0x00000010)
#define INTMASK_CIRQ2 (0x00000020)
#define INTMASK_CIRQ3 (0x00000040)
#define INTMASK_CIRQ4 (0x00000080)
#define INTMASK_TSINPUT1 (0x00000100)
#define INTMASK_TSINPUT2 (0x00000200)
#define INTMASK_TSINPUT3 (0x00000400)
#define INTMASK_TSINPUT4 (0x00000800)
#define INTMASK_TSINPUT5 (0x00001000)
#define INTMASK_TSINPUT6 (0x00002000)
#define INTMASK_TSINPUT7 (0x00004000)
#define INTMASK_TSINPUT8 (0x00008000)
#define INTMASK_TSOUTPUT1 (0x00010000)
#define INTMASK_TSOUTPUT2 (0x00020000)
#define INTMASK_TSOUTPUT3 (0x00040000)
#define INTMASK_TSOUTPUT4 (0x00080000)
/* Temperature Monitor ( 2x LM75A @ 0x90,0x92 I2c ) */
#define TEMPMON_BASE (0x1c0)
#define TEMPMON_CONTROL (TEMPMON_BASE + 0x00)
#define TEMPMON_CONTROL_AUTOSCAN (0x00000002)
#define TEMPMON_CONTROL_INTENABLE (0x00000004)
#define TEMPMON_CONTROL_OVERTEMP (0x00008000)
/* SHORT Temperature in Celsius x 256 */
#define TEMPMON_SENSOR0 (TEMPMON_BASE + 0x04)
#define TEMPMON_SENSOR1 (TEMPMON_BASE + 0x08)
#define TEMPMON_FANCONTROL (TEMPMON_BASE + 0x10)
/* ------------------------------------------------------------------------- */
/* I2C Master Controller */
#define I2C_BASE (0x80) /* Byte offset */
#define I2C_COMMAND (0x00)
#define I2C_TIMING (0x04)
#define I2C_TASKLENGTH (0x08) /* High read, low write */
#define I2C_TASKADDRESS (0x0C) /* High read, low write */
#define I2C_MONITOR (0x1C)
#define I2C_BASE_1 (I2C_BASE + 0x00)
#define I2C_BASE_2 (I2C_BASE + 0x20)
#define I2C_BASE_3 (I2C_BASE + 0x40)
#define I2C_BASE_4 (I2C_BASE + 0x60)
#define I2C_BASE_N(i) (I2C_BASE + (i) * 0x20)
#define I2C_TASKMEM_BASE (0x1000) /* Byte offset */
#define I2C_TASKMEM_SIZE (0x1000)
#define I2C_SPEED_400 (0x04030404)
#define I2C_SPEED_200 (0x09080909)
#define I2C_SPEED_154 (0x0C0B0C0C)
#define I2C_SPEED_100 (0x13121313)
#define I2C_SPEED_77 (0x19181919)
#define I2C_SPEED_50 (0x27262727)
/* ------------------------------------------------------------------------- */
/* DMA Controller */
......@@ -117,35 +95,41 @@
#define DMA_BASE_WRITE (0x100)
#define DMA_BASE_READ (0x140)
#define DMA_CONTROL (0x00) /* 64 */
#define DMA_ERROR (0x04) /* 65 ( only read instance ) */
#define DMA_DIAG_CONTROL (0x1C) /* 71 */
#define DMA_DIAG_PACKETCOUNTER_LOW (0x20) /* 72 */
#define DMA_DIAG_PACKETCOUNTER_HIGH (0x24) /* 73 */
#define DMA_DIAG_TIMECOUNTER_LOW (0x28) /* 74 */
#define DMA_DIAG_TIMECOUNTER_HIGH (0x2C) /* 75 */
#define DMA_DIAG_RECHECKCOUNTER (0x30) /* 76 ( Split completions on read ) */
#define DMA_DIAG_WAITTIMEOUTINIT (0x34) /* 77 */
#define DMA_DIAG_WAITOVERFLOWCOUNTER (0x38) /* 78 */
#define DMA_DIAG_WAITCOUNTER (0x3C) /* 79 */
#define TS_CONTROL(_io) (_io->regs + 0x00)
#define TS_CONTROL2(_io) (_io->regs + 0x04)
/* ------------------------------------------------------------------------- */
/* DMA Buffer */
#define TS_INPUT_BASE (0x200)
#define TS_INPUT_CONTROL(i) (TS_INPUT_BASE + (i) * 16 + 0x00)
#define DMA_BUFFER_CONTROL(_dma) (_dma->regs + 0x00)
#define DMA_BUFFER_ACK(_dma) (_dma->regs + 0x04)
#define DMA_BUFFER_CURRENT(_dma) (_dma->regs + 0x08)
#define DMA_BUFFER_SIZE(_dma) (_dma->regs + 0x0c)
/* ------------------------------------------------------------------------- */
/* CI Interface (only CI-Bridge) */
#define CI_BASE (0x400)
#define CI_CONTROL(i) (CI_BASE + (i) * 32 + 0x00)
#define CI_DO_ATTRIBUTE_RW(i) (CI_BASE + (i) * 32 + 0x04)
#define CI_DO_IO_RW(i) (CI_BASE + (i) * 32 + 0x08)
#define CI_READDATA(i) (CI_BASE + (i) * 32 + 0x0c)
#define CI_DO_READ_ATTRIBUTES(i) (CI_BASE + (i) * 32 + 0x10)
#define TS_OUTPUT_BASE (0x280)
#define TS_OUTPUT_CONTROL(i) (TS_OUTPUT_BASE + (i) * 16 + 0x00)
#define CI_RESET_CAM (0x00000001)
#define CI_POWER_ON (0x00000002)
#define CI_ENABLE (0x00000004)
#define CI_BYPASS_DISABLE (0x00000010)
#define DMA_BUFFER_BASE (0x300)
#define CI_CAM_READY (0x00010000)
#define CI_CAM_DETECT (0x00020000)
#define CI_READY (0x80000000)
#define DMA_BUFFER_CONTROL(i) (DMA_BUFFER_BASE + (i) * 16 + 0x00)
#define DMA_BUFFER_ACK(i) (DMA_BUFFER_BASE + (i) * 16 + 0x04)
#define DMA_BUFFER_CURRENT(i) (DMA_BUFFER_BASE + (i) * 16 + 0x08)
#define DMA_BUFFER_SIZE(i) (DMA_BUFFER_BASE + (i) * 16 + 0x0c)
#define CI_READ_CMD (0x40000000)
#define CI_WRITE_CMD (0x80000000)
#define DMA_BASE_ADDRESS_TABLE (0x2000)
#define DMA_BASE_ADDRESS_TABLE_ENTRIES (512)
#define CI_BUFFER_BASE (0x3000)
#define CI_BUFFER_SIZE (0x0800)
#define CI_BUFFER(i) (CI_BUFFER_BASE + (i) * CI_BUFFER_SIZE)
This diff is collapsed.
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