Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
adam.huang
Arm Trusted Firmware
Commits
fbf35335
Unverified
Commit
fbf35335
authored
Jan 21, 2019
by
Antonio Niño Díaz
Committed by
GitHub
Jan 21, 2019
Browse files
Merge pull request #1767 from Yann-lms/updates_stm32mp1
Updates for STM32MP1
parents
f0bfe15b
7747356d
Changes
45
Show whitespace changes
Inline
Side-by-side
plat/st/stm32mp1/stm32mp1_dt.c
View file @
fbf35335
/*
* Copyright (c) 2017-201
8
, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2017-201
9
, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <errno.h>
#include <libfdt.h>
...
...
@@ -17,135 +18,13 @@
#include <drivers/st/stm32mp1_ddr.h>
#include <drivers/st/stm32mp1_ram.h>
#include <stm32mp1_dt.h>
#define DT_GPIO_BANK_SHIFT 12
#define DT_GPIO_BANK_MASK 0x1F000U
#define DT_GPIO_PIN_SHIFT 8
#define DT_GPIO_PIN_MASK 0xF00U
#define DT_GPIO_MODE_MASK 0xFFU
static
int
fdt_checked
;
static
void
*
fdt
=
(
void
*
)(
uintptr_t
)
STM32MP1_DTB_BASE
;
/*******************************************************************************
* This function gets the pin settings from DT information.
* When analyze and parsing is done, set the GPIO registers.
* Return 0 on success, else return a negative FDT_ERR_xxx error code.
******************************************************************************/
static
int
dt_set_gpio_config
(
int
node
)
{
const
fdt32_t
*
cuint
,
*
slewrate
;
int
len
,
pinctrl_node
,
pinctrl_subnode
;
uint32_t
i
;
uint32_t
speed
=
GPIO_SPEED_LOW
;
uint32_t
pull
=
GPIO_NO_PULL
;
cuint
=
fdt_getprop
(
fdt
,
node
,
"pinmux"
,
&
len
);
if
(
cuint
==
NULL
)
{
return
-
FDT_ERR_NOTFOUND
;
}
pinctrl_node
=
fdt_parent_offset
(
fdt
,
fdt_parent_offset
(
fdt
,
node
));
if
(
pinctrl_node
<
0
)
{
return
-
FDT_ERR_NOTFOUND
;
}
slewrate
=
fdt_getprop
(
fdt
,
node
,
"slew-rate"
,
NULL
);
if
(
slewrate
!=
NULL
)
{
speed
=
fdt32_to_cpu
(
*
slewrate
);
}
if
(
fdt_getprop
(
fdt
,
node
,
"bias-pull-up"
,
NULL
)
!=
NULL
)
{
pull
=
GPIO_PULL_UP
;
}
else
if
(
fdt_getprop
(
fdt
,
node
,
"bias-pull-down"
,
NULL
)
!=
NULL
)
{
pull
=
GPIO_PULL_DOWN
;
}
else
{
VERBOSE
(
"No bias configured in node %d
\n
"
,
node
);
}
for
(
i
=
0
;
i
<
((
uint32_t
)
len
/
sizeof
(
uint32_t
));
i
++
)
{
uint32_t
pincfg
;
uint32_t
bank
;
uint32_t
pin
;
uint32_t
mode
;
uint32_t
alternate
=
GPIO_ALTERNATE_0
;
pincfg
=
fdt32_to_cpu
(
*
cuint
);
cuint
++
;
bank
=
(
pincfg
&
DT_GPIO_BANK_MASK
)
>>
DT_GPIO_BANK_SHIFT
;
pin
=
(
pincfg
&
DT_GPIO_PIN_MASK
)
>>
DT_GPIO_PIN_SHIFT
;
mode
=
pincfg
&
DT_GPIO_MODE_MASK
;
switch
(
mode
)
{
case
0
:
mode
=
GPIO_MODE_INPUT
;
break
;
case
1
...
16
:
alternate
=
mode
-
1U
;
mode
=
GPIO_MODE_ALTERNATE
;
break
;
case
17
:
mode
=
GPIO_MODE_ANALOG
;
break
;
default:
mode
=
GPIO_MODE_OUTPUT
;
break
;
}
if
(
fdt_getprop
(
fdt
,
node
,
"drive-open-drain"
,
NULL
)
!=
NULL
)
{
mode
|=
GPIO_OPEN_DRAIN
;
}
fdt_for_each_subnode
(
pinctrl_subnode
,
fdt
,
pinctrl_node
)
{
uint32_t
bank_offset
;
const
fdt32_t
*
cuint2
;
if
(
fdt_getprop
(
fdt
,
pinctrl_subnode
,
"gpio-controller"
,
NULL
)
==
NULL
)
{
continue
;
}
cuint2
=
fdt_getprop
(
fdt
,
pinctrl_subnode
,
"reg"
,
NULL
);
if
(
cuint2
==
NULL
)
{
continue
;
}
if
(
bank
==
GPIO_BANK_Z
)
{
bank_offset
=
0
;
}
else
{
bank_offset
=
bank
*
STM32_GPIO_BANK_OFFSET
;
}
if
(
fdt32_to_cpu
(
*
cuint2
)
==
bank_offset
)
{
int
clk_id
=
fdt_get_clock_id
(
pinctrl_subnode
);
if
(
clk_id
<
0
)
{
return
-
FDT_ERR_NOTFOUND
;
}
if
(
stm32mp1_clk_enable
((
unsigned
long
)
clk_id
)
<
0
)
{
return
-
FDT_ERR_BADVALUE
;
}
break
;
}
}
set_gpio
(
bank
,
pin
,
mode
,
speed
,
pull
,
alternate
);
}
return
0
;
}
/*******************************************************************************
* This function checks device tree file with its header.
* Returns 0
if
success
,
and a negative
value els
e.
* Returns 0
on
success and a negative
FDT error code on failur
e.
******************************************************************************/
int
dt_open_and_check
(
void
)
{
...
...
@@ -174,7 +53,7 @@ int fdt_get_address(void **fdt_addr)
/*******************************************************************************
* This function check the presence of a node (generic use of fdt library).
* Returns true if present,
fa
lse
e
lse.
* Returns true if present,
e
lse
return fa
lse.
******************************************************************************/
bool
fdt_check_node
(
int
node
)
{
...
...
@@ -187,37 +66,30 @@ bool fdt_check_node(int node)
}
/*******************************************************************************
* This function check the status of a node (generic use of fdt library).
* Returns true if "okay" or missing, false else.
* This function return global node status (generic use of fdt library).
******************************************************************************/
bool
fdt_check
_status
(
int
node
)
uint32_t
fdt_get
_status
(
int
node
)
{
uint32_t
status
=
DT_DISABLED
;
int
len
;
const
char
*
cchar
;
cchar
=
fdt_getprop
(
fdt
,
node
,
"status"
,
&
len
);
if
(
cchar
==
NULL
)
{
return
true
;
if
((
cchar
==
NULL
)
||
(
strncmp
(
cchar
,
"okay"
,
(
size_t
)
len
)
==
0
))
{
status
|=
DT_NON_SECURE
;
}
return
strncmp
(
cchar
,
"okay"
,
(
size_t
)
len
)
==
0
;
}
/*******************************************************************************
* This function check the secure-status of a node (generic use of fdt library).
* Returns true if "okay" or missing, false else.
******************************************************************************/
bool
fdt_check_secure_status
(
int
node
)
{
int
len
;
const
char
*
cchar
;
cchar
=
fdt_getprop
(
fdt
,
node
,
"secure-status"
,
&
len
);
if
(
cchar
==
NULL
)
{
return
true
;
if
(
status
==
DT_NON_SECURE
)
{
status
|=
DT_SECURE
;
}
}
else
if
(
strncmp
(
cchar
,
"okay"
,
(
size_t
)
len
)
==
0
)
{
status
|=
DT_SECURE
;
}
return
st
rncmp
(
cchar
,
"okay"
,
(
size_t
)
len
)
==
0
;
return
st
atus
;
}
/*******************************************************************************
...
...
@@ -245,7 +117,7 @@ uint32_t fdt_read_uint32_default(int node, const char *prop_name,
* (generic use of fdt library).
* It reads the values inside the device tree, from property name and node.
* The number of parameters is also indicated as entry parameter.
* Returns 0
if
success
,
and a negative
value els
e.
* Returns 0
on
success and a negative
FDT error code on failur
e.
* If success, values are stored at the third parameter address.
******************************************************************************/
int
fdt_read_uint32_array
(
int
node
,
const
char
*
prop_name
,
uint32_t
*
array
,
...
...
@@ -273,53 +145,10 @@ int fdt_read_uint32_array(int node, const char *prop_name, uint32_t *array,
return
0
;
}
/*******************************************************************************
* This function gets the pin settings from DT information.
* When analyze and parsing is done, set the GPIO registers.
* Returns 0 if success, and a negative value else.
******************************************************************************/
int
dt_set_pinctrl_config
(
int
node
)
{
const
fdt32_t
*
cuint
;
int
lenp
=
0
;
uint32_t
i
;
if
(
!
fdt_check_status
(
node
))
{
return
-
FDT_ERR_NOTFOUND
;
}
cuint
=
fdt_getprop
(
fdt
,
node
,
"pinctrl-0"
,
&
lenp
);
if
(
cuint
==
NULL
)
{
return
-
FDT_ERR_NOTFOUND
;
}
for
(
i
=
0
;
i
<
((
uint32_t
)
lenp
/
4U
);
i
++
)
{
int
phandle_node
,
phandle_subnode
;
phandle_node
=
fdt_node_offset_by_phandle
(
fdt
,
fdt32_to_cpu
(
*
cuint
));
if
(
phandle_node
<
0
)
{
return
-
FDT_ERR_NOTFOUND
;
}
fdt_for_each_subnode
(
phandle_subnode
,
fdt
,
phandle_node
)
{
int
ret
=
dt_set_gpio_config
(
phandle_subnode
);
if
(
ret
<
0
)
{
return
ret
;
}
}
cuint
++
;
}
return
0
;
}
/*******************************************************************************
* This function gets the stdout pin configuration information from the DT.
* And then calls the sub-function to treat it and set GPIO registers.
* Returns 0
if
success
,
and a negative
value els
e.
* Returns 0
on
success and a negative
FDT error code on failur
e.
******************************************************************************/
int
dt_set_stdout_pinctrl
(
void
)
{
...
...
@@ -363,13 +192,12 @@ void dt_fill_device_info(struct dt_node_info *info, int node)
info
->
reset
=
-
1
;
}
info
->
status
=
fdt_check_status
(
node
);
info
->
sec_status
=
fdt_check_secure_status
(
node
);
info
->
status
=
fdt_get_status
(
node
);
}
/*******************************************************************************
* This function retrieve the generic information from DT.
* Returns node
if
success
,
and a negative
value els
e.
* Returns node
on
success and a negative
FDT error code on failur
e.
******************************************************************************/
int
dt_get_node
(
struct
dt_node_info
*
info
,
int
offset
,
const
char
*
compat
)
{
...
...
@@ -387,7 +215,7 @@ int dt_get_node(struct dt_node_info *info, int offset, const char *compat)
/*******************************************************************************
* This function gets the UART instance info of stdout from the DT.
* Returns node
if
success
,
and a negative
value els
e.
* Returns node
on
success and a negative
FDT error code on failur
e.
******************************************************************************/
int
dt_get_stdout_uart_info
(
struct
dt_node_info
*
info
)
{
...
...
@@ -448,7 +276,7 @@ int dt_get_stdout_node_offset(void)
/*******************************************************************************
* This function gets DDR size information from the DT.
* Returns value in bytes
if
success, and
STM32MP1_DDR_SIZE_DFLT els
e.
* Returns value in bytes
on
success, and
0 on failur
e.
******************************************************************************/
uint32_t
dt_get_ddr_size
(
void
)
{
...
...
@@ -457,11 +285,10 @@ uint32_t dt_get_ddr_size(void)
node
=
fdt_node_offset_by_compatible
(
fdt
,
-
1
,
DT_DDR_COMPAT
);
if
(
node
<
0
)
{
INFO
(
"%s: Cannot read DDR node in DT
\n
"
,
__func__
);
return
STM32MP1_DDR_SIZE_DFLT
;
return
0
;
}
return
fdt_read_uint32_default
(
node
,
"st,mem-size"
,
STM32MP1_DDR_SIZE_DFLT
);
return
fdt_read_uint32_default
(
node
,
"st,mem-size"
,
0
);
}
/*******************************************************************************
...
...
plat/st/stm32mp1/stm32mp1_gic.c
View file @
fbf35335
/*
* Copyright (c) 2016-201
8
, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2016-201
9
, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <libfdt.h>
#include <platform_def.h>
#include <common/bl_common.h>
#include <common/debug.h>
#include <drivers/arm/gicv2.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <lib/utils.h>
#include <plat/common/platform.h>
#include <stm32mp1_dt.h>
#include <stm32mp1_private.h>
struct
stm32_gic_instance
{
uint32_t
cells
;
uint32_t
phandle_node
;
};
/******************************************************************************
* On a GICv2 system, the Group 1 secure interrupts are treated as Group 0
* interrupts.
...
...
@@ -22,19 +32,55 @@ static const interrupt_prop_t stm32mp1_interrupt_props[] = {
PLATFORM_G0_PROPS
(
GICV2_INTR_GROUP0
)
};
static
unsigned
int
target_mask_array
[
PLATFORM_CORE_COUNT
];
/* Fix target_mask_array as secondary core is not able to initialize it */
static
unsigned
int
target_mask_array
[
PLATFORM_CORE_COUNT
]
=
{
1
,
2
};
static
const
gicv2_driver_data_t
platform_gic_data
=
{
.
gicd_base
=
STM32MP1_GICD_BASE
,
.
gicc_base
=
STM32MP1_GICC_BASE
,
static
gicv2_driver_data_t
platform_gic_data
=
{
.
interrupt_props
=
stm32mp1_interrupt_props
,
.
interrupt_props_num
=
ARRAY_SIZE
(
stm32mp1_interrupt_props
),
.
target_masks
=
target_mask_array
,
.
target_masks_num
=
ARRAY_SIZE
(
target_mask_array
),
};
static
struct
stm32_gic_instance
stm32_gic
;
void
stm32mp1_gic_init
(
void
)
{
int
node
;
void
*
fdt
;
const
fdt32_t
*
cuint
;
struct
dt_node_info
dt_gic
;
if
(
fdt_get_address
(
&
fdt
)
==
0
)
{
panic
();
}
node
=
dt_get_node
(
&
dt_gic
,
-
1
,
"arm,cortex-a7-gic"
);
if
(
node
<
0
)
{
panic
();
}
platform_gic_data
.
gicd_base
=
dt_gic
.
base
;
cuint
=
fdt_getprop
(
fdt
,
node
,
"reg"
,
NULL
);
if
(
cuint
==
NULL
)
{
panic
();
}
platform_gic_data
.
gicc_base
=
fdt32_to_cpu
(
*
(
cuint
+
2
));
cuint
=
fdt_getprop
(
fdt
,
node
,
"#interrupt-cells"
,
NULL
);
if
(
cuint
==
NULL
)
{
panic
();
}
stm32_gic
.
cells
=
fdt32_to_cpu
(
*
cuint
);
stm32_gic
.
phandle_node
=
fdt_get_phandle
(
fdt
,
node
);
if
(
stm32_gic
.
phandle_node
==
0U
)
{
panic
();
}
gicv2_driver_init
(
&
platform_gic_data
);
gicv2_distif_init
();
...
...
plat/st/stm32mp1/stm32mp1_helper.S
View file @
fbf35335
/*
*
Copyright
(
c
)
2015
-
201
8
,
ARM
Limited
and
Contributors
.
All
rights
reserved
.
*
Copyright
(
c
)
2015
-
201
9
,
ARM
Limited
and
Contributors
.
All
rights
reserved
.
*
*
SPDX
-
License
-
Identifier
:
BSD
-
3
-
Clause
*/
...
...
@@ -12,11 +12,8 @@
#include <drivers/st/stm32_gpio.h>
#include <drivers/st/stm32mp1_rcc.h>
#define GPIO_BANK_G_ADDRESS 0x50008000
#define GPIO_TX_PORT 11
#define GPIO_TX_SHIFT (GPIO_TX_PORT << 1)
#define GPIO_TX_ALT_SHIFT ((GPIO_TX_PORT - GPIO_ALT_LOWER_LIMIT) << 2)
#define STM32MP1_HSI_CLK 64000000
#define GPIO_TX_SHIFT (DEBUG_UART_TX_GPIO_PORT << 1)
#define GPIO_TX_ALT_SHIFT ((DEBUG_UART_TX_GPIO_PORT - GPIO_ALT_LOWER_LIMIT) << 2)
.
globl
platform_mem_init
.
globl
plat_report_exception
...
...
@@ -112,13 +109,13 @@ endfunc plat_my_core_pos
*
---------------------------------------------
*/
func
plat_crash_console_init
/
*
Enable
GPIOs
for
UART
4
TX
*/
ldr
r1
,
=(
RCC_BASE
+
RCC_MP_AHB4ENSETR
)
/
*
Enable
GPIOs
for
UART
TX
*/
ldr
r1
,
=(
RCC_BASE
+
DEBUG_UART_TX_GPIO_BANK_CLK_REG
)
ldr
r2
,
[
r1
]
/
*
Configure
GPIO
G11
*/
orr
r2
,
r2
,
#
RCC_MP_AHB4ENSETR_GPIOG
EN
/
*
Configure
GPIO
*/
orr
r2
,
r2
,
#
DEBUG_UART_TX_GPIO_BANK_CLK_
EN
str
r2
,
[
r1
]
ldr
r1
,
=
GPIO_BANK_
G_
ADDRESS
ldr
r1
,
=
DEBUG_UART_TX_
GPIO_BANK_ADDRESS
/
*
Set
GPIO
mode
alternate
*/
ldr
r2
,
[
r1
,
#
GPIO_MODE_OFFSET
]
bic
r2
,
r2
,
#(
GPIO_MODE_MASK
<<
GPIO_TX_SHIFT
)
...
...
@@ -132,23 +129,22 @@ func plat_crash_console_init
ldr
r2
,
[
r1
,
#
GPIO_PUPD_OFFSET
]
bic
r2
,
r2
,
#(
GPIO_PULL_MASK
<<
GPIO_TX_SHIFT
)
str
r2
,
[
r1
,
#
GPIO_PUPD_OFFSET
]
/
*
Set
alternate
AF6
*/
/
*
Set
alternate
*/
ldr
r2
,
[
r1
,
#
GPIO_AFRH_OFFSET
]
bic
r2
,
r2
,
#(
GPIO_ALTERNATE_MASK
<<
GPIO_TX_ALT_SHIFT
)
orr
r2
,
r2
,
#(
GPIO_ALTERNATE
_6
<<
GPIO_TX_ALT_SHIFT
)
orr
r2
,
r2
,
#(
DEBUG_UART_TX_
GPIO_ALTERNATE
<<
GPIO_TX_ALT_SHIFT
)
str
r2
,
[
r1
,
#
GPIO_AFRH_OFFSET
]
/
*
Enable
UART
clock
,
with
HSI
source
*/
ldr
r1
,
=(
RCC_BASE
+
RCC_UART24CKSELR
)
mov
r2
,
#
RCC_UART24CKSELR_HSI
/
*
Enable
UART
clock
,
with
its
source
*/
ldr
r1
,
=(
RCC_BASE
+
DEBUG_UART_TX_CLKSRC_REG
)
mov
r2
,
#
DEBUG_UART_TX_CLKSRC
str
r2
,
[
r1
]
ldr
r1
,
=(
RCC_BASE
+
RCC_MP_APB1ENSETR
)
ldr
r1
,
=(
RCC_BASE
+
DEBUG_UART_TX_EN_REG
)
ldr
r2
,
[
r1
]
orr
r2
,
r2
,
#
RCC_MP_APB1ENSETR
_UART
4
EN
orr
r2
,
r2
,
#
DEBUG
_UART
_TX_
EN
str
r2
,
[
r1
]
ldr
r0
,
=
STM32MP1_DEBUG_USART_BASE
ldr
r1
,
=
STM32MP1_
HSI_CLK
ldr
r1
,
=
STM32MP1_
DEBUG_USART_CLK_FRQ
ldr
r2
,
=
STM32MP1_UART_BAUDRATE
b
console_stm32_core_init
endfunc
plat_crash_console_init
...
...
plat/st/stm32mp1/stm32mp1_pm.c
View file @
fbf35335
/*
* Copyright (c) 2015-201
8
, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2015-201
9
, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
...
...
@@ -23,11 +23,9 @@
#include <boot_api.h>
#include <stm32mp1_private.h>
static
uint
32
_t
stm32_sec_entrypoint
;
static
uint
ptr
_t
stm32_sec_entrypoint
;
static
uint32_t
cntfrq_core0
;
#define SEND_SECURE_IT_TO_CORE_1 0x20000U
/*******************************************************************************
* STM32MP1 handler called when a CPU is about to enter standby.
* call by core 1 to enter in wfi
...
...
@@ -42,6 +40,7 @@ static void stm32_cpu_standby(plat_local_state_t cpu_state)
* Enter standby state
* dsb is good practice before using wfi to enter low power states
*/
isb
();
dsb
();
while
(
interrupt
==
GIC_SPURIOUS_INTERRUPT
)
{
wfi
();
...
...
@@ -102,8 +101,7 @@ static int stm32_pwr_domain_on(u_register_t mpidr)
}
/* Generate an IT to core 1 */
mmio_write_32
(
STM32MP1_GICD_BASE
+
GICD_SGIR
,
SEND_SECURE_IT_TO_CORE_1
|
ARM_IRQ_SEC_SGI_0
);
gicv2_raise_sgi
(
ARM_IRQ_SEC_SGI_0
,
STM32MP1_SECONDARY_CPU
);
return
PSCI_E_SUCCESS
;
}
...
...
plat/st/stm32mp1/stm32mp1_security.c
View file @
fbf35335
/*
* Copyright (c) 2015-201
8
, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2015-201
9
, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
...
...
@@ -65,22 +65,6 @@ static void init_tzc400(void)
******************************************************************************/
static
void
early_init_tzc400
(
void
)
{
uint32_t
rstsr
,
rst_standby
;
rstsr
=
mmio_read_32
(
RCC_BASE
+
RCC_MP_RSTSCLRR
);
/* No warning if return from (C)STANDBY */
rst_standby
=
rstsr
&
(
RCC_MP_RSTSCLRR_STDBYRSTF
|
RCC_MP_RSTSCLRR_CSTDBYRSTF
);
if
(
stm32mp1_clk_is_enabled
(
TZC1
)
&&
(
rst_standby
==
0U
))
{
WARN
(
"TZC400 port 1 clock already enable
\n
"
);
}
if
(
stm32mp1_clk_is_enabled
(
TZC2
)
&&
(
rst_standby
==
0U
))
{
WARN
(
"TZC400 port 2 clock already enable
\n
"
);
}
if
(
stm32mp1_clk_enable
(
TZC1
)
!=
0
)
{
ERROR
(
"Cannot enable TZC1 clock
\n
"
);
panic
();
...
...
@@ -103,6 +87,7 @@ static void early_init_tzc400(void)
STM32MP1_DDR_BASE
+
(
STM32MP1_DDR_MAX_SIZE
-
1U
),
TZC_REGION_S_RDWR
,
TZC_REGION_ACCESS_RDWR
(
STM32MP1_TZC_A7_ID
)
|
TZC_REGION_ACCESS_RDWR
(
STM32MP1_TZC_SDMMC_ID
));
/* Raise an exception if a NS device tries to access secure memory */
...
...
Prev
1
2
3
Next
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