From cae22bd965bbee732993f3df1337c9b4167f943d Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Wed, 8 Jul 2026 00:17:37 -0700 Subject: i3c: ccc: Add actual_len to struct i3c_ccc_cmd_payload Add actual_len to struct i3c_ccc_cmd_payload so drivers can report how many bytes were received on a GET CCC without overwriting the requested buffer length in len. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Reviewed-by: Alexandre Mergnat Reviewed-by: Frank Li Tested-by: Tommaso Merciai Tested-by: Claudiu Beznea # on RZ/G3S Link: https://patch.msgid.link/e452777c3a9be734a97e20b9822d8a4264ceadba.1783493868.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- include/linux/i3c/ccc.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h index ad59a4ae60d1..d8052949e57e 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -343,11 +343,13 @@ 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) * @data: payload data. This buffer must be DMA-able */ struct i3c_ccc_cmd_payload { u16 len; + u16 actual_len; void *data; }; -- cgit v1.2.3 From 09361ed979e68622977751f6274f280d7aec5cb6 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Wed, 8 Jul 2026 00:17:40 -0700 Subject: i3c: master: Validate GET CCC payload length and retry Direct GET once Add retries to struct i3c_ccc_cmd. Validate GET payload length in i3c_master_send_ccc_cmd_locked() after a successful transfer. Retry failed Direct GET CCCs up to cmd->retries times when the driver reports failure or an I3C error; validation failures are not retried. SET CCCs are not retried by default. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Reviewed-by: Alexandre Mergnat Reviewed-by: Frank Li Tested-by: Tommaso Merciai Tested-by: Claudiu Beznea # on RZ/G3S Link: https://patch.msgid.link/b467f01edfaaa0710f30e719ce7f2753b06c1a3f.1783493868.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 59 +++++++++++++++++++++++++++++++++++++++++++++---- include/linux/i3c/ccc.h | 5 +++++ 2 files changed, 60 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 84cda266d8e3..9c32fd0c5fef 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -966,17 +966,46 @@ 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; + + if (p->actual_len > p->len) + return -EIO; + + if (p->len && p->actual_len != p->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 @@ -988,6 +1017,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; @@ -1005,7 +1037,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 * @@ -1444,6 +1494,7 @@ static int i3c_master_getmxds_locked(struct i3c_master_controller *master, * while expecting shorter length from this CCC command. */ dest.payload.len -= 3; + i3c_ccc_cmd_init(&cmd, true, I3C_CCC_GETMXDS, &dest, 1); ret = i3c_master_send_ccc_cmd_locked(master, &cmd); if (ret) goto out; diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h index d8052949e57e..2506d83b8255 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -12,6 +12,8 @@ #include /* I3C CCC (Common Command Codes) related definitions */ +#define I3C_CCC_RETRIES 1 + #define I3C_CCC_DIRECT BIT(7) #define I3C_CCC_ID(id, broadcast) \ @@ -374,12 +376,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; }; -- cgit v1.2.3 From b32f4ed0cc069206cb7b6baa0654b14410a91440 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Wed, 8 Jul 2026 00:17:41 -0700 Subject: i3c: master: Add optional_bytes for variable-length GET CCC validation Add optional_bytes to struct i3c_ccc_cmd_payload so callers describe variable-length GET CCC responses. GETMRL and GETMXDS set optional_bytes at the call site. Extend i3c_ccc_validate_payload_len() to honour it. Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Tze Yee Ng Reviewed-by: Alexandre Mergnat Reviewed-by: Frank Li Tested-by: Tommaso Merciai Tested-by: Claudiu Beznea # on RZ/G3S Link: https://patch.msgid.link/2e07dc944eab1c4358be1da87fa5000e711ac8bd.1783493868.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 29 +++++++++++++++++++++++------ include/linux/i3c/ccc.h | 2 ++ 2 files changed, 25 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 9c32fd0c5fef..fd3e79d10c84 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -953,6 +953,7 @@ 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 @@ -995,11 +996,19 @@ static int i3c_ccc_validate_payload_len(struct i3c_ccc_cmd *cmd) 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 && p->actual_len != p->len) + if (!p->len) + continue; + + min_len = p->len - p->optional_bytes; + if (p->actual_len < min_len) return -EIO; } @@ -1414,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); @@ -1486,14 +1499,18 @@ 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) diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h index 2506d83b8255..7ad677baf761 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -347,11 +347,13 @@ struct i3c_ccc_getxtime { * * @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; }; -- cgit v1.2.3 From 81e7c27b0d5cb3029fc01374c3a96019d3a9e673 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:43 +0000 Subject: dt-bindings: i3c: Add mipi-i3c-static-method to support SETAASA Add the 'mipi-i3c-static-method' property mentioned in the MIPI I3C Discovery and Configuration Specification [1] to specify which discovery method an I3C device supports during bus initialization. The property is a bitmap, where a bit value of 1 indicates support for that method, and 0 indicates lack of support. Bit 0: SETDASA CCC (Direct) Bit 1: SETAASA CCC (Broadcast) Bit 2: Other CCC (vendor / standards extension) All other bits are reserved. It is specifically needed when an I3C device requires SETAASA for the address assignment. SETDASA will be supported by default if this property is absent, which means for now the property just serves as a flag to enable SETAASA, but keep the property as a bitmap to align with the specifications. [1] https://www.mipi.org/mipi-disco-for-i3c-download Reviewed-by: Frank Li Reviewed-by: Rob Herring (Arm) Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-2-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni --- Documentation/devicetree/bindings/i3c/i3c.yaml | 36 ++++++++++++++++++++++---- include/dt-bindings/i3c/i3c.h | 4 +++ 2 files changed, 35 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/Documentation/devicetree/bindings/i3c/i3c.yaml b/Documentation/devicetree/bindings/i3c/i3c.yaml index e25fa72fd785..5603f2e7807d 100644 --- a/Documentation/devicetree/bindings/i3c/i3c.yaml +++ b/Documentation/devicetree/bindings/i3c/i3c.yaml @@ -31,10 +31,12 @@ properties: described in the device tree, which in turn means we have to describe I3C devices. - Another use case for describing an I3C device in the device tree is when - this I3C device has a static I2C address and we want to assign it a - specific I3C dynamic address before the DAA takes place (so that other - devices on the bus can't take this dynamic address). + Other use-cases for describing an I3C device in the device tree are: + - When the I3C device has a static I2C address and we want to assign + it a specific I3C dynamic address before the DAA takes place (so + that other devices on the bus can't take this dynamic address). + - When the I3C device requires SETAASA for its discovery and uses a + pre-defined static address. "#size-cells": const: 0 @@ -145,7 +147,31 @@ patternProperties: Dynamic address to be assigned to this device. In case static address is present (first cell of the reg property != 0), this address is assigned through SETDASA. If static address is not present, this address is assigned - through SETNEWDA after assigning a temporary address via ENTDAA. + through SETNEWDA after assigning a temporary address via ENTDAA. If + SETAASA is used, this property is not used, and the static address itself + becomes the dynamic address. + + mipi-i3c-static-method: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0x1 + maximum: 0x7 + default: 1 + description: | + Bitmap describing which methods of Dynamic Address Assignment from a + static address are supported by this I3C Target. For each defined bit + position, a set bit indicates support for that method and a cleared + bit indicates lack of support. + + Bit 0: SETDASA CCC (Direct) + Bit 1: SETAASA CCC (Broadcast) + Bit 2: Other CCC (vendor / standards extension) + All other bits are reserved. + + This property follows the MIPI I3C specification. The primary use + of this property is to indicate support for SETAASA, i.e Bit 1, but + will allow other values mentioned in the specification so that it + mirrors the specification. SETDASA will remain as the default method + even if this property is not present. required: - reg diff --git a/include/dt-bindings/i3c/i3c.h b/include/dt-bindings/i3c/i3c.h index 373439218bba..78b8c634aad8 100644 --- a/include/dt-bindings/i3c/i3c.h +++ b/include/dt-bindings/i3c/i3c.h @@ -13,4 +13,8 @@ #define I2C_NO_FILTER_HIGH_FREQUENCY (1 << 5) #define I2C_NO_FILTER_LOW_FREQUENCY (2 << 5) +#define I3C_ADDR_METHOD_SETDASA (1 << 0) +#define I3C_ADDR_METHOD_SETAASA (1 << 1) +#define I3C_ADDR_METHOD_VENDOR (1 << 2) + #endif -- cgit v1.2.3 From ee170021bee17124c13f42813607bca553a6b48a Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:44 +0000 Subject: i3c: master: Use unified device property interface Replace all OF-specific functions with unified device property functions as a prerequisite to support both ACPI and device tree. Reviewed-by: Frank Li Signed-off-by: Akhil R Link: https://patch.msgid.link/20260728065955.809445-3-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 77 ++++++++++++++++++++++++++-------------------- include/linux/i3c/master.h | 5 +-- 2 files changed, 46 insertions(+), 36 deletions(-) (limited to 'include') diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 16c47df67e3a..9f95d59078b1 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -13,10 +13,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -491,7 +493,7 @@ static void i3c_bus_cleanup(struct i3c_bus *i3cbus) mutex_unlock(&i3c_core_lock); } -static int i3c_bus_init(struct i3c_bus *i3cbus, struct device_node *np) +static int i3c_bus_init(struct i3c_bus *i3cbus, struct fwnode_handle *fwnode) { int ret, start, end, id = -1; @@ -501,8 +503,8 @@ static int i3c_bus_init(struct i3c_bus *i3cbus, struct device_node *np) i3c_bus_init_addrslots(i3cbus); i3cbus->mode = I3C_BUS_MODE_PURE; - if (np) - id = of_alias_get_id(np, "i3c"); + if (fwnode && is_of_node(fwnode)) + id = of_alias_get_id(to_of_node(fwnode), "i3c"); mutex_lock(&i3c_core_lock); if (id >= 0) { @@ -837,7 +839,7 @@ static void i3c_masterdev_release(struct device *dev) WARN_ON(!list_empty(&bus->devs.i2c) || !list_empty(&bus->devs.i3c)); i3c_bus_cleanup(bus); - of_node_put(dev->of_node); + fwnode_handle_put(dev->fwnode); } static const struct device_type i3c_masterdev_type = { @@ -1105,7 +1107,7 @@ static void i3c_device_release(struct device *dev) WARN_ON(i3cdev->desc); - of_node_put(i3cdev->dev.of_node); + fwnode_handle_put(dev->fwnode); kfree(i3cdev); } @@ -1998,7 +2000,7 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) desc->info.pid); if (desc->boardinfo) - desc->dev->dev.of_node = desc->boardinfo->of_node; + device_set_node(&desc->dev->dev, desc->boardinfo->fwnode); ret = device_register(&desc->dev->dev); if (ret) { @@ -2692,8 +2694,8 @@ EXPORT_SYMBOL_GPL(i3c_master_do_daa); #define OF_I3C_REG1_IS_I2C_DEV BIT(31) static int -of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, - struct device_node *node, u32 *reg) +i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, + struct fwnode_handle *fwnode, u32 *reg) { struct i2c_dev_boardinfo *boardinfo; struct device *dev = &master->dev; @@ -2703,9 +2705,13 @@ of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, if (!boardinfo) return -ENOMEM; - ret = of_i2c_get_board_info(dev, node, &boardinfo->base); - if (ret) - return ret; + if (is_of_node(fwnode)) { + ret = of_i2c_get_board_info(dev, to_of_node(fwnode), &boardinfo->base); + if (ret) + return ret; + } else { + return -EINVAL; + } /* * The I3C Specification does not clearly say I2C devices with 10-bit @@ -2721,14 +2727,14 @@ of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master, boardinfo->lvr = reg[2]; list_add_tail(&boardinfo->node, &master->boardinfo.i2c); - of_node_get(node); + fwnode_handle_get(fwnode); return 0; } static int -of_i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, - struct device_node *node, u32 *reg) +i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, + struct fwnode_handle *fwnode, u32 *reg) { struct i3c_dev_boardinfo *boardinfo; struct device *dev = &master->dev; @@ -2751,7 +2757,7 @@ of_i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, boardinfo->static_addr = reg[0]; - if (!of_property_read_u32(node, "assigned-address", &init_dyn_addr)) { + if (!fwnode_property_read_u32(fwnode, "assigned-address", &init_dyn_addr)) { if (init_dyn_addr > I3C_MAX_ADDR) return -EINVAL; @@ -2768,14 +2774,14 @@ of_i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, return -EINVAL; boardinfo->init_dyn_addr = init_dyn_addr; - boardinfo->of_node = of_node_get(node); + boardinfo->fwnode = fwnode_handle_get(fwnode); list_add_tail(&boardinfo->node, &master->boardinfo.i3c); return 0; } -static int of_i3c_master_add_dev(struct i3c_master_controller *master, - struct device_node *node) +static int i3c_master_add_dev(struct i3c_master_controller *master, + struct fwnode_handle *fwnode) { u32 reg[3]; int ret; @@ -2783,7 +2789,7 @@ static int of_i3c_master_add_dev(struct i3c_master_controller *master, if (!master) return -EINVAL; - ret = of_property_read_u32_array(node, "reg", reg, ARRAY_SIZE(reg)); + ret = fwnode_property_read_u32_array(fwnode, "reg", reg, ARRAY_SIZE(reg)); if (ret) return ret; @@ -2792,25 +2798,25 @@ static int of_i3c_master_add_dev(struct i3c_master_controller *master, * dealing with an I2C device. */ if (!reg[1]) - ret = of_i3c_master_add_i2c_boardinfo(master, node, reg); + ret = i3c_master_add_i2c_boardinfo(master, fwnode, reg); else - ret = of_i3c_master_add_i3c_boardinfo(master, node, reg); + ret = i3c_master_add_i3c_boardinfo(master, fwnode, reg); return ret; } -static int of_populate_i3c_bus(struct i3c_master_controller *master) +static int fwnode_populate_i3c_bus(struct i3c_master_controller *master) { struct device *dev = &master->dev; - struct device_node *i3cbus_np = dev->of_node; + struct fwnode_handle *fwnode = dev_fwnode(dev); int ret; u32 val; - if (!i3cbus_np) + if (!fwnode) return 0; - for_each_available_child_of_node_scoped(i3cbus_np, node) { - ret = of_i3c_master_add_dev(master, node); + fwnode_for_each_available_child_node_scoped(fwnode, child) { + ret = i3c_master_add_dev(master, child); if (ret) return ret; } @@ -2820,10 +2826,10 @@ static int of_populate_i3c_bus(struct i3c_master_controller *master) * on the bus are not supporting typical rates, or if the bus topology * prevents it from using max possible rate. */ - if (!of_property_read_u32(i3cbus_np, "i2c-scl-hz", &val)) + if (!device_property_read_u32(dev, "i2c-scl-hz", &val)) master->bus.scl_rate.i2c = val; - if (!of_property_read_u32(i3cbus_np, "i3c-scl-hz", &val)) + if (!device_property_read_u32(dev, "i3c-scl-hz", &val)) master->bus.scl_rate.i3c = val; return 0; @@ -2878,7 +2884,7 @@ static u8 i3c_master_i2c_get_lvr(struct i2c_client *client) u8 lvr = I3C_LVR_I2C_INDEX(2) | I3C_LVR_I2C_FM_MODE; u32 reg[3]; - if (!of_property_read_u32_array(client->dev.of_node, "reg", reg, ARRAY_SIZE(reg))) + if (!fwnode_property_read_u32_array(client->dev.fwnode, "reg", reg, ARRAY_SIZE(reg))) lvr = reg[2]; return lvr; @@ -2997,7 +3003,8 @@ static int i3c_master_i2c_adapter_init(struct i3c_master_controller *master) struct i2c_adapter *adap = i3c_master_to_i2c_adapter(master); struct i2c_dev_desc *i2cdev; struct i2c_dev_boardinfo *i2cboardinfo; - int ret, id; + struct fwnode_handle *fwnode = dev_fwnode(&master->dev); + int ret, id = -1; adap->dev.parent = master->dev.parent; adap->owner = master->dev.parent->driver->owner; @@ -3006,7 +3013,9 @@ static int i3c_master_i2c_adapter_init(struct i3c_master_controller *master) adap->timeout = HZ; adap->retries = 3; - id = of_alias_get_id(master->dev.of_node, "i2c"); + if (fwnode && is_of_node(fwnode)) + id = of_alias_get_id(to_of_node(fwnode), "i2c"); + if (id >= 0) { adap->nr = id; ret = i2c_add_numbered_adapter(adap); @@ -3308,7 +3317,7 @@ int i3c_master_register(struct i3c_master_controller *master, return ret; master->dev.parent = parent; - master->dev.of_node = of_node_get(parent->of_node); + device_set_node(&master->dev, fwnode_handle_get(dev_fwnode(parent))); master->dev.bus = &i3c_bus_type; master->dev.type = &i3c_masterdev_type; master->dev.release = i3c_masterdev_release; @@ -3327,13 +3336,13 @@ int i3c_master_register(struct i3c_master_controller *master, master->dev.coherent_dma_mask = parent->coherent_dma_mask; master->dev.dma_parms = parent->dma_parms; - ret = i3c_bus_init(i3cbus, master->dev.of_node); + ret = i3c_bus_init(i3cbus, dev_fwnode(&master->dev)); if (ret) goto err_put_dev; dev_set_name(&master->dev, "i3c-%d", i3cbus->id); - ret = of_populate_i3c_bus(master); + ret = fwnode_populate_i3c_bus(master); if (ret) goto err_put_dev; diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 4d2a68793324..a16deb04b2e1 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -177,7 +177,8 @@ struct i3c_device_ibi_info { * @pid: I3C Provisioned ID exposed by the device. This is a unique identifier * that may be used to attach boardinfo to i3c_dev_desc when the device * does not have a static address - * @of_node: optional DT node in case the device has been described in the DT + * @fwnode: Firmware node (DT or ACPI) in case the device has been + * described in firmware * * This structure is used to attach board-level information to an I3C device. * Not all I3C devices connected on the bus will have a boardinfo. It's only @@ -189,7 +190,7 @@ struct i3c_dev_boardinfo { u8 init_dyn_addr; u8 static_addr; u64 pid; - struct device_node *of_node; + struct fwnode_handle *fwnode; }; /** -- cgit v1.2.3 From bbaf8733b84846897d2d3b997ce650dd2d2539a4 Mon Sep 17 00:00:00 2001 From: Akhil R Date: Tue, 28 Jul 2026 06:59:46 +0000 Subject: i3c: master: Add support for devices using SETAASA Add support for devices using SETAASA, such as SPD5118 and SPD5108 attached to DDR5 memory modules that do not support ENTDAA. Follow the guidelines proposed by the MIPI Discovery and Configuration Specification [1] for discovering such devices. SETAASA (Set All Addresses to Static Address) differs from standard I3C address assignment that uses ENTDAA or SETDASA to assign dynamic addresses. Devices using SETAASA assign their pre-defined static addresses as their dynamic addresses during DAA, and it is not mandatory for these devices to implement standard CCC commands like GETPID, GETDCR, or GETBCR. For such devices, it is generally recommended to issue SETHID (specified by JEDEC JESD300) as a prerequisite for SETAASA to stop HID bit flipping. [1] https://www.mipi.org/mipi-disco-for-i3c-download Signed-off-by: Akhil R Link: https://www.mipi.org/mipi-disco-for-i3c-download Link: https://patch.msgid.link/20260728065955.809445-5-akhilrajeev@nvidia.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 114 +++++++++++++++++++++++++++++++++++++++++++-- include/linux/i3c/ccc.h | 1 + include/linux/i3c/master.h | 15 ++++++ 3 files changed, 127 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 819235440445..16260269926e 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -5,6 +5,7 @@ * Author: Boris Brezillon */ +#include #include #include #include @@ -1163,6 +1164,51 @@ static int i3c_master_rstdaa_locked(struct i3c_master_controller *master, return ret; } +/** + * i3c_master_setaasa_locked() - start a SETAASA procedure (Set All Addresses to Static Address) + * @master: I3C master object + * + * Send a SETAASA CCC command to set all attached I3C devices' dynamic addresses to + * their static address. + * + * This function must be called with the bus lock held in write mode. + * + * First, the SETHID CCC command is sent, followed by the SETAASA CCC. + * + * Return: 0 in case of success, a positive I3C error code if the error is + * one of the official Mx error codes, and a negative error code otherwise. + */ +static int i3c_master_setaasa_locked(struct i3c_master_controller *master) +{ + struct i3c_ccc_cmd_dest dest; + struct i3c_ccc_cmd cmd; + int ret; + + /* + * Send SETHID CCC command. Though it is a standard CCC command specified + * in JESD300-5, we are not defining a separate macro to be explicit that + * the value falls under the vendor specific range. + */ + i3c_ccc_cmd_dest_init(&dest, I3C_BROADCAST_ADDR, 0); + i3c_ccc_cmd_init(&cmd, false, I3C_CCC_VENDOR(0, true), &dest, 1); + ret = i3c_master_send_ccc_cmd_locked(master, &cmd); + i3c_ccc_cmd_dest_cleanup(&dest); + if (ret && cmd.err == I3C_ERROR_M2) + ret = 0; + if (ret) + return ret; + + /* Send SETAASA CCC command */ + i3c_ccc_cmd_dest_init(&dest, I3C_BROADCAST_ADDR, 0); + i3c_ccc_cmd_init(&cmd, false, I3C_CCC_SETAASA, &dest, 1); + ret = i3c_master_send_ccc_cmd_locked(master, &cmd); + i3c_ccc_cmd_dest_cleanup(&dest); + if (ret && cmd.err == I3C_ERROR_M2) + ret = 0; + + return ret; +} + /** * i3c_master_entdaa_locked() - start a DAA (Dynamic Address Assignment) * procedure @@ -1939,8 +1985,10 @@ static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master, int ret; i3cdev = i3c_master_alloc_i3c_dev(master, &info); - if (IS_ERR(i3cdev)) - return -ENOMEM; + if (IS_ERR(i3cdev)) { + ret = -ENOMEM; + goto err_reserve_addr; + } i3cdev->boardinfo = boardinfo; @@ -1948,6 +1996,22 @@ static int i3c_master_early_i3c_dev_add(struct i3c_master_controller *master, if (ret) goto err_free_dev; + /* + * For devices using SETAASA instead of ENTDAA, the address is statically + * assigned. Update the dynamic address to the provided static address. + * Reattach the I3C device after updating the dynamic address with the same + * static address. It is not mandatory for such devices to implement CCC + * commands like GETPID, GETDCR etc. Hence, we can return after reattaching. + */ + if (i3cdev->boardinfo->static_addr_method & I3C_ADDR_METHOD_SETAASA) { + i3cdev->info.dyn_addr = i3cdev->boardinfo->static_addr; + ret = i3c_master_reattach_i3c_dev_locked(i3cdev, 0); + if (ret) + goto err_detach_dev; + + return 0; + } + ret = i3c_master_setdasa_locked(master, i3cdev->info.static_addr, i3cdev->boardinfo->init_dyn_addr); if (ret) @@ -1970,6 +2034,16 @@ err_detach_dev: i3c_master_detach_i3c_dev(i3cdev); err_free_dev: i3c_master_free_i3c_dev(i3cdev); +err_reserve_addr: + /* + * A target using SETAASA may still get the static address on the + * SETAASA broadcast even if attach fails here. Keep the address + * reserved so that it is not assigned to another device during DAA. + */ + if (boardinfo->static_addr_method & I3C_ADDR_METHOD_SETAASA) + i3c_bus_set_addr_slot_status(&master->bus, + boardinfo->static_addr, + I3C_ADDR_SLOT_RSVD); return ret; } @@ -2344,6 +2418,19 @@ static int i3c_master_bus_init(struct i3c_master_controller *master) i3c_master_early_i3c_dev_add(master, i3cboardinfo); } + /* + * SETAASA is a broadcast CCC. Issue it after SETDASA so that devices + * configured for SETDASA (or supporting both methods) are assigned + * first, matching MIPI DISCO guidance to prefer SETDASA when both are + * available. Targets that already have a dynamic address ignore the + * later SETAASA broadcast. + */ + if (master->addr_method & I3C_ADDR_METHOD_SETAASA) { + ret = i3c_master_setaasa_locked(master); + if (ret) + goto err_rstdaa; + } + ret = i3c_master_do_daa(master); if (ret) goto err_rstdaa; @@ -2795,7 +2882,7 @@ i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, struct i3c_dev_boardinfo *boardinfo; struct device *dev = &master->dev; enum i3c_addr_slot_status addrstatus; - u32 init_dyn_addr = 0; + u32 init_dyn_addr = 0, static_addr_method = 0; boardinfo = devm_kzalloc(dev, sizeof(*boardinfo), GFP_KERNEL); if (!boardinfo) @@ -2813,7 +2900,19 @@ i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, boardinfo->static_addr = reg[0]; + if (!fwnode_property_read_u32(fwnode, "mipi-i3c-static-method", &static_addr_method)) + boardinfo->static_addr_method = static_addr_method & + (I3C_ADDR_METHOD_SETDASA | I3C_ADDR_METHOD_SETAASA); + if (!fwnode_property_read_u32(fwnode, "assigned-address", &init_dyn_addr)) { + /* + * When a device advertises both SETDASA and SETAASA, an explicit + * dynamic address selects SETDASA (MIPI DISCO prefers it); drop + * SETAASA so it is not used for this device. + */ + if (boardinfo->static_addr_method & I3C_ADDR_METHOD_SETDASA) + boardinfo->static_addr_method &= ~I3C_ADDR_METHOD_SETAASA; + if (init_dyn_addr > I3C_MAX_ADDR) return -EINVAL; @@ -2823,6 +2922,14 @@ i3c_master_add_i3c_boardinfo(struct i3c_master_controller *master, return -EINVAL; } + if (boardinfo->static_addr_method & I3C_ADDR_METHOD_SETAASA) { + /* For SETAASA, static address is taken as the dynamic address. */ + init_dyn_addr = boardinfo->static_addr; + } + + /* Update the address methods required for device discovery */ + master->addr_method |= boardinfo->static_addr_method; + boardinfo->pid = ((u64)reg[1] << 32) | reg[2]; if ((boardinfo->pid & GENMASK_ULL(63, 48)) || @@ -3458,6 +3565,7 @@ int i3c_master_register(struct i3c_master_controller *master, master->dev.release = i3c_masterdev_release; master->ops = ops; master->secondary = secondary; + master->addr_method = I3C_ADDR_METHOD_SETDASA; INIT_LIST_HEAD(&master->boardinfo.i2c); INIT_LIST_HEAD(&master->boardinfo.i3c); diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h index 7ad677baf761..c6947dcb0f57 100644 --- a/include/linux/i3c/ccc.h +++ b/include/linux/i3c/ccc.h @@ -34,6 +34,7 @@ #define I3C_CCC_DEFSLVS I3C_CCC_ID(0x8, true) #define I3C_CCC_ENTTM I3C_CCC_ID(0xb, true) #define I3C_CCC_ENTHDR(x) I3C_CCC_ID(0x20 + (x), true) +#define I3C_CCC_SETAASA I3C_CCC_ID(0x29, true) /* Unicast-only commands */ #define I3C_CCC_SETDASA I3C_CCC_ID(0x7, false) diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index a16deb04b2e1..2dc139a217bf 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -174,6 +174,14 @@ struct i3c_device_ibi_info { * assigned a dynamic address by the master. Will be used during * bus initialization to assign it a specific dynamic address * before starting DAA (Dynamic Address Assignment) + * @static_addr_method: Bitmap describing which methods of Dynamic Address + * Assignment from a Static Address are supported by this I3C Target. + * A value of 1 in a bit position indicates that the I3C target + * supports that method, and a value of 0 indicates that the I3C + * target does not support that method. + * Bit 0: SETDASA + * Bit 1: SETAASA + * All other bits are reserved. * @pid: I3C Provisioned ID exposed by the device. This is a unique identifier * that may be used to attach boardinfo to i3c_dev_desc when the device * does not have a static address @@ -189,6 +197,7 @@ struct i3c_dev_boardinfo { struct list_head node; u8 init_dyn_addr; u8 static_addr; + u8 static_addr_method; u64 pid; struct fwnode_handle *fwnode; }; @@ -517,6 +526,11 @@ struct i3c_master_controller_ops { * @boardinfo.i2c: list of I2C boardinfo objects * @boardinfo: board-level information attached to devices connected on the bus * @bus: I3C bus exposed by this master + * @addr_method: Bitmap describing which methods of Address Assignment required + * to be run for discovering all the devices on the bus. + * Bit 0: SETDASA + * Bit 1: SETAASA + * All other bits are reserved. * @wq: freezable workqueue which can be used by master * drivers if they need to postpone operations that need to take place * in a thread context. Typical examples are Hot Join processing which @@ -552,6 +566,7 @@ struct i3c_master_controller { struct list_head i2c; } boardinfo; struct i3c_bus bus; + u8 addr_method; struct workqueue_struct *wq; struct work_struct hj_work; struct work_struct reg_work; -- cgit v1.2.3 From 456f832e5fc26fbfd3b8200fd4553eee520cc377 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:25 +0300 Subject: i3c: master: Fix recursive locking during device registration i3c_master_register_new_i3c_devs() registers newly discovered devices while holding i3c_bus_normaluse_lock(), a down_read(). device_register() can immediately probe the device, and probe callbacks typically invoke I3C helpers that take i3c_bus_normaluse_lock() again, leading to a recursive acquisition of the same rwsem. rwsems do not support recursive read locking and can deadlock when a writer is waiting. See the "Recursive read locks" section of Documentation/locking/lockdep-design.rst. For example, with Intel LPSS I3C, LOCKDEP generates a WARNING like: # echo intel-lpss-i3c.0 > /sys/bus/platform/drivers/mipi-i3c-hci/unbind # echo intel-lpss-i3c.0 > /sys/bus/platform/drivers/mipi-i3c-hci/bind WARNING: possible recursive locking detected kworker/5:1/94 is trying to acquire lock: ffff88811c810d78 (&i3cbus->lock){++++}-{4:4}, at: i3c_device_match_id+0x45/0x370 but task is already holding lock: ffff88811c810d78 (&i3cbus->lock){++++}-{4:4}, at: i3c_master_reg_work_fn+0x21/0x5f0 Fix this by separating device creation from device registration. Populate desc->dev under the maintenance lock, collect the devices that still need registration into a local list, then release the lock before calling device_register(). Finally retake the lock and clean up any devices that failed to register. Use the maintenance lock rather than the normal-use lock while adding device objects. A write-side maintenance lock prevents readers from observing a partially initialized desc->dev during initial device population, or desc->dev disappearing if registration fails. The local list requires a list node, so add a list node member to struct i3c_device. Fixes: 3a379bbcea0a ("i3c: Add core I3C infrastructure") Cc: stable@vger.kernel.org Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-2-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 45 +++++++++++++++++++++++++++++++++------------ include/linux/i3c/master.h | 3 +++ 2 files changed, 36 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index f485b98805cf..d2fb1a110521 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2069,12 +2069,21 @@ err_reserve_addr: static void i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) { + struct i3c_device *i3cdev, *tmp; struct i3c_dev_desc *desc; + LIST_HEAD(i3c_unreg_devs); int ret; if (!master->init_done) return; + i3c_bus_maintenance_lock(&master->bus); + + if (master->shutting_down) { + i3c_bus_maintenance_unlock(&master->bus); + return; + } + i3c_bus_for_each_i3cdev(&master->bus, desc) { if (desc->dev || !desc->info.dyn_addr || desc == master->this) continue; @@ -2104,25 +2113,37 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) if (desc->boardinfo) device_set_node(&desc->dev->dev, desc->boardinfo->fwnode); - ret = device_register(&desc->dev->dev); - 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; - } + list_add_tail(&desc->dev->node, &i3c_unreg_devs); + } + + i3c_bus_maintenance_unlock(&master->bus); + + list_for_each_entry_safe(i3cdev, tmp, &i3c_unreg_devs, node) { + ret = device_register(&i3cdev->dev); + if (ret) + dev_err(&master->dev, "Failed to add I3C device (err = %d)\n", ret); + else + list_del_init(&i3cdev->node); + } + + i3c_bus_maintenance_lock(&master->bus); + + list_for_each_entry_safe(i3cdev, tmp, &i3c_unreg_devs, node) { + list_del(&i3cdev->node); + desc = i3cdev->desc; + i3cdev->desc = NULL; + put_device(&i3cdev->dev); + desc->dev = NULL; } + + i3c_bus_maintenance_unlock(&master->bus); } static void i3c_master_reg_work_fn(struct work_struct *work) { struct i3c_master_controller *master = container_of(work, typeof(*master), reg_work); - i3c_bus_normaluse_lock(&master->bus); - if (!master->shutting_down) - i3c_master_register_new_i3c_devs(master); - i3c_bus_normaluse_unlock(&master->bus); + i3c_master_register_new_i3c_devs(master); } /** diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 2dc139a217bf..26535beb1e77 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -238,6 +238,8 @@ struct i3c_dev_desc { * every time the I3C device is rediscovered with a different dynamic * address assigned * @bus: I3C bus this device is attached to + * @node: unregistered device list node, only for use by + * i3c_master_register_new_i3c_devs(), it is not protected by a lock * * I3C device object exposed to I3C device drivers. The takes care of linking * this object to the relevant &struct_i3c_dev_desc one. @@ -248,6 +250,7 @@ struct i3c_device { struct device dev; struct i3c_dev_desc *desc; struct i3c_bus *bus; + struct list_head node; }; /* -- cgit v1.2.3 From 9fd18a865591d981c0081a3e663a65dbe4a64eb9 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:32 +0300 Subject: i3c: master: Support IBI-based wakeup capability An I3C controller acts as a bus controller for one or more I3C devices. If the controller can wake the system in response to an In-Band Interrupt (IBI), then any device on that bus that is capable of generating IBIs can potentially be used as a wakeup source. Add an ibi_wakeup flag to struct i3c_master_controller so controller drivers can advertise support for IBI-based wakeup. If set, mark IBI-capable I3C devices as wakeup capable when they are registered, allowing wakeup management through the standard device wakeup framework. Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Acked-by: Mukesh Savaliya Link: https://patch.msgid.link/20260807145638.168865-9-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 4 ++++ include/linux/i3c/master.h | 2 ++ 2 files changed, 6 insertions(+) (limited to 'include') diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 83e04a2c202a..2616354dbe54 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2110,6 +2110,10 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master) if (desc->boardinfo) device_set_node(&desc->dev->dev, desc->boardinfo->fwnode); + /* If the device has IBI capability, set as wakeup capable */ + if (master->ibi_wakeup && (desc->info.bcr & I3C_BCR_IBI_REQ_CAP)) + device_set_wakeup_capable(&desc->dev->dev, true); + list_add_tail(&desc->dev->node, &i3c_unreg_devs); } diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 26535beb1e77..9d675d01522c 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -524,6 +524,7 @@ struct i3c_master_controller_ops { * @hotjoin: true if the master support hotjoin * @rpm_allowed: true if Runtime PM allowed * @rpm_ibi_allowed: true if IBI and Hot-Join allowed while runtime suspended + * @ibi_wakeup: IBI can wakeup the system * @shutting_down: set to true when master begins shutdown or unregister * @boardinfo.i3c: list of I3C boardinfo objects * @boardinfo.i2c: list of I2C boardinfo objects @@ -563,6 +564,7 @@ struct i3c_master_controller { unsigned int hotjoin: 1; unsigned int rpm_allowed: 1; unsigned int rpm_ibi_allowed: 1; + unsigned int ibi_wakeup: 1; bool shutting_down; struct { struct list_head i3c; -- cgit v1.2.3 From 60ff731f06909f9b54af27d70f00a41bd84c6246 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 7 Aug 2026 17:56:34 +0300 Subject: i3c: master: Add helper to query bus wakeup requirements Add i3c_master_has_wakeup_enabled_devs(), which iterates over the devices on an I3C bus and reports whether any of them are enabled for system wakeup and have IBI enabled. Controller drivers can use this helper to determine whether wakeup support must remain available while the system is suspended. Acked-by : Mukesh Savaliya Signed-off-by: Adrian Hunter Reviewed-by: Frank Li Link: https://patch.msgid.link/20260807145638.168865-11-adrian.hunter@intel.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master.c | 35 +++++++++++++++++++++++++++++++++++ include/linux/i3c/master.h | 1 + 2 files changed, 36 insertions(+) (limited to 'include') diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 6c5341491944..afcd7a21a3e6 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -2147,6 +2147,41 @@ static void i3c_master_reg_work_fn(struct work_struct *work) i3c_master_register_new_i3c_devs(master); } +/** + * i3c_master_has_wakeup_enabled_devs() - check if any device can wake the system + * @master: I3C master controller + * + * Iterate over devices on the bus and return true if any device has + * system wakeup enabled and IBI enabled. + * + * Whether a device is enabled for system wakeup is user space policy, + * settable at any time through the device's power/wakeup sysfs attribute, + * so the answer is only stable once user space is frozen. Call this from + * a system suspend callback. + * + * Return: true if any device may wake the system via IBI, false otherwise. + */ +bool i3c_master_has_wakeup_enabled_devs(struct i3c_master_controller *master) +{ + struct i3c_dev_desc *desc; + bool wakeup = false; + + i3c_bus_normaluse_lock(&master->bus); + i3c_bus_for_each_i3cdev(&master->bus, desc) { + if (!desc->dev || desc == master->this || !device_may_wakeup(&desc->dev->dev)) + continue; + guard(mutex)(&desc->ibi_lock); + if (desc->ibi && desc->ibi->enabled) { + wakeup = true; + break; + } + } + i3c_bus_normaluse_unlock(&master->bus); + + return wakeup; +} +EXPORT_SYMBOL_GPL(i3c_master_has_wakeup_enabled_devs); + /** * i3c_master_dma_map_single() - Map buffer for single DMA transfer * @dev: device object of a device doing DMA diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 9d675d01522c..82d9886e7f12 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -765,6 +765,7 @@ void i3c_generic_ibi_recycle_slot(struct i3c_generic_ibi_pool *pool, struct i3c_ibi_slot *slot); void i3c_master_queue_ibi(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot); +bool i3c_master_has_wakeup_enabled_devs(struct i3c_master_controller *master); struct i3c_ibi_slot *i3c_master_get_free_ibi_slot(struct i3c_dev_desc *dev); -- cgit v1.2.3 From 7315aad228c29508ec77ab64a7c75377981e8c4f Mon Sep 17 00:00:00 2001 From: Tze Yee Ng Date: Fri, 31 Jul 2026 01:01:39 -0700 Subject: i3c: master: dw-i3c-master: fix OD timing for first broadcast Implement ->set_speed() so the I3C core can switch open-drain timing for the first broadcast address per spec: I3C_OPEN_DRAIN_SLOW_SPEED programs tHIGH_INIT (200 ns) before RSTDAA, and I3C_OPEN_DRAIN_NORMAL_SPEED restores normal OD timing afterward. Cache the normal OD register value during bus init and use a separate od_hcnt for the slow path so SDR extended timing remains derived from the normal PP hcnt. For AMD_I3C_OD_PP_TIMING, cache AMD_I3C_OD_TIMING as the normal OD baseline and stop rewriting OD timing in send_ccc_cmd()/runtime resume so I3C_OPEN_DRAIN_SLOW_SPEED is preserved through RSTDAA. Use PM_RUNTIME_ACQUIRE_AUTOSUSPEND() in set_speed(). Compute od_hcnt with DIV_ROUND_UP_ULL() for 32-bit safety and clamp it to U8_MAX to match the 8-bit I3C_OD_HCNT field. Fixes I2C devices with spike filters not being detected on mixed buses. Signed-off-by: Tze Yee Ng Reviewed-by: Frank Li Link: https://patch.msgid.link/d789219ca0418898a1ef2bf9295b4f96ca7b4209.1785484707.git.tze.yee.ng@altera.com Signed-off-by: Alexandre Belloni --- drivers/i3c/master/dw-i3c-master.c | 82 ++++++++++++++++++++++++++++++-------- drivers/i3c/master/dw-i3c-master.h | 1 + include/linux/i3c/master.h | 1 + 3 files changed, 67 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 3816a50a52cc..405089f181c4 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -572,6 +572,13 @@ static unsigned long dw_i3c_master_get_core_rate(struct dw_i3c_master *master) return core_rate_prop; } +static void amd_configure_od_pp_quirk(struct dw_i3c_master *master) +{ + master->i3c_od_timing = AMD_I3C_OD_TIMING; + master->i3c_od_timing_normal = AMD_I3C_OD_TIMING; + master->i3c_pp_timing = AMD_I3C_PP_TIMING; +} + static int dw_i3c_clk_cfg(struct dw_i3c_master *master) { unsigned long core_rate, core_period; @@ -610,6 +617,18 @@ static int dw_i3c_clk_cfg(struct dw_i3c_master *master) scl_timing = SCL_I3C_TIMING_HCNT(hcnt) | SCL_I3C_TIMING_LCNT(lcnt); writel(scl_timing, master->regs + SCL_I3C_OD_TIMING); master->i3c_od_timing = scl_timing; + master->i3c_od_timing_normal = scl_timing; + + /* + * AMD legacy platforms need fixed OD/PP timings. Cache them as the + * normal OD baseline so set_speed(NORMAL) restores AMD values, and + * set_speed(SLOW) can stretch HCNT while keeping the AMD LCNT. + */ + if (master->quirks & AMD_I3C_OD_PP_TIMING) { + amd_configure_od_pp_quirk(master); + writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING); + writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING); + } lcnt = DIV_ROUND_UP(core_rate, I3C_BUS_SDR1_SCL_RATE) - hcnt; scl_timing = SCL_EXT_LCNT_1(lcnt); @@ -825,12 +844,6 @@ static int dw_i3c_ccc_get(struct dw_i3c_master *master, struct i3c_ccc_cmd *ccc) return ret; } -static void amd_configure_od_pp_quirk(struct dw_i3c_master *master) -{ - master->i3c_od_timing = AMD_I3C_OD_TIMING; - master->i3c_pp_timing = AMD_I3C_PP_TIMING; -} - static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, struct i3c_ccc_cmd *ccc) { @@ -840,13 +853,6 @@ static int dw_i3c_master_send_ccc_cmd(struct i3c_master_controller *m, if (ccc->id == I3C_CCC_ENTDAA) return -EINVAL; - /* AMD platform specific OD and PP timings */ - if (master->quirks & AMD_I3C_OD_PP_TIMING) { - amd_configure_od_pp_quirk(master); - writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING); - writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING); - } - ret = pm_runtime_resume_and_get(master->dev); if (ret < 0) { dev_err(master->dev, @@ -1531,6 +1537,50 @@ static irqreturn_t dw_i3c_master_irq_handler(int irq, void *dev_id) return IRQ_HANDLED; } +static int dw_i3c_master_set_speed(struct i3c_master_controller *m, + enum i3c_open_drain_speed speed) +{ + struct dw_i3c_master *master = to_dw_i3c_master(m); + unsigned long core_rate; + u32 scl_timing, od_hcnt; + u8 lcnt; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(master->dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + switch (speed) { + case I3C_OPEN_DRAIN_SLOW_SPEED: + core_rate = dw_i3c_master_get_core_rate(master); + if (!core_rate) + return -EINVAL; + + lcnt = SCL_I3C_TIMING_LCNT(master->i3c_od_timing_normal); + od_hcnt = DIV_ROUND_UP_ULL((u64)I3C_BUS_THIGH_INIT_OD_MIN_NS * + core_rate, NSEC_PER_SEC) - 1; + if (od_hcnt < SCL_I3C_TIMING_CNT_MIN) + od_hcnt = SCL_I3C_TIMING_CNT_MIN; + else if (od_hcnt > U8_MAX) + od_hcnt = U8_MAX; + scl_timing = SCL_I3C_TIMING_HCNT(od_hcnt) | + SCL_I3C_TIMING_LCNT(lcnt); + writel(scl_timing, master->regs + SCL_I3C_OD_TIMING); + master->i3c_od_timing = scl_timing; + break; + + case I3C_OPEN_DRAIN_NORMAL_SPEED: + writel(master->i3c_od_timing_normal, + master->regs + SCL_I3C_OD_TIMING); + master->i3c_od_timing = master->i3c_od_timing_normal; + break; + + default: + return -EINVAL; + } + + return 0; +} + static int dw_i3c_master_set_dev_nack_retry(struct i3c_master_controller *m, unsigned int dev_nack_retry_cnt) { @@ -1585,6 +1635,7 @@ static const struct i3c_master_controller_ops dw_mipi_i3c_ops = { .recycle_ibi_slot = dw_i3c_master_recycle_ibi_slot, .enable_hotjoin = dw_i3c_master_enable_hotjoin, .disable_hotjoin = dw_i3c_master_disable_hotjoin, + .set_speed = dw_i3c_master_set_speed, .set_dev_nack_retry = dw_i3c_master_set_dev_nack_retry, }; @@ -1780,10 +1831,7 @@ static void dw_i3c_master_restore_addrs(struct dw_i3c_master *master) static void dw_i3c_master_restore_timing_regs(struct dw_i3c_master *master) { - /* AMD platform specific OD and PP timings */ - if (master->quirks & AMD_I3C_OD_PP_TIMING) - amd_configure_od_pp_quirk(master); - + /* Preserve cached OD timing; it may be the SLOW setting from set_speed(). */ writel(master->i3c_pp_timing, master->regs + SCL_I3C_PP_TIMING); writel(master->bus_free_timing, master->regs + BUS_FREE_TIMING); writel(master->i3c_od_timing, master->regs + SCL_I3C_OD_TIMING); diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c-master.h index 28e9348f2153..17ad817d1f8e 100644 --- a/drivers/i3c/master/dw-i3c-master.h +++ b/drivers/i3c/master/dw-i3c-master.h @@ -46,6 +46,7 @@ struct dw_i3c_master { u32 dev_addr; u32 i3c_pp_timing; u32 i3c_od_timing; + u32 i3c_od_timing_normal; u32 ext_lcnt_timing; u32 bus_free_timing; u32 i2c_fm_timing; diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 82d9886e7f12..f7ceec2b4477 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -272,6 +272,7 @@ struct i3c_device { #define I3C_BUS_THIGH_MIXED_MAX_NS 41 #define I3C_BUS_TIDLE_MIN_NS 200000 #define I3C_BUS_TLOW_OD_MIN_NS 200 +#define I3C_BUS_THIGH_INIT_OD_MIN_NS 200 /** * enum i3c_bus_mode - I3C bus mode -- cgit v1.2.3