199 lines
6.1 KiB
Diff
199 lines
6.1 KiB
Diff
From c5235b3c4a8ab2b758140d75a7422117e917478c Mon Sep 17 00:00:00 2001
|
|
From: Vadim Pasternak <vadimp@nvidia.com>
|
|
Date: Sun, 19 Dec 2021 09:12:58 +0000
|
|
Subject: [PATCH] mlxsw: i2c: Add support for system events handling
|
|
|
|
Extend i2c bus driver with interrupt handler to support system specific
|
|
hotplug events, related to line card state change.
|
|
Provide system IRQ line for interrupt handler. Line Id could be
|
|
provided through the platform data if available, or could be set to the
|
|
default value.
|
|
Handler is supposed to be set by "mlxsw" driver through bus driver init()
|
|
call.
|
|
|
|
Signed-off-by: Vadim Pasternak <vadimp@nvidia.com>
|
|
---
|
|
drivers/net/ethernet/mellanox/mlxsw/i2c.c | 110 ++++++++++++++++++++++
|
|
1 file changed, 110 insertions(+)
|
|
|
|
diff --git a/drivers/net/ethernet/mellanox/mlxsw/i2c.c b/drivers/net/ethernet/mellanox/mlxsw/i2c.c
|
|
index b75416561..e5883b4e8 100644
|
|
--- a/drivers/net/ethernet/mellanox/mlxsw/i2c.c
|
|
+++ b/drivers/net/ethernet/mellanox/mlxsw/i2c.c
|
|
@@ -9,6 +9,7 @@
|
|
#include <linux/mutex.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mod_devicetable.h>
|
|
+#include <linux/platform_data/mlxreg.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "cmd.h"
|
|
@@ -51,6 +52,12 @@
|
|
#define MLXSW_I2C_TIMEOUT_MSECS 5000
|
|
#define MLXSW_I2C_MAX_DATA_SIZE 256
|
|
|
|
+#define MLXSW_I2C_WORK_ARMED 1
|
|
+#define MLXSW_I2C_WORK_CLOSED GENMASK(31, 0)
|
|
+#define MLXSW_I2C_WORK_DELAY (usecs_to_jiffies(100))
|
|
+#define MLXSW_I2C_DEFAULT_IRQ 17
|
|
+#define MLXSW_I2C_VIRT_SLAVE 0x37
|
|
+
|
|
/**
|
|
* struct mlxsw_i2c - device private data:
|
|
* @cmd: command attributes;
|
|
@@ -64,6 +71,12 @@
|
|
* @bus_info: bus info block;
|
|
* @block_size: maximum block size allowed to pass to under layer;
|
|
* @status: status to indicate chip reset or in-service update;
|
|
+ * @pdata: device platform data;
|
|
+ * @dwork_irq: interrupts delayed work queue;
|
|
+ * @lock - lock for interrupts sync;
|
|
+ * @sys_event_handler: system events handler callback;
|
|
+ * @irq: IRQ line number;
|
|
+ * @irq_unhandled_count: number of unhandled interrupts;
|
|
*/
|
|
struct mlxsw_i2c {
|
|
struct {
|
|
@@ -78,6 +91,12 @@ struct mlxsw_i2c {
|
|
struct mlxsw_bus_info bus_info;
|
|
u16 block_size;
|
|
u8 status;
|
|
+ struct mlxreg_core_hotplug_platform_data *pdata;
|
|
+ struct delayed_work dwork_irq;
|
|
+ spinlock_t lock; /* sync with interrupt */
|
|
+ void (*sys_event_handler)(struct mlxsw_core *mlxsw_core);
|
|
+ int irq;
|
|
+ atomic_t irq_unhandled_count;
|
|
};
|
|
|
|
#define MLXSW_I2C_READ_MSG(_client, _addr_buf, _buf, _len) { \
|
|
@@ -538,6 +557,7 @@ mlxsw_i2c_init(void *bus_priv, struct mlxsw_core *mlxsw_core,
|
|
int err;
|
|
|
|
mlxsw_i2c->core = mlxsw_core;
|
|
+ mlxsw_i2c->sys_event_handler = sys_event_handler;
|
|
|
|
mbox = mlxsw_cmd_mbox_alloc();
|
|
if (!mbox)
|
|
@@ -568,6 +588,87 @@ static void mlxsw_i2c_fini(void *bus_priv)
|
|
mlxsw_i2c->core = NULL;
|
|
}
|
|
|
|
+static void mlxsw_i2c_work_handler(struct work_struct *work)
|
|
+{
|
|
+ struct mlxsw_i2c *mlxsw_i2c;
|
|
+ unsigned long flags;
|
|
+
|
|
+ mlxsw_i2c = container_of(work, struct mlxsw_i2c, dwork_irq.work);
|
|
+
|
|
+ if (atomic_read(&mlxsw_i2c->irq_unhandled_count)) {
|
|
+ if (atomic_dec_and_test(&mlxsw_i2c->irq_unhandled_count))
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mlxsw_i2c->sys_event_handler(mlxsw_i2c->core);
|
|
+
|
|
+ spin_lock_irqsave(&mlxsw_i2c->lock, flags);
|
|
+
|
|
+ /* It is possible, that some signals have been inserted, while
|
|
+ * interrupts has been masked. In this case such signals could be missed.
|
|
+ * In order to handle these signals delayed work is canceled and work task
|
|
+ * re-scheduled for immediate execution. It allows to handle missed
|
|
+ * signals, if any. In other case work handler just validates that no new
|
|
+ * signals have been received during masking.
|
|
+ */
|
|
+ cancel_delayed_work(&mlxsw_i2c->dwork_irq);
|
|
+ schedule_delayed_work(&mlxsw_i2c->dwork_irq, MLXSW_I2C_WORK_DELAY);
|
|
+
|
|
+ spin_unlock_irqrestore(&mlxsw_i2c->lock, flags);
|
|
+
|
|
+ if (!atomic_read(&mlxsw_i2c->irq_unhandled_count))
|
|
+ atomic_set(&mlxsw_i2c->irq_unhandled_count, MLXSW_I2C_WORK_ARMED);
|
|
+}
|
|
+
|
|
+static irqreturn_t mlxsw_i2c_irq_handler(int irq, void *dev)
|
|
+{
|
|
+ struct mlxsw_i2c *mlxsw_i2c = (struct mlxsw_i2c *)dev;
|
|
+
|
|
+ /* Schedule work task for immediate execution.*/
|
|
+ schedule_delayed_work(&mlxsw_i2c->dwork_irq, 0);
|
|
+
|
|
+ return IRQ_NONE;
|
|
+}
|
|
+
|
|
+static int mlxsw_i2c_event_handler_register(struct mlxsw_i2c *mlxsw_i2c)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ /* Initialize interrupt handler if system hotplug driver is reachable
|
|
+ * and platform data is available.
|
|
+ */
|
|
+ if (!IS_REACHABLE(CONFIG_MLXREG_HOTPLUG))
|
|
+ return 0;
|
|
+
|
|
+ if (mlxsw_i2c->pdata && mlxsw_i2c->pdata->irq)
|
|
+ mlxsw_i2c->irq = mlxsw_i2c->pdata->irq;
|
|
+
|
|
+ if (!mlxsw_i2c->irq)
|
|
+ return 0;
|
|
+
|
|
+ err = request_irq(mlxsw_i2c->irq, mlxsw_i2c_irq_handler,
|
|
+ IRQF_TRIGGER_FALLING | IRQF_SHARED, "mlxsw-i2c",
|
|
+ mlxsw_i2c);
|
|
+ if (err) {
|
|
+ dev_err(mlxsw_i2c->bus_info.dev, "Failed to request irq: %d\n",
|
|
+ err);
|
|
+ return err;
|
|
+ }
|
|
+
|
|
+ spin_lock_init(&mlxsw_i2c->lock);
|
|
+ INIT_DELAYED_WORK(&mlxsw_i2c->dwork_irq, mlxsw_i2c_work_handler);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void mlxsw_i2c_event_handler_unregister(struct mlxsw_i2c *mlxsw_i2c)
|
|
+{
|
|
+ if (!IS_REACHABLE(CONFIG_MLXREG_HOTPLUG) || !mlxsw_i2c->irq)
|
|
+ return;
|
|
+ cancel_delayed_work_sync(&mlxsw_i2c->dwork_irq);
|
|
+ free_irq(mlxsw_i2c->irq, mlxsw_i2c);
|
|
+}
|
|
+
|
|
static const struct mlxsw_bus mlxsw_i2c_bus = {
|
|
.kind = "i2c",
|
|
.init = mlxsw_i2c_init,
|
|
@@ -662,6 +763,7 @@ static int mlxsw_i2c_probe(struct i2c_client *client,
|
|
mlxsw_i2c->bus_info.dev = &client->dev;
|
|
mlxsw_i2c->bus_info.low_frequency = true;
|
|
mlxsw_i2c->dev = &client->dev;
|
|
+ mlxsw_i2c->pdata = client->dev.platform_data;
|
|
|
|
err = mlxsw_core_bus_device_register(&mlxsw_i2c->bus_info,
|
|
&mlxsw_i2c_bus, mlxsw_i2c, false,
|
|
@@ -671,6 +773,12 @@ static int mlxsw_i2c_probe(struct i2c_client *client,
|
|
return err;
|
|
}
|
|
|
|
+ if (client->addr == MLXSW_I2C_VIRT_SLAVE)
|
|
+ mlxsw_i2c->irq = MLXSW_I2C_DEFAULT_IRQ;
|
|
+ err = mlxsw_i2c_event_handler_register(mlxsw_i2c);
|
|
+ if (err)
|
|
+ return err;
|
|
+
|
|
return 0;
|
|
|
|
errout:
|
|
@@ -684,6 +792,8 @@ static int mlxsw_i2c_remove(struct i2c_client *client)
|
|
{
|
|
struct mlxsw_i2c *mlxsw_i2c = i2c_get_clientdata(client);
|
|
|
|
+ atomic_set(&mlxsw_i2c->irq_unhandled_count, MLXSW_I2C_WORK_CLOSED);
|
|
+ mlxsw_i2c_event_handler_unregister(mlxsw_i2c);
|
|
mlxsw_core_bus_device_unregister(mlxsw_i2c->core, false);
|
|
mutex_destroy(&mlxsw_i2c->cmd.lock);
|
|
|
|
--
|
|
2.30.2
|
|
|