Commit 0240f307 authored by Arnd Bergmann's avatar Arnd Bergmann

Merge tag 'imx-soc-4.17' of...

Merge tag 'imx-soc-4.17' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux into next/soc

Pull "i.MX SoC changes for 4.17" from Shawn Guo:

 - Add i.MX 6SoloLiteLite (i.MX6SLL) SoC support on top of the existing
   i.MX6SL platform code.
 - Improve the SoC revision mapping by utilizing the MAJOR field of
   ANATOP DIGPROG register.
 - Add CPUIDLE_FLAG_TIMER_STOP flag for cpuidle ARM power off state,
   so that we can use ARM generic timer for some i.MX6 SoC.
 - Set low-power interrupt mask for i.MX25 to support STOP mode.
 - Drop EPIT driver as there is no user of it.
 - Simplify the error path of imx6_pm_get_base() a bit.

* tag 'imx-soc-4.17' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/shawnguo/linux:
  ARM: imx: Add basic msl support for imx6sll
  ARM: imx: pm-imx6: Return the error directly
  ARM: imx: avic: set low-power interrupt mask for imx25
  ARM: imx: Improve the soc revision calculation flow
  ARM: imx: add timer stop flag to ARM power off state
  ARM: imx: Remove epit support
parents 8694360b dee5dee2
...@@ -32,18 +32,6 @@ config MXC_DEBUG_BOARD ...@@ -32,18 +32,6 @@ config MXC_DEBUG_BOARD
data/address de-multiplexing and decode, signal level shift, data/address de-multiplexing and decode, signal level shift,
interrupt control and various board functions. interrupt control and various board functions.
config HAVE_EPIT
bool
config MXC_USE_EPIT
bool "Use EPIT instead of GPT"
depends on HAVE_EPIT
help
Use EPIT as the system timer on systems that have it. Normally you
don't have a reason to do so as the EPIT has the same features and
uses the same clocks as the GPT. Anyway, on some systems the GPT
may be in use for other purposes.
config HAVE_IMX_ANATOP config HAVE_IMX_ANATOP
bool bool
...@@ -85,7 +73,6 @@ config SOC_IMX31 ...@@ -85,7 +73,6 @@ config SOC_IMX31
config SOC_IMX35 config SOC_IMX35
bool bool
select ARCH_MXC_IOMUX_V3 select ARCH_MXC_IOMUX_V3
select HAVE_EPIT
select MXC_AVIC select MXC_AVIC
select PINCTRL_IMX35 select PINCTRL_IMX35
...@@ -512,6 +499,13 @@ config SOC_IMX6SL ...@@ -512,6 +499,13 @@ config SOC_IMX6SL
help help
This enables support for Freescale i.MX6 SoloLite processor. This enables support for Freescale i.MX6 SoloLite processor.
config SOC_IMX6SLL
bool "i.MX6 SoloLiteLite support"
select SOC_IMX6
help
This enables support for Freescale i.MX6 SoloLiteLite processor.
config SOC_IMX6SX config SOC_IMX6SX
bool "i.MX6 SoloX support" bool "i.MX6 SoloX support"
select PINCTRL_IMX6SX select PINCTRL_IMX6SX
......
...@@ -20,7 +20,6 @@ obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o ...@@ -20,7 +20,6 @@ obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o
obj-$(CONFIG_MXC_TZIC) += tzic.o obj-$(CONFIG_MXC_TZIC) += tzic.o
obj-$(CONFIG_MXC_AVIC) += avic.o obj-$(CONFIG_MXC_AVIC) += avic.o
obj-$(CONFIG_MXC_USE_EPIT) += epit.o
obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o obj-$(CONFIG_MXC_DEBUG_BOARD) += 3ds_debugboard.o
ifeq ($(CONFIG_CPU_IDLE),y) ifeq ($(CONFIG_CPU_IDLE),y)
...@@ -78,6 +77,7 @@ obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o ...@@ -78,6 +77,7 @@ obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
endif endif
obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o obj-$(CONFIG_SOC_IMX6Q) += mach-imx6q.o
obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o obj-$(CONFIG_SOC_IMX6SL) += mach-imx6sl.o
obj-$(CONFIG_SOC_IMX6SLL) += mach-imx6sl.o
obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o obj-$(CONFIG_SOC_IMX6SX) += mach-imx6sx.o
obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o obj-$(CONFIG_SOC_IMX6UL) += mach-imx6ul.o
obj-$(CONFIG_SOC_IMX7D) += mach-imx7d.o obj-$(CONFIG_SOC_IMX7D) += mach-imx7d.o
......
/* /*
* Copyright (C) 2013-2015 Freescale Semiconductor, Inc. * Copyright (C) 2013-2015 Freescale Semiconductor, Inc.
* Copyright 2017-2018 NXP.
* *
* The code contained herein is licensed under the GNU General Public * The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License * License. You may obtain a copy of the GNU General Public License
...@@ -116,6 +117,7 @@ void __init imx_init_revision_from_anatop(void) ...@@ -116,6 +117,7 @@ void __init imx_init_revision_from_anatop(void)
unsigned int revision; unsigned int revision;
u32 digprog; u32 digprog;
u16 offset = ANADIG_DIGPROG; u16 offset = ANADIG_DIGPROG;
u8 major_part, minor_part;
np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop"); np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop");
anatop_base = of_iomap(np, 0); anatop_base = of_iomap(np, 0);
...@@ -127,45 +129,25 @@ void __init imx_init_revision_from_anatop(void) ...@@ -127,45 +129,25 @@ void __init imx_init_revision_from_anatop(void)
digprog = readl_relaxed(anatop_base + offset); digprog = readl_relaxed(anatop_base + offset);
iounmap(anatop_base); iounmap(anatop_base);
switch (digprog & 0xff) { /*
case 0: * On i.MX7D digprog value match linux version format, so
/* * it needn't map again and we can use register value directly.
* For i.MX6QP, most of the code for i.MX6Q can be resued, */
* so internally, we identify it as i.MX6Q Rev 2.0 if (of_device_is_compatible(np, "fsl,imx7d-anatop")) {
*/ revision = digprog & 0xff;
if (digprog >> 8 & 0x01) } else {
revision = IMX_CHIP_REVISION_2_0;
else
revision = IMX_CHIP_REVISION_1_0;
break;
case 1:
revision = IMX_CHIP_REVISION_1_1;
break;
case 2:
revision = IMX_CHIP_REVISION_1_2;
break;
case 3:
revision = IMX_CHIP_REVISION_1_3;
break;
case 4:
revision = IMX_CHIP_REVISION_1_4;
break;
case 5:
/*
* i.MX6DQ TO1.5 is defined as Rev 1.3 in Data Sheet, marked
* as 'D' in Part Number last character.
*/
revision = IMX_CHIP_REVISION_1_5;
break;
default:
/* /*
* Fail back to return raw register value instead of 0xff. * MAJOR: [15:8], the major silicon revison;
* It will be easy to know version information in SOC if it * MINOR: [7: 0], the minor silicon revison;
* can't be recognized by known version. And some chip's (i.MX7D) *
* digprog value match linux version format, so it needn't map * please refer to the i.MX RM for the detailed
* again and we can use register value directly. * silicon revison bit define.
* format the major part and minor part to match the
* linux kernel soc version format.
*/ */
revision = digprog & 0xff; major_part = (digprog >> 8) & 0xf;
minor_part = digprog & 0xf;
revision = ((major_part + 1) << 4) | minor_part;
} }
mxc_set_cpu_type(digprog >> 16 & 0xff); mxc_set_cpu_type(digprog >> 16 & 0xff);
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <linux/irqdomain.h> #include <linux/irqdomain.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/of_address.h>
#include <asm/mach/irq.h> #include <asm/mach/irq.h>
#include <asm/exception.h> #include <asm/exception.h>
...@@ -51,7 +52,12 @@ ...@@ -51,7 +52,12 @@
#define AVIC_NUM_IRQS 64 #define AVIC_NUM_IRQS 64
/* low power interrupt mask registers */
#define MX25_CCM_LPIMR0 0x68
#define MX25_CCM_LPIMR1 0x6C
static void __iomem *avic_base; static void __iomem *avic_base;
static void __iomem *mx25_ccm_base;
static struct irq_domain *domain; static struct irq_domain *domain;
#ifdef CONFIG_FIQ #ifdef CONFIG_FIQ
...@@ -93,6 +99,18 @@ static void avic_irq_suspend(struct irq_data *d) ...@@ -93,6 +99,18 @@ static void avic_irq_suspend(struct irq_data *d)
avic_saved_mask_reg[idx] = imx_readl(avic_base + ct->regs.mask); avic_saved_mask_reg[idx] = imx_readl(avic_base + ct->regs.mask);
imx_writel(gc->wake_active, avic_base + ct->regs.mask); imx_writel(gc->wake_active, avic_base + ct->regs.mask);
if (mx25_ccm_base) {
u8 offs = d->hwirq < AVIC_NUM_IRQS / 2 ?
MX25_CCM_LPIMR0 : MX25_CCM_LPIMR1;
/*
* The interrupts which are still enabled will be used as wakeup
* sources. Allow those interrupts in low-power mode.
* The LPIMR registers use 0 to allow an interrupt, the AVIC
* registers use 1.
*/
imx_writel(~gc->wake_active, mx25_ccm_base + offs);
}
} }
static void avic_irq_resume(struct irq_data *d) static void avic_irq_resume(struct irq_data *d)
...@@ -102,6 +120,13 @@ static void avic_irq_resume(struct irq_data *d) ...@@ -102,6 +120,13 @@ static void avic_irq_resume(struct irq_data *d)
int idx = d->hwirq >> 5; int idx = d->hwirq >> 5;
imx_writel(avic_saved_mask_reg[idx], avic_base + ct->regs.mask); imx_writel(avic_saved_mask_reg[idx], avic_base + ct->regs.mask);
if (mx25_ccm_base) {
u8 offs = d->hwirq < AVIC_NUM_IRQS / 2 ?
MX25_CCM_LPIMR0 : MX25_CCM_LPIMR1;
imx_writel(0xffffffff, mx25_ccm_base + offs);
}
} }
#else #else
...@@ -158,6 +183,18 @@ void __init mxc_init_irq(void __iomem *irqbase) ...@@ -158,6 +183,18 @@ void __init mxc_init_irq(void __iomem *irqbase)
avic_base = irqbase; avic_base = irqbase;
np = of_find_compatible_node(NULL, NULL, "fsl,imx25-ccm");
mx25_ccm_base = of_iomap(np, 0);
if (mx25_ccm_base) {
/*
* By default, we mask all interrupts. We set the actual mask
* before we go into low-power mode.
*/
imx_writel(0xffffffff, mx25_ccm_base + MX25_CCM_LPIMR0);
imx_writel(0xffffffff, mx25_ccm_base + MX25_CCM_LPIMR1);
}
/* put the AVIC into the reset value with /* put the AVIC into the reset value with
* all interrupts disabled * all interrupts disabled
*/ */
......
...@@ -135,6 +135,9 @@ struct device * __init imx_soc_device_init(void) ...@@ -135,6 +135,9 @@ struct device * __init imx_soc_device_init(void)
case MXC_CPU_IMX6ULL: case MXC_CPU_IMX6ULL:
soc_id = "i.MX6ULL"; soc_id = "i.MX6ULL";
break; break;
case MXC_CPU_IMX6SLL:
soc_id = "i.MX6SLL";
break;
case MXC_CPU_IMX7D: case MXC_CPU_IMX7D:
soc_id = "i.MX7D"; soc_id = "i.MX7D";
break; break;
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "common.h" #include "common.h"
#include "cpuidle.h" #include "cpuidle.h"
#include "hardware.h"
static int imx6sl_enter_wait(struct cpuidle_device *dev, static int imx6sl_enter_wait(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index) struct cpuidle_driver *drv, int index)
...@@ -21,9 +22,11 @@ static int imx6sl_enter_wait(struct cpuidle_device *dev, ...@@ -21,9 +22,11 @@ static int imx6sl_enter_wait(struct cpuidle_device *dev,
* Software workaround for ERR005311, see function * Software workaround for ERR005311, see function
* description for details. * description for details.
*/ */
imx6sl_set_wait_clk(true); if (cpu_is_imx6sl())
imx6sl_set_wait_clk(true);
cpu_do_idle(); cpu_do_idle();
imx6sl_set_wait_clk(false); if (cpu_is_imx6sl())
imx6sl_set_wait_clk(false);
imx6_set_lpm(WAIT_CLOCKED); imx6_set_lpm(WAIT_CLOCKED);
return index; return index;
......
...@@ -89,6 +89,7 @@ static struct cpuidle_driver imx6sx_cpuidle_driver = { ...@@ -89,6 +89,7 @@ static struct cpuidle_driver imx6sx_cpuidle_driver = {
*/ */
.exit_latency = 300, .exit_latency = 300,
.target_residency = 500, .target_residency = 500,
.flags = CPUIDLE_FLAG_TIMER_STOP,
.enter = imx6sx_enter_wait, .enter = imx6sx_enter_wait,
.name = "LOW-POWER-IDLE", .name = "LOW-POWER-IDLE",
.desc = "ARM power off", .desc = "ARM power off",
......
/*
* linux/arch/arm/plat-mxc/epit.c
*
* Copyright (C) 2010 Sascha Hauer <s.hauer@pengutronix.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#define EPITCR 0x00
#define EPITSR 0x04
#define EPITLR 0x08
#define EPITCMPR 0x0c
#define EPITCNR 0x10
#define EPITCR_EN (1 << 0)
#define EPITCR_ENMOD (1 << 1)
#define EPITCR_OCIEN (1 << 2)
#define EPITCR_RLD (1 << 3)
#define EPITCR_PRESC(x) (((x) & 0xfff) << 4)
#define EPITCR_SWR (1 << 16)
#define EPITCR_IOVW (1 << 17)
#define EPITCR_DBGEN (1 << 18)
#define EPITCR_WAITEN (1 << 19)
#define EPITCR_RES (1 << 20)
#define EPITCR_STOPEN (1 << 21)
#define EPITCR_OM_DISCON (0 << 22)
#define EPITCR_OM_TOGGLE (1 << 22)
#define EPITCR_OM_CLEAR (2 << 22)
#define EPITCR_OM_SET (3 << 22)
#define EPITCR_CLKSRC_OFF (0 << 24)
#define EPITCR_CLKSRC_PERIPHERAL (1 << 24)
#define EPITCR_CLKSRC_REF_HIGH (1 << 24)
#define EPITCR_CLKSRC_REF_LOW (3 << 24)
#define EPITSR_OCIF (1 << 0)
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/clockchips.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <asm/mach/time.h>
#include "common.h"
#include "hardware.h"
static struct clock_event_device clockevent_epit;
static void __iomem *timer_base;
static inline void epit_irq_disable(void)
{
u32 val;
val = imx_readl(timer_base + EPITCR);
val &= ~EPITCR_OCIEN;
imx_writel(val, timer_base + EPITCR);
}
static inline void epit_irq_enable(void)
{
u32 val;
val = imx_readl(timer_base + EPITCR);
val |= EPITCR_OCIEN;
imx_writel(val, timer_base + EPITCR);
}
static void epit_irq_acknowledge(void)
{
imx_writel(EPITSR_OCIF, timer_base + EPITSR);
}
static int __init epit_clocksource_init(struct clk *timer_clk)
{
unsigned int c = clk_get_rate(timer_clk);
return clocksource_mmio_init(timer_base + EPITCNR, "epit", c, 200, 32,
clocksource_mmio_readl_down);
}
/* clock event */
static int epit_set_next_event(unsigned long evt,
struct clock_event_device *unused)
{
unsigned long tcmp;
tcmp = imx_readl(timer_base + EPITCNR);
imx_writel(tcmp - evt, timer_base + EPITCMPR);
return 0;
}
/* Left event sources disabled, no more interrupts appear */
static int epit_shutdown(struct clock_event_device *evt)
{
unsigned long flags;
/*
* The timer interrupt generation is disabled at least
* for enough time to call epit_set_next_event()
*/
local_irq_save(flags);
/* Disable interrupt in GPT module */
epit_irq_disable();
/* Clear pending interrupt */
epit_irq_acknowledge();
local_irq_restore(flags);
return 0;
}
static int epit_set_oneshot(struct clock_event_device *evt)
{
unsigned long flags;
/*
* The timer interrupt generation is disabled at least
* for enough time to call epit_set_next_event()
*/
local_irq_save(flags);
/* Disable interrupt in GPT module */
epit_irq_disable();
/* Clear pending interrupt, only while switching mode */
if (!clockevent_state_oneshot(evt))
epit_irq_acknowledge();
/*
* Do not put overhead of interrupt enable/disable into
* epit_set_next_event(), the core has about 4 minutes
* to call epit_set_next_event() or shutdown clock after
* mode switching
*/
epit_irq_enable();
local_irq_restore(flags);
return 0;
}
/*
* IRQ handler for the timer
*/
static irqreturn_t epit_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evt = &clockevent_epit;
epit_irq_acknowledge();
evt->event_handler(evt);
return IRQ_HANDLED;
}
static struct irqaction epit_timer_irq = {
.name = "i.MX EPIT Timer Tick",
.flags = IRQF_TIMER | IRQF_IRQPOLL,
.handler = epit_timer_interrupt,
};
static struct clock_event_device clockevent_epit = {
.name = "epit",
.features = CLOCK_EVT_FEAT_ONESHOT,
.set_state_shutdown = epit_shutdown,
.tick_resume = epit_shutdown,
.set_state_oneshot = epit_set_oneshot,
.set_next_event = epit_set_next_event,
.rating = 200,
};
static int __init epit_clockevent_init(struct clk *timer_clk)
{
clockevent_epit.cpumask = cpumask_of(0);
clockevents_config_and_register(&clockevent_epit,
clk_get_rate(timer_clk),
0x800, 0xfffffffe);
return 0;
}
void __init epit_timer_init(void __iomem *base, int irq)
{
struct clk *timer_clk;
timer_clk = clk_get_sys("imx-epit.0", NULL);
if (IS_ERR(timer_clk)) {
pr_err("i.MX epit: unable to get clk\n");
return;
}
clk_prepare_enable(timer_clk);
timer_base = base;
/*
* Initialise to a known state (all timers off, and timing reset)
*/
imx_writel(0x0, timer_base + EPITCR);
imx_writel(0xffffffff, timer_base + EPITLR);
imx_writel(EPITCR_EN | EPITCR_CLKSRC_REF_HIGH | EPITCR_WAITEN,
timer_base + EPITCR);
/* init and register the timer to the framework */
epit_clocksource_init(timer_clk);
epit_clockevent_init(timer_clk);
/* Make irqs happen */
setup_irq(irq, &epit_timer_irq);
}
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "common.h" #include "common.h"
#include "cpuidle.h" #include "cpuidle.h"
#include "hardware.h"
static void __init imx6sl_fec_init(void) static void __init imx6sl_fec_init(void)
{ {
...@@ -54,7 +55,8 @@ static void __init imx6sl_init_machine(void) ...@@ -54,7 +55,8 @@ static void __init imx6sl_init_machine(void)
of_platform_default_populate(NULL, NULL, parent); of_platform_default_populate(NULL, NULL, parent);
imx6sl_fec_init(); if (cpu_is_imx6sl())
imx6sl_fec_init();
imx_anatop_init(); imx_anatop_init();
imx6sl_pm_init(); imx6sl_pm_init();
} }
...@@ -66,11 +68,15 @@ static void __init imx6sl_init_irq(void) ...@@ -66,11 +68,15 @@ static void __init imx6sl_init_irq(void)
imx_init_l2cache(); imx_init_l2cache();
imx_src_init(); imx_src_init();
irqchip_init(); irqchip_init();
imx6_pm_ccm_init("fsl,imx6sl-ccm"); if (cpu_is_imx6sl())
imx6_pm_ccm_init("fsl,imx6sl-ccm");
else
imx6_pm_ccm_init("fsl,imx6sll-ccm");
} }
static const char * const imx6sl_dt_compat[] __initconst = { static const char * const imx6sl_dt_compat[] __initconst = {
"fsl,imx6sl", "fsl,imx6sl",
"fsl,imx6sll",
NULL, NULL,
}; };
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#define MXC_CPU_IMX6Q 0x63 #define MXC_CPU_IMX6Q 0x63
#define MXC_CPU_IMX6UL 0x64 #define MXC_CPU_IMX6UL 0x64
#define MXC_CPU_IMX6ULL 0x65 #define MXC_CPU_IMX6ULL 0x65
#define MXC_CPU_IMX6SLL 0x67
#define MXC_CPU_IMX7D 0x72 #define MXC_CPU_IMX7D 0x72
#define IMX_DDR_TYPE_LPDDR2 1 #define IMX_DDR_TYPE_LPDDR2 1
...@@ -79,6 +80,11 @@ static inline bool cpu_is_imx6ull(void) ...@@ -79,6 +80,11 @@ static inline bool cpu_is_imx6ull(void)
return __mxc_cpu_type == MXC_CPU_IMX6ULL; return __mxc_cpu_type == MXC_CPU_IMX6ULL;
} }
static inline bool cpu_is_imx6sll(void)
{
return __mxc_cpu_type == MXC_CPU_IMX6SLL;
}
static inline bool cpu_is_imx6q(void) static inline bool cpu_is_imx6q(void)
{ {
return __mxc_cpu_type == MXC_CPU_IMX6Q; return __mxc_cpu_type == MXC_CPU_IMX6Q;
......
...@@ -428,10 +428,8 @@ static int __init imx6_pm_get_base(struct imx6_pm_base *base, ...@@ -428,10 +428,8 @@ static int __init imx6_pm_get_base(struct imx6_pm_base *base,
int ret = 0; int ret = 0;
node = of_find_compatible_node(NULL, NULL, compat); node = of_find_compatible_node(NULL, NULL, compat);
if (!node) { if (!node)
ret = -ENODEV; return -ENODEV;
goto out;
}
ret = of_address_to_resource(node, 0, &res); ret = of_address_to_resource(node, 0, &res);
if (ret) if (ret)
...@@ -444,7 +442,6 @@ static int __init imx6_pm_get_base(struct imx6_pm_base *base, ...@@ -444,7 +442,6 @@ static int __init imx6_pm_get_base(struct imx6_pm_base *base,
put_node: put_node:
of_node_put(node); of_node_put(node);
out:
return ret; return ret;
} }
......
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