[PATCH v1 4/6] misc: amd-sbi: Add support for SB-TSI over I3C

Akshay Gupta
2026-03-23T11:09:02Z

From: Prathima <Prathima.Lk@amd.com>

- Extend the SB-TSI driver to support both I2C and I3C bus interfaces.
- The driver maintains backward compatibility with existing I2C
deployments while enabling support for systems using the I3C bus.

Reviewed-by: Akshay Gupta <Akshay.Gupta@amd.com>
Signed-off-by: Prathima <Prathima.Lk@amd.com>
---
drivers/misc/amd-sbi/Kconfig | 5 ++-
drivers/misc/amd-sbi/Makefile | 2 +-
drivers/misc/amd-sbi/tsi-core.c | 64 ++++++++++++++++++++++++++++++++
drivers/misc/amd-sbi/tsi-core.h | 11 +++++-
drivers/misc/amd-sbi/tsi-hwmon.c | 50 ++++++++++++++++++-------
drivers/misc/amd-sbi/tsi.c | 62 +++++++++++++++++++++++++++++--
6 files changed, 173 insertions(+), 21 deletions(-)
create mode 100644 drivers/misc/amd-sbi/tsi-core.c

diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig
index 2710860a86c7..d18652537788 100644
--- a/drivers/misc/amd-sbi/Kconfig
+++ b/drivers/misc/amd-sbi/Kconfig
@@ -23,11 +23,12 @@ config AMD_SBRMI_HWMON

config SENSORS_SBTSI
tristate "Emulated SB-TSI temperature sensor"
- depends on I2C
+ depends on I3C_OR_I2C
depends on ARM || ARM64 || COMPILE_TEST
help
If you say yes here you get support for emulated temperature
- sensors on AMD SoCs with SB-TSI interface connected to a BMC device.
+ sensors on AMD SoCs with SB-TSI interface connected to a BMC device
+ over I2C and I3C.
This driver is intended to run on the BMC, not the managed node.

This driver can also be built as a module. If so, the module will
diff --git a/drivers/misc/amd-sbi/Makefile b/drivers/misc/amd-sbi/Makefile
index a874136e438f..e87c8e618dcc 100644
--- a/drivers/misc/amd-sbi/Makefile
+++ b/drivers/misc/amd-sbi/Makefile
@@ -3,6 +3,6 @@ sbrmi-i2c-objs += rmi-i2c.o rmi-core.o
sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o
obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o
# SBTSI Configuration
-sbtsi-objs += tsi.o
+sbtsi-objs += tsi.o tsi-core.o
sbtsi-$(CONFIG_AMD_SBTSI_HWMON) += tsi-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi.o
diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c
new file mode 100644
index 000000000000..fcb7fcf87a55
--- /dev/null
+++ b/drivers/misc/amd-sbi/tsi-core.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * tsi-core.c - file defining SB-TSI protocols compliant
+ * AMD SoC device.
+ * Copyright (c) 2020, Google Inc.
+ * Copyright (c) 2020, Kun Yi <kunyi@google.com>
+ */
+
+#include "tsi-core.h"
+
+/* I3C read transfer function */
+static int sbtsi_i3c_read(struct sbtsi_data *data, u8 reg, u8 *val)
+{
+ struct i3c_xfer xfers[2];
+
+ /* Send register address */
+ xfers[0].rnw = false;
+ xfers[0].len = 1;
+ xfers[0].data.out = &reg;
+
+ /* Read data */
+ xfers[1].rnw = true;
+ xfers[1].len = 1;
+ xfers[1].data.in = val;
+
+ return i3c_device_do_xfers(data->i3cdev, xfers, 2, I3C_SDR);
+}
+
+/* I3C write transfer function */
+static int sbtsi_i3c_write(struct sbtsi_data *data, u8 reg, u8 *val)
+{
+ u8 buf[2] = { reg, *val };
+ struct i3c_xfer xfers = {
+ .rnw = false,
+ .len = 2,
+ .data.out = buf,
+ };
+
+ return i3c_device_do_xfers(data->i3cdev, &xfers, 1, I3C_SDR);
+}
+
+/* I2C transfer function */
+static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
+{
+ if (is_read) {
+ int ret = i2c_smbus_read_byte_data(data->client, reg);
+
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return 0;
+ }
+ return i2c_smbus_write_byte_data(data->client, reg, *val);
+}
+
+/* Unified transfer function for I2C and I3C access */
+int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
+{
+ if (data->is_i3c)
+ return is_read ? sbtsi_i3c_read(data, reg, val)
+ : sbtsi_i3c_write(data, reg, val);
+
+ return sbtsi_i2c_xfer(data, reg, val, is_read);
+}
diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h
index e60cf25fda7a..a4ded17942c9 100644
--- a/drivers/misc/amd-sbi/tsi-core.h
+++ b/drivers/misc/amd-sbi/tsi-core.h
@@ -11,14 +11,23 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/types.h>
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>

/* Each client has this additional data */
struct sbtsi_data {
- struct i2c_client *client;
+ union {
+ struct i2c_client *client;
+ struct i3c_device *i3cdev;
+ };
bool ext_range_mode;
bool read_order;
+ bool is_i3c;
};

+int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read);
+
#ifdef CONFIG_AMD_SBTSI_HWMON
int create_sbtsi_hwmon_sensor_device(struct device *dev, struct sbtsi_data *data);
#else
diff --git a/drivers/misc/amd-sbi/tsi-hwmon.c b/drivers/misc/amd-sbi/tsi-hwmon.c
index efd450a85d34..9b447042d4aa 100644
--- a/drivers/misc/amd-sbi/tsi-hwmon.c
+++ b/drivers/misc/amd-sbi/tsi-hwmon.c
@@ -27,8 +27,7 @@
#define SBTSI_TEMP_MIN 0
#define SBTSI_TEMP_MAX 255875

-/*
- * From SB-TSI spec: CPU temperature readings and limit registers encode the
+/* From SB-TSI spec: CPU temperature readings and limit registers encode the
* temperature in increments of 0.125 from 0 to 255.875. The "high byte"
* register encodes the base-2 of the integer portion, and the upper 3 bits of
* the "low byte" encode in base-2 the decimal portion.
@@ -61,24 +60,50 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
{
struct sbtsi_data *data = dev_get_drvdata(dev);
s32 temp_int, temp_dec;
+ u8 reg_val;
+ int err;

switch (attr) {
case hwmon_temp_input:
if (data->read_order) {
- temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
- temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_DEC, &reg_val, true);
+ if (err < 0)
+ return err;
+ temp_dec = reg_val;
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_INT, &reg_val, true);
+ if (err < 0)
+ return err;
+ temp_int = reg_val;
} else {
- temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
- temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_INT, &reg_val, true);
+ if (err < 0)
+ return err;
+ temp_int = reg_val;
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_DEC, &reg_val, true);
+ if (err < 0)
+ return err;
+ temp_dec = reg_val;
}
break;
case hwmon_temp_max:
- temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_INT);
- temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_DEC);
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_HIGH_INT, &reg_val, true);
+ if (err < 0)
+ return err;
+ temp_int = reg_val;
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_HIGH_DEC, &reg_val, true);
+ if (err < 0)
+ return err;
+ temp_dec = reg_val;
break;
case hwmon_temp_min:
- temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_INT);
- temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_DEC);
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_LOW_INT, &reg_val, true);
+ if (err < 0)
+ return err;
+ temp_int = reg_val;
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_LOW_DEC, &reg_val, true);
+ if (err < 0)
+ return err;
+ temp_dec = reg_val;
break;
default:
return -EINVAL;
@@ -115,12 +140,11 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type,
val += SBTSI_TEMP_EXT_RANGE_ADJ;
val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX);
sbtsi_mc_to_reg(val, &temp_int, &temp_dec);
-
- err = i2c_smbus_write_byte_data(data->client, reg_int, temp_int);
+ err = sbtsi_xfer(data, reg_int, &temp_int, false);
if (err)
return err;

- err = i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec);
+ err = sbtsi_xfer(data, reg_dec, &temp_dec, false);
if (err)
return err;
return 0;
diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c
index df754e60d84b..d7a8237fc4fd 100644
--- a/drivers/misc/amd-sbi/tsi.c
+++ b/drivers/misc/amd-sbi/tsi.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * tsi.c - Side band TSI over I2C support for AMD out of band management.
+ * tsi.c - Side band TSI over I2C/I3C support for AMD out of band management.
*
* Copyright (c) 2020-2026, Google Inc.
* Copyright (c) 2020-2026, Kun Yi <kunyi@google.com>
@@ -26,7 +26,7 @@

#define SBTSI_CONFIG_READ_ORDER_SHIFT 5

-static int sbtsi_probe(struct i2c_client *client)
+static int sbtsi_i2c_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct sbtsi_data *data;
@@ -36,6 +36,7 @@ static int sbtsi_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;

+ data->is_i3c = false;
data->client = client;
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
if (err < 0)
@@ -66,11 +67,64 @@ static struct i2c_driver sbtsi_driver = {
.name = "sbtsi",
.of_match_table = of_match_ptr(sbtsi_of_match),
},
- .probe = sbtsi_probe,
+ .probe = sbtsi_i2c_probe,
.id_table = sbtsi_id,
};

-module_i2c_driver(sbtsi_driver);
+static int sbtsi_i3c_probe(struct i3c_device *i3cdev)
+{
+ struct device *dev = i3cdev_to_dev(i3cdev);
+ struct sbtsi_data *data;
+ int err;
+ u8 val;
+ /*
+ * AMD OOB devices differ on basis of Instance ID,
+ * for SBTSI, instance ID is 0.
+ * As the device Id match is not on basis of Instance ID,
+ * add the below check to probe the SBTSI device only and
+ * not other OOB devices.
+ */
+ if (I3C_PID_INSTANCE_ID(i3cdev->desc->info.pid) != 0)
+ return -ENXIO;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->i3cdev = i3cdev;
+ data->is_i3c = true;
+
+ err = sbtsi_xfer(data, SBTSI_REG_CONFIG, &val, true);
+ if (err)
+ return err;
+
+ data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), val);
+ data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val);
+
+ dev_set_drvdata(dev, data);
+ return create_sbtsi_hwmon_sensor_device(dev, data);
+}
+
+static const struct i3c_device_id sbtsi_i3c_id[] = {
+ /* PID for AMD SBTSI device */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x1, NULL),
+ I3C_DEVICE_EXTRA_INFO(0x0, 0x0, 0x118, NULL), /* Socket:0, Venice A0 */
+ I3C_DEVICE_EXTRA_INFO(0x0, 0x100, 0x118, NULL), /* Socket:1, Venice A0 */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x119, NULL), /* Socket:0, Venice B0 */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x100, 0x119, NULL), /* Socket:1, Venice B0 */
+ {}
+};
+MODULE_DEVICE_TABLE(i3c, sbtsi_i3c_id);
+
+static struct i3c_driver sbtsi_i3c_driver = {
+ .driver = {
+ .name = "sbtsi-i3c",
+ },
+ .probe = sbtsi_i3c_probe,
+ .id_table = sbtsi_i3c_id,
+};
+
+module_i3c_i2c_driver(sbtsi_i3c_driver, &sbtsi_driver);

MODULE_AUTHOR("Kun Yi <kunyi@google.com>");
MODULE_DESCRIPTION("Hwmon driver for AMD SB-TSI emulated sensor");
--
2.34.1