2024-05-06 10:51:43

by Dylan Hung

[permalink] [raw]
Subject: [PATCH 0/2] [RFC] i3c: ast2600: Add MCTP-over-I3C support

Add Aspeed AST2600 I3C controllers and add minimal changes to validate
the functionality for MCTP-over-I3C.

Dylan Hung (2):
[RFC] ARM: dts: aspeed-g6: Add AST2600 I3Cs
[RFC] i3c: ast2600: Validate AST2600 I3C for MCTP-over-I3C

arch/arm/boot/dts/aspeed/aspeed-g6.dtsi | 137 +++++++
drivers/i3c/master/ast2600-i3c-master.c | 137 +++++++
drivers/i3c/master/dw-i3c-master.c | 513 ++++++++++++++++++++----
drivers/i3c/master/dw-i3c-master.h | 12 +
4 files changed, 718 insertions(+), 81 deletions(-)

--
2.25.1



2024-05-06 10:51:54

by Dylan Hung

[permalink] [raw]
Subject: [PATCH 2/2] [RFC] i3c: ast2600: Validate AST2600 I3C for MCTP-over-I3C

This patch introduces minimal changes to validate the functionality of
Aspeed AST2600 I3C controllers for MCTP-over-I3C.

Signed-off-by: Dylan Hung <[email protected]>
---
drivers/i3c/master/ast2600-i3c-master.c | 137 +++++++
drivers/i3c/master/dw-i3c-master.c | 513 ++++++++++++++++++++----
drivers/i3c/master/dw-i3c-master.h | 12 +
3 files changed, 581 insertions(+), 81 deletions(-)

diff --git a/drivers/i3c/master/ast2600-i3c-master.c b/drivers/i3c/master/ast2600-i3c-master.c
index 01a47d3dd499..a0a48f10bccb 100644
--- a/drivers/i3c/master/ast2600-i3c-master.c
+++ b/drivers/i3c/master/ast2600-i3c-master.c
@@ -10,6 +10,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/bitfield.h>

#include "dw-i3c-master.h"

@@ -33,9 +34,29 @@
#define AST2600_I3CG_REG1_SA_EN BIT(15)
#define AST2600_I3CG_REG1_INST_ID_MASK GENMASK(19, 16)
#define AST2600_I3CG_REG1_INST_ID(x) (((x) << 16) & AST2600_I3CG_REG1_INST_ID_MASK)
+#define SCL_SW_MODE_OE BIT(20)
+#define SCL_OUT_SW_MODE_VAL BIT(21)
+#define SCL_IN_SW_MODE_VAL BIT(23)
+#define SDA_SW_MODE_OE BIT(24)
+#define SDA_OUT_SW_MODE_VAL BIT(25)
+#define SDA_IN_SW_MODE_VAL BIT(27)
+#define SCL_IN_SW_MODE_EN BIT(28)
+#define SDA_IN_SW_MODE_EN BIT(29)
+#define SCL_OUT_SW_MODE_EN BIT(30)
+#define SDA_OUT_SW_MODE_EN BIT(31)

#define AST2600_DEFAULT_SDA_PULLUP_OHMS 2000

+/* dw-i3c registers */
+#define IBI_QUEUE_STATUS 0x18
+
+#define PRESENT_STATE 0x54
+#define CM_TFR_STS GENMASK(13, 8)
+#define CM_TFR_STS_MASTER_SERV_IBI 0xe
+#define SDA_LINE_SIGNAL_LEVEL BIT(1)
+#define SCL_LINE_SIGNAL_LEVEL BIT(0)
+
+/* DAT */
#define DEV_ADDR_TABLE_IBI_PEC BIT(11)

struct ast2600_i3c {
@@ -117,9 +138,125 @@ static void ast2600_i3c_set_dat_ibi(struct dw_i3c_master *i3c,
}
}

+static int aspeed_i3c_bus_recovery(struct dw_i3c_master *dw)
+{
+ struct ast2600_i3c *i3c = to_ast2600_i3c(dw);
+ int i, ret = -1;
+
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SCL_OUT_SW_MODE_VAL, SCL_OUT_SW_MODE_VAL);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SCL_SW_MODE_OE, SCL_SW_MODE_OE);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SCL_OUT_SW_MODE_EN, SCL_OUT_SW_MODE_EN);
+
+ for (i = 0; i < 19; i++) {
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SCL_OUT_SW_MODE_VAL, 0);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SCL_OUT_SW_MODE_VAL, SCL_OUT_SW_MODE_VAL);
+ if (readl(dw->regs + PRESENT_STATE) & SDA_LINE_SIGNAL_LEVEL) {
+ ret = 0;
+ break;
+ }
+ }
+
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SCL_OUT_SW_MODE_EN, 0);
+ if (ret)
+ dev_err(&dw->base.dev, "Failed to recover the bus\n");
+
+ return ret;
+}
+
+static void ast2600_i3c_gen_target_reset_pattern(struct dw_i3c_master *dw)
+{
+ struct ast2600_i3c *i3c = to_ast2600_i3c(dw);
+ int i;
+
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_OUT_SW_MODE_VAL | SCL_OUT_SW_MODE_VAL,
+ SDA_OUT_SW_MODE_VAL | SCL_OUT_SW_MODE_VAL);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_SW_MODE_OE | SCL_SW_MODE_OE,
+ SDA_SW_MODE_OE | SCL_SW_MODE_OE);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_OUT_SW_MODE_EN | SCL_OUT_SW_MODE_EN,
+ SDA_OUT_SW_MODE_EN | SCL_OUT_SW_MODE_EN);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_IN_SW_MODE_VAL | SCL_IN_SW_MODE_VAL,
+ SDA_IN_SW_MODE_VAL | SCL_IN_SW_MODE_VAL);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_IN_SW_MODE_EN | SCL_IN_SW_MODE_EN,
+ SDA_IN_SW_MODE_EN | SCL_IN_SW_MODE_EN);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SCL_OUT_SW_MODE_VAL, 0);
+ for (i = 0; i < 7; i++) {
+ regmap_write_bits(i3c->global_regs,
+ AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_OUT_SW_MODE_VAL, 0);
+ regmap_write_bits(i3c->global_regs,
+ AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_OUT_SW_MODE_VAL, SDA_OUT_SW_MODE_VAL);
+ }
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SCL_OUT_SW_MODE_VAL, SCL_OUT_SW_MODE_VAL);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_OUT_SW_MODE_VAL, 0);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_OUT_SW_MODE_VAL, SDA_OUT_SW_MODE_VAL);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_OUT_SW_MODE_EN | SCL_OUT_SW_MODE_EN, 0);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_IN_SW_MODE_EN | SCL_IN_SW_MODE_EN, 0);
+}
+
+static bool ast2600_i3c_fsm_exit_serv_ibi(struct dw_i3c_master *dw)
+{
+ u32 state;
+
+ /*
+ * Clear the IBI queue to enable the hardware to generate SCL and
+ * begin detecting the T-bit low to stop reading IBI data.
+ */
+ readl(dw->regs + IBI_QUEUE_STATUS);
+ state = FIELD_GET(CM_TFR_STS, readl(dw->regs + PRESENT_STATE));
+ if (state == CM_TFR_STS_MASTER_SERV_IBI)
+ return false;
+
+ return true;
+}
+
+static void ast2600_i3c_gen_tbits_in(struct dw_i3c_master *dw)
+{
+ struct ast2600_i3c *i3c = to_ast2600_i3c(dw);
+ bool is_idle;
+ int ret;
+
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_IN_SW_MODE_VAL, SDA_IN_SW_MODE_VAL);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_IN_SW_MODE_EN, SDA_IN_SW_MODE_EN);
+
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_IN_SW_MODE_VAL, 0);
+ ret = readx_poll_timeout_atomic(ast2600_i3c_fsm_exit_serv_ibi, dw,
+ is_idle, is_idle, 0, 2000000);
+ regmap_write_bits(i3c->global_regs, AST2600_I3CG_REG1(i3c->global_idx),
+ SDA_IN_SW_MODE_EN, 0);
+ if (ret)
+ dev_err(&dw->base.dev,
+ "Failed to exit the I3C fsm from %lx(MASTER_SERV_IBI): %d",
+ FIELD_GET(CM_TFR_STS, readl(dw->regs + PRESENT_STATE)),
+ ret);
+}
+
static const struct dw_i3c_platform_ops ast2600_i3c_ops = {
.init = ast2600_i3c_init,
.set_dat_ibi = ast2600_i3c_set_dat_ibi,
+ .gen_target_reset_pattern = ast2600_i3c_gen_target_reset_pattern,
+ .gen_tbits_in = ast2600_i3c_gen_tbits_in,
+ .bus_recovery = aspeed_i3c_bus_recovery,
};

static int ast2600_i3c_probe(struct platform_device *pdev)
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 0ec00e644bd4..a9fe886ee840 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -6,6 +6,7 @@
*/

#include <linux/bitops.h>
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/err.h>
@@ -26,8 +27,10 @@
#define DEVICE_CTRL 0x0
#define DEV_CTRL_ENABLE BIT(31)
#define DEV_CTRL_RESUME BIT(30)
+#define DEV_CTRL_ABORT BIT(29)
#define DEV_CTRL_HOT_JOIN_NACK BIT(8)
#define DEV_CTRL_I2C_SLAVE_PRESENT BIT(7)
+#define DEV_CTRL_IBA_INCLUDE BIT(0)

#define DEVICE_ADDR 0x4
#define DEV_ADDR_DYNAMIC_ADDR_VALID BIT(31)
@@ -40,6 +43,16 @@
#define COMMAND_PORT_SDAP BIT(27)
#define COMMAND_PORT_ROC BIT(26)
#define COMMAND_PORT_SPEED(x) (((x) << 21) & GENMASK(23, 21))
+#define SPEED_I3C_SDR0 0x0
+#define SPEED_I3C_SDR1 0x1
+#define SPEED_I3C_SDR2 0x2
+#define SPEED_I3C_SDR3 0x3
+#define SPEED_I3C_SDR4 0x4
+#define SPEED_I3C_HDR_TS 0x5
+#define SPEED_I3C_HDR_DDR 0x6
+#define SPEED_I3C_I2C_FM 0x7
+#define SPEED_I2C_FM 0x0
+#define SPEED_I2C_FMP 0x1
#define COMMAND_PORT_DEV_INDEX(x) (((x) << 16) & GENMASK(20, 16))
#define COMMAND_PORT_CP BIT(15)
#define COMMAND_PORT_CMD(x) (((x) << 7) & GENMASK(14, 7))
@@ -76,6 +89,7 @@

#define RX_TX_DATA_PORT 0x14
#define IBI_QUEUE_STATUS 0x18
+#define IBI_QUEUE_STATUS_RSP_NACK BIT(31)
#define IBI_QUEUE_STATUS_IBI_ID(x) (((x) & GENMASK(15, 8)) >> 8)
#define IBI_QUEUE_STATUS_DATA_LEN(x) ((x) & GENMASK(7, 0))
#define IBI_QUEUE_IBI_ADDR(x) (IBI_QUEUE_STATUS_IBI_ID(x) >> 1)
@@ -96,7 +110,8 @@
#define QUEUE_THLD_CTRL_RESP_BUF(x) (((x) - 1) << 8)

#define DATA_BUFFER_THLD_CTRL 0x20
-#define DATA_BUFFER_THLD_CTRL_RX_BUF GENMASK(11, 8)
+#define DATA_BUFFER_THLD_TX_START GENMASK(18, 16)
+#define DATA_BUFFER_THLD_CTRL_RX_BUF GENMASK(10, 8)

#define IBI_QUEUE_CTRL 0x24
#define IBI_MR_REQ_REJECT 0x2C
@@ -156,6 +171,12 @@
#define DATA_BUFFER_STATUS_LEVEL_TX(x) ((x) & GENMASK(7, 0))

#define PRESENT_STATE 0x54
+#define CM_TFR_STS GENMASK(13, 8)
+#define CM_TFR_STS_MASTER_SERV_IBI 0xe
+#define CM_TFR_STS_MASTER_HALT 0xf
+#define SDA_LINE_SIGNAL_LEVEL BIT(1)
+#define SCL_LINE_SIGNAL_LEVEL BIT(0)
+
#define CCC_DEVICE_STATUS 0x58
#define DEVICE_ADDR_TABLE_POINTER 0x5c
#define DEVICE_ADDR_TABLE_DEPTH(x) (((x) & GENMASK(31, 16)) >> 16)
@@ -173,17 +194,17 @@
#define DEVICE_CTRL_EXTENDED 0xb0
#define SCL_I3C_OD_TIMING 0xb4
#define SCL_I3C_PP_TIMING 0xb8
-#define SCL_I3C_TIMING_HCNT(x) (((x) << 16) & GENMASK(23, 16))
-#define SCL_I3C_TIMING_LCNT(x) ((x) & GENMASK(7, 0))
-#define SCL_I3C_TIMING_CNT_MIN 5
+#define SCL_I3C_TIMING_HCNT GENMASK(23, 16)
+#define SCL_I3C_TIMING_LCNT GENMASK(7, 0)
+#define SCL_I3C_TIMING_CNT_MIN 5

#define SCL_I2C_FM_TIMING 0xbc
-#define SCL_I2C_FM_TIMING_HCNT(x) (((x) << 16) & GENMASK(31, 16))
-#define SCL_I2C_FM_TIMING_LCNT(x) ((x) & GENMASK(15, 0))
+#define SCL_I2C_FM_TIMING_HCNT GENMASK(31, 16)
+#define SCL_I2C_FM_TIMING_LCNT GENMASK(15, 0)

#define SCL_I2C_FMP_TIMING 0xc0
-#define SCL_I2C_FMP_TIMING_HCNT(x) (((x) << 16) & GENMASK(23, 16))
-#define SCL_I2C_FMP_TIMING_LCNT(x) ((x) & GENMASK(15, 0))
+#define SCL_I2C_FMP_TIMING_HCNT GENMASK(23, 16)
+#define SCL_I2C_FMP_TIMING_LCNT GENMASK(15, 0)

#define SCL_EXT_LCNT_TIMING 0xc8
#define SCL_EXT_LCNT_4(x) (((x) << 24) & GENMASK(31, 24))
@@ -192,8 +213,14 @@
#define SCL_EXT_LCNT_1(x) ((x) & GENMASK(7, 0))

#define SCL_EXT_TERMN_LCNT_TIMING 0xcc
+#define SDA_HOLD_SWITCH_DLY_TIMING 0xd0
+#define SDA_TX_HOLD GENMASK(18, 16)
+#define SDA_TX_HOLD_MIN 0b001
+#define SDA_TX_HOLD_MAX 0b111
#define BUS_FREE_TIMING 0xd4
-#define BUS_I3C_MST_FREE(x) ((x) & GENMASK(15, 0))
+#define BUS_AVAIL_TIME GENMASK(31, 16)
+#define MAX_BUS_AVAIL_CNT 0xffffU
+#define BUS_I3C_MST_FREE GENMASK(15, 0)

#define BUS_IDLE_TIMING 0xd8
#define I3C_VER_ID 0xe0
@@ -201,10 +228,11 @@
#define EXTENDED_CAPABILITY 0xe8
#define SLAVE_CONFIG 0xec

-#define DEV_ADDR_TABLE_IBI_MDB BIT(12)
-#define DEV_ADDR_TABLE_SIR_REJECT BIT(13)
#define DEV_ADDR_TABLE_LEGACY_I2C_DEV BIT(31)
#define DEV_ADDR_TABLE_DYNAMIC_ADDR(x) (((x) << 16) & GENMASK(23, 16))
+#define DEV_ADDR_TABLE_MR_REJECT BIT(14)
+#define DEV_ADDR_TABLE_SIR_REJECT BIT(13)
+#define DEV_ADDR_TABLE_IBI_MDB BIT(12)
#define DEV_ADDR_TABLE_STATIC_ADDR(x) ((x) & GENMASK(6, 0))
#define DEV_ADDR_TABLE_LOC(start, idx) ((start) + ((idx) << 2))

@@ -212,8 +240,19 @@
#define I3C_BUS_SDR2_SCL_RATE 6000000
#define I3C_BUS_SDR3_SCL_RATE 4000000
#define I3C_BUS_SDR4_SCL_RATE 2000000
+#define I3C_BUS_I2C_STD_SCL_RATE 100000
+#define I3C_BUS_I2C_STD_TLOW_MIN_NS 4700
+#define I3C_BUS_I2C_STD_THIGH_MIN_NS 4000
+#define I3C_BUS_I2C_STD_TR_MAX_NS 1000
+#define I3C_BUS_I2C_STD_TF_MAX_NS 300
#define I3C_BUS_I2C_FM_TLOW_MIN_NS 1300
+#define I3C_BUS_I2C_FM_THIGH_MIN_NS 600
+#define I3C_BUS_I2C_FM_TR_MAX_NS 300
+#define I3C_BUS_I2C_FM_TF_MAX_NS 300
#define I3C_BUS_I2C_FMP_TLOW_MIN_NS 500
+#define I3C_BUS_I2C_FMP_THIGH_MIN_NS 260
+#define I3C_BUS_I2C_FMP_TR_MAX_NS 120
+#define I3C_BUS_I2C_FMP_TF_MAX_NS 120
#define I3C_BUS_THIGH_MAX_NS 41

#define XFER_TIMEOUT (msecs_to_jiffies(1000))
@@ -292,6 +331,18 @@ to_dw_i3c_master(struct i3c_master_controller *master)
return container_of(master, struct dw_i3c_master, base);
}

+static void dw_i3c_master_set_iba(struct dw_i3c_master *master, bool enable)
+{
+ u32 reg;
+
+ reg = readl(master->regs + DEVICE_CTRL);
+ reg &= ~DEV_CTRL_IBA_INCLUDE;
+ if (enable)
+ reg |= DEV_CTRL_IBA_INCLUDE;
+
+ writel(reg, master->regs + DEVICE_CTRL);
+}
+
static void dw_i3c_master_disable(struct dw_i3c_master *master)
{
writel(readl(master->regs + DEVICE_CTRL) & ~DEV_CTRL_ENABLE,
@@ -304,6 +355,50 @@ static void dw_i3c_master_enable(struct dw_i3c_master *master)
master->regs + DEVICE_CTRL);
}

+static int dw_i3c_master_exit_halt(struct dw_i3c_master *master)
+{
+ u32 status;
+ u32 halt_state = CM_TFR_STS_MASTER_HALT;
+ int ret;
+
+ writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_RESUME,
+ master->regs + DEVICE_CTRL);
+
+ ret = readl_poll_timeout_atomic(master->regs + PRESENT_STATE, status,
+ FIELD_GET(CM_TFR_STS, status) != halt_state,
+ 10, 1000000);
+
+ if (ret)
+ dev_err(&master->base.dev,
+ "Exit halt state failed: %d %#x %#x\n", ret,
+ readl(master->regs + PRESENT_STATE),
+ readl(master->regs + QUEUE_STATUS_LEVEL));
+ return ret;
+}
+
+static int dw_i3c_master_enter_halt(struct dw_i3c_master *master, bool by_sw)
+{
+ u32 status;
+ u32 halt_state = CM_TFR_STS_MASTER_HALT;
+ int ret;
+
+ if (by_sw)
+ writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_ABORT,
+ master->regs + DEVICE_CTRL);
+
+ ret = readl_poll_timeout_atomic(master->regs + PRESENT_STATE, status,
+ FIELD_GET(CM_TFR_STS, status) == halt_state,
+ 10, 1000000);
+
+ if (ret)
+ dev_err(&master->base.dev,
+ "Enter halt state failed: %d %#x %#x\n", ret,
+ readl(master->regs + PRESENT_STATE),
+ readl(master->regs + QUEUE_STATUS_LEVEL));
+
+ return ret;
+}
+
static int dw_i3c_master_get_addr_pos(struct dw_i3c_master *master, u8 addr)
{
int pos;
@@ -506,9 +601,14 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
complete(&xfer->comp);

if (ret < 0) {
+ /*
+ * The controller will enter the HALT state if an error occurs.
+ * Therefore, there is no need to manually halt the controller
+ * through software.
+ */
+ dw_i3c_master_enter_halt(master, false);
dw_i3c_master_dequeue_xfer_locked(master, xfer);
- writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_RESUME,
- master->regs + DEVICE_CTRL);
+ dw_i3c_master_exit_halt(master);
}

xfer = list_first_entry_or_null(&master->xferqueue.list,
@@ -521,41 +621,76 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
dw_i3c_master_start_xfer_locked(master);
}

+static int calc_i2c_clk(struct dw_i3c_master *master, unsigned long fscl,
+ u16 *hcnt, u16 *lcnt)
+{
+ unsigned long core_rate, core_period;
+ u32 period_cnt, margin;
+ u32 hcnt_min, lcnt_min;
+
+ core_rate = master->timing.core_rate;
+ core_period = master->timing.core_period;
+
+ if (fscl <= I3C_BUS_I2C_STD_SCL_RATE) {
+ lcnt_min = DIV_ROUND_UP(I3C_BUS_I2C_STD_TLOW_MIN_NS +
+ I3C_BUS_I2C_STD_TF_MAX_NS,
+ core_period);
+ hcnt_min = DIV_ROUND_UP(I3C_BUS_I2C_STD_THIGH_MIN_NS +
+ I3C_BUS_I2C_STD_TR_MAX_NS,
+ core_period);
+ } else if (fscl <= I3C_BUS_I2C_FM_SCL_RATE) {
+ lcnt_min = DIV_ROUND_UP(I3C_BUS_I2C_FM_TLOW_MIN_NS +
+ I3C_BUS_I2C_FM_TF_MAX_NS,
+ core_period);
+ hcnt_min = DIV_ROUND_UP(I3C_BUS_I2C_FM_THIGH_MIN_NS +
+ I3C_BUS_I2C_FM_TR_MAX_NS,
+ core_period);
+ } else {
+ lcnt_min = DIV_ROUND_UP(I3C_BUS_I2C_FMP_TLOW_MIN_NS +
+ I3C_BUS_I2C_FMP_TF_MAX_NS,
+ core_period);
+ hcnt_min = DIV_ROUND_UP(I3C_BUS_I2C_FMP_THIGH_MIN_NS +
+ I3C_BUS_I2C_FMP_TR_MAX_NS,
+ core_period);
+ }
+
+ period_cnt = DIV_ROUND_UP(core_rate, fscl);
+ margin = (period_cnt - hcnt_min - lcnt_min) >> 1;
+ *lcnt = lcnt_min + margin;
+ *hcnt = max(period_cnt - *lcnt, hcnt_min);
+
+ return 0;
+}
+
static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
{
unsigned long core_rate, core_period;
u32 scl_timing;
u8 hcnt, lcnt;

- core_rate = clk_get_rate(master->core_clk);
- if (!core_rate)
- return -EINVAL;
-
- core_period = DIV_ROUND_UP(1000000000, core_rate);
-
- hcnt = DIV_ROUND_UP(I3C_BUS_THIGH_MAX_NS, core_period) - 1;
- if (hcnt < SCL_I3C_TIMING_CNT_MIN)
- hcnt = SCL_I3C_TIMING_CNT_MIN;
+ core_rate = master->timing.core_rate;
+ core_period = master->timing.core_period;

- lcnt = DIV_ROUND_UP(core_rate, master->base.bus.scl_rate.i3c) - hcnt;
- if (lcnt < SCL_I3C_TIMING_CNT_MIN)
- lcnt = SCL_I3C_TIMING_CNT_MIN;
+ if (master->timing.i3c_pp_scl_high && master->timing.i3c_pp_scl_low) {
+ hcnt = DIV_ROUND_CLOSEST(master->timing.i3c_pp_scl_high,
+ core_period);
+ lcnt = DIV_ROUND_CLOSEST(master->timing.i3c_pp_scl_low,
+ core_period);
+ } else {
+ hcnt = DIV_ROUND_UP(I3C_BUS_THIGH_MAX_NS, core_period) - 1;
+ if (hcnt < SCL_I3C_TIMING_CNT_MIN)
+ hcnt = SCL_I3C_TIMING_CNT_MIN;
+
+ lcnt = DIV_ROUND_UP(core_rate, master->base.bus.scl_rate.i3c) -
+ hcnt;
+ if (lcnt < SCL_I3C_TIMING_CNT_MIN)
+ lcnt = SCL_I3C_TIMING_CNT_MIN;
+ }

- scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
+ scl_timing = FIELD_PREP(SCL_I3C_TIMING_HCNT, hcnt) |
+ FIELD_PREP(SCL_I3C_TIMING_LCNT, lcnt);
writel(scl_timing, master->regs + SCL_I3C_PP_TIMING);

- /*
- * In pure i3c mode, MST_FREE represents tCAS. In shared mode, this
- * will be set up by dw_i2c_clk_cfg as tLOW.
- */
- if (master->base.bus.mode == I3C_BUS_MODE_PURE)
- writel(BUS_I3C_MST_FREE(lcnt), master->regs + BUS_FREE_TIMING);
-
- lcnt = max_t(u8,
- DIV_ROUND_UP(I3C_BUS_TLOW_OD_MIN_NS, core_period), lcnt);
- scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt);
- writel(scl_timing, master->regs + SCL_I3C_OD_TIMING);
-
lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_SDR1_SCL_RATE) - hcnt;
scl_timing = SCL_EXT_LCNT_1(lcnt);
lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_SDR2_SCL_RATE) - hcnt;
@@ -566,6 +701,20 @@ static int dw_i3c_clk_cfg(struct dw_i3c_master *master)
scl_timing |= SCL_EXT_LCNT_4(lcnt);
writel(scl_timing, master->regs + SCL_EXT_LCNT_TIMING);

+ if (master->timing.i3c_od_scl_high && master->timing.i3c_od_scl_low) {
+ hcnt = DIV_ROUND_CLOSEST(master->timing.i3c_od_scl_high,
+ core_period);
+ lcnt = DIV_ROUND_CLOSEST(master->timing.i3c_od_scl_low,
+ core_period);
+ } else {
+ lcnt = max_t(u8,
+ DIV_ROUND_UP(I3C_BUS_TLOW_OD_MIN_NS, core_period),
+ lcnt);
+ }
+ scl_timing = FIELD_PREP(SCL_I3C_TIMING_HCNT, hcnt) |
+ FIELD_PREP(SCL_I3C_TIMING_LCNT, lcnt);
+ writel(scl_timing, master->regs + SCL_I3C_OD_TIMING);
+
return 0;
}

@@ -575,27 +724,56 @@ static int dw_i2c_clk_cfg(struct dw_i3c_master *master)
u16 hcnt, lcnt;
u32 scl_timing;

- core_rate = clk_get_rate(master->core_clk);
- if (!core_rate)
- return -EINVAL;
+ core_rate = master->timing.core_rate;
+ core_period = master->timing.core_period;

- core_period = DIV_ROUND_UP(1000000000, core_rate);
-
- lcnt = DIV_ROUND_UP(I3C_BUS_I2C_FMP_TLOW_MIN_NS, core_period);
- hcnt = DIV_ROUND_UP(core_rate, I3C_BUS_I2C_FM_PLUS_SCL_RATE) - lcnt;
- scl_timing = SCL_I2C_FMP_TIMING_HCNT(hcnt) |
- SCL_I2C_FMP_TIMING_LCNT(lcnt);
+ calc_i2c_clk(master, I3C_BUS_I2C_FM_PLUS_SCL_RATE, &hcnt, &lcnt);
+ scl_timing = FIELD_PREP(SCL_I2C_FMP_TIMING_HCNT, hcnt) |
+ FIELD_PREP(SCL_I2C_FMP_TIMING_LCNT, lcnt);
writel(scl_timing, master->regs + SCL_I2C_FMP_TIMING);

- lcnt = DIV_ROUND_UP(I3C_BUS_I2C_FM_TLOW_MIN_NS, core_period);
- hcnt = DIV_ROUND_UP(core_rate, I3C_BUS_I2C_FM_SCL_RATE) - lcnt;
- scl_timing = SCL_I2C_FM_TIMING_HCNT(hcnt) |
- SCL_I2C_FM_TIMING_LCNT(lcnt);
+ calc_i2c_clk(master, master->base.bus.scl_rate.i2c, &hcnt, &lcnt);
+ scl_timing = FIELD_PREP(SCL_I2C_FM_TIMING_HCNT, hcnt) |
+ FIELD_PREP(SCL_I2C_FM_TIMING_LCNT, lcnt);
writel(scl_timing, master->regs + SCL_I2C_FM_TIMING);

- writel(BUS_I3C_MST_FREE(lcnt), master->regs + BUS_FREE_TIMING);
- writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_I2C_SLAVE_PRESENT,
- master->regs + DEVICE_CTRL);
+ return 0;
+}
+
+static int dw_i3c_bus_clk_cfg(struct i3c_master_controller *m)
+{
+ struct dw_i3c_master *master = to_dw_i3c_master(m);
+ struct i3c_bus *bus = i3c_master_get_bus(m);
+ int ret;
+ u16 lcnt;
+
+ ret = dw_i2c_clk_cfg(master);
+ if (ret)
+ return ret;
+
+ ret = dw_i3c_clk_cfg(master);
+ if (ret)
+ return ret;
+
+ /*
+ * I3C register 0xd4[15:0] BUS_FREE_TIMING used to control several parameters:
+ * - bus free time between a STOP condition and a START condition
+ *
+ * The constraints of these parameters differ in various bus contexts:
+ * MIPI I3C, pure bus : BUS_FREE_TIMING = I3C PP SCL low period
+ * MIPI I3C, mixed bus: BUS_FREE_TIMING = I2C FM SCL low period
+ */
+ if (bus->mode == I3C_BUS_MODE_PURE) {
+ lcnt = FIELD_GET(SCL_I3C_TIMING_LCNT,
+ readl(master->regs + SCL_I3C_PP_TIMING));
+ } else {
+ writel(readl(master->regs + DEVICE_CTRL) | DEV_CTRL_I2C_SLAVE_PRESENT,
+ master->regs + DEVICE_CTRL);
+ lcnt = FIELD_GET(SCL_I2C_FM_TIMING_LCNT,
+ readl(master->regs + SCL_I2C_FM_TIMING));
+ }
+
+ writel(FIELD_PREP(BUS_I3C_MST_FREE, lcnt), master->regs + BUS_FREE_TIMING);

return 0;
}
@@ -603,7 +781,6 @@ static int dw_i2c_clk_cfg(struct dw_i3c_master *master)
static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
{
struct dw_i3c_master *master = to_dw_i3c_master(m);
- struct i3c_bus *bus = i3c_master_get_bus(m);
struct i3c_device_info info = { };
u32 thld_ctrl;
int ret;
@@ -612,21 +789,11 @@ static int dw_i3c_master_bus_init(struct i3c_master_controller *m)
if (ret)
return ret;

- switch (bus->mode) {
- case I3C_BUS_MODE_MIXED_FAST:
- case I3C_BUS_MODE_MIXED_LIMITED:
- ret = dw_i2c_clk_cfg(master);
- if (ret)
- return ret;
- fallthrough;
- case I3C_BUS_MODE_PURE:
- ret = dw_i3c_clk_cfg(master);
- if (ret)
- return ret;
- break;
- default:
- return -EINVAL;
- }
+ spin_lock_init(&master->devs_lock);
+
+ ret = dw_i3c_bus_clk_cfg(m);
+ if (ret)
+ return ret;

thld_ctrl = readl(master->regs + QUEUE_THLD_CTRL);
thld_ctrl &= ~(QUEUE_THLD_CTRL_RESP_BUF_MASK |
@@ -682,6 +849,7 @@ static int dw_i3c_ccc_set(struct dw_i3c_master *master,
{
struct dw_i3c_xfer *xfer;
struct dw_i3c_cmd *cmd;
+ u32 sda_lvl_pre, sda_lvl_post;
int ret, pos = 0;

if (ccc->id & I3C_CCC_DIRECT) {
@@ -707,9 +875,21 @@ static int dw_i3c_ccc_set(struct dw_i3c_master *master,
COMMAND_PORT_TOC |
COMMAND_PORT_ROC;

+ sda_lvl_pre = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
dw_i3c_master_enqueue_xfer(master, xfer);
- if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
+ if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT)) {
+ dw_i3c_master_enter_halt(master, true);
dw_i3c_master_dequeue_xfer(master, xfer);
+ sda_lvl_post = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
+ if (sda_lvl_pre == 0 && sda_lvl_post == 0) {
+ dev_warn(&master->base.dev,
+ "SDA stuck low! Try to recover the bus...\n");
+ master->platform_ops->bus_recovery(master);
+ }
+ dw_i3c_master_exit_halt(master);
+ }

ret = xfer->ret;
if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK)
@@ -724,6 +904,7 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc)
{
struct dw_i3c_xfer *xfer;
struct dw_i3c_cmd *cmd;
+ u32 sda_lvl_pre, sda_lvl_post;
int ret, pos;

pos = dw_i3c_master_get_addr_pos(master, ccc->dests[0].addr);
@@ -748,9 +929,21 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc)
COMMAND_PORT_TOC |
COMMAND_PORT_ROC;

+ sda_lvl_pre = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
dw_i3c_master_enqueue_xfer(master, xfer);
- if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
+ if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT)) {
+ dw_i3c_master_enter_halt(master, true);
dw_i3c_master_dequeue_xfer(master, xfer);
+ sda_lvl_post = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
+ if (sda_lvl_pre == 0 && sda_lvl_post == 0) {
+ dev_warn(&master->base.dev,
+ "SDA stuck low! Try to recover the bus...\n");
+ master->platform_ops->bus_recovery(master);
+ }
+ dw_i3c_master_exit_halt(master);
+ }

ret = xfer->ret;
if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK)
@@ -782,7 +975,7 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
struct dw_i3c_master *master = to_dw_i3c_master(m);
struct dw_i3c_xfer *xfer;
struct dw_i3c_cmd *cmd;
- u32 olddevs, newdevs;
+ u32 olddevs, newdevs, sda_lvl_pre, sda_lvl_post;
u8 p, last_addr = 0;
int ret, pos;

@@ -825,9 +1018,21 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
COMMAND_PORT_TOC |
COMMAND_PORT_ROC;

+ sda_lvl_pre = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
dw_i3c_master_enqueue_xfer(master, xfer);
- if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
+ if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT)) {
+ dw_i3c_master_enter_halt(master, true);
dw_i3c_master_dequeue_xfer(master, xfer);
+ sda_lvl_post = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
+ if (sda_lvl_pre == 0 && sda_lvl_post == 0) {
+ dev_warn(&master->base.dev,
+ "SDA stuck low! Try to recover the bus...\n");
+ master->platform_ops->bus_recovery(master);
+ }
+ dw_i3c_master_exit_halt(master);
+ }

newdevs = GENMASK(master->maxdevs - cmd->rx_len - 1, 0);
newdevs &= ~olddevs;
@@ -835,6 +1040,16 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
for (pos = 0; pos < master->maxdevs; pos++) {
if (newdevs & BIT(pos))
i3c_master_add_i3c_dev_locked(m, master->devs[pos].addr);
+
+ /* cleanup the free HW DATs */
+ if (master->free_pos & BIT(pos)) {
+ u32 dat = DEV_ADDR_TABLE_SIR_REJECT |
+ DEV_ADDR_TABLE_MR_REJECT;
+
+ writel(dat,
+ master->regs +
+ DEV_ADDR_TABLE_LOC(master->datstartaddr, pos));
+ }
}

dw_i3c_master_free_xfer(xfer);
@@ -851,6 +1066,7 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
struct dw_i3c_master *master = to_dw_i3c_master(m);
unsigned int nrxwords = 0, ntxwords = 0;
struct dw_i3c_xfer *xfer;
+ u32 sda_lvl_pre, sda_lvl_post;
int i, ret = 0;

if (!i3c_nxfers)
@@ -901,9 +1117,21 @@ static int dw_i3c_master_priv_xfers(struct i3c_dev_desc *dev,
cmd->cmd_lo |= COMMAND_PORT_TOC;
}

+ sda_lvl_pre = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
dw_i3c_master_enqueue_xfer(master, xfer);
- if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
+ if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT)) {
+ dw_i3c_master_enter_halt(master, true);
dw_i3c_master_dequeue_xfer(master, xfer);
+ sda_lvl_post = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
+ if (sda_lvl_pre == 0 && sda_lvl_post == 0) {
+ dev_warn(&master->base.dev,
+ "SDA stuck low! Try to recover the bus...\n");
+ master->platform_ops->bus_recovery(master);
+ }
+ dw_i3c_master_exit_halt(master);
+ }

for (i = 0; i < i3c_nxfers; i++) {
struct dw_i3c_cmd *cmd = &xfer->cmds[i];
@@ -1002,6 +1230,7 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
struct dw_i3c_master *master = to_dw_i3c_master(m);
unsigned int nrxwords = 0, ntxwords = 0;
struct dw_i3c_xfer *xfer;
+ u32 sda_lvl_pre, sda_lvl_post;
int i, ret = 0;

if (!i2c_nxfers)
@@ -1021,6 +1250,12 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
nrxwords > master->caps.datafifodepth)
return -ENOTSUPP;

+ if (ntxwords == 0 && nrxwords == 0) {
+ dev_warn(&master->base.dev,
+ "Transfers w/o data bytes are not supported");
+ return -EOPNOTSUPP;
+ }
+
xfer = dw_i3c_master_alloc_xfer(master, i2c_nxfers);
if (!xfer)
return -ENOMEM;
@@ -1048,9 +1283,21 @@ static int dw_i3c_master_i2c_xfers(struct i2c_dev_desc *dev,
cmd->cmd_lo |= COMMAND_PORT_TOC;
}

+ sda_lvl_pre = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
dw_i3c_master_enqueue_xfer(master, xfer);
- if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
+ if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT)) {
+ dw_i3c_master_enter_halt(master, true);
dw_i3c_master_dequeue_xfer(master, xfer);
+ sda_lvl_post = FIELD_GET(SDA_LINE_SIGNAL_LEVEL,
+ readl(master->regs + PRESENT_STATE));
+ if (sda_lvl_pre == 0 && sda_lvl_post == 0) {
+ dev_warn(&master->base.dev,
+ "SDA stuck low! Try to recover the bus...\n");
+ master->platform_ops->bus_recovery(master);
+ }
+ dw_i3c_master_exit_halt(master);
+ }

ret = xfer->ret;
dw_i3c_master_free_xfer(xfer);
@@ -1272,8 +1519,10 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,
struct i3c_ibi_slot *slot;
struct i3c_dev_desc *dev;
unsigned long flags;
+ u32 state;
u8 addr, len;
int idx;
+ bool terminate_ibi = false;

addr = IBI_QUEUE_IBI_ADDR(status);
len = IBI_QUEUE_STATUS_DATA_LEN(status);
@@ -1316,6 +1565,7 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,
dev_dbg_ratelimited(&master->base.dev,
"IBI payload len %d greater than max %d\n",
len, dev->ibi->max_payload_len);
+ terminate_ibi = true;
goto err_drain;
}

@@ -1331,6 +1581,9 @@ static void dw_i3c_master_handle_ibi_sir(struct dw_i3c_master *master,

err_drain:
dw_i3c_master_drain_ibi_queue(master, len);
+ state = FIELD_GET(CM_TFR_STS, readl(master->regs + PRESENT_STATE));
+ if (terminate_ibi && state == CM_TFR_STS_MASTER_SERV_IBI)
+ master->platform_ops->gen_tbits_in(master);

spin_unlock_irqrestore(&master->devs_lock, flags);
}
@@ -1343,15 +1596,30 @@ static void dw_i3c_master_irq_handle_ibis(struct dw_i3c_master *master)
{
unsigned int i, len, n_ibis;
u32 reg;
+ int ret;

reg = readl(master->regs + QUEUE_STATUS_LEVEL);
n_ibis = QUEUE_STATUS_IBI_STATUS_CNT(reg);
if (!n_ibis)
return;

+ if (n_ibis > 16) {
+ dev_err(&master->base.dev,
+ "The n_ibis %d surpasses the tolerance level for the IBI buffer\n",
+ n_ibis);
+ goto ibi_fifo_clear;
+ }
+
for (i = 0; i < n_ibis; i++) {
reg = readl(master->regs + IBI_QUEUE_STATUS);

+ if (reg & IBI_QUEUE_STATUS_RSP_NACK) {
+ dev_dbg_ratelimited(&master->base.dev,
+ "Nacked IBI from non-requested dev addr %02lx\n",
+ IBI_QUEUE_IBI_ADDR(reg));
+ goto ibi_fifo_clear;
+ }
+
if (IBI_TYPE_SIRQ(reg)) {
dw_i3c_master_handle_ibi_sir(master, reg);
} else if (IBI_TYPE_HJ(reg)) {
@@ -1364,6 +1632,19 @@ static void dw_i3c_master_irq_handle_ibis(struct dw_i3c_master *master)
dw_i3c_master_drain_ibi_queue(master, len);
}
}
+
+ return;
+
+ibi_fifo_clear:
+ dw_i3c_master_enter_halt(master, true);
+ writel(RESET_CTRL_IBI_QUEUE, master->regs + RESET_CTRL);
+ ret = readl_poll_timeout_atomic(master->regs + RESET_CTRL, reg, !reg,
+ 10, 1000000);
+ if (ret)
+ dev_err(&master->base.dev,
+ "Timeout waiting for IBI FIFO reset\n");
+
+ dw_i3c_master_exit_halt(master);
}

static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
@@ -1378,11 +1659,14 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id)
return IRQ_NONE;
}

- spin_lock(&master->xferqueue.lock);
- dw_i3c_master_end_xfer_locked(master, status);
- if (status & INTR_TRANSFER_ERR_STAT)
- writel(INTR_TRANSFER_ERR_STAT, master->regs + INTR_STATUS);
- spin_unlock(&master->xferqueue.lock);
+ if (status & INTR_RESP_READY_STAT ||
+ status & INTR_TRANSFER_ERR_STAT) {
+ spin_lock(&master->xferqueue.lock);
+ dw_i3c_master_end_xfer_locked(master, status);
+ if (status & INTR_TRANSFER_ERR_STAT)
+ writel(INTR_TRANSFER_ERR_STAT, master->regs + INTR_STATUS);
+ spin_unlock(&master->xferqueue.lock);
+ }

if (status & INTR_IBI_THLD_STAT)
dw_i3c_master_irq_handle_ibis(master);
@@ -1439,11 +1723,70 @@ static void dw_i3c_platform_set_dat_ibi_nop(struct dw_i3c_master *i3c,
{
}

+static void dw_i3c_gen_target_reset_pattern_nop(struct dw_i3c_master *i3c)
+{
+}
+
+static void dw_i3c_gen_tbits_in_nop(struct dw_i3c_master *i3c)
+{
+}
+
+static int dw_i3c_bus_recovery_nop(struct dw_i3c_master *i3c)
+{
+ return 0;
+}
+
static const struct dw_i3c_platform_ops dw_i3c_platform_ops_default = {
.init = dw_i3c_platform_init_nop,
.set_dat_ibi = dw_i3c_platform_set_dat_ibi_nop,
+ .gen_target_reset_pattern = dw_i3c_gen_target_reset_pattern_nop,
+ .gen_tbits_in = dw_i3c_gen_tbits_in_nop,
+ .bus_recovery = dw_i3c_bus_recovery_nop,
};

+static int dw_i3c_of_populate_bus_timing(struct dw_i3c_master *master,
+ struct device_node *np)
+{
+ u32 val, reg, sda_tx_hold_ns;
+
+ master->timing.core_rate = clk_get_rate(master->core_clk);
+ if (!master->timing.core_rate) {
+ dev_err(&master->base.dev, "core clock rate not found\n");
+ return -EINVAL;
+ }
+
+ /* core_period is in nanosecond */
+ master->timing.core_period =
+ DIV_ROUND_UP(1000000000, master->timing.core_rate);
+
+ /* Parse configurations from the device tree */
+ if (!of_property_read_u32(np, "i3c-pp-scl-hi-period-ns", &val))
+ master->timing.i3c_pp_scl_high = val;
+
+ if (!of_property_read_u32(np, "i3c-pp-scl-lo-period-ns", &val))
+ master->timing.i3c_pp_scl_low = val;
+
+ if (!of_property_read_u32(np, "i3c-od-scl-hi-period-ns", &val))
+ master->timing.i3c_od_scl_high = val;
+
+ if (!of_property_read_u32(np, "i3c-od-scl-lo-period-ns", &val))
+ master->timing.i3c_od_scl_low = val;
+
+ sda_tx_hold_ns = SDA_TX_HOLD_MIN * master->timing.core_period;
+ if (!of_property_read_u32(np, "sda-tx-hold-ns", &val))
+ sda_tx_hold_ns = val;
+
+ val = clamp((u32)DIV_ROUND_CLOSEST(sda_tx_hold_ns,
+ master->timing.core_period),
+ (u32)SDA_TX_HOLD_MIN, (u32)SDA_TX_HOLD_MAX);
+ reg = readl(master->regs + SDA_HOLD_SWITCH_DLY_TIMING);
+ reg &= ~SDA_TX_HOLD;
+ reg |= FIELD_PREP(SDA_TX_HOLD, val);
+ writel(reg, master->regs + SDA_HOLD_SWITCH_DLY_TIMING);
+
+ return 0;
+}
+
static void dw_i3c_hj_work(struct work_struct *work)
{
struct dw_i3c_master *master =
@@ -1456,6 +1799,7 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
struct platform_device *pdev)
{
const struct i3c_master_controller_ops *ops;
+ struct device_node *np;
int ret, irq;

if (!master->platform_ops)
@@ -1493,6 +1837,11 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,

platform_set_drvdata(pdev, master);

+ np = pdev->dev.of_node;
+ ret = dw_i3c_of_populate_bus_timing(master, np);
+ if (ret)
+ goto err_assert_rst;
+
/* Information regarding the FIFOs/QUEUEs depth */
ret = readl(master->regs + QUEUE_STATUS_LEVEL);
master->caps.cmdfifodepth = QUEUE_STATUS_LEVEL_CMD(ret);
@@ -1514,6 +1863,8 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
if (ret)
goto err_assert_rst;

+ dw_i3c_master_set_iba(master, true);
+
return 0;

err_assert_rst:
diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c-master.h
index 4ab94aa72252..4c41bbd6490a 100644
--- a/drivers/i3c/master/dw-i3c-master.h
+++ b/drivers/i3c/master/dw-i3c-master.h
@@ -58,6 +58,15 @@ struct dw_i3c_master {
/* platform-specific data */
const struct dw_i3c_platform_ops *platform_ops;

+ struct {
+ unsigned long core_rate;
+ unsigned long core_period;
+ u32 i3c_od_scl_low;
+ u32 i3c_od_scl_high;
+ u32 i3c_pp_scl_low;
+ u32 i3c_pp_scl_high;
+ u32 timed_reset_scl_low_ns;
+ } timing;
struct work_struct hj_work;
};

@@ -78,6 +87,9 @@ struct dw_i3c_platform_ops {
*/
void (*set_dat_ibi)(struct dw_i3c_master *i3c,
struct i3c_dev_desc *dev, bool enable, u32 *reg);
+ void (*gen_target_reset_pattern)(struct dw_i3c_master *i3c);
+ void (*gen_tbits_in)(struct dw_i3c_master *i3c);
+ int (*bus_recovery)(struct dw_i3c_master *i3c);
};

extern int dw_i3c_common_probe(struct dw_i3c_master *master,
--
2.25.1


2024-05-06 10:52:14

by Dylan Hung

[permalink] [raw]
Subject: [PATCH 1/2] [RFC] ARM: dts: aspeed-g6: Add AST2600 I3Cs

The AST2600 has 6 I3Cs which are based on dw-i3c along with Aspeed
proprietary enhancements.

Signed-off-by: Dylan Hung <[email protected]>
---
arch/arm/boot/dts/aspeed/aspeed-g6.dtsi | 137 ++++++++++++++++++++++++
1 file changed, 137 insertions(+)

diff --git a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
index 29f94696d8b1..d0d685747b74 100644
--- a/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
+++ b/arch/arm/boot/dts/aspeed/aspeed-g6.dtsi
@@ -13,6 +13,12 @@ / {
interrupt-parent = <&gic>;

aliases {
+ i3c0 = &i3c0;
+ i3c1 = &i3c1;
+ i3c2 = &i3c2;
+ i3c3 = &i3c3;
+ i3c4 = &i3c4;
+ i3c5 = &i3c5;
i2c0 = &i2c0;
i2c1 = &i2c1;
i2c2 = &i2c2;
@@ -579,6 +585,13 @@ wdt4: watchdog@1e7850c0 {
status = "disabled";
};

+ i3c: bus@1e7a0000 {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0 0x1e7a0000 0x8000>;
+ };
+
peci0: peci-controller@1e78b000 {
compatible = "aspeed,ast2600-peci";
reg = <0x1e78b000 0x100>;
@@ -1125,3 +1138,127 @@ i2c15: i2c-bus@800 {
status = "disabled";
};
};
+
+&i3c {
+ i3c_global: i3c-global {
+ compatible = "aspeed,ast2600-i3c-global", "simple-mfd", "syscon";
+ resets = <&syscon ASPEED_RESET_I3C>;
+ reg = <0x0 0x1000>;
+ };
+
+ i3c0: i3c@2000 {
+ compatible = "aspeed,ast2600-i3c";
+ reg = <0x2000 0x1000>;
+ clocks = <&syscon ASPEED_CLK_GATE_I3C0CLK>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i3c1_default>;
+ interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+ aspeed,global-regs = <&i3c_global 0>;
+ status = "disabled";
+
+ i2c-scl-hz = <1000000>;
+ i3c-scl-hz = <2000000>;
+ i3c-od-scl-hi-period-ns = <380>;
+ i3c-od-scl-lo-period-ns = <620>;
+ i3c-pp-scl-hi-period-ns = <250>;
+ i3c-pp-scl-lo-period-ns = <250>;
+ sda-tx-hold-ns = <70>;
+ sda-pullup-ohms = <2000>;
+ };
+
+ i3c1: i3c@3000 {
+ compatible = "aspeed,ast2600-i3c";
+ reg = <0x3000 0x1000>;
+ clocks = <&syscon ASPEED_CLK_GATE_I3C1CLK>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i3c2_default>;
+ interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+ aspeed,global-regs = <&i3c_global 1>;
+ status = "disabled";
+
+ i2c-scl-hz = <1000000>;
+ i3c-scl-hz = <2000000>;
+ i3c-od-scl-hi-period-ns = <380>;
+ i3c-od-scl-lo-period-ns = <620>;
+ i3c-pp-scl-hi-period-ns = <250>;
+ i3c-pp-scl-lo-period-ns = <250>;
+ sda-tx-hold-ns = <70>;
+ sda-pullup-ohms = <2000>;
+ };
+
+ i3c2: i3c@4000 {
+ compatible = "aspeed,ast2600-i3c";
+ reg = <0x4000 0x1000>;
+ clocks = <&syscon ASPEED_CLK_GATE_I3C2CLK>;
+ interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>;
+ aspeed,global-regs = <&i3c_global 2>;
+ status = "disabled";
+
+ i2c-scl-hz = <1000000>;
+ i3c-scl-hz = <2000000>;
+ i3c-od-scl-hi-period-ns = <380>;
+ i3c-od-scl-lo-period-ns = <620>;
+ i3c-pp-scl-hi-period-ns = <250>;
+ i3c-pp-scl-lo-period-ns = <250>;
+ sda-tx-hold-ns = <70>;
+ sda-pullup-ohms = <2000>;
+ };
+
+ i3c3: i3c@5000 {
+ compatible = "aspeed,ast2600-i3c";
+ reg = <0x5000 0x1000>;
+ clocks = <&syscon ASPEED_CLK_GATE_I3C3CLK>;
+ interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>;
+ aspeed,global-regs = <&i3c_global 3>;
+ status = "disabled";
+
+ i2c-scl-hz = <1000000>;
+ i3c-scl-hz = <2000000>;
+ i3c-od-scl-hi-period-ns = <380>;
+ i3c-od-scl-lo-period-ns = <620>;
+ i3c-pp-scl-hi-period-ns = <250>;
+ i3c-pp-scl-lo-period-ns = <250>;
+ sda-tx-hold-ns = <70>;
+ sda-pullup-ohms = <2000>;
+ };
+
+ i3c4: i3c@6000 {
+ compatible = "aspeed,ast2600-i3c";
+ reg = <0x6000 0x1000>;
+ clocks = <&syscon ASPEED_CLK_GATE_I3C4CLK>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i3c5_default>;
+ interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
+ aspeed,global-regs = <&i3c_global 4>;
+ status = "disabled";
+
+ i2c-scl-hz = <1000000>;
+ i3c-scl-hz = <2000000>;
+ i3c-od-scl-hi-period-ns = <380>;
+ i3c-od-scl-lo-period-ns = <620>;
+ i3c-pp-scl-hi-period-ns = <250>;
+ i3c-pp-scl-lo-period-ns = <250>;
+ sda-tx-hold-ns = <70>;
+ sda-pullup-ohms = <2000>;
+ };
+
+ i3c5: i3c@7000 {
+ compatible = "aspeed,ast2600-i3c";
+ reg = <0x7000 0x1000>;
+ clocks = <&syscon ASPEED_CLK_GATE_I3C5CLK>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_i3c6_default>;
+ interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>;
+ aspeed,global-regs = <&i3c_global 5>;
+ status = "disabled";
+
+ i2c-scl-hz = <1000000>;
+ i3c-scl-hz = <2000000>;
+ i3c-od-scl-hi-period-ns = <380>;
+ i3c-od-scl-lo-period-ns = <620>;
+ i3c-pp-scl-hi-period-ns = <250>;
+ i3c-pp-scl-lo-period-ns = <250>;
+ sda-tx-hold-ns = <70>;
+ sda-pullup-ohms = <2000>;
+ };
+};
--
2.25.1


2024-05-06 13:47:22

by Jeremy Kerr

[permalink] [raw]
Subject: Re: [PATCH 0/2] [RFC] i3c: ast2600: Add MCTP-over-I3C support

Hi Dylan,

> Add Aspeed AST2600 I3C controllers and add minimal changes to
> validate
> the functionality for MCTP-over-I3C.
>
> Dylan Hung (2):
>   [RFC] ARM: dts: aspeed-g6: Add AST2600 I3Cs

I'd sent a similar patch earlier:

https://lists.ozlabs.org/pipermail/linux-aspeed/2024-May/012193.html

And a v2 to account for some feedback on the dts layout:

https://lists.ozlabs.org/pipermail/linux-aspeed/2024-May/012213.html

>   [RFC] i3c: ast2600: Validate AST2600 I3C for MCTP-over-I3C

Looks interesting, I'll check this out soon.

Cheers,


Jeremy