Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
2580390c
Commit
2580390c
authored
Nov 06, 2012
by
Tony Lindgren
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'dev-dt-timer' of github.com:jonhunter/linux into omap-for-v3.8/dt
parents
ad325255
9883f7c8
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
275 additions
and
39 deletions
+275
-39
arch/arm/mach-omap2/board-generic.c
arch/arm/mach-omap2/board-generic.c
+17
-0
arch/arm/mach-omap2/timer.c
arch/arm/mach-omap2/timer.c
+170
-33
arch/arm/plat-omap/dmtimer.c
arch/arm/plat-omap/dmtimer.c
+87
-6
arch/arm/plat-omap/include/plat/dmtimer.h
arch/arm/plat-omap/include/plat/dmtimer.h
+1
-0
No files found.
arch/arm/mach-omap2/board-generic.c
View file @
2580390c
...
...
@@ -97,6 +97,23 @@ DT_MACHINE_START(OMAP3_DT, "Generic OMAP3 (Flattened Device Tree)")
.
dt_compat
=
omap3_boards_compat
,
.
restart
=
omap_prcm_restart
,
MACHINE_END
static
const
char
*
omap3_gp_boards_compat
[]
__initdata
=
{
"ti,omap3-beagle"
,
NULL
,
};
DT_MACHINE_START
(
OMAP3_GP_DT
,
"Generic OMAP3-GP (Flattened Device Tree)"
)
.
reserve
=
omap_reserve
,
.
map_io
=
omap3_map_io
,
.
init_early
=
omap3430_init_early
,
.
init_irq
=
omap_intc_of_init
,
.
handle_irq
=
omap3_intc_handle_irq
,
.
init_machine
=
omap_generic_init
,
.
timer
=
&
omap3_secure_timer
,
.
dt_compat
=
omap3_gp_boards_compat
,
.
restart
=
omap_prcm_restart
,
MACHINE_END
#endif
#ifdef CONFIG_SOC_AM33XX
...
...
arch/arm/mach-omap2/timer.c
View file @
2580390c
...
...
@@ -37,6 +37,8 @@
#include <linux/clockchips.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/time.h>
#include <asm/smp_twd.h>
...
...
@@ -66,11 +68,13 @@
#define OMAP3_CLKEV_SOURCE OMAP3_32K_SOURCE
#define OMAP4_CLKEV_SOURCE OMAP4_32K_SOURCE
#define OMAP3_SECURE_TIMER 12
#define TIMER_PROP_SECURE "ti,timer-secure"
#else
#define OMAP2_CLKEV_SOURCE OMAP2_MPU_SOURCE
#define OMAP3_CLKEV_SOURCE OMAP3_MPU_SOURCE
#define OMAP4_CLKEV_SOURCE OMAP4_MPU_SOURCE
#define OMAP3_SECURE_TIMER 1
#define TIMER_PROP_SECURE "ti,timer-alwon"
#endif
#define REALTIME_COUNTER_BASE 0x48243200
...
...
@@ -144,36 +148,141 @@ static struct clock_event_device clockevent_gpt = {
.
set_mode
=
omap2_gp_timer_set_mode
,
};
static
struct
property
device_disabled
=
{
.
name
=
"status"
,
.
length
=
sizeof
(
"disabled"
),
.
value
=
"disabled"
,
};
static
struct
of_device_id
omap_timer_match
[]
__initdata
=
{
{
.
compatible
=
"ti,omap2-timer"
,
},
{
}
};
static
struct
of_device_id
omap_counter_match
[]
__initdata
=
{
{
.
compatible
=
"ti,omap-counter32k"
,
},
{
}
};
/**
* omap_get_timer_dt - get a timer using device-tree
* @match - device-tree match structure for matching a device type
* @property - optional timer property to match
*
* Helper function to get a timer during early boot using device-tree for use
* as kernel system timer. Optionally, the property argument can be used to
* select a timer with a specific property. Once a timer is found then mark
* the timer node in device-tree as disabled, to prevent the kernel from
* registering this timer as a platform device and so no one else can use it.
*/
static
struct
device_node
*
__init
omap_get_timer_dt
(
struct
of_device_id
*
match
,
const
char
*
property
)
{
struct
device_node
*
np
;
for_each_matching_node
(
np
,
match
)
{
if
(
!
of_device_is_available
(
np
))
{
of_node_put
(
np
);
continue
;
}
if
(
property
&&
!
of_get_property
(
np
,
property
,
NULL
))
{
of_node_put
(
np
);
continue
;
}
prom_add_property
(
np
,
&
device_disabled
);
return
np
;
}
return
NULL
;
}
/**
* omap_dmtimer_init - initialisation function when device tree is used
*
* For secure OMAP3 devices, timers with device type "timer-secure" cannot
* be used by the kernel as they are reserved. Therefore, to prevent the
* kernel registering these devices remove them dynamically from the device
* tree on boot.
*/
void
__init
omap_dmtimer_init
(
void
)
{
struct
device_node
*
np
;
if
(
!
cpu_is_omap34xx
())
return
;
/* If we are a secure device, remove any secure timer nodes */
if
((
omap_type
()
!=
OMAP2_DEVICE_TYPE_GP
))
{
np
=
omap_get_timer_dt
(
omap_timer_match
,
"ti,timer-secure"
);
if
(
np
)
of_node_put
(
np
);
}
}
static
int
__init
omap_dm_timer_init_one
(
struct
omap_dm_timer
*
timer
,
int
gptimer_id
,
const
char
*
fck_source
)
const
char
*
fck_source
,
const
char
*
property
)
{
char
name
[
10
];
/* 10 = sizeof("gptXX_Xck0") */
const
char
*
oh_name
;
struct
device_node
*
np
;
struct
omap_hwmod
*
oh
;
struct
resource
irq_rsrc
,
mem_rsrc
;
size_t
size
;
int
res
=
0
;
int
r
;
sprintf
(
name
,
"timer%d"
,
gptimer_id
);
omap_hwmod_setup_one
(
name
);
oh
=
omap_hwmod_lookup
(
name
);
if
(
of_have_populated_dt
())
{
np
=
omap_get_timer_dt
(
omap_timer_match
,
NULL
);
if
(
!
np
)
return
-
ENODEV
;
of_property_read_string_index
(
np
,
"ti,hwmods"
,
0
,
&
oh_name
);
if
(
!
oh_name
)
return
-
ENODEV
;
timer
->
irq
=
irq_of_parse_and_map
(
np
,
0
);
if
(
!
timer
->
irq
)
return
-
ENXIO
;
timer
->
io_base
=
of_iomap
(
np
,
0
);
of_node_put
(
np
);
}
else
{
if
(
omap_dm_timer_reserve_systimer
(
gptimer_id
))
return
-
ENODEV
;
sprintf
(
name
,
"timer%d"
,
gptimer_id
);
oh_name
=
name
;
}
omap_hwmod_setup_one
(
oh_name
);
oh
=
omap_hwmod_lookup
(
oh_name
);
if
(
!
oh
)
return
-
ENODEV
;
r
=
omap_hwmod_get_resource_byname
(
oh
,
IORESOURCE_IRQ
,
NULL
,
&
irq_rsrc
);
if
(
r
)
return
-
ENXIO
;
timer
->
irq
=
irq_rsrc
.
start
;
r
=
omap_hwmod_get_resource_byname
(
oh
,
IORESOURCE_MEM
,
NULL
,
&
mem_rsrc
);
if
(
r
)
return
-
ENXIO
;
timer
->
phys_base
=
mem_rsrc
.
start
;
size
=
mem_rsrc
.
end
-
mem_rsrc
.
start
;
if
(
!
of_have_populated_dt
())
{
r
=
omap_hwmod_get_resource_byname
(
oh
,
IORESOURCE_IRQ
,
NULL
,
&
irq_rsrc
);
if
(
r
)
return
-
ENXIO
;
timer
->
irq
=
irq_rsrc
.
start
;
r
=
omap_hwmod_get_resource_byname
(
oh
,
IORESOURCE_MEM
,
NULL
,
&
mem_rsrc
);
if
(
r
)
return
-
ENXIO
;
timer
->
phys_base
=
mem_rsrc
.
start
;
size
=
mem_rsrc
.
end
-
mem_rsrc
.
start
;
/* Static mapping, never released */
timer
->
io_base
=
ioremap
(
timer
->
phys_base
,
size
);
}
/* Static mapping, never released */
timer
->
io_base
=
ioremap
(
timer
->
phys_base
,
size
);
if
(
!
timer
->
io_base
)
return
-
ENXIO
;
...
...
@@ -184,9 +293,7 @@ static int __init omap_dm_timer_init_one(struct omap_dm_timer *timer,
omap_hwmod_enable
(
oh
);
if
(
omap_dm_timer_reserve_systimer
(
gptimer_id
))
return
-
ENODEV
;
/* FIXME: Need to remove hard-coded test on timer ID */
if
(
gptimer_id
!=
12
)
{
struct
clk
*
src
;
...
...
@@ -196,8 +303,8 @@ static int __init omap_dm_timer_init_one(struct omap_dm_timer *timer,
}
else
{
res
=
__omap_dm_timer_set_source
(
timer
->
fclk
,
src
);
if
(
IS_ERR_VALUE
(
res
))
pr_warn
ing
(
"%s: timer%i
cannot set source
\n
"
,
__func__
,
gptimer_id
);
pr_warn
(
"%s: %s
cannot set source
\n
"
,
__func__
,
oh
->
name
);
clk_put
(
src
);
}
}
...
...
@@ -213,11 +320,12 @@ static int __init omap_dm_timer_init_one(struct omap_dm_timer *timer,
}
static
void
__init
omap2_gp_clockevent_init
(
int
gptimer_id
,
const
char
*
fck_source
)
const
char
*
fck_source
,
const
char
*
property
)
{
int
res
;
res
=
omap_dm_timer_init_one
(
&
clkev
,
gptimer_id
,
fck_source
);
res
=
omap_dm_timer_init_one
(
&
clkev
,
gptimer_id
,
fck_source
,
property
);
BUG_ON
(
res
);
omap2_gp_timer_irq
.
dev_id
=
&
clkev
;
...
...
@@ -274,10 +382,25 @@ static u32 notrace dmtimer_read_sched_clock(void)
static
int
__init
omap2_sync32k_clocksource_init
(
void
)
{
int
ret
;
struct
device_node
*
np
=
NULL
;
struct
omap_hwmod
*
oh
;
void
__iomem
*
vbase
;
const
char
*
oh_name
=
"counter_32k"
;
/*
* If device-tree is present, then search the DT blob
* to see if the 32kHz counter is supported.
*/
if
(
of_have_populated_dt
())
{
np
=
omap_get_timer_dt
(
omap_counter_match
,
NULL
);
if
(
!
np
)
return
-
ENODEV
;
of_property_read_string_index
(
np
,
"ti,hwmods"
,
0
,
&
oh_name
);
if
(
!
oh_name
)
return
-
ENODEV
;
}
/*
* First check hwmod data is available for sync32k counter
*/
...
...
@@ -287,7 +410,13 @@ static int __init omap2_sync32k_clocksource_init(void)
omap_hwmod_setup_one
(
oh_name
);
vbase
=
omap_hwmod_get_mpu_rt_va
(
oh
);
if
(
np
)
{
vbase
=
of_iomap
(
np
,
0
);
of_node_put
(
np
);
}
else
{
vbase
=
omap_hwmod_get_mpu_rt_va
(
oh
);
}
if
(
!
vbase
)
{
pr_warn
(
"%s: failed to get counter_32k resource
\n
"
,
__func__
);
return
-
ENXIO
;
...
...
@@ -321,7 +450,7 @@ static void __init omap2_gptimer_clocksource_init(int gptimer_id,
{
int
res
;
res
=
omap_dm_timer_init_one
(
&
clksrc
,
gptimer_id
,
fck_source
);
res
=
omap_dm_timer_init_one
(
&
clksrc
,
gptimer_id
,
fck_source
,
NULL
);
BUG_ON
(
res
);
__omap_dm_timer_load_start
(
&
clksrc
,
...
...
@@ -433,11 +562,12 @@ static inline void __init realtime_counter_init(void)
{}
#endif
#define OMAP_SYS_TIMER_INIT(name, clkev_nr, clkev_src,
\
#define OMAP_SYS_TIMER_INIT(name, clkev_nr, clkev_src,
clkev_prop,
\
clksrc_nr, clksrc_src) \
static void __init omap##name##_timer_init(void) \
{ \
omap2_gp_clockevent_init((clkev_nr), clkev_src); \
omap_dmtimer_init(); \
omap2_gp_clockevent_init((clkev_nr), clkev_src, clkev_prop); \
omap2_clocksource_init((clksrc_nr), clksrc_src); \
}
...
...
@@ -447,20 +577,23 @@ struct sys_timer omap##name##_timer = { \
};
#ifdef CONFIG_ARCH_OMAP2
OMAP_SYS_TIMER_INIT
(
2
,
1
,
OMAP2_CLKEV_SOURCE
,
2
,
OMAP2_MPU_SOURCE
)
OMAP_SYS_TIMER_INIT
(
2
,
1
,
OMAP2_CLKEV_SOURCE
,
"ti,timer-alwon"
,
2
,
OMAP2_MPU_SOURCE
)
OMAP_SYS_TIMER
(
2
)
#endif
#ifdef CONFIG_ARCH_OMAP3
OMAP_SYS_TIMER_INIT
(
3
,
1
,
OMAP3_CLKEV_SOURCE
,
2
,
OMAP3_MPU_SOURCE
)
OMAP_SYS_TIMER_INIT
(
3
,
1
,
OMAP3_CLKEV_SOURCE
,
"ti,timer-alwon"
,
2
,
OMAP3_MPU_SOURCE
)
OMAP_SYS_TIMER
(
3
)
OMAP_SYS_TIMER_INIT
(
3
_secure
,
OMAP3_SECURE_TIMER
,
OMAP3_CLKEV_SOURCE
,
2
,
OMAP3_MPU_SOURCE
)
TIMER_PROP_SECURE
,
2
,
OMAP3_MPU_SOURCE
)
OMAP_SYS_TIMER
(
3
_secure
)
#endif
#ifdef CONFIG_SOC_AM33XX
OMAP_SYS_TIMER_INIT
(
3
_am33xx
,
1
,
OMAP4_MPU_SOURCE
,
2
,
OMAP4_MPU_SOURCE
)
OMAP_SYS_TIMER_INIT
(
3
_am33xx
,
1
,
OMAP4_MPU_SOURCE
,
"ti,timer-alwon"
,
2
,
OMAP4_MPU_SOURCE
)
OMAP_SYS_TIMER
(
3
_am33xx
)
#endif
...
...
@@ -472,7 +605,7 @@ static DEFINE_TWD_LOCAL_TIMER(twd_local_timer,
static
void
__init
omap4_timer_init
(
void
)
{
omap2_gp_clockevent_init
(
1
,
OMAP4_CLKEV_SOURCE
);
omap2_gp_clockevent_init
(
1
,
OMAP4_CLKEV_SOURCE
,
"ti,timer-alwon"
);
omap2_clocksource_init
(
2
,
OMAP4_MPU_SOURCE
);
#ifdef CONFIG_LOCAL_TIMERS
/* Local timers are not supprted on OMAP4430 ES1.0 */
...
...
@@ -498,7 +631,7 @@ static void __init omap5_timer_init(void)
{
int
err
;
omap2_gp_clockevent_init
(
1
,
OMAP4_CLKEV_SOURCE
);
omap2_gp_clockevent_init
(
1
,
OMAP4_CLKEV_SOURCE
,
"ti,timer-alwon"
);
omap2_clocksource_init
(
2
,
OMAP4_MPU_SOURCE
);
realtime_counter_init
();
...
...
@@ -583,6 +716,10 @@ static int __init omap2_dm_timer_init(void)
{
int
ret
;
/* If dtb is there, the devices will be created dynamically */
if
(
of_have_populated_dt
())
return
-
ENODEV
;
ret
=
omap_hwmod_for_each_by_class
(
"timer"
,
omap_timer_init
,
NULL
);
if
(
unlikely
(
ret
))
{
pr_err
(
"%s: device registration failed.
\n
"
,
__func__
);
...
...
arch/arm/plat-omap/dmtimer.c
View file @
2580390c
...
...
@@ -40,6 +40,8 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <plat/dmtimer.h>
#include <plat/omap-pm.h>
...
...
@@ -212,6 +214,13 @@ struct omap_dm_timer *omap_dm_timer_request_specific(int id)
unsigned
long
flags
;
int
ret
=
0
;
/* Requesting timer by ID is not supported when device tree is used */
if
(
of_have_populated_dt
())
{
pr_warn
(
"%s: Please use omap_dm_timer_request_by_cap()
\n
"
,
__func__
);
return
NULL
;
}
spin_lock_irqsave
(
&
dm_timer_lock
,
flags
);
list_for_each_entry
(
t
,
&
omap_timer_list
,
node
)
{
if
(
t
->
pdev
->
id
==
id
&&
!
t
->
reserved
)
{
...
...
@@ -237,6 +246,58 @@ struct omap_dm_timer *omap_dm_timer_request_specific(int id)
}
EXPORT_SYMBOL_GPL
(
omap_dm_timer_request_specific
);
/**
* omap_dm_timer_request_by_cap - Request a timer by capability
* @cap: Bit mask of capabilities to match
*
* Find a timer based upon capabilities bit mask. Callers of this function
* should use the definitions found in the plat/dmtimer.h file under the
* comment "timer capabilities used in hwmod database". Returns pointer to
* timer handle on success and a NULL pointer on failure.
*/
struct
omap_dm_timer
*
omap_dm_timer_request_by_cap
(
u32
cap
)
{
struct
omap_dm_timer
*
timer
=
NULL
,
*
t
;
unsigned
long
flags
;
if
(
!
cap
)
return
NULL
;
spin_lock_irqsave
(
&
dm_timer_lock
,
flags
);
list_for_each_entry
(
t
,
&
omap_timer_list
,
node
)
{
if
((
!
t
->
reserved
)
&&
((
t
->
capability
&
cap
)
==
cap
))
{
/*
* If timer is not NULL, we have already found one timer
* but it was not an exact match because it had more
* capabilites that what was required. Therefore,
* unreserve the last timer found and see if this one
* is a better match.
*/
if
(
timer
)
timer
->
reserved
=
0
;
timer
=
t
;
timer
->
reserved
=
1
;
/* Exit loop early if we find an exact match */
if
(
t
->
capability
==
cap
)
break
;
}
}
spin_unlock_irqrestore
(
&
dm_timer_lock
,
flags
);
if
(
timer
&&
omap_dm_timer_prepare
(
timer
))
{
timer
->
reserved
=
0
;
timer
=
NULL
;
}
if
(
!
timer
)
pr_debug
(
"%s: timer request failed!
\n
"
,
__func__
);
return
timer
;
}
EXPORT_SYMBOL_GPL
(
omap_dm_timer_request_by_cap
);
int
omap_dm_timer_free
(
struct
omap_dm_timer
*
timer
)
{
if
(
unlikely
(
!
timer
))
...
...
@@ -414,7 +475,7 @@ int omap_dm_timer_set_source(struct omap_dm_timer *timer, int source)
* use the clock framework to set the parent clock. To be removed
* once OMAP1 migrated to using clock framework for dmtimers
*/
if
(
pdata
->
set_timer_src
)
if
(
pdata
&&
pdata
->
set_timer_src
)
return
pdata
->
set_timer_src
(
timer
->
pdev
,
source
);
fclk
=
clk_get
(
&
timer
->
pdev
->
dev
,
"fck"
);
...
...
@@ -695,7 +756,7 @@ static int __devinit omap_dm_timer_probe(struct platform_device *pdev)
struct
device
*
dev
=
&
pdev
->
dev
;
struct
dmtimer_platform_data
*
pdata
=
pdev
->
dev
.
platform_data
;
if
(
!
pdata
)
{
if
(
!
pdata
&&
!
dev
->
of_node
)
{
dev_err
(
dev
,
"%s: no platform data.
\n
"
,
__func__
);
return
-
ENODEV
;
}
...
...
@@ -724,11 +785,23 @@ static int __devinit omap_dm_timer_probe(struct platform_device *pdev)
return
-
ENOMEM
;
}
timer
->
id
=
pdev
->
id
;
if
(
dev
->
of_node
)
{
if
(
of_find_property
(
dev
->
of_node
,
"ti,timer-alwon"
,
NULL
))
timer
->
capability
|=
OMAP_TIMER_ALWON
;
if
(
of_find_property
(
dev
->
of_node
,
"ti,timer-dsp"
,
NULL
))
timer
->
capability
|=
OMAP_TIMER_HAS_DSP_IRQ
;
if
(
of_find_property
(
dev
->
of_node
,
"ti,timer-pwm"
,
NULL
))
timer
->
capability
|=
OMAP_TIMER_HAS_PWM
;
if
(
of_find_property
(
dev
->
of_node
,
"ti,timer-secure"
,
NULL
))
timer
->
capability
|=
OMAP_TIMER_SECURE
;
}
else
{
timer
->
id
=
pdev
->
id
;
timer
->
capability
=
pdata
->
timer_capability
;
timer
->
reserved
=
omap_dm_timer_reserved_systimer
(
timer
->
id
);
}
timer
->
irq
=
irq
->
start
;
timer
->
reserved
=
omap_dm_timer_reserved_systimer
(
timer
->
id
);
timer
->
pdev
=
pdev
;
timer
->
capability
=
pdata
->
timer_capability
;
/* Skip pm_runtime_enable for OMAP1 */
if
(
!
(
timer
->
capability
&
OMAP_TIMER_NEEDS_RESET
))
{
...
...
@@ -768,7 +841,8 @@ static int __devexit omap_dm_timer_remove(struct platform_device *pdev)
spin_lock_irqsave
(
&
dm_timer_lock
,
flags
);
list_for_each_entry
(
timer
,
&
omap_timer_list
,
node
)
if
(
timer
->
pdev
->
id
==
pdev
->
id
)
{
if
(
!
strcmp
(
dev_name
(
&
timer
->
pdev
->
dev
),
dev_name
(
&
pdev
->
dev
)))
{
list_del
(
&
timer
->
node
);
ret
=
0
;
break
;
...
...
@@ -778,11 +852,18 @@ static int __devexit omap_dm_timer_remove(struct platform_device *pdev)
return
ret
;
}
static
const
struct
of_device_id
omap_timer_match
[]
=
{
{
.
compatible
=
"ti,omap2-timer"
,
},
{},
};
MODULE_DEVICE_TABLE
(
of
,
omap_timer_match
);
static
struct
platform_driver
omap_dm_timer_driver
=
{
.
probe
=
omap_dm_timer_probe
,
.
remove
=
__devexit_p
(
omap_dm_timer_remove
),
.
driver
=
{
.
name
=
"omap_timer"
,
.
of_match_table
=
of_match_ptr
(
omap_timer_match
),
},
};
...
...
arch/arm/plat-omap/include/plat/dmtimer.h
View file @
2580390c
...
...
@@ -99,6 +99,7 @@ struct dmtimer_platform_data {
int
omap_dm_timer_reserve_systimer
(
int
id
);
struct
omap_dm_timer
*
omap_dm_timer_request
(
void
);
struct
omap_dm_timer
*
omap_dm_timer_request_specific
(
int
timer_id
);
struct
omap_dm_timer
*
omap_dm_timer_request_by_cap
(
u32
cap
);
int
omap_dm_timer_free
(
struct
omap_dm_timer
*
timer
);
void
omap_dm_timer_enable
(
struct
omap_dm_timer
*
timer
);
void
omap_dm_timer_disable
(
struct
omap_dm_timer
*
timer
);
...
...
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