summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-23 14:03:04 +0100
committerMark Brown <broonie@kernel.org>2026-07-23 14:03:04 +0100
commit11c3cb9bb3f764c0fdabe5316a902d4e343c95d7 (patch)
treece371abc430aa3d56aee37d6626bf87b51dc9723
parentae37b12f68eeaea25f6fee8e881e67c3785be102 (diff)
parent0043b9bb5806605185cdd4b90b0ba02c9746d3df (diff)
downloadlinux-next-11c3cb9bb3f764c0fdabe5316a902d4e343c95d7.tar.gz
linux-next-11c3cb9bb3f764c0fdabe5316a902d4e343c95d7.zip
Merge branch 'i3c/next' of https://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux.git
-rw-r--r--Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml1
-rw-r--r--Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml58
-rw-r--r--MAINTAINERS8
-rw-r--r--drivers/i3c/master.c99
-rw-r--r--drivers/i3c/master/Kconfig15
-rw-r--r--drivers/i3c/master/Makefile1
-rw-r--r--drivers/i3c/master/adi-i3c-master.c19
-rw-r--r--drivers/i3c/master/amd-i3c-master.c1124
-rw-r--r--drivers/i3c/master/dw-i3c-master.c41
-rw-r--r--drivers/i3c/master/i3c-master-cdns.c3
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/core.c5
-rw-r--r--drivers/i3c/master/renesas-i3c.c391
-rw-r--r--drivers/i3c/master/svc-i3c-master.c22
-rw-r--r--include/linux/i3c/ccc.h11
14 files changed, 1644 insertions, 154 deletions
diff --git a/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml b/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml
index 6fa3078074d0..280f0ada3c43 100644
--- a/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml
+++ b/Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml
@@ -19,6 +19,7 @@ properties:
- items:
- enum:
- axiado,ax3000-i3c
+ - axiado,ax3005-i3c
- const: cdns,i3c-master
reg:
diff --git a/Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml b/Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml
new file mode 100644
index 000000000000..2caa245a8656
--- /dev/null
+++ b/Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i3c/xlnx,axi-i3c-1.0.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: AMD I3C master
+
+maintainers:
+ - Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
+ - Shubham Patil <shubhamsanjay.patil@amd.com>
+
+description:
+ The AXI-I3C IP is an I3C Controller with an AXI4-Lite interface, compatible
+ with the MIPI I3C Specification v1.1.1. The design includes bidirectional I/O
+ buffers that implement open collector drivers for the SDA and SCL signals.
+ External pull-up resistors are required to properly hold the bus at a Logic-1
+ level when the drivers are released.
+
+ For more details, please see https://docs.amd.com/r/en-US/pg439-axi-i3c
+
+properties:
+ compatible:
+ const: xlnx,axi-i3c-1.0
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+
+allOf:
+ - $ref: i3c.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ i3c@80000000 {
+ compatible = "xlnx,axi-i3c-1.0";
+ reg = <0x80000000 0x10000>;
+ clocks = <&zynqmp_clk 71>;
+ interrupt-parent = <&imux>;
+ interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
+ #address-cells = <3>;
+ #size-cells = <0>;
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index f42b1af79f33..05766202cfb1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1044,6 +1044,14 @@ L: linux-sound@vger.kernel.org
S: Supported
F: sound/soc/amd/
+AMD AXI I3C MASTER DRIVER
+M: Shubhrajyoti Datta <shubhrajyoti.datta@amd.com>
+M: Shubham Patil <shubhamsanjay.patil@amd.com>
+L: linux-i3c@lists.infradead.org
+S: Maintained
+F: Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml
+F: drivers/i3c/master/amd-i3c-master.c
+
AMD AXI W1 DRIVER
M: Kris Chaplin <kris.chaplin@amd.com>
R: Thomas Delev <thomas.delev@amd.com>
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index f1be38a640ca..fd3e79d10c84 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -952,6 +952,8 @@ static void *i3c_ccc_cmd_dest_init(struct i3c_ccc_cmd_dest *dest, u8 addr,
{
dest->addr = addr;
dest->payload.len = payloadlen;
+ dest->payload.actual_len = 0;
+ dest->payload.optional_bytes = 0;
if (payloadlen)
dest->payload.data = kzalloc(payloadlen, GFP_KERNEL);
else
@@ -965,17 +967,54 @@ static void i3c_ccc_cmd_dest_cleanup(struct i3c_ccc_cmd_dest *dest)
kfree(dest->payload.data);
}
-static void i3c_ccc_cmd_init(struct i3c_ccc_cmd *cmd, bool rnw, u8 id,
- struct i3c_ccc_cmd_dest *dests,
- unsigned int ndests)
+static void i3c_ccc_cmd_init_retries(struct i3c_ccc_cmd *cmd, bool rnw, u8 id,
+ struct i3c_ccc_cmd_dest *dests,
+ unsigned int ndests, unsigned int retries)
{
cmd->rnw = rnw ? 1 : 0;
cmd->id = id;
cmd->dests = dests;
cmd->ndests = ndests;
+ cmd->retries = retries;
cmd->err = I3C_ERROR_UNKNOWN;
}
+static void i3c_ccc_cmd_init(struct i3c_ccc_cmd *cmd, bool rnw, u8 id,
+ struct i3c_ccc_cmd_dest *dests,
+ unsigned int ndests)
+{
+ i3c_ccc_cmd_init_retries(cmd, rnw, id, dests, ndests,
+ rnw ? I3C_CCC_RETRIES : 0);
+}
+
+static int i3c_ccc_validate_payload_len(struct i3c_ccc_cmd *cmd)
+{
+ unsigned int i;
+
+ if (!cmd->rnw)
+ return 0;
+
+ for (i = 0; i < cmd->ndests; i++) {
+ struct i3c_ccc_cmd_payload *p = &cmd->dests[i].payload;
+ u16 min_len;
+
+ if (p->optional_bytes > p->len)
+ return -EINVAL;
+
+ if (p->actual_len > p->len)
+ return -EIO;
+
+ if (!p->len)
+ continue;
+
+ min_len = p->len - p->optional_bytes;
+ if (p->actual_len < min_len)
+ return -EIO;
+ }
+
+ return 0;
+}
+
/**
* i3c_master_send_ccc_cmd_locked() - send a CCC (Common Command Codes)
* @master: master used to send frames on the bus
@@ -987,6 +1026,9 @@ static void i3c_ccc_cmd_init(struct i3c_ccc_cmd *cmd, bool rnw, u8 id,
static int i3c_master_send_ccc_cmd_locked(struct i3c_master_controller *master,
struct i3c_ccc_cmd *cmd)
{
+ unsigned int attempt, max_attempts;
+ int ret;
+
if (!cmd || !master)
return -EINVAL;
@@ -1004,7 +1046,25 @@ static int i3c_master_send_ccc_cmd_locked(struct i3c_master_controller *master,
!master->ops->supports_ccc_cmd(master, cmd))
return -EOPNOTSUPP;
- return master->ops->send_ccc_cmd(master, cmd);
+ max_attempts = cmd->retries + 1;
+ ret = -EIO;
+ for (attempt = 0; attempt < max_attempts; attempt++) {
+ unsigned int i;
+
+ if (cmd->rnw)
+ for (i = 0; i < cmd->ndests; i++)
+ cmd->dests[i].payload.actual_len = 0;
+
+ cmd->err = I3C_ERROR_UNKNOWN;
+ ret = master->ops->send_ccc_cmd(master, cmd);
+ if (!ret && cmd->err == I3C_ERROR_UNKNOWN)
+ break;
+ }
+
+ if (!ret)
+ ret = i3c_ccc_validate_payload_len(cmd);
+
+ return ret;
}
static struct i2c_dev_desc *
@@ -1363,10 +1423,14 @@ static int i3c_master_getmrl_locked(struct i3c_master_controller *master,
return -ENOMEM;
/*
- * When the device does not have IBI payload GETMRL only returns 2
- * bytes of data.
+ * GETMRL returns 2 bytes (max read length) when the device does not
+ * advertise IBI payload, or 2 or 3 bytes when it does (the optional
+ * third byte is max IBI length). Use optional_bytes to allow either
+ * length when IBI payload is supported.
*/
- if (!(info->bcr & I3C_BCR_IBI_PAYLOAD))
+ if (info->bcr & I3C_BCR_IBI_PAYLOAD)
+ dest.payload.optional_bytes = 1;
+ else
dest.payload.len -= 1;
i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMRL, &dest, 1);
@@ -1374,7 +1438,7 @@ static int i3c_master_getmrl_locked(struct i3c_master_controller *master,
if (ret)
goto out;
- switch (dest.payload.len) {
+ switch (dest.payload.actual_len) {
case 3:
info->max_ibi_len = mrl->ibi_len;
fallthrough;
@@ -1409,7 +1473,7 @@ static int i3c_master_getmwl_locked(struct i3c_master_controller *master,
if (ret)
goto out;
- if (dest.payload.len != sizeof(*mwl)) {
+ if (dest.payload.actual_len != sizeof(*mwl)) {
ret = -EIO;
goto out;
}
@@ -1435,27 +1499,32 @@ static int i3c_master_getmxds_locked(struct i3c_master_controller *master,
if (!getmaxds)
return -ENOMEM;
+ dest.payload.optional_bytes = 3;
+
i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMXDS, &dest, 1);
ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
if (ret) {
/*
- * Retry when the device does not support max read turnaround
- * while expecting shorter length from this CCC command.
+ * optional_bytes = 3 accepts a 2-byte response on the first
+ * attempt, so this fallback runs only when the 5-byte request
+ * fails rather than returning a short read.
*/
dest.payload.len -= 3;
+ dest.payload.optional_bytes = 0;
+ i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMXDS, &dest, 1);
ret = i3c_master_send_ccc_cmd_locked(master, &cmd);
if (ret)
goto out;
}
- if (dest.payload.len != 2 && dest.payload.len != 5) {
+ if (dest.payload.actual_len != 2 && dest.payload.actual_len != 5) {
ret = -EIO;
goto out;
}
info->max_read_ds = getmaxds->maxrd;
info->max_write_ds = getmaxds->maxwr;
- if (dest.payload.len == 5)
+ if (dest.payload.actual_len == 5)
info->max_read_turnaround = getmaxds->maxrdturn[0] |
((u32)getmaxds->maxrdturn[1] << 8) |
((u32)getmaxds->maxrdturn[2] << 16);
@@ -1484,7 +1553,7 @@ static int i3c_master_gethdrcap_locked(struct i3c_master_controller *master,
if (ret)
goto out;
- if (dest.payload.len != 1) {
+ if (dest.payload.actual_len != 1) {
ret = -EIO;
goto out;
}
@@ -1934,7 +2003,9 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master)
if (ret) {
dev_err(&master->dev,
"Failed to add I3C device (err = %d)\n", ret);
+ desc->dev->desc = NULL;
put_device(&desc->dev->dev);
+ desc->dev = NULL;
}
}
}
diff --git a/drivers/i3c/master/Kconfig b/drivers/i3c/master/Kconfig
index 2609f2b18e0a..da96d2aaa399 100644
--- a/drivers/i3c/master/Kconfig
+++ b/drivers/i3c/master/Kconfig
@@ -86,3 +86,18 @@ config RENESAS_I3C
This driver can also be built as a module. If so, the module will be
called renesas-i3c.
+
+config AMD_AXI_I3C_MASTER
+ tristate "AMD AXI I3C Master driver"
+ depends on HAS_IOMEM
+ help
+ Support for the AMD AXI I3C master controller, a soft IP used on
+ AMD (Xilinx) FPGAs and adaptive SoCs with ARM or MicroBlaze
+ processors.
+
+ The controller currently supports Standard Data Rate (SDR) mode.
+ Features include Dynamic Address Assignment, private transfers,
+ and CCC transfers in both broadcast and direct modes.
+
+ This driver can also be built as a module. If so, the module
+ will be called amd-i3c-master.
diff --git a/drivers/i3c/master/Makefile b/drivers/i3c/master/Makefile
index 816a227b6f7a..8d82196dcf83 100644
--- a/drivers/i3c/master/Makefile
+++ b/drivers/i3c/master/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_AST2600_I3C_MASTER) += ast2600-i3c-master.o
obj-$(CONFIG_SVC_I3C_MASTER) += svc-i3c-master.o
obj-$(CONFIG_MIPI_I3C_HCI) += mipi-i3c-hci/
obj-$(CONFIG_RENESAS_I3C) += renesas-i3c.o
+obj-$(CONFIG_AMD_AXI_I3C_MASTER) += amd-i3c-master.o
diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
index 047081c9f064..b35386260350 100644
--- a/drivers/i3c/master/adi-i3c-master.c
+++ b/drivers/i3c/master/adi-i3c-master.c
@@ -246,6 +246,7 @@ static void adi_i3c_master_end_xfer_locked(struct adi_i3c_master *master,
if (cmd->cmd0 & REG_CMD_FIFO_0_RNW) {
rx_len = min_t(u32, REG_CMDR_FIFO_XFER_BYTES(cmdr), cmd->rx_len);
adi_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
+ cmd->rx_len = rx_len;
}
cmd->error = REG_CMDR_FIFO_ERROR(cmdr);
}
@@ -360,6 +361,8 @@ static int adi_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
adi_i3c_master_unqueue_xfer(master, xfer);
cmd->err = adi_i3c_cmd_get_err(&xfer->cmds[0]);
+ if (!xfer->ret && cmd->rnw)
+ cmd->dests[0].payload.actual_len = ccmd->rx_len;
return xfer->ret;
}
@@ -929,6 +932,7 @@ static const struct of_device_id adi_i3c_master_of_match[] = {
{ .compatible = "adi,i3c-master-v1" },
{}
};
+MODULE_DEVICE_TABLE(of, adi_i3c_master_of_match);
static int adi_i3c_master_probe(struct platform_device *pdev)
{
@@ -964,17 +968,9 @@ static int adi_i3c_master_probe(struct platform_device *pdev)
writel(0x00, master->regs + REG_ENABLE);
writel(0x00, master->regs + REG_IRQ_MASK);
- ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
- dev_name(&pdev->dev), master);
- if (ret)
- return ret;
-
platform_set_drvdata(pdev, master);
master->free_rr_slots = GENMASK(ADI_MAX_DEVS, 1);
-
- writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
-
spin_lock_init(&master->ibi.lock);
master->ibi.num_slots = 15;
master->ibi.slots = devm_kcalloc(&pdev->dev, master->ibi.num_slots,
@@ -986,6 +982,13 @@ static int adi_i3c_master_probe(struct platform_device *pdev)
spin_lock_init(&master->xferqueue.lock);
INIT_LIST_HEAD(&master->xferqueue.list);
+ ret = devm_request_irq(&pdev->dev, irq, adi_i3c_master_irq, 0,
+ dev_name(&pdev->dev), master);
+ if (ret)
+ return ret;
+
+ writel(REG_IRQ_PENDING_CMDR, master->regs + REG_IRQ_MASK);
+
return i3c_master_register(&master->base, &pdev->dev,
&adi_i3c_master_ops, false);
}
diff --git a/drivers/i3c/master/amd-i3c-master.c b/drivers/i3c/master/amd-i3c-master.c
new file mode 100644
index 000000000000..ef5ad5abb788
--- /dev/null
+++ b/drivers/i3c/master/amd-i3c-master.c
@@ -0,0 +1,1124 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * I3C master driver for the AMD I3C controller.
+ *
+ * Copyright (C) 2026, Advanced Micro Devices, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i3c/master.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/unaligned.h>
+
+#define XI3C_VERSION_OFFSET 0x00 /* Version Register */
+#define XI3C_RESET_OFFSET 0x04 /* Soft Reset Register */
+#define XI3C_CR_OFFSET 0x08 /* Control Register */
+#define XI3C_ADDRESS_OFFSET 0x0C /* Target Address Register */
+#define XI3C_SR_OFFSET 0x10 /* Status Register */
+#define XI3C_CMD_FIFO_OFFSET 0x20 /* I3C Command FIFO Register */
+#define XI3C_WR_FIFO_OFFSET 0x24 /* I3C Write Data FIFO Register */
+#define XI3C_RD_FIFO_OFFSET 0x28 /* I3C Read Data FIFO Register */
+#define XI3C_RESP_STATUS_FIFO_OFFSET 0x2C /* I3C Response status FIFO Register */
+#define XI3C_FIFO_LVL_STATUS_OFFSET 0x30 /* CMD slots free | WR-FIFO free (words) */
+#define XI3C_FIFO_LVL_STATUS_1_OFFSET 0x34 /* RESP fill | RD-FIFO fill level (words) */
+#define XI3C_SCL_HIGH_TIME_OFFSET 0x38 /* I3C SCL HIGH Register */
+#define XI3C_SCL_LOW_TIME_OFFSET 0x3C /* I3C SCL LOW Register */
+#define XI3C_SDA_HOLD_TIME_OFFSET 0x40 /* I3C SDA HOLD Register */
+#define XI3C_TSU_START_OFFSET 0x48 /* I3C START SETUP Register */
+#define XI3C_THD_START_OFFSET 0x4C /* I3C START HOLD Register */
+#define XI3C_TSU_STOP_OFFSET 0x50 /* I3C STOP Setup Register */
+#define XI3C_OD_SCL_HIGH_TIME_OFFSET 0x54 /* I3C OD SCL HIGH Register */
+#define XI3C_OD_SCL_LOW_TIME_OFFSET 0x58 /* I3C OD SCL LOW Register */
+#define XI3C_PID0_OFFSET 0x6C /* LSB 4 bytes of the PID */
+#define XI3C_PID1_BCR_DCR 0x70 /* MSB 2 bytes of the PID, BCR and DCR */
+
+#define XI3C_CR_EN_MASK BIT(0) /* Core Enable */
+#define XI3C_CR_RESUME_MASK BIT(2) /* Core Resume */
+#define XI3C_SR_RESP_NOT_EMPTY_MASK BIT(4) /* Resp Fifo not empty status mask */
+#define XI3C_RD_FIFO_NOT_EMPTY_MASK BIT(15) /* Read Fifo not empty status mask */
+
+#define XI3C_BCR_MASK GENMASK(23, 16)
+#define XI3C_DCR_MASK GENMASK(31, 24)
+#define XI3C_PID_MASK GENMASK_ULL(63, 16)
+#define XI3C_TIMING_MASK GENMASK(17, 0)
+#define XI3C_REV_NUM_MASK GENMASK(15, 8)
+#define XI3C_PID1_MASK GENMASK(15, 0)
+#define XI3C_FIFO_LEVEL_MASK GENMASK(15, 0)
+#define XI3C_RESP_CODE_MASK GENMASK(8, 5)
+
+/* Controller response codes; PG439 page 34, Table 46 */
+#define XI3C_RESP_CODE_SUCCESS 0 /* Transfer completed OK */
+#define XI3C_RESP_CODE_NO_TARGET 2 /* 7E NACK: no target on bus */
+#define XI3C_RESP_CODE_NACK 3 /* Target NACK / DAA end */
+#define XI3C_RESP_CODE_READ_EARLY_TERM 5 /* Target ended read early (T-bit), short read */
+
+/* xi3c_get_response() private return: read ended early with valid data; resume needed */
+#define XI3C_XFER_SHORT_READ 1
+
+#define XI3C_RESP_BYTES_MASK GENMASK(20, 9) /* NUM_BYTES processed */
+#define XI3C_ADDR_MASK GENMASK(6, 0)
+#define XI3C_FIFOS_RST_MASK GENMASK(4, 1)
+
+/* Command FIFO word layout (bit ranges encoded in the GENMASK/BIT args) */
+#define XI3C_CMD_TYPE GENMASK(3, 0) /* command type */
+#define XI3C_CMD_TERMINATE BIT(4) /* terminate (last cmd of xfer) */
+#define XI3C_CMD_ADDR GENMASK(15, 8) /* target address << 1 | RnW */
+#define XI3C_CMD_LEN GENMASK(27, 16) /* payload length in bytes */
+#define XI3C_CMD_TID GENMASK(31, 28) /* transfer ID */
+
+/* tLOW_OD open-drain SCL low; MIPI I3C v1.1.1 Table 74 min 200 ns, 500 ns chosen conservatively */
+#define XI3C_OD_TLOW_NS 500
+/* Open-drain SCL high (tHIGH) max; MIPI I3C v1.1.1 Table 74 (41 ns) */
+#define XI3C_OD_THIGH_NS 41
+/*
+ * tSU_STA/tHD_STA/tSU_STO min, 400 kHz/Fm; MIPI I3C v1.1.1 Table 73
+ * (mixed bus with legacy I2C device)
+ */
+#define XI3C_I2C_TCASMIN_NS 600
+/* tSU_STA/tHD_STA/tSU_STO min, 1 MHz/Fm+; MIPI I3C v1.1.1 Table 73 (pure I3C bus) */
+#define XI3C_TCASMIN_NS 260
+/* Max payload per transfer: 12-bit CMD length field (XI3C_CMD_LEN); PG439 page 32, Table 42 */
+#define XI3C_MAXDATA_LENGTH 4095
+/* Max enumerated devices; PG439 page 27, AXI_I3C_IBI_TARGET_ADDR register detail */
+#define XI3C_MAX_DEVS 128
+/* DAA target response = 48-bit PID + BCR + DCR = 8 bytes; PG439 page 28 */
+#define XI3C_DAA_SLAVEINFO_READ_BYTECOUNT 8
+
+/*
+ * Min SDA hold cycles, rev 0 IP. Revision-specific value, PG439 page 24,
+ * AXI_I3C_SDA_HOLD_TIME register detail
+ */
+#define XI3C_THOLD_MIN_REV0 5
+/*
+ * Min SDA hold cycles, rev >= 1 IP. Revision-specific value, PG439 page 24,
+ * AXI_I3C_SDA_HOLD_TIME register detail
+ */
+#define XI3C_THOLD_MIN_REV1 6
+/*
+ * SCL/SDA pre-bias to account for the HW pipeline. PG439 page 24,
+ * AXI_I3C_SDA_HOLD_TIME register detail
+ */
+#define XI3C_CYCLE_ADJUST 2
+/* Short settling delay so the FIFO reset assert/de-assert takes effect before the FIFOs are used */
+#define XI3C_FIFO_RESET_DELAY_US 10
+/*
+ * Poll/sleep slice for FIFO and response waits: small enough to stay
+ * responsive, avoids busy-waiting
+ */
+#define XI3C_POLL_INTERVAL_US 10
+
+#define XI3C_I2C_MODE 0
+#define XI3C_I2C_TID 0
+#define XI3C_SDR_MODE 1
+#define XI3C_SDR_TID 1
+
+#define XI3C_WORD_LEN 4
+
+/* Software guard: 500 ms (us, for readl_poll_timeout) to bail out if no response word arrives */
+#define XI3C_RESP_TIMEOUT_US 500000
+/* Software guard: 1 s (ms, for msecs_to_jiffies) to bail out if a transfer never completes */
+#define XI3C_XFER_TIMEOUT_MS 1000
+
+struct xi3c_cmd {
+ const void *tx_buf;
+ void *rx_buf;
+ u16 tx_len;
+ u16 rx_len;
+ u16 rx_actual;
+ u8 addr;
+ u8 type;
+ u8 tid;
+ bool rnw;
+ bool is_daa;
+ bool continued;
+ enum i3c_error_code err;
+};
+
+struct xi3c_xfer {
+ unsigned int ncmds;
+ unsigned int nissued;
+ struct xi3c_cmd cmds[] __counted_by(ncmds);
+};
+
+/**
+ * struct xi3c_master - I3C master controller state.
+ * @base: I3C master controller embedded by the framework.
+ * @dev: Pointer to the backing device structure.
+ * @membase: Memory base of the HW registers.
+ * @pclk: Input clock driving the controller.
+ * @lock: Serializes transfers and CCC submission.
+ * @daa: ENTDAA enumeration state.
+ * @daa.addrs: Dynamic addresses assigned in enumeration order.
+ * @daa.index: Number of responders enumerated so far.
+ */
+struct xi3c_master {
+ struct i3c_master_controller base;
+ struct device *dev;
+ void __iomem *membase;
+ struct clk *pclk;
+ struct mutex lock; /* serializes transfers and CCC submission */
+ struct {
+ u8 addrs[XI3C_MAX_DEVS];
+ u8 index;
+ } daa;
+};
+
+static inline struct xi3c_master *
+to_xi3c_master(struct i3c_master_controller *master)
+{
+ return container_of(master, struct xi3c_master, base);
+}
+
+static inline u8 xi3c_get_revision_number(struct xi3c_master *master)
+{
+ return FIELD_GET(XI3C_REV_NUM_MASK,
+ ioread32(master->membase + XI3C_VERSION_OFFSET));
+}
+
+static inline u16 xi3c_wr_fifo_level(struct xi3c_master *master)
+{
+ return ioread32(master->membase + XI3C_FIFO_LVL_STATUS_OFFSET) &
+ XI3C_FIFO_LEVEL_MASK;
+}
+
+static inline u16 xi3c_rd_fifo_level(struct xi3c_master *master)
+{
+ return ioread32(master->membase + XI3C_FIFO_LVL_STATUS_1_OFFSET) &
+ XI3C_FIFO_LEVEL_MASK;
+}
+
+static inline bool xi3c_is_resp_available(struct xi3c_master *master)
+{
+ return FIELD_GET(XI3C_SR_RESP_NOT_EMPTY_MASK,
+ ioread32(master->membase + XI3C_SR_OFFSET));
+}
+
+static int xi3c_get_response(struct xi3c_master *master, struct xi3c_cmd *cmd)
+{
+ u32 response_data;
+ u32 resp_reg;
+ u8 code;
+ int ret;
+
+ ret = readl_poll_timeout(master->membase + XI3C_SR_OFFSET,
+ resp_reg,
+ resp_reg & XI3C_SR_RESP_NOT_EMPTY_MASK,
+ XI3C_POLL_INTERVAL_US, XI3C_RESP_TIMEOUT_US);
+ if (ret) {
+ dev_err(master->dev, "XI3C response timeout\n");
+ return ret;
+ }
+
+ response_data = ioread32(master->membase + XI3C_RESP_STATUS_FIFO_OFFSET);
+ code = FIELD_GET(XI3C_RESP_CODE_MASK, response_data);
+
+ switch (code) {
+ case XI3C_RESP_CODE_SUCCESS:
+ cmd->err = I3C_ERROR_UNKNOWN;
+ cmd->rx_actual = FIELD_GET(XI3C_RESP_BYTES_MASK, response_data);
+ return 0;
+ case XI3C_RESP_CODE_READ_EARLY_TERM:
+ /* Short read: valid data, but controller parked in STOP and must be resumed */
+ cmd->err = I3C_ERROR_UNKNOWN;
+ cmd->rx_actual = FIELD_GET(XI3C_RESP_BYTES_MASK, response_data);
+ return XI3C_XFER_SHORT_READ;
+ case XI3C_RESP_CODE_NO_TARGET:
+ case XI3C_RESP_CODE_NACK:
+ cmd->err = I3C_ERROR_M2;
+ return cmd->is_daa ? -ENODEV : -EIO;
+ default:
+ cmd->err = I3C_ERROR_M0;
+ dev_err(master->dev, "XI3C transfer error, response code %u\n",
+ code);
+ return -EIO;
+ }
+}
+
+static inline void xi3c_writesl_be(void __iomem *addr, const void *buffer,
+ unsigned int count)
+{
+ const u32 *buf = buffer;
+
+ while (count--)
+ iowrite32be(get_unaligned(buf++), addr);
+}
+
+static inline void xi3c_readsl_be(const void __iomem *addr, void *buffer,
+ unsigned int count)
+{
+ u32 *buf = buffer;
+
+ while (count--)
+ put_unaligned(ioread32be(addr), buf++);
+}
+
+static inline void xi3c_writel_fifo(void __iomem *addr, const void *buf,
+ int nbytes)
+{
+ xi3c_writesl_be(addr, buf, nbytes / 4);
+ if (nbytes & 3) {
+ u32 tmp = 0;
+
+ memcpy(&tmp, (const u8 *)buf + (nbytes & ~3), nbytes & 3);
+ xi3c_writesl_be(addr, &tmp, 1);
+ }
+}
+
+static inline void xi3c_readl_fifo(const void __iomem *addr, void *buf,
+ int nbytes)
+{
+ xi3c_readsl_be(addr, buf, nbytes / 4);
+ if (nbytes & 3) {
+ u32 tmp;
+
+ xi3c_readsl_be(addr, &tmp, 1);
+ memcpy((u8 *)buf + (nbytes & ~3), &tmp, nbytes & 3);
+ }
+}
+
+static void xi3c_master_write_to_cmdfifo(struct xi3c_master *master,
+ struct xi3c_cmd *cmd, u16 len)
+{
+ u32 transfer_cmd;
+ u8 addr;
+
+ addr = ((cmd->addr & XI3C_ADDR_MASK) << 1) | (u8)cmd->rnw;
+
+ transfer_cmd = FIELD_PREP(XI3C_CMD_TYPE, cmd->type);
+ transfer_cmd |= FIELD_PREP(XI3C_CMD_TERMINATE, !cmd->continued);
+ transfer_cmd |= FIELD_PREP(XI3C_CMD_ADDR, addr);
+ transfer_cmd |= FIELD_PREP(XI3C_CMD_TID, cmd->tid);
+
+ /*
+ * For dynamic addressing, an additional 1-byte length must be added
+ * to the command FIFO to account for the address present in the TX FIFO
+ */
+ if (cmd->is_daa) {
+ xi3c_writel_fifo(master->membase + XI3C_WR_FIFO_OFFSET,
+ cmd->tx_buf, cmd->tx_len);
+
+ len++;
+ }
+
+ transfer_cmd |= FIELD_PREP(XI3C_CMD_LEN, len);
+ iowrite32(transfer_cmd, master->membase + XI3C_CMD_FIFO_OFFSET);
+}
+
+static inline void xi3c_master_enable(struct xi3c_master *master)
+{
+ iowrite32(ioread32(master->membase + XI3C_CR_OFFSET) | XI3C_CR_EN_MASK,
+ master->membase + XI3C_CR_OFFSET);
+}
+
+static inline void xi3c_master_disable(struct xi3c_master *master)
+{
+ iowrite32(ioread32(master->membase + XI3C_CR_OFFSET) & ~XI3C_CR_EN_MASK,
+ master->membase + XI3C_CR_OFFSET);
+}
+
+static inline void xi3c_master_resume(struct xi3c_master *master)
+{
+ iowrite32(ioread32(master->membase + XI3C_CR_OFFSET) |
+ XI3C_CR_RESUME_MASK, master->membase + XI3C_CR_OFFSET);
+}
+
+static void xi3c_master_reset_fifos(struct xi3c_master *master)
+{
+ u32 data;
+
+ /* Assert FIFO reset. */
+ data = ioread32(master->membase + XI3C_RESET_OFFSET);
+ data |= XI3C_FIFOS_RST_MASK;
+ iowrite32(data, master->membase + XI3C_RESET_OFFSET);
+ /* Read-back flushes the posted write before the settling delay below. */
+ ioread32(master->membase + XI3C_RESET_OFFSET);
+ fsleep(XI3C_FIFO_RESET_DELAY_US);
+
+ /* De-assert FIFO reset, then wait for the FIFOs to come back up. */
+ data &= ~XI3C_FIFOS_RST_MASK;
+ iowrite32(data, master->membase + XI3C_RESET_OFFSET);
+ ioread32(master->membase + XI3C_RESET_OFFSET);
+ fsleep(XI3C_FIFO_RESET_DELAY_US);
+}
+
+static inline void xi3c_master_init(struct xi3c_master *master)
+{
+ /* Reset fifos */
+ xi3c_master_reset_fifos(master);
+
+ /* Enable controller */
+ xi3c_master_enable(master);
+}
+
+static inline void xi3c_master_reinit(struct xi3c_master *master)
+{
+ /* Reset fifos */
+ xi3c_master_reset_fifos(master);
+
+ /* Resume controller */
+ xi3c_master_resume(master);
+}
+
+static struct xi3c_xfer *xi3c_master_alloc_xfer(unsigned int ncmds)
+{
+ struct xi3c_xfer *xfer;
+
+ xfer = kzalloc_flex(*xfer, cmds, ncmds, GFP_KERNEL);
+ if (!xfer)
+ return NULL;
+
+ xfer->ncmds = ncmds;
+
+ return xfer;
+}
+
+static void xi3c_master_rd_from_rx_fifo(struct xi3c_master *master,
+ struct xi3c_cmd *cmd)
+{
+ u16 rx_data_available;
+ u16 copy_len;
+ u16 len;
+
+ rx_data_available = xi3c_rd_fifo_level(master);
+ len = rx_data_available * XI3C_WORD_LEN;
+
+ if (!len)
+ return;
+
+ copy_len = min(len, cmd->rx_len);
+ xi3c_readl_fifo(master->membase + XI3C_RD_FIFO_OFFSET,
+ (u8 *)cmd->rx_buf, copy_len);
+
+ cmd->rx_buf = (u8 *)cmd->rx_buf + copy_len;
+ cmd->rx_len -= copy_len;
+}
+
+static int xi3c_master_read(struct xi3c_master *master, struct xi3c_cmd *cmd)
+{
+ unsigned long timeout;
+ u32 status_reg;
+ int ret;
+
+ if (cmd->rx_len > XI3C_MAXDATA_LENGTH)
+ return -EINVAL;
+ /*
+ * Zero-length probes (e.g. i2cdetect) legitimately pass a NULL
+ * buffer; only a non-zero length requires one.
+ */
+ if (cmd->rx_len && !cmd->rx_buf)
+ return -EINVAL;
+
+ /* Fill command fifo */
+ xi3c_master_write_to_cmdfifo(master, cmd, cmd->rx_len);
+
+ if (!cmd->rx_len)
+ return 0;
+
+ ret = readl_poll_timeout(master->membase + XI3C_SR_OFFSET,
+ status_reg,
+ status_reg & (XI3C_RD_FIFO_NOT_EMPTY_MASK |
+ XI3C_SR_RESP_NOT_EMPTY_MASK),
+ XI3C_POLL_INTERVAL_US, XI3C_RESP_TIMEOUT_US);
+ if (ret) {
+ dev_err(master->dev, "XI3C read timeout\n");
+ return ret;
+ }
+
+ if (!(status_reg & XI3C_RD_FIFO_NOT_EMPTY_MASK))
+ return 0;
+
+ timeout = jiffies + msecs_to_jiffies(XI3C_XFER_TIMEOUT_MS);
+
+ /* Read data from rx fifo */
+ while (cmd->rx_len > 0 && !xi3c_is_resp_available(master)) {
+ if (time_after(jiffies, timeout)) {
+ dev_err(master->dev, "XI3C read timeout\n");
+ return -EIO;
+ }
+ xi3c_master_rd_from_rx_fifo(master, cmd);
+ usleep_range(XI3C_POLL_INTERVAL_US, 2 * XI3C_POLL_INTERVAL_US);
+ }
+
+ /* Read remaining data */
+ xi3c_master_rd_from_rx_fifo(master, cmd);
+
+ return 0;
+}
+
+static void xi3c_master_wr_to_tx_fifo(struct xi3c_master *master,
+ struct xi3c_cmd *cmd)
+{
+ u16 wrfifo_space;
+ u16 len;
+
+ wrfifo_space = xi3c_wr_fifo_level(master);
+ if (cmd->tx_len > wrfifo_space * XI3C_WORD_LEN)
+ len = wrfifo_space * XI3C_WORD_LEN;
+ else
+ len = cmd->tx_len;
+
+ if (len) {
+ xi3c_writel_fifo(master->membase + XI3C_WR_FIFO_OFFSET, cmd->tx_buf,
+ len);
+
+ cmd->tx_buf = (const u8 *)cmd->tx_buf + len;
+ cmd->tx_len -= len;
+ }
+}
+
+static int xi3c_master_write(struct xi3c_master *master, struct xi3c_cmd *cmd)
+{
+ unsigned long timeout;
+ u16 cmd_len;
+
+ if (cmd->tx_len > XI3C_MAXDATA_LENGTH)
+ return -EINVAL;
+ /*
+ * Zero-length probes (e.g. i2cdetect) legitimately pass a NULL
+ * buffer; only a non-zero length requires one.
+ */
+ if (cmd->tx_len && !cmd->tx_buf)
+ return -EINVAL;
+
+ cmd_len = cmd->tx_len;
+
+ /* Fill Tx fifo */
+ xi3c_master_wr_to_tx_fifo(master, cmd);
+
+ /* Write to command fifo */
+ xi3c_master_write_to_cmdfifo(master, cmd, cmd_len);
+
+ timeout = jiffies + msecs_to_jiffies(XI3C_XFER_TIMEOUT_MS);
+ /* Fill if any remaining data to tx fifo */
+ while (cmd->tx_len > 0 && !xi3c_is_resp_available(master)) {
+ if (time_after(jiffies, timeout)) {
+ dev_err(master->dev, "XI3C write timeout\n");
+ return -EIO;
+ }
+
+ xi3c_master_wr_to_tx_fifo(master, cmd);
+ usleep_range(XI3C_POLL_INTERVAL_US, 2 * XI3C_POLL_INTERVAL_US);
+ }
+
+ return 0;
+}
+
+static int xi3c_master_xfer(struct xi3c_master *master, struct xi3c_cmd *cmd)
+{
+ int ret;
+
+ if (cmd->rnw)
+ ret = xi3c_master_read(master, cmd);
+ else
+ ret = xi3c_master_write(master, cmd);
+
+ if (ret)
+ goto err_xfer_out;
+
+ ret = xi3c_get_response(master, cmd);
+ if (ret < 0)
+ goto err_xfer_out;
+
+ /* Short read leaves the controller parked in STOP; resume it for the next command */
+ if (ret == XI3C_XFER_SHORT_READ)
+ xi3c_master_resume(master);
+
+ return 0;
+
+err_xfer_out:
+ xi3c_master_reinit(master);
+ return ret;
+}
+
+static int xi3c_master_common_xfer(struct xi3c_master *master,
+ struct xi3c_xfer *xfer)
+{
+ unsigned int i;
+ int ret;
+
+ guard(mutex)(&master->lock);
+
+ for (i = 0; i < xfer->ncmds; i++) {
+ ret = xi3c_master_xfer(master, &xfer->cmds[i]);
+ if (ret) {
+ /* Count commands sent on the bus; the rest never ran */
+ xfer->nissued = i + 1;
+ return ret;
+ }
+ }
+
+ xfer->nissued = xfer->ncmds;
+
+ return 0;
+}
+
+static int xi3c_master_do_daa(struct i3c_master_controller *m)
+{
+ u8 (*pid_bufs)[XI3C_DAA_SLAVEINFO_READ_BYTECOUNT];
+ struct xi3c_master *master = to_xi3c_master(m);
+ struct xi3c_cmd *daa_cmd;
+ struct xi3c_xfer *xfer;
+ int addr, ret, i;
+ u8 last_addr = 0;
+ u8 *pid_buf;
+ u8 ccc_id;
+
+ xfer = xi3c_master_alloc_xfer(1);
+ if (!xfer)
+ return -ENOMEM;
+
+ pid_bufs = kcalloc(XI3C_MAX_DEVS, XI3C_DAA_SLAVEINFO_READ_BYTECOUNT,
+ GFP_KERNEL);
+ if (!pid_bufs) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* Fill ENTDAA CCC */
+ ccc_id = I3C_CCC_ENTDAA;
+ daa_cmd = &xfer->cmds[0];
+ daa_cmd->addr = I3C_BROADCAST_ADDR;
+ daa_cmd->rnw = false;
+ daa_cmd->tx_buf = &ccc_id;
+ daa_cmd->tx_len = 1;
+ daa_cmd->type = XI3C_SDR_MODE;
+ daa_cmd->tid = XI3C_SDR_TID;
+ daa_cmd->continued = true;
+
+ ret = xi3c_master_common_xfer(master, xfer);
+ if (ret) {
+ /* NACK on ENTDAA broadcast means no devices to enumerate */
+ if (daa_cmd->err == I3C_ERROR_M2)
+ ret = 0;
+ goto err_daa;
+ }
+
+ master->daa.index = 0;
+
+ while (true) {
+ struct xi3c_cmd *cmd = &xfer->cmds[0];
+ u8 daa_byte;
+
+ /* Out of device slots; stop and keep what was enumerated */
+ if (master->daa.index >= XI3C_MAX_DEVS) {
+ dev_warn(master->dev,
+ "DAA: reached %d devices, stopping enumeration\n",
+ XI3C_MAX_DEVS);
+ xi3c_master_reinit(master);
+ break;
+ }
+
+ addr = i3c_master_get_free_addr(m, last_addr + 1);
+ if (addr < 0) {
+ dev_warn(master->dev,
+ "DAA: no free dynamic address, stopping enumeration\n");
+ xi3c_master_reinit(master);
+ break;
+ }
+
+ pid_buf = pid_bufs[master->daa.index];
+
+ daa_byte = (addr << 1) | (parity8(addr) ^ 1);
+
+ cmd->tx_buf = &daa_byte;
+ cmd->tx_len = 1;
+ cmd->addr = I3C_BROADCAST_ADDR;
+ cmd->rnw = true;
+ cmd->rx_buf = pid_buf;
+ cmd->rx_len = XI3C_DAA_SLAVEINFO_READ_BYTECOUNT;
+ cmd->is_daa = true;
+ cmd->type = XI3C_SDR_MODE;
+ cmd->tid = XI3C_SDR_TID;
+ cmd->continued = true;
+
+ ret = xi3c_master_common_xfer(master, xfer);
+
+ /* -ENODEV: no more responders, enumeration complete */
+ if (ret == -ENODEV) {
+ ret = 0;
+ break;
+ }
+ if (ret)
+ goto err_daa;
+
+ master->daa.addrs[master->daa.index] = addr;
+ last_addr = addr;
+ master->daa.index++;
+ }
+
+ for (i = 0; i < master->daa.index; i++) {
+ u64 pid;
+
+ /* Ignore per-device add errors so one failure doesn't abort the rest */
+ i3c_master_add_i3c_dev_locked(m, master->daa.addrs[i]);
+
+ pid = FIELD_GET(XI3C_PID_MASK,
+ get_unaligned_be64(pid_bufs[i]));
+ dev_dbg(master->dev, "Client %d: PID: 0x%llx\n", i, pid);
+ }
+
+ ret = 0;
+ goto out;
+
+err_daa:
+ xi3c_master_reinit(master);
+out:
+ kfree(pid_bufs);
+ kfree(xfer);
+ return ret;
+}
+
+static bool
+xi3c_master_supports_ccc_cmd(struct i3c_master_controller *master,
+ const struct i3c_ccc_cmd *cmd)
+{
+ if (cmd->ndests > 1)
+ return false;
+
+ switch (cmd->id) {
+ case I3C_CCC_ENEC(true):
+ case I3C_CCC_ENEC(false):
+ case I3C_CCC_DISEC(true):
+ case I3C_CCC_DISEC(false):
+ case I3C_CCC_ENTAS(0, true):
+ case I3C_CCC_ENTAS(0, false):
+ case I3C_CCC_RSTDAA(true):
+ case I3C_CCC_RSTDAA(false):
+ case I3C_CCC_ENTDAA:
+ case I3C_CCC_SETMWL(true):
+ case I3C_CCC_SETMWL(false):
+ case I3C_CCC_SETMRL(true):
+ case I3C_CCC_SETMRL(false):
+ case I3C_CCC_SETDASA:
+ case I3C_CCC_SETNEWDA:
+ case I3C_CCC_GETMWL:
+ case I3C_CCC_GETMRL:
+ case I3C_CCC_GETPID:
+ case I3C_CCC_GETBCR:
+ case I3C_CCC_GETDCR:
+ case I3C_CCC_GETSTATUS:
+ case I3C_CCC_GETMXDS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int xi3c_master_send_bdcast_ccc_cmd(struct xi3c_master *master,
+ struct i3c_ccc_cmd *ccc)
+{
+ struct xi3c_xfer *xfer __free(kfree) = NULL;
+ u8 *buf __free(kfree) = NULL;
+ struct xi3c_cmd *cmd;
+ u16 xfer_len;
+ int ret;
+
+ if (ccc->dests[0].payload.len >= XI3C_MAXDATA_LENGTH)
+ return -EINVAL;
+
+ xfer_len = ccc->dests[0].payload.len + 1;
+
+ xfer = xi3c_master_alloc_xfer(1);
+ if (!xfer)
+ return -ENOMEM;
+
+ buf = kmalloc_objs(*buf, xfer_len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = ccc->id;
+ memcpy(&buf[1], ccc->dests[0].payload.data, ccc->dests[0].payload.len);
+
+ cmd = &xfer->cmds[0];
+ cmd->addr = ccc->dests[0].addr;
+ cmd->rnw = ccc->rnw;
+ cmd->tx_buf = buf;
+ cmd->tx_len = xfer_len;
+ cmd->type = XI3C_SDR_MODE;
+ cmd->tid = XI3C_SDR_TID;
+ cmd->continued = false;
+
+ ret = xi3c_master_common_xfer(master, xfer);
+ ccc->err = cmd->err;
+
+ return ret;
+}
+
+static int xi3c_master_send_direct_ccc_cmd(struct xi3c_master *master,
+ struct i3c_ccc_cmd *ccc)
+{
+ struct xi3c_xfer *xfer __free(kfree) = NULL;
+ struct xi3c_cmd *cmd;
+ int ret;
+
+ if (ccc->dests[0].payload.len > XI3C_MAXDATA_LENGTH)
+ return -EINVAL;
+
+ xfer = xi3c_master_alloc_xfer(2);
+ if (!xfer)
+ return -ENOMEM;
+
+ /* Broadcasted message */
+ cmd = &xfer->cmds[0];
+ cmd->addr = I3C_BROADCAST_ADDR;
+ cmd->rnw = false;
+ cmd->tx_buf = &ccc->id;
+ cmd->tx_len = 1;
+ cmd->type = XI3C_SDR_MODE;
+ cmd->tid = XI3C_SDR_TID;
+ cmd->continued = true;
+
+ /* Directed message */
+ cmd = &xfer->cmds[1];
+ cmd->addr = ccc->dests[0].addr;
+ cmd->rnw = ccc->rnw;
+ if (cmd->rnw) {
+ cmd->rx_buf = ccc->dests[0].payload.data;
+ cmd->rx_len = ccc->dests[0].payload.len;
+ } else {
+ cmd->tx_buf = ccc->dests[0].payload.data;
+ cmd->tx_len = ccc->dests[0].payload.len;
+ }
+ cmd->type = XI3C_SDR_MODE;
+ cmd->tid = XI3C_SDR_TID;
+ cmd->continued = false;
+
+ ret = xi3c_master_common_xfer(master, xfer);
+
+ /*
+ * Report broadcast error if any, else the directed one, so either
+ * NACK reaches the caller
+ */
+ ccc->err = xfer->cmds[0].err ? xfer->cmds[0].err : xfer->cmds[1].err;
+
+ /* Report actual byte count so the core sees the right length on short reads */
+ if (!ret && ccc->rnw)
+ ccc->dests[0].payload.len = min(xfer->cmds[1].rx_actual,
+ ccc->dests[0].payload.len);
+
+ return ret;
+}
+
+static int xi3c_master_send_ccc_cmd(struct i3c_master_controller *m,
+ struct i3c_ccc_cmd *cmd)
+{
+ struct xi3c_master *master = to_xi3c_master(m);
+
+ if (cmd->id & I3C_CCC_DIRECT)
+ return xi3c_master_send_direct_ccc_cmd(master, cmd);
+
+ return xi3c_master_send_bdcast_ccc_cmd(master, cmd);
+}
+
+static int xi3c_master_i3c_xfers(struct i3c_dev_desc *dev,
+ struct i3c_xfer *xfers,
+ int nxfers, enum i3c_xfer_mode mode)
+{
+ struct i3c_master_controller *m = i3c_dev_get_master(dev);
+ struct xi3c_master *master = to_xi3c_master(m);
+ struct xi3c_xfer *xfer __free(kfree) = NULL;
+ int i, ret;
+
+ if (!nxfers)
+ return 0;
+
+ if (mode != I3C_SDR)
+ return -EOPNOTSUPP;
+
+ for (i = 0; i < nxfers; i++)
+ if (xfers[i].len > XI3C_MAXDATA_LENGTH)
+ return -EINVAL;
+
+ xfer = xi3c_master_alloc_xfer(nxfers);
+ if (!xfer)
+ return -ENOMEM;
+
+ for (i = 0; i < nxfers; i++) {
+ struct xi3c_cmd *cmd = &xfer->cmds[i];
+
+ cmd->addr = dev->info.dyn_addr;
+ cmd->rnw = xfers[i].rnw;
+
+ if (cmd->rnw) {
+ cmd->rx_buf = xfers[i].data.in;
+ cmd->rx_len = xfers[i].len;
+ } else {
+ cmd->tx_buf = xfers[i].data.out;
+ cmd->tx_len = xfers[i].len;
+ }
+
+ cmd->type = XI3C_SDR_MODE;
+ cmd->tid = XI3C_SDR_TID;
+ cmd->continued = (i + 1) < nxfers;
+ }
+
+ ret = xi3c_master_common_xfer(master, xfer);
+
+ for (i = 0; i < xfer->nissued; i++) {
+ xfers[i].err = xfer->cmds[i].err;
+ if (xfers[i].rnw)
+ xfers[i].actual_len = min(xfer->cmds[i].rx_actual,
+ xfers[i].len);
+ }
+
+ return ret;
+}
+
+static int xi3c_master_i2c_xfers(struct i2c_dev_desc *dev,
+ struct i2c_msg *xfers,
+ int nxfers)
+{
+ struct i3c_master_controller *m = i2c_dev_get_master(dev);
+ struct xi3c_master *master = to_xi3c_master(m);
+ struct xi3c_xfer *xfer __free(kfree) = NULL;
+ int i;
+
+ if (!nxfers)
+ return 0;
+
+ for (i = 0; i < nxfers; i++)
+ if (xfers[i].len > XI3C_MAXDATA_LENGTH)
+ return -EINVAL;
+
+ xfer = xi3c_master_alloc_xfer(nxfers);
+ if (!xfer)
+ return -ENOMEM;
+
+ for (i = 0; i < nxfers; i++) {
+ struct xi3c_cmd *cmd = &xfer->cmds[i];
+
+ cmd->addr = xfers[i].addr & XI3C_ADDR_MASK;
+ cmd->rnw = !!(xfers[i].flags & I2C_M_RD);
+
+ if (cmd->rnw) {
+ cmd->rx_buf = xfers[i].buf;
+ cmd->rx_len = xfers[i].len;
+ } else {
+ cmd->tx_buf = xfers[i].buf;
+ cmd->tx_len = xfers[i].len;
+ }
+
+ cmd->type = XI3C_I2C_MODE;
+ cmd->tid = XI3C_I2C_TID;
+ cmd->continued = (i + 1) < nxfers;
+ }
+
+ return xi3c_master_common_xfer(master, xfer);
+}
+
+static int xi3c_clk_cfg(struct xi3c_master *master, unsigned long sclhz, u8 mode)
+{
+ unsigned long core_rate, core_periodns;
+ u32 tcasmin, tsustart, tsustop, thdstart;
+ u32 thigh, tlow, thold;
+ u32 odthigh, odtlow;
+
+ core_rate = clk_get_rate(master->pclk);
+ if (!core_rate)
+ return -EINVAL;
+
+ if (!sclhz)
+ return -EINVAL;
+
+ core_periodns = DIV_ROUND_UP(NSEC_PER_SEC, core_rate);
+
+ thigh = DIV_ROUND_UP(core_rate, sclhz) >> 1;
+ tlow = thigh;
+
+ /* Reject rates whose timing exceeds the 18-bit registers (would wrap) */
+ if (thigh <= XI3C_CYCLE_ADJUST ||
+ (thigh - XI3C_CYCLE_ADJUST) > XI3C_TIMING_MASK)
+ return -EINVAL;
+
+ /* Hold time : 40% of tlow time */
+ thold = (tlow * 4) / 10;
+
+ if (xi3c_get_revision_number(master) == 0)
+ thold = max_t(u32, thold, XI3C_THOLD_MIN_REV0);
+ else
+ thold = max_t(u32, thold, XI3C_THOLD_MIN_REV1);
+
+ iowrite32((thigh - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_SCL_HIGH_TIME_OFFSET);
+ iowrite32((tlow - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_SCL_LOW_TIME_OFFSET);
+ iowrite32((thold - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_SDA_HOLD_TIME_OFFSET);
+
+ if (mode == XI3C_I2C_MODE) {
+ iowrite32((thigh - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_OD_SCL_HIGH_TIME_OFFSET);
+ iowrite32((tlow - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_OD_SCL_LOW_TIME_OFFSET);
+
+ tcasmin = DIV_ROUND_UP(XI3C_I2C_TCASMIN_NS, core_periodns);
+ } else {
+ odtlow = DIV_ROUND_UP(XI3C_OD_TLOW_NS, core_periodns);
+ odthigh = DIV_ROUND_UP(XI3C_OD_THIGH_NS, core_periodns);
+
+ odtlow = max(tlow, odtlow);
+ odthigh = min(thigh, odthigh);
+
+ if (odthigh <= XI3C_CYCLE_ADJUST)
+ return -EINVAL;
+
+ iowrite32((odthigh - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_OD_SCL_HIGH_TIME_OFFSET);
+ iowrite32((odtlow - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_OD_SCL_LOW_TIME_OFFSET);
+
+ tcasmin = DIV_ROUND_UP(XI3C_TCASMIN_NS, core_periodns);
+ }
+
+ thdstart = max(thigh, tcasmin);
+ tsustart = max(tlow, tcasmin);
+ tsustop = max(tlow, tcasmin);
+
+ iowrite32((tsustart - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_TSU_START_OFFSET);
+ iowrite32((thdstart - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_THD_START_OFFSET);
+ iowrite32((tsustop - XI3C_CYCLE_ADJUST) & XI3C_TIMING_MASK,
+ master->membase + XI3C_TSU_STOP_OFFSET);
+
+ return 0;
+}
+
+static int xi3c_master_bus_init(struct i3c_master_controller *m)
+{
+ struct xi3c_master *master = to_xi3c_master(m);
+ struct i3c_bus *bus = i3c_master_get_bus(m);
+ struct i3c_device_info info = {};
+ unsigned long sclhz;
+ u32 pid1_bcr_dcr;
+ u8 mode;
+ int ret;
+
+ switch (bus->mode) {
+ case I3C_BUS_MODE_MIXED_FAST:
+ case I3C_BUS_MODE_MIXED_LIMITED:
+ case I3C_BUS_MODE_MIXED_SLOW:
+ mode = XI3C_I2C_MODE;
+ sclhz = bus->scl_rate.i2c;
+ break;
+ case I3C_BUS_MODE_PURE:
+ mode = XI3C_SDR_MODE;
+ sclhz = bus->scl_rate.i3c;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = xi3c_clk_cfg(master, sclhz, mode);
+ if (ret)
+ return ret;
+
+ xi3c_master_init(master);
+
+ /* Get an address for the master. */
+ ret = i3c_master_get_free_addr(m, 0);
+ if (ret < 0)
+ return ret;
+
+ info.dyn_addr = ret;
+
+ /* Write the dynamic address value to the address register. */
+ iowrite32(info.dyn_addr, master->membase + XI3C_ADDRESS_OFFSET);
+
+ /* Read PID, BCR and DCR values, and assign to i3c device info. */
+ pid1_bcr_dcr = ioread32(master->membase + XI3C_PID1_BCR_DCR);
+ info.pid = ((u64)FIELD_GET(XI3C_PID1_MASK, pid1_bcr_dcr) << 32) |
+ ioread32(master->membase + XI3C_PID0_OFFSET);
+ info.bcr = FIELD_GET(XI3C_BCR_MASK, pid1_bcr_dcr);
+ info.dcr = FIELD_GET(XI3C_DCR_MASK, pid1_bcr_dcr);
+
+ return i3c_master_set_info(&master->base, &info);
+}
+
+static void xi3c_master_bus_cleanup(struct i3c_master_controller *m)
+{
+ struct xi3c_master *master = to_xi3c_master(m);
+
+ xi3c_master_disable(master);
+}
+
+static const struct i3c_master_controller_ops xi3c_master_ops = {
+ .bus_init = xi3c_master_bus_init,
+ .bus_cleanup = xi3c_master_bus_cleanup,
+ .do_daa = xi3c_master_do_daa,
+ .supports_ccc_cmd = xi3c_master_supports_ccc_cmd,
+ .send_ccc_cmd = xi3c_master_send_ccc_cmd,
+ .i3c_xfers = xi3c_master_i3c_xfers,
+ .i2c_xfers = xi3c_master_i2c_xfers,
+};
+
+static int xi3c_master_probe(struct platform_device *pdev)
+{
+ struct xi3c_master *master;
+ int ret;
+
+ master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
+ if (!master)
+ return -ENOMEM;
+
+ master->dev = &pdev->dev;
+
+ master->membase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(master->membase))
+ return dev_err_probe(master->dev, PTR_ERR(master->membase),
+ "Failed to map registers\n");
+
+ master->pclk = devm_clk_get_enabled(master->dev, NULL);
+ if (IS_ERR(master->pclk))
+ return dev_err_probe(master->dev, PTR_ERR(master->pclk),
+ "Failed to get and enable clock\n");
+
+ ret = devm_mutex_init(master->dev, &master->lock);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, master);
+
+ return i3c_master_register(&master->base, master->dev,
+ &xi3c_master_ops, false);
+}
+
+static void xi3c_master_remove(struct platform_device *pdev)
+{
+ struct xi3c_master *master = platform_get_drvdata(pdev);
+
+ i3c_master_unregister(&master->base);
+}
+
+static const struct of_device_id xi3c_master_of_ids[] = {
+ { .compatible = "xlnx,axi-i3c-1.0" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, xi3c_master_of_ids);
+
+static struct platform_driver xi3c_master_driver = {
+ .probe = xi3c_master_probe,
+ .remove = xi3c_master_remove,
+ .driver = {
+ .name = "axi-i3c-master",
+ .of_match_table = xi3c_master_of_ids,
+ },
+};
+module_platform_driver(xi3c_master_driver);
+
+MODULE_AUTHOR("Manikanta Guntupalli <manikanta.guntupalli@amd.com>");
+MODULE_DESCRIPTION("AMD AXI I3C master driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 2f8c0c4683e0..84ffcb189338 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -493,6 +493,7 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
break;
case RESPONSE_ERROR_PARITY:
case RESPONSE_ERROR_IBA_NACK:
+ case RESPONSE_ERROR_ADDRESS_NACK:
case RESPONSE_ERROR_TRANSF_ABORT:
case RESPONSE_ERROR_CRC:
case RESPONSE_ERROR_FRAME:
@@ -502,7 +503,6 @@ static void dw_i3c_master_end_xfer_locked(struct dw_i3c_master *master, u32 isr)
ret = -ENOSPC;
break;
case RESPONSE_ERROR_I2C_W_NACK_ERR:
- case RESPONSE_ERROR_ADDRESS_NACK:
default:
ret = -EINVAL;
break;
@@ -708,12 +708,29 @@ static void dw_i3c_master_bus_cleanup(struct i3c_master_controller *m)
dw_i3c_master_disable(master);
}
+static enum i3c_error_code dw_i3c_ccc_map_err(u8 dw_err)
+{
+ switch (dw_err) {
+ case RESPONSE_ERROR_IBA_NACK:
+ return I3C_ERROR_M2;
+ case RESPONSE_ERROR_CRC:
+ case RESPONSE_ERROR_PARITY:
+ case RESPONSE_ERROR_FRAME:
+ case RESPONSE_ERROR_TRANSF_ABORT:
+ return I3C_ERROR_M0;
+ default:
+ return I3C_ERROR_UNKNOWN;
+ }
+}
+
static int dw_i3c_ccc_set(struct dw_i3c_master *master,
struct i3c_ccc_cmd *ccc)
{
struct dw_i3c_cmd *cmd;
int ret, pos = 0;
+ ccc->err = I3C_ERROR_UNKNOWN;
+
if (ccc->id & I3C_CCC_DIRECT) {
pos = dw_i3c_master_get_addr_pos(master, ccc->dests[0].addr);
if (pos < 0)
@@ -742,8 +759,8 @@ static int dw_i3c_ccc_set(struct dw_i3c_master *master,
dw_i3c_master_dequeue_xfer(master, xfer);
ret = xfer->ret;
- if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK)
- ccc->err = I3C_ERROR_M2;
+ cmd = &xfer->cmds[0];
+ ccc->err = dw_i3c_ccc_map_err(cmd->error);
return ret;
}
@@ -753,6 +770,8 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc)
struct dw_i3c_cmd *cmd;
int ret, pos;
+ ccc->err = I3C_ERROR_UNKNOWN;
+
pos = dw_i3c_master_get_addr_pos(master, ccc->dests[0].addr);
if (pos < 0)
return pos;
@@ -780,8 +799,10 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc)
dw_i3c_master_dequeue_xfer(master, xfer);
ret = xfer->ret;
- if (xfer->cmds[0].error == RESPONSE_ERROR_IBA_NACK)
- ccc->err = I3C_ERROR_M2;
+ cmd = &xfer->cmds[0];
+ ccc->err = dw_i3c_ccc_map_err(cmd->error);
+ if (!ret)
+ ccc->dests[0].payload.actual_len = cmd->rx_len;
return ret;
}
@@ -888,7 +909,15 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
if (!wait_for_completion_timeout(&xfer->comp, XFER_TIMEOUT))
dw_i3c_master_dequeue_xfer(master, xfer);
- newdevs = GENMASK(master->maxdevs - cmd->rx_len - 1, 0);
+ /*
+ * cmd->rx_len holds the number of addresses ENTDAA left unassigned.
+ * On an empty bus rx_len == maxdevs, so avoid GENMASK(-1, 0).
+ */
+ if (cmd->rx_len >= master->maxdevs)
+ newdevs = 0;
+ else
+ newdevs = GENMASK(master->maxdevs - cmd->rx_len - 1, 0);
+
newdevs &= ~olddevs;
for (pos = 0; pos < master->maxdevs; pos++) {
diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
index 6d221596ea35..2d98c1ce9b12 100644
--- a/drivers/i3c/master/i3c-master-cdns.c
+++ b/drivers/i3c/master/i3c-master-cdns.c
@@ -573,6 +573,7 @@ static void cdns_i3c_master_end_xfer_locked(struct cdns_i3c_master *master,
cmd = &xfer->cmds[CMDR_CMDID(cmdr)];
rx_len = min_t(u32, CMDR_XFER_BYTES(cmdr), cmd->rx_len);
cdns_i3c_master_rd_from_rx_fifo(master, cmd->rx_buf, rx_len);
+ cmd->rx_len = rx_len;
cmd->error = CMDR_ERROR(cmdr);
}
@@ -714,6 +715,8 @@ static int cdns_i3c_master_send_ccc_cmd(struct i3c_master_controller *m,
ret = xfer->ret;
cmd->err = cdns_i3c_cmd_get_err(&xfer->cmds[0]);
+ if (!ret && cmd->rnw)
+ cmd->dests[0].payload.actual_len = ccmd->rx_len;
cdns_i3c_master_free_xfer(xfer);
return ret;
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index e80aa1f5722e..cfe9b5390b56 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -375,7 +375,7 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
goto out;
for (i = prefixed; i < nxfers; i++) {
if (ccc->rnw)
- ccc->dests[i - prefixed].payload.len =
+ ccc->dests[i - prefixed].payload.actual_len =
RESP_DATA_LENGTH(xfer[i].response);
switch (RESP_STATUS(xfer[i].response)) {
case RESP_SUCCESS:
@@ -392,7 +392,8 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
if (ccc->rnw)
dev_dbg(&hci->master.dev, "got: %*ph",
- ccc->dests[0].payload.len, ccc->dests[0].payload.data);
+ ccc->dests[0].payload.actual_len,
+ ccc->dests[0].payload.data);
out:
hci_free_xfer(xfer, nxfers);
diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c
index f39c449922ca..2b501f31e874 100644
--- a/drivers/i3c/master/renesas-i3c.c
+++ b/drivers/i3c/master/renesas-i3c.c
@@ -22,6 +22,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include "../internals.h"
@@ -109,7 +110,7 @@
#define NCMDQP_DATA_LENGTH(x) FIELD_PREP(GENMASK(31, 16), x)
#define NRSPQP 0x154 /* Normal Respone Queue */
-#define NRSPQP_NO_ERROR 0
+#define NRSPQP_NO_ERROR 0
#define NRSPQP_ERROR_CRC 1
#define NRSPQP_ERROR_PARITY 2
#define NRSPQP_ERROR_FRAME 3
@@ -199,8 +200,6 @@
#define RENESAS_I3C_MAX_DEVS 8
#define I2C_INIT_MSG -1
-#define RENESAS_I3C_TCLK_IDX 1
-
enum i3c_internal_state {
I3C_INTERNAL_STATE_DISABLED,
I3C_INTERNAL_STATE_CONTROLLER_IDLE,
@@ -221,19 +220,19 @@ enum renesas_i3c_event {
};
struct renesas_i3c_cmd {
+ const void *tx_buf;
+ void *rx_buf;
+ /* i2c xfer */
+ u8 *i2c_buf;
+ const struct i2c_msg *msg;
+ int i2c_bytes_left;
+ int i2c_is_last;
u32 cmd0;
u32 len;
- const void *tx_buf;
u32 tx_count;
- void *rx_buf;
u32 rx_count;
u32 err;
u8 rnw;
- /* i2c xfer */
- int i2c_bytes_left;
- int i2c_is_last;
- u8 *i2c_buf;
- const struct i2c_msg *msg;
};
struct renesas_i3c_xfer {
@@ -253,22 +252,23 @@ struct renesas_i3c_xferqueue {
};
struct renesas_i3c {
+ void __iomem *regs;
+ struct clk *tclk;
+ struct reset_control *presetn;
+ struct reset_control *tresetn;
+ struct device *dev;
+ struct renesas_i3c_xferqueue xferqueue;
struct i3c_master_controller base;
+ u8 addrs[RENESAS_I3C_MAX_DEVS];
+ unsigned long rate;
enum i3c_internal_state internal_state;
- u16 maxdevs;
+ bool resuming;
u32 free_pos;
u32 dyn_addr;
u32 i2c_STDBR;
u32 i3c_STDBR;
- unsigned long rate;
- u8 addrs[RENESAS_I3C_MAX_DEVS];
- struct renesas_i3c_xferqueue xferqueue;
- void __iomem *regs;
- u32 *DATBASn;
- struct clk_bulk_data *clks;
- struct reset_control *presetn;
- struct reset_control *tresetn;
- u8 num_clks;
+ u32 extbr;
+ u16 maxdevs;
u8 refclk_div;
};
@@ -433,7 +433,29 @@ static void renesas_i3c_enqueue_xfer(struct renesas_i3c *i3c, struct renesas_i3c
}
}
-static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer)
+static void renesas_i3c_irqs_mask_and_clear_locked(struct renesas_i3c *i3c)
+{
+ /* Disable all the interrupts. */
+ renesas_writel(i3c->regs, BIE, 0);
+ renesas_writel(i3c->regs, NTIE, 0);
+
+ /* Clear normal transfer status flags. */
+ renesas_writel(i3c->regs, NTST, 0);
+
+ /* Clear bus status flags. */
+ renesas_writel(i3c->regs, BST, 0);
+ /* Read back registers to confirm writes have fully propagated. */
+ renesas_readl(i3c->regs, BST);
+}
+
+static void renesas_i3c_irqs_mask_and_clear(struct renesas_i3c *i3c)
+{
+ guard(spinlock_irqsave)(&i3c->xferqueue.lock);
+
+ renesas_i3c_irqs_mask_and_clear_locked(i3c);
+}
+
+static unsigned long renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xfer *xfer)
{
unsigned long time_left;
@@ -442,6 +464,8 @@ static void renesas_i3c_wait_xfer(struct renesas_i3c *i3c, struct renesas_i3c_xf
time_left = wait_for_completion_timeout(&xfer->comp, msecs_to_jiffies(1000));
if (!time_left)
renesas_i3c_dequeue_xfer(i3c, xfer);
+
+ return time_left;
}
static void renesas_i3c_set_prts(struct renesas_i3c *i3c, u32 val)
@@ -475,6 +499,12 @@ static void renesas_i3c_bus_enable(struct i3c_master_controller *m, bool i3c_mod
static int renesas_i3c_reset(struct renesas_i3c *i3c)
{
u32 val;
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
renesas_writel(i3c->regs, BCTL, 0);
renesas_set_bit(i3c->regs, RSTCTL, RSTCTL_RI3CRST);
@@ -546,14 +576,10 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
int od_high_ticks, od_low_ticks, i2c_total_ticks;
int ret;
- i3c->rate = clk_get_rate(i3c->clks[RENESAS_I3C_TCLK_IDX].clk);
+ i3c->rate = clk_get_rate(i3c->tclk);
if (!i3c->rate)
return -EINVAL;
- ret = renesas_i3c_reset(i3c);
- if (ret)
- return ret;
-
i2c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i2c);
i3c_total_ticks = DIV_ROUND_UP(i3c->rate, bus->scl_rate.i3c);
@@ -604,29 +630,36 @@ static int renesas_i3c_bus_init(struct i3c_master_controller *m)
STDBR_SBRHO(double_SBR, od_high_ticks) |
STDBR_SBRLP(pp_low_ticks) |
STDBR_SBRHP(pp_high_ticks);
- renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
/* Extended Bit Rate setting */
- renesas_writel(i3c->regs, EXTBR, EXTBR_EBRLO(od_low_ticks) |
- EXTBR_EBRHO(od_high_ticks) |
- EXTBR_EBRLP(pp_low_ticks) |
- EXTBR_EBRHP(pp_high_ticks));
-
- renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
- i3c->refclk_div = cks;
-
- /* I3C hw init*/
- renesas_i3c_hw_init(i3c);
+ i3c->extbr = EXTBR_EBRLO(od_low_ticks) | EXTBR_EBRHO(od_high_ticks) |
+ EXTBR_EBRLP(pp_low_ticks) | EXTBR_EBRHP(pp_high_ticks);
ret = i3c_master_get_free_addr(m, 0);
if (ret < 0)
return ret;
+ info.dyn_addr = ret;
i3c->dyn_addr = ret;
- renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(ret) | MSDVAD_MDYADV);
+ i3c->refclk_div = cks;
+
+ ret = renesas_i3c_reset(i3c);
+ if (ret)
+ return ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
+ renesas_writel(i3c->regs, EXTBR, i3c->extbr);
+ renesas_writel(i3c->regs, REFCKCTL, REFCKCTL_IREFCKS(cks));
+ renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYAD(i3c->dyn_addr) | MSDVAD_MDYADV);
+
+ /* I3C hw init*/
+ renesas_i3c_hw_init(i3c);
- memset(&info, 0, sizeof(info));
- info.dyn_addr = ret;
return i3c_master_set_info(&i3c->base, &info);
}
@@ -641,6 +674,7 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
{
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_cmd *cmd;
+ unsigned long time_left;
u32 olddevs, newdevs;
u8 last_addr = 0, pos;
int ret;
@@ -649,6 +683,15 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
if (!xfer)
return -ENOMEM;
+ init_completion(&xfer->comp);
+ cmd = xfer->cmds;
+ cmd->rx_count = 0;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
/* Enable I3C bus. */
renesas_i3c_bus_enable(m, true);
@@ -670,10 +713,6 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
renesas_writel(i3c->regs, DATBAS(pos), datbas_dvdyad_with_parity(ret));
}
- init_completion(&xfer->comp);
- cmd = xfer->cmds;
- cmd->rx_count = 0;
-
ret = renesas_i3c_get_free_pos(i3c);
if (ret < 0)
return ret;
@@ -687,9 +726,15 @@ static int renesas_i3c_daa(struct i3c_master_controller *m)
NCMDQP_CMD(I3C_CCC_ENTDAA) | NCMDQP_DEV_INDEX(ret) |
NCMDQP_DEV_COUNT(i3c->maxdevs - ret) | NCMDQP_TOC;
- renesas_i3c_wait_xfer(i3c, xfer);
+ time_left = renesas_i3c_wait_xfer(i3c, xfer);
+ if (!time_left)
+ renesas_i3c_irqs_mask_and_clear(i3c);
+
+ if (cmd->rx_count >= i3c->maxdevs)
+ newdevs = 0;
+ else
+ newdevs = GENMASK(i3c->maxdevs - cmd->rx_count - 1, 0);
- newdevs = GENMASK(i3c->maxdevs - cmd->rx_count - 1, 0);
newdevs &= ~olddevs;
for (pos = 0; pos < i3c->maxdevs; pos++) {
@@ -749,6 +794,7 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
{
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_cmd *cmd;
+ unsigned long time_left;
int ret, pos = 0;
if (ccc->id & I3C_CCC_DIRECT) {
@@ -761,13 +807,18 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
if (!xfer)
return -ENOMEM;
- renesas_i3c_bus_enable(m, true);
-
init_completion(&xfer->comp);
cmd = xfer->cmds;
cmd->rnw = ccc->rnw;
cmd->cmd0 = 0;
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ renesas_i3c_bus_enable(m, true);
+
/* Calculate the command descriptor. */
switch (ccc->id) {
case I3C_CCC_SETDASA:
@@ -800,11 +851,15 @@ static int renesas_i3c_send_ccc_cmd(struct i3c_master_controller *m,
}
}
- renesas_i3c_wait_xfer(i3c, xfer);
+ time_left = renesas_i3c_wait_xfer(i3c, xfer);
+ if (!time_left)
+ renesas_i3c_irqs_mask_and_clear(i3c);
ret = xfer->ret;
if (ret)
ccc->err = I3C_ERROR_M2;
+ else if (ccc->rnw)
+ ccc->dests[0].payload.actual_len = cmd->rx_count;
return ret;
}
@@ -815,10 +870,9 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
- int i;
-
- /* Enable I3C bus. */
- renesas_i3c_bus_enable(m, true);
+ unsigned long time_left;
+ bool xfer_failed = false;
+ int i, ret;
struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1);
if (!xfer)
@@ -826,6 +880,14 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
init_completion(&xfer->comp);
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ /* Enable I3C bus. */
+ renesas_i3c_bus_enable(m, true);
+
for (i = 0; i < i3c_nxfers; i++) {
struct renesas_i3c_cmd *cmd = xfer->cmds;
@@ -854,9 +916,14 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
renesas_set_bit(i3c->regs, NTIE, NTIE_TDBEIE0);
}
- renesas_i3c_wait_xfer(i3c, xfer);
+ time_left = renesas_i3c_wait_xfer(i3c, xfer);
+ if (!time_left)
+ xfer_failed = true;
}
+ if (xfer_failed)
+ renesas_i3c_irqs_mask_and_clear(i3c);
+
return 0;
}
@@ -865,12 +932,17 @@ static int renesas_i3c_attach_i3c_dev(struct i3c_dev_desc *dev)
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_i2c_dev_data *data;
- int pos;
+ int pos, ret;
pos = renesas_i3c_get_free_pos(i3c);
if (pos < 0)
return pos;
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
@@ -892,10 +964,31 @@ static int renesas_i3c_reattach_i3c_dev(struct i3c_dev_desc *dev,
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
+ int pos, ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ pos = renesas_i3c_get_free_pos(i3c);
+
+ if (data->index > pos && pos >= 0) {
+ renesas_writel(i3c->regs, DATBAS(data->index), 0);
+ i3c->addrs[data->index] = 0;
+ i3c->free_pos |= BIT(data->index);
+
+ data->index = pos;
+ i3c->free_pos &= ~BIT(data->index);
+ }
i3c->addrs[data->index] = dev->info.dyn_addr ? dev->info.dyn_addr :
dev->info.static_addr;
+ renesas_writel(i3c->regs, DATBAS(data->index),
+ DATBAS_DVSTAD(dev->info.static_addr) |
+ datbas_dvdyad_with_parity(i3c->addrs[data->index]));
+
return 0;
}
@@ -904,6 +997,12 @@ static void renesas_i3c_detach_i3c_dev(struct i3c_dev_desc *dev)
struct renesas_i3c_i2c_dev_data *data = i3c_dev_get_master_data(dev);
struct i3c_master_controller *m = i3c_dev_get_master(dev);
struct renesas_i3c *i3c = to_renesas_i3c(m);
+ int ret;
+
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (!ret)
+ renesas_writel(i3c->regs, DATBAS(data->index), 0);
i3c_dev_set_master_data(dev, NULL);
i3c->addrs[data->index] = 0;
@@ -919,21 +1018,28 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
struct renesas_i3c *i3c = to_renesas_i3c(m);
struct renesas_i3c_cmd *cmd;
u8 start_bit = CNDCTL_STCND;
- int i;
-
- struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1);
- if (!xfer)
- return -ENOMEM;
+ unsigned long time_left;
+ bool xfer_failed = false;
+ int i, ret;
if (!i2c_nxfers)
return 0;
- renesas_i3c_bus_enable(m, false);
+ struct renesas_i3c_xfer *xfer __free(kfree) = renesas_i3c_alloc_xfer(i3c, 1);
+ if (!xfer)
+ return -ENOMEM;
init_completion(&xfer->comp);
xfer->is_i2c_xfer = true;
cmd = xfer->cmds;
+ PM_RUNTIME_ACQUIRE_IF_ENABLED_AUTOSUSPEND(i3c->dev, pm);
+ ret = PM_RUNTIME_ACQUIRE_ERR(&pm);
+ if (ret)
+ return ret;
+
+ renesas_i3c_bus_enable(m, false);
+
if (!(renesas_readl(i3c->regs, BCST) & BCST_BFREF)) {
cmd->err = -EBUSY;
return cmd->err;
@@ -958,7 +1064,9 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
renesas_set_bit(i3c->regs, NTSTE, NTSTE_TDBEE0);
- wait_for_completion_timeout(&xfer->comp, m->i2c.timeout);
+ time_left = wait_for_completion_timeout(&xfer->comp, m->i2c.timeout);
+ if (!time_left)
+ xfer_failed = true;
if (cmd->err)
break;
@@ -967,6 +1075,10 @@ static int renesas_i3c_i2c_xfers(struct i2c_dev_desc *dev,
}
renesas_i3c_dequeue_xfer(i3c, xfer);
+
+ if (xfer_failed)
+ renesas_i3c_irqs_mask_and_clear(i3c);
+
return cmd->err;
}
@@ -1014,6 +1126,11 @@ static irqreturn_t renesas_i3c_tx_isr(int irq, void *data)
scoped_guard(spinlock, &i3c->xferqueue.lock) {
xfer = i3c->xferqueue.cur;
+ if (!xfer) {
+ renesas_i3c_irqs_mask_and_clear_locked(i3c);
+ return IRQ_HANDLED;
+ }
+
cmd = xfer->cmds;
if (xfer->is_i2c_xfer) {
@@ -1054,6 +1171,11 @@ static irqreturn_t renesas_i3c_resp_isr(int irq, void *data)
scoped_guard(spinlock, &i3c->xferqueue.lock) {
xfer = i3c->xferqueue.cur;
+ if (!xfer) {
+ renesas_i3c_irqs_mask_and_clear_locked(i3c);
+ return IRQ_HANDLED;
+ }
+
cmd = xfer->cmds;
/* Clear the Respone Queue Full status flag*/
@@ -1072,10 +1194,16 @@ static irqreturn_t renesas_i3c_resp_isr(int irq, void *data)
break;
case I3C_INTERNAL_STATE_CONTROLLER_READ:
case I3C_INTERNAL_STATE_CONTROLLER_COMMAND_READ:
- if (NDBSTLV0_RDBLV(renesas_readl(i3c->regs, NDBSTLV0)) && !cmd->err)
- bytes_remaining = data_len - cmd->rx_count;
-
- i3c_readl_fifo(i3c->regs + NTDTBP0, cmd->rx_buf, bytes_remaining);
+ if (!cmd->err) {
+ u32 rx_count = min(cmd->rx_count, data_len);
+
+ bytes_remaining = data_len - rx_count;
+ if (bytes_remaining)
+ i3c_readl_fifo(i3c->regs + NTDTBP0,
+ cmd->rx_buf + rx_count,
+ bytes_remaining);
+ cmd->rx_count = data_len;
+ }
renesas_clear_bit(i3c->regs, NTIE, NTIE_RDBFIE0);
break;
default:
@@ -1138,6 +1266,11 @@ static irqreturn_t renesas_i3c_tend_isr(int irq, void *data)
scoped_guard(spinlock, &i3c->xferqueue.lock) {
xfer = i3c->xferqueue.cur;
+ if (!xfer) {
+ renesas_i3c_irqs_mask_and_clear_locked(i3c);
+ return IRQ_HANDLED;
+ }
+
cmd = xfer->cmds;
if (xfer->is_i2c_xfer) {
@@ -1184,6 +1317,11 @@ static irqreturn_t renesas_i3c_rx_isr(int irq, void *data)
scoped_guard(spinlock, &i3c->xferqueue.lock) {
xfer = i3c->xferqueue.cur;
+ if (!xfer) {
+ renesas_i3c_irqs_mask_and_clear_locked(i3c);
+ return IRQ_HANDLED;
+ }
+
cmd = xfer->cmds;
if (xfer->is_i2c_xfer) {
@@ -1234,15 +1372,13 @@ static irqreturn_t renesas_i3c_stop_isr(int irq, void *data)
struct renesas_i3c_xfer *xfer;
scoped_guard(spinlock, &i3c->xferqueue.lock) {
- xfer = i3c->xferqueue.cur;
-
- /* read back registers to confirm writes have fully propagated */
- renesas_writel(i3c->regs, BST, 0);
- renesas_readl(i3c->regs, BST);
- renesas_writel(i3c->regs, BIE, 0);
- renesas_clear_bit(i3c->regs, NTST, NTST_TDBEF0 | NTST_RDBFF0);
+ renesas_i3c_irqs_mask_and_clear_locked(i3c);
renesas_clear_bit(i3c->regs, SCSTRCTL, SCSTRCTL_RWE);
+ xfer = i3c->xferqueue.cur;
+ if (!xfer)
+ return IRQ_HANDLED;
+
xfer->ret = 0;
complete(&xfer->comp);
}
@@ -1259,6 +1395,11 @@ static irqreturn_t renesas_i3c_start_isr(int irq, void *data)
scoped_guard(spinlock, &i3c->xferqueue.lock) {
xfer = i3c->xferqueue.cur;
+ if (!xfer) {
+ renesas_i3c_irqs_mask_and_clear_locked(i3c);
+ return IRQ_HANDLED;
+ }
+
cmd = xfer->cmds;
if (xfer->is_i2c_xfer) {
@@ -1325,12 +1466,16 @@ static int renesas_i3c_probe(struct platform_device *pdev)
if (IS_ERR(i3c->regs))
return PTR_ERR(i3c->regs);
- ret = devm_clk_bulk_get_all_enabled(&pdev->dev, &i3c->clks);
- if (ret <= RENESAS_I3C_TCLK_IDX)
- return dev_err_probe(&pdev->dev, ret < 0 ? ret : -EINVAL,
- "Failed to get clocks (need > %d, got %d)\n",
- RENESAS_I3C_TCLK_IDX, ret);
- i3c->num_clks = ret;
+ i3c->tclk = devm_clk_get(&pdev->dev, "tclk");
+ if (IS_ERR(i3c->tclk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(i3c->tclk), "Failed to get tclk");
+
+ i3c->dev = &pdev->dev;
+ pm_runtime_set_autosuspend_delay(&pdev->dev, 300);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ ret = devm_pm_runtime_enable(&pdev->dev);
+ if (ret)
+ return ret;
i3c->tresetn = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, "tresetn");
if (IS_ERR(i3c->tresetn))
@@ -1350,12 +1495,19 @@ static int renesas_i3c_probe(struct platform_device *pdev)
return ret;
for (i = 0; i < ARRAY_SIZE(renesas_i3c_irqs); i++) {
+ const char *irqname;
+
ret = platform_get_irq_byname(pdev, renesas_i3c_irqs[i].name);
if (ret < 0)
return ret;
+ irqname = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s:%s", dev_name(&pdev->dev),
+ renesas_i3c_irqs[i].desc);
+ if (!irqname)
+ return -ENOMEM;
+
ret = devm_request_irq(&pdev->dev, ret, renesas_i3c_irqs[i].isr,
- 0, renesas_i3c_irqs[i].desc, i3c);
+ 0, irqname, i3c);
if (ret)
return ret;
}
@@ -1365,12 +1517,6 @@ static int renesas_i3c_probe(struct platform_device *pdev)
i3c->maxdevs = RENESAS_I3C_MAX_DEVS;
i3c->free_pos = GENMASK(i3c->maxdevs - 1, 0);
- /* Allocate dynamic Device Address Table backup. */
- i3c->DATBASn = devm_kzalloc(&pdev->dev, sizeof(u32) * i3c->maxdevs,
- GFP_KERNEL);
- if (!i3c->DATBASn)
- return -ENOMEM;
-
return i3c_master_register(&i3c->base, &pdev->dev, &renesas_i3c_ops, false);
}
@@ -1381,81 +1527,90 @@ static void renesas_i3c_remove(struct platform_device *pdev)
i3c_master_unregister(&i3c->base);
}
-static int renesas_i3c_suspend_noirq(struct device *dev)
+static int renesas_i3c_suspend(struct device *dev)
{
struct renesas_i3c *i3c = dev_get_drvdata(dev);
- int i, ret;
+ struct reset_control_bulk_data resets[] = {
+ { .rstc = i3c->presetn },
+ { .rstc = i3c->tresetn },
+ };
+ int ret;
i2c_mark_adapter_suspended(&i3c->base.i2c);
- /* Store Device Address Table values. */
- for (i = 0; i < i3c->maxdevs; i++)
- i3c->DATBASn[i] = renesas_readl(i3c->regs, DATBAS(i));
-
- ret = reset_control_assert(i3c->presetn);
+ ret = reset_control_bulk_assert(ARRAY_SIZE(resets), resets);
if (ret)
goto err_mark_resumed;
- ret = reset_control_assert(i3c->tresetn);
- if (ret)
- goto err_presetn;
-
- clk_bulk_disable(i3c->num_clks, i3c->clks);
-
return 0;
-err_presetn:
- reset_control_deassert(i3c->presetn);
err_mark_resumed:
i2c_mark_adapter_resumed(&i3c->base.i2c);
return ret;
}
-static int renesas_i3c_resume_noirq(struct device *dev)
+static int renesas_i3c_resume(struct device *dev)
{
struct renesas_i3c *i3c = dev_get_drvdata(dev);
- int i, ret;
+ struct reset_control_bulk_data resets[] = {
+ { .rstc = i3c->presetn },
+ { .rstc = i3c->tresetn },
+ };
+ int ret;
- ret = reset_control_deassert(i3c->presetn);
+ ret = reset_control_bulk_deassert(ARRAY_SIZE(resets), resets);
if (ret)
return ret;
- ret = reset_control_deassert(i3c->tresetn);
+ ret = renesas_i3c_reset(i3c);
if (ret)
- goto err_presetn;
+ goto err_resets_asserted;
- ret = clk_bulk_enable(i3c->num_clks, i3c->clks);
+ ret = pm_runtime_resume_and_get(dev);
if (ret)
- goto err_tresetn;
+ goto err_resets_asserted;
/* Re-store I3C registers value. */
+ renesas_writel(i3c->regs, STDBR, i3c->i3c_STDBR);
+ renesas_writel(i3c->regs, EXTBR, i3c->extbr);
renesas_writel(i3c->regs, REFCKCTL,
REFCKCTL_IREFCKS(i3c->refclk_div));
renesas_writel(i3c->regs, MSDVAD, MSDVAD_MDYADV |
MSDVAD_MDYAD(i3c->dyn_addr));
- /* Restore Device Address Table values. */
- for (i = 0; i < i3c->maxdevs; i++)
- renesas_writel(i3c->regs, DATBAS(i), i3c->DATBASn[i]);
-
/* I3C hw init. */
renesas_i3c_hw_init(i3c);
+ ret = i3c_master_do_daa_ext(&i3c->base, true);
+ if (ret)
+ dev_err(dev, "DAA failed on resume, ret=%d", ret);
+
i2c_mark_adapter_resumed(&i3c->base.i2c);
+ pm_runtime_put_autosuspend(dev);
+
+ /*
+ * I3C devices may have retained their dynamic address anyway. Do not
+ * fail the resume because of DAA error.
+ */
return 0;
-err_tresetn:
- reset_control_assert(i3c->tresetn);
-err_presetn:
- reset_control_assert(i3c->presetn);
+err_resets_asserted:
+ /*
+ * If this happens, there is no way to recover from this state without
+ * reloading the driver. We want to avoid keeping the reset line
+ * deasserted unnecessarily. The runtime paths will still work correctly
+ * even if the IP registers are accessed while reset is asserted (e.g.
+ * if a runtime path is triggered after a failed resume). Checked on
+ * RZ/G3S.
+ */
+ reset_control_bulk_assert(ARRAY_SIZE(resets), resets);
return ret;
}
static const struct dev_pm_ops renesas_i3c_pm_ops = {
- NOIRQ_SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend_noirq,
- renesas_i3c_resume_noirq)
+ SYSTEM_SLEEP_PM_OPS(renesas_i3c_suspend, renesas_i3c_resume)
};
static const struct of_device_id renesas_i3c_of_ids[] = {
diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
index 93805df8a940..e700392f0801 100644
--- a/drivers/i3c/master/svc-i3c-master.c
+++ b/drivers/i3c/master/svc-i3c-master.c
@@ -455,14 +455,22 @@ static int svc_i3c_master_handle_ibi(struct svc_i3c_master *master,
buf = slot->data;
while (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS)) &&
- slot->len < SVC_I3C_FIFO_SIZE) {
+ slot->len < dev->ibi->max_payload_len) {
mdatactrl = readl(master->regs + SVC_I3C_MDATACTRL);
count = SVC_I3C_MDATACTRL_RXCOUNT(mdatactrl);
+ count = min(count, dev->ibi->max_payload_len - slot->len);
readsb(master->regs + SVC_I3C_MRDATAB, buf, count);
slot->len += count;
buf += count;
}
+ /*
+ * The device may have sent more than the requested payload. Drop the
+ * extra bytes so they do not leak into the next transfer.
+ */
+ if (SVC_I3C_MSTATUS_RXPEND(readl(master->regs + SVC_I3C_MSTATUS)))
+ writel(SVC_I3C_MDATACTRL_FLUSHRB, master->regs + SVC_I3C_MDATACTRL);
+
master->ibi.tbq_slot = slot;
return 0;
@@ -1488,8 +1496,11 @@ static int svc_i3c_master_xfer(struct svc_i3c_master *master,
svc_i3c_master_emit_force_exit(master);
/* Wait idle if stop is sent. */
- readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
- SVC_I3C_MSTATUS_STATE_IDLE(reg), 0, 1000);
+ ret = readl_poll_timeout(master->regs + SVC_I3C_MSTATUS, reg,
+ SVC_I3C_MSTATUS_STATE_IDLE(reg),
+ 0, 1000);
+ if (ret)
+ goto cleanup;
}
return 0;
@@ -1500,6 +1511,7 @@ emit_stop:
else
svc_i3c_master_emit_force_exit(master);
+cleanup:
svc_i3c_master_clear_merrwarn(master);
svc_i3c_master_flush_fifo(master);
@@ -1713,8 +1725,8 @@ static int svc_i3c_master_send_direct_ccc_cmd(struct svc_i3c_master *master,
svc_i3c_master_dequeue_xfer(master, xfer);
mutex_unlock(&master->lock);
- if (cmd->actual_len != xfer_len)
- ccc->dests[0].payload.len = cmd->actual_len;
+ if (ccc->rnw)
+ ccc->dests[0].payload.actual_len = cmd->actual_len;
ret = xfer->ret;
svc_i3c_master_free_xfer(xfer);
diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h
index ad59a4ae60d1..7ad677baf761 100644
--- a/include/linux/i3c/ccc.h
+++ b/include/linux/i3c/ccc.h
@@ -12,6 +12,8 @@
#include <linux/i3c/device.h>
/* I3C CCC (Common Command Codes) related definitions */
+#define I3C_CCC_RETRIES 1
+
#define I3C_CCC_DIRECT BIT(7)
#define I3C_CCC_ID(id, broadcast) \
@@ -343,11 +345,15 @@ struct i3c_ccc_getxtime {
/**
* struct i3c_ccc_cmd_payload - CCC payload
*
- * @len: payload length
+ * @len: requested payload length
+ * @actual_len: number of bytes received on a GET CCC (filled by the driver)
+ * @optional_bytes: GET CCCs may return up to this many fewer bytes than @len
* @data: payload data. This buffer must be DMA-able
*/
struct i3c_ccc_cmd_payload {
u16 len;
+ u16 actual_len;
+ u16 optional_bytes;
void *data;
};
@@ -372,12 +378,15 @@ struct i3c_ccc_cmd_dest {
* @ndests: number of destinations. Should always be one for broadcast commands
* @dests: array of destinations and associated payload for this CCC. Most of
* the time, only one destination is provided
+ * @retries: number of times to retry a failed Direct GET CCC (see
+ * &I3C_CCC_RETRIES)
* @err: I3C error code
*/
struct i3c_ccc_cmd {
u8 rnw;
u8 id;
unsigned int ndests;
+ unsigned int retries;
struct i3c_ccc_cmd_dest *dests;
enum i3c_error_code err;
};