[PATCH 8/8] i3c: mipi-i3c-hci: Support the AST2700 internal pull-ups

Billy Tsai billy_tsai at aspeedtech.com
Tue Sep 1 04:35:35 PDT 2026


Boards that do not populate external I3C pull-up resistors can use the
pull-ups built into the AST2700 PHY.

When bias-pull-up is present, determine the pin voltage domain from
the pinctrl-0 group already selected for the bus and enable the
internal pull-ups on both SCL and SDA at the requested strength.
Currently only pinctrl-0 is inspected; a node selecting its group
through a different pinctrl state is not yet supported. The controller
reset clears the SW force control register, so the internal pull-ups
start disabled unless bias-pull-up requests otherwise. A resistance
with no entry in the domain's table, or an unrecognized pinctrl-0
group, is rejected with an error.

Signed-off-by: Billy Tsai <billy_tsai at aspeedtech.com>
Assisted-by: Claude:claude-fable-5
---
 drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.c | 118 ++++++++++++++++++++++++
 drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h |   6 ++
 2 files changed, 124 insertions(+)

diff --git a/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.c b/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.c
index 182111b857c9..d28d5680d326 100644
--- a/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.c
+++ b/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.c
@@ -12,6 +12,7 @@
 #include <linux/i3c/master.h>
 #include <linux/io.h>
 #include <linux/of.h>
+#include <linux/string.h>
 
 #include "hci.h"
 #include "vendor_aspeed.h"
@@ -86,6 +87,100 @@ static u32 aspeed_i3c_get_sdr_ctrl0_reg(struct i3c_hci *hci)
 	return ASPEED_I3C_PHY_I3C_SDR4_CTRL0;
 }
 
+struct aspeed_i3c_pullup_ohms {
+	u32 ohms;
+	u8 code;
+};
+
+/* SW_FORCE_CTRL strength selector to resistance, by pin voltage domain */
+static const struct aspeed_i3c_pullup_ohms aspeed_i3c_pullup_hv[] = {
+	{ 600, 1 },
+	{ 470, 2 },
+	{ 400, 3 },
+};
+
+static const struct aspeed_i3c_pullup_ohms aspeed_i3c_pullup_lv[] = {
+	{ 750, 1 },
+	{ 550, 2 },
+	{ 317, 3 },
+	{ 400, 4 },
+	{ 261, 5 },
+	{ 231, 6 },
+	{ 177, 7 },
+};
+
+/* pinctrl-0 group names for each pin voltage domain */
+static const char * const aspeed_i3c_hv_groups[] = {
+	"HVI3C0", "HVI3C1", "HVI3C2", "HVI3C3",
+	"HVI3C12", "HVI3C13", "HVI3C14", "HVI3C15",
+};
+
+static const char * const aspeed_i3c_lv_groups[] = {
+	"I3C4", "I3C5", "I3C6", "I3C7",
+	"I3C8", "I3C9", "I3C10", "I3C11",
+};
+
+static int aspeed_i3c_pin_is_hv(struct i3c_hci *hci, bool *is_hv)
+{
+	struct device_node *np = hci->master.dev.of_node;
+	struct device_node *state;
+	const char *group;
+	int ret, i;
+
+	state = of_parse_phandle(np, "pinctrl-0", 0);
+	if (!state)
+		return -ENODEV;
+
+	ret = of_property_read_string(state, "groups", &group);
+	if (ret)
+		goto put_state;
+
+	for (i = 0; i < ARRAY_SIZE(aspeed_i3c_hv_groups); i++) {
+		if (!strcmp(group, aspeed_i3c_hv_groups[i])) {
+			*is_hv = true;
+			goto put_state;
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(aspeed_i3c_lv_groups); i++) {
+		if (!strcmp(group, aspeed_i3c_lv_groups[i])) {
+			*is_hv = false;
+			goto put_state;
+		}
+	}
+
+	dev_err(&hci->master.dev, "unrecognized pinctrl-0 group \"%s\"\n",
+		group);
+	ret = -EINVAL;
+
+put_state:
+	of_node_put(state);
+	return ret;
+}
+
+static int aspeed_i3c_pullup_code(struct i3c_hci *hci, bool is_hv, u32 ohms)
+{
+	const struct aspeed_i3c_pullup_ohms *map;
+	size_t i, n;
+
+	if (is_hv) {
+		map = aspeed_i3c_pullup_hv;
+		n = ARRAY_SIZE(aspeed_i3c_pullup_hv);
+	} else {
+		map = aspeed_i3c_pullup_lv;
+		n = ARRAY_SIZE(aspeed_i3c_pullup_lv);
+	}
+
+	for (i = 0; i < n; i++)
+		if (map[i].ohms == ohms)
+			return map[i].code;
+
+	dev_err(&hci->master.dev,
+		"unsupported internal pull-up %u ohms for %s-voltage domain\n",
+		ohms, is_hv ? "high" : "low");
+	return -EINVAL;
+}
+
 int aspeed_i3c_phy_init(struct i3c_hci *hci)
 {
 	u16 hcnt, lcnt, total_cnt, min_tbit_cnt, cas_lcnt, cas_cnt, cbp_cnt;
@@ -95,6 +190,7 @@ int aspeed_i3c_phy_init(struct i3c_hci *hci)
 	struct device_node *np = hci->master.dev.of_node;
 	u32 sdr_ctrl0_reg, ctrl0, ctrl1, ctrl2;
 	unsigned long core_rate, period_ns;
+	u32 pullup_ohms = 0;
 
 	core_rate = clk_get_rate(to_aspeed_vendor_data(hci)->clk);
 	if (!core_rate) {
@@ -257,6 +353,28 @@ int aspeed_i3c_phy_init(struct i3c_hci *hci)
 
 	aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_PULLUP_EN, 0);
 
+	of_property_read_u32(np, "bias-pull-up", &pullup_ohms);
+	if (pullup_ohms) {
+		int ret, strength;
+		bool is_hv;
+
+		ret = aspeed_i3c_pin_is_hv(hci, &is_hv);
+		if (ret)
+			return ret;
+
+		strength = aspeed_i3c_pullup_code(hci, is_hv, pullup_ohms);
+		if (strength < 0)
+			return strength;
+
+		aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_SW_FORCE_CTRL,
+				     ASPEED_I3C_PHY_SW_FORCE_SCL_PU_EN |
+				     ASPEED_I3C_PHY_SW_FORCE_SDA_PU_EN |
+				     FIELD_PREP(ASPEED_I3C_PHY_SW_FORCE_SCL_PU_VAL,
+						strength) |
+				     FIELD_PREP(ASPEED_I3C_PHY_SW_FORCE_SDA_PU_VAL,
+						strength));
+	}
+
 	hcnt = aspeed_i3c_ns_to_cnt(ASPEED_I3C_PHY_OD_DAP_NS, period_ns);
 	aspeed_i3c_phy_write(hci, ASPEED_I3C_PHY_I3C_OD_CTRL4,
 			     FIELD_PREP(ASPEED_I3C_PHY_I3C_OD_CTRL4_DAP, hcnt));
diff --git a/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h b/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h
index 7147abea830d..80938c580800 100644
--- a/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h
+++ b/drivers/i3c/master/mipi-i3c-hci/vendor_aspeed.h
@@ -83,6 +83,12 @@ unsigned int aspeed_i3c_avail_tx_entries(struct i3c_hci *hci);
 #define   ASPEED_INTR_SUM_PIO			BIT(1)
 #define   ASPEED_INTR_SUM_CAP			BIT(0)
 
+#define ASPEED_I3C_PHY_SW_FORCE_CTRL		0x04
+#define   ASPEED_I3C_PHY_SW_FORCE_SCL_PU_EN	BIT(28)
+#define   ASPEED_I3C_PHY_SW_FORCE_SDA_PU_EN	BIT(24)
+#define   ASPEED_I3C_PHY_SW_FORCE_SCL_PU_VAL	GENMASK(10, 8)
+#define   ASPEED_I3C_PHY_SW_FORCE_SDA_PU_VAL	GENMASK(2, 0)
+
 #define ASPEED_I3C_PHY_I2C_FM_CTRL0		0x08
 #define   ASPEED_I3C_PHY_I2C_FM_CTRL0_CAS	GENMASK(26, 16)
 #define   ASPEED_I3C_PHY_I2C_FM_CTRL0_SU_STO	GENMASK(10, 0)

-- 
2.34.1




More information about the linux-arm-kernel mailing list