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
nexedi
linux
Commits
a8d99344
Commit
a8d99344
authored
Jan 05, 2016
by
Mark Brown
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branches 'regmap/topic/64bit' and 'regmap/topic/irq-type' into regmap-next
parents
8798975b
782035ea
7a78479f
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
239 additions
and
0 deletions
+239
-0
drivers/base/regmap/regcache.c
drivers/base/regmap/regcache.c
+21
-0
drivers/base/regmap/regmap-irq.c
drivers/base/regmap/regmap-irq.c
+100
-0
drivers/base/regmap/regmap.c
drivers/base/regmap/regmap.c
+102
-0
include/linux/regmap.h
include/linux/regmap.h
+16
-0
No files found.
drivers/base/regmap/regcache.c
View file @
a8d99344
...
@@ -543,19 +543,30 @@ bool regcache_set_val(struct regmap *map, void *base, unsigned int idx,
...
@@ -543,19 +543,30 @@ bool regcache_set_val(struct regmap *map, void *base, unsigned int idx,
switch
(
map
->
cache_word_size
)
{
switch
(
map
->
cache_word_size
)
{
case
1
:
{
case
1
:
{
u8
*
cache
=
base
;
u8
*
cache
=
base
;
cache
[
idx
]
=
val
;
cache
[
idx
]
=
val
;
break
;
break
;
}
}
case
2
:
{
case
2
:
{
u16
*
cache
=
base
;
u16
*
cache
=
base
;
cache
[
idx
]
=
val
;
cache
[
idx
]
=
val
;
break
;
break
;
}
}
case
4
:
{
case
4
:
{
u32
*
cache
=
base
;
u32
*
cache
=
base
;
cache
[
idx
]
=
val
;
break
;
}
#ifdef CONFIG_64BIT
case
8
:
{
u64
*
cache
=
base
;
cache
[
idx
]
=
val
;
cache
[
idx
]
=
val
;
break
;
break
;
}
}
#endif
default:
default:
BUG
();
BUG
();
}
}
...
@@ -576,16 +587,26 @@ unsigned int regcache_get_val(struct regmap *map, const void *base,
...
@@ -576,16 +587,26 @@ unsigned int regcache_get_val(struct regmap *map, const void *base,
switch
(
map
->
cache_word_size
)
{
switch
(
map
->
cache_word_size
)
{
case
1
:
{
case
1
:
{
const
u8
*
cache
=
base
;
const
u8
*
cache
=
base
;
return
cache
[
idx
];
return
cache
[
idx
];
}
}
case
2
:
{
case
2
:
{
const
u16
*
cache
=
base
;
const
u16
*
cache
=
base
;
return
cache
[
idx
];
return
cache
[
idx
];
}
}
case
4
:
{
case
4
:
{
const
u32
*
cache
=
base
;
const
u32
*
cache
=
base
;
return
cache
[
idx
];
}
#ifdef CONFIG_64BIT
case
8
:
{
const
u64
*
cache
=
base
;
return
cache
[
idx
];
return
cache
[
idx
];
}
}
#endif
default:
default:
BUG
();
BUG
();
}
}
...
...
drivers/base/regmap/regmap-irq.c
View file @
a8d99344
...
@@ -39,8 +39,11 @@ struct regmap_irq_chip_data {
...
@@ -39,8 +39,11 @@ struct regmap_irq_chip_data {
unsigned
int
*
mask_buf
;
unsigned
int
*
mask_buf
;
unsigned
int
*
mask_buf_def
;
unsigned
int
*
mask_buf_def
;
unsigned
int
*
wake_buf
;
unsigned
int
*
wake_buf
;
unsigned
int
*
type_buf
;
unsigned
int
*
type_buf_def
;
unsigned
int
irq_reg_stride
;
unsigned
int
irq_reg_stride
;
unsigned
int
type_reg_stride
;
};
};
static
inline
const
static
inline
const
...
@@ -144,6 +147,22 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
...
@@ -144,6 +147,22 @@ static void regmap_irq_sync_unlock(struct irq_data *data)
}
}
}
}
for
(
i
=
0
;
i
<
d
->
chip
->
num_type_reg
;
i
++
)
{
if
(
!
d
->
type_buf_def
[
i
])
continue
;
reg
=
d
->
chip
->
type_base
+
(
i
*
map
->
reg_stride
*
d
->
type_reg_stride
);
if
(
d
->
chip
->
type_invert
)
ret
=
regmap_update_bits
(
d
->
map
,
reg
,
d
->
type_buf_def
[
i
],
~
d
->
type_buf
[
i
]);
else
ret
=
regmap_update_bits
(
d
->
map
,
reg
,
d
->
type_buf_def
[
i
],
d
->
type_buf
[
i
]);
if
(
ret
!=
0
)
dev_err
(
d
->
map
->
dev
,
"Failed to sync type in %x
\n
"
,
reg
);
}
if
(
d
->
chip
->
runtime_pm
)
if
(
d
->
chip
->
runtime_pm
)
pm_runtime_put
(
map
->
dev
);
pm_runtime_put
(
map
->
dev
);
...
@@ -178,6 +197,38 @@ static void regmap_irq_disable(struct irq_data *data)
...
@@ -178,6 +197,38 @@ static void regmap_irq_disable(struct irq_data *data)
d
->
mask_buf
[
irq_data
->
reg_offset
/
map
->
reg_stride
]
|=
irq_data
->
mask
;
d
->
mask_buf
[
irq_data
->
reg_offset
/
map
->
reg_stride
]
|=
irq_data
->
mask
;
}
}
static
int
regmap_irq_set_type
(
struct
irq_data
*
data
,
unsigned
int
type
)
{
struct
regmap_irq_chip_data
*
d
=
irq_data_get_irq_chip_data
(
data
);
struct
regmap
*
map
=
d
->
map
;
const
struct
regmap_irq
*
irq_data
=
irq_to_regmap_irq
(
d
,
data
->
hwirq
);
int
reg
=
irq_data
->
type_reg_offset
/
map
->
reg_stride
;
if
(
!
(
irq_data
->
type_rising_mask
|
irq_data
->
type_falling_mask
))
return
0
;
d
->
type_buf
[
reg
]
&=
~
(
irq_data
->
type_falling_mask
|
irq_data
->
type_rising_mask
);
switch
(
type
)
{
case
IRQ_TYPE_EDGE_FALLING
:
d
->
type_buf
[
reg
]
|=
irq_data
->
type_falling_mask
;
break
;
case
IRQ_TYPE_EDGE_RISING
:
d
->
type_buf
[
reg
]
|=
irq_data
->
type_rising_mask
;
break
;
case
IRQ_TYPE_EDGE_BOTH
:
d
->
type_buf
[
reg
]
|=
(
irq_data
->
type_falling_mask
|
irq_data
->
type_rising_mask
);
break
;
default:
return
-
EINVAL
;
}
return
0
;
}
static
int
regmap_irq_set_wake
(
struct
irq_data
*
data
,
unsigned
int
on
)
static
int
regmap_irq_set_wake
(
struct
irq_data
*
data
,
unsigned
int
on
)
{
{
struct
regmap_irq_chip_data
*
d
=
irq_data_get_irq_chip_data
(
data
);
struct
regmap_irq_chip_data
*
d
=
irq_data_get_irq_chip_data
(
data
);
...
@@ -204,6 +255,7 @@ static const struct irq_chip regmap_irq_chip = {
...
@@ -204,6 +255,7 @@ static const struct irq_chip regmap_irq_chip = {
.
irq_bus_sync_unlock
=
regmap_irq_sync_unlock
,
.
irq_bus_sync_unlock
=
regmap_irq_sync_unlock
,
.
irq_disable
=
regmap_irq_disable
,
.
irq_disable
=
regmap_irq_disable
,
.
irq_enable
=
regmap_irq_enable
,
.
irq_enable
=
regmap_irq_enable
,
.
irq_set_type
=
regmap_irq_set_type
,
.
irq_set_wake
=
regmap_irq_set_wake
,
.
irq_set_wake
=
regmap_irq_set_wake
,
};
};
...
@@ -408,6 +460,18 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
...
@@ -408,6 +460,18 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
goto
err_alloc
;
goto
err_alloc
;
}
}
if
(
chip
->
num_type_reg
)
{
d
->
type_buf_def
=
kcalloc
(
chip
->
num_type_reg
,
sizeof
(
unsigned
int
),
GFP_KERNEL
);
if
(
!
d
->
type_buf_def
)
goto
err_alloc
;
d
->
type_buf
=
kcalloc
(
chip
->
num_type_reg
,
sizeof
(
unsigned
int
),
GFP_KERNEL
);
if
(
!
d
->
type_buf
)
goto
err_alloc
;
}
d
->
irq_chip
=
regmap_irq_chip
;
d
->
irq_chip
=
regmap_irq_chip
;
d
->
irq_chip
.
name
=
chip
->
name
;
d
->
irq_chip
.
name
=
chip
->
name
;
d
->
irq
=
irq
;
d
->
irq
=
irq
;
...
@@ -420,6 +484,11 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
...
@@ -420,6 +484,11 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
else
else
d
->
irq_reg_stride
=
1
;
d
->
irq_reg_stride
=
1
;
if
(
chip
->
type_reg_stride
)
d
->
type_reg_stride
=
chip
->
type_reg_stride
;
else
d
->
type_reg_stride
=
1
;
if
(
!
map
->
use_single_read
&&
map
->
reg_stride
==
1
&&
if
(
!
map
->
use_single_read
&&
map
->
reg_stride
==
1
&&
d
->
irq_reg_stride
==
1
)
{
d
->
irq_reg_stride
==
1
)
{
d
->
status_reg_buf
=
kmalloc_array
(
chip
->
num_regs
,
d
->
status_reg_buf
=
kmalloc_array
(
chip
->
num_regs
,
...
@@ -512,6 +581,33 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
...
@@ -512,6 +581,33 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
}
}
}
}
if
(
chip
->
num_type_reg
)
{
for
(
i
=
0
;
i
<
chip
->
num_irqs
;
i
++
)
{
reg
=
chip
->
irqs
[
i
].
type_reg_offset
/
map
->
reg_stride
;
d
->
type_buf_def
[
reg
]
|=
chip
->
irqs
[
i
].
type_rising_mask
|
chip
->
irqs
[
i
].
type_falling_mask
;
}
for
(
i
=
0
;
i
<
chip
->
num_type_reg
;
++
i
)
{
if
(
!
d
->
type_buf_def
[
i
])
continue
;
reg
=
chip
->
type_base
+
(
i
*
map
->
reg_stride
*
d
->
type_reg_stride
);
if
(
chip
->
type_invert
)
ret
=
regmap_update_bits
(
map
,
reg
,
d
->
type_buf_def
[
i
],
0xFF
);
else
ret
=
regmap_update_bits
(
map
,
reg
,
d
->
type_buf_def
[
i
],
0x0
);
if
(
ret
!=
0
)
{
dev_err
(
map
->
dev
,
"Failed to set type in 0x%x: %x
\n
"
,
reg
,
ret
);
goto
err_alloc
;
}
}
}
if
(
irq_base
)
if
(
irq_base
)
d
->
domain
=
irq_domain_add_legacy
(
map
->
dev
->
of_node
,
d
->
domain
=
irq_domain_add_legacy
(
map
->
dev
->
of_node
,
chip
->
num_irqs
,
irq_base
,
0
,
chip
->
num_irqs
,
irq_base
,
0
,
...
@@ -542,6 +638,8 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
...
@@ -542,6 +638,8 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
err_domain:
err_domain:
/* Should really dispose of the domain but... */
/* Should really dispose of the domain but... */
err_alloc:
err_alloc:
kfree
(
d
->
type_buf
);
kfree
(
d
->
type_buf_def
);
kfree
(
d
->
wake_buf
);
kfree
(
d
->
wake_buf
);
kfree
(
d
->
mask_buf_def
);
kfree
(
d
->
mask_buf_def
);
kfree
(
d
->
mask_buf
);
kfree
(
d
->
mask_buf
);
...
@@ -565,6 +663,8 @@ void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d)
...
@@ -565,6 +663,8 @@ void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d)
free_irq
(
irq
,
d
);
free_irq
(
irq
,
d
);
irq_domain_remove
(
d
->
domain
);
irq_domain_remove
(
d
->
domain
);
kfree
(
d
->
type_buf
);
kfree
(
d
->
type_buf_def
);
kfree
(
d
->
wake_buf
);
kfree
(
d
->
wake_buf
);
kfree
(
d
->
mask_buf_def
);
kfree
(
d
->
mask_buf_def
);
kfree
(
d
->
mask_buf
);
kfree
(
d
->
mask_buf
);
...
...
drivers/base/regmap/regmap.c
View file @
a8d99344
...
@@ -245,6 +245,28 @@ static void regmap_format_32_native(void *buf, unsigned int val,
...
@@ -245,6 +245,28 @@ static void regmap_format_32_native(void *buf, unsigned int val,
*
(
u32
*
)
buf
=
val
<<
shift
;
*
(
u32
*
)
buf
=
val
<<
shift
;
}
}
#ifdef CONFIG_64BIT
static
void
regmap_format_64_be
(
void
*
buf
,
unsigned
int
val
,
unsigned
int
shift
)
{
__be64
*
b
=
buf
;
b
[
0
]
=
cpu_to_be64
((
u64
)
val
<<
shift
);
}
static
void
regmap_format_64_le
(
void
*
buf
,
unsigned
int
val
,
unsigned
int
shift
)
{
__le64
*
b
=
buf
;
b
[
0
]
=
cpu_to_le64
((
u64
)
val
<<
shift
);
}
static
void
regmap_format_64_native
(
void
*
buf
,
unsigned
int
val
,
unsigned
int
shift
)
{
*
(
u64
*
)
buf
=
(
u64
)
val
<<
shift
;
}
#endif
static
void
regmap_parse_inplace_noop
(
void
*
buf
)
static
void
regmap_parse_inplace_noop
(
void
*
buf
)
{
{
}
}
...
@@ -332,6 +354,41 @@ static unsigned int regmap_parse_32_native(const void *buf)
...
@@ -332,6 +354,41 @@ static unsigned int regmap_parse_32_native(const void *buf)
return
*
(
u32
*
)
buf
;
return
*
(
u32
*
)
buf
;
}
}
#ifdef CONFIG_64BIT
static
unsigned
int
regmap_parse_64_be
(
const
void
*
buf
)
{
const
__be64
*
b
=
buf
;
return
be64_to_cpu
(
b
[
0
]);
}
static
unsigned
int
regmap_parse_64_le
(
const
void
*
buf
)
{
const
__le64
*
b
=
buf
;
return
le64_to_cpu
(
b
[
0
]);
}
static
void
regmap_parse_64_be_inplace
(
void
*
buf
)
{
__be64
*
b
=
buf
;
b
[
0
]
=
be64_to_cpu
(
b
[
0
]);
}
static
void
regmap_parse_64_le_inplace
(
void
*
buf
)
{
__le64
*
b
=
buf
;
b
[
0
]
=
le64_to_cpu
(
b
[
0
]);
}
static
unsigned
int
regmap_parse_64_native
(
const
void
*
buf
)
{
return
*
(
u64
*
)
buf
;
}
#endif
static
void
regmap_lock_mutex
(
void
*
__map
)
static
void
regmap_lock_mutex
(
void
*
__map
)
{
{
struct
regmap
*
map
=
__map
;
struct
regmap
*
map
=
__map
;
...
@@ -712,6 +769,21 @@ struct regmap *__regmap_init(struct device *dev,
...
@@ -712,6 +769,21 @@ struct regmap *__regmap_init(struct device *dev,
}
}
break
;
break
;
#ifdef CONFIG_64BIT
case
64
:
switch
(
reg_endian
)
{
case
REGMAP_ENDIAN_BIG
:
map
->
format
.
format_reg
=
regmap_format_64_be
;
break
;
case
REGMAP_ENDIAN_NATIVE
:
map
->
format
.
format_reg
=
regmap_format_64_native
;
break
;
default:
goto
err_map
;
}
break
;
#endif
default:
default:
goto
err_map
;
goto
err_map
;
}
}
...
@@ -771,6 +843,28 @@ struct regmap *__regmap_init(struct device *dev,
...
@@ -771,6 +843,28 @@ struct regmap *__regmap_init(struct device *dev,
goto
err_map
;
goto
err_map
;
}
}
break
;
break
;
#ifdef CONFIG_64BIT
case
64
:
switch
(
val_endian
)
{
case
REGMAP_ENDIAN_BIG
:
map
->
format
.
format_val
=
regmap_format_64_be
;
map
->
format
.
parse_val
=
regmap_parse_64_be
;
map
->
format
.
parse_inplace
=
regmap_parse_64_be_inplace
;
break
;
case
REGMAP_ENDIAN_LITTLE
:
map
->
format
.
format_val
=
regmap_format_64_le
;
map
->
format
.
parse_val
=
regmap_parse_64_le
;
map
->
format
.
parse_inplace
=
regmap_parse_64_le_inplace
;
break
;
case
REGMAP_ENDIAN_NATIVE
:
map
->
format
.
format_val
=
regmap_format_64_native
;
map
->
format
.
parse_val
=
regmap_parse_64_native
;
break
;
default:
goto
err_map
;
}
break
;
#endif
}
}
if
(
map
->
format
.
format_write
)
{
if
(
map
->
format
.
format_write
)
{
...
@@ -2488,11 +2582,19 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val,
...
@@ -2488,11 +2582,19 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val,
* we assume that the values are native
* we assume that the values are native
* endian.
* endian.
*/
*/
#ifdef CONFIG_64BIT
u64
*
u64
=
val
;
#endif
u32
*
u32
=
val
;
u32
*
u32
=
val
;
u16
*
u16
=
val
;
u16
*
u16
=
val
;
u8
*
u8
=
val
;
u8
*
u8
=
val
;
switch
(
map
->
format
.
val_bytes
)
{
switch
(
map
->
format
.
val_bytes
)
{
#ifdef CONFIG_64BIT
case
8
:
u64
[
i
]
=
ival
;
break
;
#endif
case
4
:
case
4
:
u32
[
i
]
=
ival
;
u32
[
i
]
=
ival
;
break
;
break
;
...
...
include/linux/regmap.h
View file @
a8d99344
...
@@ -788,10 +788,16 @@ int regmap_fields_update_bits(struct regmap_field *field, unsigned int id,
...
@@ -788,10 +788,16 @@ int regmap_fields_update_bits(struct regmap_field *field, unsigned int id,
*
*
* @reg_offset: Offset of the status/mask register within the bank
* @reg_offset: Offset of the status/mask register within the bank
* @mask: Mask used to flag/control the register.
* @mask: Mask used to flag/control the register.
* @type_reg_offset: Offset register for the irq type setting.
* @type_rising_mask: Mask bit to configure RISING type irq.
* @type_falling_mask: Mask bit to configure FALLING type irq.
*/
*/
struct
regmap_irq
{
struct
regmap_irq
{
unsigned
int
reg_offset
;
unsigned
int
reg_offset
;
unsigned
int
mask
;
unsigned
int
mask
;
unsigned
int
type_reg_offset
;
unsigned
int
type_rising_mask
;
unsigned
int
type_falling_mask
;
};
};
#define REGMAP_IRQ_REG(_irq, _off, _mask) \
#define REGMAP_IRQ_REG(_irq, _off, _mask) \
...
@@ -811,18 +817,23 @@ struct regmap_irq {
...
@@ -811,18 +817,23 @@ struct regmap_irq {
* @ack_base: Base ack address. If zero then the chip is clear on read.
* @ack_base: Base ack address. If zero then the chip is clear on read.
* Using zero value is possible with @use_ack bit.
* Using zero value is possible with @use_ack bit.
* @wake_base: Base address for wake enables. If zero unsupported.
* @wake_base: Base address for wake enables. If zero unsupported.
* @type_base: Base address for irq type. If zero unsupported.
* @irq_reg_stride: Stride to use for chips where registers are not contiguous.
* @irq_reg_stride: Stride to use for chips where registers are not contiguous.
* @init_ack_masked: Ack all masked interrupts once during initalization.
* @init_ack_masked: Ack all masked interrupts once during initalization.
* @mask_invert: Inverted mask register: cleared bits are masked out.
* @mask_invert: Inverted mask register: cleared bits are masked out.
* @use_ack: Use @ack register even if it is zero.
* @use_ack: Use @ack register even if it is zero.
* @ack_invert: Inverted ack register: cleared bits for ack.
* @ack_invert: Inverted ack register: cleared bits for ack.
* @wake_invert: Inverted wake register: cleared bits are wake enabled.
* @wake_invert: Inverted wake register: cleared bits are wake enabled.
* @type_invert: Invert the type flags.
* @runtime_pm: Hold a runtime PM lock on the device when accessing it.
* @runtime_pm: Hold a runtime PM lock on the device when accessing it.
*
*
* @num_regs: Number of registers in each control bank.
* @num_regs: Number of registers in each control bank.
* @irqs: Descriptors for individual IRQs. Interrupt numbers are
* @irqs: Descriptors for individual IRQs. Interrupt numbers are
* assigned based on the index in the array of the interrupt.
* assigned based on the index in the array of the interrupt.
* @num_irqs: Number of descriptors.
* @num_irqs: Number of descriptors.
* @num_type_reg: Number of type registers.
* @type_reg_stride: Stride to use for chips where type registers are not
* contiguous.
*/
*/
struct
regmap_irq_chip
{
struct
regmap_irq_chip
{
const
char
*
name
;
const
char
*
name
;
...
@@ -832,6 +843,7 @@ struct regmap_irq_chip {
...
@@ -832,6 +843,7 @@ struct regmap_irq_chip {
unsigned
int
unmask_base
;
unsigned
int
unmask_base
;
unsigned
int
ack_base
;
unsigned
int
ack_base
;
unsigned
int
wake_base
;
unsigned
int
wake_base
;
unsigned
int
type_base
;
unsigned
int
irq_reg_stride
;
unsigned
int
irq_reg_stride
;
bool
init_ack_masked
:
1
;
bool
init_ack_masked
:
1
;
bool
mask_invert
:
1
;
bool
mask_invert
:
1
;
...
@@ -839,11 +851,15 @@ struct regmap_irq_chip {
...
@@ -839,11 +851,15 @@ struct regmap_irq_chip {
bool
ack_invert
:
1
;
bool
ack_invert
:
1
;
bool
wake_invert
:
1
;
bool
wake_invert
:
1
;
bool
runtime_pm
:
1
;
bool
runtime_pm
:
1
;
bool
type_invert
:
1
;
int
num_regs
;
int
num_regs
;
const
struct
regmap_irq
*
irqs
;
const
struct
regmap_irq
*
irqs
;
int
num_irqs
;
int
num_irqs
;
int
num_type_reg
;
unsigned
int
type_reg_stride
;
};
};
struct
regmap_irq_chip_data
;
struct
regmap_irq_chip_data
;
...
...
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