Commit fde35c9c authored by Chris Brandt's avatar Chris Brandt Committed by Geert Uytterhoeven

clk: renesas: cpg-mssr: Add R7S9210 support

Add support for the R7S9210 (RZ/A2) Clock Pulse Generator and Module
Standby.

The Module Standby HW in the RZ/A series is very close to R-Car HW, except
for how the registers are laid out.
The MSTP registers are only 8-bits wide, there are no status registers
(MSTPSR), and the register offsets are a little different. Since the RZ/A
hardware manuals refer to these registers as the Standby Control Registers,
we'll use that name to distinguish the RZ/A type from the R-Car type.
Signed-off-by: default avatarChris Brandt <chris.brandt@renesas.com>
Acked-by: Rob Herring <robh@kernel.org> # DT bits
Signed-off-by: default avatarGeert Uytterhoeven <geert+renesas@glider.be>
parent 6207ba04
...@@ -13,6 +13,7 @@ They provide the following functionalities: ...@@ -13,6 +13,7 @@ They provide the following functionalities:
Required Properties: Required Properties:
- compatible: Must be one of: - compatible: Must be one of:
- "renesas,r7s9210-cpg-mssr" for the r7s9210 SoC (RZ/A2)
- "renesas,r8a7743-cpg-mssr" for the r8a7743 SoC (RZ/G1M) - "renesas,r8a7743-cpg-mssr" for the r8a7743 SoC (RZ/G1M)
- "renesas,r8a7745-cpg-mssr" for the r8a7745 SoC (RZ/G1E) - "renesas,r8a7745-cpg-mssr" for the r8a7745 SoC (RZ/G1E)
- "renesas,r8a77470-cpg-mssr" for the r8a77470 SoC (RZ/G1C) - "renesas,r8a77470-cpg-mssr" for the r8a77470 SoC (RZ/G1C)
...@@ -36,8 +37,8 @@ Required Properties: ...@@ -36,8 +37,8 @@ Required Properties:
- clocks: References to external parent clocks, one entry for each entry in - clocks: References to external parent clocks, one entry for each entry in
clock-names clock-names
- clock-names: List of external parent clock names. Valid names are: - clock-names: List of external parent clock names. Valid names are:
- "extal" (r8a7743, r8a7745, r8a77470, r8a774a1, r8a7790, r8a7791, - "extal" (r7s9210, r8a7743, r8a7745, r8a77470, r8a774a1, r8a7790,
r8a7792, r8a7793, r8a7794, r8a7795, r8a7796, r8a77965, r8a7791, r8a7792, r8a7793, r8a7794, r8a7795, r8a7796, r8a77965,
r8a77970, r8a77980, r8a77990, r8a77995) r8a77970, r8a77980, r8a77990, r8a77995)
- "extalr" (r8a774a1, r8a7795, r8a7796, r8a77965, r8a77970, r8a77980) - "extalr" (r8a774a1, r8a7795, r8a7796, r8a77965, r8a77970, r8a77980)
- "usb_extal" (r8a7743, r8a7745, r8a77470, r8a7790, r8a7791, r8a7793, - "usb_extal" (r8a7743, r8a7745, r8a77470, r8a7790, r8a7791, r8a7793,
......
...@@ -3,6 +3,7 @@ config CLK_RENESAS ...@@ -3,6 +3,7 @@ config CLK_RENESAS
default y if ARCH_RENESAS default y if ARCH_RENESAS
select CLK_EMEV2 if ARCH_EMEV2 select CLK_EMEV2 if ARCH_EMEV2
select CLK_RZA1 if ARCH_R7S72100 select CLK_RZA1 if ARCH_R7S72100
select CLK_R7S9210 if ARCH_R7S9210
select CLK_R8A73A4 if ARCH_R8A73A4 select CLK_R8A73A4 if ARCH_R8A73A4
select CLK_R8A7740 if ARCH_R8A7740 select CLK_R8A7740 if ARCH_R8A7740
select CLK_R8A7743 if ARCH_R8A7743 select CLK_R8A7743 if ARCH_R8A7743
...@@ -46,6 +47,10 @@ config CLK_RZA1 ...@@ -46,6 +47,10 @@ config CLK_RZA1
bool "RZ/A1H clock support" if COMPILE_TEST bool "RZ/A1H clock support" if COMPILE_TEST
select CLK_RENESAS_CPG_MSTP select CLK_RENESAS_CPG_MSTP
config CLK_R7S9210
bool "RZ/A2 clock support" if COMPILE_TEST
select CLK_RENESAS_CPG_MSSR
config CLK_R8A73A4 config CLK_R8A73A4
bool "R-Mobile APE6 clock support" if COMPILE_TEST bool "R-Mobile APE6 clock support" if COMPILE_TEST
select CLK_RENESAS_CPG_MSTP select CLK_RENESAS_CPG_MSTP
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# SoC # SoC
obj-$(CONFIG_CLK_EMEV2) += clk-emev2.o obj-$(CONFIG_CLK_EMEV2) += clk-emev2.o
obj-$(CONFIG_CLK_RZA1) += clk-rz.o obj-$(CONFIG_CLK_RZA1) += clk-rz.o
obj-$(CONFIG_CLK_R7S9210) += r7s9210-cpg-mssr.o
obj-$(CONFIG_CLK_R8A73A4) += clk-r8a73a4.o obj-$(CONFIG_CLK_R8A73A4) += clk-r8a73a4.o
obj-$(CONFIG_CLK_R8A7740) += clk-r8a7740.o obj-$(CONFIG_CLK_R8A7740) += clk-r8a7740.o
obj-$(CONFIG_CLK_R8A7743) += r8a7743-cpg-mssr.o obj-$(CONFIG_CLK_R8A7743) += r8a7743-cpg-mssr.o
......
// SPDX-License-Identifier: GPL-2.0
/*
* R7S9210 Clock Pulse Generator / Module Standby
*
* Based on r8a7795-cpg-mssr.c
*
* Copyright (C) 2018 Chris Brandt
* Copyright (C) 2018 Renesas Electronics Corp.
*
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <dt-bindings/clock/r7s9210-cpg-mssr.h>
#include "renesas-cpg-mssr.h"
#define CPG_FRQCR 0x00
static u8 cpg_mode;
/* Internal Clock ratio table */
static const struct {
unsigned int i;
unsigned int g;
unsigned int b;
unsigned int p1;
/* p0 is always 32 */;
} ratio_tab[5] = { /* I, G, B, P1 */
{ 2, 4, 8, 16}, /* FRQCR = 0x012 */
{ 4, 4, 8, 16}, /* FRQCR = 0x112 */
{ 8, 4, 8, 16}, /* FRQCR = 0x212 */
{ 16, 8, 16, 16}, /* FRQCR = 0x322 */
{ 16, 16, 32, 32}, /* FRQCR = 0x333 */
};
enum rz_clk_types {
CLK_TYPE_RZA_MAIN = CLK_TYPE_CUSTOM,
CLK_TYPE_RZA_PLL,
};
enum clk_ids {
/* Core Clock Outputs exported to DT */
LAST_DT_CORE_CLK = R7S9210_CLK_P0,
/* External Input Clocks */
CLK_EXTAL,
/* Internal Core Clocks */
CLK_MAIN,
CLK_PLL,
/* Module Clocks */
MOD_CLK_BASE
};
static struct cpg_core_clk r7s9210_core_clks[] = {
/* External Clock Inputs */
DEF_INPUT("extal", CLK_EXTAL),
/* Internal Core Clocks */
DEF_BASE(".main", CLK_MAIN, CLK_TYPE_RZA_MAIN, CLK_EXTAL),
DEF_BASE(".pll", CLK_PLL, CLK_TYPE_RZA_PLL, CLK_MAIN),
/* Core Clock Outputs */
DEF_FIXED("i", R7S9210_CLK_I, CLK_PLL, 2, 1),
DEF_FIXED("g", R7S9210_CLK_G, CLK_PLL, 4, 1),
DEF_FIXED("b", R7S9210_CLK_B, CLK_PLL, 8, 1),
DEF_FIXED("p1", R7S9210_CLK_P1, CLK_PLL, 16, 1),
DEF_FIXED("p1c", R7S9210_CLK_P1C, CLK_PLL, 16, 1),
DEF_FIXED("p0", R7S9210_CLK_P0, CLK_PLL, 32, 1),
};
static const struct mssr_mod_clk r7s9210_mod_clks[] __initconst = {
DEF_MOD_STB("ostm2", 34, R7S9210_CLK_P1C),
DEF_MOD_STB("ostm1", 35, R7S9210_CLK_P1C),
DEF_MOD_STB("ostm0", 36, R7S9210_CLK_P1C),
DEF_MOD_STB("scif4", 43, R7S9210_CLK_P1C),
DEF_MOD_STB("scif3", 44, R7S9210_CLK_P1C),
DEF_MOD_STB("scif2", 45, R7S9210_CLK_P1C),
DEF_MOD_STB("scif1", 46, R7S9210_CLK_P1C),
DEF_MOD_STB("scif0", 47, R7S9210_CLK_P1C),
DEF_MOD_STB("ether1", 64, R7S9210_CLK_B),
DEF_MOD_STB("ether0", 65, R7S9210_CLK_B),
DEF_MOD_STB("i2c3", 84, R7S9210_CLK_P1),
DEF_MOD_STB("i2c2", 85, R7S9210_CLK_P1),
DEF_MOD_STB("i2c1", 86, R7S9210_CLK_P1),
DEF_MOD_STB("i2c0", 87, R7S9210_CLK_P1),
};
struct clk * __init rza2_cpg_clk_register(struct device *dev,
const struct cpg_core_clk *core, const struct cpg_mssr_info *info,
struct clk **clks, void __iomem *base,
struct raw_notifier_head *notifiers)
{
struct clk *parent;
unsigned int mult = 1;
unsigned int div = 1;
u16 frqcr;
u8 index;
int i;
parent = clks[core->parent];
if (IS_ERR(parent))
return ERR_CAST(parent);
switch (core->id) {
case CLK_MAIN:
break;
case CLK_PLL:
if (cpg_mode)
mult = 44; /* Divider 1 is 1/2 */
else
mult = 88; /* Divider 1 is 1 */
break;
default:
return ERR_PTR(-EINVAL);
}
/* Adjust the dividers based on the current FRQCR setting */
if (core->id == CLK_MAIN) {
/* If EXTAL is above 12MHz, then we know it is Mode 1 */
if (clk_get_rate(parent) > 12000000)
cpg_mode = 1;
frqcr = clk_readl(base + CPG_FRQCR) & 0xFFF;
if (frqcr == 0x012)
index = 0;
else if (frqcr == 0x112)
index = 1;
else if (frqcr == 0x212)
index = 2;
else if (frqcr == 0x322)
index = 3;
else if (frqcr == 0x333)
index = 4;
else
BUG_ON(1); /* Illegal FRQCR value */
for (i = 0; i < ARRAY_SIZE(r7s9210_core_clks); i++) {
switch (r7s9210_core_clks[i].id) {
case R7S9210_CLK_I:
r7s9210_core_clks[i].div = ratio_tab[index].i;
break;
case R7S9210_CLK_G:
r7s9210_core_clks[i].div = ratio_tab[index].g;
break;
case R7S9210_CLK_B:
r7s9210_core_clks[i].div = ratio_tab[index].b;
break;
case R7S9210_CLK_P1:
case R7S9210_CLK_P1C:
r7s9210_core_clks[i].div = ratio_tab[index].p1;
break;
case R7S9210_CLK_P0:
r7s9210_core_clks[i].div = 32;
break;
}
}
}
return clk_register_fixed_factor(NULL, core->name,
__clk_get_name(parent), 0, mult, div);
}
const struct cpg_mssr_info r7s9210_cpg_mssr_info __initconst = {
/* Core Clocks */
.core_clks = r7s9210_core_clks,
.num_core_clks = ARRAY_SIZE(r7s9210_core_clks),
.last_dt_core_clk = LAST_DT_CORE_CLK,
.num_total_core_clks = MOD_CLK_BASE,
/* Module Clocks */
.mod_clks = r7s9210_mod_clks,
.num_mod_clks = ARRAY_SIZE(r7s9210_mod_clks),
.num_hw_mod_clks = 11 * 32, /* includes STBCR0 which doesn't exist */
/* Callbacks */
.cpg_clk_register = rza2_cpg_clk_register,
/* RZ/A2 has Standby Control Registers */
.stbyctrl = true,
};
...@@ -73,6 +73,17 @@ static const u16 smstpcr[] = { ...@@ -73,6 +73,17 @@ static const u16 smstpcr[] = {
#define SMSTPCR(i) smstpcr[i] #define SMSTPCR(i) smstpcr[i]
/*
* Standby Control Register offsets (RZ/A)
* Base address is FRQCR register
*/
static const u16 stbcr[] = {
0xFFFF/*dummy*/, 0x010, 0x014, 0x410, 0x414, 0x418, 0x41C, 0x420,
0x424, 0x428, 0x42C,
};
#define STBCR(i) stbcr[i]
/* /*
* Software Reset Register offsets * Software Reset Register offsets
...@@ -110,6 +121,7 @@ static const u16 srcr[] = { ...@@ -110,6 +121,7 @@ static const u16 srcr[] = {
* @notifiers: Notifier chain to save/restore clock state for system resume * @notifiers: Notifier chain to save/restore clock state for system resume
* @smstpcr_saved[].mask: Mask of SMSTPCR[] bits under our control * @smstpcr_saved[].mask: Mask of SMSTPCR[] bits under our control
* @smstpcr_saved[].val: Saved values of SMSTPCR[] * @smstpcr_saved[].val: Saved values of SMSTPCR[]
* @stbyctrl: This device has Standby Control Registers
*/ */
struct cpg_mssr_priv { struct cpg_mssr_priv {
#ifdef CONFIG_RESET_CONTROLLER #ifdef CONFIG_RESET_CONTROLLER
...@@ -123,6 +135,7 @@ struct cpg_mssr_priv { ...@@ -123,6 +135,7 @@ struct cpg_mssr_priv {
unsigned int num_core_clks; unsigned int num_core_clks;
unsigned int num_mod_clks; unsigned int num_mod_clks;
unsigned int last_dt_core_clk; unsigned int last_dt_core_clk;
bool stbyctrl;
struct raw_notifier_head notifiers; struct raw_notifier_head notifiers;
struct { struct {
...@@ -162,16 +175,29 @@ static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable) ...@@ -162,16 +175,29 @@ static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable)
enable ? "ON" : "OFF"); enable ? "ON" : "OFF");
spin_lock_irqsave(&priv->rmw_lock, flags); spin_lock_irqsave(&priv->rmw_lock, flags);
if (priv->stbyctrl) {
value = readb(priv->base + STBCR(reg));
if (enable)
value &= ~bitmask;
else
value |= bitmask;
writeb(value, priv->base + STBCR(reg));
/* dummy read to ensure write has completed */
readb(priv->base + STBCR(reg));
barrier_data(priv->base + STBCR(reg));
} else {
value = readl(priv->base + SMSTPCR(reg)); value = readl(priv->base + SMSTPCR(reg));
if (enable) if (enable)
value &= ~bitmask; value &= ~bitmask;
else else
value |= bitmask; value |= bitmask;
writel(value, priv->base + SMSTPCR(reg)); writel(value, priv->base + SMSTPCR(reg));
}
spin_unlock_irqrestore(&priv->rmw_lock, flags); spin_unlock_irqrestore(&priv->rmw_lock, flags);
if (!enable) if (!enable || priv->stbyctrl)
return 0; return 0;
for (i = 1000; i > 0; --i) { for (i = 1000; i > 0; --i) {
...@@ -205,6 +231,9 @@ static int cpg_mstp_clock_is_enabled(struct clk_hw *hw) ...@@ -205,6 +231,9 @@ static int cpg_mstp_clock_is_enabled(struct clk_hw *hw)
struct cpg_mssr_priv *priv = clock->priv; struct cpg_mssr_priv *priv = clock->priv;
u32 value; u32 value;
if (priv->stbyctrl)
value = readb(priv->base + STBCR(clock->index / 32));
else
value = readl(priv->base + MSTPSR(clock->index / 32)); value = readl(priv->base + MSTPSR(clock->index / 32));
return !(value & BIT(clock->index % 32)); return !(value & BIT(clock->index % 32));
...@@ -226,6 +255,7 @@ struct clk *cpg_mssr_clk_src_twocell_get(struct of_phandle_args *clkspec, ...@@ -226,6 +255,7 @@ struct clk *cpg_mssr_clk_src_twocell_get(struct of_phandle_args *clkspec,
unsigned int idx; unsigned int idx;
const char *type; const char *type;
struct clk *clk; struct clk *clk;
int range_check;
switch (clkspec->args[0]) { switch (clkspec->args[0]) {
case CPG_CORE: case CPG_CORE:
...@@ -240,8 +270,14 @@ struct clk *cpg_mssr_clk_src_twocell_get(struct of_phandle_args *clkspec, ...@@ -240,8 +270,14 @@ struct clk *cpg_mssr_clk_src_twocell_get(struct of_phandle_args *clkspec,
case CPG_MOD: case CPG_MOD:
type = "module"; type = "module";
if (priv->stbyctrl) {
idx = MOD_CLK_PACK_10(clkidx);
range_check = 7 - (clkidx % 10);
} else {
idx = MOD_CLK_PACK(clkidx); idx = MOD_CLK_PACK(clkidx);
if (clkidx % 100 > 31 || idx >= priv->num_mod_clks) { range_check = 31 - (clkidx % 100);
}
if (range_check < 0 || idx >= priv->num_mod_clks) {
dev_err(dev, "Invalid %s clock index %u\n", type, dev_err(dev, "Invalid %s clock index %u\n", type,
clkidx); clkidx);
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
...@@ -646,6 +682,12 @@ static inline int cpg_mssr_reset_controller_register(struct cpg_mssr_priv *priv) ...@@ -646,6 +682,12 @@ static inline int cpg_mssr_reset_controller_register(struct cpg_mssr_priv *priv)
static const struct of_device_id cpg_mssr_match[] = { static const struct of_device_id cpg_mssr_match[] = {
#ifdef CONFIG_CLK_R7S9210
{
.compatible = "renesas,r7s9210-cpg-mssr",
.data = &r7s9210_cpg_mssr_info,
},
#endif
#ifdef CONFIG_CLK_R8A7743 #ifdef CONFIG_CLK_R8A7743
{ {
.compatible = "renesas,r8a7743-cpg-mssr", .compatible = "renesas,r8a7743-cpg-mssr",
...@@ -791,12 +833,22 @@ static int cpg_mssr_resume_noirq(struct device *dev) ...@@ -791,12 +833,22 @@ static int cpg_mssr_resume_noirq(struct device *dev)
if (!mask) if (!mask)
continue; continue;
if (priv->stbyctrl)
oldval = readb(priv->base + STBCR(reg));
else
oldval = readl(priv->base + SMSTPCR(reg)); oldval = readl(priv->base + SMSTPCR(reg));
newval = oldval & ~mask; newval = oldval & ~mask;
newval |= priv->smstpcr_saved[reg].val & mask; newval |= priv->smstpcr_saved[reg].val & mask;
if (newval == oldval) if (newval == oldval)
continue; continue;
if (priv->stbyctrl) {
writeb(newval, priv->base + STBCR(reg));
/* dummy read to ensure write has completed */
readb(priv->base + STBCR(reg));
barrier_data(priv->base + STBCR(reg));
continue;
} else
writel(newval, priv->base + SMSTPCR(reg)); writel(newval, priv->base + SMSTPCR(reg));
/* Wait until enabled clocks are really enabled */ /* Wait until enabled clocks are really enabled */
...@@ -869,6 +921,7 @@ static int __init cpg_mssr_probe(struct platform_device *pdev) ...@@ -869,6 +921,7 @@ static int __init cpg_mssr_probe(struct platform_device *pdev)
priv->num_mod_clks = info->num_hw_mod_clks; priv->num_mod_clks = info->num_hw_mod_clks;
priv->last_dt_core_clk = info->last_dt_core_clk; priv->last_dt_core_clk = info->last_dt_core_clk;
RAW_INIT_NOTIFIER_HEAD(&priv->notifiers); RAW_INIT_NOTIFIER_HEAD(&priv->notifiers);
priv->stbyctrl = info->stbyctrl;
for (i = 0; i < nclks; i++) for (i = 0; i < nclks; i++)
clks[i] = ERR_PTR(-ENOENT); clks[i] = ERR_PTR(-ENOENT);
...@@ -894,6 +947,10 @@ static int __init cpg_mssr_probe(struct platform_device *pdev) ...@@ -894,6 +947,10 @@ static int __init cpg_mssr_probe(struct platform_device *pdev)
if (error) if (error)
return error; return error;
/* Reset Controller not supported for Standby Control SoCs */
if (info->stbyctrl)
return 0;
error = cpg_mssr_reset_controller_register(priv); error = cpg_mssr_reset_controller_register(priv);
if (error) if (error)
return error; return error;
......
...@@ -78,6 +78,13 @@ struct mssr_mod_clk { ...@@ -78,6 +78,13 @@ struct mssr_mod_clk {
#define DEF_MOD(_name, _mod, _parent...) \ #define DEF_MOD(_name, _mod, _parent...) \
{ .name = _name, .id = MOD_CLK_ID(_mod), .parent = _parent } { .name = _name, .id = MOD_CLK_ID(_mod), .parent = _parent }
/* Convert from sparse base-10 to packed index space */
#define MOD_CLK_PACK_10(x) ((x / 10) * 32 + (x % 10))
#define MOD_CLK_ID_10(x) (MOD_CLK_BASE + MOD_CLK_PACK_10(x))
#define DEF_MOD_STB(_name, _mod, _parent...) \
{ .name = _name, .id = MOD_CLK_ID_10(_mod), .parent = _parent }
struct device_node; struct device_node;
...@@ -103,6 +110,10 @@ struct device_node; ...@@ -103,6 +110,10 @@ struct device_node;
* *
* @init: Optional callback to perform SoC-specific initialization * @init: Optional callback to perform SoC-specific initialization
* @cpg_clk_register: Optional callback to handle special Core Clock types * @cpg_clk_register: Optional callback to handle special Core Clock types
*
* @stbyctrl: This device has Standby Control Registers which are 8-bits
* wide, no status registers (MSTPSR) and have different address
* offsets.
*/ */
struct cpg_mssr_info { struct cpg_mssr_info {
...@@ -111,6 +122,7 @@ struct cpg_mssr_info { ...@@ -111,6 +122,7 @@ struct cpg_mssr_info {
unsigned int num_core_clks; unsigned int num_core_clks;
unsigned int last_dt_core_clk; unsigned int last_dt_core_clk;
unsigned int num_total_core_clks; unsigned int num_total_core_clks;
bool stbyctrl;
/* Module Clocks */ /* Module Clocks */
const struct mssr_mod_clk *mod_clks; const struct mssr_mod_clk *mod_clks;
...@@ -134,6 +146,7 @@ struct cpg_mssr_info { ...@@ -134,6 +146,7 @@ struct cpg_mssr_info {
struct raw_notifier_head *notifiers); struct raw_notifier_head *notifiers);
}; };
extern const struct cpg_mssr_info r7s9210_cpg_mssr_info;
extern const struct cpg_mssr_info r8a7743_cpg_mssr_info; extern const struct cpg_mssr_info r8a7743_cpg_mssr_info;
extern const struct cpg_mssr_info r8a7745_cpg_mssr_info; extern const struct cpg_mssr_info r8a7745_cpg_mssr_info;
extern const struct cpg_mssr_info r8a77470_cpg_mssr_info; extern const struct cpg_mssr_info r8a77470_cpg_mssr_info;
......
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2018 Renesas Electronics Corp.
*
*/
#ifndef __DT_BINDINGS_CLOCK_R7S9210_CPG_MSSR_H__
#define __DT_BINDINGS_CLOCK_R7S9210_CPG_MSSR_H__
#include <dt-bindings/clock/renesas-cpg-mssr.h>
/* R7S9210 CPG Core Clocks */
#define R7S9210_CLK_I 0
#define R7S9210_CLK_G 1
#define R7S9210_CLK_B 2
#define R7S9210_CLK_P1 3
#define R7S9210_CLK_P1C 4
#define R7S9210_CLK_P0 5
#endif /* __DT_BINDINGS_CLOCK_R7S9210_CPG_MSSR_H__ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment