mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
i2c: core: Add i2c_update_timeout() helper for dynamic transfer timeouts
The transfer timeout for an I2C controller should reflect the actual message length and bus frequency rather than a static 1-second value. A static timeout causes unnecessary delays on error paths for short messages, and may be insufficient for very long transfers. Add i2c_update_timeout() to i2c-core which computes a transfer-specific timeout and stores it directly in the standard adap->timeout field. The formula accounts for 9 bits per byte (8 data + 1 ACK) at the configured bus frequency. The caller supplies a safety multiplier and a minimum floor so that each driver retains full control over its timing policy without those values becoming public API. Storing the result in adap->timeout makes it visible to all consumers of that field, including the arbitration-loss retry loop in __i2c_transfer(). The function is gated by CONFIG_I2C_DYNAMIC_TIMEOUT. When the config is disabled, i2c_update_timeout() compiles to a no-op inline stub so drivers that call it build cleanly and the existing static 1-second default is preserved unchanged. A timeout explicitly configured by userspace via the I2C_TIMEOUT ioctl is stored in a new adap->user_timeout field and always takes precedence over the kernel-computed value. When userspace has not configured a timeout, the computed value is used. The ioctl keeps writing adap->timeout as well, so adapters that never call i2c_update_timeout() continue to honour it exactly as before. As i2c_update_timeout() is an exported helper, guard against a zero bus frequency from a misbehaving caller with WARN_ON_ONCE() and return early, leaving the existing timeout untouched as a safe fallback rather than dividing by zero. Signed-off-by: Aniket Randive <aniket.randive@oss.qualcomm.com> Reviewed-by: Mukesh Kumar Savaliya <mukesh.savaliya@oss.qualcomm.com> Signed-off-by: Andi Shyti <andi.shyti@kernel.org> Link: https://patch.msgid.link/20260911-master-v9-1-77ac458344e2@oss.qualcomm.com
This commit is contained in:
parent
bb436f2a85
commit
6d49edd9a3
|
|
@ -136,6 +136,19 @@ config I2C_SLAVE_TESTUNIT
|
|||
|
||||
endif
|
||||
|
||||
config I2C_DYNAMIC_TIMEOUT
|
||||
bool "Dynamic per-transfer timeout based on message length"
|
||||
depends on I2C
|
||||
help
|
||||
When enabled, the I2C core computes a per-transfer timeout from the
|
||||
message length and bus frequency instead of using a static 1-second
|
||||
default. A timeout explicitly configured via the I2C_TIMEOUT userspace
|
||||
interface always takes precedence over the computed value.
|
||||
|
||||
When disabled, the existing static 1-second timeout is preserved.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config I2C_DEBUG_CORE
|
||||
bool "I2C Core debugging messages"
|
||||
help
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <linux/irq.h>
|
||||
#include <linux/jump_label.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_device.h>
|
||||
|
|
@ -2010,6 +2011,48 @@ void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_de
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(i2c_parse_fw_timings);
|
||||
|
||||
#ifdef CONFIG_I2C_DYNAMIC_TIMEOUT
|
||||
/**
|
||||
* i2c_update_timeout - compute and set a dynamic transfer timeout on an adapter
|
||||
* @adap: the i2c_adapter whose timeout field will be updated
|
||||
* @bus_freq_hz: I2C bus clock frequency in Hz
|
||||
* @len: transfer length in bytes
|
||||
* @safety_coeff: multiplier applied over the theoretical wire time
|
||||
* @min_usec: minimum timeout floor in microseconds
|
||||
*
|
||||
* Computes a transfer-specific timeout from the message length and bus
|
||||
* frequency, applies a safety multiplier and a minimum floor, then stores
|
||||
* the result in adap->timeout (in jiffies). The caller supplies the policy
|
||||
* constants so they remain internal to the driver.
|
||||
*
|
||||
* A timeout explicitly configured by userspace via the I2C_TIMEOUT ioctl
|
||||
* always takes precedence; the computed value is used only when userspace
|
||||
* has not configured one.
|
||||
*/
|
||||
void i2c_update_timeout(struct i2c_adapter *adap, u32 bus_freq_hz,
|
||||
size_t len, unsigned int safety_coeff,
|
||||
unsigned int min_usec)
|
||||
{
|
||||
u64 bit_usec, total_usec;
|
||||
unsigned long jiffies_val;
|
||||
|
||||
if (adap->user_timeout > 0) {
|
||||
adap->timeout = adap->user_timeout;
|
||||
return;
|
||||
}
|
||||
|
||||
if (WARN_ON_ONCE(!bus_freq_hz))
|
||||
return;
|
||||
|
||||
bit_usec = mul_u64_u32_div(len * 9, USEC_PER_SEC, bus_freq_hz);
|
||||
total_usec = bit_usec * safety_coeff + min_usec;
|
||||
|
||||
jiffies_val = usecs_to_jiffies((unsigned int)min_t(u64, total_usec, UINT_MAX));
|
||||
adap->timeout = (int)min_t(unsigned long, jiffies_val, INT_MAX);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(i2c_update_timeout);
|
||||
#endif /* CONFIG_I2C_DYNAMIC_TIMEOUT */
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
int i2c_for_each_dev(void *data, int (*fn)(struct device *dev, void *data))
|
||||
|
|
|
|||
|
|
@ -495,6 +495,9 @@ static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
return -EINVAL;
|
||||
|
||||
client->adapter->timeout = msecs_to_jiffies(arg * 10);
|
||||
#ifdef CONFIG_I2C_DYNAMIC_TIMEOUT
|
||||
client->adapter->user_timeout = client->adapter->timeout;
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
/* NOTE: returning a fault code here could cause trouble
|
||||
|
|
|
|||
|
|
@ -757,6 +757,9 @@ struct i2c_adapter {
|
|||
struct rt_mutex mux_lock;
|
||||
|
||||
int timeout; /* in jiffies */
|
||||
#ifdef CONFIG_I2C_DYNAMIC_TIMEOUT
|
||||
int user_timeout; /* I2C_TIMEOUT ioctl value in jiffies */
|
||||
#endif
|
||||
int retries;
|
||||
struct device dev; /* the adapter device */
|
||||
unsigned long locked_flags; /* owned by the I2C core */
|
||||
|
|
@ -928,6 +931,16 @@ unsigned int i2c_adapter_depth(struct i2c_adapter *adapter);
|
|||
|
||||
void i2c_parse_fw_timings(struct device *dev, struct i2c_timings *t, bool use_defaults);
|
||||
|
||||
#ifdef CONFIG_I2C_DYNAMIC_TIMEOUT
|
||||
void i2c_update_timeout(struct i2c_adapter *adap, u32 bus_freq_hz,
|
||||
size_t len, unsigned int safety_coeff,
|
||||
unsigned int min_usec);
|
||||
#else
|
||||
static inline void i2c_update_timeout(struct i2c_adapter *adap, u32 bus_freq_hz,
|
||||
size_t len, unsigned int safety_coeff,
|
||||
unsigned int min_usec) {}
|
||||
#endif
|
||||
|
||||
/* Return the functionality mask */
|
||||
static inline u32 i2c_get_functionality(struct i2c_adapter *adap)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user