Commit a1016ce3 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge branch 'for-next/musb' of...

Merge branch 'for-next/musb' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb into usb-next

* 'for-next/musb' of git://git.kernel.org/pub/scm/linux/kernel/git/balbi/usb:
  usb: musb: omap2430: fix compile warning
  usb: musb: fix pm_runtime calls while atomic
  usb: musb: drop ARCH dependency
  usb: musb: headers cleanup
  usb: musb: allow building USB_MUSB_TUSB6010 as a module
  usb: musb: use a Kconfig choice to pick the right DMA method
  usb: musb: omap2+: save and restore OTG_INTERFSEL
  usb: musb: omap2+: fix context api's
  usb: musb: ux500: optimize DMA callback routine
parents 007d00d4 e7f4e732
...@@ -46,7 +46,7 @@ static struct device *mmc_device; ...@@ -46,7 +46,7 @@ static struct device *mmc_device;
#define TUSB6010_GPIO_ENABLE 0 #define TUSB6010_GPIO_ENABLE 0
#define TUSB6010_DMACHAN 0x3f #define TUSB6010_DMACHAN 0x3f
#ifdef CONFIG_USB_MUSB_TUSB6010 #if defined(CONFIG_USB_MUSB_TUSB6010) || defined(CONFIG_USB_MUSB_TUSB6010_MODULE)
/* /*
* Enable or disable power to TUSB6010. When enabling, turn on 3.3 V and * Enable or disable power to TUSB6010. When enabling, turn on 3.3 V and
* 1.5 V voltage regulators of PM companion chip. Companion chip will then * 1.5 V voltage regulators of PM companion chip. Companion chip will then
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
# (M)HDRC = (Multipoint) Highspeed Dual-Role Controller # (M)HDRC = (Multipoint) Highspeed Dual-Role Controller
config USB_MUSB_HDRC config USB_MUSB_HDRC
depends on USB && USB_GADGET depends on USB && USB_GADGET
depends on (ARM || (BF54x && !BF544) || (BF52x && !BF522 && !BF523))
select NOP_USB_XCEIV if (ARCH_DAVINCI || MACH_OMAP3EVM || BLACKFIN) select NOP_USB_XCEIV if (ARCH_DAVINCI || MACH_OMAP3EVM || BLACKFIN)
select TWL4030_USB if MACH_OMAP_3430SDP select TWL4030_USB if MACH_OMAP_3430SDP
select TWL6030_USB if MACH_OMAP_4430SDP || MACH_OMAP4_PANDA select TWL6030_USB if MACH_OMAP_4430SDP || MACH_OMAP4_PANDA
...@@ -45,7 +44,6 @@ config USB_MUSB_DA8XX ...@@ -45,7 +44,6 @@ config USB_MUSB_DA8XX
config USB_MUSB_TUSB6010 config USB_MUSB_TUSB6010
tristate "TUSB6010" tristate "TUSB6010"
depends on ARCH_OMAP
config USB_MUSB_OMAP2PLUS config USB_MUSB_OMAP2PLUS
tristate "OMAP2430 and onwards" tristate "OMAP2430 and onwards"
...@@ -65,46 +63,57 @@ config USB_MUSB_UX500 ...@@ -65,46 +63,57 @@ config USB_MUSB_UX500
endchoice endchoice
config MUSB_PIO_ONLY choice
bool 'Disable DMA (always use PIO)' prompt 'MUSB DMA mode'
depends on USB_MUSB_HDRC default USB_UX500_DMA if USB_MUSB_UX500
default USB_MUSB_TUSB6010 || USB_MUSB_DA8XX || USB_MUSB_AM35X default USB_INVENTRA_DMA if USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN
default USB_TI_CPPI_DMA if USB_MUSB_DAVINCI
default USB_TUSB_OMAP_DMA if USB_MUSB_TUSB6010
default MUSB_PIO_ONLY if USB_MUSB_TUSB6010 || USB_MUSB_DA8XX || USB_MUSB_AM35X
help help
All data is copied between memory and FIFO by the CPU. Unfortunately, only one option can be enabled here. Ideally one
DMA controllers are ignored. should be able to build all these drivers into one kernel to
allow using DMA on multiplatform kernels.
Do not select 'n' here unless DMA support for your SOC or board
is unavailable (or unstable). When DMA is enabled at compile time,
you can still disable it at run time using the "use_dma=n" module
parameter.
config USB_UX500_DMA config USB_UX500_DMA
bool bool 'ST Ericsson U8500 and U5500'
depends on USB_MUSB_HDRC && !MUSB_PIO_ONLY depends on USB_MUSB_HDRC
default USB_MUSB_UX500 depends on USB_MUSB_UX500
help help
Enable DMA transfers on UX500 platforms. Enable DMA transfers on UX500 platforms.
config USB_INVENTRA_DMA config USB_INVENTRA_DMA
bool bool 'Inventra'
depends on USB_MUSB_HDRC && !MUSB_PIO_ONLY depends on USB_MUSB_HDRC
default USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN depends on USB_MUSB_OMAP2PLUS || USB_MUSB_BLACKFIN
help help
Enable DMA transfers using Mentor's engine. Enable DMA transfers using Mentor's engine.
config USB_TI_CPPI_DMA config USB_TI_CPPI_DMA
bool bool 'TI CPPI (Davinci)'
depends on USB_MUSB_HDRC && !MUSB_PIO_ONLY depends on USB_MUSB_HDRC
default USB_MUSB_DAVINCI depends on USB_MUSB_DAVINCI
help help
Enable DMA transfers when TI CPPI DMA is available. Enable DMA transfers when TI CPPI DMA is available.
config USB_TUSB_OMAP_DMA config USB_TUSB_OMAP_DMA
bool bool 'TUSB 6010'
depends on USB_MUSB_HDRC && !MUSB_PIO_ONLY depends on USB_MUSB_HDRC
depends on USB_MUSB_TUSB6010 depends on USB_MUSB_TUSB6010
depends on ARCH_OMAP depends on ARCH_OMAP
default y
help help
Enable DMA transfers on TUSB 6010 when OMAP DMA is available. Enable DMA transfers on TUSB 6010 when OMAP DMA is available.
config MUSB_PIO_ONLY
bool 'Disable DMA (always use PIO)'
depends on USB_MUSB_HDRC
help
All data is copied between memory and FIFO by the CPU.
DMA controllers are ignored.
Do not choose this unless DMA support for your SOC or board
is unavailable (or unstable). When DMA is enabled at compile time,
you can still disable it at run time using the "use_dma=n" module
parameter.
endchoice
...@@ -24,25 +24,7 @@ obj-$(CONFIG_USB_MUSB_UX500) += ux500.o ...@@ -24,25 +24,7 @@ obj-$(CONFIG_USB_MUSB_UX500) += ux500.o
# PIO only, or DMA (several potential schemes). # PIO only, or DMA (several potential schemes).
# though PIO is always there to back up DMA, and for ep0 # though PIO is always there to back up DMA, and for ep0
ifneq ($(CONFIG_MUSB_PIO_ONLY),y) musb_hdrc-$(CONFIG_USB_INVENTRA_DMA) += musbhsdma.o
musb_hdrc-$(CONFIG_USB_TI_CPPI_DMA) += cppi_dma.o
ifeq ($(CONFIG_USB_INVENTRA_DMA),y) musb_hdrc-$(CONFIG_USB_TUSB_OMAP_DMA) += tusb6010_omap.o
musb_hdrc-y += musbhsdma.o musb_hdrc-$(CONFIG_USB_UX500_DMA) += ux500_dma.o
else
ifeq ($(CONFIG_USB_TI_CPPI_DMA),y)
musb_hdrc-y += cppi_dma.o
else
ifeq ($(CONFIG_USB_TUSB_OMAP_DMA),y)
musb_hdrc-y += tusb6010_omap.o
else
ifeq ($(CONFIG_USB_UX500_DMA),y)
musb_hdrc-y += ux500_dma.o
endif
endif
endif
endif
endif
...@@ -1431,7 +1431,7 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb) ...@@ -1431,7 +1431,7 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb)
struct musb_hw_ep *hw_ep = musb->endpoints + i; struct musb_hw_ep *hw_ep = musb->endpoints + i;
hw_ep->fifo = MUSB_FIFO_OFFSET(i) + mbase; hw_ep->fifo = MUSB_FIFO_OFFSET(i) + mbase;
#ifdef CONFIG_USB_MUSB_TUSB6010 #if defined(CONFIG_USB_MUSB_TUSB6010) || defined (CONFIG_USB_MUSB_TUSB6010_MODULE)
hw_ep->fifo_async = musb->async + 0x400 + MUSB_FIFO_OFFSET(i); hw_ep->fifo_async = musb->async + 0x400 + MUSB_FIFO_OFFSET(i);
hw_ep->fifo_sync = musb->sync + 0x400 + MUSB_FIFO_OFFSET(i); hw_ep->fifo_sync = musb->sync + 0x400 + MUSB_FIFO_OFFSET(i);
hw_ep->fifo_sync_va = hw_ep->fifo_sync_va =
...@@ -1630,6 +1630,7 @@ void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit) ...@@ -1630,6 +1630,7 @@ void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit)
} }
} }
} }
EXPORT_SYMBOL_GPL(musb_dma_completion);
#else #else
#define use_dma 0 #define use_dma 0
...@@ -2157,6 +2158,7 @@ static void musb_save_context(struct musb *musb) ...@@ -2157,6 +2158,7 @@ static void musb_save_context(struct musb *musb)
if (!epio) if (!epio)
continue; continue;
musb_writeb(musb_base, MUSB_INDEX, i);
musb->context.index_regs[i].txmaxp = musb->context.index_regs[i].txmaxp =
musb_readw(epio, MUSB_TXMAXP); musb_readw(epio, MUSB_TXMAXP);
musb->context.index_regs[i].txcsr = musb->context.index_regs[i].txcsr =
...@@ -2232,6 +2234,7 @@ static void musb_restore_context(struct musb *musb) ...@@ -2232,6 +2234,7 @@ static void musb_restore_context(struct musb *musb)
if (!epio) if (!epio)
continue; continue;
musb_writeb(musb_base, MUSB_INDEX, i);
musb_writew(epio, MUSB_TXMAXP, musb_writew(epio, MUSB_TXMAXP,
musb->context.index_regs[i].txmaxp); musb->context.index_regs[i].txmaxp);
musb_writew(epio, MUSB_TXCSR, musb_writew(epio, MUSB_TXCSR,
......
...@@ -40,7 +40,6 @@ ...@@ -40,7 +40,6 @@
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/clk.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/usb/ch9.h> #include <linux/usb/ch9.h>
#include <linux/usb/gadget.h> #include <linux/usb/gadget.h>
...@@ -311,6 +310,7 @@ struct musb_context_registers { ...@@ -311,6 +310,7 @@ struct musb_context_registers {
u8 index, testmode; u8 index, testmode;
u8 devctl, busctl, misc; u8 devctl, busctl, misc;
u32 otg_interfsel;
struct musb_csr_regs index_regs[MUSB_C_NUM_EPS]; struct musb_csr_regs index_regs[MUSB_C_NUM_EPS];
}; };
...@@ -327,6 +327,7 @@ struct musb { ...@@ -327,6 +327,7 @@ struct musb {
irqreturn_t (*isr)(int, void *); irqreturn_t (*isr)(int, void *);
struct work_struct irq_work; struct work_struct irq_work;
struct work_struct otg_notifier_work;
u16 hwvers; u16 hwvers;
/* this hub status bit is reserved by USB 2.0 and not seen by usbcore */ /* this hub status bit is reserved by USB 2.0 and not seen by usbcore */
...@@ -372,6 +373,7 @@ struct musb { ...@@ -372,6 +373,7 @@ struct musb {
u16 int_tx; u16 int_tx;
struct otg_transceiver *xceiv; struct otg_transceiver *xceiv;
u8 xceiv_event;
int nIrq; int nIrq;
unsigned irq_wake:1; unsigned irq_wake:1;
......
...@@ -33,11 +33,7 @@ ...@@ -33,11 +33,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/list.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/debugfs.h> #include <linux/debugfs.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
...@@ -46,10 +42,6 @@ ...@@ -46,10 +42,6 @@
#include "musb_core.h" #include "musb_core.h"
#include "musb_debug.h" #include "musb_debug.h"
#ifdef CONFIG_ARCH_DAVINCI
#include "davinci.h"
#endif
struct musb_register_map { struct musb_register_map {
char *name; char *name;
unsigned offset; unsigned offset;
......
...@@ -40,8 +40,6 @@ ...@@ -40,8 +40,6 @@
#include <linux/smp.h> #include <linux/smp.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/stat.h>
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/slab.h> #include <linux/slab.h>
......
...@@ -37,7 +37,6 @@ ...@@ -37,7 +37,6 @@
#include <linux/list.h> #include <linux/list.h>
#include <linux/timer.h> #include <linux/timer.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
......
...@@ -74,7 +74,7 @@ static inline void musb_writel(void __iomem *addr, unsigned offset, u32 data) ...@@ -74,7 +74,7 @@ static inline void musb_writel(void __iomem *addr, unsigned offset, u32 data)
{ __raw_writel(data, addr + offset); } { __raw_writel(data, addr + offset); }
#ifdef CONFIG_USB_MUSB_TUSB6010 #if defined(CONFIG_USB_MUSB_TUSB6010) || defined (CONFIG_USB_MUSB_TUSB6010_MODULE)
/* /*
* TUSB6010 doesn't allow 8-bit access; 16-bit access is the minimum. * TUSB6010 doesn't allow 8-bit access; 16-bit access is the minimum.
......
...@@ -29,7 +29,6 @@ ...@@ -29,7 +29,6 @@
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/clk.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
...@@ -228,11 +227,21 @@ static int musb_otg_notifications(struct notifier_block *nb, ...@@ -228,11 +227,21 @@ static int musb_otg_notifications(struct notifier_block *nb,
unsigned long event, void *unused) unsigned long event, void *unused)
{ {
struct musb *musb = container_of(nb, struct musb, nb); struct musb *musb = container_of(nb, struct musb, nb);
musb->xceiv_event = event;
schedule_work(&musb->otg_notifier_work);
return 0;
}
static void musb_otg_notifier_work(struct work_struct *data_notifier_work)
{
struct musb *musb = container_of(data_notifier_work, struct musb, otg_notifier_work);
struct device *dev = musb->controller; struct device *dev = musb->controller;
struct musb_hdrc_platform_data *pdata = dev->platform_data; struct musb_hdrc_platform_data *pdata = dev->platform_data;
struct omap_musb_board_data *data = pdata->board_data; struct omap_musb_board_data *data = pdata->board_data;
switch (event) { switch (musb->xceiv_event) {
case USB_EVENT_ID: case USB_EVENT_ID:
dev_dbg(musb->controller, "ID GND\n"); dev_dbg(musb->controller, "ID GND\n");
...@@ -274,10 +283,7 @@ static int musb_otg_notifications(struct notifier_block *nb, ...@@ -274,10 +283,7 @@ static int musb_otg_notifications(struct notifier_block *nb,
break; break;
default: default:
dev_dbg(musb->controller, "ID float\n"); dev_dbg(musb->controller, "ID float\n");
return NOTIFY_DONE;
} }
return NOTIFY_OK;
} }
static int omap2430_musb_init(struct musb *musb) static int omap2430_musb_init(struct musb *musb)
...@@ -297,6 +303,8 @@ static int omap2430_musb_init(struct musb *musb) ...@@ -297,6 +303,8 @@ static int omap2430_musb_init(struct musb *musb)
return -ENODEV; return -ENODEV;
} }
INIT_WORK(&musb->otg_notifier_work, musb_otg_notifier_work);
status = pm_runtime_get_sync(dev); status = pm_runtime_get_sync(dev);
if (status < 0) { if (status < 0) {
dev_err(dev, "pm_runtime_get_sync FAILED"); dev_err(dev, "pm_runtime_get_sync FAILED");
...@@ -491,6 +499,9 @@ static int omap2430_runtime_suspend(struct device *dev) ...@@ -491,6 +499,9 @@ static int omap2430_runtime_suspend(struct device *dev)
struct omap2430_glue *glue = dev_get_drvdata(dev); struct omap2430_glue *glue = dev_get_drvdata(dev);
struct musb *musb = glue_to_musb(glue); struct musb *musb = glue_to_musb(glue);
musb->context.otg_interfsel = musb_readl(musb->mregs,
OTG_INTERFSEL);
omap2430_low_level_exit(musb); omap2430_low_level_exit(musb);
otg_set_suspend(musb->xceiv, 1); otg_set_suspend(musb->xceiv, 1);
...@@ -503,6 +514,9 @@ static int omap2430_runtime_resume(struct device *dev) ...@@ -503,6 +514,9 @@ static int omap2430_runtime_resume(struct device *dev)
struct musb *musb = glue_to_musb(glue); struct musb *musb = glue_to_musb(glue);
omap2430_low_level_init(musb); omap2430_low_level_init(musb);
musb_writel(musb->mregs, OTG_INTERFSEL,
musb->context.otg_interfsel);
otg_set_suspend(musb->xceiv, 0); otg_set_suspend(musb->xceiv, 0);
return 0; return 0;
......
...@@ -56,6 +56,7 @@ u8 tusb_get_revision(struct musb *musb) ...@@ -56,6 +56,7 @@ u8 tusb_get_revision(struct musb *musb)
return rev; return rev;
} }
EXPORT_SYMBOL_GPL(tusb_get_revision);
static int tusb_print_revision(struct musb *musb) static int tusb_print_revision(struct musb *musb)
{ {
......
...@@ -37,7 +37,6 @@ struct ux500_dma_channel { ...@@ -37,7 +37,6 @@ struct ux500_dma_channel {
struct dma_channel channel; struct dma_channel channel;
struct ux500_dma_controller *controller; struct ux500_dma_controller *controller;
struct musb_hw_ep *hw_ep; struct musb_hw_ep *hw_ep;
struct work_struct channel_work;
struct dma_chan *dma_chan; struct dma_chan *dma_chan;
unsigned int cur_len; unsigned int cur_len;
dma_cookie_t cookie; dma_cookie_t cookie;
...@@ -56,31 +55,11 @@ struct ux500_dma_controller { ...@@ -56,31 +55,11 @@ struct ux500_dma_controller {
dma_addr_t phy_base; dma_addr_t phy_base;
}; };
/* Work function invoked from DMA callback to handle tx transfers. */
static void ux500_tx_work(struct work_struct *data)
{
struct ux500_dma_channel *ux500_channel = container_of(data,
struct ux500_dma_channel, channel_work);
struct musb_hw_ep *hw_ep = ux500_channel->hw_ep;
struct musb *musb = hw_ep->musb;
unsigned long flags;
dev_dbg(musb->controller, "DMA tx transfer done on hw_ep=%d\n",
hw_ep->epnum);
spin_lock_irqsave(&musb->lock, flags);
ux500_channel->channel.actual_len = ux500_channel->cur_len;
ux500_channel->channel.status = MUSB_DMA_STATUS_FREE;
musb_dma_completion(musb, hw_ep->epnum,
ux500_channel->is_tx);
spin_unlock_irqrestore(&musb->lock, flags);
}
/* Work function invoked from DMA callback to handle rx transfers. */ /* Work function invoked from DMA callback to handle rx transfers. */
static void ux500_rx_work(struct work_struct *data) void ux500_dma_callback(void *private_data)
{ {
struct ux500_dma_channel *ux500_channel = container_of(data, struct dma_channel *channel = private_data;
struct ux500_dma_channel, channel_work); struct ux500_dma_channel *ux500_channel = channel->private_data;
struct musb_hw_ep *hw_ep = ux500_channel->hw_ep; struct musb_hw_ep *hw_ep = ux500_channel->hw_ep;
struct musb *musb = hw_ep->musb; struct musb *musb = hw_ep->musb;
unsigned long flags; unsigned long flags;
...@@ -94,14 +73,7 @@ static void ux500_rx_work(struct work_struct *data) ...@@ -94,14 +73,7 @@ static void ux500_rx_work(struct work_struct *data)
musb_dma_completion(musb, hw_ep->epnum, musb_dma_completion(musb, hw_ep->epnum,
ux500_channel->is_tx); ux500_channel->is_tx);
spin_unlock_irqrestore(&musb->lock, flags); spin_unlock_irqrestore(&musb->lock, flags);
}
void ux500_dma_callback(void *private_data)
{
struct dma_channel *channel = (struct dma_channel *)private_data;
struct ux500_dma_channel *ux500_channel = channel->private_data;
schedule_work(&ux500_channel->channel_work);
} }
static bool ux500_configure_channel(struct dma_channel *channel, static bool ux500_configure_channel(struct dma_channel *channel,
...@@ -330,7 +302,6 @@ static int ux500_dma_controller_start(struct dma_controller *c) ...@@ -330,7 +302,6 @@ static int ux500_dma_controller_start(struct dma_controller *c)
void **param_array; void **param_array;
struct ux500_dma_channel *channel_array; struct ux500_dma_channel *channel_array;
u32 ch_count; u32 ch_count;
void (*musb_channel_work)(struct work_struct *);
dma_cap_mask_t mask; dma_cap_mask_t mask;
if ((data->num_rx_channels > UX500_MUSB_DMA_NUM_RX_CHANNELS) || if ((data->num_rx_channels > UX500_MUSB_DMA_NUM_RX_CHANNELS) ||
...@@ -347,7 +318,6 @@ static int ux500_dma_controller_start(struct dma_controller *c) ...@@ -347,7 +318,6 @@ static int ux500_dma_controller_start(struct dma_controller *c)
channel_array = controller->rx_channel; channel_array = controller->rx_channel;
ch_count = data->num_rx_channels; ch_count = data->num_rx_channels;
param_array = data->dma_rx_param_array; param_array = data->dma_rx_param_array;
musb_channel_work = ux500_rx_work;
for (dir = 0; dir < 2; dir++) { for (dir = 0; dir < 2; dir++) {
for (ch_num = 0; ch_num < ch_count; ch_num++) { for (ch_num = 0; ch_num < ch_count; ch_num++) {
...@@ -374,15 +344,12 @@ static int ux500_dma_controller_start(struct dma_controller *c) ...@@ -374,15 +344,12 @@ static int ux500_dma_controller_start(struct dma_controller *c)
return -EBUSY; return -EBUSY;
} }
INIT_WORK(&ux500_channel->channel_work,
musb_channel_work);
} }
/* Prepare the loop for TX channels */ /* Prepare the loop for TX channels */
channel_array = controller->tx_channel; channel_array = controller->tx_channel;
ch_count = data->num_tx_channels; ch_count = data->num_tx_channels;
param_array = data->dma_tx_param_array; param_array = data->dma_tx_param_array;
musb_channel_work = ux500_tx_work;
is_tx = 1; is_tx = 1;
} }
......
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