summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
commit4352b8aee98005853aa63f57d6377282de17a33f (patch)
tree27614d7a9ef05c32d089004e104364ce42324837
parent570f7e331f5febb30f1384817463c7e42b65ca7d (diff)
parentcab40cfc9e116acd4d60f95b4b1264cab78f3803 (diff)
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux
Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ...
-rw-r--r--Documentation/devicetree/bindings/i3c/cdns,i3c-master.yaml1
-rw-r--r--Documentation/devicetree/bindings/i3c/i3c.yaml36
-rw-r--r--Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml3
-rw-r--r--Documentation/devicetree/bindings/i3c/xlnx,axi-i3c-1.0.yaml58
-rw-r--r--MAINTAINERS8
-rw-r--r--drivers/hwmon/Kconfig9
-rw-r--r--drivers/hwmon/spd5118.c119
-rw-r--r--drivers/i3c/device.c13
-rw-r--r--drivers/i3c/internals.h5
-rw-r--r--drivers/i3c/master.c628
-rw-r--r--drivers/i3c/master/Kconfig15
-rw-r--r--drivers/i3c/master/Makefile1
-rw-r--r--drivers/i3c/master/adi-i3c-master.c19
-rw-r--r--drivers/i3c/master/amd-i3c-master.c1124
-rw-r--r--drivers/i3c/master/dw-i3c-master.c188
-rw-r--r--drivers/i3c/master/dw-i3c-master.h1
-rw-r--r--drivers/i3c/master/i3c-master-cdns.c3
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/core.c30
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/dma.c15
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/hci.h7
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c32
-rw-r--r--drivers/i3c/master/mipi-i3c-hci/pio.c74
-rw-r--r--drivers/i3c/master/renesas-i3c.c388
-rw-r--r--drivers/i3c/master/svc-i3c-master.c22
-rw-r--r--include/dt-bindings/i3c/i3c.h4
-rw-r--r--include/linux/i3c/ccc.h12
-rw-r--r--include/linux/i3c/master.h27
27 files changed, 2467 insertions, 375 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/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/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml b/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml
index e803457d3f55..dc7ee38001d6 100644
--- a/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml
+++ b/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml
@@ -38,6 +38,9 @@ properties:
interrupts:
maxItems: 1
+ resets:
+ maxItems: 1
+
power-domains:
maxItems: 1
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 cae7da554b5e..e3f4e0fcdb3a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1043,6 +1043,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/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 81a9a1d40eec..942282614c48 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2386,12 +2386,15 @@ config SENSORS_INA3221
config SENSORS_SPD5118
tristate "SPD5118 Compliant Temperature Sensors"
- depends on I2C
+ depends on I3C_OR_I2C
select REGMAP_I2C
+ select REGMAP_I3C if I3C
help
If you say yes here you get support for SPD5118 (JEDEC JESD300)
- compliant temperature sensors. Such sensors are found on DDR5 memory
- modules.
+ compliant temperature sensors using I2C or I3C bus interface.
+ Such sensors are found on DDR5 memory modules.
+
+ This driver supports both I2C and I3C interfaces.
This driver can also be built as a module. If so, the module
will be called spd5118.
diff --git a/drivers/hwmon/spd5118.c b/drivers/hwmon/spd5118.c
index cc40661cab21..9724cf70b61d 100644
--- a/drivers/hwmon/spd5118.c
+++ b/drivers/hwmon/spd5118.c
@@ -18,6 +18,7 @@
#include <linux/bits.h>
#include <linux/err.h>
#include <linux/i2c.h>
+#include <linux/i3c/device.h>
#include <linux/hwmon.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -66,9 +67,6 @@ static const unsigned short normal_i2c[] = {
#define SPD5118_EEPROM_BASE 0x80
#define SPD5118_EEPROM_SIZE (SPD5118_PAGE_SIZE * SPD5118_NUM_PAGES)
-#define PAGE_ADDR0(page) (((page) & BIT(0)) << 6)
-#define PAGE_ADDR1_4(page) (((page) & GENMASK(4, 1)) >> 1)
-
/* Temperature unit in millicelsius */
#define SPD5118_TEMP_UNIT (MILLIDEGREE_PER_DEGREE / 4)
/* Representable temperature range in millicelsius */
@@ -78,7 +76,6 @@ static const unsigned short normal_i2c[] = {
struct spd5118_data {
struct regmap *regmap;
struct mutex nvmem_lock;
- bool is_16bit;
};
/* hwmon */
@@ -348,12 +345,7 @@ static ssize_t spd5118_nvmem_read_page(struct spd5118_data *data, char *buf,
if (offset + count > SPD5118_PAGE_SIZE)
count = SPD5118_PAGE_SIZE - offset;
- if (data->is_16bit) {
- addr = SPD5118_EEPROM_BASE | PAGE_ADDR0(page) |
- (PAGE_ADDR1_4(page) << 8);
- } else {
- addr = page * 0x100 + SPD5118_EEPROM_BASE;
- }
+ addr = page * 0x100 + SPD5118_EEPROM_BASE;
err = regmap_bulk_read(regmap, addr + offset, buf, count);
if (err)
return err;
@@ -473,10 +465,22 @@ static const struct regmap_config spd5118_regmap8_config = {
.num_ranges = ARRAY_SIZE(spd5118_i2c_regmap_range_cfg),
};
-static const struct regmap_config spd5118_regmap16_config = {
+/*
+ * SPD5118 2-byte register address format (JESD300-5, Tables 7 & 20):
+ * Byte 1 (on wire first): MemReg | BlkAddr[0] | Address[5:0]
+ * Byte 2 (on wire second): 0000 | BlkAddr[4:1]
+ *
+ * The address byte (with MemReg and lower address bits) must be sent first,
+ * followed by the upper block address byte. With regmap 16-bit register
+ * format, this maps to little-endian: the low byte of the 16-bit value is
+ * transmitted first. No range config is needed since I3C does not use MR11
+ * page switching.
+ */
+static const struct regmap_config spd5118_regmap_i3c_config = {
.reg_bits = 16,
.val_bits = 8,
.max_register = 0x7ff,
+ .reg_format_endian = REGMAP_ENDIAN_LITTLE,
.writeable_reg = spd5118_writeable_reg,
.volatile_reg = spd5118_volatile_reg,
.cache_type = REGCACHE_MAPLE,
@@ -519,8 +523,7 @@ static int spd5118_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(spd5118_pm_ops, spd5118_suspend, spd5118_resume);
-static int spd5118_common_probe(struct device *dev, struct regmap *regmap,
- bool is_16bit)
+static int spd5118_common_probe(struct device *dev, struct regmap *regmap)
{
unsigned int capability, revision, vendor, bank;
struct spd5118_data *data;
@@ -537,8 +540,6 @@ static int spd5118_common_probe(struct device *dev, struct regmap *regmap,
if (!(capability & SPD5118_CAP_TS_SUPPORT))
return -ENODEV;
- data->is_16bit = is_16bit;
-
err = regmap_read(regmap, SPD5118_REG_REVISION, &revision);
if (err)
return err;
@@ -680,69 +681,21 @@ static int spd5118_i2c_init(struct i2c_client *client)
return 0;
}
-/*
- * 16-bit addressing note:
- *
- * If I2C_FUNC_I2C is not supported by an I2C adapter driver, regmap uses
- * SMBus operations as alternative. To simulate a read operation with a 16-bit
- * address, it writes the address using i2c_smbus_write_byte_data(), followed
- * by one or more calls to i2c_smbus_read_byte() to read the data.
- * Per spd5118 standard, a read operation after writing the address must start
- * with <Sr> (Repeat Start). However, a SMBus read byte operation starts with
- * <S> (Start). This resets the register address in the spd5118 chip. As result,
- * i2c_smbus_read_byte() always returns data from register address 0x00.
- *
- * A working alternative to access chips with 16-bit register addresses in the
- * absence of I2C_FUNC_I2C support is not known.
- *
- * For this reason, 16-bit addressing can only be supported with I2C if the
- * adapter supports I2C_FUNC_I2C.
- *
- * For I2C, the addressing mode selected by the BIOS must not be changed.
- * Experiments show that at least some PC BIOS versions will not change the
- * addressing mode on a soft reboot and end up in setup, claiming that some
- * configuration change happened. This will happen again after a power cycle,
- * which does reset the addressing mode. To prevent this from happening,
- * detect if 16-bit addressing is enabled and always use the currently
- * configured addressing mode.
- */
-
static int spd5118_i2c_probe(struct i2c_client *client)
{
- const struct regmap_config *config;
struct device *dev = &client->dev;
struct regmap *regmap;
- int err, mode;
- bool is_16bit;
+ int err;
err = spd5118_i2c_init(client);
if (err)
return err;
- mode = i2c_smbus_read_byte_data(client, SPD5118_REG_I2C_LEGACY_MODE);
- if (mode < 0)
- return mode;
-
- is_16bit = mode & SPD5118_LEGACY_MODE_ADDR;
- if (is_16bit) {
- /*
- * See 16-bit addressing note above explaining why it is
- * necessary to check for I2C_FUNC_I2C support here.
- */
- if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
- dev_err(dev, "Adapter does not support 16-bit register addresses\n");
- return -ENODEV;
- }
- config = &spd5118_regmap16_config;
- } else {
- config = &spd5118_regmap8_config;
- }
-
- regmap = devm_regmap_init_i2c(client, config);
+ regmap = devm_regmap_init_i2c(client, &spd5118_regmap8_config);
if (IS_ERR(regmap))
return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n");
- return spd5118_common_probe(dev, regmap, is_16bit);
+ return spd5118_common_probe(dev, regmap);
}
static const struct i2c_device_id spd5118_i2c_id[] = {
@@ -770,7 +723,39 @@ static struct i2c_driver spd5118_i2c_driver = {
.address_list = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? normal_i2c : NULL,
};
-module_i2c_driver(spd5118_i2c_driver);
+/* I3C */
+
+static int spd5118_i3c_probe(struct i3c_device *i3cdev)
+{
+ struct device *dev = i3cdev_to_dev(i3cdev);
+ struct regmap *regmap;
+ u8 regval[2];
+ int err;
+
+ regmap = devm_regmap_init_i3c(i3cdev, &spd5118_regmap_i3c_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "regmap init failed\n");
+
+ err = regmap_bulk_read(regmap, SPD5118_REG_TYPE, regval, 2);
+ if (err)
+ return dev_err_probe(dev, err, "failed to read device type\n");
+
+ if (regval[0] != 0x51 || regval[1] != 0x18)
+ return -ENODEV;
+
+ return spd5118_common_probe(dev, regmap);
+}
+
+static struct i3c_driver spd5118_i3c_driver = {
+ .driver = {
+ .name = "spd5118_i3c",
+ .of_match_table = spd5118_of_ids,
+ .pm = pm_sleep_ptr(&spd5118_pm_ops),
+ },
+ .probe = spd5118_i3c_probe,
+};
+
+module_i3c_i2c_driver(spd5118_i3c_driver, &spd5118_i2c_driver);
MODULE_AUTHOR("René Rebe <rene@exactcode.de>");
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c
index 101eaa77de68..f1ba363b22a1 100644
--- a/drivers/i3c/device.c
+++ b/drivers/i3c/device.c
@@ -101,8 +101,7 @@ void i3c_device_get_info(const struct i3c_device *dev,
return;
i3c_bus_normaluse_lock(dev->bus);
- if (dev->desc)
- *info = dev->desc->info;
+ *info = dev->desc->info;
i3c_bus_normaluse_unlock(dev->bus);
}
EXPORT_SYMBOL_GPL(i3c_device_get_info);
@@ -205,12 +204,14 @@ int i3c_device_request_ibi(struct i3c_device *dev,
return ret;
i3c_bus_normaluse_lock(dev->bus);
- if (dev->desc) {
+ if (!dev->desc) {
+ ret = -ENOENT;
+ } else if (!(dev->desc->info.bcr & I3C_BCR_IBI_REQ_CAP)) {
+ ret = -EOPNOTSUPP;
+ } else {
mutex_lock(&dev->desc->ibi_lock);
ret = i3c_dev_request_ibi_locked(dev->desc, req);
mutex_unlock(&dev->desc->ibi_lock);
- } else {
- ret = -ENOENT;
}
i3c_bus_normaluse_unlock(dev->bus);
@@ -309,7 +310,7 @@ EXPORT_SYMBOL_GPL(i3c_device_match_id);
*/
u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev)
{
- return i3c_dev_get_master(dev->desc)->this->info.hdr_cap | BIT(I3C_SDR);
+ return i3c_bus_to_i3c_master(dev->bus)->this->info.hdr_cap | BIT(I3C_SDR);
}
EXPORT_SYMBOL_GPL(i3c_device_get_supported_xfer_mode);
diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
index 0f1f3f766623..86a36b951e0d 100644
--- a/drivers/i3c/internals.h
+++ b/drivers/i3c/internals.h
@@ -72,4 +72,9 @@ static inline void i3c_readl_fifo(const void __iomem *addr, void *buf,
}
}
+static inline struct i3c_master_controller *i3c_bus_to_i3c_master(struct i3c_bus *i3cbus)
+{
+ return container_of(i3cbus, struct i3c_master_controller, bus);
+}
+
#endif /* I3C_INTERNAL_H */
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index f1be38a640ca..afcd7a21a3e6 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -5,6 +5,8 @@
* Author: Boris Brezillon <boris.brezillon@bootlin.com>
*/
+#include <dt-bindings/i3c/i3c.h>
+#include <linux/acpi.h>
#include <linux/atomic.h>
#include <linux/bitmap.h>
#include <linux/bug.h>
@@ -13,10 +15,13 @@
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/export.h>
+#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/pm_runtime.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
@@ -97,12 +102,6 @@ void i3c_bus_normaluse_unlock(struct i3c_bus *bus)
up_read(&bus->lock);
}
-static struct i3c_master_controller *
-i3c_bus_to_i3c_master(struct i3c_bus *i3cbus)
-{
- return container_of(i3cbus, struct i3c_master_controller, bus);
-}
-
static struct i3c_master_controller *dev_to_i3cmaster(struct device *dev)
{
return container_of(dev, struct i3c_master_controller, dev);
@@ -317,8 +316,7 @@ static int i3c_device_uevent(const struct device *dev, struct kobj_uevent_env *e
struct i3c_device_info devinfo;
u16 manuf, part, ext;
- if (i3cdev->desc)
- devinfo = i3cdev->desc->info;
+ i3c_device_get_info(i3cdev, &devinfo);
manuf = I3C_PID_MANUF_ID(devinfo.pid);
part = I3C_PID_PART_ID(devinfo.pid);
ext = I3C_PID_EXTRA_INFO(devinfo.pid);
@@ -341,15 +339,34 @@ static int i3c_device_match(struct device *dev, const struct device_driver *drv)
{
struct i3c_device *i3cdev;
const struct i3c_driver *i3cdrv;
+ u8 static_addr_method = 0;
if (dev->type != &i3c_device_type)
return 0;
i3cdev = dev_to_i3cdev(dev);
i3cdrv = drv_to_i3cdrv(drv);
- if (i3c_device_match_id(i3cdev, i3cdrv->id_table))
+
+ i3c_bus_normaluse_lock(i3cdev->bus);
+ if (i3cdev->desc->boardinfo)
+ static_addr_method = i3cdev->desc->boardinfo->static_addr_method;
+ i3c_bus_normaluse_unlock(i3cdev->bus);
+
+ /*
+ * SETAASA-based devices need not always have a matching ID since
+ * it is not mandatory for such devices to implement deviceinfo
+ * CCC commands. Allow them to register through DT or ACPI.
+ */
+ if (i3cdrv->id_table && i3c_device_match_id(i3cdev, i3cdrv->id_table))
return 1;
+ if (static_addr_method & I3C_ADDR_METHOD_SETAASA) {
+ if (of_driver_match_device(dev, drv))
+ return 1;
+ if (acpi_driver_match_device(dev, drv))
+ return 1;
+ }
+
return 0;
}
@@ -491,7 +508,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 +518,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) {
@@ -826,6 +843,11 @@ static struct attribute *i3c_masterdev_attrs[] = {
};
ATTRIBUTE_GROUPS(i3c_masterdev);
+static void i3c_master_free_i3c_dev(struct i3c_dev_desc *dev)
+{
+ kfree(dev);
+}
+
static void i3c_masterdev_release(struct device *dev)
{
struct i3c_master_controller *master = dev_to_i3cmaster(dev);
@@ -837,7 +859,9 @@ 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);
+
+ i3c_master_free_i3c_dev(master->this);
}
static const struct device_type i3c_masterdev_type = {
@@ -952,6 +976,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 +991,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 +1050,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;
@@ -997,14 +1063,33 @@ static int i3c_master_send_ccc_cmd_locked(struct i3c_master_controller *master,
if (!master->ops->send_ccc_cmd)
return -EOPNOTSUPP;
- if ((cmd->id & I3C_CCC_DIRECT) && (!cmd->dests || !cmd->ndests))
+ if ((cmd->rnw || (cmd->id & I3C_CCC_DIRECT)) &&
+ (!cmd->dests || !cmd->ndests))
return -EINVAL;
if (master->ops->supports_ccc_cmd &&
!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 *
@@ -1044,15 +1129,10 @@ 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);
}
-static void i3c_master_free_i3c_dev(struct i3c_dev_desc *dev)
-{
- kfree(dev);
-}
-
static struct i3c_dev_desc *
i3c_master_alloc_i3c_dev(struct i3c_master_controller *master,
const struct i3c_device_info *info)
@@ -1100,6 +1180,51 @@ static int i3c_master_rstdaa_locked(struct i3c_master_controller *master,
}
/**
+ * 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
* @master: master used to send frames on the bus
@@ -1363,10 +1488,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 +1503,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 +1538,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 +1564,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 +1618,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;
}
@@ -1866,8 +2000,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;
@@ -1875,6 +2011,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)
@@ -1897,6 +2049,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;
}
@@ -1904,12 +2066,21 @@ err_free_dev:
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;
@@ -1924,30 +2095,92 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master)
desc->dev->dev.type = &i3c_device_type;
desc->dev->dev.bus = &i3c_bus_type;
desc->dev->dev.release = i3c_device_release;
- dev_set_name(&desc->dev->dev, "%d-%llx", master->bus.id,
- desc->info.pid);
+
+ /*
+ * For devices without PID (e.g., SETAASA devices), use
+ * static address for naming instead.
+ */
+ if (desc->info.pid)
+ dev_set_name(&desc->dev->dev, "%d-%llx", master->bus.id,
+ desc->info.pid);
+ else
+ dev_set_name(&desc->dev->dev, "%d-%02x", master->bus.id,
+ desc->info.static_addr);
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) {
- dev_err(&master->dev,
- "Failed to add I3C device (err = %d)\n", ret);
- put_device(&desc->dev->dev);
- }
+ /* 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);
+ }
+
+ 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_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);
- if (!master->shutting_down)
- i3c_master_register_new_i3c_devs(master);
+ 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
@@ -2075,6 +2308,8 @@ int i3c_master_set_info(struct i3c_master_controller *master,
return 0;
err_free_dev:
+ master->bus.cur_master = NULL;
+ master->this = NULL;
i3c_master_free_i3c_dev(i3cdev);
return ret;
@@ -2095,7 +2330,8 @@ static void i3c_master_detach_free_devs(struct i3c_master_controller *master)
i3cdev->boardinfo->init_dyn_addr,
I3C_ADDR_SLOT_FREE);
- i3c_master_free_i3c_dev(i3cdev);
+ if (i3cdev != master->this)
+ i3c_master_free_i3c_dev(i3cdev);
}
list_for_each_entry_safe(i2cdev, i2ctmp, &master->bus.devs.i2c,
@@ -2269,6 +2505,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;
@@ -2311,8 +2560,18 @@ static void i3c_master_attach_boardinfo(struct i3c_dev_desc *i3cdev)
struct i3c_dev_boardinfo *i3cboardinfo;
list_for_each_entry(i3cboardinfo, &master->boardinfo.i3c, node) {
- if (i3cdev->info.pid != i3cboardinfo->pid)
- continue;
+ /*
+ * For devices without PID (e.g., SETAASA devices), match by
+ * static address. For devices with PID, match by PID.
+ */
+ if (i3cboardinfo->pid) {
+ if (i3cdev->info.pid != i3cboardinfo->pid)
+ continue;
+ } else {
+ if (!i3cboardinfo->static_addr ||
+ i3cdev->info.static_addr != i3cboardinfo->static_addr)
+ continue;
+ }
i3cdev->boardinfo = i3cboardinfo;
i3cdev->info.static_addr = i3cboardinfo->static_addr;
@@ -2326,8 +2585,13 @@ i3c_master_search_i3c_dev_duplicate(struct i3c_dev_desc *refdev)
struct i3c_master_controller *master = i3c_dev_get_master(refdev);
struct i3c_dev_desc *i3cdev;
+ if (!refdev->info.pid)
+ return NULL;
+
i3c_bus_for_each_i3cdev(&master->bus, i3cdev) {
- if (i3cdev != refdev && i3cdev->info.pid == refdev->info.pid)
+ if (i3cdev != refdev && i3cdev->info.pid &&
+ i3cdev->info.pid == refdev->info.pid &&
+ i3cdev != master->this)
return i3cdev;
}
@@ -2619,9 +2883,58 @@ EXPORT_SYMBOL_GPL(i3c_master_do_daa);
#define OF_I3C_REG1_IS_I2C_DEV BIT(31)
+#ifdef CONFIG_ACPI
+static int i3c_acpi_get_i2c_resource(struct acpi_resource *ares, void *data)
+{
+ struct i2c_dev_boardinfo *boardinfo = data;
+ struct acpi_resource_i2c_serialbus *sb;
+
+ if (boardinfo->base.addr || !i2c_acpi_get_i2c_resource(ares, &sb))
+ return 1;
+
+ boardinfo->base.addr = sb->slave_address;
+ if (sb->access_mode == ACPI_I2C_10BIT_MODE)
+ boardinfo->base.flags |= I2C_CLIENT_TEN;
+
+ boardinfo->lvr = sb->lvr;
+
+ return 1;
+}
+
+static int i3c_acpi_add_i2c_boardinfo(struct i2c_dev_boardinfo *boardinfo,
+ struct fwnode_handle *fwnode)
+{
+ struct acpi_device *adev = to_acpi_device_node(fwnode);
+ LIST_HEAD(resources);
+ int ret;
+
+ boardinfo->base.fwnode = acpi_fwnode_handle(adev);
+ acpi_set_modalias(adev, dev_name(&adev->dev), boardinfo->base.type,
+ sizeof(boardinfo->base.type));
+
+ ret = acpi_dev_get_resources(adev, &resources,
+ i3c_acpi_get_i2c_resource, boardinfo);
+ if (ret < 0)
+ return ret;
+
+ acpi_dev_free_resource_list(&resources);
+
+ if (!boardinfo->base.addr)
+ return -ENODEV;
+
+ return 0;
+}
+#else
+static inline int i3c_acpi_add_i2c_boardinfo(struct i2c_dev_boardinfo *boardinfo,
+ struct fwnode_handle *fwnode)
+{
+ return -ENODEV;
+}
+#endif
+
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;
@@ -2631,9 +2944,22 @@ 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;
+
+ /* LVR is encoded in reg[2] for Device Tree. */
+ boardinfo->lvr = reg[2];
+ } else if (is_acpi_device_node(fwnode)) {
+ ret = i3c_acpi_add_i2c_boardinfo(boardinfo, fwnode);
+ if (ret) {
+ devm_kfree(dev, boardinfo);
+ return ret;
+ }
+ } else {
+ return -EINVAL;
+ }
/*
* The I3C Specification does not clearly say I2C devices with 10-bit
@@ -2645,23 +2971,20 @@ of_i3c_master_add_i2c_boardinfo(struct i3c_master_controller *master,
return -EOPNOTSUPP;
}
- /* LVR is encoded in reg[2]. */
- 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;
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)
@@ -2679,7 +3002,19 @@ 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, "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;
@@ -2689,21 +3024,36 @@ of_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)) ||
- I3C_PID_RND_LOWER_32BITS(boardinfo->pid))
- return -EINVAL;
+ /* For SETAASA devices, validate the static address instead of PID */
+ if (boardinfo->static_addr_method & I3C_ADDR_METHOD_SETAASA) {
+ if (!boardinfo->static_addr)
+ return -EINVAL;
+ } else {
+ if (!I3C_PID_MANUF_ID(boardinfo->pid) ||
+ (boardinfo->pid & GENMASK_ULL(63, 48)) ||
+ I3C_PID_RND_LOWER_32BITS(boardinfo->pid))
+ 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_of_dev(struct i3c_master_controller *master,
+ struct fwnode_handle *fwnode)
{
u32 reg[3];
int ret;
@@ -2711,34 +3061,108 @@ 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;
/*
- * The manufacturer ID can't be 0. If reg[1] == 0 that means we're
- * dealing with an I2C device.
+ * I3C device should have either the manufacturer ID specified or the
+ * address discovery method specified. Else treat it as an I2C device.
*/
- if (!reg[1])
- ret = of_i3c_master_add_i2c_boardinfo(master, node, reg);
+ if (!reg[1] && !fwnode_property_present(fwnode, "mipi-i3c-static-method"))
+ 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)
+#ifdef CONFIG_ACPI
+static int i3c_master_add_acpi_dev(struct i3c_master_controller *master,
+ struct fwnode_handle *fwnode)
+{
+ struct acpi_device *adev = to_acpi_device_node(fwnode);
+ acpi_bus_address adr;
+ u32 reg[3] = { 0 };
+ int ret;
+
+ /*
+ * If the ACPI table entry has _ADR method, it's an I3C device.
+ * Otherwise it may be an I2C device described by an I2cSerialBus
+ * resource. If no I2cSerialBus resource is found, ignore the entry.
+ */
+ if (!acpi_has_method(adev->handle, "_ADR")) {
+ ret = i3c_master_add_i2c_boardinfo(master, fwnode, reg);
+ if (ret == -ENODEV)
+ return 0;
+
+ return ret;
+ }
+
+ adr = acpi_device_adr(adev);
+
+ /* For I3C devices, _ADR will have the 48 bit PID of the device */
+ reg[1] = upper_32_bits(adr);
+ reg[2] = lower_32_bits(adr);
+
+ fwnode_property_read_u32(fwnode, "mipi-i3c-static-address", &reg[0]);
+
+ return i3c_master_add_i3c_boardinfo(master, fwnode, reg);
+}
+
+static u8 i3c_acpi_i2c_get_lvr(struct i2c_client *client)
+{
+ struct acpi_device *adev = to_acpi_device_node(client->dev.fwnode);
+ struct i2c_dev_boardinfo boardinfo = {};
+ LIST_HEAD(resources);
+ int ret;
+ u8 lvr;
+
+ lvr = I3C_LVR_I2C_INDEX(2) | I3C_LVR_I2C_FM_MODE;
+
+ ret = acpi_dev_get_resources(adev, &resources,
+ i3c_acpi_get_i2c_resource, &boardinfo);
+ if (ret < 0)
+ return lvr;
+
+ if (boardinfo.base.addr)
+ lvr = boardinfo.lvr;
+
+ acpi_dev_free_resource_list(&resources);
+
+ return lvr;
+}
+#else
+static inline int i3c_master_add_acpi_dev(struct i3c_master_controller *master,
+ struct fwnode_handle *fwnode)
+{
+ return -ENODEV;
+}
+
+static inline u8 i3c_acpi_i2c_get_lvr(struct i2c_client *client)
+{
+ return I3C_LVR_I2C_INDEX(2) | I3C_LVR_I2C_FM_MODE;
+}
+#endif
+
+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) {
+ if (is_of_node(child))
+ ret = i3c_master_add_of_dev(master, child);
+ else if (is_acpi_device_node(child))
+ ret = i3c_master_add_acpi_dev(master, child);
+ else
+ continue;
+
if (ret)
return ret;
}
@@ -2748,10 +3172,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;
@@ -2806,8 +3230,13 @@ 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)))
- lvr = reg[2];
+ if (is_of_node(client->dev.fwnode)) {
+ if (!fwnode_property_read_u32_array(client->dev.fwnode, "reg",
+ reg, ARRAY_SIZE(reg)))
+ lvr = reg[2];
+ } else if (is_acpi_device_node(client->dev.fwnode)) {
+ lvr = i3c_acpi_i2c_get_lvr(client);
+ }
return lvr;
}
@@ -2925,7 +3354,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;
@@ -2934,7 +3364,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);
@@ -2977,15 +3409,19 @@ static void i3c_master_unregister_i3c_devs(struct i3c_master_controller *master)
if (!i3cdev->dev)
continue;
- i3cdev->dev->desc = NULL;
- if (device_is_registered(&i3cdev->dev->dev))
+ if (device_is_registered(&i3cdev->dev->dev)) {
+ get_device(&i3cdev->dev->dev);
device_unregister(&i3cdev->dev->dev);
- else
- put_device(&i3cdev->dev->dev);
+ }
+ i3cdev->dev->desc = NULL;
+ put_device(&i3cdev->dev->dev);
i3cdev->dev = NULL;
}
}
+/* Approximate time for IBI handler to run */
+#define I3C_WAKEUP_PROCESSING_TIME_MS 100
+
/**
* i3c_master_queue_ibi() - Queue an IBI
* @dev: the device this IBI is coming from
@@ -2999,6 +3435,9 @@ void i3c_master_queue_ibi(struct i3c_dev_desc *dev, struct i3c_ibi_slot *slot)
if (!dev->ibi || !slot)
return;
+ if (device_may_wakeup(&dev->dev->dev))
+ pm_wakeup_event(&dev->dev->dev, I3C_WAKEUP_PROCESSING_TIME_MS);
+
atomic_inc(&dev->ibi->pending_ibis);
queue_work(dev->ibi->wq, &slot->work);
}
@@ -3235,12 +3674,13 @@ 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;
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);
@@ -3254,13 +3694,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/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..4563d8761ba0 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -242,15 +242,16 @@
/* List of quirks */
#define AMD_I3C_OD_PP_TIMING BIT(1)
#define DW_I3C_DISABLE_RUNTIME_PM_QUIRK BIT(2)
+#define DW_I3C_ACPI_SKIP_CLK_RST BIT(3)
struct dw_i3c_cmd {
u32 cmd_lo;
u32 cmd_hi;
u16 tx_len;
- const void *tx_buf;
u16 rx_len;
- void *rx_buf;
u8 error;
+ const void *tx_buf;
+ void *rx_buf;
};
struct dw_i3c_xfer {
@@ -309,6 +310,8 @@ static bool dw_i3c_master_supports_ccc_cmd(struct i3c_master_controller *m,
case I3C_CCC_GETSTATUS:
case I3C_CCC_GETMXDS:
case I3C_CCC_GETHDRCAP:
+ case I3C_CCC_SETAASA:
+ case I3C_CCC_VENDOR(0, true): /* SETHID */
return true;
default:
return false;
@@ -493,6 +496,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 +506,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;
@@ -554,13 +557,35 @@ static void dw_i3c_master_set_intr_regs(struct dw_i3c_master *master)
writel(IBI_REQ_REJECT_ALL, master->regs + IBI_MR_REQ_REJECT);
}
+static unsigned long dw_i3c_master_get_core_rate(struct dw_i3c_master *master)
+{
+ unsigned int core_rate_prop;
+
+ if (master->core_clk)
+ return clk_get_rate(master->core_clk);
+
+ if (device_property_read_u32(master->dev, "clock-frequency", &core_rate_prop)) {
+ dev_err(master->dev, "missing clock-frequency property\n");
+ return 0;
+ }
+
+ 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;
u32 scl_timing;
u8 hcnt, lcnt;
- core_rate = clk_get_rate(master->core_clk);
+ core_rate = dw_i3c_master_get_core_rate(master);
if (!core_rate)
return -EINVAL;
@@ -592,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);
@@ -613,7 +650,7 @@ static int dw_i2c_clk_cfg(struct dw_i3c_master *master)
u16 hcnt, lcnt;
u32 scl_timing;
- core_rate = clk_get_rate(master->core_clk);
+ core_rate = dw_i3c_master_get_core_rate(master);
if (!core_rate)
return -EINVAL;
@@ -708,12 +745,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 +796,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 +807,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,18 +836,14 @@ 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;
}
-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)
{
@@ -801,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,
@@ -876,7 +921,8 @@ static int dw_i3c_master_daa(struct i3c_master_controller *m)
goto rpm_out;
}
cmd = &xfer->cmds[0];
- cmd->cmd_hi = 0x1;
+ cmd->rx_len = master->maxdevs;
+ cmd->cmd_hi = COMMAND_PORT_TRANSFER_ARG;
cmd->cmd_lo = COMMAND_PORT_DEV_COUNT(master->maxdevs - pos) |
COMMAND_PORT_DEV_INDEX(pos) |
COMMAND_PORT_CMD(I3C_CCC_ENTDAA) |
@@ -888,7 +934,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++) {
@@ -1484,6 +1538,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)
{
@@ -1538,6 +1636,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,
};
@@ -1571,20 +1670,34 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
master->dev = &pdev->dev;
+ if (has_acpi_companion(&pdev->dev)) {
+ quirks = (unsigned long)device_get_match_data(&pdev->dev);
+ } else if (pdev->dev.of_node) {
+ drvdata = device_get_match_data(&pdev->dev);
+ if (drvdata)
+ quirks = drvdata->flags;
+ }
+ master->quirks = quirks;
+
master->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(master->regs))
return PTR_ERR(master->regs);
- master->core_clk = devm_clk_get_enabled(&pdev->dev, NULL);
+ master->core_clk = devm_clk_get_optional_enabled(&pdev->dev, NULL);
if (IS_ERR(master->core_clk))
return PTR_ERR(master->core_clk);
- master->pclk = devm_clk_get_optional_enabled(&pdev->dev, "pclk");
+ if (!master->core_clk && !(master->quirks & DW_I3C_ACPI_SKIP_CLK_RST)) {
+ dev_err(&pdev->dev, "missing core clock\n");
+ return -EINVAL;
+ }
+
+ master->pclk = devm_clk_get_optional_enabled(&pdev->dev, "apb");
if (IS_ERR(master->pclk))
return PTR_ERR(master->pclk);
master->core_rst = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev,
- "core_rst");
+ NULL);
if (IS_ERR(master->core_rst))
return PTR_ERR(master->core_rst);
@@ -1634,15 +1747,6 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
master->has_ibi_data = true;
writel(thld_ctrl, master->regs + QUEUE_THLD_CTRL);
- if (has_acpi_companion(&pdev->dev)) {
- quirks = (unsigned long)device_get_match_data(&pdev->dev);
- } else if (pdev->dev.of_node) {
- drvdata = device_get_match_data(&pdev->dev);
- if (drvdata)
- quirks = drvdata->flags;
- }
- master->quirks = quirks;
-
/* Keep controller enabled by preventing runtime suspend */
if (master->quirks & DW_I3C_DISABLE_RUNTIME_PM_QUIRK)
pm_runtime_get_noresume(&pdev->dev);
@@ -1728,10 +1832,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);
@@ -1832,11 +1933,12 @@ static const struct of_device_id dw_i3c_master_of_match[] = {
};
MODULE_DEVICE_TABLE(of, dw_i3c_master_of_match);
-static const struct acpi_device_id amd_i3c_device_match[] = {
+static const struct acpi_device_id dw_i3c_master_acpi_match[] = {
{ "AMDI0015", AMD_I3C_OD_PP_TIMING },
+ { "NVDA2018", DW_I3C_ACPI_SKIP_CLK_RST },
{ }
};
-MODULE_DEVICE_TABLE(acpi, amd_i3c_device_match);
+MODULE_DEVICE_TABLE(acpi, dw_i3c_master_acpi_match);
static struct platform_driver dw_i3c_driver = {
.probe = dw_i3c_probe,
@@ -1845,7 +1947,7 @@ static struct platform_driver dw_i3c_driver = {
.driver = {
.name = "dw-i3c-master",
.of_match_table = dw_i3c_master_of_match,
- .acpi_match_table = amd_i3c_device_match,
+ .acpi_match_table = dw_i3c_master_acpi_match,
.pm = &dw_i3c_pm_ops,
},
};
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/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..dadf049bd4b5 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -15,6 +15,7 @@
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/module.h>
+#include <linux/pci.h>
#include <linux/platform_data/mipi-i3c-hci.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
@@ -117,6 +118,22 @@ static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m)
return container_of(m, struct i3c_hci, master);
}
+/**
+ * i3c_hci_sysdev() - Get the device to use for DMA and system PM
+ * @dev: Device the HCI controller is bound to
+ *
+ * When an IOMMU is enabled, DMA API calls must use the device that IOMMU
+ * setup was done for. Under PCI enumeration that is the PCI device, not
+ * the "mipi-i3c-hci" platform device below it. The same device owns
+ * system PM and wakeup configuration.
+ *
+ * Return: @dev's parent if it is a PCI device, otherwise @dev.
+ */
+struct device *i3c_hci_sysdev(struct device *dev)
+{
+ return dev->parent && dev_is_pci(dev->parent) ? dev->parent : dev;
+}
+
static void i3c_hci_set_master_dyn_addr(struct i3c_hci *hci)
{
reg_write(MASTER_DEVICE_ADDR,
@@ -375,7 +392,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 +409,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);
@@ -1031,6 +1049,7 @@ static int i3c_hci_init(struct i3c_hci *hci)
switch (regval & ~0xf) {
case 0x100: /* version 1.0 */
case 0x110: /* version 1.1 */
+ case 0x120: /* version 1.2 */
case 0x200: /* version 2.0 */
break;
default:
@@ -1163,6 +1182,9 @@ static int i3c_hci_probe(struct platform_device *pdev)
if (hci->quirks & HCI_QUIRK_RPM_IBI_ALLOWED)
hci->master.rpm_ibi_allowed = true;
+ if (device_can_wakeup(i3c_hci_sysdev(&pdev->dev)))
+ hci->master.ibi_wakeup = true;
+
return i3c_master_register(&hci->master, &pdev->dev, &i3c_hci_ops, false);
}
@@ -1197,6 +1219,10 @@ static const struct platform_device_id i3c_hci_driver_ids[] = {
HCI_QUIRK_DMA_ABORT_REQUIRES_PIO_RESET |
HCI_QUIRK_DMA_REQUIRES_HC_ABORT,
},
+ {
+ .name = "amd-pt-i3c-hci",
+ .driver_data = HCI_QUIRK_RPM_ALLOWED,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, i3c_hci_driver_ids);
diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c
index 0672ed1132f8..7c2b20474130 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dma.c
+++ b/drivers/i3c/master/mipi-i3c-hci/dma.c
@@ -15,7 +15,6 @@
#include <linux/errno.h>
#include <linux/i3c/master.h>
#include <linux/io.h>
-#include <linux/pci.h>
#include "hci.h"
#include "cmd.h"
@@ -301,23 +300,11 @@ static int hci_dma_init(struct i3c_hci *hci)
{
struct hci_rings_data *rings;
struct hci_rh_data *rh;
- struct device *sysdev;
u32 regval;
unsigned int i, nr_rings, xfers_sz, resps_sz;
unsigned int ibi_status_ring_sz, ibi_data_ring_sz;
int ret;
- /*
- * Set pointer to a physical device that does DMA and has IOMMU setup
- * done for it in case of enabled IOMMU and use it with the DMA API.
- * Here such device is either
- * "mipi-i3c-hci" platform device (OF/ACPI enumeration) parent or
- * grandparent (PCI enumeration).
- */
- sysdev = hci->master.dev.parent;
- if (sysdev->parent && dev_is_pci(sysdev->parent))
- sysdev = sysdev->parent;
-
regval = rhs_reg_read(CONTROL);
nr_rings = FIELD_GET(MAX_HEADER_COUNT_CAP, regval);
dev_dbg(&hci->master.dev, "%d DMA rings available\n", nr_rings);
@@ -332,7 +319,7 @@ static int hci_dma_init(struct i3c_hci *hci)
return -ENOMEM;
hci->io_data = rings;
rings->total = nr_rings;
- rings->sysdev = sysdev;
+ rings->sysdev = i3c_hci_sysdev(hci->master.dev.parent);
for (i = 0; i < rings->total; i++) {
u32 offset = rhs_reg_read(RHn_OFFSET(i));
diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
index b3d9803b1968..ee73f6e6756a 100644
--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
+++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
@@ -30,6 +30,11 @@
#define reg_set(r, v) reg_write(r, reg_read(r) | (v))
#define reg_clear(r, v) reg_write(r, reg_read(r) & ~(v))
+/* helper macro for HCI version check */
+#define hci_version_at_least(hci, maj, min) \
+ ((hci)->version_major > (maj) || \
+ ((hci)->version_major == (maj) && (hci)->version_minor >= (min)))
+
struct hci_cmd_ops;
struct dat_words {
@@ -184,6 +189,8 @@ void amd_set_resp_buf_thld(struct i3c_hci *hci);
void i3c_hci_sync_irq_inactive(struct i3c_hci *hci);
int i3c_hci_process_xfer(struct i3c_hci *hci, struct hci_xfer *xfer, int n);
+struct device *i3c_hci_sysdev(struct device *dev);
+
#define DEFAULT_AUTOSUSPEND_DELAY_MS 1000
int i3c_hci_rpm_suspend(struct device *dev);
diff --git a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c
index 5a9e2a43eff8..ab595661db1e 100644
--- a/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c
+++ b/drivers/i3c/master/mipi-i3c-hci/mipi-i3c-hci-pci.c
@@ -223,6 +223,13 @@ static const struct mipi_i3c_hci_pci_info intel_si_2_info = {
.control_instance_pm = true,
};
+static const struct mipi_i3c_hci_pci_info amd_pt_info = {
+ .name = "amd-pt-i3c-hci",
+ .id = {0},
+ .instance_offset = {0},
+ .instance_count = 1,
+};
+
static int mipi_i3c_hci_pci_find_instance(struct mipi_i3c_hci_pci *hci, struct device *dev)
{
for (int i = 0; i < INST_MAX; i++) {
@@ -265,6 +272,8 @@ static bool mipi_i3c_hci_pci_is_operational(struct device *dev, bool update)
struct mipi_i3c_hci_pci_pm_data {
struct device *dev[INST_MAX];
int dev_cnt;
+ bool can_wakeup;
+ bool may_wakeup;
};
static bool mipi_i3c_hci_pci_is_mfd(struct device *dev)
@@ -272,6 +281,13 @@ static bool mipi_i3c_hci_pci_is_mfd(struct device *dev)
return dev_is_platform(dev) && mfd_get_cell(to_platform_device(dev));
}
+static bool mipi_i3c_hci_pci_any_wakeup_enabled(struct device *dev)
+{
+ struct i3c_hci *hci = dev_get_drvdata(dev);
+
+ return i3c_master_has_wakeup_enabled_devs(&hci->master);
+}
+
static int mipi_i3c_hci_pci_suspend_instance(struct device *dev, void *data)
{
struct mipi_i3c_hci_pci_pm_data *pm_data = data;
@@ -287,6 +303,9 @@ static int mipi_i3c_hci_pci_suspend_instance(struct device *dev, void *data)
pm_data->dev[pm_data->dev_cnt++] = dev;
+ if (pm_data->can_wakeup && mipi_i3c_hci_pci_any_wakeup_enabled(dev))
+ pm_data->may_wakeup = true;
+
return 0;
}
@@ -317,12 +336,19 @@ static int mipi_i3c_hci_pci_suspend(struct device *dev)
if (!hci->info->control_instance_pm)
return 0;
+ pm_data.can_wakeup = device_can_wakeup(dev);
+
ret = device_for_each_child_reverse(dev, &pm_data, mipi_i3c_hci_pci_suspend_instance);
- if (ret)
+ if (ret) {
for (int i = 0; i < pm_data.dev_cnt; i++)
i3c_hci_rpm_resume(pm_data.dev[i]);
+ return ret;
+ }
- return ret;
+ if (device_may_wakeup(dev) != pm_data.may_wakeup)
+ device_set_wakeup_enable(dev, pm_data.may_wakeup);
+
+ return 0;
}
static int mipi_i3c_hci_pci_resume(struct device *dev)
@@ -475,6 +501,8 @@ static const struct pci_device_id mipi_i3c_hci_pci_devices[] = {
/* Nova Lake-H */
{ PCI_VDEVICE(INTEL, 0xd37c), .driver_data = (kernel_ulong_t)&intel_mi_1_info },
{ PCI_VDEVICE(INTEL, 0xd36f), .driver_data = (kernel_ulong_t)&intel_mi_2_info },
+ /* AMD_PT */
+ { PCI_VDEVICE(AMD, 0x444c), .driver_data = (kernel_ulong_t)&amd_pt_info },
{ }
};
MODULE_DEVICE_TABLE(pci, mipi_i3c_hci_pci_devices);
diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c
index ff2657ee220b..439578a6eb54 100644
--- a/drivers/i3c/master/mipi-i3c-hci/pio.c
+++ b/drivers/i3c/master/mipi-i3c-hci/pio.c
@@ -45,6 +45,11 @@
#define IBI_STATUS_SIZE GENMASK(15, 8)
#define CR_QUEUE_SIZE GENMASK(7, 0)
+#define PIO_ALT_QUEUE_SIZE 0x1C
+#define EXT_IBI_QUEUE_EN BIT(28)
+#define ALT_RESP_QUEUE_EN BIT(24)
+#define ALT_RESP_QUEUE_SIZE GENMASK(7, 0)
+
#define PIO_INTR_STATUS 0x20
#define PIO_INTR_STATUS_ENABLE 0x24
#define PIO_INTR_SIGNAL_ENABLE 0x28
@@ -72,6 +77,11 @@
#define STAT_RX_THLD BIT(1)
#define STAT_TX_THLD BIT(0)
+#define PIO_CONTROL 0x30
+#define PIO_CONTROL_ABORT BIT(2)
+#define PIO_CONTROL_RS BIT(1)
+#define PIO_CONTROL_ENABLE BIT(0)
+
#define PIO_QUEUE_CUR_STATUS 0x38
#define CUR_IBI_Q_LEVEL GENMASK(28, 20)
#define CUR_RESP_Q_LEVEL GENMASK(18, 10)
@@ -173,6 +183,14 @@ static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr)
* IBI queue size within allowed bounds.
*/
ibi_val = FIELD_GET(IBI_STATUS_SIZE, size_val);
+ /* Adjust actual IBI queue size based on v1.2 ALT_QUEUE_SIZE */
+ if (hci_version_at_least(hci, 1, 2)) {
+ u32 alt_val = pio_reg_read(ALT_QUEUE_SIZE);
+
+ if (alt_val & EXT_IBI_QUEUE_EN)
+ ibi_val *= 8;
+ }
+
pio->max_ibi_thresh = clamp_val(ibi_val/2, 1, 63);
val = FIELD_PREP(QUEUE_IBI_STATUS_THLD, 1) |
FIELD_PREP(QUEUE_IBI_DATA_THLD, pio->max_ibi_thresh) |
@@ -185,8 +203,22 @@ static void __hci_pio_init(struct i3c_hci *hci, u32 *size_val_ptr)
pio_reg_write(INTR_SIGNAL_ENABLE, 0x0);
pio_reg_write(INTR_STATUS_ENABLE, 0xffffffff);
- /* Always accept error interrupts (will be activated on first xfer) */
- pio->enabled_irqs = STAT_ALL_ERRORS;
+ /*
+ * Always accept error interrupts and IBI threshold interrupt
+ * (will be activated on first xfer).
+ */
+ pio->enabled_irqs = STAT_ALL_ERRORS | STAT_IBI_STATUS_THLD;
+
+ /* MIPI I3C HCI v1.2 requires explicitly enabling and starting PIO queues */
+ if (hci_version_at_least(hci, 1, 2)) {
+ u32 ctl_val = pio_reg_read(CONTROL);
+
+ if (!(ctl_val & PIO_CONTROL_ENABLE)) {
+ ctl_val |= PIO_CONTROL_ENABLE;
+ pio_reg_write(CONTROL, ctl_val);
+ }
+ pio_reg_write(CONTROL, ctl_val | PIO_CONTROL_RS);
+ }
}
static void hci_pio_suspend(struct i3c_hci *hci)
@@ -205,6 +237,7 @@ static int hci_pio_init(struct i3c_hci *hci)
{
struct hci_pio_data *pio;
u32 size_val;
+ u32 cmd_sz, resp_sz, ibi_val;
pio = devm_kzalloc(hci->master.dev.parent, sizeof(*pio), GFP_KERNEL);
if (!pio)
@@ -214,10 +247,24 @@ static int hci_pio_init(struct i3c_hci *hci)
__hci_pio_init(hci, &size_val);
- dev_dbg(&hci->master.dev, "CMD/RESP FIFO = %ld entries\n",
- FIELD_GET(CR_QUEUE_SIZE, size_val));
- dev_dbg(&hci->master.dev, "IBI FIFO = %ld bytes\n",
- 4 * FIELD_GET(IBI_STATUS_SIZE, size_val));
+ cmd_sz = FIELD_GET(CR_QUEUE_SIZE, size_val);
+ resp_sz = cmd_sz;
+ ibi_val = FIELD_GET(IBI_STATUS_SIZE, size_val);
+
+ /* MIPI I3C HCI v1.2 supports alternate RESP/IBI queue size */
+ if (hci_version_at_least(hci, 1, 2)) {
+ u32 alt_val = pio_reg_read(ALT_QUEUE_SIZE);
+
+ if (alt_val & ALT_RESP_QUEUE_EN)
+ resp_sz = FIELD_GET(ALT_RESP_QUEUE_SIZE, alt_val);
+ if (alt_val & EXT_IBI_QUEUE_EN)
+ ibi_val *= 8;
+ }
+
+ dev_dbg(&hci->master.dev, "CMD FIFO = %u, RESP FIFO = %u entries\n",
+ cmd_sz, resp_sz);
+ dev_dbg(&hci->master.dev, "IBI FIFO = %u bytes\n",
+ 4 * ibi_val);
dev_dbg(&hci->master.dev, "RX data FIFO = %d bytes\n",
4 * (2 << FIELD_GET(RX_DATA_BUFFER_SIZE, size_val)));
dev_dbg(&hci->master.dev, "TX data FIFO = %d bytes\n",
@@ -241,6 +288,9 @@ static void hci_pio_cleanup(struct i3c_hci *hci)
BUG_ON(pio->curr_rx);
BUG_ON(pio->curr_tx);
BUG_ON(pio->curr_resp);
+ /* MIPI I3C HCI v1.2 requires explicitly stopping and disabling PIO queues */
+ if (hci_version_at_least(hci, 1, 2))
+ pio_reg_write(CONTROL, 0x0);
}
}
@@ -761,6 +811,18 @@ static void hci_pio_err(struct i3c_hci *hci, struct hci_pio_data *pio,
hci_pio_dequeue_xfer_common(hci, pio, pio->curr_tx, 1);
/* then reset the hardware */
mipi_i3c_hci_pio_reset(hci);
+
+ /* MIPI I3C HCI v1.2 requires explicitly restarting PIO queues after error/abort */
+ if (hci_version_at_least(hci, 1, 2)) {
+ u32 ctl_val = pio_reg_read(CONTROL);
+
+ if (!(ctl_val & PIO_CONTROL_ENABLE)) {
+ ctl_val |= PIO_CONTROL_ENABLE;
+ pio_reg_write(CONTROL, ctl_val);
+ }
+ pio_reg_write(CONTROL, ctl_val | PIO_CONTROL_RS);
+ }
+
mipi_i3c_hci_resume(hci);
dev_dbg(&hci->master.dev, "status=%#x/%#x",
diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c
index f39c449922ca..28c0927a0179 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,22 @@ 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;
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 +432,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 +463,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 +498,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 +575,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 +629,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 +673,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 +682,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 = i3c->maxdevs;
+
+ 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 +712,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 +725,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 +793,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 +806,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 +850,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 +869,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 +879,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 +915,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 +931,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 +963,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 +996,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 +1017,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 +1063,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 +1074,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 +1125,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 +1170,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 +1193,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;
+ if (!cmd->err) {
+ u32 rx_count = min(cmd->rx_count, data_len);
- i3c_readl_fifo(i3c->regs + NTDTBP0, cmd->rx_buf, bytes_remaining);
+ 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 +1265,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 +1316,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 +1371,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 +1394,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 +1465,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 +1494,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 +1516,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 +1526,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/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
diff --git a/include/linux/i3c/ccc.h b/include/linux/i3c/ccc.h
index ad59a4ae60d1..c6947dcb0f57 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) \
@@ -32,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)
@@ -343,11 +346,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 +379,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;
};
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 4d2a68793324..f7ceec2b4477 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -174,10 +174,19 @@ 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
- * @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
@@ -188,8 +197,9 @@ struct i3c_dev_boardinfo {
struct list_head node;
u8 init_dyn_addr;
u8 static_addr;
+ u8 static_addr_method;
u64 pid;
- struct device_node *of_node;
+ struct fwnode_handle *fwnode;
};
/**
@@ -228,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.
@@ -238,6 +250,7 @@ struct i3c_device {
struct device dev;
struct i3c_dev_desc *desc;
struct i3c_bus *bus;
+ struct list_head node;
};
/*
@@ -259,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
@@ -511,11 +525,17 @@ 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
* @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
@@ -545,12 +565,14 @@ 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;
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;
@@ -744,6 +766,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);