[PATCH 6/8] platform: arm64: qcom-hamoa-ec: Retry I2C transfers on NACK
Anvesh Jain P <[email protected]> Tue, 28 Jul 2026 23:14:34 +0530
| Newsgroups | org.kernel.vger.platform-driver-x86,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The EC firmware NACKs the bus instead of clock-stretching while it is busy servicing internal work (e.g. right after boot, when it is also emitting a burst of SCI events). A transfer issued in that window fails with -ENXIO even though the EC is otherwise healthy. This isn't visible with the smbus-only command set used previously; it surfaced while stress-testing the new fan LUT command on Hamoa, which issues raw i2c_transfer() calls more aggressively and is thus more likely to land in the NACK window right after boot or during an SCI event burst. Retry qcom_ec_write() and qcom_ec_read() a bounded number of times with a short delay when the EC NACKs the transfer, before giving up. Signed-off-by: Anvesh Jain P <[email protected]> --- drivers/platform/arm64/qcom-hamoa-ec.c | 40 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/drivers/platform/arm64/qcom-hamoa-ec.c b/drivers/platform/arm64/qcom-hamoa-ec.c index a420a180a194..122ae27c2b48 100644 --- a/drivers/platform/arm64/qcom-hamoa-ec.c +++ b/drivers/platform/arm64/qcom-hamoa-ec.c @@ -64,6 +64,18 @@ #define EC_SOC_TJ_TEMP_POLL_JIFFIES msecs_to_jiffies(125) +/* + * The EC firmware NACKs the bus instead of clock-stretching while it is + * busy servicing internal work (e.g. right after boot, when it is also + * emitting a burst of SCI events). This NACK only occurs at the address + * phase, so retrying the whole transfer is safe. Attempt the transfer a + * bounded number of times on NACK before giving up; a genuinely missing + * or unpowered device pays a worst-case added latency of + * (EC_I2C_NACK_MAX_ATTEMPTS - 1) * EC_I2C_NACK_RETRY_DELAY_MS. + */ +#define EC_I2C_NACK_MAX_ATTEMPTS 5 +#define EC_I2C_NACK_RETRY_DELAY_MS 20 + enum qcom_ec_sci_events { EC_FAN1_STATUS_CHANGE_EVT = 0x30, EC_FAN2_STATUS_CHANGE_EVT, @@ -233,11 +245,20 @@ static int qcom_ec_write(struct qcom_ec *ec, u8 cmd, const u8 *data, size_t len) msg.buf[0] = cmd; memcpy(&msg.buf[1], data, len); - ret = i2c_transfer(ec->client->adapter, &msg, 1); + for (int i = 0; i < EC_I2C_NACK_MAX_ATTEMPTS; i++) { + ret = i2c_transfer(ec->client->adapter, &msg, 1); + ret = ret == 1 ? 0 : (ret < 0 ? ret : -EIO); + if (ret != -ENXIO || i == EC_I2C_NACK_MAX_ATTEMPTS - 1) + break; + + dev_dbg(&ec->client->dev, + "EC busy (NACK), retrying write of cmd 0x%02x\n", cmd); + msleep(EC_I2C_NACK_RETRY_DELAY_MS); + } kfree(msg.buf); - return ret == 1 ? 0 : (ret < 0 ? ret : -EIO); + return ret; } static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, const u8 *subcmd, @@ -281,11 +302,18 @@ static int qcom_ec_read(struct qcom_ec *ec, u8 cmd, const u8 *subcmd, msgs[1].len = resp_len; msgs[1].buf = read_buf; - ret = i2c_transfer(client->adapter, msgs, 2); - if (ret != 2) { - ret = ret < 0 ? ret : -EIO; - goto out; + for (int i = 0; i < EC_I2C_NACK_MAX_ATTEMPTS; i++) { + ret = i2c_transfer(client->adapter, msgs, 2); + ret = ret == 2 ? 0 : (ret < 0 ? ret : -EIO); + if (ret != -ENXIO || i == EC_I2C_NACK_MAX_ATTEMPTS - 1) + break; + + dev_dbg(&client->dev, + "EC busy (NACK), retrying read of cmd 0x%02x\n", cmd); + msleep(EC_I2C_NACK_RETRY_DELAY_MS); } + if (ret) + goto out; if (read_buf[0] == 0 || read_buf[0] == 0xff) { ret = -EOPNOTSUPP; -- 2.34.1