Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1020: Linux patches #219

Merged
merged 1 commit into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
From e56ee640d1213b83c2c926ecb8c0b4e709cd0049 Mon Sep 17 00:00:00 2001
From: Andrew Jeffery <andrew@aj.id.au>
Date: Tue, 1 Sep 2020 20:58:21 +0930
Subject: [PATCH 01/15] i2c: Allow throttling of transfers to client devices

Some devices fail to cope in disastrous ways with the small command
turn-around times enabled by in-kernel device drivers.

Introduce per-client throttling of transfers to avoid these issues.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
drivers/i2c/i2c-core-base.c | 8 +-
drivers/i2c/i2c-core-smbus.c | 169 +++++++++++++++++++++++++++++------
drivers/i2c/i2c-core.h | 21 +++++
include/linux/i2c.h | 5 ++
4 files changed, 172 insertions(+), 31 deletions(-)

diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index 0ebde16bef33..dba7d358344c 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -914,13 +914,17 @@ int i2c_dev_irq_from_resources(const struct resource *resources,
struct i2c_client *
i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
+ struct i2c_client_priv *priv;
struct i2c_client *client;
int status;

- client = kzalloc(sizeof *client, GFP_KERNEL);
- if (!client)
+ priv = kzalloc(sizeof *priv, GFP_KERNEL);
+ if (!priv)
return ERR_PTR(-ENOMEM);

+ mutex_init(&priv->throttle_lock);
+ client = &priv->client;
+
client->adapter = adap;

client->dev.platform_data = info->platform_data;
diff --git a/drivers/i2c/i2c-core-smbus.c b/drivers/i2c/i2c-core-smbus.c
index e5b2d1465e7e..5485a21faf9b 100644
--- a/drivers/i2c/i2c-core-smbus.c
+++ b/drivers/i2c/i2c-core-smbus.c
@@ -10,6 +10,7 @@
* SMBus 2.0 support by Mark Studebaker <mdsxyz123@yahoo.com> and
* Jean Delvare <jdelvare@suse.de>
*/
+#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
@@ -21,6 +22,9 @@
#define CREATE_TRACE_POINTS
#include <trace/events/smbus.h>

+static s32 i2c_smbus_throttle_xfer(const struct i2c_client *client,
+ char read_write, u8 command, int protocol,
+ union i2c_smbus_data *data);

/* The SMBus parts */

@@ -103,9 +107,9 @@ s32 i2c_smbus_read_byte(const struct i2c_client *client)
union i2c_smbus_data data;
int status;

- status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_READ, 0,
- I2C_SMBUS_BYTE, &data);
+ status = i2c_smbus_throttle_xfer(client, I2C_SMBUS_READ, 0,
+ I2C_SMBUS_BYTE, &data);
+
return (status < 0) ? status : data.byte;
}
EXPORT_SYMBOL(i2c_smbus_read_byte);
@@ -120,8 +124,8 @@ EXPORT_SYMBOL(i2c_smbus_read_byte);
*/
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
{
- return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL);
+ return i2c_smbus_throttle_xfer(client, I2C_SMBUS_WRITE, value,
+ I2C_SMBUS_BYTE, NULL);
}
EXPORT_SYMBOL(i2c_smbus_write_byte);

@@ -138,9 +142,9 @@ s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)
union i2c_smbus_data data;
int status;

- status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_READ, command,
- I2C_SMBUS_BYTE_DATA, &data);
+ status = i2c_smbus_throttle_xfer(client, I2C_SMBUS_READ, command,
+ I2C_SMBUS_BYTE_DATA, &data);
+
return (status < 0) ? status : data.byte;
}
EXPORT_SYMBOL(i2c_smbus_read_byte_data);
@@ -158,10 +162,10 @@ s32 i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command,
u8 value)
{
union i2c_smbus_data data;
+
data.byte = value;
- return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_WRITE, command,
- I2C_SMBUS_BYTE_DATA, &data);
+ return i2c_smbus_throttle_xfer(client, I2C_SMBUS_WRITE, command,
+ I2C_SMBUS_BYTE_DATA, &data);
}
EXPORT_SYMBOL(i2c_smbus_write_byte_data);

@@ -178,9 +182,9 @@ s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command)
union i2c_smbus_data data;
int status;

- status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_READ, command,
- I2C_SMBUS_WORD_DATA, &data);
+ status = i2c_smbus_throttle_xfer(client, I2C_SMBUS_READ, command,
+ I2C_SMBUS_WORD_DATA, &data);
+
return (status < 0) ? status : data.word;
}
EXPORT_SYMBOL(i2c_smbus_read_word_data);
@@ -198,10 +202,10 @@ s32 i2c_smbus_write_word_data(const struct i2c_client *client, u8 command,
u16 value)
{
union i2c_smbus_data data;
+
data.word = value;
- return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_WRITE, command,
- I2C_SMBUS_WORD_DATA, &data);
+ return i2c_smbus_throttle_xfer(client, I2C_SMBUS_WRITE, command,
+ I2C_SMBUS_WORD_DATA, &data);
}
EXPORT_SYMBOL(i2c_smbus_write_word_data);

@@ -226,9 +230,9 @@ s32 i2c_smbus_read_block_data(const struct i2c_client *client, u8 command,
union i2c_smbus_data data;
int status;

- status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_READ, command,
- I2C_SMBUS_BLOCK_DATA, &data);
+ status = i2c_smbus_throttle_xfer(client, I2C_SMBUS_READ, command,
+ I2C_SMBUS_BLOCK_DATA, &data);
+
if (status)
return status;

@@ -256,9 +260,8 @@ s32 i2c_smbus_write_block_data(const struct i2c_client *client, u8 command,
length = I2C_SMBUS_BLOCK_MAX;
data.block[0] = length;
memcpy(&data.block[1], values, length);
- return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_WRITE, command,
- I2C_SMBUS_BLOCK_DATA, &data);
+ return i2c_smbus_throttle_xfer(client, I2C_SMBUS_WRITE, command,
+ I2C_SMBUS_BLOCK_DATA, &data);
}
EXPORT_SYMBOL(i2c_smbus_write_block_data);

@@ -272,9 +275,9 @@ s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client, u8 command,
if (length > I2C_SMBUS_BLOCK_MAX)
length = I2C_SMBUS_BLOCK_MAX;
data.block[0] = length;
- status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_READ, command,
- I2C_SMBUS_I2C_BLOCK_DATA, &data);
+ status = i2c_smbus_throttle_xfer(client, I2C_SMBUS_READ, command,
+ I2C_SMBUS_I2C_BLOCK_DATA, &data);
+
if (status < 0)
return status;

@@ -292,9 +295,8 @@ s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client, u8 command,
length = I2C_SMBUS_BLOCK_MAX;
data.block[0] = length;
memcpy(data.block + 1, values, length);
- return i2c_smbus_xfer(client->adapter, client->addr, client->flags,
- I2C_SMBUS_WRITE, command,
- I2C_SMBUS_I2C_BLOCK_DATA, &data);
+ return i2c_smbus_throttle_xfer(client, I2C_SMBUS_WRITE, command,
+ I2C_SMBUS_I2C_BLOCK_DATA, &data);
}
EXPORT_SYMBOL(i2c_smbus_write_i2c_block_data);

@@ -549,6 +551,71 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
}
EXPORT_SYMBOL(i2c_smbus_xfer);

+static int i2c_smbus_throttle_enter(const struct i2c_client *client)
+ __acquires(&priv->throttle_lock)
+{
+ struct i2c_client_priv *priv;
+ ktime_t earliest;
+ int rc;
+
+ priv = to_i2c_client_priv(client);
+
+ if (i2c_in_atomic_xfer_mode()) {
+ if (!mutex_trylock(&priv->throttle_lock))
+ return -EAGAIN;
+ } else {
+ rc = mutex_lock_interruptible(&priv->throttle_lock);
+ if (rc)
+ return rc;
+ }
+ earliest = ktime_add_us(priv->last, priv->delay_us);
+
+ if (priv->delay_us && ktime_before(ktime_get(), earliest)) {
+ if (i2c_in_atomic_xfer_mode()) {
+ mutex_unlock(&priv->throttle_lock);
+ return -EAGAIN;
+ }
+
+ usleep_range(priv->delay_us, 2 * priv->delay_us);
+ }
+
+ return 0;
+}
+
+static void i2c_smbus_throttle_exit(const struct i2c_client *client)
+ __releases(&priv->throttle_lock)
+{
+ struct i2c_client_priv *priv;
+
+ priv = to_i2c_client_priv(client);
+
+ if (priv->delay_us)
+ priv->last = ktime_get();
+ mutex_unlock(&priv->throttle_lock);
+}
+
+static s32 i2c_smbus_throttle_xfer(const struct i2c_client *client,
+ char read_write, u8 command, int protocol,
+ union i2c_smbus_data *data)
+{
+ s32 res;
+
+ res = i2c_smbus_throttle_enter(client);
+ if (res)
+ return res;
+
+ res = __i2c_lock_bus_helper(client->adapter);
+ if (!res)
+ res = __i2c_smbus_xfer(client->adapter, client->addr,
+ client->flags, read_write, command,
+ protocol, data);
+ i2c_unlock_bus(client->adapter, I2C_LOCK_SEGMENT);
+
+ i2c_smbus_throttle_exit(client);
+
+ return res;
+}
+
s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
unsigned short flags, char read_write,
u8 command, int protocol, union i2c_smbus_data *data)
@@ -717,3 +784,47 @@ int of_i2c_setup_smbus_alert(struct i2c_adapter *adapter)
}
EXPORT_SYMBOL_GPL(of_i2c_setup_smbus_alert);
#endif
+
+int i2c_smbus_throttle_client(struct i2c_client *client,
+ unsigned long delay_us)
+{
+ struct i2c_client_priv *priv;
+ int rc;
+
+ priv = to_i2c_client_priv(client);
+
+ if (i2c_in_atomic_xfer_mode()) {
+ if (!mutex_trylock(&priv->throttle_lock))
+ return -EAGAIN;
+ } else {
+ rc = mutex_lock_interruptible(&priv->throttle_lock);
+ if (rc)
+ return rc;
+ }
+ priv->delay_us = delay_us;
+ priv->last = ktime_get();
+ mutex_unlock(&priv->throttle_lock);
+
+ return 0;
+}
+
+int i2c_smbus_throttle_value(struct i2c_client *client, unsigned long *delay_us)
+{
+ struct i2c_client_priv *priv;
+ int rc;
+
+ priv = to_i2c_client_priv(client);
+
+ if (i2c_in_atomic_xfer_mode()) {
+ if (!mutex_trylock(&priv->throttle_lock))
+ return -EAGAIN;
+ } else {
+ rc = mutex_lock_interruptible(&priv->throttle_lock);
+ if (rc)
+ return rc;
+ }
+ *delay_us = priv->delay_us;
+ mutex_unlock(&priv->throttle_lock);
+
+ return 0;
+}
diff --git a/drivers/i2c/i2c-core.h b/drivers/i2c/i2c-core.h
index 8ce261167a2d..e916624c7b98 100644
--- a/drivers/i2c/i2c-core.h
+++ b/drivers/i2c/i2c-core.h
@@ -4,6 +4,27 @@
*/

#include <linux/rwsem.h>
+#include <linux/i2c.h>
+#include <linux/timekeeping.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+
+struct i2c_client_priv {
+ struct i2c_client client;
+
+ /*
+ * Per-client access throttling, described in terms of microsecond
+ * delay between the end of the nth transfer and the start of the
+ * (n+1)th transfer
+ *
+ * Do it in a wrapper struct to preserve const-ness of the i2c_smbus_*
+ * interfaces.
+ */
+ struct mutex throttle_lock;
+ unsigned long delay_us;
+ ktime_t last;
+};
+#define to_i2c_client_priv(c) container_of(c, struct i2c_client_priv, client)

struct i2c_devinfo {
struct list_head list;
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index 34f2d8e96ce8..4792c7108449 100644
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -186,8 +186,13 @@ s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
s32 i2c_smbus_read_i2c_block_data_or_emulated(const struct i2c_client *client,
u8 command, u8 length,
u8 *values);
+int i2c_smbus_throttle_client(struct i2c_client *client,
+ unsigned long delay_us);
+int i2c_smbus_throttle_value(struct i2c_client *client,
+ unsigned long *delay_us);
int i2c_get_device_id(const struct i2c_client *client,
struct i2c_device_identity *id);
+
#endif /* I2C */

/**
--
2.25.1