Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
F
FMC ADC 100M 14b 4cha - Software
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
image/svg+xml
Discourse
Discourse
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Projects
FMC ADC 100M 14b 4cha - Software
Commits
4102bbde
Commit
4102bbde
authored
Jul 22, 2019
by
Federico Vaga
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
drv: move DMA code to dedicated file
Signed-off-by:
Federico Vaga
<
federico.vaga@cern.ch
>
parent
9389ab19
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
256 additions
and
244 deletions
+256
-244
Kbuild
kernel/Kbuild
+1
-0
fa-dma.c
kernel/fa-dma.c
+249
-0
fa-irq.c
kernel/fa-irq.c
+0
-241
fmc-adc-100m14b4cha.h
kernel/fmc-adc-100m14b4cha.h
+6
-3
No files found.
kernel/Kbuild
View file @
4102bbde
...
...
@@ -38,6 +38,7 @@ fmc-adc-100m14b4ch-y += fa-regtable.o
fmc-adc-100m14b4ch-y += fa-zio-trg.o
fmc-adc-100m14b4ch-y += fa-irq.o
fmc-adc-100m14b4ch-y += fa-debug.o
fmc-adc-100m14b4ch-y += fa-dma.o
fmc-adc-100m14b4ch-y += onewire.o
fmc-adc-100m14b4ch-y += spi.o
fmc-adc-100m14b4ch-y += fa-spec-core.o
...
...
kernel/fa-dma.c
0 → 100644
View file @
4102bbde
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright CERN 2012-2019
* Author: Federico Vaga <federico.vaga@cern.ch>
*/
#include <linux/errno.h>
#include "fmc-adc-100m14b4cha.h"
/**
* It maps the ZIO blocks with an sg table, then it starts the DMA transfer
* from the ADC to the host memory.
*
* @param cset
*/
int
zfad_dma_start
(
struct
zio_cset
*
cset
)
{
struct
fa_dev
*
fa
=
cset
->
zdev
->
priv_d
;
struct
zfad_block
*
zfad_block
=
cset
->
interleave
->
priv_d
;
uint32_t
dev_mem_off
,
trg_pos
,
pre_samp
;
uint32_t
val
=
0
;
int
try
=
5
,
err
;
/*
* All programmed triggers fire, so the acquisition is ended.
* If the state machine is _idle_ we can start the DMA transfer.
* If the state machine it is not idle, try again 5 times
*/
while
(
try
--
&&
val
!=
FA100M14B4C_STATE_IDLE
)
{
/* udelay(2); */
val
=
fa_readl
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFA_STA_FSM
]);
}
if
(
val
!=
FA100M14B4C_STATE_IDLE
)
{
/* we can't DMA if the state machine is not idle */
dev_warn
(
fa
->
msgdev
,
"Can't start DMA on the last acquisition, "
"State Machine is not IDLE (status:%d)
\n
"
,
val
);
return
-
EBUSY
;
}
/*
* Disable all triggers to prevent fires between
* different DMA transfers required for multi-shots
*/
fa_writel
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFAT_CFG_SRC
],
0
);
/* Fix dev_mem_addr in single-shot mode */
if
(
fa
->
n_shots
==
1
)
{
int
nchan
=
FA100M14B4C_NCHAN
;
struct
zio_control
*
ctrl
=
cset
->
chan
[
nchan
].
current_ctrl
;
/* get pre-samples from the current control (interleave chan) */
pre_samp
=
ctrl
->
attr_trigger
.
std_val
[
ZIO_ATTR_TRIG_PRE_SAMP
];
/* Get trigger position in DDR */
trg_pos
=
fa_readl
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFAT_POS
]);
/*
* compute mem offset (in bytes): pre-samp is converted to
* bytes
*/
dev_mem_off
=
trg_pos
-
(
pre_samp
*
cset
->
ssize
*
nchan
);
dev_dbg
(
fa
->
msgdev
,
"Trigger @ 0x%08x, pre_samp %i, offset 0x%08x
\n
"
,
trg_pos
,
pre_samp
,
dev_mem_off
);
zfad_block
[
0
].
dev_mem_off
=
dev_mem_off
;
}
dev_dbg
(
fa
->
msgdev
,
"Start DMA transfer
\n
"
);
err
=
fa
->
carrier_op
->
dma_start
(
cset
);
if
(
err
)
return
err
;
return
0
;
}
/**
* It completes a DMA transfer.
* It tells to the ZIO framework that all blocks are done. Then, it re-enable
* the trigger for the next acquisition. If the device is configured for
* continuous acquisition, the function automatically start the next
* acquisition
*
* @param cset
*/
void
zfad_dma_done
(
struct
zio_cset
*
cset
)
{
struct
fa_dev
*
fa
=
cset
->
zdev
->
priv_d
;
struct
zio_channel
*
interleave
=
cset
->
interleave
;
struct
zfad_block
*
zfad_block
=
interleave
->
priv_d
;
struct
zio_control
*
ctrl
=
NULL
;
struct
zio_ti
*
ti
=
cset
->
ti
;
struct
zio_block
*
block
;
struct
zio_timestamp
ztstamp
;
int
i
;
uint32_t
*
trig_timetag
;
fa
->
carrier_op
->
dma_done
(
cset
);
/* for each shot, set the timetag of each ctrl block by reading the
* trig-timetag appended after the samples. Set also the acquisition
* start timetag on every blocks
*/
ztstamp
.
secs
=
fa_readl
(
fa
,
fa
->
fa_utc_base
,
&
zfad_regs
[
ZFA_UTC_ACQ_START_SECONDS
]);
ztstamp
.
ticks
=
fa_readl
(
fa
,
fa
->
fa_utc_base
,
&
zfad_regs
[
ZFA_UTC_ACQ_START_COARSE
]);
ztstamp
.
bins
=
fa_readl
(
fa
,
fa
->
fa_utc_base
,
&
zfad_regs
[
ZFA_UTC_ACQ_START_FINE
]);
for
(
i
=
0
;
i
<
fa
->
n_shots
;
++
i
)
{
block
=
zfad_block
[
i
].
block
;
ctrl
=
zio_get_ctrl
(
block
);
trig_timetag
=
(
uint32_t
*
)(
block
->
data
+
block
->
datalen
-
FA_TRIG_TIMETAG_BYTES
);
if
(
unlikely
((
*
(
trig_timetag
+
1
)
>>
8
)
!=
0xACCE55
))
dev_err
(
fa
->
msgdev
,
"Wrong acquisition TAG, expected 0xACCE55 but got 0x%X (0x%X)
\n
"
,
(
*
(
trig_timetag
+
1
)
>>
8
),
*
trig_timetag
);
ctrl
->
tstamp
.
secs
=
((
uint64_t
)
*
(
trig_timetag
+
1
)
&
0xFF
)
<<
32
;
ctrl
->
tstamp
.
secs
|=
*
(
trig_timetag
);
ctrl
->
tstamp
.
ticks
=
*
(
trig_timetag
+
2
);
ctrl
->
tstamp
.
bins
=
0
;
ctrl
->
attr_trigger
.
ext_val
[
FA100M14B4C_TATTR_STA
]
=
*
(
trig_timetag
+
3
);
/* Acquisition start Timetag */
ctrl
->
attr_channel
.
ext_val
[
FA100M14B4C_DATTR_ACQ_START_S
]
=
ztstamp
.
secs
;
ctrl
->
attr_channel
.
ext_val
[
FA100M14B4C_DATTR_ACQ_START_C
]
=
ztstamp
.
ticks
;
ctrl
->
attr_channel
.
ext_val
[
FA100M14B4C_DATTR_ACQ_START_F
]
=
ztstamp
.
bins
;
/* resize the datalen, by removing the trigger tstamp */
block
->
datalen
=
block
->
datalen
-
FA_TRIG_TIMETAG_BYTES
;
/* update seq num */
ctrl
->
seq_num
=
i
;
}
/* Sync the channel current control with the last ctrl block*/
memcpy
(
&
interleave
->
current_ctrl
->
tstamp
,
&
ctrl
->
tstamp
,
sizeof
(
struct
zio_timestamp
));
/* Update sequence number */
interleave
->
current_ctrl
->
seq_num
=
ctrl
->
seq_num
;
/*
* All DMA transfers done! Inform the trigger about this, so
* it can store blocks into the buffer
*/
dev_dbg
(
fa
->
msgdev
,
"%i blocks transfered
\n
"
,
fa
->
n_shots
);
zio_trigger_data_done
(
cset
);
fa_writel
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFAT_CFG_SRC
],
ti
->
zattr_set
.
ext_zattr
[
FA100M14B4C_TATTR_SRC
].
value
);
}
/**
* It handles the error condition of a DMA transfer.
* The function turn off the state machine by sending the STOP command
*
* @param cset
*/
void
zfad_dma_error
(
struct
zio_cset
*
cset
)
{
struct
fa_dev
*
fa
=
cset
->
zdev
->
priv_d
;
fa
->
carrier_op
->
dma_error
(
cset
);
zfad_fsm_command
(
fa
,
FA100M14B4C_CMD_STOP
);
fa
->
n_dma_err
++
;
if
(
fa
->
n_fires
==
0
)
dev_err
(
fa
->
msgdev
,
"DMA error occurs but no block was acquired
\n
"
);
}
/*
* job executed within a work thread
* Depending of the carrier the job slightly differs:
* SVEC: dma_start() blocks till the the DMA ends
* (fully managed by the vmebus driver)
* Therefore the DMA outcome can be processed immediately
* SPEC: dma_start() launch the job an returns immediately.
* An interrupt DMA_DONE or ERROR is expecting to signal the end
* of the DMA transaction
* (See fa-spec-irq.c::fa-spec_irq_handler)
*/
void
fa_irq_work
(
struct
work_struct
*
work
)
{
struct
fa_dev
*
fa
=
container_of
(
work
,
struct
fa_dev
,
irq_work
);
struct
zio_cset
*
cset
=
fa
->
zdev
->
cset
;
int
res
;
/*
* This check is not crucial because the HW implements
* a solid state machine and acq-end can happens only after
* the execution of the n requested shots.
*/
fa
->
n_fires
=
fa
->
n_shots
-
fa_readl
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFAT_SHOTS_REM
]);
if
(
fa
->
n_fires
!=
fa
->
n_shots
)
{
dev_err
(
fa
->
msgdev
,
"Expected %i trigger fires, but %i occurs
\n
"
,
fa
->
n_shots
,
fa
->
n_fires
);
}
res
=
zfad_dma_start
(
cset
);
if
(
!
res
)
{
/*
* No error.
* If there is an IRQ DMA src to notify the ends of the DMA,
* leave the workqueue.
* dma_done will be proceed on DMA_END reception.
* Otherwhise call dma_done in sequence
*/
if
(
fa
->
irq_src
&
FA_IRQ_SRC_DMA
)
/*
* waiting for END_OF_DMA IRQ
* with the CSET_BUSY flag Raised
* The flag will be lowered by the irq_handler
* handling END_DMA
*/
goto
end
;
zfad_dma_done
(
cset
);
}
/*
* Lower CSET_HW_BUSY
*/
spin_lock
(
&
cset
->
lock
);
cset
->
flags
&=
~
ZIO_CSET_HW_BUSY
;
spin_unlock
(
&
cset
->
lock
);
end:
if
(
res
)
{
/* Stop acquisition on error */
zfad_dma_error
(
cset
);
}
else
if
(
fa
->
enable_auto_start
)
{
/* Automatic start next acquisition */
dev_dbg
(
fa
->
msgdev
,
"Automatic start
\n
"
);
zfad_fsm_command
(
fa
,
FA100M14B4C_CMD_START
);
}
}
kernel/fa-irq.c
View file @
4102bbde
...
...
@@ -16,247 +16,6 @@
#include "fmc-adc-100m14b4cha.h"
#include "fa-spec.h"
/**
* It maps the ZIO blocks with an sg table, then it starts the DMA transfer
* from the ADC to the host memory.
*
* @param cset
*/
int
zfad_dma_start
(
struct
zio_cset
*
cset
)
{
struct
fa_dev
*
fa
=
cset
->
zdev
->
priv_d
;
struct
zfad_block
*
zfad_block
=
cset
->
interleave
->
priv_d
;
uint32_t
dev_mem_off
,
trg_pos
,
pre_samp
;
uint32_t
val
=
0
;
int
try
=
5
,
err
;
/*
* All programmed triggers fire, so the acquisition is ended.
* If the state machine is _idle_ we can start the DMA transfer.
* If the state machine it is not idle, try again 5 times
*/
while
(
try
--
&&
val
!=
FA100M14B4C_STATE_IDLE
)
{
/* udelay(2); */
val
=
fa_readl
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFA_STA_FSM
]);
}
if
(
val
!=
FA100M14B4C_STATE_IDLE
)
{
/* we can't DMA if the state machine is not idle */
dev_warn
(
fa
->
msgdev
,
"Can't start DMA on the last acquisition, "
"State Machine is not IDLE (status:%d)
\n
"
,
val
);
return
-
EBUSY
;
}
/*
* Disable all triggers to prevent fires between
* different DMA transfers required for multi-shots
*/
fa_writel
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFAT_CFG_SRC
],
0
);
/* Fix dev_mem_addr in single-shot mode */
if
(
fa
->
n_shots
==
1
)
{
int
nchan
=
FA100M14B4C_NCHAN
;
struct
zio_control
*
ctrl
=
cset
->
chan
[
nchan
].
current_ctrl
;
/* get pre-samples from the current control (interleave chan) */
pre_samp
=
ctrl
->
attr_trigger
.
std_val
[
ZIO_ATTR_TRIG_PRE_SAMP
];
/* Get trigger position in DDR */
trg_pos
=
fa_readl
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFAT_POS
]);
/*
* compute mem offset (in bytes): pre-samp is converted to
* bytes
*/
dev_mem_off
=
trg_pos
-
(
pre_samp
*
cset
->
ssize
*
nchan
);
dev_dbg
(
fa
->
msgdev
,
"Trigger @ 0x%08x, pre_samp %i, offset 0x%08x
\n
"
,
trg_pos
,
pre_samp
,
dev_mem_off
);
zfad_block
[
0
].
dev_mem_off
=
dev_mem_off
;
}
dev_dbg
(
fa
->
msgdev
,
"Start DMA transfer
\n
"
);
err
=
fa
->
carrier_op
->
dma_start
(
cset
);
if
(
err
)
return
err
;
return
0
;
}
/**
* It completes a DMA transfer.
* It tells to the ZIO framework that all blocks are done. Then, it re-enable
* the trigger for the next acquisition. If the device is configured for
* continuous acquisition, the function automatically start the next
* acquisition
*
* @param cset
*/
void
zfad_dma_done
(
struct
zio_cset
*
cset
)
{
struct
fa_dev
*
fa
=
cset
->
zdev
->
priv_d
;
struct
zio_channel
*
interleave
=
cset
->
interleave
;
struct
zfad_block
*
zfad_block
=
interleave
->
priv_d
;
struct
zio_control
*
ctrl
=
NULL
;
struct
zio_ti
*
ti
=
cset
->
ti
;
struct
zio_block
*
block
;
struct
zio_timestamp
ztstamp
;
int
i
;
uint32_t
*
trig_timetag
;
fa
->
carrier_op
->
dma_done
(
cset
);
/* for each shot, set the timetag of each ctrl block by reading the
* trig-timetag appended after the samples. Set also the acquisition
* start timetag on every blocks
*/
ztstamp
.
secs
=
fa_readl
(
fa
,
fa
->
fa_utc_base
,
&
zfad_regs
[
ZFA_UTC_ACQ_START_SECONDS
]);
ztstamp
.
ticks
=
fa_readl
(
fa
,
fa
->
fa_utc_base
,
&
zfad_regs
[
ZFA_UTC_ACQ_START_COARSE
]);
ztstamp
.
bins
=
fa_readl
(
fa
,
fa
->
fa_utc_base
,
&
zfad_regs
[
ZFA_UTC_ACQ_START_FINE
]);
for
(
i
=
0
;
i
<
fa
->
n_shots
;
++
i
)
{
block
=
zfad_block
[
i
].
block
;
ctrl
=
zio_get_ctrl
(
block
);
trig_timetag
=
(
uint32_t
*
)(
block
->
data
+
block
->
datalen
-
FA_TRIG_TIMETAG_BYTES
);
if
(
unlikely
((
*
(
trig_timetag
+
1
)
>>
8
)
!=
0xACCE55
))
dev_err
(
fa
->
msgdev
,
"Wrong acquisition TAG, expected 0xACCE55 but got 0x%X (0x%X)
\n
"
,
(
*
(
trig_timetag
+
1
)
>>
8
),
*
trig_timetag
);
ctrl
->
tstamp
.
secs
=
((
uint64_t
)
*
(
trig_timetag
+
1
)
&
0xFF
)
<<
32
;
ctrl
->
tstamp
.
secs
|=
*
(
trig_timetag
);
ctrl
->
tstamp
.
ticks
=
*
(
trig_timetag
+
2
);
ctrl
->
tstamp
.
bins
=
0
;
ctrl
->
attr_trigger
.
ext_val
[
FA100M14B4C_TATTR_STA
]
=
*
(
trig_timetag
+
3
);
/* Acquisition start Timetag */
ctrl
->
attr_channel
.
ext_val
[
FA100M14B4C_DATTR_ACQ_START_S
]
=
ztstamp
.
secs
;
ctrl
->
attr_channel
.
ext_val
[
FA100M14B4C_DATTR_ACQ_START_C
]
=
ztstamp
.
ticks
;
ctrl
->
attr_channel
.
ext_val
[
FA100M14B4C_DATTR_ACQ_START_F
]
=
ztstamp
.
bins
;
/* resize the datalen, by removing the trigger tstamp */
block
->
datalen
=
block
->
datalen
-
FA_TRIG_TIMETAG_BYTES
;
/* update seq num */
ctrl
->
seq_num
=
i
;
}
/* Sync the channel current control with the last ctrl block*/
memcpy
(
&
interleave
->
current_ctrl
->
tstamp
,
&
ctrl
->
tstamp
,
sizeof
(
struct
zio_timestamp
));
/* Update sequence number */
interleave
->
current_ctrl
->
seq_num
=
ctrl
->
seq_num
;
/*
* All DMA transfers done! Inform the trigger about this, so
* it can store blocks into the buffer
*/
dev_dbg
(
fa
->
msgdev
,
"%i blocks transfered
\n
"
,
fa
->
n_shots
);
zio_trigger_data_done
(
cset
);
fa_writel
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFAT_CFG_SRC
],
ti
->
zattr_set
.
ext_zattr
[
FA100M14B4C_TATTR_SRC
].
value
);
}
/**
* It handles the error condition of a DMA transfer.
* The function turn off the state machine by sending the STOP command
*
* @param cset
*/
void
zfad_dma_error
(
struct
zio_cset
*
cset
)
{
struct
fa_dev
*
fa
=
cset
->
zdev
->
priv_d
;
fa
->
carrier_op
->
dma_error
(
cset
);
zfad_fsm_command
(
fa
,
FA100M14B4C_CMD_STOP
);
fa
->
n_dma_err
++
;
if
(
fa
->
n_fires
==
0
)
dev_err
(
fa
->
msgdev
,
"DMA error occurs but no block was acquired
\n
"
);
}
/*
* job executed within a work thread
* Depending of the carrier the job slightly differs:
* SVEC: dma_start() blocks till the the DMA ends
* (fully managed by the vmebus driver)
* Therefore the DMA outcome can be processed immediately
* SPEC: dma_start() launch the job an returns immediately.
* An interrupt DMA_DONE or ERROR is expecting to signal the end
* of the DMA transaction
* (See fa-spec-irq.c::fa-spec_irq_handler)
*/
static
void
fa_irq_work
(
struct
work_struct
*
work
)
{
struct
fa_dev
*
fa
=
container_of
(
work
,
struct
fa_dev
,
irq_work
);
struct
zio_cset
*
cset
=
fa
->
zdev
->
cset
;
int
res
;
/*
* This check is not crucial because the HW implements
* a solid state machine and acq-end can happens only after
* the execution of the n requested shots.
*/
fa
->
n_fires
=
fa
->
n_shots
-
fa_readl
(
fa
,
fa
->
fa_adc_csr_base
,
&
zfad_regs
[
ZFAT_SHOTS_REM
]);
if
(
fa
->
n_fires
!=
fa
->
n_shots
)
{
dev_err
(
fa
->
msgdev
,
"Expected %i trigger fires, but %i occurs
\n
"
,
fa
->
n_shots
,
fa
->
n_fires
);
}
res
=
zfad_dma_start
(
cset
);
if
(
!
res
)
{
/*
* No error.
* If there is an IRQ DMA src to notify the ends of the DMA,
* leave the workqueue.
* dma_done will be proceed on DMA_END reception.
* Otherwhise call dma_done in sequence
*/
if
(
fa
->
irq_src
&
FA_IRQ_SRC_DMA
)
/*
* waiting for END_OF_DMA IRQ
* with the CSET_BUSY flag Raised
* The flag will be lowered by the irq_handler
* handling END_DMA
*/
goto
end
;
zfad_dma_done
(
cset
);
}
/*
* Lower CSET_HW_BUSY
*/
spin_lock
(
&
cset
->
lock
);
cset
->
flags
&=
~
ZIO_CSET_HW_BUSY
;
spin_unlock
(
&
cset
->
lock
);
end:
if
(
res
)
{
/* Stop acquisition on error */
zfad_dma_error
(
cset
);
}
else
if
(
fa
->
enable_auto_start
)
{
/* Automatic start next acquisition */
dev_dbg
(
fa
->
msgdev
,
"Automatic start
\n
"
);
zfad_fsm_command
(
fa
,
FA100M14B4C_CMD_START
);
}
}
/*
* fat_get_irq_status
* @fa: adc descriptor
...
...
kernel/fmc-adc-100m14b4cha.h
View file @
4102bbde
...
...
@@ -595,6 +595,12 @@ extern int zfad_get_chx_index(unsigned long addr, struct zio_channel *chan);
extern
int
zfad_pattern_data_enable
(
struct
fa_dev
*
fa
,
uint16_t
pattern
,
unsigned
int
enable
);
/* Function exported by fa-dma.c */
extern
int
zfad_dma_start
(
struct
zio_cset
*
cset
);
extern
void
zfad_dma_done
(
struct
zio_cset
*
cset
);
extern
void
zfad_dma_error
(
struct
zio_cset
*
cset
);
extern
void
fa_irq_work
(
struct
work_struct
*
work
);
/* Functions exported by fa-zio-drv.c */
extern
int
fa_zio_register
(
void
);
extern
void
fa_zio_unregister
(
void
);
...
...
@@ -606,9 +612,6 @@ extern int fa_trig_init(void);
extern
void
fa_trig_exit
(
void
);
/* Functions exported by fa-irq.c */
extern
int
zfad_dma_start
(
struct
zio_cset
*
cset
);
extern
void
zfad_dma_done
(
struct
zio_cset
*
cset
);
extern
void
zfad_dma_error
(
struct
zio_cset
*
cset
);
extern
void
zfat_irq_trg_fire
(
struct
zio_cset
*
cset
);
extern
int
fa_setup_irqs
(
struct
fa_dev
*
fa
);
extern
int
fa_free_irqs
(
struct
fa_dev
*
fa
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment