diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-23 14:03:04 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-23 14:03:04 +0100 |
| commit | 11c3cb9bb3f764c0fdabe5316a902d4e343c95d7 (patch) | |
| tree | ce371abc430aa3d56aee37d6626bf87b51dc9723 | |
| parent | ae37b12f68eeaea25f6fee8e881e67c3785be102 (diff) | |
| parent | 0043b9bb5806605185cdd4b90b0ba02c9746d3df (diff) | |
| download | linux-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.yaml | 1 | ||||
| -rw-r--r-- | Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml | 58 | ||||
| -rw-r--r-- | MAINTAINERS | 8 | ||||
| -rw-r--r-- | drivers/i3c/master.c | 99 | ||||
| -rw-r--r-- | drivers/i3c/master/Kconfig | 15 | ||||
| -rw-r--r-- | drivers/i3c/master/Makefile | 1 | ||||
| -rw-r--r-- | drivers/i3c/master/adi-i3c-master.c | 19 | ||||
| -rw-r--r-- | drivers/i3c/master/amd-i3c-master.c | 1124 | ||||
| -rw-r--r-- | drivers/i3c/master/dw-i3c-master.c | 41 | ||||
| -rw-r--r-- | drivers/i3c/master/i3c-master-cdns.c | 3 | ||||
| -rw-r--r-- | drivers/i3c/master/mipi-i3c-hci/core.c | 5 | ||||
| -rw-r--r-- | drivers/i3c/master/renesas-i3c.c | 391 | ||||
| -rw-r--r-- | drivers/i3c/master/svc-i3c-master.c | 22 | ||||
| -rw-r--r-- | include/linux/i3c/ccc.h | 11 |
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; }; |
