From: Jisheng Zhang <jszhang@kernel.org>
To: Jarkko Nikula <jarkko.nikula@linux.intel.com>,
Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
Mika Westerberg <mika.westerberg@linux.intel.com>,
Jan Dabros <jsd@semihalf.com>, Andi Shyti <andi.shyti@kernel.org>
Cc: linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org
Subject: [PATCH 2/2] i2c: designware: Implement atomic transfer suppot
Date: Wed, 20 Aug 2025 23:31:25 +0800 [thread overview]
Message-ID: <20250820153125.22002-3-jszhang@kernel.org> (raw)
In-Reply-To: <20250820153125.22002-1-jszhang@kernel.org>
Rework the read and write code paths in the driver to support operation
in atomic contexts. To achieve this, the driver must not rely on IRQs
or perform any scheduling, e.g., via a sleep or schedule routine.
Implement atomic, sleep-free, and IRQ-less operation. This increases
complexity but is necessary for atomic I2C transfers required by some
hardware configurations, e.g., to trigger reboots on an external PMIC chip.
Signed-off-by: Jisheng Zhang <jszhang@kernel.org>
---
drivers/i2c/busses/i2c-designware-common.c | 38 +++++++---
drivers/i2c/busses/i2c-designware-core.h | 5 +-
drivers/i2c/busses/i2c-designware-master.c | 80 +++++++++++++++++++---
3 files changed, 100 insertions(+), 23 deletions(-)
diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index b4e38bc0f876..0b24ac0357ad 100644
--- a/drivers/i2c/busses/i2c-designware-common.c
+++ b/drivers/i2c/busses/i2c-designware-common.c
@@ -532,15 +532,23 @@ void __i2c_dw_disable(struct dw_i2c_dev *dev)
* 25us) to ensure the I2C ENABLE bit is already set
* as described in the DesignWare I2C databook.
*/
- fsleep(DIV_ROUND_CLOSEST_ULL(10 * MICRO, t->bus_freq_hz));
+ if (dev->atomic)
+ udelay(DIV_ROUND_CLOSEST_ULL(10 * MICRO, t->bus_freq_hz));
+ else
+ fsleep(DIV_ROUND_CLOSEST_ULL(10 * MICRO, t->bus_freq_hz));
/* Set ENABLE bit before setting ABORT */
enable |= DW_IC_ENABLE_ENABLE;
}
regmap_write(dev->map, DW_IC_ENABLE, enable | DW_IC_ENABLE_ABORT);
- ret = regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, enable,
- !(enable & DW_IC_ENABLE_ABORT), 10,
- 100);
+ if (dev->atomic)
+ ret = regmap_read_poll_timeout_atomic(dev->map, DW_IC_ENABLE, enable,
+ !(enable & DW_IC_ENABLE_ABORT), 10,
+ 100);
+ else
+ ret = regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, enable,
+ !(enable & DW_IC_ENABLE_ABORT), 10,
+ 100);
if (ret)
dev_err(dev->dev, "timeout while trying to abort current transfer\n");
}
@@ -560,7 +568,10 @@ void __i2c_dw_disable(struct dw_i2c_dev *dev)
* transfer supported by the driver (for 400kHz this is
* 25us) as described in the DesignWare I2C databook.
*/
- usleep_range(25, 250);
+ if (dev->atomic)
+ udelay(25);
+ else
+ usleep_range(25, 250);
} while (timeout--);
dev_warn(dev->dev, "timeout in disabling adapter\n");
@@ -607,7 +618,7 @@ int i2c_dw_acquire_lock(struct dw_i2c_dev *dev)
{
int ret;
- if (!dev->acquire_lock)
+ if (dev->atomic || !dev->acquire_lock)
return 0;
ret = dev->acquire_lock();
@@ -621,7 +632,7 @@ int i2c_dw_acquire_lock(struct dw_i2c_dev *dev)
void i2c_dw_release_lock(struct dw_i2c_dev *dev)
{
- if (dev->release_lock)
+ if (!dev->atomic && dev->release_lock)
dev->release_lock();
}
@@ -633,11 +644,18 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev)
unsigned int status;
int ret;
- ret = regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status,
- !(status & DW_IC_STATUS_ACTIVITY),
- 1100, 20000);
+ if (dev->atomic)
+ ret = regmap_read_poll_timeout_atomic(dev->map, DW_IC_STATUS, status,
+ !(status & DW_IC_STATUS_ACTIVITY),
+ 1100, 20000);
+ else
+ ret = regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status,
+ !(status & DW_IC_STATUS_ACTIVITY),
+ 1100, 20000);
if (ret) {
dev_warn(dev->dev, "timeout waiting for bus ready\n");
+ if (dev->atomic)
+ return ret;
i2c_recover_bus(&dev->adapter);
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index 347843b4f5dd..7384ec5a7ab7 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -299,6 +299,7 @@ struct dw_i2c_dev {
void (*release_lock)(void);
int semaphore_idx;
bool shared_with_punit;
+ bool atomic;
int (*init)(struct dw_i2c_dev *dev);
int (*set_sda_hold_time)(struct dw_i2c_dev *dev);
int mode;
@@ -365,7 +366,7 @@ static inline void __i2c_dw_disable_nowait(struct dw_i2c_dev *dev)
static inline void __i2c_dw_write_intr_mask(struct dw_i2c_dev *dev,
unsigned int intr_mask)
{
- unsigned int val = dev->flags & ACCESS_POLLING ? 0 : intr_mask;
+ unsigned int val = (dev->atomic || dev->flags & ACCESS_POLLING) ? 0 : intr_mask;
regmap_write(dev->map, DW_IC_INTR_MASK, val);
dev->sw_mask = intr_mask;
@@ -374,7 +375,7 @@ static inline void __i2c_dw_write_intr_mask(struct dw_i2c_dev *dev,
static inline void __i2c_dw_read_intr_mask(struct dw_i2c_dev *dev,
unsigned int *intr_mask)
{
- if (!(dev->flags & ACCESS_POLLING))
+ if (!(dev->flags & ACCESS_POLLING) && !dev->atomic)
regmap_read(dev->map, DW_IC_INTR_MASK, intr_mask);
else
*intr_mask = dev->sw_mask;
diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c
index cbd88ffa5610..333ec9bacae4 100644
--- a/drivers/i2c/busses/i2c-designware-master.c
+++ b/drivers/i2c/busses/i2c-designware-master.c
@@ -312,9 +312,14 @@ static bool i2c_dw_is_controller_active(struct dw_i2c_dev *dev)
if (!(status & DW_IC_STATUS_MASTER_ACTIVITY))
return false;
- return regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status,
- !(status & DW_IC_STATUS_MASTER_ACTIVITY),
- 1100, 20000) != 0;
+ if (dev->atomic)
+ return regmap_read_poll_timeout_atomic(dev->map, DW_IC_STATUS, status,
+ !(status & DW_IC_STATUS_MASTER_ACTIVITY),
+ 1100, 20000) != 0;
+ else
+ return regmap_read_poll_timeout(dev->map, DW_IC_STATUS, status,
+ !(status & DW_IC_STATUS_MASTER_ACTIVITY),
+ 1100, 20000) != 0;
}
static int i2c_dw_check_stopbit(struct dw_i2c_dev *dev)
@@ -654,7 +659,7 @@ static u32 i2c_dw_read_clear_intrbits(struct dw_i2c_dev *dev)
*
* The raw version might be useful for debugging purposes.
*/
- if (!(dev->flags & ACCESS_POLLING)) {
+ if (!(dev->flags & ACCESS_POLLING) && !dev->atomic) {
regmap_read(dev->map, DW_IC_INTR_STAT, &stat);
} else {
regmap_read(dev->map, DW_IC_RAW_INTR_STAT, &stat);
@@ -801,11 +806,32 @@ static int i2c_dw_wait_transfer(struct dw_i2c_dev *dev)
return ret ? 0 : -ETIMEDOUT;
}
+static int i2c_dw_wait_transfer_atomic(struct dw_i2c_dev *dev)
+{
+ ktime_t timeout = ktime_add_us(ktime_get(), jiffies_to_usecs(dev->adapter.timeout));
+ unsigned int stat;
+ int ret;
+
+ do {
+ ret = try_wait_for_completion(&dev->cmd_complete);
+ if (ret)
+ break;
+
+ stat = i2c_dw_read_clear_intrbits(dev);
+ if (stat)
+ i2c_dw_process_transfer(dev, stat);
+ else
+ udelay(15);
+ } while (ktime_compare(ktime_get(), timeout) < 0);
+
+ return ret ? 0 : -ETIMEDOUT;
+}
+
/*
* Prepare controller for a transaction and call i2c_dw_xfer_msg.
*/
static int
-i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+i2c_dw_xfer_core(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
int ret;
@@ -816,13 +842,19 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
switch (dev->flags & MODEL_MASK) {
case MODEL_AMD_NAVI_GPU:
+ if (dev->atomic) {
+ ret = -EOPNOTSUPP;
+ goto done_nolock;
+ }
+
ret = amd_i2c_dw_xfer_quirk(adap, msgs, num);
goto done_nolock;
default:
break;
}
- reinit_completion(&dev->cmd_complete);
+ if (!dev->atomic)
+ reinit_completion(&dev->cmd_complete);
dev->msgs = msgs;
dev->msgs_num = num;
dev->cmd_err = 0;
@@ -845,12 +877,18 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
i2c_dw_xfer_init(dev);
/* Wait for tx to complete */
- ret = i2c_dw_wait_transfer(dev);
+ if (dev->atomic)
+ ret = i2c_dw_wait_transfer_atomic(dev);
+ else
+ ret = i2c_dw_wait_transfer(dev);
+
if (ret) {
dev_err(dev->dev, "controller timed out\n");
- /* i2c_dw_init_master() implicitly disables the adapter */
- i2c_recover_bus(&dev->adapter);
- i2c_dw_init_master(dev);
+ if (!dev->atomic) {
+ /* i2c_dw_init_master() implicitly disables the adapter */
+ i2c_recover_bus(&dev->adapter);
+ i2c_dw_init_master(dev);
+ }
goto done;
}
@@ -907,7 +945,25 @@ i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
return ret;
}
-static const struct i2c_algorithm i2c_dw_algo = {
+static int
+i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+ struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+
+ dev->atomic = false;
+ return i2c_dw_xfer_core(adap, msgs, num);
+}
+
+static int
+i2c_dw_xfer_atomic(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+ struct dw_i2c_dev *dev = i2c_get_adapdata(adap);
+
+ dev->atomic = true;
+ return i2c_dw_xfer_core(adap, msgs, num);
+}
+
+static struct i2c_algorithm i2c_dw_algo = {
.xfer = i2c_dw_xfer,
.functionality = i2c_dw_func,
};
@@ -1048,6 +1104,8 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
"Synopsys DesignWare I2C adapter");
adap->retries = 3;
adap->algo = &i2c_dw_algo;
+ if (!dev->acquire_lock)
+ i2c_dw_algo.xfer_atomic = i2c_dw_xfer_atomic,
adap->quirks = &i2c_dw_quirks;
adap->dev.parent = dev->dev;
i2c_set_adapdata(adap, dev);
--
2.50.1
next prev parent reply other threads:[~2025-08-20 15:48 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-20 15:31 [PATCH 0/2] i2c: designware: Implement atomic transfer suppot Jisheng Zhang
2025-08-20 15:31 ` [PATCH 1/2] i2c: designware: Avoid taking clk_prepare mutex in PM callbacks Jisheng Zhang
2025-08-20 16:05 ` Andy Shevchenko
2025-08-20 16:33 ` Jisheng Zhang
2025-08-21 12:45 ` Jarkko Nikula
2025-08-21 13:01 ` Andy Shevchenko
2025-08-21 16:32 ` Jisheng Zhang
2025-08-22 9:18 ` Andy Shevchenko
2025-08-22 9:34 ` Andy Shevchenko
2025-08-22 13:56 ` Jisheng Zhang
2025-08-22 23:51 ` Jisheng Zhang
2025-08-20 15:31 ` Jisheng Zhang [this message]
2025-08-20 17:00 ` [PATCH 2/2] i2c: designware: Implement atomic transfer suppot Andy Shevchenko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250820153125.22002-3-jszhang@kernel.org \
--to=jszhang@kernel.org \
--cc=andi.shyti@kernel.org \
--cc=andriy.shevchenko@linux.intel.com \
--cc=jarkko.nikula@linux.intel.com \
--cc=jsd@semihalf.com \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mika.westerberg@linux.intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).