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
de2ea581
Commit
de2ea581
authored
Jul 23, 2014
by
Daniel Lezcano
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'clockevents/renesas-timers-dt' into clockevents/3.17
parents
afdb0943
cca8d059
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
309 additions
and
327 deletions
+309
-327
Documentation/devicetree/bindings/timer/renesas,cmt.txt
Documentation/devicetree/bindings/timer/renesas,cmt.txt
+47
-0
Documentation/devicetree/bindings/timer/renesas,mtu2.txt
Documentation/devicetree/bindings/timer/renesas,mtu2.txt
+39
-0
Documentation/devicetree/bindings/timer/renesas,tmu.txt
Documentation/devicetree/bindings/timer/renesas,tmu.txt
+39
-0
drivers/clocksource/sh_cmt.c
drivers/clocksource/sh_cmt.c
+86
-147
drivers/clocksource/sh_mtu2.c
drivers/clocksource/sh_mtu2.c
+44
-102
drivers/clocksource/sh_tmu.c
drivers/clocksource/sh_tmu.c
+54
-73
include/linux/sh_timer.h
include/linux/sh_timer.h
+0
-5
No files found.
Documentation/devicetree/bindings/timer/renesas,cmt.txt
0 → 100644
View file @
de2ea581
* Renesas R-Car Compare Match Timer (CMT)
The CMT is a multi-channel 16/32/48-bit timer/counter with configurable clock
inputs and programmable compare match.
Channels share hardware resources but their counter and compare match value
are independent. A particular CMT instance can implement only a subset of the
channels supported by the CMT model. Channel indices represent the hardware
position of the channel in the CMT and don't match the channel numbers in the
datasheets.
Required Properties:
- compatible: must contain one of the following.
- "renesas,cmt-32" for the 32-bit CMT
(CMT0 on sh7372, sh73a0 and r8a7740)
- "renesas,cmt-32-fast" for the 32-bit CMT with fast clock support
(CMT[234] on sh7372, sh73a0 and r8a7740)
- "renesas,cmt-48" for the 48-bit CMT
(CMT1 on sh7372, sh73a0 and r8a7740)
- "renesas,cmt-48-gen2" for the second generation 48-bit CMT
(CMT[01] on r8a73a4, r8a7790 and r8a7791)
- reg: base address and length of the registers block for the timer module.
- interrupts: interrupt-specifier for the timer, one per channel.
- clocks: a list of phandle + clock-specifier pairs, one for each entry
in clock-names.
- clock-names: must contain "fck" for the functional clock.
- renesas,channels-mask: bitmask of the available channels.
Example: R8A7790 (R-Car H2) CMT0 node
CMT0 on R8A7790 implements hardware channels 5 and 6 only and names
them channels 0 and 1 in the documentation.
cmt0: timer@ffca0000 {
compatible = "renesas,cmt-48-gen2";
reg = <0 0xffca0000 0 0x1004>;
interrupts = <0 142 IRQ_TYPE_LEVEL_HIGH>,
<0 142 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&mstp1_clks R8A7790_CLK_CMT0>;
clock-names = "fck";
renesas,channels-mask = <0x60>;
};
Documentation/devicetree/bindings/timer/renesas,mtu2.txt
0 → 100644
View file @
de2ea581
* Renesas R-Car Multi-Function Timer Pulse Unit 2 (MTU2)
The MTU2 is a multi-purpose, multi-channel timer/counter with configurable
clock inputs and programmable compare match.
Channels share hardware resources but their counter and compare match value
are independent. The MTU2 hardware supports five channels indexed from 0 to 4.
Required Properties:
- compatible: must contain "renesas,mtu2"
- reg: base address and length of the registers block for the timer module.
- interrupts: interrupt specifiers for the timer, one for each entry in
interrupt-names.
- interrupt-names: must contain one entry named "tgi?a" for each enabled
channel, where "?" is the channel index expressed as one digit from "0" to
"4".
- clocks: a list of phandle + clock-specifier pairs, one for each entry
in clock-names.
- clock-names: must contain "fck" for the functional clock.
Example: R7S72100 (RZ/A1H) MTU2 node
mtu2: timer@fcff0000 {
compatible = "renesas,mtu2";
reg = <0xfcff0000 0x400>;
interrupts = <0 139 IRQ_TYPE_LEVEL_HIGH>,
<0 146 IRQ_TYPE_LEVEL_HIGH>,
<0 150 IRQ_TYPE_LEVEL_HIGH>,
<0 154 IRQ_TYPE_LEVEL_HIGH>,
<0 159 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "tgi0a", "tgi1a", "tgi2a", "tgi3a", "tgi4a";
clocks = <&mstp3_clks R7S72100_CLK_MTU2>;
clock-names = "fck";
};
Documentation/devicetree/bindings/timer/renesas,tmu.txt
0 → 100644
View file @
de2ea581
* Renesas R-Car Timer Unit (TMU)
The TMU is a 32-bit timer/counter with configurable clock inputs and
programmable compare match.
Channels share hardware resources but their counter and compare match value
are independent. The TMU hardware supports up to three channels.
Required Properties:
- compatible: must contain "renesas,tmu"
- reg: base address and length of the registers block for the timer module.
- interrupts: interrupt-specifier for the timer, one per channel.
- clocks: a list of phandle + clock-specifier pairs, one for each entry
in clock-names.
- clock-names: must contain "fck" for the functional clock.
Optional Properties:
- #renesas,channels: number of channels implemented by the timer, must be 2
or 3 (if not specified the value defaults to 3).
Example: R8A7779 (R-Car H1) TMU0 node
tmu0: timer@ffd80000 {
compatible = "renesas,tmu";
reg = <0xffd80000 0x30>;
interrupts = <0 32 IRQ_TYPE_LEVEL_HIGH>,
<0 33 IRQ_TYPE_LEVEL_HIGH>,
<0 34 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&mstp0_clks R8A7779_CLK_TMU0>;
clock-names = "fck";
#renesas,channels = <3>;
};
drivers/clocksource/sh_cmt.c
View file @
de2ea581
...
...
@@ -24,6 +24,7 @@
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
...
...
@@ -114,14 +115,15 @@ struct sh_cmt_device {
struct
platform_device
*
pdev
;
const
struct
sh_cmt_info
*
info
;
bool
legacy
;
void
__iomem
*
mapbase_ch
;
void
__iomem
*
mapbase
;
struct
clk
*
clk
;
raw_spinlock_t
lock
;
/* Protect the shared start/stop register */
struct
sh_cmt_channel
*
channels
;
unsigned
int
num_channels
;
unsigned
int
hw_channels
;
bool
has_clockevent
;
bool
has_clocksource
;
...
...
@@ -301,14 +303,12 @@ static unsigned long sh_cmt_get_counter(struct sh_cmt_channel *ch,
return
v2
;
}
static
DEFINE_RAW_SPINLOCK
(
sh_cmt_lock
);
static
void
sh_cmt_start_stop_ch
(
struct
sh_cmt_channel
*
ch
,
int
start
)
{
unsigned
long
flags
,
value
;
/* start stop register shared by multiple timer channels */
raw_spin_lock_irqsave
(
&
sh_cmt_
lock
,
flags
);
raw_spin_lock_irqsave
(
&
ch
->
cmt
->
lock
,
flags
);
value
=
sh_cmt_read_cmstr
(
ch
);
if
(
start
)
...
...
@@ -317,7 +317,7 @@ static void sh_cmt_start_stop_ch(struct sh_cmt_channel *ch, int start)
value
&=
~
(
1
<<
ch
->
timer_bit
);
sh_cmt_write_cmstr
(
ch
,
value
);
raw_spin_unlock_irqrestore
(
&
sh_cmt_
lock
,
flags
);
raw_spin_unlock_irqrestore
(
&
ch
->
cmt
->
lock
,
flags
);
}
static
int
sh_cmt_enable
(
struct
sh_cmt_channel
*
ch
,
unsigned
long
*
rate
)
...
...
@@ -792,7 +792,7 @@ static int sh_cmt_register_clockevent(struct sh_cmt_channel *ch,
int
irq
;
int
ret
;
irq
=
platform_get_irq
(
ch
->
cmt
->
pdev
,
ch
->
cmt
->
legacy
?
0
:
ch
->
index
);
irq
=
platform_get_irq
(
ch
->
cmt
->
pdev
,
ch
->
index
);
if
(
irq
<
0
)
{
dev_err
(
&
ch
->
cmt
->
pdev
->
dev
,
"ch%u: failed to get irq
\n
"
,
ch
->
index
);
...
...
@@ -863,33 +863,26 @@ static int sh_cmt_setup_channel(struct sh_cmt_channel *ch, unsigned int index,
* Compute the address of the channel control register block. For the
* timers with a per-channel start/stop register, compute its address
* as well.
*
* For legacy configuration the address has been mapped explicitly.
*/
if
(
cmt
->
legacy
)
{
ch
->
ioctrl
=
cmt
->
mapbase_ch
;
}
else
{
switch
(
cmt
->
info
->
model
)
{
case
SH_CMT_16BIT
:
ch
->
ioctrl
=
cmt
->
mapbase
+
2
+
ch
->
hwidx
*
6
;
break
;
case
SH_CMT_32BIT
:
case
SH_CMT_48BIT
:
ch
->
ioctrl
=
cmt
->
mapbase
+
0x10
+
ch
->
hwidx
*
0x10
;
break
;
case
SH_CMT_32BIT_FAST
:
/*
* The 32-bit "fast" timer has a single channel at hwidx
* 5 but is located at offset 0x40 instead of 0x60 for
* some reason.
*/
ch
->
ioctrl
=
cmt
->
mapbase
+
0x40
;
break
;
case
SH_CMT_48BIT_GEN2
:
ch
->
iostart
=
cmt
->
mapbase
+
ch
->
hwidx
*
0x100
;
ch
->
ioctrl
=
ch
->
iostart
+
0x10
;
break
;
}
switch
(
cmt
->
info
->
model
)
{
case
SH_CMT_16BIT
:
ch
->
ioctrl
=
cmt
->
mapbase
+
2
+
ch
->
hwidx
*
6
;
break
;
case
SH_CMT_32BIT
:
case
SH_CMT_48BIT
:
ch
->
ioctrl
=
cmt
->
mapbase
+
0x10
+
ch
->
hwidx
*
0x10
;
break
;
case
SH_CMT_32BIT_FAST
:
/*
* The 32-bit "fast" timer has a single channel at hwidx 5 but
* is located at offset 0x40 instead of 0x60 for some reason.
*/
ch
->
ioctrl
=
cmt
->
mapbase
+
0x40
;
break
;
case
SH_CMT_48BIT_GEN2
:
ch
->
iostart
=
cmt
->
mapbase
+
ch
->
hwidx
*
0x100
;
ch
->
ioctrl
=
ch
->
iostart
+
0x10
;
break
;
}
if
(
cmt
->
info
->
width
==
(
sizeof
(
ch
->
max_match_value
)
*
8
))
...
...
@@ -900,12 +893,7 @@ static int sh_cmt_setup_channel(struct sh_cmt_channel *ch, unsigned int index,
ch
->
match_value
=
ch
->
max_match_value
;
raw_spin_lock_init
(
&
ch
->
lock
);
if
(
cmt
->
legacy
)
{
ch
->
timer_bit
=
ch
->
hwidx
;
}
else
{
ch
->
timer_bit
=
cmt
->
info
->
model
==
SH_CMT_48BIT_GEN2
?
0
:
ch
->
hwidx
;
}
ch
->
timer_bit
=
cmt
->
info
->
model
==
SH_CMT_48BIT_GEN2
?
0
:
ch
->
hwidx
;
ret
=
sh_cmt_register
(
ch
,
dev_name
(
&
cmt
->
pdev
->
dev
),
clockevent
,
clocksource
);
...
...
@@ -938,75 +926,65 @@ static int sh_cmt_map_memory(struct sh_cmt_device *cmt)
return
0
;
}
static
int
sh_cmt_map_memory_legacy
(
struct
sh_cmt_device
*
cmt
)
{
struct
sh_timer_config
*
cfg
=
cmt
->
pdev
->
dev
.
platform_data
;
struct
resource
*
res
,
*
res2
;
/* map memory, let mapbase_ch point to our channel */
res
=
platform_get_resource
(
cmt
->
pdev
,
IORESOURCE_MEM
,
0
);
if
(
!
res
)
{
dev_err
(
&
cmt
->
pdev
->
dev
,
"failed to get I/O memory
\n
"
);
return
-
ENXIO
;
}
cmt
->
mapbase_ch
=
ioremap_nocache
(
res
->
start
,
resource_size
(
res
));
if
(
cmt
->
mapbase_ch
==
NULL
)
{
dev_err
(
&
cmt
->
pdev
->
dev
,
"failed to remap I/O memory
\n
"
);
return
-
ENXIO
;
}
/* optional resource for the shared timer start/stop register */
res2
=
platform_get_resource
(
cmt
->
pdev
,
IORESOURCE_MEM
,
1
);
/* map second resource for CMSTR */
cmt
->
mapbase
=
ioremap_nocache
(
res2
?
res2
->
start
:
res
->
start
-
cfg
->
channel_offset
,
res2
?
resource_size
(
res2
)
:
2
);
if
(
cmt
->
mapbase
==
NULL
)
{
dev_err
(
&
cmt
->
pdev
->
dev
,
"failed to remap I/O second memory
\n
"
);
iounmap
(
cmt
->
mapbase_ch
);
return
-
ENXIO
;
}
/* identify the model based on the resources */
if
(
resource_size
(
res
)
==
6
)
cmt
->
info
=
&
sh_cmt_info
[
SH_CMT_16BIT
];
else
if
(
res2
&&
(
resource_size
(
res2
)
==
4
))
cmt
->
info
=
&
sh_cmt_info
[
SH_CMT_48BIT_GEN2
];
else
cmt
->
info
=
&
sh_cmt_info
[
SH_CMT_32BIT
];
static
const
struct
platform_device_id
sh_cmt_id_table
[]
=
{
{
"sh-cmt-16"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_16BIT
]
},
{
"sh-cmt-32"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_32BIT
]
},
{
"sh-cmt-32-fast"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_32BIT_FAST
]
},
{
"sh-cmt-48"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_48BIT
]
},
{
"sh-cmt-48-gen2"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_48BIT_GEN2
]
},
{
}
};
MODULE_DEVICE_TABLE
(
platform
,
sh_cmt_id_table
);
return
0
;
}
static
const
struct
of_device_id
sh_cmt_of_table
[]
__maybe_unused
=
{
{
.
compatible
=
"renesas,cmt-32"
,
.
data
=
&
sh_cmt_info
[
SH_CMT_32BIT
]
},
{
.
compatible
=
"renesas,cmt-32-fast"
,
.
data
=
&
sh_cmt_info
[
SH_CMT_32BIT_FAST
]
},
{
.
compatible
=
"renesas,cmt-48"
,
.
data
=
&
sh_cmt_info
[
SH_CMT_48BIT
]
},
{
.
compatible
=
"renesas,cmt-48-gen2"
,
.
data
=
&
sh_cmt_info
[
SH_CMT_48BIT_GEN2
]
},
{
}
};
MODULE_DEVICE_TABLE
(
of
,
sh_cmt_of_table
);
static
void
sh_cmt_unmap_memory
(
struct
sh_cmt_device
*
cmt
)
static
int
sh_cmt_parse_dt
(
struct
sh_cmt_device
*
cmt
)
{
iounmap
(
cmt
->
mapbase
);
if
(
cmt
->
mapbase_ch
)
iounmap
(
cmt
->
mapbase_ch
);
struct
device_node
*
np
=
cmt
->
pdev
->
dev
.
of_node
;
return
of_property_read_u32
(
np
,
"renesas,channels-mask"
,
&
cmt
->
hw_channels
);
}
static
int
sh_cmt_setup
(
struct
sh_cmt_device
*
cmt
,
struct
platform_device
*
pdev
)
{
struct
sh_timer_config
*
cfg
=
pdev
->
dev
.
platform_data
;
const
struct
platform_device_id
*
id
=
pdev
->
id_entry
;
unsigned
int
hw_channels
;
unsigned
int
mask
;
unsigned
int
i
;
int
ret
;
memset
(
cmt
,
0
,
sizeof
(
*
cmt
));
cmt
->
pdev
=
pdev
;
raw_spin_lock_init
(
&
cmt
->
lock
);
if
(
IS_ENABLED
(
CONFIG_OF
)
&&
pdev
->
dev
.
of_node
)
{
const
struct
of_device_id
*
id
;
id
=
of_match_node
(
sh_cmt_of_table
,
pdev
->
dev
.
of_node
);
cmt
->
info
=
id
->
data
;
if
(
!
cfg
)
{
ret
=
sh_cmt_parse_dt
(
cmt
);
if
(
ret
<
0
)
return
ret
;
}
else
if
(
pdev
->
dev
.
platform_data
)
{
struct
sh_timer_config
*
cfg
=
pdev
->
dev
.
platform_data
;
const
struct
platform_device_id
*
id
=
pdev
->
id_entry
;
cmt
->
info
=
(
const
struct
sh_cmt_info
*
)
id
->
driver_data
;
cmt
->
hw_channels
=
cfg
->
channels_mask
;
}
else
{
dev_err
(
&
cmt
->
pdev
->
dev
,
"missing platform data
\n
"
);
return
-
ENXIO
;
}
cmt
->
info
=
(
const
struct
sh_cmt_info
*
)
id
->
driver_data
;
cmt
->
legacy
=
cmt
->
info
?
false
:
true
;
/* Get hold of clock. */
cmt
->
clk
=
clk_get
(
&
cmt
->
pdev
->
dev
,
cmt
->
legacy
?
"cmt_fck"
:
"fck"
);
cmt
->
clk
=
clk_get
(
&
cmt
->
pdev
->
dev
,
"fck"
);
if
(
IS_ERR
(
cmt
->
clk
))
{
dev_err
(
&
cmt
->
pdev
->
dev
,
"cannot get clock
\n
"
);
return
PTR_ERR
(
cmt
->
clk
);
...
...
@@ -1016,28 +994,13 @@ static int sh_cmt_setup(struct sh_cmt_device *cmt, struct platform_device *pdev)
if
(
ret
<
0
)
goto
err_clk_put
;
/*
* Map the memory resource(s). We need to support both the legacy
* platform device configuration (with one device per channel) and the
* new version (with multiple channels per device).
*/
if
(
cmt
->
legacy
)
ret
=
sh_cmt_map_memory_legacy
(
cmt
);
else
ret
=
sh_cmt_map_memory
(
cmt
);
/* Map the memory resource(s). */
ret
=
sh_cmt_map_memory
(
cmt
);
if
(
ret
<
0
)
goto
err_clk_unprepare
;
/* Allocate and setup the channels. */
if
(
cmt
->
legacy
)
{
cmt
->
num_channels
=
1
;
hw_channels
=
0
;
}
else
{
cmt
->
num_channels
=
hweight8
(
cfg
->
channels_mask
);
hw_channels
=
cfg
->
channels_mask
;
}
cmt
->
num_channels
=
hweight8
(
cmt
->
hw_channels
);
cmt
->
channels
=
kzalloc
(
cmt
->
num_channels
*
sizeof
(
*
cmt
->
channels
),
GFP_KERNEL
);
if
(
cmt
->
channels
==
NULL
)
{
...
...
@@ -1045,35 +1008,21 @@ static int sh_cmt_setup(struct sh_cmt_device *cmt, struct platform_device *pdev)
goto
err_unmap
;
}
if
(
cmt
->
legacy
)
{
ret
=
sh_cmt_setup_channel
(
&
cmt
->
channels
[
0
],
cfg
->
timer_bit
,
cfg
->
timer_bit
,
cfg
->
clockevent_rating
!=
0
,
cfg
->
clocksource_rating
!=
0
,
cmt
);
/*
* Use the first channel as a clock event device and the second channel
* as a clock source. If only one channel is available use it for both.
*/
for
(
i
=
0
,
mask
=
cmt
->
hw_channels
;
i
<
cmt
->
num_channels
;
++
i
)
{
unsigned
int
hwidx
=
ffs
(
mask
)
-
1
;
bool
clocksource
=
i
==
1
||
cmt
->
num_channels
==
1
;
bool
clockevent
=
i
==
0
;
ret
=
sh_cmt_setup_channel
(
&
cmt
->
channels
[
i
],
i
,
hwidx
,
clockevent
,
clocksource
,
cmt
);
if
(
ret
<
0
)
goto
err_unmap
;
}
else
{
unsigned
int
mask
=
hw_channels
;
unsigned
int
i
;
/*
* Use the first channel as a clock event device and the second
* channel as a clock source. If only one channel is available
* use it for both.
*/
for
(
i
=
0
;
i
<
cmt
->
num_channels
;
++
i
)
{
unsigned
int
hwidx
=
ffs
(
mask
)
-
1
;
bool
clocksource
=
i
==
1
||
cmt
->
num_channels
==
1
;
bool
clockevent
=
i
==
0
;
ret
=
sh_cmt_setup_channel
(
&
cmt
->
channels
[
i
],
i
,
hwidx
,
clockevent
,
clocksource
,
cmt
);
if
(
ret
<
0
)
goto
err_unmap
;
mask
&=
~
(
1
<<
hwidx
);
}
mask
&=
~
(
1
<<
hwidx
);
}
platform_set_drvdata
(
pdev
,
cmt
);
...
...
@@ -1082,7 +1031,7 @@ static int sh_cmt_setup(struct sh_cmt_device *cmt, struct platform_device *pdev)
err_unmap:
kfree
(
cmt
->
channels
);
sh_cmt_unmap_memory
(
cmt
);
iounmap
(
cmt
->
mapbase
);
err_clk_unprepare:
clk_unprepare
(
cmt
->
clk
);
err_clk_put:
...
...
@@ -1132,22 +1081,12 @@ static int sh_cmt_remove(struct platform_device *pdev)
return
-
EBUSY
;
/* cannot unregister clockevent and clocksource */
}
static
const
struct
platform_device_id
sh_cmt_id_table
[]
=
{
{
"sh_cmt"
,
0
},
{
"sh-cmt-16"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_16BIT
]
},
{
"sh-cmt-32"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_32BIT
]
},
{
"sh-cmt-32-fast"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_32BIT_FAST
]
},
{
"sh-cmt-48"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_48BIT
]
},
{
"sh-cmt-48-gen2"
,
(
kernel_ulong_t
)
&
sh_cmt_info
[
SH_CMT_48BIT_GEN2
]
},
{
}
};
MODULE_DEVICE_TABLE
(
platform
,
sh_cmt_id_table
);
static
struct
platform_driver
sh_cmt_device_driver
=
{
.
probe
=
sh_cmt_probe
,
.
remove
=
sh_cmt_remove
,
.
driver
=
{
.
name
=
"sh_cmt"
,
.
of_match_table
=
of_match_ptr
(
sh_cmt_of_table
),
},
.
id_table
=
sh_cmt_id_table
,
};
...
...
drivers/clocksource/sh_mtu2.c
View file @
de2ea581
...
...
@@ -23,6 +23,7 @@
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
...
...
@@ -37,7 +38,6 @@ struct sh_mtu2_channel {
unsigned
int
index
;
void
__iomem
*
base
;
int
irq
;
struct
clock_event_device
ced
;
};
...
...
@@ -48,15 +48,14 @@ struct sh_mtu2_device {
void
__iomem
*
mapbase
;
struct
clk
*
clk
;
raw_spinlock_t
lock
;
/* Protect the shared registers */
struct
sh_mtu2_channel
*
channels
;
unsigned
int
num_channels
;
bool
legacy
;
bool
has_clockevent
;
};
static
DEFINE_RAW_SPINLOCK
(
sh_mtu2_lock
);
#define TSTR -1
/* shared register */
#define TCR 0
/* channel register */
#define TMDR 1
/* channel register */
...
...
@@ -162,12 +161,8 @@ static inline unsigned long sh_mtu2_read(struct sh_mtu2_channel *ch, int reg_nr)
{
unsigned
long
offs
;
if
(
reg_nr
==
TSTR
)
{
if
(
ch
->
mtu
->
legacy
)
return
ioread8
(
ch
->
mtu
->
mapbase
);
else
return
ioread8
(
ch
->
mtu
->
mapbase
+
0x280
);
}
if
(
reg_nr
==
TSTR
)
return
ioread8
(
ch
->
mtu
->
mapbase
+
0x280
);
offs
=
mtu2_reg_offs
[
reg_nr
];
...
...
@@ -182,12 +177,8 @@ static inline void sh_mtu2_write(struct sh_mtu2_channel *ch, int reg_nr,
{
unsigned
long
offs
;
if
(
reg_nr
==
TSTR
)
{
if
(
ch
->
mtu
->
legacy
)
return
iowrite8
(
value
,
ch
->
mtu
->
mapbase
);
else
return
iowrite8
(
value
,
ch
->
mtu
->
mapbase
+
0x280
);
}
if
(
reg_nr
==
TSTR
)
return
iowrite8
(
value
,
ch
->
mtu
->
mapbase
+
0x280
);
offs
=
mtu2_reg_offs
[
reg_nr
];
...
...
@@ -202,7 +193,7 @@ static void sh_mtu2_start_stop_ch(struct sh_mtu2_channel *ch, int start)
unsigned
long
flags
,
value
;
/* start stop register shared by multiple timer channels */
raw_spin_lock_irqsave
(
&
sh_mtu2_
lock
,
flags
);
raw_spin_lock_irqsave
(
&
ch
->
mtu
->
lock
,
flags
);
value
=
sh_mtu2_read
(
ch
,
TSTR
);
if
(
start
)
...
...
@@ -211,7 +202,7 @@ static void sh_mtu2_start_stop_ch(struct sh_mtu2_channel *ch, int start)
value
&=
~
(
1
<<
ch
->
index
);
sh_mtu2_write
(
ch
,
TSTR
,
value
);
raw_spin_unlock_irqrestore
(
&
sh_mtu2_
lock
,
flags
);
raw_spin_unlock_irqrestore
(
&
ch
->
mtu
->
lock
,
flags
);
}
static
int
sh_mtu2_enable
(
struct
sh_mtu2_channel
*
ch
)
...
...
@@ -331,7 +322,6 @@ static void sh_mtu2_register_clockevent(struct sh_mtu2_channel *ch,
const
char
*
name
)
{
struct
clock_event_device
*
ced
=
&
ch
->
ced
;
int
ret
;
ced
->
name
=
name
;
ced
->
features
=
CLOCK_EVT_FEAT_PERIODIC
;
...
...
@@ -344,24 +334,12 @@ static void sh_mtu2_register_clockevent(struct sh_mtu2_channel *ch,
dev_info
(
&
ch
->
mtu
->
pdev
->
dev
,
"ch%u: used for clock events
\n
"
,
ch
->
index
);
clockevents_register_device
(
ced
);
ret
=
request_irq
(
ch
->
irq
,
sh_mtu2_interrupt
,
IRQF_TIMER
|
IRQF_IRQPOLL
|
IRQF_NOBALANCING
,
dev_name
(
&
ch
->
mtu
->
pdev
->
dev
),
ch
);
if
(
ret
)
{
dev_err
(
&
ch
->
mtu
->
pdev
->
dev
,
"ch%u: failed to request irq %d
\n
"
,
ch
->
index
,
ch
->
irq
);
return
;
}
}
static
int
sh_mtu2_register
(
struct
sh_mtu2_channel
*
ch
,
const
char
*
name
,
bool
clockevent
)
static
int
sh_mtu2_register
(
struct
sh_mtu2_channel
*
ch
,
const
char
*
name
)
{
if
(
clockevent
)
{
ch
->
mtu
->
has_clockevent
=
true
;
sh_mtu2_register_clockevent
(
ch
,
name
);
}
ch
->
mtu
->
has_clockevent
=
true
;
sh_mtu2_register_clockevent
(
ch
,
name
);
return
0
;
}
...
...
@@ -372,40 +350,32 @@ static int sh_mtu2_setup_channel(struct sh_mtu2_channel *ch, unsigned int index,
static
const
unsigned
int
channel_offsets
[]
=
{
0x300
,
0x380
,
0x000
,
};
bool
clockevent
;
char
name
[
6
];
int
irq
;
int
ret
;
ch
->
mtu
=
mtu
;
if
(
mtu
->
legacy
)
{
struct
sh_timer_config
*
cfg
=
mtu
->
pdev
->
dev
.
platform_data
;
clockevent
=
cfg
->
clockevent_rating
!=
0
;
ch
->
irq
=
platform_get_irq
(
mtu
->
pdev
,
0
);
ch
->
base
=
mtu
->
mapbase
-
cfg
->
channel_offset
;
ch
->
index
=
cfg
->
timer_bit
;
}
else
{
char
name
[
6
];
clockevent
=
true
;
sprintf
(
name
,
"tgi%ua"
,
index
);
ch
->
irq
=
platform_get_irq_byname
(
mtu
->
pdev
,
name
);
ch
->
base
=
mtu
->
mapbase
+
channel_offsets
[
index
];
ch
->
index
=
index
;
}
if
(
ch
->
irq
<
0
)
{
sprintf
(
name
,
"tgi%ua"
,
index
);
irq
=
platform_get_irq_byname
(
mtu
->
pdev
,
name
);
if
(
irq
<
0
)
{
/* Skip channels with no declared interrupt. */
if
(
!
mtu
->
legacy
)
return
0
;
return
0
;
}
dev_err
(
&
mtu
->
pdev
->
dev
,
"ch%u: failed to get irq
\n
"
,
ch
->
index
);
return
ch
->
irq
;
ret
=
request_irq
(
irq
,
sh_mtu2_interrupt
,
IRQF_TIMER
|
IRQF_IRQPOLL
|
IRQF_NOBALANCING
,
dev_name
(
&
ch
->
mtu
->
pdev
->
dev
),
ch
);
if
(
ret
)
{
dev_err
(
&
ch
->
mtu
->
pdev
->
dev
,
"ch%u: failed to request irq %d
\n
"
,
index
,
irq
);
return
ret
;
}
return
sh_mtu2_register
(
ch
,
dev_name
(
&
mtu
->
pdev
->
dev
),
clockevent
);
ch
->
base
=
mtu
->
mapbase
+
channel_offsets
[
index
];
ch
->
index
=
index
;
return
sh_mtu2_register
(
ch
,
dev_name
(
&
mtu
->
pdev
->
dev
));
}
static
int
sh_mtu2_map_memory
(
struct
sh_mtu2_device
*
mtu
)
...
...
@@ -422,46 +392,21 @@ static int sh_mtu2_map_memory(struct sh_mtu2_device *mtu)
if
(
mtu
->
mapbase
==
NULL
)
return
-
ENXIO
;
/*
* In legacy platform device configuration (with one device per channel)
* the resource points to the channel base address.
*/
if
(
mtu
->
legacy
)
{
struct
sh_timer_config
*
cfg
=
mtu
->
pdev
->
dev
.
platform_data
;
mtu
->
mapbase
+=
cfg
->
channel_offset
;
}
return
0
;
}
static
void
sh_mtu2_unmap_memory
(
struct
sh_mtu2_device
*
mtu
)
{
if
(
mtu
->
legacy
)
{
struct
sh_timer_config
*
cfg
=
mtu
->
pdev
->
dev
.
platform_data
;
mtu
->
mapbase
-=
cfg
->
channel_offset
;
}
iounmap
(
mtu
->
mapbase
);
}
static
int
sh_mtu2_setup
(
struct
sh_mtu2_device
*
mtu
,
struct
platform_device
*
pdev
)
{
struct
sh_timer_config
*
cfg
=
pdev
->
dev
.
platform_data
;
const
struct
platform_device_id
*
id
=
pdev
->
id_entry
;
unsigned
int
i
;
int
ret
;
mtu
->
pdev
=
pdev
;
mtu
->
legacy
=
id
->
driver_data
;
if
(
mtu
->
legacy
&&
!
cfg
)
{
dev_err
(
&
mtu
->
pdev
->
dev
,
"missing platform data
\n
"
);
return
-
ENXIO
;
}
raw_spin_lock_init
(
&
mtu
->
lock
);
/* Get hold of clock. */
mtu
->
clk
=
clk_get
(
&
mtu
->
pdev
->
dev
,
mtu
->
legacy
?
"mtu2_fck"
:
"fck"
);
mtu
->
clk
=
clk_get
(
&
mtu
->
pdev
->
dev
,
"fck"
);
if
(
IS_ERR
(
mtu
->
clk
))
{
dev_err
(
&
mtu
->
pdev
->
dev
,
"cannot get clock
\n
"
);
return
PTR_ERR
(
mtu
->
clk
);
...
...
@@ -479,10 +424,7 @@ static int sh_mtu2_setup(struct sh_mtu2_device *mtu,
}
/* Allocate and setup the channels. */
if
(
mtu
->
legacy
)
mtu
->
num_channels
=
1
;
else
mtu
->
num_channels
=
3
;
mtu
->
num_channels
=
3
;
mtu
->
channels
=
kzalloc
(
sizeof
(
*
mtu
->
channels
)
*
mtu
->
num_channels
,
GFP_KERNEL
);
...
...
@@ -491,16 +433,10 @@ static int sh_mtu2_setup(struct sh_mtu2_device *mtu,
goto
err_unmap
;
}
if
(
mtu
->
legacy
)
{
ret
=
sh_mtu2_setup_channel
(
&
mtu
->
channels
[
0
],
0
,
mtu
);
for
(
i
=
0
;
i
<
mtu
->
num_channels
;
++
i
)
{
ret
=
sh_mtu2_setup_channel
(
&
mtu
->
channels
[
i
],
i
,
mtu
);
if
(
ret
<
0
)
goto
err_unmap
;
}
else
{
for
(
i
=
0
;
i
<
mtu
->
num_channels
;
++
i
)
{
ret
=
sh_mtu2_setup_channel
(
&
mtu
->
channels
[
i
],
i
,
mtu
);
if
(
ret
<
0
)
goto
err_unmap
;
}
}
platform_set_drvdata
(
pdev
,
mtu
);
...
...
@@ -509,7 +445,7 @@ static int sh_mtu2_setup(struct sh_mtu2_device *mtu,
err_unmap:
kfree
(
mtu
->
channels
);
sh_mtu2_unmap_memory
(
mtu
);
iounmap
(
mtu
->
mapbase
);
err_clk_unprepare:
clk_unprepare
(
mtu
->
clk
);
err_clk_put:
...
...
@@ -560,17 +496,23 @@ static int sh_mtu2_remove(struct platform_device *pdev)
}
static
const
struct
platform_device_id
sh_mtu2_id_table
[]
=
{
{
"sh_mtu2"
,
1
},
{
"sh-mtu2"
,
0
},
{
},
};
MODULE_DEVICE_TABLE
(
platform
,
sh_mtu2_id_table
);
static
const
struct
of_device_id
sh_mtu2_of_table
[]
__maybe_unused
=
{
{
.
compatible
=
"renesas,mtu2"
},
{
}
};
MODULE_DEVICE_TABLE
(
of
,
sh_mtu2_of_table
);
static
struct
platform_driver
sh_mtu2_device_driver
=
{
.
probe
=
sh_mtu2_probe
,
.
remove
=
sh_mtu2_remove
,
.
driver
=
{
.
name
=
"sh_mtu2"
,
.
of_match_table
=
of_match_ptr
(
sh_mtu2_of_table
),
},
.
id_table
=
sh_mtu2_id_table
,
};
...
...
drivers/clocksource/sh_tmu.c
View file @
de2ea581
...
...
@@ -24,6 +24,7 @@
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
...
...
@@ -32,7 +33,6 @@
#include <linux/spinlock.h>
enum
sh_tmu_model
{
SH_TMU_LEGACY
,
SH_TMU
,
SH_TMU_SH3
,
};
...
...
@@ -62,6 +62,8 @@ struct sh_tmu_device {
enum
sh_tmu_model
model
;
raw_spinlock_t
lock
;
/* Protect the shared start/stop register */
struct
sh_tmu_channel
*
channels
;
unsigned
int
num_channels
;
...
...
@@ -69,8 +71,6 @@ struct sh_tmu_device {
bool
has_clocksource
;
};
static
DEFINE_RAW_SPINLOCK
(
sh_tmu_lock
);
#define TSTR -1
/* shared register */
#define TCOR 0
/* channel register */
#define TCNT 1
/* channel register */
...
...
@@ -91,8 +91,6 @@ static inline unsigned long sh_tmu_read(struct sh_tmu_channel *ch, int reg_nr)
if
(
reg_nr
==
TSTR
)
{
switch
(
ch
->
tmu
->
model
)
{
case
SH_TMU_LEGACY
:
return
ioread8
(
ch
->
tmu
->
mapbase
);
case
SH_TMU_SH3
:
return
ioread8
(
ch
->
tmu
->
mapbase
+
2
);
case
SH_TMU
:
...
...
@@ -115,8 +113,6 @@ static inline void sh_tmu_write(struct sh_tmu_channel *ch, int reg_nr,
if
(
reg_nr
==
TSTR
)
{
switch
(
ch
->
tmu
->
model
)
{
case
SH_TMU_LEGACY
:
return
iowrite8
(
value
,
ch
->
tmu
->
mapbase
);
case
SH_TMU_SH3
:
return
iowrite8
(
value
,
ch
->
tmu
->
mapbase
+
2
);
case
SH_TMU
:
...
...
@@ -137,7 +133,7 @@ static void sh_tmu_start_stop_ch(struct sh_tmu_channel *ch, int start)
unsigned
long
flags
,
value
;
/* start stop register shared by multiple timer channels */
raw_spin_lock_irqsave
(
&
sh_tmu_
lock
,
flags
);
raw_spin_lock_irqsave
(
&
ch
->
tmu
->
lock
,
flags
);
value
=
sh_tmu_read
(
ch
,
TSTR
);
if
(
start
)
...
...
@@ -146,7 +142,7 @@ static void sh_tmu_start_stop_ch(struct sh_tmu_channel *ch, int start)
value
&=
~
(
1
<<
ch
->
index
);
sh_tmu_write
(
ch
,
TSTR
,
value
);
raw_spin_unlock_irqrestore
(
&
sh_tmu_
lock
,
flags
);
raw_spin_unlock_irqrestore
(
&
ch
->
tmu
->
lock
,
flags
);
}
static
int
__sh_tmu_enable
(
struct
sh_tmu_channel
*
ch
)
...
...
@@ -476,27 +472,12 @@ static int sh_tmu_channel_setup(struct sh_tmu_channel *ch, unsigned int index,
return
0
;
ch
->
tmu
=
tmu
;
ch
->
index
=
index
;
if
(
tmu
->
model
==
SH_TMU_LEGACY
)
{
struct
sh_timer_config
*
cfg
=
tmu
->
pdev
->
dev
.
platform_data
;
/*
* The SH3 variant (SH770x, SH7705, SH7710 and SH7720) maps
* channel registers blocks at base + 2 + 12 * index, while all
* other variants map them at base + 4 + 12 * index. We can
* compute the index by just dividing by 12, the 2 bytes or 4
* bytes offset being hidden by the integer division.
*/
ch
->
index
=
cfg
->
channel_offset
/
12
;
ch
->
base
=
tmu
->
mapbase
+
cfg
->
channel_offset
;
}
else
{
ch
->
index
=
index
;
if
(
tmu
->
model
==
SH_TMU_SH3
)
ch
->
base
=
tmu
->
mapbase
+
4
+
ch
->
index
*
12
;
else
ch
->
base
=
tmu
->
mapbase
+
8
+
ch
->
index
*
12
;
}
if
(
tmu
->
model
==
SH_TMU_SH3
)
ch
->
base
=
tmu
->
mapbase
+
4
+
ch
->
index
*
12
;
else
ch
->
base
=
tmu
->
mapbase
+
8
+
ch
->
index
*
12
;
ch
->
irq
=
platform_get_irq
(
tmu
->
pdev
,
index
);
if
(
ch
->
irq
<
0
)
{
...
...
@@ -526,46 +507,53 @@ static int sh_tmu_map_memory(struct sh_tmu_device *tmu)
if
(
tmu
->
mapbase
==
NULL
)
return
-
ENXIO
;
/*
* In legacy platform device configuration (with one device per channel)
* the resource points to the channel base address.
*/
if
(
tmu
->
model
==
SH_TMU_LEGACY
)
{
struct
sh_timer_config
*
cfg
=
tmu
->
pdev
->
dev
.
platform_data
;
tmu
->
mapbase
-=
cfg
->
channel_offset
;
}
return
0
;
}
static
void
sh_tmu_unmap_memory
(
struct
sh_tmu_device
*
tmu
)
static
int
sh_tmu_parse_dt
(
struct
sh_tmu_device
*
tmu
)
{
if
(
tmu
->
model
==
SH_TMU_LEGACY
)
{
struct
sh_timer_config
*
cfg
=
tmu
->
pdev
->
dev
.
platform_data
;
tmu
->
mapbase
+=
cfg
->
channel_offset
;
struct
device_node
*
np
=
tmu
->
pdev
->
dev
.
of_node
;
tmu
->
model
=
SH_TMU
;
tmu
->
num_channels
=
3
;
of_property_read_u32
(
np
,
"#renesas,channels"
,
&
tmu
->
num_channels
);
if
(
tmu
->
num_channels
!=
2
&&
tmu
->
num_channels
!=
3
)
{
dev_err
(
&
tmu
->
pdev
->
dev
,
"invalid number of channels %u
\n
"
,
tmu
->
num_channels
);
return
-
EINVAL
;
}
iounmap
(
tmu
->
mapbase
)
;
return
0
;
}
static
int
sh_tmu_setup
(
struct
sh_tmu_device
*
tmu
,
struct
platform_device
*
pdev
)
{
struct
sh_timer_config
*
cfg
=
pdev
->
dev
.
platform_data
;
const
struct
platform_device_id
*
id
=
pdev
->
id_entry
;
unsigned
int
i
;
int
ret
;
if
(
!
cfg
)
{
tmu
->
pdev
=
pdev
;
raw_spin_lock_init
(
&
tmu
->
lock
);
if
(
IS_ENABLED
(
CONFIG_OF
)
&&
pdev
->
dev
.
of_node
)
{
ret
=
sh_tmu_parse_dt
(
tmu
);
if
(
ret
<
0
)
return
ret
;
}
else
if
(
pdev
->
dev
.
platform_data
)
{
const
struct
platform_device_id
*
id
=
pdev
->
id_entry
;
struct
sh_timer_config
*
cfg
=
pdev
->
dev
.
platform_data
;
tmu
->
model
=
id
->
driver_data
;
tmu
->
num_channels
=
hweight8
(
cfg
->
channels_mask
);
}
else
{
dev_err
(
&
tmu
->
pdev
->
dev
,
"missing platform data
\n
"
);
return
-
ENXIO
;
}
tmu
->
pdev
=
pdev
;
tmu
->
model
=
id
->
driver_data
;
/* Get hold of clock. */
tmu
->
clk
=
clk_get
(
&
tmu
->
pdev
->
dev
,
tmu
->
model
==
SH_TMU_LEGACY
?
"tmu_fck"
:
"fck"
);
tmu
->
clk
=
clk_get
(
&
tmu
->
pdev
->
dev
,
"fck"
);
if
(
IS_ERR
(
tmu
->
clk
))
{
dev_err
(
&
tmu
->
pdev
->
dev
,
"cannot get clock
\n
"
);
return
PTR_ERR
(
tmu
->
clk
);
...
...
@@ -583,11 +571,6 @@ static int sh_tmu_setup(struct sh_tmu_device *tmu, struct platform_device *pdev)
}
/* Allocate and setup the channels. */
if
(
tmu
->
model
==
SH_TMU_LEGACY
)
tmu
->
num_channels
=
1
;
else
tmu
->
num_channels
=
hweight8
(
cfg
->
channels_mask
);
tmu
->
channels
=
kzalloc
(
sizeof
(
*
tmu
->
channels
)
*
tmu
->
num_channels
,
GFP_KERNEL
);
if
(
tmu
->
channels
==
NULL
)
{
...
...
@@ -595,23 +578,15 @@ static int sh_tmu_setup(struct sh_tmu_device *tmu, struct platform_device *pdev)
goto
err_unmap
;
}
if
(
tmu
->
model
==
SH_TMU_LEGACY
)
{
ret
=
sh_tmu_channel_setup
(
&
tmu
->
channels
[
0
],
0
,
cfg
->
clockevent_rating
!=
0
,
cfg
->
clocksource_rating
!=
0
,
tmu
);
/*
* Use the first channel as a clock event device and the second channel
* as a clock source.
*/
for
(
i
=
0
;
i
<
tmu
->
num_channels
;
++
i
)
{
ret
=
sh_tmu_channel_setup
(
&
tmu
->
channels
[
i
],
i
,
i
==
0
,
i
==
1
,
tmu
);
if
(
ret
<
0
)
goto
err_unmap
;
}
else
{
/*
* Use the first channel as a clock event device and the second
* channel as a clock source.
*/
for
(
i
=
0
;
i
<
tmu
->
num_channels
;
++
i
)
{
ret
=
sh_tmu_channel_setup
(
&
tmu
->
channels
[
i
],
i
,
i
==
0
,
i
==
1
,
tmu
);
if
(
ret
<
0
)
goto
err_unmap
;
}
}
platform_set_drvdata
(
pdev
,
tmu
);
...
...
@@ -620,7 +595,7 @@ static int sh_tmu_setup(struct sh_tmu_device *tmu, struct platform_device *pdev)
err_unmap:
kfree
(
tmu
->
channels
);
sh_tmu_unmap_memory
(
tmu
);
iounmap
(
tmu
->
mapbase
);
err_clk_unprepare:
clk_unprepare
(
tmu
->
clk
);
err_clk_put:
...
...
@@ -671,18 +646,24 @@ static int sh_tmu_remove(struct platform_device *pdev)
}
static
const
struct
platform_device_id
sh_tmu_id_table
[]
=
{
{
"sh_tmu"
,
SH_TMU_LEGACY
},
{
"sh-tmu"
,
SH_TMU
},
{
"sh-tmu-sh3"
,
SH_TMU_SH3
},
{
}
};
MODULE_DEVICE_TABLE
(
platform
,
sh_tmu_id_table
);
static
const
struct
of_device_id
sh_tmu_of_table
[]
__maybe_unused
=
{
{
.
compatible
=
"renesas,tmu"
},
{
}
};
MODULE_DEVICE_TABLE
(
of
,
sh_tmu_of_table
);
static
struct
platform_driver
sh_tmu_device_driver
=
{
.
probe
=
sh_tmu_probe
,
.
remove
=
sh_tmu_remove
,
.
driver
=
{
.
name
=
"sh_tmu"
,
.
of_match_table
=
of_match_ptr
(
sh_tmu_of_table
),
},
.
id_table
=
sh_tmu_id_table
,
};
...
...
include/linux/sh_timer.h
View file @
de2ea581
...
...
@@ -2,11 +2,6 @@
#define __SH_TIMER_H__
struct
sh_timer_config
{
char
*
name
;
long
channel_offset
;
int
timer_bit
;
unsigned
long
clockevent_rating
;
unsigned
long
clocksource_rating
;
unsigned
int
channels_mask
;
};
...
...
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