Commit 415e4993 authored by Wey-Yi Guy's avatar Wey-Yi Guy Committed by John W. Linville

iwlwifi: traverse linklist to find the valid OTP block

For devices using OTP memory, EEPROM image can start from
any one of the OTP blocks. If shadow RAM is disabled, we need to
traverse link list to find the last valid block, then start the EEPROM
image reading.

If OTP is not full, the valid block is the block _before_ the last block
on the link list; the last block on the link list is the empty block
ready for next OTP refresh/update.

If OTP is full, then the last block is the valid block to be used for
configure the device.
Signed-off-by: default avatarWey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: default avatarReinette Chatre <reinette.chatre@intel.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 3b24716f
...@@ -149,12 +149,14 @@ struct iwl_cfg iwl1000_bgn_cfg = { ...@@ -149,12 +149,14 @@ struct iwl_cfg iwl1000_bgn_cfg = {
.ucode_api_min = IWL1000_UCODE_API_MIN, .ucode_api_min = IWL1000_UCODE_API_MIN,
.sku = IWL_SKU_G|IWL_SKU_N, .sku = IWL_SKU_G|IWL_SKU_N,
.ops = &iwl1000_ops, .ops = &iwl1000_ops,
.eeprom_size = IWL_5000_EEPROM_IMG_SIZE, .eeprom_size = OTP_LOW_IMAGE_SIZE,
.eeprom_ver = EEPROM_5000_EEPROM_VERSION, .eeprom_ver = EEPROM_5000_EEPROM_VERSION,
.eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION, .eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION,
.mod_params = &iwl50_mod_params, .mod_params = &iwl50_mod_params,
.valid_tx_ant = ANT_A, .valid_tx_ant = ANT_A,
.valid_rx_ant = ANT_AB, .valid_rx_ant = ANT_AB,
.need_pll_cfg = true, .need_pll_cfg = true,
.max_ll_items = OTP_MAX_LL_ITEMS_1000,
.shadow_ram_support = false,
}; };
...@@ -161,7 +161,7 @@ struct iwl_cfg iwl6000h_2agn_cfg = { ...@@ -161,7 +161,7 @@ struct iwl_cfg iwl6000h_2agn_cfg = {
.ucode_api_min = IWL6000_UCODE_API_MIN, .ucode_api_min = IWL6000_UCODE_API_MIN,
.sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N, .sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N,
.ops = &iwl6000_ops, .ops = &iwl6000_ops,
.eeprom_size = IWL_5000_EEPROM_IMG_SIZE, .eeprom_size = OTP_LOW_IMAGE_SIZE,
.eeprom_ver = EEPROM_5000_EEPROM_VERSION, .eeprom_ver = EEPROM_5000_EEPROM_VERSION,
.eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION, .eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION,
.mod_params = &iwl50_mod_params, .mod_params = &iwl50_mod_params,
...@@ -169,6 +169,8 @@ struct iwl_cfg iwl6000h_2agn_cfg = { ...@@ -169,6 +169,8 @@ struct iwl_cfg iwl6000h_2agn_cfg = {
.valid_rx_ant = ANT_AB, .valid_rx_ant = ANT_AB,
.need_pll_cfg = false, .need_pll_cfg = false,
.pa_type = IWL_PA_HYBRID, .pa_type = IWL_PA_HYBRID,
.max_ll_items = OTP_MAX_LL_ITEMS_6x00,
.shadow_ram_support = true,
}; };
/* /*
...@@ -181,7 +183,7 @@ struct iwl_cfg iwl6000i_2agn_cfg = { ...@@ -181,7 +183,7 @@ struct iwl_cfg iwl6000i_2agn_cfg = {
.ucode_api_min = IWL6000_UCODE_API_MIN, .ucode_api_min = IWL6000_UCODE_API_MIN,
.sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N, .sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N,
.ops = &iwl6000_ops, .ops = &iwl6000_ops,
.eeprom_size = IWL_5000_EEPROM_IMG_SIZE, .eeprom_size = OTP_LOW_IMAGE_SIZE,
.eeprom_ver = EEPROM_5000_EEPROM_VERSION, .eeprom_ver = EEPROM_5000_EEPROM_VERSION,
.eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION, .eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION,
.mod_params = &iwl50_mod_params, .mod_params = &iwl50_mod_params,
...@@ -189,6 +191,8 @@ struct iwl_cfg iwl6000i_2agn_cfg = { ...@@ -189,6 +191,8 @@ struct iwl_cfg iwl6000i_2agn_cfg = {
.valid_rx_ant = ANT_BC, .valid_rx_ant = ANT_BC,
.need_pll_cfg = false, .need_pll_cfg = false,
.pa_type = IWL_PA_INTERNAL, .pa_type = IWL_PA_INTERNAL,
.max_ll_items = OTP_MAX_LL_ITEMS_6x00,
.shadow_ram_support = true,
}; };
struct iwl_cfg iwl6050_2agn_cfg = { struct iwl_cfg iwl6050_2agn_cfg = {
...@@ -198,7 +202,7 @@ struct iwl_cfg iwl6050_2agn_cfg = { ...@@ -198,7 +202,7 @@ struct iwl_cfg iwl6050_2agn_cfg = {
.ucode_api_min = IWL6050_UCODE_API_MIN, .ucode_api_min = IWL6050_UCODE_API_MIN,
.sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N, .sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N,
.ops = &iwl6000_ops, .ops = &iwl6000_ops,
.eeprom_size = IWL_5000_EEPROM_IMG_SIZE, .eeprom_size = OTP_LOW_IMAGE_SIZE,
.eeprom_ver = EEPROM_5000_EEPROM_VERSION, .eeprom_ver = EEPROM_5000_EEPROM_VERSION,
.eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION, .eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION,
.mod_params = &iwl50_mod_params, .mod_params = &iwl50_mod_params,
...@@ -206,6 +210,8 @@ struct iwl_cfg iwl6050_2agn_cfg = { ...@@ -206,6 +210,8 @@ struct iwl_cfg iwl6050_2agn_cfg = {
.valid_rx_ant = ANT_AB, .valid_rx_ant = ANT_AB,
.need_pll_cfg = false, .need_pll_cfg = false,
.pa_type = IWL_PA_SYSTEM, .pa_type = IWL_PA_SYSTEM,
.max_ll_items = OTP_MAX_LL_ITEMS_6x00,
.shadow_ram_support = true,
}; };
struct iwl_cfg iwl6000_3agn_cfg = { struct iwl_cfg iwl6000_3agn_cfg = {
...@@ -215,7 +221,7 @@ struct iwl_cfg iwl6000_3agn_cfg = { ...@@ -215,7 +221,7 @@ struct iwl_cfg iwl6000_3agn_cfg = {
.ucode_api_min = IWL6000_UCODE_API_MIN, .ucode_api_min = IWL6000_UCODE_API_MIN,
.sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N, .sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N,
.ops = &iwl6000_ops, .ops = &iwl6000_ops,
.eeprom_size = IWL_5000_EEPROM_IMG_SIZE, .eeprom_size = OTP_LOW_IMAGE_SIZE,
.eeprom_ver = EEPROM_5000_EEPROM_VERSION, .eeprom_ver = EEPROM_5000_EEPROM_VERSION,
.eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION, .eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION,
.mod_params = &iwl50_mod_params, .mod_params = &iwl50_mod_params,
...@@ -223,6 +229,8 @@ struct iwl_cfg iwl6000_3agn_cfg = { ...@@ -223,6 +229,8 @@ struct iwl_cfg iwl6000_3agn_cfg = {
.valid_rx_ant = ANT_ABC, .valid_rx_ant = ANT_ABC,
.need_pll_cfg = false, .need_pll_cfg = false,
.pa_type = IWL_PA_SYSTEM, .pa_type = IWL_PA_SYSTEM,
.max_ll_items = OTP_MAX_LL_ITEMS_6x00,
.shadow_ram_support = true,
}; };
struct iwl_cfg iwl6050_3agn_cfg = { struct iwl_cfg iwl6050_3agn_cfg = {
...@@ -232,7 +240,7 @@ struct iwl_cfg iwl6050_3agn_cfg = { ...@@ -232,7 +240,7 @@ struct iwl_cfg iwl6050_3agn_cfg = {
.ucode_api_min = IWL6050_UCODE_API_MIN, .ucode_api_min = IWL6050_UCODE_API_MIN,
.sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N, .sku = IWL_SKU_A|IWL_SKU_G|IWL_SKU_N,
.ops = &iwl6000_ops, .ops = &iwl6000_ops,
.eeprom_size = IWL_5000_EEPROM_IMG_SIZE, .eeprom_size = OTP_LOW_IMAGE_SIZE,
.eeprom_ver = EEPROM_5000_EEPROM_VERSION, .eeprom_ver = EEPROM_5000_EEPROM_VERSION,
.eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION, .eeprom_calib_ver = EEPROM_5000_TX_POWER_VERSION,
.mod_params = &iwl50_mod_params, .mod_params = &iwl50_mod_params,
...@@ -240,6 +248,8 @@ struct iwl_cfg iwl6050_3agn_cfg = { ...@@ -240,6 +248,8 @@ struct iwl_cfg iwl6050_3agn_cfg = {
.valid_rx_ant = ANT_ABC, .valid_rx_ant = ANT_ABC,
.need_pll_cfg = false, .need_pll_cfg = false,
.pa_type = IWL_PA_SYSTEM, .pa_type = IWL_PA_SYSTEM,
.max_ll_items = OTP_MAX_LL_ITEMS_6x00,
.shadow_ram_support = true,
}; };
MODULE_FIRMWARE(IWL6000_MODULE_FIRMWARE(IWL6000_UCODE_API_MAX)); MODULE_FIRMWARE(IWL6000_MODULE_FIRMWARE(IWL6000_UCODE_API_MAX));
......
...@@ -209,6 +209,8 @@ struct iwl_mod_params { ...@@ -209,6 +209,8 @@ struct iwl_mod_params {
* @ucode_api_max: Highest version of uCode API supported by driver. * @ucode_api_max: Highest version of uCode API supported by driver.
* @ucode_api_min: Lowest version of uCode API supported by driver. * @ucode_api_min: Lowest version of uCode API supported by driver.
* @pa_type: used by 6000 series only to identify the type of Power Amplifier * @pa_type: used by 6000 series only to identify the type of Power Amplifier
* @max_ll_items: max number of OTP blocks
* @shadow_ram_support: shadow support for OTP memory
* *
* We enable the driver to be backward compatible wrt API version. The * We enable the driver to be backward compatible wrt API version. The
* driver specifies which APIs it supports (with @ucode_api_max being the * driver specifies which APIs it supports (with @ucode_api_max being the
...@@ -247,6 +249,8 @@ struct iwl_cfg { ...@@ -247,6 +249,8 @@ struct iwl_cfg {
bool need_pll_cfg; bool need_pll_cfg;
bool use_isr_legacy; bool use_isr_legacy;
enum iwl_pa_type pa_type; enum iwl_pa_type pa_type;
const u16 max_ll_items;
const bool shadow_ram_support;
}; };
/*************************** /***************************
......
...@@ -887,6 +887,17 @@ enum iwl_nvm_type { ...@@ -887,6 +887,17 @@ enum iwl_nvm_type {
NVM_DEVICE_TYPE_OTP, NVM_DEVICE_TYPE_OTP,
}; };
/*
* Two types of OTP memory access modes
* IWL_OTP_ACCESS_ABSOLUTE - absolute address mode,
* based on physical memory addressing
* IWL_OTP_ACCESS_RELATIVE - relative address mode,
* based on logical memory addressing
*/
enum iwl_access_mode {
IWL_OTP_ACCESS_ABSOLUTE,
IWL_OTP_ACCESS_RELATIVE,
};
/** /**
* enum iwl_pa_type - Power Amplifier type * enum iwl_pa_type - Power Amplifier type
......
...@@ -152,6 +152,19 @@ int iwlcore_eeprom_verify_signature(struct iwl_priv *priv) ...@@ -152,6 +152,19 @@ int iwlcore_eeprom_verify_signature(struct iwl_priv *priv)
} }
EXPORT_SYMBOL(iwlcore_eeprom_verify_signature); EXPORT_SYMBOL(iwlcore_eeprom_verify_signature);
static void iwl_set_otp_access(struct iwl_priv *priv, enum iwl_access_mode mode)
{
u32 otpgp;
otpgp = iwl_read32(priv, CSR_OTP_GP_REG);
if (mode == IWL_OTP_ACCESS_ABSOLUTE)
iwl_clear_bit(priv, CSR_OTP_GP_REG,
CSR_OTP_GP_REG_OTP_ACCESS_MODE);
else
iwl_set_bit(priv, CSR_OTP_GP_REG,
CSR_OTP_GP_REG_OTP_ACCESS_MODE);
}
static int iwlcore_get_nvm_type(struct iwl_priv *priv) static int iwlcore_get_nvm_type(struct iwl_priv *priv)
{ {
u32 otpgp; u32 otpgp;
...@@ -252,6 +265,124 @@ static int iwl_init_otp_access(struct iwl_priv *priv) ...@@ -252,6 +265,124 @@ static int iwl_init_otp_access(struct iwl_priv *priv)
return ret; return ret;
} }
static int iwl_read_otp_word(struct iwl_priv *priv, u16 addr, u16 *eeprom_data)
{
int ret = 0;
u32 r;
u32 otpgp;
_iwl_write32(priv, CSR_EEPROM_REG,
CSR_EEPROM_REG_MSK_ADDR & (addr << 1));
ret = iwl_poll_direct_bit(priv, CSR_EEPROM_REG,
CSR_EEPROM_REG_READ_VALID_MSK,
IWL_EEPROM_ACCESS_TIMEOUT);
if (ret < 0) {
IWL_ERR(priv, "Time out reading OTP[%d]\n", addr);
return ret;
}
r = _iwl_read_direct32(priv, CSR_EEPROM_REG);
/* check for ECC errors: */
otpgp = iwl_read32(priv, CSR_OTP_GP_REG);
if (otpgp & CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK) {
/* stop in this case */
/* set the uncorrectable OTP ECC bit for acknowledgement */
iwl_set_bit(priv, CSR_OTP_GP_REG,
CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK);
IWL_ERR(priv, "Uncorrectable OTP ECC error, abort OTP read\n");
return -EINVAL;
}
if (otpgp & CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK) {
/* continue in this case */
/* set the correctable OTP ECC bit for acknowledgement */
iwl_set_bit(priv, CSR_OTP_GP_REG,
CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK);
IWL_ERR(priv, "Correctable OTP ECC error, continue read\n");
}
*eeprom_data = le16_to_cpu((__force __le16)(r >> 16));
return 0;
}
/*
* iwl_is_otp_empty: check for empty OTP
*/
static bool iwl_is_otp_empty(struct iwl_priv *priv)
{
u16 next_link_addr = 0, link_value;
bool is_empty = false;
/* locate the beginning of OTP link list */
if (!iwl_read_otp_word(priv, next_link_addr, &link_value)) {
if (!link_value) {
IWL_ERR(priv, "OTP is empty\n");
is_empty = true;
}
} else {
IWL_ERR(priv, "Unable to read first block of OTP list.\n");
is_empty = true;
}
return is_empty;
}
/*
* iwl_find_otp_image: find EEPROM image in OTP
* finding the OTP block that contains the EEPROM image.
* the last valid block on the link list (the block _before_ the last block)
* is the block we should read and used to configure the device.
* If all the available OTP blocks are full, the last block will be the block
* we should read and used to configure the device.
* only perform this operation if shadow RAM is disabled
*/
static int iwl_find_otp_image(struct iwl_priv *priv,
u16 *validblockaddr)
{
u16 next_link_addr = 0, link_value = 0, valid_addr;
int ret = 0;
int usedblocks = 0;
/* set addressing mode to absolute to traverse the link list */
iwl_set_otp_access(priv, IWL_OTP_ACCESS_ABSOLUTE);
/* checking for empty OTP or error */
if (iwl_is_otp_empty(priv))
return -EINVAL;
/*
* start traverse link list
* until reach the max number of OTP blocks
* different devices have different number of OTP blocks
*/
do {
/* save current valid block address
* check for more block on the link list
*/
valid_addr = next_link_addr;
next_link_addr = link_value;
IWL_DEBUG_INFO(priv, "OTP blocks %d addr 0x%x\n",
usedblocks, next_link_addr);
if (iwl_read_otp_word(priv, next_link_addr, &link_value))
return -EINVAL;
if (!link_value) {
/*
* reach the end of link list,
* set address point to the starting address
* of the image
*/
goto done;
}
/* more in the link list, continue */
usedblocks++;
} while (usedblocks < priv->cfg->max_ll_items);
/* OTP full, use last block */
IWL_DEBUG_INFO(priv, "OTP is full, use last block\n");
done:
*validblockaddr = valid_addr;
/* skip first 2 bytes (link list pointer) */
*validblockaddr += 2;
return ret;
}
/** /**
* iwl_eeprom_init - read EEPROM contents * iwl_eeprom_init - read EEPROM contents
* *
...@@ -266,15 +397,14 @@ int iwl_eeprom_init(struct iwl_priv *priv) ...@@ -266,15 +397,14 @@ int iwl_eeprom_init(struct iwl_priv *priv)
int sz; int sz;
int ret; int ret;
u16 addr; u16 addr;
u32 otpgp; u16 validblockaddr = 0;
u16 cache_addr = 0;
priv->nvm_device_type = iwlcore_get_nvm_type(priv); priv->nvm_device_type = iwlcore_get_nvm_type(priv);
if (priv->nvm_device_type == -ENOENT) if (priv->nvm_device_type == -ENOENT)
return -ENOENT; return -ENOENT;
/* allocate eeprom */ /* allocate eeprom */
if (priv->nvm_device_type == NVM_DEVICE_TYPE_OTP) IWL_DEBUG_INFO(priv, "NVM size = %d\n", priv->cfg->eeprom_size);
priv->cfg->eeprom_size =
OTP_BLOCK_SIZE * OTP_LOWER_BLOCKS_TOTAL;
sz = priv->cfg->eeprom_size; sz = priv->cfg->eeprom_size;
priv->eeprom = kzalloc(sz, GFP_KERNEL); priv->eeprom = kzalloc(sz, GFP_KERNEL);
if (!priv->eeprom) { if (!priv->eeprom) {
...@@ -302,46 +432,31 @@ int iwl_eeprom_init(struct iwl_priv *priv) ...@@ -302,46 +432,31 @@ int iwl_eeprom_init(struct iwl_priv *priv)
if (ret) { if (ret) {
IWL_ERR(priv, "Failed to initialize OTP access.\n"); IWL_ERR(priv, "Failed to initialize OTP access.\n");
ret = -ENOENT; ret = -ENOENT;
goto err; goto done;
} }
_iwl_write32(priv, CSR_EEPROM_GP, _iwl_write32(priv, CSR_EEPROM_GP,
iwl_read32(priv, CSR_EEPROM_GP) & iwl_read32(priv, CSR_EEPROM_GP) &
~CSR_EEPROM_GP_IF_OWNER_MSK); ~CSR_EEPROM_GP_IF_OWNER_MSK);
/* clear */
_iwl_write32(priv, CSR_OTP_GP_REG, iwl_set_bit(priv, CSR_OTP_GP_REG,
iwl_read32(priv, CSR_OTP_GP_REG) |
CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK | CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK |
CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK); CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK);
/* traversing the linked list if no shadow ram supported */
for (addr = 0; addr < sz; addr += sizeof(u16)) { if (!priv->cfg->shadow_ram_support) {
u32 r; if (iwl_find_otp_image(priv, &validblockaddr)) {
ret = -ENOENT;
_iwl_write32(priv, CSR_EEPROM_REG,
CSR_EEPROM_REG_MSK_ADDR & (addr << 1));
ret = iwl_poll_direct_bit(priv, CSR_EEPROM_REG,
CSR_EEPROM_REG_READ_VALID_MSK,
IWL_EEPROM_ACCESS_TIMEOUT);
if (ret < 0) {
IWL_ERR(priv, "Time out reading OTP[%d]\n", addr);
goto done;
}
r = _iwl_read_direct32(priv, CSR_EEPROM_REG);
/* check for ECC errors: */
otpgp = iwl_read32(priv, CSR_OTP_GP_REG);
if (otpgp & CSR_OTP_GP_REG_ECC_UNCORR_STATUS_MSK) {
/* stop in this case */
IWL_ERR(priv, "Uncorrectable OTP ECC error, Abort OTP read\n");
goto done; goto done;
} }
if (otpgp & CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK) {
/* continue in this case */
_iwl_write32(priv, CSR_OTP_GP_REG,
iwl_read32(priv, CSR_OTP_GP_REG) |
CSR_OTP_GP_REG_ECC_CORR_STATUS_MSK);
IWL_ERR(priv, "Correctable OTP ECC error, continue read\n");
} }
e[addr / 2] = le16_to_cpu((__force __le16)(r >> 16)); for (addr = validblockaddr; addr < validblockaddr + sz;
addr += sizeof(u16)) {
u16 eeprom_data;
ret = iwl_read_otp_word(priv, addr, &eeprom_data);
if (ret)
goto done;
e[cache_addr / 2] = eeprom_data;
cache_addr += sizeof(u16);
} }
} else { } else {
/* eeprom is an array of 16bit values */ /* eeprom is an array of 16bit values */
......
...@@ -180,8 +180,14 @@ struct iwl_eeprom_channel { ...@@ -180,8 +180,14 @@ struct iwl_eeprom_channel {
#define EEPROM_5050_EEPROM_VERSION (0x21E) #define EEPROM_5050_EEPROM_VERSION (0x21E)
/* OTP */ /* OTP */
#define OTP_LOWER_BLOCKS_TOTAL (3) /* lower blocks contain EEPROM image and calibration data */
#define OTP_BLOCK_SIZE (0x400) #define OTP_LOW_IMAGE_SIZE (2 * 512 * sizeof(u16)) /* 2 KB */
/* high blocks contain PAPD data */
#define OTP_HIGH_IMAGE_SIZE_6x00 (6 * 512 * sizeof(u16)) /* 6 KB */
#define OTP_HIGH_IMAGE_SIZE_1000 (0x200 * sizeof(u16)) /* 1024 bytes */
#define OTP_MAX_LL_ITEMS_1000 (3) /* OTP blocks for 1000 */
#define OTP_MAX_LL_ITEMS_6x00 (4) /* OTP blocks for 6x00 */
#define OTP_MAX_LL_ITEMS_6x50 (7) /* OTP blocks for 6x50 */
/* 2.4 GHz */ /* 2.4 GHz */
extern const u8 iwl_eeprom_band_1[14]; extern const u8 iwl_eeprom_band_1[14];
......
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