mirror of
https://github.com/Telecominfraproject/OpenNetworkLinux.git
synced 2026-01-10 17:21:46 +00:00
Update Mellanox kernel patches
1. Add 4.9 kernel patch for I2C access optimization 2. Add gearbox kernel patch 3. Add Watchdog introduce patch Oleksandr shamray <oleksandrs@mellanox.com>
This commit is contained in:
committed by
Oleksandr Shamray
parent
5e744cfaa9
commit
2c6f911438
@@ -0,0 +1,839 @@
|
||||
From a648f2856518f8884a7798469faf755d0f5dcd50 Mon Sep 17 00:00:00 2001
|
||||
From: Michael Shych <michaelsh@mellanox.com>
|
||||
Date: Mon, 17 Dec 2018 12:32:45 +0000
|
||||
Subject: [PATCH v1 mlx-wdt 1/1] watchdog: mlx-wdt: introduce watchdog driver
|
||||
for Mellanox systems
|
||||
|
||||
Watchdog driver for Mellanox watchdog devices, implemented in
|
||||
programmable logic device.
|
||||
|
||||
Main and auxiliary watchdog devices can exist on the same system.
|
||||
There are several actions that can be defined in the watchdog:
|
||||
system reset, start fans on full speed and increase counter.
|
||||
The last 2 actions are performed without system reset.
|
||||
Actions without reset are provided for auxiliary watchdog devices,
|
||||
which is optional.
|
||||
Access to CPLD registers is performed through generic
|
||||
regmap interface.
|
||||
|
||||
There are 2 types of HW CPLD watchdog implementations.
|
||||
Type 1: actual HW timeout can be defined as power of 2 msec.
|
||||
e.g. timeout 20 sec will be rounded up to 32768 msec.;
|
||||
maximum timeout period is 32 sec (32768 msec.);
|
||||
get time-left isn't supported
|
||||
Type 2: actual HW timeout is defined in sec. and it's a same as
|
||||
user defined timeout;
|
||||
maximum timeout is 255 sec;
|
||||
get time-left is supported;
|
||||
|
||||
Watchdog driver is probed from common mlx_platform driver.
|
||||
|
||||
Signed-off-by: Michael Shych <michaelsh@mellanox.com>
|
||||
---
|
||||
drivers/platform/x86/mlx-platform.c | 202 +++++++++++++++++-
|
||||
drivers/watchdog/Kconfig | 15 ++
|
||||
drivers/watchdog/Makefile | 1 +
|
||||
drivers/watchdog/mlx_wdt.c | 391 +++++++++++++++++++++++++++++++++++
|
||||
include/linux/platform_data/mlxreg.h | 6 +
|
||||
5 files changed, 612 insertions(+), 3 deletions(-)
|
||||
create mode 100644 drivers/watchdog/mlx_wdt.c
|
||||
|
||||
diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c
|
||||
index 6e150b4..fc8d655 100644
|
||||
--- a/drivers/platform/x86/mlx-platform.c
|
||||
+++ b/drivers/platform/x86/mlx-platform.c
|
||||
@@ -55,6 +55,14 @@
|
||||
#define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET 0x88
|
||||
#define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET 0x89
|
||||
#define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET 0x8a
|
||||
+#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET 0xc7
|
||||
+#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET 0xc8
|
||||
+#define MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET 0xc9
|
||||
+#define MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET 0xcb
|
||||
+#define MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET 0xcd
|
||||
+#define MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET 0xcf
|
||||
+#define MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET 0xd1
|
||||
+#define MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET 0xd2
|
||||
#define MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET 0xe3
|
||||
#define MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET 0xe4
|
||||
#define MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET 0xe5
|
||||
@@ -128,6 +136,18 @@
|
||||
#define MLXPLAT_CPLD_FAN3_DEFAULT_NR 13
|
||||
#define MLXPLAT_CPLD_FAN4_DEFAULT_NR 14
|
||||
|
||||
+/* Masks and default values for watchdogs */
|
||||
+#define MLXPLAT_CPLD_WD1_CLEAR_MASK GENMASK(7, 1)
|
||||
+#define MLXPLAT_CPLD_WD2_CLEAR_MASK (GENMASK(7, 0) & ~BIT(1))
|
||||
+
|
||||
+#define MLXPLAT_CPLD_WD_TYPE1_TO_MASK GENMASK(7, 4)
|
||||
+#define MLXPLAT_CPLD_WD_TYPE2_TO_MASK 0
|
||||
+#define MLXPLAT_CPLD_WD_RESET_ACT_MASK GENMASK(7, 1)
|
||||
+#define MLXPLAT_CPLD_WD_FAN_ACT_MASK (GENMASK(7, 0) & ~BIT(4))
|
||||
+#define MLXPLAT_CPLD_WD_COUNT_ACT_MASK (GENMASK(7, 0) & ~BIT(7))
|
||||
+#define MLXPLAT_CPLD_WD_DFLT_TIMEOUT 30
|
||||
+#define MLXPLAT_CPLD_WD_MAX_DEVS 2
|
||||
+
|
||||
/* mlxplat_priv - platform private data
|
||||
* @pdev_i2c - i2c controller platform device
|
||||
* @pdev_mux - array of mux platform devices
|
||||
@@ -135,6 +155,7 @@
|
||||
* @pdev_led - led platform devices
|
||||
* @pdev_io_regs - register access platform devices
|
||||
* @pdev_fan - FAN platform devices
|
||||
+ * @pdev_wd - array of watchdog platform devices
|
||||
*/
|
||||
struct mlxplat_priv {
|
||||
struct platform_device *pdev_i2c;
|
||||
@@ -143,6 +164,7 @@ struct mlxplat_priv {
|
||||
struct platform_device *pdev_led;
|
||||
struct platform_device *pdev_io_regs;
|
||||
struct platform_device *pdev_fan;
|
||||
+ struct platform_device *pdev_wd[MLXPLAT_CPLD_WD_MAX_DEVS];
|
||||
};
|
||||
|
||||
/* Regions for LPC I2C controller and LPC base register space */
|
||||
@@ -1340,6 +1362,132 @@ static struct mlxreg_core_platform_data mlxplat_default_fan_data = {
|
||||
.counter = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_data),
|
||||
};
|
||||
|
||||
+/* Type1 watchdog implementation on MSN2700, MSN2100 and MSN2140 systems */
|
||||
+static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type1[] = {
|
||||
+ {
|
||||
+ .label = "action",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK,
|
||||
+ .bit = 0,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "timeout",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_TYPE1_TO_MASK,
|
||||
+ .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "ping",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD1_CLEAR_MASK,
|
||||
+ .bit = 0,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "reset",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
+ .mask = GENMASK(7, 0) & ~BIT(6),
|
||||
+ .bit = 6,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type1[] = {
|
||||
+ {
|
||||
+ .label = "action",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK,
|
||||
+ .bit = 4,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "timeout",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_TYPE1_TO_MASK,
|
||||
+ .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "ping",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD1_CLEAR_MASK,
|
||||
+ .bit = 1,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type1[] = {
|
||||
+ {
|
||||
+ .data = mlxplat_mlxcpld_wd_main_regs_type1,
|
||||
+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type1),
|
||||
+ .identity = "mlx-wdt-main",
|
||||
+ },
|
||||
+ {
|
||||
+ .data = mlxplat_mlxcpld_wd_aux_regs_type1,
|
||||
+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type1),
|
||||
+ .identity = "mlx-wdt-aux",
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+/* Type2 watchdog implementation on MSB8700 and up systems
|
||||
+ * To differentiate: ping reg == action reg
|
||||
+ */
|
||||
+static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type2[] = {
|
||||
+ {
|
||||
+ .label = "action",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK,
|
||||
+ .bit = 0,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "timeout",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK,
|
||||
+ .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "ping",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK,
|
||||
+ .bit = 0,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "reset",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET,
|
||||
+ .mask = GENMASK(7, 0) & ~BIT(6),
|
||||
+ .bit = 6,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type2[] = {
|
||||
+ {
|
||||
+ .label = "action",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK,
|
||||
+ .bit = 4,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "timeout",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK,
|
||||
+ .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT,
|
||||
+ },
|
||||
+ {
|
||||
+ .label = "ping",
|
||||
+ .reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET,
|
||||
+ .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK,
|
||||
+ .bit = 4,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type2[] = {
|
||||
+ {
|
||||
+ .data = mlxplat_mlxcpld_wd_main_regs_type2,
|
||||
+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type2),
|
||||
+ .identity = "mlx-wdt-main",
|
||||
+ },
|
||||
+ {
|
||||
+ .data = mlxplat_mlxcpld_wd_aux_regs_type2,
|
||||
+ .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type2),
|
||||
+ .identity = "mlx-wdt-aux",
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
@@ -1362,6 +1510,14 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg)
|
||||
case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET:
|
||||
return true;
|
||||
@@ -1404,6 +1560,14 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg)
|
||||
case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET:
|
||||
@@ -1460,6 +1624,8 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg)
|
||||
case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET:
|
||||
+ case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET:
|
||||
case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET:
|
||||
@@ -1487,6 +1653,7 @@ static const struct reg_default mlxplat_mlxcpld_regmap_default[] = {
|
||||
{ MLXPLAT_CPLD_LPC_REG_WP1_OFFSET, 0x00 },
|
||||
{ MLXPLAT_CPLD_LPC_REG_WP2_OFFSET, 0x00 },
|
||||
{ MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 },
|
||||
+ { MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 },
|
||||
};
|
||||
|
||||
struct mlxplat_mlxcpld_regmap_context {
|
||||
@@ -1536,6 +1703,8 @@ static struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug;
|
||||
static struct mlxreg_core_platform_data *mlxplat_led;
|
||||
static struct mlxreg_core_platform_data *mlxplat_regs_io;
|
||||
static struct mlxreg_core_platform_data *mlxplat_fan;
|
||||
+static struct mlxreg_core_platform_data
|
||||
+ *mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS];
|
||||
|
||||
static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
|
||||
{
|
||||
@@ -1551,6 +1720,7 @@ static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi)
|
||||
mlxplat_default_channels[i - 1][MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
|
||||
mlxplat_led = &mlxplat_default_led_data;
|
||||
mlxplat_regs_io = &mlxplat_default_regs_io_data;
|
||||
+ mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
|
||||
return 1;
|
||||
};
|
||||
@@ -1569,6 +1739,7 @@ static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi)
|
||||
mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
|
||||
mlxplat_led = &mlxplat_msn21xx_led_data;
|
||||
mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data;
|
||||
+ mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
|
||||
return 1;
|
||||
};
|
||||
@@ -1587,6 +1758,7 @@ static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi)
|
||||
mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
|
||||
mlxplat_led = &mlxplat_default_led_data;
|
||||
mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data;
|
||||
+ mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
|
||||
return 1;
|
||||
};
|
||||
@@ -1605,6 +1777,7 @@ static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi)
|
||||
mlxplat_default_channels[i - 1][MLXPLAT_CPLD_GRP_CHNL_NUM - 1];
|
||||
mlxplat_led = &mlxplat_msn21xx_led_data;
|
||||
mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data;
|
||||
+ mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0];
|
||||
|
||||
return 1;
|
||||
};
|
||||
@@ -1624,6 +1797,8 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi)
|
||||
mlxplat_led = &mlxplat_default_ng_led_data;
|
||||
mlxplat_regs_io = &mlxplat_default_ng_regs_io_data;
|
||||
mlxplat_fan = &mlxplat_default_fan_data;
|
||||
+ for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++)
|
||||
+ mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i];
|
||||
|
||||
return 1;
|
||||
};
|
||||
@@ -1906,15 +2081,33 @@ static int __init mlxplat_init(void)
|
||||
}
|
||||
}
|
||||
|
||||
+ /* Add WD drivers. */
|
||||
+ for (j = 0; j < MLXPLAT_CPLD_WD_MAX_DEVS; j++) {
|
||||
+ if (mlxplat_wd_data[j]) {
|
||||
+ mlxplat_wd_data[j]->regmap = mlxplat_hotplug->regmap;
|
||||
+ priv->pdev_wd[j] = platform_device_register_resndata(
|
||||
+ &mlxplat_dev->dev,
|
||||
+ "mlx-wdt", j, NULL, 0,
|
||||
+ mlxplat_wd_data[j],
|
||||
+ sizeof(*mlxplat_wd_data[j]));
|
||||
+ if (IS_ERR(priv->pdev_wd[j])) {
|
||||
+ err = PTR_ERR(priv->pdev_wd[j]);
|
||||
+ goto fail_platform_wd_register;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/* Sync registers with hardware. */
|
||||
regcache_mark_dirty(mlxplat_hotplug->regmap);
|
||||
err = regcache_sync(mlxplat_hotplug->regmap);
|
||||
if (err)
|
||||
- goto fail_platform_fan_register;
|
||||
+ goto fail_platform_wd_register;
|
||||
|
||||
return 0;
|
||||
|
||||
-fail_platform_fan_register:
|
||||
+fail_platform_wd_register:
|
||||
+ while (--j >= 0)
|
||||
+ platform_device_unregister(priv->pdev_wd[j]);
|
||||
if (mlxplat_fan)
|
||||
platform_device_unregister(priv->pdev_fan);
|
||||
fail_platform_io_regs_register:
|
||||
@@ -1946,7 +2139,10 @@ static void __exit mlxplat_exit(void)
|
||||
platform_device_unregister(priv->pdev_io_regs);
|
||||
platform_device_unregister(priv->pdev_led);
|
||||
platform_device_unregister(priv->pdev_hotplug);
|
||||
-
|
||||
+ for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0 ; i--) {
|
||||
+ if (mlxplat_wd_data[i])
|
||||
+ platform_device_unregister(priv->pdev_wd[i]);
|
||||
+ }
|
||||
for (i = ARRAY_SIZE(mlxplat_mux_data) - 1; i >= 0 ; i--)
|
||||
platform_device_unregister(priv->pdev_mux[i]);
|
||||
|
||||
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
|
||||
index 3eb58cb..ada5a33 100644
|
||||
--- a/drivers/watchdog/Kconfig
|
||||
+++ b/drivers/watchdog/Kconfig
|
||||
@@ -141,6 +141,21 @@ config MENF21BMC_WATCHDOG
|
||||
This driver can also be built as a module. If so the module
|
||||
will be called menf21bmc_wdt.
|
||||
|
||||
+config MLX_WDT
|
||||
+ tristate "Mellanox Watchdog"
|
||||
+ select WATCHDOG_CORE
|
||||
+ select REGMAP
|
||||
+ ---help---
|
||||
+ This is the driver for the hardware watchdog on Mellanox systems.
|
||||
+ If you are going to use it, say Y here, otherwise N.
|
||||
+ This driver can be used together with the watchdog daemon.
|
||||
+ It can also watch your kernel to make sure it doesn't freeze,
|
||||
+ and if it does, it reboots your system after a certain amount of
|
||||
+ time.
|
||||
+
|
||||
+ To compile this driver as a module, choose M here: the
|
||||
+ module will be called mlx-wdt.
|
||||
+
|
||||
config TANGOX_WATCHDOG
|
||||
tristate "Sigma Designs SMP86xx/SMP87xx watchdog"
|
||||
select WATCHDOG_CORE
|
||||
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
|
||||
index caa9f4a..50494df 100644
|
||||
--- a/drivers/watchdog/Makefile
|
||||
+++ b/drivers/watchdog/Makefile
|
||||
@@ -139,6 +139,7 @@ obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o
|
||||
obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o
|
||||
obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o
|
||||
obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o
|
||||
+obj-$(CONFIG_MLX_WDT) += mlx_wdt.o
|
||||
|
||||
# M32R Architecture
|
||||
|
||||
diff --git a/drivers/watchdog/mlx_wdt.c b/drivers/watchdog/mlx_wdt.c
|
||||
new file mode 100644
|
||||
index 0000000..7effe8c
|
||||
--- /dev/null
|
||||
+++ b/drivers/watchdog/mlx_wdt.c
|
||||
@@ -0,0 +1,391 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0+
|
||||
+/*
|
||||
+ * Mellanox watchdog driver
|
||||
+ *
|
||||
+ * Copyright (C) 2018 Mellanox Technologies
|
||||
+ * Copyright (C) 2018 Michael Shych <mshych@mellanox.com>
|
||||
+ */
|
||||
+
|
||||
+#include <linux/bitops.h>
|
||||
+#include <linux/device.h>
|
||||
+#include <linux/errno.h>
|
||||
+#include <linux/log2.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_data/mlxreg.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+#include <linux/types.h>
|
||||
+#include <linux/watchdog.h>
|
||||
+
|
||||
+#define MLXREG_WDT_CLOCK_SCALE 1000
|
||||
+#define MLXREG_WDT_MAX_TIMEOUT_TYPE1 32
|
||||
+#define MLXREG_WDT_MAX_TIMEOUT_TYPE2 255
|
||||
+#define MLXREG_WDT_MIN_TIMEOUT 1
|
||||
+#define MLXREG_WDT_HW_TIMEOUT_CONVERT(hw_timeout) ((1 << (hw_timeout)) \
|
||||
+ / MLXREG_WDT_CLOCK_SCALE)
|
||||
+
|
||||
+/**
|
||||
+ * enum mlxreg_wdt_type - type of HW watchdog
|
||||
+ *
|
||||
+ * TYPE1 can be differentiated by different register/mask
|
||||
+ * for WD action set and ping.
|
||||
+ */
|
||||
+enum mlxreg_wdt_type {
|
||||
+ MLX_WDT_TYPE1,
|
||||
+ MLX_WDT_TYPE2,
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * struct mlxreg_wdt - wd private data:
|
||||
+ *
|
||||
+ * @wdd: watchdog device;
|
||||
+ * @device: basic device;
|
||||
+ * @pdata: data received from platform driver;
|
||||
+ * @regmap: register map of parent device;
|
||||
+ * @timeout: defined timeout in sec.;
|
||||
+ * @hw_timeout: real timeout set in hw;
|
||||
+ * It will be roundup base of 2 in WD type 1,
|
||||
+ * in WD type 2 it will be same number of sec as timeout;
|
||||
+ * @action_idx: index for direct access to action register;
|
||||
+ * @timeout_idx:index for direct access to TO register;
|
||||
+ * @ping_idx: index for direct access to ping register;
|
||||
+ * @reset_idx: index for direct access to reset cause register;
|
||||
+ * @wd_type: watchdog HW type;
|
||||
+ * @hw_timeout: actual HW timeout;
|
||||
+ * @io_lock: spinlock for io access;
|
||||
+ */
|
||||
+struct mlxreg_wdt {
|
||||
+ struct watchdog_device wdd;
|
||||
+ struct mlxreg_core_platform_data *pdata;
|
||||
+ void *regmap;
|
||||
+ int action_idx;
|
||||
+ int timeout_idx;
|
||||
+ int ping_idx;
|
||||
+ int reset_idx;
|
||||
+ enum mlxreg_wdt_type wdt_type;
|
||||
+ u8 hw_timeout;
|
||||
+ spinlock_t io_lock; /* the lock for io operations */
|
||||
+};
|
||||
+
|
||||
+static int mlxreg_wdt_roundup_to_base_2(struct mlxreg_wdt *wdt, int timeout)
|
||||
+{
|
||||
+ timeout *= MLXREG_WDT_CLOCK_SCALE;
|
||||
+
|
||||
+ wdt->hw_timeout = order_base_2(timeout);
|
||||
+ dev_info(wdt->wdd.parent,
|
||||
+ "watchdog %s timeout %d was rounded up to %lu (msec)\n",
|
||||
+ wdt->wdd.info->identity, timeout, roundup_pow_of_two(timeout));
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static enum mlxreg_wdt_type
|
||||
+mlxreg_wdt_check_watchdog_type(struct mlxreg_wdt *wdt,
|
||||
+ struct mlxreg_core_platform_data *pdata)
|
||||
+{
|
||||
+ if ((pdata->data[wdt->action_idx].reg ==
|
||||
+ pdata->data[wdt->ping_idx].reg) &&
|
||||
+ (pdata->data[wdt->action_idx].mask ==
|
||||
+ pdata->data[wdt->ping_idx].mask))
|
||||
+ return MLX_WDT_TYPE2;
|
||||
+ else
|
||||
+ return MLX_WDT_TYPE1;
|
||||
+}
|
||||
+
|
||||
+static int mlxreg_wdt_check_card_reset(struct mlxreg_wdt *wdt)
|
||||
+{
|
||||
+ struct mlxreg_core_data *reg_data;
|
||||
+ u32 regval;
|
||||
+ int rc;
|
||||
+
|
||||
+ if (wdt->reset_idx == -EINVAL)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ if (!(wdt->wdd.info->options & WDIOF_CARDRESET))
|
||||
+ return 0;
|
||||
+
|
||||
+ spin_lock(&wdt->io_lock);
|
||||
+ reg_data = &wdt->pdata->data[wdt->reset_idx];
|
||||
+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val);
|
||||
+ spin_unlock(&wdt->io_lock);
|
||||
+ if (rc)
|
||||
+ goto read_error;
|
||||
+
|
||||
+ if (regval & ~reg_data->mask) {
|
||||
+ wdt->wdd.bootstatus = WDIOF_CARDRESET;
|
||||
+ dev_info(wdt->wdd.parent,
|
||||
+ "watchdog previously reset the CPU\n");
|
||||
+ }
|
||||
+
|
||||
+read_error:
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static int mlxreg_wdt_start(struct watchdog_device *wdd)
|
||||
+{
|
||||
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx];
|
||||
+ u32 regval;
|
||||
+ int rc;
|
||||
+
|
||||
+ spin_lock(&wdt->io_lock);
|
||||
+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val);
|
||||
+ if (rc) {
|
||||
+ spin_unlock(&wdt->io_lock);
|
||||
+ goto read_error;
|
||||
+ }
|
||||
+
|
||||
+ regval = (regval & reg_data->mask) | BIT(reg_data->bit);
|
||||
+ rc = regmap_write(wdt->regmap, reg_data->reg, regval);
|
||||
+ spin_unlock(&wdt->io_lock);
|
||||
+ if (!rc) {
|
||||
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
|
||||
+ dev_info(wdt->wdd.parent, "watchdog %s started\n",
|
||||
+ wdd->info->identity);
|
||||
+ }
|
||||
+
|
||||
+read_error:
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static int mlxreg_wdt_stop(struct watchdog_device *wdd)
|
||||
+{
|
||||
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx];
|
||||
+ u32 regval;
|
||||
+ int rc;
|
||||
+
|
||||
+ spin_lock(&wdt->io_lock);
|
||||
+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val);
|
||||
+ if (rc) {
|
||||
+ spin_unlock(&wdt->io_lock);
|
||||
+ goto read_error;
|
||||
+ }
|
||||
+
|
||||
+ regval = (regval & reg_data->mask) & ~BIT(reg_data->bit);
|
||||
+ rc = regmap_write(wdt->regmap, reg_data->reg, regval);
|
||||
+ spin_unlock(&wdt->io_lock);
|
||||
+ if (!rc)
|
||||
+ dev_info(wdt->wdd.parent, "watchdog %s stopped\n",
|
||||
+ wdd->info->identity);
|
||||
+
|
||||
+read_error:
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static int mlxreg_wdt_ping(struct watchdog_device *wdd)
|
||||
+{
|
||||
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->ping_idx];
|
||||
+ u32 regval;
|
||||
+ int rc;
|
||||
+
|
||||
+ spin_lock(&wdt->io_lock);
|
||||
+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val);
|
||||
+ if (rc)
|
||||
+ goto read_error;
|
||||
+
|
||||
+ regval = (regval & reg_data->mask) | BIT(reg_data->bit);
|
||||
+ rc = regmap_write(wdt->regmap, reg_data->reg, regval);
|
||||
+
|
||||
+read_error:
|
||||
+ spin_unlock(&wdt->io_lock);
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
+ unsigned int timeout)
|
||||
+{
|
||||
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx];
|
||||
+ u32 regval;
|
||||
+ int rc;
|
||||
+
|
||||
+ spin_lock(&wdt->io_lock);
|
||||
+
|
||||
+ if (wdt->wdt_type == MLX_WDT_TYPE1) {
|
||||
+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val);
|
||||
+ if (rc)
|
||||
+ goto read_error;
|
||||
+ regval = (regval & reg_data->mask) | wdt->hw_timeout;
|
||||
+ } else {
|
||||
+ wdt->hw_timeout = timeout;
|
||||
+ regval = timeout;
|
||||
+ }
|
||||
+
|
||||
+ rc = regmap_write(wdt->regmap, reg_data->reg, regval);
|
||||
+
|
||||
+read_error:
|
||||
+ spin_unlock(&wdt->io_lock);
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static unsigned int mlxreg_wdt_get_timeleft(struct watchdog_device *wdd)
|
||||
+{
|
||||
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx];
|
||||
+ u32 regval;
|
||||
+ int rc;
|
||||
+
|
||||
+ if (wdt->wdt_type == MLX_WDT_TYPE1)
|
||||
+ return 0;
|
||||
+
|
||||
+ spin_lock(&wdt->io_lock);
|
||||
+ rc = regmap_read(wdt->regmap, reg_data->reg, ®val);
|
||||
+ if (rc)
|
||||
+ rc = 0;
|
||||
+ else
|
||||
+ rc = regval;
|
||||
+
|
||||
+ spin_unlock(&wdt->io_lock);
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static const struct watchdog_ops mlxreg_wdt_ops_type1 = {
|
||||
+ .start = mlxreg_wdt_start,
|
||||
+ .stop = mlxreg_wdt_stop,
|
||||
+ .ping = mlxreg_wdt_ping,
|
||||
+ .set_timeout = mlxreg_wdt_set_timeout,
|
||||
+ .owner = THIS_MODULE,
|
||||
+};
|
||||
+
|
||||
+static const struct watchdog_ops mlxreg_wdt_ops_type2 = {
|
||||
+ .start = mlxreg_wdt_start,
|
||||
+ .stop = mlxreg_wdt_stop,
|
||||
+ .ping = mlxreg_wdt_ping,
|
||||
+ .set_timeout = mlxreg_wdt_set_timeout,
|
||||
+ .get_timeleft = mlxreg_wdt_get_timeleft,
|
||||
+ .owner = THIS_MODULE,
|
||||
+};
|
||||
+
|
||||
+static const struct watchdog_info mlxreg_wdt_main_info = {
|
||||
+ .options = WDIOF_KEEPALIVEPING
|
||||
+ | WDIOF_MAGICCLOSE
|
||||
+ | WDIOF_SETTIMEOUT
|
||||
+ | WDIOF_CARDRESET,
|
||||
+ .identity = "mlx-wdt-main",
|
||||
+};
|
||||
+
|
||||
+static const struct watchdog_info mlxreg_wdt_aux_info = {
|
||||
+ .options = WDIOF_KEEPALIVEPING
|
||||
+ | WDIOF_MAGICCLOSE
|
||||
+ | WDIOF_SETTIMEOUT
|
||||
+ | WDIOF_ALARMONLY,
|
||||
+ .identity = "mlx-wdt-aux",
|
||||
+};
|
||||
+
|
||||
+static int mlxreg_wdt_config(struct mlxreg_wdt *wdt,
|
||||
+ struct mlxreg_core_platform_data *pdata)
|
||||
+{
|
||||
+ struct mlxreg_core_data *data = pdata->data;
|
||||
+ int i, timeout;
|
||||
+
|
||||
+ wdt->reset_idx = -EINVAL;
|
||||
+ for (i = 0; i < pdata->counter; i++, data++) {
|
||||
+ if (strnstr(data->label, "action", sizeof(data->label)))
|
||||
+ wdt->action_idx = i;
|
||||
+ else if (strnstr(data->label, "timeout", sizeof(data->label)))
|
||||
+ wdt->timeout_idx = i;
|
||||
+ else if (strnstr(data->label, "ping", sizeof(data->label)))
|
||||
+ wdt->ping_idx = i;
|
||||
+ else if (strnstr(data->label, "reset", sizeof(data->label)))
|
||||
+ wdt->reset_idx = i;
|
||||
+ }
|
||||
+
|
||||
+ wdt->pdata = pdata;
|
||||
+ if (strnstr(pdata->identity, mlxreg_wdt_main_info.identity,
|
||||
+ sizeof(mlxreg_wdt_main_info.identity)))
|
||||
+ wdt->wdd.info = &mlxreg_wdt_main_info;
|
||||
+ else
|
||||
+ wdt->wdd.info = &mlxreg_wdt_aux_info;
|
||||
+
|
||||
+ timeout = pdata->data[wdt->timeout_idx].health_cntr;
|
||||
+ wdt->wdt_type = mlxreg_wdt_check_watchdog_type(wdt, pdata);
|
||||
+ if (wdt->wdt_type == MLX_WDT_TYPE2) {
|
||||
+ wdt->hw_timeout = timeout;
|
||||
+ wdt->wdd.ops = &mlxreg_wdt_ops_type2;
|
||||
+ wdt->wdd.timeout = wdt->hw_timeout;
|
||||
+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2;
|
||||
+ } else {
|
||||
+ mlxreg_wdt_roundup_to_base_2(wdt, timeout);
|
||||
+ wdt->wdd.ops = &mlxreg_wdt_ops_type1;
|
||||
+ /* Rowndown to actual closest number of sec. */
|
||||
+ wdt->wdd.timeout =
|
||||
+ MLXREG_WDT_HW_TIMEOUT_CONVERT(wdt->hw_timeout);
|
||||
+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE1;
|
||||
+ }
|
||||
+ wdt->wdd.min_timeout = MLXREG_WDT_MIN_TIMEOUT;
|
||||
+
|
||||
+ return -EINVAL;
|
||||
+}
|
||||
+
|
||||
+static int mlxreg_wdt_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct mlxreg_core_platform_data *pdata;
|
||||
+ struct mlxreg_wdt *wdt;
|
||||
+ int rc;
|
||||
+
|
||||
+ pdata = dev_get_platdata(&pdev->dev);
|
||||
+ if (!pdata) {
|
||||
+ dev_err(&pdev->dev, "Failed to get platform data.\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
|
||||
+ if (!wdt)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ spin_lock_init(&wdt->io_lock);
|
||||
+
|
||||
+ wdt->wdd.parent = &pdev->dev;
|
||||
+ wdt->regmap = pdata->regmap;
|
||||
+ mlxreg_wdt_config(wdt, pdata);
|
||||
+
|
||||
+ if ((pdata->features & MLXREG_CORE_WD_FEATURE_NOSTOP_AFTER_START))
|
||||
+ watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT);
|
||||
+ watchdog_stop_on_reboot(&wdt->wdd);
|
||||
+ watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev);
|
||||
+ watchdog_set_drvdata(&wdt->wdd, wdt);
|
||||
+
|
||||
+ mlxreg_wdt_check_card_reset(wdt);
|
||||
+ rc = devm_watchdog_register_device(&pdev->dev, &wdt->wdd);
|
||||
+ if (rc) {
|
||||
+ dev_err(&pdev->dev,
|
||||
+ "Cannot register watchdog device (err=%d)\n", rc);
|
||||
+ return rc;
|
||||
+ }
|
||||
+
|
||||
+ mlxreg_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
|
||||
+ if ((pdata->features & MLXREG_CORE_WD_FEATURE_START_AT_BOOT))
|
||||
+ mlxreg_wdt_start(&wdt->wdd);
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+static int mlxreg_wdt_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct mlxreg_wdt *wdt = dev_get_platdata(&pdev->dev);
|
||||
+
|
||||
+ mlxreg_wdt_stop(&wdt->wdd);
|
||||
+ watchdog_unregister_device(&wdt->wdd);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver mlxreg_wdt_driver = {
|
||||
+ .probe = mlxreg_wdt_probe,
|
||||
+ .remove = mlxreg_wdt_remove,
|
||||
+ .driver = {
|
||||
+ .name = "mlx-wdt",
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+module_platform_driver(mlxreg_wdt_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Michael Shych <michaelsh@mellanox.com>");
|
||||
+MODULE_DESCRIPTION("Mellanox watchdog driver");
|
||||
+MODULE_LICENSE("GPL");
|
||||
+MODULE_ALIAS("platform:mlx-wdt");
|
||||
diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h
|
||||
index 1b2f86f..4d70c00 100644
|
||||
--- a/include/linux/platform_data/mlxreg.h
|
||||
+++ b/include/linux/platform_data/mlxreg.h
|
||||
@@ -35,6 +35,8 @@
|
||||
#define __LINUX_PLATFORM_DATA_MLXREG_H
|
||||
|
||||
#define MLXREG_CORE_LABEL_MAX_SIZE 32
|
||||
+#define MLXREG_CORE_WD_FEATURE_NOSTOP_AFTER_START BIT(0)
|
||||
+#define MLXREG_CORE_WD_FEATURE_START_AT_BOOT BIT(1)
|
||||
|
||||
/**
|
||||
* struct mlxreg_hotplug_device - I2C device data:
|
||||
@@ -112,11 +114,15 @@ struct mlxreg_core_item {
|
||||
* @data: instance private data;
|
||||
* @regmap: register map of parent device;
|
||||
* @counter: number of instances;
|
||||
+ * @features: supported features of device;
|
||||
+ * @identity: device identity name;
|
||||
*/
|
||||
struct mlxreg_core_platform_data {
|
||||
struct mlxreg_core_data *data;
|
||||
void *regmap;
|
||||
int counter;
|
||||
+ u32 features;
|
||||
+ char identity[MLXREG_CORE_LABEL_MAX_SIZE];
|
||||
};
|
||||
|
||||
/**
|
||||
--
|
||||
2.1.4
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
From 7f31d9b833b0c31f5c8d75416758a9f30add6190 Mon Sep 17 00:00:00 2001
|
||||
From: Vadim Pasternak <vadimp@mellanox.com>
|
||||
Date: Wed, 15 May 2019 15:22:44 +0000
|
||||
Subject: [PATCH backport v5.2] mlxsw: core: Prevent reading unsupported slave
|
||||
address from SFP EEPROM
|
||||
|
||||
Prevent reading unsupported slave address from SFP EEPROM by testing
|
||||
Diagnostic Monitoring Type byte in EEPROM. Read only page zero of
|
||||
EEPROM, in case this byte is zero.
|
||||
|
||||
If some SFP transceiver don't support Digital Optical Monitoring (DOM),
|
||||
thus reading SFP EEPROM slave address 0x51 could return error.
|
||||
Availability of DOM support is verified by reading from zero page
|
||||
Diagnostic Monitoring Type byte describing how diagnostic monitoring is
|
||||
implemented by transceiver. If bit 6 of this byte is set, it indicates
|
||||
that digital diagnostic monitoring has been implemented. Otherwise it is
|
||||
not and transceiver could fail to reply to transaction for slave address
|
||||
0x51 [1010001X (A2h)], which is used to access measurements page.
|
||||
|
||||
Such issue has been observed when reading cable MCP2M00-xxxx,
|
||||
MCP7F00-xxxx, and few others.
|
||||
|
||||
Signed-off-by: Vadim Pasternak <vadimp@mellanox.com>
|
||||
---
|
||||
drivers/net/ethernet/mellanox/mlxsw/core_env.c | 18 ++++++++++++++++--
|
||||
1 file changed, 16 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_env.c b/drivers/net/ethernet/mellanox/mlxsw/core_env.c
|
||||
index 7a15e932ed2f..edcf1b656cbf 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/core_env.c
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/core_env.c
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/err.h>
|
||||
+#include <linux/sfp.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "core_env.h"
|
||||
@@ -162,7 +163,7 @@ int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module,
|
||||
{
|
||||
u8 module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE];
|
||||
u16 offset = MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE;
|
||||
- u8 module_rev_id, module_id;
|
||||
+ u8 module_rev_id, module_id, diag_mon;
|
||||
unsigned int read_size;
|
||||
int err;
|
||||
|
||||
@@ -195,8 +196,21 @@ int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module,
|
||||
}
|
||||
break;
|
||||
case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP:
|
||||
+ /* Verify if transceiver provides digital diagnostic monitoring page */
|
||||
+ err = mlxsw_env_query_module_eeprom(mlxsw_core, module,
|
||||
+ SFP_DIAGMON, 1, &diag_mon,
|
||||
+ &read_size);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ if (read_size < 1)
|
||||
+ return -EIO;
|
||||
+
|
||||
modinfo->type = ETH_MODULE_SFF_8472;
|
||||
- modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
|
||||
+ if (diag_mon)
|
||||
+ modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
|
||||
+ else
|
||||
+ modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN / 2;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
--
|
||||
2.11.0
|
||||
|
||||
@@ -459,7 +459,7 @@ index 1f7ef90b2270..5f970798ab6a 100644
|
||||
+ int i;
|
||||
+
|
||||
+ for (i = thermal->tz_gearbox_num - 1; i >= 0; i--)
|
||||
+ mlxsw_thermal_module_fini(&thermal->tz_module_arr[i]);
|
||||
+ mlxsw_thermal_gearbox_tz_fini(&thermal->tz_gearbox_arr[i]);
|
||||
+ kfree(thermal->tz_gearbox_arr);
|
||||
+}
|
||||
+
|
||||
|
||||
@@ -0,0 +1,668 @@
|
||||
From 8c964a3e17bb41eef69c81d45864d72cbc1185f4 Mon Sep 17 00:00:00 2001
|
||||
From: Vadim Pasternak <vadimp@mellanox.com>
|
||||
Date: Fri, 17 May 2019 09:22:21 +0000
|
||||
Subject: [PATCH backport 5.2] mlxsw: minimal: Provide optimization for I2C bus
|
||||
access
|
||||
|
||||
Use register MTMP instead of MTBR.
|
||||
Use maximum buffer size allowed by I2C adapter.
|
||||
|
||||
Signed-off-by: Vadim Pasternak <vadimp@mellanox.com>
|
||||
---
|
||||
drivers/i2c/busses/i2c-mlxcpld.c | 2 +-
|
||||
drivers/net/ethernet/mellanox/mlxsw/core.c | 6 ++
|
||||
drivers/net/ethernet/mellanox/mlxsw/core.h | 2 +
|
||||
drivers/net/ethernet/mellanox/mlxsw/core_env.c | 29 ++-----
|
||||
drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c | 98 +++++++++-------------
|
||||
drivers/net/ethernet/mellanox/mlxsw/core_thermal.c | 60 +++++++------
|
||||
drivers/net/ethernet/mellanox/mlxsw/i2c.c | 56 +++++++++----
|
||||
drivers/net/ethernet/mellanox/mlxsw/minimal.c | 18 ++++
|
||||
drivers/net/ethernet/mellanox/mlxsw/reg.h | 7 +-
|
||||
9 files changed, 144 insertions(+), 134 deletions(-)
|
||||
|
||||
diff --git a/drivers/i2c/busses/i2c-mlxcpld.c b/drivers/i2c/busses/i2c-mlxcpld.c
|
||||
index 745ed43a22d6..2fd717d8dd30 100644
|
||||
--- a/drivers/i2c/busses/i2c-mlxcpld.c
|
||||
+++ b/drivers/i2c/busses/i2c-mlxcpld.c
|
||||
@@ -503,6 +503,7 @@ static int mlxcpld_i2c_probe(struct platform_device *pdev)
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
priv->dev = &pdev->dev;
|
||||
+ priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR;
|
||||
|
||||
/* Register with i2c layer */
|
||||
mlxcpld_i2c_adapter.timeout = usecs_to_jiffies(MLXCPLD_I2C_XFER_TO);
|
||||
@@ -518,7 +519,6 @@ static int mlxcpld_i2c_probe(struct platform_device *pdev)
|
||||
mlxcpld_i2c_adapter.nr = pdev->id;
|
||||
priv->adap = mlxcpld_i2c_adapter;
|
||||
priv->adap.dev.parent = &pdev->dev;
|
||||
- priv->base_addr = MLXPLAT_CPLD_LPC_I2C_BASE_ADDR;
|
||||
i2c_set_adapdata(&priv->adap, priv);
|
||||
|
||||
err = i2c_add_numbered_adapter(&priv->adap);
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c
|
||||
index e420451942e2..9a74ae8beb43 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/core.c
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/core.c
|
||||
@@ -122,6 +122,12 @@ void *mlxsw_core_driver_priv(struct mlxsw_core *mlxsw_core)
|
||||
}
|
||||
EXPORT_SYMBOL(mlxsw_core_driver_priv);
|
||||
|
||||
+bool mlxsw_core_res_query_enabled(const struct mlxsw_core *mlxsw_core)
|
||||
+{
|
||||
+ return mlxsw_core->driver->res_query_enabled;
|
||||
+}
|
||||
+EXPORT_SYMBOL(mlxsw_core_res_query_enabled);
|
||||
+
|
||||
struct mlxsw_rx_listener_item {
|
||||
struct list_head list;
|
||||
struct mlxsw_rx_listener rxl;
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.h b/drivers/net/ethernet/mellanox/mlxsw/core.h
|
||||
index 76e8fd76ef36..1e1bcfd8d032 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/core.h
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/core.h
|
||||
@@ -28,6 +28,8 @@ unsigned int mlxsw_core_max_ports(const struct mlxsw_core *mlxsw_core);
|
||||
|
||||
void *mlxsw_core_driver_priv(struct mlxsw_core *mlxsw_core);
|
||||
|
||||
+bool mlxsw_core_res_query_enabled(const struct mlxsw_core *mlxsw_core);
|
||||
+
|
||||
int mlxsw_core_driver_register(struct mlxsw_driver *mlxsw_driver);
|
||||
void mlxsw_core_driver_unregister(struct mlxsw_driver *mlxsw_driver);
|
||||
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_env.c b/drivers/net/ethernet/mellanox/mlxsw/core_env.c
|
||||
index edcf1b656cbf..d2c7ce67c300 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/core_env.c
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/core_env.c
|
||||
@@ -92,33 +92,20 @@ int mlxsw_env_module_temp_thresholds_get(struct mlxsw_core *core, int module,
|
||||
u16 temp;
|
||||
} temp_thresh;
|
||||
char mcia_pl[MLXSW_REG_MCIA_LEN] = {0};
|
||||
- char mtbr_pl[MLXSW_REG_MTBR_LEN] = {0};
|
||||
- u16 module_temp;
|
||||
+ char mtmp_pl[MLXSW_REG_MTMP_LEN];
|
||||
+ unsigned int module_temp;
|
||||
bool qsfp;
|
||||
int err;
|
||||
|
||||
- mlxsw_reg_mtbr_pack(mtbr_pl, MLXSW_REG_MTBR_BASE_MODULE_INDEX + module,
|
||||
- 1);
|
||||
- err = mlxsw_reg_query(core, MLXSW_REG(mtbr), mtbr_pl);
|
||||
+ mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN + module,
|
||||
+ false, false);
|
||||
+ err = mlxsw_reg_query(core, MLXSW_REG(mtmp), mtmp_pl);
|
||||
if (err)
|
||||
return err;
|
||||
-
|
||||
- /* Don't read temperature thresholds for module with no valid info. */
|
||||
- mlxsw_reg_mtbr_temp_unpack(mtbr_pl, 0, &module_temp, NULL);
|
||||
- switch (module_temp) {
|
||||
- case MLXSW_REG_MTBR_BAD_SENS_INFO: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_NO_CONN: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_NO_TEMP_SENS: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_INDEX_NA:
|
||||
+ mlxsw_reg_mtmp_unpack(mtmp_pl, &module_temp, NULL, NULL);
|
||||
+ if (!module_temp) {
|
||||
*temp = 0;
|
||||
return 0;
|
||||
- default:
|
||||
- /* Do not consider thresholds for zero temperature. */
|
||||
- if (!MLXSW_REG_MTMP_TEMP_TO_MC(module_temp)) {
|
||||
- *temp = 0;
|
||||
- return 0;
|
||||
- }
|
||||
- break;
|
||||
}
|
||||
|
||||
/* Read Free Side Device Temperature Thresholds from page 03h
|
||||
@@ -196,7 +183,7 @@ int mlxsw_env_get_module_info(struct mlxsw_core *mlxsw_core, int module,
|
||||
}
|
||||
break;
|
||||
case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP:
|
||||
- /* Verify if transceiver provides digital diagnostic monitoring page */
|
||||
+ /* Verify if transceiver provides diagnostic monitoring page */
|
||||
err = mlxsw_env_query_module_eeprom(mlxsw_core, module,
|
||||
SFP_DIAGMON, 1, &diag_mon,
|
||||
&read_size);
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c b/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c
|
||||
index 976f81a2bbba..a414a09efb5d 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/core_hwmon.c
|
||||
@@ -15,9 +15,6 @@
|
||||
#define MLXSW_HWMON_TEMP_SENSOR_MAX_COUNT 127
|
||||
#define MLXSW_HWMON_ATTR_COUNT (MLXSW_HWMON_TEMP_SENSOR_MAX_COUNT * 4 + \
|
||||
MLXSW_MFCR_TACHOS_MAX + MLXSW_MFCR_PWMS_MAX)
|
||||
-#define MLXSW_HWMON_GET_ATTR_INDEX(ind, count) \
|
||||
- (((ind) >= (count)) ? (ind) % (count) + \
|
||||
- MLXSW_REG_MTMP_GBOX_INDEX_MIN : (ind))
|
||||
|
||||
struct mlxsw_hwmon_attr {
|
||||
struct device_attribute dev_attr;
|
||||
@@ -26,6 +23,14 @@ struct mlxsw_hwmon_attr {
|
||||
char name[32];
|
||||
};
|
||||
|
||||
+static int mlxsw_hwmon_get_attr_index(int index, int count)
|
||||
+{
|
||||
+ if (index >= count)
|
||||
+ return index % count + MLXSW_REG_MTMP_GBOX_INDEX_MIN;
|
||||
+
|
||||
+ return index;
|
||||
+}
|
||||
+
|
||||
struct mlxsw_hwmon {
|
||||
struct mlxsw_core *core;
|
||||
const struct mlxsw_bus_info *bus_info;
|
||||
@@ -51,7 +56,7 @@ static ssize_t mlxsw_hwmon_temp_show(struct device *dev,
|
||||
int index;
|
||||
int err;
|
||||
|
||||
- index = MLXSW_HWMON_GET_ATTR_INDEX(mlwsw_hwmon_attr->type_index,
|
||||
+ index = mlxsw_hwmon_get_attr_index(mlwsw_hwmon_attr->type_index,
|
||||
mlxsw_hwmon->module_sensor_count);
|
||||
mlxsw_reg_mtmp_pack(mtmp_pl, index, false, false);
|
||||
err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl);
|
||||
@@ -75,7 +80,7 @@ static ssize_t mlxsw_hwmon_temp_max_show(struct device *dev,
|
||||
int index;
|
||||
int err;
|
||||
|
||||
- index = MLXSW_HWMON_GET_ATTR_INDEX(mlwsw_hwmon_attr->type_index,
|
||||
+ index = mlxsw_hwmon_get_attr_index(mlwsw_hwmon_attr->type_index,
|
||||
mlxsw_hwmon->module_sensor_count);
|
||||
mlxsw_reg_mtmp_pack(mtmp_pl, index, false, false);
|
||||
err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl);
|
||||
@@ -105,7 +110,7 @@ static ssize_t mlxsw_hwmon_temp_rst_store(struct device *dev,
|
||||
if (val != 1)
|
||||
return -EINVAL;
|
||||
|
||||
- index = MLXSW_HWMON_GET_ATTR_INDEX(mlwsw_hwmon_attr->type_index,
|
||||
+ index = mlxsw_hwmon_get_attr_index(mlwsw_hwmon_attr->type_index,
|
||||
mlxsw_hwmon->module_sensor_count);
|
||||
mlxsw_reg_mtmp_pack(mtmp_pl, index, true, true);
|
||||
err = mlxsw_reg_write(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl);
|
||||
@@ -209,38 +214,18 @@ static ssize_t mlxsw_hwmon_module_temp_show(struct device *dev,
|
||||
struct mlxsw_hwmon_attr *mlwsw_hwmon_attr =
|
||||
container_of(attr, struct mlxsw_hwmon_attr, dev_attr);
|
||||
struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon;
|
||||
- char mtbr_pl[MLXSW_REG_MTBR_LEN] = {0};
|
||||
- u16 temp;
|
||||
+ char mtmp_pl[MLXSW_REG_MTMP_LEN];
|
||||
+ unsigned int temp;
|
||||
u8 module;
|
||||
int err;
|
||||
|
||||
module = mlwsw_hwmon_attr->type_index - mlxsw_hwmon->sensor_count;
|
||||
- mlxsw_reg_mtbr_pack(mtbr_pl, MLXSW_REG_MTBR_BASE_MODULE_INDEX + module,
|
||||
- 1);
|
||||
- err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtbr), mtbr_pl);
|
||||
- if (err) {
|
||||
- dev_err(dev, "Failed to query module temperature sensor\n");
|
||||
+ mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN + module,
|
||||
+ false, false);
|
||||
+ err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl);
|
||||
+ if (err)
|
||||
return err;
|
||||
- }
|
||||
-
|
||||
- mlxsw_reg_mtbr_temp_unpack(mtbr_pl, 0, &temp, NULL);
|
||||
- /* Update status and temperature cache. */
|
||||
- switch (temp) {
|
||||
- case MLXSW_REG_MTBR_NO_CONN: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_NO_TEMP_SENS: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_INDEX_NA:
|
||||
- temp = 0;
|
||||
- break;
|
||||
- case MLXSW_REG_MTBR_BAD_SENS_INFO:
|
||||
- /* Untrusted cable is connected. Reading temperature from its
|
||||
- * sensor is faulty.
|
||||
- */
|
||||
- temp = 0;
|
||||
- break;
|
||||
- default:
|
||||
- temp = MLXSW_REG_MTMP_TEMP_TO_MC(temp);
|
||||
- break;
|
||||
- }
|
||||
+ mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL);
|
||||
|
||||
return sprintf(buf, "%u\n", temp);
|
||||
}
|
||||
@@ -252,37 +237,18 @@ static ssize_t mlxsw_hwmon_module_temp_fault_show(struct device *dev,
|
||||
struct mlxsw_hwmon_attr *mlwsw_hwmon_attr =
|
||||
container_of(attr, struct mlxsw_hwmon_attr, dev_attr);
|
||||
struct mlxsw_hwmon *mlxsw_hwmon = mlwsw_hwmon_attr->hwmon;
|
||||
- char mtbr_pl[MLXSW_REG_MTBR_LEN] = {0};
|
||||
+ char mtmp_pl[MLXSW_REG_MTMP_LEN];
|
||||
u8 module, fault;
|
||||
- u16 temp;
|
||||
int err;
|
||||
|
||||
module = mlwsw_hwmon_attr->type_index - mlxsw_hwmon->sensor_count;
|
||||
- mlxsw_reg_mtbr_pack(mtbr_pl, MLXSW_REG_MTBR_BASE_MODULE_INDEX + module,
|
||||
- 1);
|
||||
- err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtbr), mtbr_pl);
|
||||
- if (err) {
|
||||
- dev_err(dev, "Failed to query module temperature sensor\n");
|
||||
- return err;
|
||||
- }
|
||||
-
|
||||
- mlxsw_reg_mtbr_temp_unpack(mtbr_pl, 0, &temp, NULL);
|
||||
-
|
||||
- /* Update status and temperature cache. */
|
||||
- switch (temp) {
|
||||
- case MLXSW_REG_MTBR_BAD_SENS_INFO:
|
||||
- /* Untrusted cable is connected. Reading temperature from its
|
||||
- * sensor is faulty.
|
||||
- */
|
||||
+ mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTBR_BASE_MODULE_INDEX + module,
|
||||
+ false, false);
|
||||
+ err = mlxsw_reg_query(mlxsw_hwmon->core, MLXSW_REG(mtmp), mtmp_pl);
|
||||
+ if (err)
|
||||
fault = 1;
|
||||
- break;
|
||||
- case MLXSW_REG_MTBR_NO_CONN: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_NO_TEMP_SENS: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_INDEX_NA:
|
||||
- default:
|
||||
+ else
|
||||
fault = 0;
|
||||
- break;
|
||||
- }
|
||||
|
||||
return sprintf(buf, "%u\n", fault);
|
||||
}
|
||||
@@ -551,6 +517,9 @@ static int mlxsw_hwmon_module_init(struct mlxsw_hwmon *mlxsw_hwmon)
|
||||
u8 width;
|
||||
int err;
|
||||
|
||||
+ if (!mlxsw_core_res_query_enabled(mlxsw_hwmon->core))
|
||||
+ return 0;
|
||||
+
|
||||
/* Add extra attributes for module temperature. Sensor index is
|
||||
* assigned to sensor_count value, while all indexed before
|
||||
* sensor_count are already utilized by the sensors connected through
|
||||
@@ -593,8 +562,9 @@ static int mlxsw_hwmon_module_init(struct mlxsw_hwmon *mlxsw_hwmon)
|
||||
|
||||
static int mlxsw_hwmon_gearbox_init(struct mlxsw_hwmon *mlxsw_hwmon)
|
||||
{
|
||||
+ int index, max_index, sensor_index;
|
||||
char mgpir_pl[MLXSW_REG_MGPIR_LEN];
|
||||
- int index, max_index;
|
||||
+ char mtmp_pl[MLXSW_REG_MTMP_LEN];
|
||||
u8 gbox_num;
|
||||
int err;
|
||||
|
||||
@@ -610,6 +580,16 @@ static int mlxsw_hwmon_gearbox_init(struct mlxsw_hwmon *mlxsw_hwmon)
|
||||
index = mlxsw_hwmon->module_sensor_count;
|
||||
max_index = mlxsw_hwmon->module_sensor_count + gbox_num;
|
||||
while (index < max_index) {
|
||||
+ sensor_index = index % mlxsw_hwmon->module_sensor_count +
|
||||
+ MLXSW_REG_MTMP_GBOX_INDEX_MIN;
|
||||
+ mlxsw_reg_mtmp_pack(mtmp_pl, sensor_index, true, true);
|
||||
+ err = mlxsw_reg_write(mlxsw_hwmon->core,
|
||||
+ MLXSW_REG(mtmp), mtmp_pl);
|
||||
+ if (err) {
|
||||
+ dev_err(mlxsw_hwmon->bus_info->dev, "Failed to setup temp sensor number %d\n",
|
||||
+ sensor_index);
|
||||
+ return err;
|
||||
+ }
|
||||
mlxsw_hwmon_attr_add(mlxsw_hwmon, MLXSW_HWMON_ATTR_TYPE_TEMP,
|
||||
index, index);
|
||||
mlxsw_hwmon_attr_add(mlxsw_hwmon,
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c
|
||||
index 5f970798ab6a..e9451e447bf0 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/core_thermal.c
|
||||
@@ -452,36 +452,27 @@ static int mlxsw_thermal_module_temp_get(struct thermal_zone_device *tzdev,
|
||||
struct mlxsw_thermal_module *tz = tzdev->devdata;
|
||||
struct mlxsw_thermal *thermal = tz->parent;
|
||||
struct device *dev = thermal->bus_info->dev;
|
||||
- char mtbr_pl[MLXSW_REG_MTBR_LEN];
|
||||
- u16 temp;
|
||||
+ char mtmp_pl[MLXSW_REG_MTMP_LEN];
|
||||
+ unsigned int temp;
|
||||
int err;
|
||||
|
||||
/* Read module temperature. */
|
||||
- mlxsw_reg_mtbr_pack(mtbr_pl, MLXSW_REG_MTBR_BASE_MODULE_INDEX +
|
||||
- tz->module, 1);
|
||||
- err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtbr), mtbr_pl);
|
||||
- if (err)
|
||||
- return err;
|
||||
-
|
||||
- mlxsw_reg_mtbr_temp_unpack(mtbr_pl, 0, &temp, NULL);
|
||||
- /* Update temperature. */
|
||||
- switch (temp) {
|
||||
- case MLXSW_REG_MTBR_NO_CONN: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_NO_TEMP_SENS: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_INDEX_NA: /* fall-through */
|
||||
- case MLXSW_REG_MTBR_BAD_SENS_INFO:
|
||||
+ mlxsw_reg_mtmp_pack(mtmp_pl, MLXSW_REG_MTMP_MODULE_INDEX_MIN +
|
||||
+ tz->module, false, false);
|
||||
+ err = mlxsw_reg_query(thermal->core, MLXSW_REG(mtmp), mtmp_pl);
|
||||
+ if (err) {
|
||||
+ dev_err(dev, "Failed to query temp sensor\n");
|
||||
temp = 0;
|
||||
- break;
|
||||
- default:
|
||||
- temp = MLXSW_REG_MTMP_TEMP_TO_MC(temp);
|
||||
- /* Reset all trip point. */
|
||||
- mlxsw_thermal_module_trips_reset(tz);
|
||||
- /* Update trip points. */
|
||||
+ *p_temp = (int) temp;
|
||||
+ return 0;
|
||||
+ }
|
||||
+ mlxsw_reg_mtmp_unpack(mtmp_pl, &temp, NULL, NULL);
|
||||
+
|
||||
+ if (temp) {
|
||||
err = mlxsw_thermal_module_trips_update(dev, thermal->core,
|
||||
tz);
|
||||
if (err)
|
||||
return err;
|
||||
- break;
|
||||
}
|
||||
|
||||
*p_temp = (int) temp;
|
||||
@@ -714,12 +705,12 @@ mlxsw_thermal_module_tz_init(struct mlxsw_thermal_module *module_tz)
|
||||
snprintf(tz_name, sizeof(tz_name), "mlxsw-module%d",
|
||||
module_tz->module + 1);
|
||||
module_tz->tzdev = thermal_zone_device_register(tz_name,
|
||||
- MLXSW_THERMAL_NUM_TRIPS,
|
||||
- MLXSW_THERMAL_TRIP_MASK,
|
||||
- module_tz,
|
||||
- &mlxsw_thermal_module_ops,
|
||||
- &mlxsw_thermal_module_params,
|
||||
- 0, 0);
|
||||
+ MLXSW_THERMAL_NUM_TRIPS,
|
||||
+ MLXSW_THERMAL_TRIP_MASK,
|
||||
+ module_tz,
|
||||
+ &mlxsw_thermal_module_ops,
|
||||
+ &mlxsw_thermal_module_params,
|
||||
+ 0, 0);
|
||||
if (IS_ERR(module_tz->tzdev)) {
|
||||
err = PTR_ERR(module_tz->tzdev);
|
||||
return err;
|
||||
@@ -789,6 +780,9 @@ mlxsw_thermal_modules_init(struct device *dev, struct mlxsw_core *core,
|
||||
struct mlxsw_thermal_module *module_tz;
|
||||
int i, err;
|
||||
|
||||
+ if (!mlxsw_core_res_query_enabled(core))
|
||||
+ return 0;
|
||||
+
|
||||
thermal->tz_module_arr = kcalloc(module_count,
|
||||
sizeof(*thermal->tz_module_arr),
|
||||
GFP_KERNEL);
|
||||
@@ -801,8 +795,7 @@ mlxsw_thermal_modules_init(struct device *dev, struct mlxsw_core *core,
|
||||
goto err_unreg_tz_module_arr;
|
||||
}
|
||||
|
||||
- module_count -= 1;
|
||||
- for (i = 0; i < module_count; i++) {
|
||||
+ for (i = 0; i < module_count - 1; i++) {
|
||||
module_tz = &thermal->tz_module_arr[i];
|
||||
if (!module_tz->parent)
|
||||
continue;
|
||||
@@ -814,7 +807,7 @@ mlxsw_thermal_modules_init(struct device *dev, struct mlxsw_core *core,
|
||||
return 0;
|
||||
|
||||
err_unreg_tz_module_arr:
|
||||
- for (i = module_count; i >= 0; i--)
|
||||
+ for (i = module_count - 1; i >= 0; i--)
|
||||
mlxsw_thermal_module_fini(&thermal->tz_module_arr[i]);
|
||||
kfree(thermal->tz_module_arr);
|
||||
return err;
|
||||
@@ -826,6 +819,9 @@ mlxsw_thermal_modules_fini(struct mlxsw_thermal *thermal)
|
||||
unsigned int module_count = mlxsw_core_max_ports(thermal->core);
|
||||
int i;
|
||||
|
||||
+ if (!mlxsw_core_res_query_enabled(thermal->core))
|
||||
+ return;
|
||||
+
|
||||
for (i = module_count - 1; i >= 0; i--)
|
||||
mlxsw_thermal_module_fini(&thermal->tz_module_arr[i]);
|
||||
kfree(thermal->tz_module_arr);
|
||||
@@ -910,7 +906,7 @@ mlxsw_thermal_gearboxes_fini(struct mlxsw_thermal *thermal)
|
||||
int i;
|
||||
|
||||
for (i = thermal->tz_gearbox_num - 1; i >= 0; i--)
|
||||
- mlxsw_thermal_gearbox_tz_fini(&thermal->tz_gearbox_arr[i]);
|
||||
+ mlxsw_thermal_gearbox_tz_fini(&thermal->tz_gearbox_arr[i]); /*Remove*/
|
||||
kfree(thermal->tz_gearbox_arr);
|
||||
}
|
||||
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c
|
||||
index 307fd5fcd302..08c774875d1e 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c
|
||||
@@ -43,11 +43,10 @@
|
||||
#define MLXSW_I2C_PREP_SIZE (MLXSW_I2C_ADDR_WIDTH + 28)
|
||||
#define MLXSW_I2C_MBOX_SIZE 20
|
||||
#define MLXSW_I2C_MBOX_OUT_PARAM_OFF 12
|
||||
-#define MLXSW_I2C_MAX_BUFF_SIZE 32
|
||||
#define MLXSW_I2C_MBOX_OFFSET_BITS 20
|
||||
#define MLXSW_I2C_MBOX_SIZE_BITS 12
|
||||
#define MLXSW_I2C_ADDR_BUF_SIZE 4
|
||||
-#define MLXSW_I2C_BLK_MAX 32
|
||||
+#define MLXSW_I2C_BLK_DEF 32
|
||||
#define MLXSW_I2C_RETRY 5
|
||||
#define MLXSW_I2C_TIMEOUT_MSECS 5000
|
||||
#define MLXSW_I2C_MAX_DATA_SIZE 256
|
||||
@@ -62,6 +61,7 @@
|
||||
* @dev: I2C device;
|
||||
* @core: switch core pointer;
|
||||
* @bus_info: bus info block;
|
||||
+ * @block_size: maximum block size allowed to pass to under layer;
|
||||
*/
|
||||
struct mlxsw_i2c {
|
||||
struct {
|
||||
@@ -74,6 +74,7 @@ struct mlxsw_i2c {
|
||||
struct device *dev;
|
||||
struct mlxsw_core *core;
|
||||
struct mlxsw_bus_info bus_info;
|
||||
+ u16 block_size;
|
||||
};
|
||||
|
||||
#define MLXSW_I2C_READ_MSG(_client, _addr_buf, _buf, _len) { \
|
||||
@@ -315,20 +316,26 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num,
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client);
|
||||
unsigned long timeout = msecs_to_jiffies(MLXSW_I2C_TIMEOUT_MSECS);
|
||||
- u8 tran_buf[MLXSW_I2C_MAX_BUFF_SIZE + MLXSW_I2C_ADDR_BUF_SIZE];
|
||||
int off = mlxsw_i2c->cmd.mb_off_in, chunk_size, i, j;
|
||||
unsigned long end;
|
||||
+ u8 *tran_buf;
|
||||
struct i2c_msg write_tran =
|
||||
- MLXSW_I2C_WRITE_MSG(client, tran_buf, MLXSW_I2C_PUSH_CMD_SIZE);
|
||||
+ MLXSW_I2C_WRITE_MSG(client, NULL, MLXSW_I2C_PUSH_CMD_SIZE);
|
||||
int err;
|
||||
|
||||
+ tran_buf = kmalloc(mlxsw_i2c->block_size + MLXSW_I2C_ADDR_BUF_SIZE,
|
||||
+ GFP_KERNEL);
|
||||
+ if (!tran_buf)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ write_tran.buf = tran_buf;
|
||||
for (i = 0; i < num; i++) {
|
||||
- chunk_size = (in_mbox_size > MLXSW_I2C_BLK_MAX) ?
|
||||
- MLXSW_I2C_BLK_MAX : in_mbox_size;
|
||||
+ chunk_size = (in_mbox_size > mlxsw_i2c->block_size) ?
|
||||
+ mlxsw_i2c->block_size : in_mbox_size;
|
||||
write_tran.len = MLXSW_I2C_ADDR_WIDTH + chunk_size;
|
||||
mlxsw_i2c_set_slave_addr(tran_buf, off);
|
||||
memcpy(&tran_buf[MLXSW_I2C_ADDR_BUF_SIZE], in_mbox +
|
||||
- MLXSW_I2C_BLK_MAX * i, chunk_size);
|
||||
+ mlxsw_i2c->block_size * i, chunk_size);
|
||||
|
||||
j = 0;
|
||||
end = jiffies + timeout;
|
||||
@@ -342,9 +349,10 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num,
|
||||
(j++ < MLXSW_I2C_RETRY));
|
||||
|
||||
if (err != 1) {
|
||||
- if (!err)
|
||||
+ if (!err) {
|
||||
err = -EIO;
|
||||
- return err;
|
||||
+ goto mlxsw_i2c_write_exit;
|
||||
+ }
|
||||
}
|
||||
|
||||
off += chunk_size;
|
||||
@@ -355,24 +363,28 @@ mlxsw_i2c_write(struct device *dev, size_t in_mbox_size, u8 *in_mbox, int num,
|
||||
err = mlxsw_i2c_write_cmd(client, mlxsw_i2c, 0);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "Could not start transaction");
|
||||
- return -EIO;
|
||||
+ err = -EIO;
|
||||
+ goto mlxsw_i2c_write_exit;
|
||||
}
|
||||
|
||||
/* Wait until go bit is cleared. */
|
||||
err = mlxsw_i2c_wait_go_bit(client, mlxsw_i2c, p_status);
|
||||
if (err) {
|
||||
dev_err(&client->dev, "HW semaphore is not released");
|
||||
- return err;
|
||||
+ goto mlxsw_i2c_write_exit;
|
||||
}
|
||||
|
||||
/* Validate transaction completion status. */
|
||||
if (*p_status) {
|
||||
dev_err(&client->dev, "Bad transaction completion status %x\n",
|
||||
*p_status);
|
||||
- return -EIO;
|
||||
+ err = -EIO;
|
||||
}
|
||||
|
||||
- return 0;
|
||||
+mlxsw_i2c_write_exit:
|
||||
+ kfree(tran_buf);
|
||||
+
|
||||
+ return err;
|
||||
}
|
||||
|
||||
/* Routine executes I2C command. */
|
||||
@@ -395,8 +407,8 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size,
|
||||
|
||||
if (in_mbox) {
|
||||
reg_size = mlxsw_i2c_get_reg_size(in_mbox);
|
||||
- num = reg_size / MLXSW_I2C_BLK_MAX;
|
||||
- if (reg_size % MLXSW_I2C_BLK_MAX)
|
||||
+ num = reg_size / mlxsw_i2c->block_size;
|
||||
+ if (reg_size % mlxsw_i2c->block_size)
|
||||
num++;
|
||||
|
||||
if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) {
|
||||
@@ -416,7 +428,7 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size,
|
||||
} else {
|
||||
/* No input mailbox is case of initialization query command. */
|
||||
reg_size = MLXSW_I2C_MAX_DATA_SIZE;
|
||||
- num = reg_size / MLXSW_I2C_BLK_MAX;
|
||||
+ num = reg_size / mlxsw_i2c->block_size;
|
||||
|
||||
if (mutex_lock_interruptible(&mlxsw_i2c->cmd.lock) < 0) {
|
||||
dev_err(&client->dev, "Could not acquire lock");
|
||||
@@ -432,8 +444,8 @@ mlxsw_i2c_cmd(struct device *dev, u16 opcode, u32 in_mod, size_t in_mbox_size,
|
||||
/* Send read transaction to get output mailbox content. */
|
||||
read_tran[1].buf = out_mbox;
|
||||
for (i = 0; i < num; i++) {
|
||||
- chunk_size = (reg_size > MLXSW_I2C_BLK_MAX) ?
|
||||
- MLXSW_I2C_BLK_MAX : reg_size;
|
||||
+ chunk_size = (reg_size > mlxsw_i2c->block_size) ?
|
||||
+ mlxsw_i2c->block_size : reg_size;
|
||||
read_tran[1].len = chunk_size;
|
||||
mlxsw_i2c_set_slave_addr(tran_buf, off);
|
||||
|
||||
@@ -546,6 +558,7 @@ static const struct mlxsw_bus mlxsw_i2c_bus = {
|
||||
static int mlxsw_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
+ const struct i2c_adapter_quirks *quirks = client->adapter->quirks;
|
||||
struct mlxsw_i2c *mlxsw_i2c;
|
||||
u8 status;
|
||||
int err;
|
||||
@@ -554,6 +567,13 @@ static int mlxsw_i2c_probe(struct i2c_client *client,
|
||||
if (!mlxsw_i2c)
|
||||
return -ENOMEM;
|
||||
|
||||
+ if (quirks)
|
||||
+ mlxsw_i2c->block_size = max_t(u16, MLXSW_I2C_BLK_DEF,
|
||||
+ min_t(u16, quirks->max_read_len,
|
||||
+ quirks->max_write_len));
|
||||
+ else
|
||||
+ mlxsw_i2c->block_size = MLXSW_I2C_BLK_DEF;
|
||||
+
|
||||
i2c_set_clientdata(client, mlxsw_i2c);
|
||||
mutex_init(&mlxsw_i2c->cmd.lock);
|
||||
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/minimal.c b/drivers/net/ethernet/mellanox/mlxsw/minimal.c
|
||||
index cac36c231864..5290993ff93f 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/minimal.c
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/minimal.c
|
||||
@@ -45,6 +45,23 @@ static const struct net_device_ops mlxsw_m_port_netdev_ops = {
|
||||
.ndo_stop = mlxsw_m_port_dummy_open_stop,
|
||||
};
|
||||
|
||||
+static void mlxsw_m_module_get_drvinfo(struct net_device *dev,
|
||||
+ struct ethtool_drvinfo *drvinfo)
|
||||
+{
|
||||
+ struct mlxsw_m_port *mlxsw_m_port = netdev_priv(dev);
|
||||
+ struct mlxsw_m *mlxsw_m = mlxsw_m_port->mlxsw_m;
|
||||
+
|
||||
+ strlcpy(drvinfo->driver, mlxsw_m->bus_info->device_kind,
|
||||
+ sizeof(drvinfo->driver));
|
||||
+ snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version),
|
||||
+ "%d.%d.%d",
|
||||
+ mlxsw_m->bus_info->fw_rev.major,
|
||||
+ mlxsw_m->bus_info->fw_rev.minor,
|
||||
+ mlxsw_m->bus_info->fw_rev.subminor);
|
||||
+ strlcpy(drvinfo->bus_info, mlxsw_m->bus_info->device_name,
|
||||
+ sizeof(drvinfo->bus_info));
|
||||
+}
|
||||
+
|
||||
static int mlxsw_m_get_module_info(struct net_device *netdev,
|
||||
struct ethtool_modinfo *modinfo)
|
||||
{
|
||||
@@ -66,6 +83,7 @@ mlxsw_m_get_module_eeprom(struct net_device *netdev, struct ethtool_eeprom *ee,
|
||||
}
|
||||
|
||||
static const struct ethtool_ops mlxsw_m_port_ethtool_ops = {
|
||||
+ .get_drvinfo = mlxsw_m_module_get_drvinfo,
|
||||
.get_module_info = mlxsw_m_get_module_info,
|
||||
.get_module_eeprom = mlxsw_m_get_module_eeprom,
|
||||
};
|
||||
diff --git a/drivers/net/ethernet/mellanox/mlxsw/reg.h b/drivers/net/ethernet/mellanox/mlxsw/reg.h
|
||||
index f16d27e115d2..eb58940d2e9c 100644
|
||||
--- a/drivers/net/ethernet/mellanox/mlxsw/reg.h
|
||||
+++ b/drivers/net/ethernet/mellanox/mlxsw/reg.h
|
||||
@@ -7589,17 +7589,18 @@ MLXSW_ITEM32(reg, mtcap, sensor_count, 0x00, 0, 7);
|
||||
*/
|
||||
#define MLXSW_REG_MTMP_ID 0x900A
|
||||
#define MLXSW_REG_MTMP_LEN 0x20
|
||||
-#define MLXSW_REG_MTMP_GBOX_INDEX_MIN 256
|
||||
|
||||
MLXSW_REG_DEFINE(mtmp, MLXSW_REG_MTMP_ID, MLXSW_REG_MTMP_LEN);
|
||||
|
||||
+#define MLXSW_REG_MTMP_MODULE_INDEX_MIN 64
|
||||
+#define MLXSW_REG_MTMP_GBOX_INDEX_MIN 256
|
||||
/* reg_mtmp_sensor_index
|
||||
* Sensors index to access.
|
||||
* 64-127 of sensor_index are mapped to the SFP+/QSFP modules sequentially
|
||||
* (module 0 is mapped to sensor_index 64).
|
||||
* Access: Index
|
||||
*/
|
||||
-MLXSW_ITEM32(reg, mtmp, sensor_index, 0x00, 0, 11);
|
||||
+MLXSW_ITEM32(reg, mtmp, sensor_index, 0x00, 0, 12);
|
||||
|
||||
/* Convert to milli degrees Celsius */
|
||||
#define MLXSW_REG_MTMP_TEMP_TO_MC(val) (val * 125)
|
||||
@@ -7710,7 +7711,7 @@ MLXSW_REG_DEFINE(mtbr, MLXSW_REG_MTBR_ID, MLXSW_REG_MTBR_LEN);
|
||||
* 64-127 are mapped to the SFP+/QSFP modules sequentially).
|
||||
* Access: Index
|
||||
*/
|
||||
-MLXSW_ITEM32(reg, mtbr, base_sensor_index, 0x00, 0, 7);
|
||||
+MLXSW_ITEM32(reg, mtbr, base_sensor_index, 0x00, 0, 12);
|
||||
|
||||
/* reg_mtbr_num_rec
|
||||
* Request: Number of records to read
|
||||
--
|
||||
2.11.0
|
||||
|
||||
@@ -19,6 +19,7 @@ driver-add-the-support-max6620.patch
|
||||
0016-qsfp_sysfs-Fix-dmidecode-call.patch
|
||||
0017-mlxsw-qsfp_sysfs-Support-extended-port-numbers-for-S.patch
|
||||
0018-mlxsw-thermal-monitoring-amendments.patch
|
||||
0019-watchdog-mlx-wdt-introduce-watchdog-driver-for-Mella.patch
|
||||
0020-mlxsw-qsfp_sysfs-Support-port-numbers-initialization.patch
|
||||
0021-mlxsw-Align-code-with-kernel-v-5.0.patch
|
||||
0023-hwmon-pmbus-Fix-driver-info-initialization-in-probe-.patch
|
||||
@@ -27,4 +28,6 @@ driver-add-the-support-max6620.patch
|
||||
0026-mlxsw-Align-code-with-kernel-v-5.1.patch
|
||||
0027-mlxsw-core-Add-check-for-split-port-during-thermal-z.patch
|
||||
0028-watchdog-mlx-wdt-kernel-upstream-and-wd-type2-change.patch
|
||||
0029-mlxsw-core-Prevent-reading-unsupported-slave-address.patch
|
||||
0029-mlxsw-core-add-support-for-Gear-Box-temperatures-in-.patch
|
||||
0030-mlxsw-minimal-Provide-optimization-for-I2C-bus-acces.patch
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>x86-64-mlnx-msn3800</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
Reference in New Issue
Block a user