diff --git a/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml new file mode 100644 index 000000000000..09508a7bcbeb --- /dev/null +++ b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml @@ -0,0 +1,330 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2025-2026 NXP +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i3c/nxp,p3h2840.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP P3H2X4X I3C HUB + +maintainers: + - Aman Kumar Pandey + - Vikash Bansal + - Lakshay Piplani + +description: | + P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) is a family of multiport I3C + hub devices that connect to:- + 1. A host CPU via I3C/I2C/SMBus bus on upstream side and connect to multiple + peripheral devices on the downstream side. + 2. Have two Controller Ports which can support either + I2C/SMBus or I3C buses and connect to a CPU, BMC or SOC. + 3. P3H2840/ P3H2841 are 8 port I3C hub with eight Target Ports. + 4. P3H2440/ P3H2441 are 4 port I3C hub with four Target Ports. + Target ports can be configured as SMBus or I3C and connect to + peripherals. + + All variants share the same register interface and programmable + functionality. The x0 and x1 variants differ only in power-up defaults. + +properties: + compatible: + oneOf: + - const: nxp,p3h2840 + - items: + - enum: + - nxp,p3h2440 + - nxp,p3h2441 + - nxp,p3h2841 + - const: nxp,p3h2840 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + assigned-address: + maximum: 0x7f + + nxp,tp0145-pullup-ohms: + description: + Selects the pull up resistance for target Port 0/1/4/5, in ohms. + enum: [250, 500, 1000, 2000] + default: 500 + + nxp,tp2367-pullup-ohms: + description: + Selects the pull up resistance for target Port 2/3/6/7, in ohms. + enum: [250, 500, 1000, 2000] + default: 500 + + nxp,cp0-io-strength-ohms: + description: + Selects the IO drive strength for controller Port 0, in ohms. + enum: [20, 30, 40, 50] + default: 20 + + nxp,cp1-io-strength-ohms: + description: + Selects the IO drive strength for controller Port 1, in ohms. + enum: [20, 30, 40, 50] + default: 20 + + nxp,tp0145-io-strength-ohms: + description: + Selects the IO drive strength for target port 0/1/4/5, in ohms. + enum: [20, 30, 40, 50] + default: 20 + + nxp,tp2367-io-strength-ohms: + description: + Selects the IO drive strength for target port 2/3/6/7, in ohms. + enum: [20, 30, 40, 50] + default: 20 + + vcc1-supply: + description: Controller port 0 power supply. + + vcc2-supply: + description: Controller port 1 power supply. + + vcc3-supply: + description: Target port 0/1/4/5 power supply. + + vcc4-supply: + description: Target port 2/3/6/7 power supply. + + regulators: + type: object + additionalProperties: false + + properties: + ldo-cp0: + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + + ldo-cp1: + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + + ldo-tpg0: + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + + ldo-tpg1: + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + +required: + - reg + - '#address-cells' + - '#size-cells' + +patternProperties: + "^i3c@[0-7]$": + type: object + $ref: /schemas/i3c/i3c.yaml# + unevaluatedProperties: false + + properties: + reg: + description: + The I3C HUB Target Port number. + items: + - minimum: 0 + maximum: 7 + + nxp,pullup-enable: + type: boolean + description: + Enables the on-die pull-up for Target Port. + + required: + - reg + + "^smbus@[0-7]$": + type: object + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + + properties: + reg: + description: + The I3C HUB Target Port number. + items: + - minimum: 0 + maximum: 7 + + nxp,pullup-enable: + type: boolean + description: + Enables the on-die pull-up for Target Port. + + required: + - reg + +allOf: + - if: + properties: + compatible: + contains: + enum: + - nxp,p3h2440 + - nxp,p3h2441 + then: + patternProperties: + "^i3c@[0-3]$": + properties: + reg: + items: + - maximum: 3 + "^smbus@[0-3]$": + properties: + reg: + items: + - maximum: 3 + "^i3c@[4-7]$": false + "^smbus@[4-7]$": false + +unevaluatedProperties: false + +examples: + - | + i3c { + #address-cells = <3>; + #size-cells = <0>; + + hub@70,236153000c2 { + reg = <0x70 0x236 0x153000c2>; + compatible = "nxp,p3h2441", "nxp,p3h2840"; + #address-cells = <1>; + #size-cells = <0>; + assigned-address = <0x50>; + + nxp,tp0145-pullup-ohms = <1000>; + nxp,tp2367-pullup-ohms = <1000>; + nxp,cp0-io-strength-ohms = <50>; + nxp,cp1-io-strength-ohms = <50>; + nxp,tp0145-io-strength-ohms = <50>; + nxp,tp2367-io-strength-ohms = <50>; + vcc3-supply = <®_tpg0>; + vcc4-supply = <®_tpg1>; + + regulators { + reg_cp0: ldo-cp0 { + regulator-name = "ldo-cp0"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + reg_cp1: ldo-cp1 { + regulator-name = "ldo-cp1"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + reg_tpg0: ldo-tpg0 { + regulator-name = "ldo-tpg0"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + reg_tpg1: ldo-tpg1 { + regulator-name = "ldo-tpg1"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + }; + + smbus@0 { + reg = <0x0>; + #address-cells = <1>; + #size-cells = <0>; + nxp,pullup-enable; + }; + + smbus@1 { + reg = <0x1>; + #address-cells = <1>; + #size-cells = <0>; + nxp,pullup-enable; + }; + + i3c@2 { + reg = <0x2>; + #address-cells = <3>; + #size-cells = <0>; + nxp,pullup-enable; + }; + }; + }; + + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + hub@70 { + reg = <0x70>; + compatible = "nxp,p3h2840"; + #address-cells = <1>; + #size-cells = <0>; + + nxp,tp0145-pullup-ohms = <1000>; + nxp,tp2367-pullup-ohms = <1000>; + nxp,cp0-io-strength-ohms = <50>; + nxp,cp1-io-strength-ohms = <50>; + nxp,tp0145-io-strength-ohms = <50>; + nxp,tp2367-io-strength-ohms = <50>; + vcc3-supply = <®_tpg0_i2c>; + vcc4-supply = <®_tpg1_i2c>; + + regulators { + reg_cp0_i2c: ldo-cp0 { + regulator-name = "ldo-cp0"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + reg_cp1_i2c: ldo-cp1 { + regulator-name = "ldo-cp1"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + reg_tpg0_i2c: ldo-tpg0 { + regulator-name = "ldo-tpg0"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + reg_tpg1_i2c: ldo-tpg1 { + regulator-name = "ldo-tpg1"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + }; + + smbus@0 { + reg = <0x0>; + #address-cells = <1>; + #size-cells = <0>; + nxp,pullup-enable; + }; + + smbus@1 { + reg = <0x1>; + #address-cells = <1>; + #size-cells = <0>; + nxp,pullup-enable; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml b/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml index dc7ee38001d6..6806a22114f9 100644 --- a/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml +++ b/Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml @@ -12,12 +12,24 @@ maintainers: allOf: - $ref: i3c.yaml# + - if: + properties: + compatible: + contains: + const: altr,agilex5-dw-i3c-master + then: + properties: + resets: + maxItems: 1 + properties: compatible: oneOf: - const: snps,dw-i3c-master-1.00a - items: - - const: altr,agilex5-dw-i3c-master + - enum: + - altr,agilex5-dw-i3c-master + - syna,sl2610-i3c - const: snps,dw-i3c-master-1.00a reg: @@ -39,7 +51,7 @@ properties: maxItems: 1 resets: - maxItems: 1 + maxItems: 2 power-domains: maxItems: 1 diff --git a/MAINTAINERS b/MAINTAINERS index 801f964899ba..f4e7d371bbee 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20178,6 +20178,15 @@ L: linux-wireless@vger.kernel.org S: Maintained F: drivers/net/wireless/nxp/ +NXP P3H2X4X I3C-HUB DRIVER +M: Vikash Bansal +M: Aman Kumar Pandey +M: Lakshay Piplani +L: linux-kernel@vger.kernel.org +L: linux-i3c@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml + NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER M: Woodrow Douglass S: Maintained diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig index 626c54b386d5..7536f3740c94 100644 --- a/drivers/i3c/Kconfig +++ b/drivers/i3c/Kconfig @@ -21,6 +21,20 @@ menuconfig I3C if I3C source "drivers/i3c/master/Kconfig" + +config I3C_HUB + tristate "I3C Hub Support" + help + Enable support for the I3C interface in hub devices. + + This option adds virtual I3C bus support for hubs by creating + virtual master controllers for downstream ports and forwarding + bus operations through the hub device. It also provides an + interface used by hub drivers to enable or disable downstream + ports during bus transactions. + + Say Y here if your platform includes an I3C hub device + endif # I3C config I3C_OR_I2C diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile index 11982efbc6d9..9ddee56a6338 100644 --- a/drivers/i3c/Makefile +++ b/drivers/i3c/Makefile @@ -2,3 +2,4 @@ i3c-y := device.o master.o obj-$(CONFIG_I3C) += i3c.o obj-$(CONFIG_I3C) += master/ +obj-$(CONFIG_I3C_HUB) += hub.o diff --git a/drivers/i3c/device.c b/drivers/i3c/device.c index f1ba363b22a1..4df4682af150 100644 --- a/drivers/i3c/device.c +++ b/drivers/i3c/device.c @@ -301,16 +301,23 @@ i3c_device_match_id(struct i3c_device *i3cdev, EXPORT_SYMBOL_GPL(i3c_device_match_id); /** - * i3c_device_get_supported_xfer_mode - Returns the supported transfer mode by - * connected master controller. + * i3c_device_get_supported_xfer_mode - Returns the transfer modes supported by + * the connected master controller and @dev, + * and allowed by the bus configuration. * @dev: I3C device * * Return: a bit mask, which supported transfer mode, bit position is defined at - * enum i3c_hdr_mode + * enum i3c_xfer_mode */ u32 i3c_device_get_supported_xfer_mode(struct i3c_device *dev) { - return i3c_bus_to_i3c_master(dev->bus)->this->info.hdr_cap | BIT(I3C_SDR); + u32 modes; + + i3c_bus_normaluse_lock(dev->bus); + modes = i3c_dev_supported_xfer_modes_locked(dev->desc); + i3c_bus_normaluse_unlock(dev->bus); + + return modes; } EXPORT_SYMBOL_GPL(i3c_device_get_supported_xfer_mode); diff --git a/drivers/i3c/hub.c b/drivers/i3c/hub.c new file mode 100644 index 000000000000..539028768084 --- /dev/null +++ b/drivers/i3c/hub.c @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 NXP + * Generic I3C Hub core implementing virtual controller operations. + */ +#include +#include +#include + +#include "internals.h" + +/** + * struct i3c_hub_dev_data - Per-downstream-device hub state + * @parent_desc: Permanent parent-facing descriptor whose master points at the + * physical parent controller, used to forward controller-specific + * operations there. + * + * The logical descriptor on the virtual hub bus keeps its master aimed at the + * virtual hub controller and is never modified. This separate descriptor lets + * the controller-only core helpers resolve the physical parent without racing + * concurrent readers on the virtual bus. + */ +struct i3c_hub_dev_data { + struct i3c_dev_desc parent_desc; +}; + +/* + * All i3c_bus rw_semaphores are initialized from a single call site in the + * I3C core, so lockdep assigns them one shared class. When a hub forwards an + * operation it takes the parent bus lock while already holding its own virtual + * bus lock, which lockdep then reports as recursive locking on that shared + * class. The bus maintenance and normal-use helpers use plain down_write() and + * down_read(), which always acquire with subclass 0, so lockdep_set_subclass() + * cannot separate them; a distinct lock_class_key per nesting level is used + * instead. + * + * A top-level hub uses depth 1, a hub behind another hub uses depth 2, and so + * on, so a virtual bus lock never shares a class with the parent bus lock it + * nests under. Sibling ports on the same hub share a class, which is safe + * because they are never nested against each other. The array must stay a + * file-local definition: lockdep keys are identified by their address, so a + * single set of unique objects is required. + * + * The depth bound is generous; exceeding it only loses lockdep coverage, not + * correctness. + */ +#define I3C_HUB_MAX_LOCK_DEPTH 8 +static struct lock_class_key i3c_hub_bus_lock_keys[I3C_HUB_MAX_LOCK_DEPTH]; + +/* + * The hub routing mutex (hub->lock) serializes port switching and forwarding. + * A child hub holds its routing mutex while reaching a parent hub that takes + * its own, so it needs the same per-depth lock_class_key treatment as the bus + * lock above, keyed identically (top-level hub depth 1, and so on). The class + * is assigned once in i3c_hub_init(), not per port, because all ports on a hub + * share this single routing mutex. + */ +static struct lock_class_key i3c_hub_routing_lock_keys[I3C_HUB_MAX_LOCK_DEPTH]; + +/** + * i3c_hub_controller_depth() - Count hub nesting levels above a controller + * @controller: Virtual hub controller being initialized + * + * Walk the parent chain and count how many stacked hub controllers lead to + * @controller. A top-level hub attached to a physical controller returns 1. + * The walk stops at the first non-hub (physical) controller. + * + * Return: The hub nesting depth (>= 1 for a hub controller). + */ +static unsigned int +i3c_hub_controller_depth(struct i3c_master_controller *controller) +{ + struct i3c_hub_controller *hub_controller; + unsigned int depth = 0; + + while (controller && controller->ops == &i3c_hub_master_ops) { + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller) + break; + controller = hub_controller->parent; + depth++; + } + + return depth; +} + +/** + * i3c_hub_master_bus_init() - Bind controller to hub device + * @controller: Virtual controller for a hub port + * + * Associates the virtual controller with the hub device descriptor so that + * transfers are executed through the hub on the parent bus. + */ +static int i3c_hub_master_bus_init(struct i3c_master_controller *controller) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + unsigned int depth; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub = hub_controller->hub; + + if (!hub->hub_dev) + return -ENODEV; + + /* + * Give this virtual bus lock a lockdep class keyed on its hub nesting + * depth before the core runs the first DAA (which forwards to the + * parent bus and takes the parent lock while this one is held). The + * lock is not held here, and controller->ops is already set, so the + * class can be assigned safely. Deeper hubs than the key array + * supports fall back to the shared class and may warn under lockdep, + * but still function correctly. + */ + depth = i3c_hub_controller_depth(controller); + if (depth >= 1 && depth <= I3C_HUB_MAX_LOCK_DEPTH) + lockdep_set_class(&controller->bus.lock, + &i3c_hub_bus_lock_keys[depth - 1]); + else + WARN_ONCE(1, "i3c-hub: nesting depth %u exceeds lockdep support\n", + depth); + + controller->this = hub->hub_dev->desc; + return 0; +} + +static void i3c_hub_master_bus_cleanup(struct i3c_master_controller *controller) +{ + controller->this = NULL; +} + +static int i3c_hub_attach_i3c_dev(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *controller = i3c_dev_get_master(dev); + struct i3c_hub_controller *hub_controller; + struct i3c_hub_dev_data *data; + struct i3c_master_controller *parent; + struct i3c_hub *hub; + int ret; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub = hub_controller->hub; + if (!hub->hub_dev) + return -ENODEV; + + parent = i3c_dev_get_master(hub->hub_dev->desc); + if (!parent) + return -ENODEV; + + data = kzalloc_obj(*data); + if (!data) + return -ENOMEM; + + /* Initialize the parent-facing descriptor to target the physical parent. */ + INIT_LIST_HEAD(&data->parent_desc.common.node); + mutex_init(&data->parent_desc.ibi_lock); + + data->parent_desc.common.master = parent; + data->parent_desc.info = dev->info; + + i3c_bus_maintenance_lock(&parent->bus); + ret = i3c_master_attach_i3c_dev_controller_locked(&data->parent_desc); + i3c_bus_maintenance_unlock(&parent->bus); + if (ret) { + mutex_destroy(&data->parent_desc.ibi_lock); + kfree(data); + return ret; + } + + /* Link the hub-private data (see struct i3c_hub_dev_data). */ + i3c_dev_set_master_data(dev, data); + + return 0; +} + +static int i3c_hub_reattach_i3c_dev(struct i3c_dev_desc *dev, + u8 old_dyn_addr) +{ + struct i3c_hub_dev_data *data = i3c_dev_get_master_data(dev); + struct i3c_master_controller *parent; + int ret; + + if (!data) + return -ENODEV; + + parent = i3c_dev_get_master(&data->parent_desc); + if (!parent) + return -ENODEV; + + /* + * Reattach must not race asynchronous IBI delivery on the parent-facing + * descriptor. Once IBI resources are requested the parent controller may + * use parent_desc concurrently, so reject a reattach that arrives while + * the generic IBI object is still live. + */ + if (WARN_ON_ONCE(data->parent_desc.ibi)) + return -EBUSY; + + /* + * Re-sync device information after the address change and reattach + * under the parent bus lock so both updates are applied as one + * operation with respect to parent controller state. + */ + i3c_bus_maintenance_lock(&parent->bus); + data->parent_desc.info = dev->info; + ret = i3c_master_reattach_i3c_dev_controller_locked(&data->parent_desc, + old_dyn_addr); + i3c_bus_maintenance_unlock(&parent->bus); + + return ret; +} + +static void i3c_hub_detach_i3c_dev(struct i3c_dev_desc *dev) +{ + struct i3c_hub_dev_data *data = i3c_dev_get_master_data(dev); + struct i3c_master_controller *parent; + + if (!data) + return; + + parent = i3c_dev_get_master(&data->parent_desc); + + /* + * parent_desc.ibi should already be cleared by i3c_hub_free_ibi() + * before we get here. If it is still set, the kfree(data) below frees + * a descriptor the parent controller can still reach via un-flushed + * asynchronous IBI work (use-after-free, not just a leak). + */ + WARN_ON_ONCE(data->parent_desc.ibi); + + if (parent) { + i3c_bus_maintenance_lock(&parent->bus); + i3c_master_detach_i3c_dev_controller_locked(&data->parent_desc); + i3c_bus_maintenance_unlock(&parent->bus); + } + + i3c_dev_set_master_data(dev, NULL); + mutex_destroy(&data->parent_desc.ibi_lock); + kfree(data); +} + +/** + * i3c_hub_do_daa() - Perform DAA via hub port + * @hub: Hub instance + * @controller: Virtual controller for a hub port + * + * Enables the port connection, performs DAA on the parent controller, + * then disables the connection. + */ +static int i3c_hub_do_daa(struct i3c_hub *hub, + struct i3c_master_controller *controller) +{ + struct i3c_master_controller *parent; + int ret; + + if (!hub || !hub->hub_dev) + return -ENODEV; + + parent = i3c_dev_get_master(hub->hub_dev->desc); + if (!parent) + return -ENODEV; + + /* + * Lock order: hub routing mutex before the parent bus lock (taken here + * inside i3c_master_do_daa()). The depth-keyed lockdep classes above + * keep this nesting acyclic when the parent is itself a hub. + */ + mutex_lock(&hub->lock); + i3c_hub_enable_port(controller); + + /* + * Downstream devices reachable through hub target-port routes share the + * parent controller's I3C address space. The hub gates access to a + * target-port network, but it does not create an independent dynamic + * address domain per virtual bus. + * + * Run DAA on the parent controller so dynamic addresses remain unique + * across all downstream devices, even when they are behind different + * target ports. + * + * The current hub model shares the parent controller's dynamic-address + * domain and does not support independent per-port DAA domains, so a + * DAA result does not go stale behind a port toggle. Addresses reserved + * from the Device Tree are held for the lifetime of the parent bus and + * are never released on a port toggle, so enabling or disabling a port + * only gates reachability and never causes an address to be handed out + * twice. + */ + ret = i3c_master_do_daa(parent); + i3c_hub_disable_port(controller); + mutex_unlock(&hub->lock); + + return ret; +} + +static bool i3c_hub_supports_ccc_cmd(struct i3c_hub *hub, + const struct i3c_ccc_cmd *cmd) +{ + struct i3c_master_controller *parent; + + if (!hub || !hub->hub_dev) + return false; + + parent = i3c_dev_get_master(hub->hub_dev->desc); + if (!parent) + return false; + + return i3c_master_supports_ccc_cmd(parent, cmd); +} + +/** + * i3c_hub_send_ccc_cmd() - Send CCC through hub port + * @hub: Hub instance + * @controller: Virtual controller + * @cmd: CCC command + * + * Enables the port connection while issuing CCC on the parent controller. + * + * A hub does not isolate the CCC address space of its virtual buses from the + * parent bus. A broadcast CCC issued on one virtual bus is forwarded to the + * parent controller and therefore reaches every device on the parent bus, as + * well as the downstream devices of any other connected target port. Drivers + * that need per-device scope must use the directed form of the command. + */ +static int i3c_hub_send_ccc_cmd(struct i3c_hub *hub, + struct i3c_master_controller *controller, + struct i3c_ccc_cmd *cmd) +{ + struct i3c_master_controller *parent; + int ret; + + if (!hub || !hub->hub_dev) + return -ENODEV; + + parent = i3c_dev_get_master(hub->hub_dev->desc); + if (!parent) + return -ENODEV; + + mutex_lock(&hub->lock); + i3c_hub_enable_port(controller); + ret = i3c_master_send_ccc_cmd(parent, cmd); + i3c_hub_disable_port(controller); + mutex_unlock(&hub->lock); + + return ret; +} + +/** + * i3c_hub_master_priv_xfers() - Execute private transfers via hub + * @dev: Target device descriptor + * @xfers: Transfer array + * @nxfers: Number of transfers + * @mode: Transfer mode (SDR, HDR, etc.) + * + * Refreshes the parent-facing device info (while no IBI is pending) and + * forwards private transfers through the hub to the parent controller. + */ +static int i3c_hub_master_priv_xfers(struct i3c_dev_desc *dev, + struct i3c_xfer *xfers, + int nxfers, + enum i3c_xfer_mode mode) +{ + struct i3c_master_controller *controller = i3c_dev_get_master(dev); + struct i3c_hub_controller *hub_controller; + struct i3c_master_controller *parent; + struct i3c_hub_dev_data *data; + struct i3c_hub *hub; + int ret; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub = hub_controller->hub; + + data = i3c_dev_get_master_data(dev); + if (!data) + return -ENODEV; + + parent = i3c_dev_get_master(&data->parent_desc); + if (!parent) + return -ENODEV; + + /* Lock order: hub routing mutex before the parent bus lock (see do_daa). */ + mutex_lock(&hub->lock); + + /* + * Only refresh the parent-facing info while no IBI is requested; once + * parent_desc.ibi is set it must stay immutable (see i3c_hub_request_ibi()). + */ + if (!data->parent_desc.ibi) + data->parent_desc.info = dev->info; + + i3c_hub_enable_port(controller); + + i3c_bus_normaluse_lock(&parent->bus); + ret = i3c_dev_do_xfers_locked(&data->parent_desc, xfers, + nxfers, mode); + i3c_bus_normaluse_unlock(&parent->bus); + + i3c_hub_disable_port(controller); + + mutex_unlock(&hub->lock); + + return ret; +} + +static int i3c_hub_attach_i2c_dev(struct i2c_dev_desc *dev) +{ + return -EOPNOTSUPP; +} + +static void i3c_hub_detach_i2c_dev(struct i2c_dev_desc *dev) +{ +} + +static int i3c_hub_i2c_xfers(struct i2c_dev_desc *dev, + struct i2c_msg *xfers, int nxfers) +{ + return -EOPNOTSUPP; +} + +static int i3c_hub_master_do_daa(struct i3c_master_controller *controller) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub = hub_controller->hub; + + return i3c_hub_do_daa(hub, controller); +} + +static int i3c_hub_master_send_ccc_cmd(struct i3c_master_controller *controller, + struct i3c_ccc_cmd *cmd) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub = hub_controller->hub; + + if (!hub->hub_dev) + return -ENODEV; + + /* + * Do not forward broadcast RSTDAA through the hub. The hub itself + * is visible on the parent bus, so forwarding RSTDAA would also + * reset the hub dynamic address. Downstream RSTDAA is not supported + * by the hub virtual-controller model. + */ + if (cmd->id == I3C_CCC_RSTDAA(true)) + return 0; + + return i3c_hub_send_ccc_cmd(hub, controller, cmd); +} + +static bool i3c_hub_master_supports_ccc_cmd(struct i3c_master_controller *controller, + const struct i3c_ccc_cmd *cmd) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return false; + + hub = hub_controller->hub; + + return i3c_hub_supports_ccc_cmd(hub, cmd); +} + +/** + * i3c_hub_request_ibi() - Request IBI through parent controller + * @desc: Target device descriptor + * @req: IBI setup + * + * Publishes the generic IBI object on the permanent parent-facing descriptor + * and requests IBI for a device connected through the hub. The parent-facing + * descriptor references the same IBI object so the physical controller uses + * the logical workqueue, pending counter and client device during + * asynchronous IBI delivery. + */ +static int i3c_hub_request_ibi(struct i3c_dev_desc *desc, + const struct i3c_ibi_setup *req) +{ + struct i3c_master_controller *controller = i3c_dev_get_master(desc); + struct i3c_hub_controller *hub_controller; + struct i3c_master_controller *parent; + struct i3c_hub_dev_data *data; + struct i3c_hub *hub; + int ret; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub = hub_controller->hub; + + data = i3c_dev_get_master_data(desc); + if (!data) + return -ENODEV; + + parent = i3c_dev_get_master(&data->parent_desc); + if (!parent) + return -ENODEV; + + /* + * Publish the final device information snapshot together with the + * generic IBI object under hub->lock. Keep the parent-facing + * information immutable while parent_desc.ibi is set and the parent + * controller may use the descriptor asynchronously. + */ + mutex_lock(&hub->lock); + data->parent_desc.info = desc->info; + data->parent_desc.dev = desc->dev; + data->parent_desc.ibi = desc->ibi; + mutex_unlock(&hub->lock); + + i3c_bus_normaluse_lock(&parent->bus); + ret = i3c_dev_request_ibi_controller_locked(&data->parent_desc, req); + i3c_bus_normaluse_unlock(&parent->bus); + + if (ret) { + mutex_lock(&hub->lock); + data->parent_desc.ibi = NULL; + data->parent_desc.dev = NULL; + mutex_unlock(&hub->lock); + } + + return ret; +} + +static void i3c_hub_free_ibi(struct i3c_dev_desc *desc) +{ + struct i3c_master_controller *controller = i3c_dev_get_master(desc); + struct i3c_hub_controller *hub_controller; + struct i3c_master_controller *parent; + struct i3c_hub_dev_data *data; + struct i3c_hub *hub; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return; + + hub = hub_controller->hub; + + data = i3c_dev_get_master_data(desc); + if (!data || !data->parent_desc.ibi) + return; + + parent = i3c_dev_get_master(&data->parent_desc); + if (!parent) + return; + + i3c_bus_normaluse_lock(&parent->bus); + i3c_dev_free_ibi_controller_locked(&data->parent_desc); + i3c_bus_normaluse_unlock(&parent->bus); + + /* + * The outer generic IBI free path owns and releases desc->ibi after + * this callback returns. + */ + mutex_lock(&hub->lock); + data->parent_desc.ibi = NULL; + data->parent_desc.dev = NULL; + mutex_unlock(&hub->lock); +} + +/** + * i3c_hub_enable_ibi() - Enable IBI via hub port + * @desc: Target device descriptor + * + * Enables port connection and forwards the IBI enable request to the parent + * controller. + */ +static int i3c_hub_enable_ibi(struct i3c_dev_desc *desc) +{ + struct i3c_master_controller *controller = i3c_dev_get_master(desc); + struct i3c_hub_controller *hub_controller; + struct i3c_master_controller *parent; + struct i3c_hub_dev_data *data; + struct i3c_hub *hub; + int ret; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub = hub_controller->hub; + + data = i3c_dev_get_master_data(desc); + if (!data || !data->parent_desc.ibi) + return -ENODEV; + + parent = i3c_dev_get_master(&data->parent_desc); + if (!parent) + return -ENODEV; + + mutex_lock(&hub->lock); + + i3c_hub_enable_port(controller); + + i3c_bus_maintenance_lock(&parent->bus); + ret = i3c_dev_enable_ibi_controller_locked(&data->parent_desc); + i3c_bus_maintenance_unlock(&parent->bus); + + i3c_hub_disable_port(controller); + + mutex_unlock(&hub->lock); + + return ret; +} + +/** + * i3c_hub_disable_ibi() - Disable IBI via hub port + * @desc: Target device descriptor + * + * Enables port connection and forwards the IBI disable request to the parent + * controller. + */ +static int i3c_hub_disable_ibi(struct i3c_dev_desc *desc) +{ + struct i3c_master_controller *controller = i3c_dev_get_master(desc); + struct i3c_hub_controller *hub_controller; + struct i3c_master_controller *parent; + struct i3c_hub_dev_data *data; + struct i3c_hub *hub; + int ret; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub = hub_controller->hub; + + data = i3c_dev_get_master_data(desc); + if (!data || !data->parent_desc.ibi) + return -ENODEV; + + parent = i3c_dev_get_master(&data->parent_desc); + if (!parent) + return -ENODEV; + + mutex_lock(&hub->lock); + + i3c_hub_enable_port(controller); + + i3c_bus_maintenance_lock(&parent->bus); + ret = i3c_dev_disable_ibi_controller_locked(&data->parent_desc); + i3c_bus_maintenance_unlock(&parent->bus); + + i3c_hub_disable_port(controller); + + mutex_unlock(&hub->lock); + + return ret; +} + +static void i3c_hub_recycle_ibi_slot(struct i3c_dev_desc *desc, + struct i3c_ibi_slot *slot) +{ + struct i3c_hub_dev_data *data = i3c_dev_get_master_data(desc); + + if (!data) + return; + + i3c_dev_recycle_ibi_slot_controller(&data->parent_desc, slot); +} + +const struct i3c_master_controller_ops i3c_hub_master_ops = { + .bus_init = i3c_hub_master_bus_init, + .bus_cleanup = i3c_hub_master_bus_cleanup, + .attach_i3c_dev = i3c_hub_attach_i3c_dev, + .reattach_i3c_dev = i3c_hub_reattach_i3c_dev, + .detach_i3c_dev = i3c_hub_detach_i3c_dev, + .do_daa = i3c_hub_master_do_daa, + .supports_ccc_cmd = i3c_hub_master_supports_ccc_cmd, + .send_ccc_cmd = i3c_hub_master_send_ccc_cmd, + .i3c_xfers = i3c_hub_master_priv_xfers, + .attach_i2c_dev = i3c_hub_attach_i2c_dev, + .detach_i2c_dev = i3c_hub_detach_i2c_dev, + .i2c_xfers = i3c_hub_i2c_xfers, + .request_ibi = i3c_hub_request_ibi, + .free_ibi = i3c_hub_free_ibi, + .enable_ibi = i3c_hub_enable_ibi, + .disable_ibi = i3c_hub_disable_ibi, + .recycle_ibi_slot = i3c_hub_recycle_ibi_slot, +}; +EXPORT_SYMBOL_GPL(i3c_hub_master_ops); + +/** + * i3c_hub_set_routing_lock_class() - Key the routing mutex for lockdep + * @hub: Hub instance whose routing mutex has just been initialized + * + * Give @hub->lock a lockdep class keyed on the hub nesting depth, so that a + * child hub taking its routing mutex while a parent hub holds its own is not + * reported as recursive locking. Does nothing when lockdep is disabled. + */ +static void i3c_hub_set_routing_lock_class(struct i3c_hub *hub) +{ + struct i3c_master_controller *parent; + unsigned int depth; + + if (!IS_ENABLED(CONFIG_LOCKDEP)) + return; + + /* + * The routing mutex has the same hub nesting depth as the virtual + * controllers this hub exposes, so the parent controller is one level + * shallower. Keying it once here, rather than per port, avoids + * reclassifying the single shared routing mutex from a later port that + * may already have used it. + */ + parent = i3c_dev_get_master(hub->hub_dev->desc); + depth = i3c_hub_controller_depth(parent) + 1; + if (WARN_ONCE(depth > I3C_HUB_MAX_LOCK_DEPTH, + "i3c-hub: routing lock depth %u exceeds lockdep support\n", + depth)) + depth = I3C_HUB_MAX_LOCK_DEPTH; + + lockdep_set_class(&hub->lock, &i3c_hub_routing_lock_keys[depth - 1]); +} + +/** + * i3c_hub_init() - Initialize hub context + * @hub: Hub instance to initialize + * @ops: Vendor callbacks used to connect and disconnect target ports + * @hub_dev: I3C device representing the hub on the parent bus + * + * Store the vendor callbacks and the hub device in @hub and initialize the + * routing mutex used to serialize port switching and forwarding. + * + * @hub_dev must be a registered I3C device, so it already has a descriptor + * and a parent controller. + */ +void i3c_hub_init(struct i3c_hub *hub, + const struct i3c_hub_ops *ops, + struct i3c_device *hub_dev) +{ + hub->ops = ops; + hub->hub_dev = hub_dev; + mutex_init(&hub->lock); + + i3c_hub_set_routing_lock_class(hub); +} +EXPORT_SYMBOL_GPL(i3c_hub_init); + +/** + * i3c_hub_reserve_parent_addrslots_from_dt() - Reserve child addresses in parent bus. + * @hubc: I3C hub controller for a target-port virtual bus. + * @node: Target-port bus Device Tree node. + * + * Reserve parent bus address slots for the assigned dynamic address of each + * downstream I3C device described in the Device Tree, so that parent DAA does + * not hand the same address to another device. Downstream devices behind hub + * target ports share the parent controller's dynamic address space. + * + * The reservations describe a fixed Device Tree topology and are kept for the + * lifetime of the parent bus; they are not released when a target port is + * disconnected. If the hub is removed and probed again it re-reads the same + * Device Tree and reserves the same addresses, so repeated probing does not + * consume additional address space. + * + * Only the assigned dynamic addresses are reserved here. A hub that supports + * hot-joining downstream devices would additionally have to reserve the static + * addresses of every device that may appear. Supporting runtime hub hotplug + * would also require tracking ownership of these reservations and releasing + * them when a hub is removed. + * + * Return: 0 on success, or a negative error code. + */ +int i3c_hub_reserve_parent_addrslots_from_dt(struct i3c_hub_controller *hubc, + struct device_node *node) +{ + struct i3c_master_controller *parent = hubc->parent; + enum i3c_addr_slot_status status; + u32 assigned_addr; + u32 reg[3]; + int ret; + + if (!parent || !node) + return -ENODEV; + + for_each_available_child_of_node_scoped(node, child) { + /* + * Only consider addressable bus nodes: a valid "reg" is + * required to describe a device, but its static-address value + * does not affect the reservation below. + */ + ret = of_property_read_variable_u32_array(child, "reg", reg, 1, 3); + if (ret < 0) + continue; + + ret = of_property_read_u32(child, "assigned-address", &assigned_addr); + if (ret) + continue; + + /* Skip nodes without a usable dynamic address. */ + if (!assigned_addr || assigned_addr > I3C_MAX_ADDR) + continue; + + i3c_bus_maintenance_lock(&parent->bus); + status = i3c_bus_get_addr_slot_status(&parent->bus, + assigned_addr); + if (status == I3C_ADDR_SLOT_FREE) + i3c_bus_set_addr_slot_status(&parent->bus, + assigned_addr, + I3C_ADDR_SLOT_I3C_DEV); + i3c_bus_maintenance_unlock(&parent->bus); + } + return 0; +} +EXPORT_SYMBOL_GPL(i3c_hub_reserve_parent_addrslots_from_dt); + +MODULE_AUTHOR("Aman Kumar Pandey "); +MODULE_AUTHOR("Vikash Bansal "); +MODULE_AUTHOR("Lakshay Piplani "); +MODULE_DESCRIPTION("Generic I3C hub support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h index 86a36b951e0d..24f67fef65bd 100644 --- a/drivers/i3c/internals.h +++ b/drivers/i3c/internals.h @@ -18,10 +18,26 @@ bool i3c_bus_rpm_ibi_allowed(struct i3c_bus *bus); void i3c_bus_normaluse_lock(struct i3c_bus *bus); void i3c_bus_normaluse_unlock(struct i3c_bus *bus); +u32 i3c_dev_supported_xfer_modes_locked(struct i3c_dev_desc *dev); + int i3c_dev_setdasa_locked(struct i3c_dev_desc *dev); int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers, int nxfers, enum i3c_xfer_mode mode); + +int i3c_master_attach_i3c_dev_controller_locked(struct i3c_dev_desc *dev); +int i3c_master_reattach_i3c_dev_controller_locked(struct i3c_dev_desc *dev, + u8 old_dyn_addr); +void i3c_master_detach_i3c_dev_controller_locked(struct i3c_dev_desc *dev); + +int i3c_dev_disable_ibi_controller_locked(struct i3c_dev_desc *dev); +int i3c_dev_enable_ibi_controller_locked(struct i3c_dev_desc *dev); +int i3c_dev_request_ibi_controller_locked(struct i3c_dev_desc *dev, + const struct i3c_ibi_setup *req); +void i3c_dev_free_ibi_controller_locked(struct i3c_dev_desc *dev); +void i3c_dev_recycle_ibi_slot_controller(struct i3c_dev_desc *dev, + struct i3c_ibi_slot *slot); + int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev); int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev); int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index afcd7a21a3e6..01e05bad8661 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -49,10 +50,11 @@ static BLOCKING_NOTIFIER_HEAD(i3c_bus_notifier); * logic to rely on I3C device information that could be changed behind their * back. */ -static void i3c_bus_maintenance_lock(struct i3c_bus *bus) +void i3c_bus_maintenance_lock(struct i3c_bus *bus) { down_write(&bus->lock); } +EXPORT_SYMBOL_GPL(i3c_bus_maintenance_lock); /** * i3c_bus_maintenance_unlock - Release the bus lock after a maintenance @@ -63,10 +65,11 @@ static void i3c_bus_maintenance_lock(struct i3c_bus *bus) * i3c_bus_maintenance_lock() for more details on what these maintenance * operations are. */ -static void i3c_bus_maintenance_unlock(struct i3c_bus *bus) +void i3c_bus_maintenance_unlock(struct i3c_bus *bus) { up_write(&bus->lock); } +EXPORT_SYMBOL_GPL(i3c_bus_maintenance_unlock); /** * i3c_bus_normaluse_lock - Lock the bus for a normal operation @@ -88,6 +91,7 @@ void i3c_bus_normaluse_lock(struct i3c_bus *bus) { down_read(&bus->lock); } +EXPORT_SYMBOL_GPL(i3c_bus_normaluse_lock); /** * i3c_bus_normaluse_unlock - Release the bus lock after a normal operation @@ -101,6 +105,7 @@ void i3c_bus_normaluse_unlock(struct i3c_bus *bus) { up_read(&bus->lock); } +EXPORT_SYMBOL_GPL(i3c_bus_normaluse_unlock); static struct i3c_master_controller *dev_to_i3cmaster(struct device *dev) { @@ -402,11 +407,19 @@ i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, u32 mask) return status & mask; } -static enum i3c_addr_slot_status +/** + * i3c_bus_get_addr_slot_status() - Get I3C bus address slot status + * @bus: I3C bus. + * @addr: I3C address to query. + * + * Return: Address slot status for @addr. + */ +enum i3c_addr_slot_status i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr) { return i3c_bus_get_addr_slot_status_mask(bus, addr, I3C_ADDR_SLOT_STATUS_MASK); } +EXPORT_SYMBOL_GPL(i3c_bus_get_addr_slot_status); static void i3c_bus_set_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, enum i3c_addr_slot_status status, u32 mask) @@ -422,11 +435,18 @@ static void i3c_bus_set_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, *ptr |= ((unsigned long)status & mask) << (bitpos % BITS_PER_LONG); } -static void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, - enum i3c_addr_slot_status status) +/** + * i3c_bus_set_addr_slot_status() - Set I3C bus address slot status + * @bus: I3C bus. + * @addr: I3C address to update. + * @status: Address slot status to set. + */ +void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, + enum i3c_addr_slot_status status) { i3c_bus_set_addr_slot_status_mask(bus, addr, status, I3C_ADDR_SLOT_STATUS_MASK); } +EXPORT_SYMBOL_GPL(i3c_bus_set_addr_slot_status); static bool i3c_bus_dev_addr_is_avail(struct i3c_bus *bus, u8 addr) { @@ -937,6 +957,61 @@ static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode, return 0; } +/* + * I3C v1.1.1 Section 5.1.2.4 Table 9 lists the HDR Modes each Bus + * Configuration allows. A Mixed Slow / Limited Bus has Legacy I2C Devices + * without a 50 ns spike filter, so there is no way to hide any HDR Mode from + * them. Of the two Ternary Modes, only HDR-TSL is defined for a Bus that + * also has Legacy I2C Devices; HDR-TSP is defined for a Pure Bus. + */ +static u32 i3c_bus_hdr_modes(struct i3c_bus *bus) +{ + switch (bus->mode) { + case I3C_BUS_MODE_PURE: + return BIT(I3C_HDR_DDR) | BIT(I3C_HDR_TSP) | BIT(I3C_HDR_TSL); + case I3C_BUS_MODE_MIXED_FAST: + return BIT(I3C_HDR_DDR) | BIT(I3C_HDR_TSL); + case I3C_BUS_MODE_MIXED_LIMITED: + case I3C_BUS_MODE_MIXED_SLOW: + break; + } + + return 0; +} + +static u32 i3c_dev_hdr_modes(struct i3c_dev_desc *dev) +{ + if (!(dev->info.bcr & I3C_BCR_HDR_CAP)) + return 0; + + return dev->info.hdr_cap; +} + +/** + * i3c_dev_supported_xfer_modes_locked() - Get the transfer modes usable with a + * device + * @dev: I3C device descriptor + * + * The HDR Modes the controller and @dev both support, restricted to those the + * bus configuration allows. SDR is always supported. + * + * The bus lock must be held in normal use mode. + * + * Return: a bit mask of &enum i3c_xfer_mode values. + */ +u32 i3c_dev_supported_xfer_modes_locked(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *master = i3c_dev_get_master(dev); + + /* + * master->this->info.bcr is ignored because it describes the master's + * target capability, not its controller capability. + */ + return (master->this->info.hdr_cap & + i3c_bus_hdr_modes(&master->bus) & + i3c_dev_hdr_modes(dev)) | BIT(I3C_SDR); +} + static struct i3c_master_controller * i2c_adapter_to_i3c_master(struct i2c_adapter *adap) { @@ -1776,11 +1851,6 @@ static int i3c_master_getstatus_locked(struct i3c_master_controller *master, if (ret) goto out; - if (dest.payload.len != sizeof(*getstatus)) { - ret = -EIO; - goto out; - } - if (status) *status = be16_to_cpu(getstatus->status); out: @@ -1879,6 +1949,30 @@ static int i3c_master_get_i3c_addrs(struct i3c_dev_desc *dev) return -EBUSY; } +/** + * i3c_master_attach_i3c_dev_controller_locked() - Attach device state to + * controller + * @dev: I3C device descriptor + * + * Invoke the current controller's attach callback without changing address + * slot state or adding the device to the controller's device list. + * + * Context: The caller must hold the bus lock. + * + * Return: 0 on success, or a negative error code returned by the controller. + */ +int i3c_master_attach_i3c_dev_controller_locked(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *master = i3c_dev_get_master(dev); + + /* Do not attach the master device itself. */ + if (master->this != dev && master->ops->attach_i3c_dev) + return master->ops->attach_i3c_dev(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(i3c_master_attach_i3c_dev_controller_locked); + static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master, struct i3c_dev_desc *dev) { @@ -1896,12 +1990,10 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master, return ret; /* Do not attach the master device itself. */ - if (master->this != dev && master->ops->attach_i3c_dev) { - ret = master->ops->attach_i3c_dev(dev); - if (ret) { - i3c_master_put_i3c_addrs(dev); - return ret; - } + ret = i3c_master_attach_i3c_dev_controller_locked(dev); + if (ret) { + i3c_master_put_i3c_addrs(dev); + return ret; } list_add_tail(&dev->common.node, &master->bus.devs.i3c); @@ -1909,6 +2001,31 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master, return 0; } +/** + * i3c_master_reattach_i3c_dev_controller_locked() - Reattach controller + * device state + * @dev: I3C device descriptor + * @old_dyn_addr: Previous dynamic address + * + * Invoke the current controller's reattach callback without modifying the + * controller's address-slot state. + * + * Context: The caller must hold the bus lock. + * + * Return: 0 on success, or a negative error code returned by the controller. + */ +int i3c_master_reattach_i3c_dev_controller_locked(struct i3c_dev_desc *dev, + u8 old_dyn_addr) +{ + struct i3c_master_controller *master = i3c_dev_get_master(dev); + + if (master->ops->reattach_i3c_dev) + return master->ops->reattach_i3c_dev(dev, old_dyn_addr); + + return 0; +} +EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev_controller_locked); + /** * i3c_master_reattach_i3c_dev_locked() - reattach an I3C device with a new address * @dev: I3C device descriptor to reattach @@ -1939,25 +2056,39 @@ int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev, I3C_ADDR_SLOT_FREE); } - if (master->ops->reattach_i3c_dev) { - ret = master->ops->reattach_i3c_dev(dev, old_dyn_addr); - if (ret) { - i3c_master_put_i3c_addrs(dev); - return ret; - } + ret = i3c_master_reattach_i3c_dev_controller_locked(dev, old_dyn_addr); + if (ret) { + i3c_master_put_i3c_addrs(dev); + return ret; } return 0; } EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev_locked); -static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev) +/** + * i3c_master_detach_i3c_dev_controller_locked() - Detach device state from + * controller + * @dev: I3C device descriptor + * + * Invoke the current controller's detach callback without releasing address + * slots or removing the device from the controller's device list. + * + * Context: The caller must hold the bus lock. + */ +void i3c_master_detach_i3c_dev_controller_locked(struct i3c_dev_desc *dev) { struct i3c_master_controller *master = i3c_dev_get_master(dev); /* Do not detach the master device itself. */ if (master->this != dev && master->ops->detach_i3c_dev) master->ops->detach_i3c_dev(dev); +} +EXPORT_SYMBOL_GPL(i3c_master_detach_i3c_dev_controller_locked); + +static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev) +{ + i3c_master_detach_i3c_dev_controller_locked(dev); i3c_master_put_i3c_addrs(dev); list_del(&dev->common.node); @@ -2216,12 +2347,11 @@ struct i3c_dma *i3c_master_dma_map_single(struct device *dev, void *buf, if (force_bounce) { dma_xfer->map_len = ALIGN(len, cache_line_size()); - if (dir == DMA_FROM_DEVICE) - bounce = kzalloc(dma_xfer->map_len, GFP_KERNEL); - else - bounce = kmemdup(buf, dma_xfer->map_len, GFP_KERNEL); + bounce = kzalloc(dma_xfer->map_len, GFP_KERNEL); if (!bounce) return NULL; + if (dir != DMA_FROM_DEVICE) + memcpy(bounce, buf, len); dma_buf = bounce; } @@ -2812,6 +2942,59 @@ static void i3c_master_reconcile_dyn_addrs(struct i3c_master_controller *master) } } +/** + * i3c_master_supports_ccc_cmd() - check CCC command support + * @master: I3C master controller + * @cmd: CCC command to verify + * + * Return: true if @cmd is supported, false otherwise. + */ +bool i3c_master_supports_ccc_cmd(struct i3c_master_controller *master, + const struct i3c_ccc_cmd *cmd) +{ + if (!master || !cmd) + return false; + + if (!master->ops->send_ccc_cmd) + return false; + + if (!master->ops->supports_ccc_cmd) + return true; + + return master->ops->supports_ccc_cmd(master, cmd); +} +EXPORT_SYMBOL_GPL(i3c_master_supports_ccc_cmd); + +/** + * i3c_master_send_ccc_cmd() - send a CCC command + * @master: I3C master controller issuing the command + * @cmd: CCC command to be sent + * + * This function sends a Common Command Code (CCC) command to devices on the + * I3C bus. It acquires the bus maintenance lock, executes the command, and + * then releases the lock to ensure safe access to the bus. + * + * Return: 0 on success, or a negative error code on failure. + */ +int i3c_master_send_ccc_cmd(struct i3c_master_controller *master, + struct i3c_ccc_cmd *cmd) +{ + int ret; + + ret = i3c_master_rpm_get(master); + if (ret) + return ret; + + i3c_bus_maintenance_lock(&master->bus); + ret = i3c_master_send_ccc_cmd_locked(master, cmd); + i3c_bus_maintenance_unlock(&master->bus); + + i3c_master_rpm_put(master); + + return ret; +} +EXPORT_SYMBOL_GPL(i3c_master_send_ccc_cmd); + /** * i3c_master_do_daa_ext() - Dynamic Address Assignment (extended version) * @master: controller @@ -3077,6 +3260,13 @@ static int i3c_master_add_of_dev(struct i3c_master_controller *master, return ret; } +#define I3C_ACPI_ADR_PID GENMASK_U64(47, 0) +/* + * Zero-based instance number of the Bus Controller to which the Target is + * connected. + */ +#define I3C_ACPI_ADR_INSTANCE GENMASK_U64(51, 48) + #ifdef CONFIG_ACPI static int i3c_master_add_acpi_dev(struct i3c_master_controller *master, struct fwnode_handle *fwnode) @@ -3084,6 +3274,7 @@ static int i3c_master_add_acpi_dev(struct i3c_master_controller *master, struct acpi_device *adev = to_acpi_device_node(fwnode); acpi_bus_address adr; u32 reg[3] = { 0 }; + u64 pid; int ret; /* @@ -3101,9 +3292,15 @@ static int i3c_master_add_acpi_dev(struct i3c_master_controller *master, adr = acpi_device_adr(adev); + /* Match the multi-bus instance number */ + if (FIELD_GET(I3C_ACPI_ADR_INSTANCE, adr) != master->instance) + return 0; + /* 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); + pid = FIELD_GET(I3C_ACPI_ADR_PID, adr); + + reg[1] = upper_32_bits(pid); + reg[2] = lower_32_bits(pid); fwnode_property_read_u32(fwnode, "mipi-i3c-static-address", ®[0]); @@ -3448,7 +3645,6 @@ static void i3c_master_handle_ibi(struct work_struct *work) struct i3c_ibi_slot *slot = container_of(work, struct i3c_ibi_slot, work); struct i3c_dev_desc *dev = slot->dev; - struct i3c_master_controller *master = i3c_dev_get_master(dev); struct i3c_ibi_payload payload; payload.data = slot->data; @@ -3457,7 +3653,7 @@ static void i3c_master_handle_ibi(struct work_struct *work) if (dev->dev) dev->ibi->handler(dev->dev, &payload); - master->ops->recycle_ibi_slot(dev, slot); + i3c_dev_recycle_ibi_slot_controller(dev, slot); if (atomic_dec_and_test(&dev->ibi->pending_ibis)) complete(&dev->ibi->all_ibis_handled); } @@ -3568,6 +3764,29 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, } EXPORT_SYMBOL_GPL(i3c_generic_ibi_alloc_pool); +/** + * i3c_dev_recycle_ibi_slot_controller() - Recycle an IBI slot through + * the current controller + * @dev: I3C device descriptor + * @slot: IBI slot to recycle + * + * Invoke the current controller's IBI slot recycling callback. + * + * Context: Called from the generic IBI work handler in workqueue context. + * No bus lock is taken here: the controller owns its IBI pool and is + * responsible for synchronizing access to it. The generic pool + * implementation uses its own spinlock. + */ +void i3c_dev_recycle_ibi_slot_controller(struct i3c_dev_desc *dev, + struct i3c_ibi_slot *slot) +{ + struct i3c_master_controller *master = i3c_dev_get_master(dev); + + if (master->ops->recycle_ibi_slot) + master->ops->recycle_ibi_slot(dev, slot); +} +EXPORT_SYMBOL_GPL(i3c_dev_recycle_ibi_slot_controller); + /** * i3c_generic_ibi_get_free_slot() - Get a free slot from a generic IBI pool * @pool: the pool to query an IBI slot on @@ -3634,30 +3853,23 @@ static int i3c_master_check_ops(const struct i3c_master_controller_ops *ops) } /** - * i3c_master_register() - register an I3C master + * i3c_master_register_fwnode() - register an I3C master with a custom fwnode * @master: master used to send frames on the bus - * @parent: the parent device (the one that provides this I3C master - * controller) + * @parent: the parent device providing this I3C master controller + * @fwnode: firmware node describing this I3C bus, or NULL * @ops: the master controller operations - * @secondary: true if you are registering a secondary master. Will return - * -EOPNOTSUPP if set to true since secondary masters are not yet - * supported + * @secondary: true if registering a secondary master * - * This function takes care of everything for you: - * - * - creates and initializes the I3C bus - * - populates the bus with static I2C devs if @parent->of_node is not - * NULL - * - registers all I3C devices added by the controller during bus - * initialization - * - registers the I2C adapter and all I2C devices + * This helper is useful for virtual I3C masters whose firmware node is not + * the same as @parent's firmware node. * * Return: 0 in case of success, a negative error code otherwise. */ -int i3c_master_register(struct i3c_master_controller *master, - struct device *parent, - const struct i3c_master_controller_ops *ops, - bool secondary) +int i3c_master_register_fwnode(struct i3c_master_controller *master, + struct device *parent, + struct fwnode_handle *fwnode, + const struct i3c_master_controller_ops *ops, + bool secondary) { unsigned long i2c_scl_rate = I3C_BUS_I2C_FM_PLUS_SCL_MAX_RATE; struct i3c_bus *i3cbus = i3c_master_get_bus(master); @@ -3674,7 +3886,7 @@ int i3c_master_register(struct i3c_master_controller *master, return ret; master->dev.parent = parent; - device_set_node(&master->dev, fwnode_handle_get(dev_fwnode(parent))); + device_set_node(&master->dev, fwnode_handle_get(fwnode)); master->dev.bus = &i3c_bus_type; master->dev.type = &i3c_masterdev_type; master->dev.release = i3c_masterdev_release; @@ -3792,6 +4004,38 @@ int i3c_master_register(struct i3c_master_controller *master, return ret; } +EXPORT_SYMBOL_GPL(i3c_master_register_fwnode); + +/** + * i3c_master_register() - register an I3C master + * @master: master used to send frames on the bus + * @parent: the parent device (the one that provides this I3C master + * controller) + * @ops: the master controller operations + * @secondary: true if you are registering a secondary master. Will return + * -EOPNOTSUPP if set to true since secondary masters are not yet + * supported + * + * This function takes care of everything for you: + * + * - creates and initializes the I3C bus + * - populates the bus with static I2C devs if @parent->of_node is not + * NULL + * - registers all I3C devices added by the controller during bus + * initialization + * - registers the I2C adapter and all I2C devices + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int i3c_master_register(struct i3c_master_controller *master, + struct device *parent, + const struct i3c_master_controller_ops *ops, + bool secondary) +{ + return i3c_master_register_fwnode(master, parent, + dev_fwnode(parent), + ops, secondary); +} EXPORT_SYMBOL_GPL(i3c_master_register); /** @@ -3847,11 +4091,38 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers, if (!master || !xfers) return -EINVAL; - if (mode != I3C_SDR && !(master->this->info.hdr_cap & BIT(mode))) + if (mode != I3C_SDR && !(i3c_dev_supported_xfer_modes_locked(dev) & BIT(mode))) return -EOPNOTSUPP; return master->ops->i3c_xfers(dev, xfers, nxfers, mode); } +EXPORT_SYMBOL_GPL(i3c_dev_do_xfers_locked); + +/** + * i3c_dev_disable_ibi_controller_locked() - Disable IBI in the controller + * @dev: I3C device descriptor + * + * Invoke the current controller's IBI disable callback without waiting for + * pending IBIs or updating the generic IBI enabled state. + * + * Context: The caller must serialize access to @dev->ibi and the generic + * IBI lifecycle. + * + * Return: 0 on success, or a negative error code. + */ +int i3c_dev_disable_ibi_controller_locked(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *master = i3c_dev_get_master(dev); + + if (!dev->ibi) + return -EINVAL; + + if (!master->ops->disable_ibi) + return -EOPNOTSUPP; + + return master->ops->disable_ibi(dev); +} +EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_controller_locked); /** * i3c_dev_disable_ibi_locked() - Disable IBIs coming from a specific device @@ -3865,14 +4136,9 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers, */ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev) { - struct i3c_master_controller *master; int ret; - if (!dev->ibi) - return -EINVAL; - - master = i3c_dev_get_master(dev); - ret = master->ops->disable_ibi(dev); + ret = i3c_dev_disable_ibi_controller_locked(dev); if (ret) return ret; @@ -3886,6 +4152,32 @@ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev) } EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked); +/** + * i3c_dev_enable_ibi_controller_locked() - Enable controller IBI resources + * @dev: I3C device descriptor + * + * Invoke the current controller's IBI enable callback without updating the + * generic IBI enabled state. + * + * Context: The caller must serialize access to @dev->ibi and the generic + * IBI lifecycle. + * + * Return: 0 on success, or a negative error code. + */ +int i3c_dev_enable_ibi_controller_locked(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *master = i3c_dev_get_master(dev); + + if (!dev->ibi) + return -EINVAL; + + if (!master->ops->enable_ibi) + return -EOPNOTSUPP; + + return master->ops->enable_ibi(dev); +} +EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_controller_locked); + /** * i3c_dev_enable_ibi_locked() - Enable IBIs from a specific device (lock held) * @dev: device on which IBIs should be enabled @@ -3902,13 +4194,9 @@ EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked); */ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev) { - struct i3c_master_controller *master = i3c_dev_get_master(dev); int ret; - if (!dev->ibi) - return -EINVAL; - - ret = master->ops->enable_ibi(dev); + ret = i3c_dev_enable_ibi_controller_locked(dev); if (!ret) dev->ibi->enabled = true; @@ -3916,6 +4204,35 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev) } EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_locked); +/** + * i3c_dev_request_ibi_controller_locked() - Request controller IBI resources + * @dev: I3C device descriptor + * @req: IBI setup request + * + * Invoke the current controller's IBI request callback without allocating the + * generic IBI object or workqueue. The caller must ensure that @dev->ibi has + * already been initialized. + * + * Context: The caller must serialize access to @dev->ibi and the generic + * IBI lifecycle. + * + * Return: 0 on success, or a negative error code. + */ +int i3c_dev_request_ibi_controller_locked(struct i3c_dev_desc *dev, + const struct i3c_ibi_setup *req) +{ + struct i3c_master_controller *master = i3c_dev_get_master(dev); + + if (!dev->ibi) + return -EINVAL; + + if (!master->ops->request_ibi) + return -EOPNOTSUPP; + + return master->ops->request_ibi(dev, req); +} +EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_controller_locked); + /** * i3c_dev_request_ibi_locked() - Request an IBI * @dev: device for which we should enable IBIs @@ -3945,7 +4262,8 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, if (!ibi) return -ENOMEM; - ibi->wq = alloc_ordered_workqueue(dev_name(i3cdev_to_dev(dev->dev)), WQ_MEM_RECLAIM); + ibi->wq = alloc_ordered_workqueue(dev_name(i3cdev_to_dev(dev->dev)), + WQ_MEM_RECLAIM | WQ_HIGHPRI); if (!ibi->wq) { kfree(ibi); return -ENOMEM; @@ -3958,8 +4276,15 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, ibi->num_slots = req->num_slots; dev->ibi = ibi; - ret = master->ops->request_ibi(dev, req); + ret = i3c_dev_request_ibi_controller_locked(dev, req); if (ret) { + /* + * The controller request callback failed, so tear down the + * workqueue allocated above before freeing the IBI object. + * This is the owner of the workqueue, so it must destroy it + * here to avoid leaking it on the error path. + */ + destroy_workqueue(ibi->wq); kfree(ibi); dev->ibi = NULL; } @@ -3968,6 +4293,27 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, } EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_locked); +/** + * i3c_dev_free_ibi_controller_locked() - Free controller IBI resources + * @dev: I3C device descriptor + * + * Invoke the current controller's IBI free callback without destroying the + * generic IBI workqueue or freeing @dev->ibi. + * + * Context: The caller must serialize access to @dev->ibi and the generic + * IBI lifecycle. + */ +void i3c_dev_free_ibi_controller_locked(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *master = i3c_dev_get_master(dev); + + if (!dev->ibi) + return; + + master->ops->free_ibi(dev); +} +EXPORT_SYMBOL_GPL(i3c_dev_free_ibi_controller_locked); + /** * i3c_dev_free_ibi_locked() - Free all resources needed for IBI handling * @dev: device on which you want to release IBI resources @@ -3998,7 +4344,7 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev) dev_err(&master->dev, "Failed to disable IBI before freeing\n"); } - master->ops->free_ibi(dev); + i3c_dev_free_ibi_controller_locked(dev); if (dev->ibi->wq) { destroy_workqueue(dev->ibi->wq); diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c index b35386260350..ad57b7500678 100644 --- a/drivers/i3c/master/adi-i3c-master.c +++ b/drivers/i3c/master/adi-i3c-master.c @@ -115,6 +115,7 @@ struct adi_i3c_master { spinlock_t lock; /* Protect transfer */ } xferqueue; void __iomem *regs; + int irq; struct clk *clk; unsigned long i3c_scl_lim; struct { @@ -957,6 +958,7 @@ static int adi_i3c_master_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; + master->irq = irq; version = readl(master->regs + ADI_AXI_REG_VERSION); if (ADI_AXI_PCORE_VER_MAJOR(version) != 1) @@ -1001,6 +1003,8 @@ static void adi_i3c_master_remove(struct platform_device *pdev) writel(0x00, master->regs + REG_IRQ_MASK); writel(0x01, master->regs + REG_ENABLE); + devm_free_irq(&pdev->dev, master->irq, master); + i3c_master_unregister(&master->base); } diff --git a/drivers/i3c/master/amd-i3c-master.c b/drivers/i3c/master/amd-i3c-master.c index e4da9c38eccb..b62157729a5b 100644 --- a/drivers/i3c/master/amd-i3c-master.c +++ b/drivers/i3c/master/amd-i3c-master.c @@ -806,8 +806,8 @@ static int xi3c_master_send_direct_ccc_cmd(struct xi3c_master *master, /* 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); + ccc->dests[0].payload.actual_len = min(xfer->cmds[1].rx_actual, + ccc->dests[0].payload.len); return ret; } diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c index 4563d8761ba0..d1119f203d1b 100644 --- a/drivers/i3c/master/dw-i3c-master.c +++ b/drivers/i3c/master/dw-i3c-master.c @@ -1657,6 +1657,11 @@ static const struct dw_i3c_platform_ops dw_i3c_platform_ops_default = { .set_dat_ibi = dw_i3c_platform_set_dat_ibi_nop, }; +static void dw_i3c_resets_assert(void *data) +{ + reset_control_assert(data); +} + int dw_i3c_common_probe(struct dw_i3c_master *master, struct platform_device *pdev) { @@ -1696,10 +1701,17 @@ int dw_i3c_common_probe(struct dw_i3c_master *master, if (IS_ERR(master->pclk)) return PTR_ERR(master->pclk); - master->core_rst = devm_reset_control_get_optional_exclusive_deasserted(&pdev->dev, - NULL); - if (IS_ERR(master->core_rst)) - return PTR_ERR(master->core_rst); + master->resets = devm_reset_control_array_get_optional_exclusive(&pdev->dev); + if (IS_ERR(master->resets)) + return PTR_ERR(master->resets); + + ret = reset_control_deassert(master->resets); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, dw_i3c_resets_assert, master->resets); + if (ret) + return ret; spin_lock_init(&master->xferqueue.lock); INIT_LIST_HEAD(&master->xferqueue.list); @@ -1873,7 +1885,7 @@ static int __maybe_unused dw_i3c_master_runtime_suspend(struct device *dev) dw_i3c_master_disable(master); - reset_control_assert(master->core_rst); + reset_control_assert(master->resets); dw_i3c_master_disable_clks(master); pinctrl_pm_select_sleep_state(dev); return 0; @@ -1885,7 +1897,7 @@ static int __maybe_unused dw_i3c_master_runtime_resume(struct device *dev) pinctrl_pm_select_default_state(dev); dw_i3c_master_enable_clks(master); - reset_control_deassert(master->core_rst); + reset_control_deassert(master->resets); dw_i3c_master_set_intr_regs(master); dw_i3c_master_restore_timing_regs(master); @@ -1934,8 +1946,8 @@ 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 dw_i3c_master_acpi_match[] = { - { "AMDI0015", AMD_I3C_OD_PP_TIMING }, - { "NVDA2018", DW_I3C_ACPI_SKIP_CLK_RST }, + { .id = "AMDI0015", .driver_data = AMD_I3C_OD_PP_TIMING }, + { .id = "NVDA2018", .driver_data = DW_I3C_ACPI_SKIP_CLK_RST }, { } }; MODULE_DEVICE_TABLE(acpi, dw_i3c_master_acpi_match); diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c-master.h index 17ad817d1f8e..f5b4491b247a 100644 --- a/drivers/i3c/master/dw-i3c-master.h +++ b/drivers/i3c/master/dw-i3c-master.h @@ -36,7 +36,7 @@ struct dw_i3c_master { } xferqueue; struct dw_i3c_master_caps caps; void __iomem *regs; - struct reset_control *core_rst; + struct reset_control *resets; struct clk *core_clk; struct clk *pclk; char version[5]; diff --git a/drivers/i3c/master/mipi-i3c-hci/cmd.h b/drivers/i3c/master/mipi-i3c-hci/cmd.h index 7bada7b4b2de..fa3107df7c51 100644 --- a/drivers/i3c/master/mipi-i3c-hci/cmd.h +++ b/drivers/i3c/master/mipi-i3c-hci/cmd.h @@ -24,7 +24,7 @@ */ #define RESP_STATUS(resp) FIELD_GET(GENMASK(31, 28), resp) #define RESP_TID(resp) FIELD_GET(GENMASK(27, 24), resp) -#define RESP_DATA_LENGTH(resp) FIELD_GET(GENMASK(21, 0), resp) +#define RESP_DATA_LENGTH(resp) FIELD_GET(GENMASK(15, 0), resp) #define RESP_ERR_FIELD GENMASK(31, 28) @@ -55,7 +55,7 @@ struct hci_cmd_ops { int (*prep_ccc)(struct i3c_hci *hci, struct hci_xfer *xfer, u8 ccc_addr, u8 ccc_cmd, bool raw); void (*prep_i3c_xfer)(struct i3c_hci *hci, struct i3c_dev_desc *dev, - struct hci_xfer *xfer); + struct hci_xfer *xfer, enum i3c_xfer_mode mode); void (*prep_i2c_xfer)(struct i3c_hci *hci, struct i2c_dev_desc *dev, struct hci_xfer *xfer); int (*perform_daa)(struct i3c_hci *hci); diff --git a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c index 3b9345718d27..78ad2ec2bd93 100644 --- a/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c +++ b/drivers/i3c/master/mipi-i3c-hci/cmd_v1.c @@ -108,8 +108,6 @@ enum hci_cmd_mode { MODE_I3C_SDR4 = 0x4, MODE_I3C_HDR_TSx = 0x5, MODE_I3C_HDR_DDR = 0x6, - MODE_I3C_HDR_BT = 0x7, - MODE_I3C_Fm_FmP = 0x8, MODE_I2C_Fm = 0x0, MODE_I2C_FmP = 0x1, MODE_I2C_UD1 = 0x2, @@ -217,14 +215,30 @@ static int hci_cmd_v1_prep_ccc(struct i3c_hci *hci, static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci, struct i3c_dev_desc *dev, - struct hci_xfer *xfer) + struct hci_xfer *xfer, + enum i3c_xfer_mode xfer_mode) { struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev); unsigned int dat_idx = dev_data->dat_idx; - enum hci_cmd_mode mode = get_i3c_mode(hci); + enum hci_cmd_mode mode; u8 *data = xfer->data; unsigned int data_len = xfer->data_len; bool rnw = xfer->rnw; + u32 cp_cmd = 0; + + if (xfer_mode == I3C_SDR) { + mode = get_i3c_mode(hci); + } else { + /* + * HDR-DDR is the only advertised HDR Mode, so a non-SDR + * transfer uses the HDR-DDR Command Code encoding: CP marks CMD + * as valid, CMD carries bits[6:0], and RNW carries bit[7]. CP + * and CMD occupy the same descriptor bits in both command + * formats. + */ + mode = MODE_I3C_HDR_DDR; + cp_cmd = CMD_R0_CP | CMD_R0_CMD(xfer->hdr_cmd & I3C_HDR_CMD_CODE); + } xfer->cmd_tid = hci_get_tid(); @@ -235,7 +249,8 @@ static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci, CMD_I0_TID(xfer->cmd_tid) | CMD_I0_DEV_INDEX(dat_idx) | CMD_I0_DTT(data_len) | - CMD_I0_MODE(mode); + CMD_I0_MODE(mode) | + cp_cmd; fill_data_bytes(xfer, data, data_len); } else { /* we use a Regular Data Transfer Command */ @@ -244,7 +259,8 @@ static void hci_cmd_v1_prep_i3c_xfer(struct i3c_hci *hci, CMD_R0_TID(xfer->cmd_tid) | CMD_R0_DEV_INDEX(dat_idx) | CMD_R0_MODE(mode) | - (rnw ? CMD_R0_RNW : 0); + (rnw ? CMD_R0_RNW : 0) | + cp_cmd; xfer->cmd_desc[1] = CMD_R1_DATA_LENGTH(data_len); } diff --git a/drivers/i3c/master/mipi-i3c-hci/cmd_v2.c b/drivers/i3c/master/mipi-i3c-hci/cmd_v2.c index 8d93748e858d..b5e25f05f03e 100644 --- a/drivers/i3c/master/mipi-i3c-hci/cmd_v2.c +++ b/drivers/i3c/master/mipi-i3c-hci/cmd_v2.c @@ -212,9 +212,11 @@ static int hci_cmd_v2_prep_ccc(struct i3c_hci *hci, struct hci_xfer *xfer, return 0; } +/* For cmd_v2, only SDR capability is advertised */ static void hci_cmd_v2_prep_i3c_xfer(struct i3c_hci *hci, struct i3c_dev_desc *dev, - struct hci_xfer *xfer) + struct hci_xfer *xfer, + enum i3c_xfer_mode xfer_mode) { unsigned int mode = XFERMODE_IDX_I3C_SDR; unsigned int rate = get_i3c_rate_idx(hci); diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c index dadf049bd4b5..23853ca134fb 100644 --- a/drivers/i3c/master/mipi-i3c-hci/core.c +++ b/drivers/i3c/master/mipi-i3c-hci/core.c @@ -51,12 +51,10 @@ #define HC_CAP_SG_DC_EN BIT(30) #define HC_CAP_SG_IBI_EN BIT(29) #define HC_CAP_SG_CR_EN BIT(28) -#define HC_CAP_MAX_DATA_LENGTH GENMASK(24, 22) #define HC_CAP_CMD_SIZE GENMASK(21, 20) #define HC_CAP_DIRECT_COMMANDS_EN BIT(18) #define HC_CAP_MULTI_LANE_EN BIT(15) #define HC_CAP_CMD_CCC_DEFBYTE BIT(10) -#define HC_CAP_HDR_BT_EN BIT(8) #define HC_CAP_HDR_TS_EN BIT(7) #define HC_CAP_HDR_DDR_EN BIT(6) #define HC_CAP_NON_CURRENT_MASTER_CAP BIT(5) /* master handoff capable */ @@ -118,6 +116,13 @@ static inline struct i3c_hci *to_i3c_hci(struct i3c_master_controller *m) return container_of(m, struct i3c_hci, master); } +/* HDR support has been added for cmd_v1 only */ +static bool i3c_hci_hdr_ddr_capable(struct i3c_hci *hci) +{ + return hci->cmd == &mipi_i3c_hci_cmd_v1 && + hci->caps & HC_CAP_HDR_DDR_EN; +} + /** * i3c_hci_sysdev() - Get the device to use for DMA and system PM * @dev: Device the HCI controller is bound to @@ -160,6 +165,8 @@ static int i3c_hci_bus_init(struct i3c_master_controller *m) i3c_hci_set_master_dyn_addr(hci); memset(&info, 0, sizeof(info)); info.dyn_addr = hci->dyn_addr; + if (i3c_hci_hdr_ddr_capable(hci)) + info.hdr_cap = BIT(I3C_HDR_DDR); ret = i3c_master_set_info(m, &info); if (ret) return ret; @@ -465,6 +472,27 @@ static int i3c_hci_daa(struct i3c_master_controller *m) return ret; } +static bool i3c_hci_rnw(struct i3c_xfer *i3c_xfer, enum i3c_xfer_mode mode) +{ + if (mode == I3C_SDR) + return i3c_xfer->rnw; + + return i3c_xfer->cmd & I3C_HDR_CMD_RNW; +} + +static int i3c_hci_check_hdr_ddr_xfers(struct i3c_xfer *i3c_xfers, int nxfers) +{ + /* + * HDR-DDR frames 16-bit Data Words, and at least one Data Word must + * follow the Command Word. + */ + for (int i = 0; i < nxfers; i++) + if (i3c_xfers[i].len < 2 || i3c_xfers[i].len % 2) + return -EINVAL; + + return 0; +} + static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_xfers, int nxfers, enum i3c_xfer_mode mode) @@ -473,30 +501,31 @@ static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_hci *hci = to_i3c_hci(m); struct hci_xfer *xfer; DECLARE_COMPLETION_ONSTACK(done); - unsigned int size_limit; int i, last, ret = 0; dev_dbg(&hci->master.dev, "nxfers = %d", nxfers); + if (mode == I3C_HDR_DDR) { + ret = i3c_hci_check_hdr_ddr_xfers(i3c_xfers, nxfers); + if (ret) + return ret; + } + xfer = hci_alloc_xfer(nxfers); if (!xfer) return -ENOMEM; - size_limit = 1U << (16 + FIELD_GET(HC_CAP_MAX_DATA_LENGTH, hci->caps)); - for (i = 0; i < nxfers; i++) { xfer[i].data_len = i3c_xfers[i].len; - ret = -EFBIG; - if (xfer[i].data_len >= size_limit) - goto out; - xfer[i].rnw = i3c_xfers[i].rnw; - if (i3c_xfers[i].rnw) { + xfer[i].rnw = i3c_hci_rnw(i3c_xfers + i, mode); + xfer[i].hdr_cmd = i3c_xfers[i].cmd; + if (xfer[i].rnw) { xfer[i].data = i3c_xfers[i].data.in; } else { /* silence the const qualifier warning with a cast */ xfer[i].data = (void *) i3c_xfers[i].data.out; } - hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i]); + hci->cmd->prep_i3c_xfer(hci, dev, &xfer[i], mode); xfer[i].cmd_desc[0] |= CMD_0_ROC; } last = i - 1; @@ -508,7 +537,7 @@ static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev, if (ret) goto out; for (i = 0; i < nxfers; i++) { - if (i3c_xfers[i].rnw) + if (xfer[i].rnw) i3c_xfers[i].len = RESP_DATA_LENGTH(xfer[i].response); if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) { ret = -EIO; @@ -563,6 +592,11 @@ static int i3c_hci_i2c_xfers(struct i2c_dev_desc *dev, return ret; } +static void i3c_hci_dat_v1_set_curr_nack_retry(struct i3c_hci *hci, unsigned int dat_idx) +{ + mipi_i3c_hci_dat_v1.set_nack_retry(hci, dat_idx, hci->master.dev_nack_retry_count); +} + static int i3c_hci_attach_i3c_dev(struct i3c_dev_desc *dev) { struct i3c_master_controller *m = i3c_dev_get_master(dev); @@ -581,6 +615,7 @@ static int i3c_hci_attach_i3c_dev(struct i3c_dev_desc *dev) } mipi_i3c_hci_dat_v1.set_dynamic_addr(hci, ret, dev->info.dyn_addr ?: dev->info.static_addr); + i3c_hci_dat_v1_set_curr_nack_retry(hci, ret); dev_data->dat_idx = ret; } i3c_dev_set_master_data(dev, dev_data); @@ -630,6 +665,7 @@ static int i3c_hci_attach_i2c_dev(struct i2c_dev_desc *dev) } mipi_i3c_hci_dat_v1.set_static_addr(hci, ret, dev->addr); mipi_i3c_hci_dat_v1.set_flags(hci, ret, DAT_0_I2C_DEVICE, 0); + i3c_hci_dat_v1_set_curr_nack_retry(hci, ret); dev_data->dat_idx = ret; i2c_dev_set_master_data(dev, dev_data); return 0; @@ -675,11 +711,13 @@ static void __i3c_hci_disable_ibi(struct i3c_hci *hci, struct i3c_dev_desc *dev) static void i3c_hci_free_ibi(struct i3c_dev_desc *dev) { + struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev); struct i3c_master_controller *m = i3c_dev_get_master(dev); struct i3c_hci *hci = to_i3c_hci(m); - /* Must ensure the IBI has been disabled */ - __i3c_hci_disable_ibi(hci, dev); + /* Must ensure IBIs for this device will no longer be processed */ + scoped_guard(spinlock_irqsave, &hci->lock) + hci->ibi_devs[dev_data->dat_idx] = NULL; hci->io->free_ibi(hci, dev); } @@ -703,11 +741,15 @@ static int i3c_hci_enable_ibi(struct i3c_dev_desc *dev) struct i3c_master_controller *m = i3c_dev_get_master(dev); struct i3c_hci *hci = to_i3c_hci(m); struct i3c_hci_dev_data *dev_data = i3c_dev_get_master_data(dev); + int ret; mipi_i3c_hci_dat_v1.clear_flags(hci, dev_data->dat_idx, DAT_0_SIR_REJECT, 0); scoped_guard(spinlock_irqsave, &hci->lock) hci->ibi_devs[dev_data->dat_idx] = dev; - return i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR); + ret = i3c_master_enec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR); + if (ret) + __i3c_hci_disable_ibi(hci, dev); + return ret; } static int i3c_hci_disable_ibi(struct i3c_dev_desc *dev) @@ -715,13 +757,14 @@ static int i3c_hci_disable_ibi(struct i3c_dev_desc *dev) struct i3c_master_controller *m = i3c_dev_get_master(dev); struct i3c_hci *hci = to_i3c_hci(m); - __i3c_hci_disable_ibi(hci, dev); /* - * The DAT entry is now set to NACK and DISEC this target's IBIs, so + * The DAT entry will be set to NACK and DISEC this target's IBIs, so * the IBI teardown can proceed even if DISEC below fails, so ignore * errors. */ i3c_master_disec_locked(m, dev->info.dyn_addr, I3C_CCC_EVENT_SIR); + + __i3c_hci_disable_ibi(hci, dev); return 0; } @@ -734,6 +777,23 @@ static void i3c_hci_recycle_ibi_slot(struct i3c_dev_desc *dev, hci->io->recycle_ibi_slot(hci, dev, slot); } +static int i3c_hci_set_dev_nack_retry(struct i3c_master_controller *m, unsigned int cnt) +{ + struct i3c_hci *hci = to_i3c_hci(m); + unsigned int dat_idx; + + if (hci->cmd != &mipi_i3c_hci_cmd_v1) + return -EOPNOTSUPP; + + if (cnt > FIELD_MAX(DAT_0_DEV_NACK_RETRY_CNT)) + return -ERANGE; + + for_each_set_bit(dat_idx, hci->DAT_data, hci->DAT_entries) + mipi_i3c_hci_dat_v1.set_nack_retry(hci, dat_idx, cnt); + + return 0; +} + static const struct i3c_master_controller_ops i3c_hci_ops = { .bus_init = i3c_hci_bus_init, .bus_cleanup = i3c_hci_bus_cleanup, @@ -753,6 +813,7 @@ static const struct i3c_master_controller_ops i3c_hci_ops = { .recycle_ibi_slot = i3c_hci_recycle_ibi_slot, .enable_hotjoin = i3c_hci_enable_hotjoin, .disable_hotjoin = i3c_hci_disable_hotjoin, + .set_dev_nack_retry = i3c_hci_set_dev_nack_retry, }; static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id) @@ -1142,8 +1203,10 @@ static int i3c_hci_probe(struct platform_device *pdev) * necessarily in separate contiguous sub-ranges. To avoid overlapping * mappings, provide base_regs from the parent mapping. */ - if (pdata) + if (pdata) { hci->base_regs = pdata->base_regs; + hci->master.instance = pdata->instance; + } if (!hci->base_regs) { hci->base_regs = devm_platform_ioremap_resource(pdev, 0); @@ -1185,6 +1248,13 @@ static int i3c_hci_probe(struct platform_device *pdev) if (device_can_wakeup(i3c_hci_sysdev(&pdev->dev))) hci->master.ibi_wakeup = true; + /* + * HCI v1.1 onward does 1 retry for Direct CCCs anyway, so for v1.0 to + * be consistent, promote 0 to 1. + */ + if (!hci->master.dev_nack_retry_count) + hci->master.dev_nack_retry_count = 1; + return i3c_master_register(&hci->master, &pdev->dev, &i3c_hci_ops, false); } @@ -1205,8 +1275,10 @@ static const __maybe_unused struct of_device_id i3c_hci_of_match[] = { MODULE_DEVICE_TABLE(of, i3c_hci_of_match); static const struct acpi_device_id i3c_hci_acpi_match[] = { - { "AMDI5017", HCI_QUIRK_PIO_MODE | HCI_QUIRK_OD_PP_TIMING | HCI_QUIRK_RESP_BUF_THLD }, - {} + { .id = "AMDI5017", + .driver_data = HCI_QUIRK_PIO_MODE | HCI_QUIRK_OD_PP_TIMING | + HCI_QUIRK_RESP_BUF_THLD }, + { } }; MODULE_DEVICE_TABLE(acpi, i3c_hci_acpi_match); diff --git a/drivers/i3c/master/mipi-i3c-hci/dat.h b/drivers/i3c/master/mipi-i3c-hci/dat.h index 6881f19da77f..31becc6f9aec 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dat.h +++ b/drivers/i3c/master/mipi-i3c-hci/dat.h @@ -12,6 +12,7 @@ /* Global DAT flags */ #define DAT_0_I2C_DEVICE W0_BIT_(31) +#define DAT_0_DEV_NACK_RETRY_CNT W0_MASK(30, 29) #define DAT_0_SIR_REJECT W0_BIT_(13) #define DAT_0_IBI_PAYLOAD W0_BIT_(12) @@ -25,6 +26,7 @@ struct hci_dat_ops { void (*clear_flags)(struct i3c_hci *hci, unsigned int dat_idx, u32 w0, u32 w1); int (*get_index)(struct i3c_hci *hci, u8 address); void (*restore)(struct i3c_hci *hci); + void (*set_nack_retry)(struct i3c_hci *hci, unsigned int dat_idx, unsigned int cnt); }; extern const struct hci_dat_ops mipi_i3c_hci_dat_v1; diff --git a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c index 852966aa20d9..ec0764a27c62 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dat_v1.c +++ b/drivers/i3c/master/mipi-i3c-hci/dat_v1.c @@ -24,7 +24,7 @@ #define DAT_1_AUTOCMD_VALUE W1_MASK(47, 40) #define DAT_1_AUTOCMD_MASK W1_MASK(39, 32) /* DAT_0_I2C_DEVICE W0_BIT_(31) */ -#define DAT_0_DEV_NACK_RETRY_CNT W0_MASK(30, 29) +/* DAT_0_DEV_NACK_RETRY_CNT W0_MASK(30, 29) */ #define DAT_0_RING_ID W0_MASK(28, 26) #define DAT_0_DYNADDR_PARITY W0_BIT_(23) #define DAT_0_DYNAMIC_ADDRESS W0_MASK(22, 16) @@ -189,6 +189,16 @@ static void hci_dat_v1_restore(struct i3c_hci *hci) } } +static void hci_dat_v1_set_nack_retry(struct i3c_hci *hci, unsigned int dat_idx, unsigned int cnt) +{ + u32 dat_w0; + + dat_w0 = dat_w0_read(dat_idx); + dat_w0 &= ~DAT_0_DEV_NACK_RETRY_CNT; + dat_w0 |= FIELD_PREP(DAT_0_DEV_NACK_RETRY_CNT, cnt); + dat_w0_write(dat_idx, dat_w0); +} + const struct hci_dat_ops mipi_i3c_hci_dat_v1 = { .init = hci_dat_v1_init, .alloc_entry = hci_dat_v1_alloc_entry, @@ -199,4 +209,5 @@ const struct hci_dat_ops mipi_i3c_hci_dat_v1 = { .clear_flags = hci_dat_v1_clear_flags, .get_index = hci_dat_v1_get_index, .restore = hci_dat_v1_restore, + .set_nack_retry = hci_dat_v1_set_nack_retry, }; diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c index 7c2b20474130..b8fcea0870ab 100644 --- a/drivers/i3c/master/mipi-i3c-hci/dma.c +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "hci.h" #include "cmd.h" @@ -273,29 +274,6 @@ static void hci_dma_init_rings(struct i3c_hci *hci) hci_dma_init_rh(hci, &rings->headers[i], i); } -static void hci_dma_suspend(struct i3c_hci *hci) -{ - struct hci_rings_data *rings = hci->io_data; - int n = rings ? rings->total : 0; - - for (int i = 0; i < n; i++) { - struct hci_rh_data *rh = &rings->headers[i]; - - rh_reg_write(INTR_SIGNAL_ENABLE, 0); - rh_reg_write(RING_CONTROL, 0); - } - - i3c_hci_sync_irq_inactive(hci); -} - -static void hci_dma_resume(struct i3c_hci *hci) -{ - struct hci_rings_data *rings = hci->io_data; - - if (rings) - hci_dma_init_rings(hci); -} - static int hci_dma_init(struct i3c_hci *hci) { struct hci_rings_data *rings; @@ -428,7 +406,7 @@ static void hci_dma_unmap_xfer(struct i3c_hci *hci, static struct i3c_dma *hci_dma_map_xfer(struct device *dev, struct hci_xfer *xfer) { enum dma_data_direction dir = xfer->rnw ? DMA_FROM_DEVICE : DMA_TO_DEVICE; - bool need_bounce = device_iommu_mapped(dev) && xfer->rnw && (xfer->data_len & 3); + bool need_bounce = xfer->rnw && (xfer->data_len & 3); return i3c_master_dma_map_single(dev, xfer->data, xfer->data_len, need_bounce, dir); } @@ -868,25 +846,24 @@ static void hci_dma_recycle_ibi_slot(struct i3c_hci *hci, i3c_generic_ibi_recycle_slot(dev_ibi->pool, slot); } -static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh) +static bool hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh, + u32 *op1_val, unsigned int enq_ptr) { struct hci_rings_data *rings = hci->io_data; struct i3c_dev_desc *dev; struct i3c_hci_dev_data *dev_data; struct hci_dma_dev_ibi_data *dev_ibi; struct i3c_ibi_slot *slot; - u32 op1_val, op2_val, ibi_status_error; - unsigned int ptr, enq_ptr, deq_ptr; + u32 ibi_status_error; + unsigned int ptr, deq_ptr; unsigned int ibi_size, ibi_chunks, ibi_data_offset, first_part; int ibi_addr, last_ptr; void *ring_ibi_data; dma_addr_t ring_ibi_data_dma; - op1_val = rh_reg_read(RING_OPERATION1); - deq_ptr = FIELD_GET(RING_OP1_IBI_DEQ_PTR, op1_val); - - op2_val = rh_reg_read(RING_OPERATION2); - enq_ptr = FIELD_GET(RING_OP2_IBI_ENQ_PTR, op2_val); + deq_ptr = FIELD_GET(RING_OP1_IBI_DEQ_PTR, *op1_val); + if (deq_ptr == enq_ptr) + return false; ibi_status_error = 0; ibi_addr = -1; @@ -936,7 +913,7 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh) dev_dbg(&hci->master.dev, "no LAST_STATUS available (e=%d d=%d)", enq_ptr, deq_ptr); - return; + return false; } deq_ptr = last_ptr + 1; deq_ptr %= rh->ibi_status_entries; @@ -1015,10 +992,9 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh) i3c_master_queue_ibi(dev, slot); done: - op1_val = rh_reg_read(RING_OPERATION1); - op1_val &= ~RING_OP1_IBI_DEQ_PTR; - op1_val |= FIELD_PREP(RING_OP1_IBI_DEQ_PTR, deq_ptr); - rh_reg_write(RING_OPERATION1, op1_val); + *op1_val &= ~RING_OP1_IBI_DEQ_PTR; + *op1_val |= FIELD_PREP(RING_OP1_IBI_DEQ_PTR, deq_ptr); + rh_reg_write(RING_OPERATION1, *op1_val); /* update the chunk pointer */ rh->ibi_chunk_ptr += ibi_chunks; @@ -1026,6 +1002,19 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh) /* and tell the hardware about freed chunks */ rh_reg_write(CHUNK_CONTROL, rh_reg_read(CHUNK_CONTROL) + ibi_chunks); + + return true; +} + +static void hci_dma_drain_ibi_ring(struct i3c_hci *hci, struct hci_rh_data *rh) +{ + u32 op1_val = rh_reg_read(RING_OPERATION1); + u32 op2_val = rh_reg_read(RING_OPERATION2); + unsigned int enq_ptr = FIELD_GET(RING_OP2_IBI_ENQ_PTR, op2_val); + + /* Loop is bounded by enq_ptr. Further IBIs will re-assert INTR_IBI_READY */ + while (hci_dma_process_ibi(hci, rh, &op1_val, enq_ptr)) + ; } static bool hci_dma_irq_handler(struct i3c_hci *hci) @@ -1047,7 +1036,7 @@ static bool hci_dma_irq_handler(struct i3c_hci *hci) rh_reg_write(INTR_STATUS, status); if (status & INTR_IBI_READY) - hci_dma_process_ibi(hci, rh); + hci_dma_drain_ibi_ring(hci, rh); if (status & (INTR_TRANSFER_COMPLETION | INTR_TRANSFER_ERR)) hci_dma_xfer_done(hci, rh); if (status & INTR_RING_OP) @@ -1064,6 +1053,77 @@ static bool hci_dma_irq_handler(struct i3c_hci *hci) return handled; } +/* + * With the bus disabled, a ring should stop within a few microseconds. The + * timeout is therefore only expected to expire if the hardware is stuck. + * Allow sufficient margin for slow systems, but keep the delay acceptable + * during suspend. + */ +#define RING_STOP_TIMEOUT_US (100 * USEC_PER_MSEC) +/* + * The ring is usually already stopped, so polling typically completes on the + * first iteration. Use a modest sleep interval to avoid busy-waiting without + * adding excessive latency. + */ +#define RING_STOP_SLEEP_US 100 + +static void hci_dma_suspend(struct i3c_hci *hci) +{ + struct hci_rings_data *rings = hci->io_data; + int n = rings ? rings->total : 0; + struct hci_rh_data *rh; + u32 regval; + + /* Gracefully stop the rings */ + scoped_guard(spinlock_irqsave, &hci->lock) { + for (int i = 0; i < n; i++) { + rh = &rings->headers[i]; + regval = rh_reg_read(RING_CONTROL); + if (regval & RING_CTRL_RUN_STOP) + rh_reg_write(RING_CONTROL, regval & ~RING_CTRL_RUN_STOP); + } + } + + /* Wait for actual stop */ + for (int i = 0; i < n; i++) { + rh = &rings->headers[i]; + if (readx_poll_timeout(readl, rh->regs + RH_RING_STATUS, regval, + !(regval & RING_STATUS_RUNNING), + RING_STOP_SLEEP_US, RING_STOP_TIMEOUT_US)) + dev_err(&hci->master.dev, "%s: Ring did not stop, status %#x\n", + __func__, regval); + } + + /* + * With the rings stopped, no more IBIs can be received. Flush and make + * the interrupt handler inactive. + */ + i3c_hci_sync_irq_inactive(hci); + + /* Disable interrupt signals and disable the rings */ + scoped_guard(spinlock_irqsave, &hci->lock) + for (int i = 0; i < n; i++) { + rh = &rings->headers[i]; + rh_reg_write(INTR_SIGNAL_ENABLE, 0); + /* + * Be absolutely certain there is no unprocessed IBI. + * hci_dma_drain_ibi_ring() will do nothing if there is + * none. + */ + if (i < IBI_RINGS) + hci_dma_drain_ibi_ring(hci, rh); + rh_reg_write(RING_CONTROL, 0); + } +} + +static void hci_dma_resume(struct i3c_hci *hci) +{ + struct hci_rings_data *rings = hci->io_data; + + if (rings) + hci_dma_init_rings(hci); +} + const struct hci_io_ops mipi_i3c_hci_dma = { .init = hci_dma_init, .cleanup = hci_dma_cleanup, diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h index ee73f6e6756a..4288a1b5b491 100644 --- a/drivers/i3c/master/mipi-i3c-hci/hci.h +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h @@ -85,10 +85,10 @@ struct i3c_hci { /* * Structure to represent a master initiated transfer. - * The rnw, data and data_len fields must be initialized before calling any - * hci->cmd->*() method. The cmd method will initialize cmd_desc[] and - * possibly modify (clear) the data field. Then xfer->cmd_desc[0] can - * be augmented with CMD_0_ROC and/or CMD_0_TOC. + * The rnw, data and data_len (and hdr_cmd for HDR) fields must be initialized + * before calling any hci->cmd->*() method. The cmd method will initialize + * cmd_desc[] and possibly modify (clear) the data field. Then xfer->cmd_desc[0] + * can be augmented with CMD_0_ROC and/or CMD_0_TOC. * The completion field needs to be initialized before queueing with * hci->io->queue_xfer(), and requires CMD_0_ROC to be set. */ @@ -97,6 +97,7 @@ struct hci_xfer { u32 response; bool rnw; bool started; + u8 hdr_cmd; void *data; unsigned int data_len; unsigned int cmd_tid; 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 ab595661db1e..5b8e10d05cee 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 @@ -390,6 +390,7 @@ static void mipi_i3c_hci_pci_setup_cell(struct mipi_i3c_hci_pci *hci, int idx, struct mfd_cell *cell) { data->pdata.base_regs = hci->base + hci->info->instance_offset[idx]; + data->pdata.instance = idx; data->res = DEFINE_RES_IRQ(0); @@ -445,6 +446,8 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, if (ret < 0) return ret; + pci_set_drvdata(pci, hci); + hci->info = (const struct mipi_i3c_hci_pci_info *)id->driver_data; ret = hci->info->init ? hci->info->init(hci) : 0; @@ -455,8 +458,6 @@ static int mipi_i3c_hci_pci_probe(struct pci_dev *pci, if (ret) goto err_exit; - pci_set_drvdata(pci, hci); - mipi_i3c_hci_pci_rpm_allow(&pci->dev); return 0; diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index 0f065b883ee0..e30ea2cb13cb 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -79,6 +79,10 @@ struct i3c_xfer { enum i3c_error_code err; }; +/* For splitting 'cmd' from struct i3c_xfer */ +#define I3C_HDR_CMD_RNW BIT(7) +#define I3C_HDR_CMD_CODE GENMASK(6, 0) + /** * enum i3c_dcr - I3C DCR values * @I3C_DCR_GENERIC_DEVICE: generic I3C device diff --git a/include/linux/i3c/hub.h b/include/linux/i3c/hub.h new file mode 100644 index 000000000000..a368ea9e5ef7 --- /dev/null +++ b/include/linux/i3c/hub.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2026 NXP + * Generic hub definitions and helper interfaces. + */ +#ifndef _LINUX_I3C_HUB_H +#define _LINUX_I3C_HUB_H + +#include +#include + +/** + * struct i3c_hub - Generic I3C hub context + * @ops: Vendor callbacks for port connection control + * @hub_dev: I3C device representing the hub on the parent bus + * @lock: Serializes hub port routing/forwarding; its lockdep class is keyed + * per hub nesting depth in i3c_hub_init(). + */ +struct i3c_hub { + const struct i3c_hub_ops *ops; + struct i3c_device *hub_dev; + struct mutex lock; /* Serializes hub port routing. */ +}; + +struct i3c_hub_controller { + struct i3c_master_controller *parent; + struct i3c_master_controller controller; + struct i3c_hub *hub; +}; + +struct i3c_hub_ops { + void (*enable_port)(struct i3c_master_controller *controller); + void (*disable_port)(struct i3c_master_controller *controller); +}; + +/** + * i3c_hub_enable_port() - Enable hub connection for a controller + * @controller: Virtual controller representing a hub port + * + * Retrieves hub context from controller drvdata and invokes the vendor + * callback to enable the associated port connection. + */ +static inline void i3c_hub_enable_port(struct i3c_master_controller *controller) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return; + + hub = hub_controller->hub; + + if (hub && hub->ops && hub->ops->enable_port) + hub->ops->enable_port(controller); +} + +/** + * i3c_hub_disable_port() - Disable hub connection for a controller + * @controller: Virtual controller representing a hub port + * + * Retrieves hub context from controller drvdata and invokes the vendor + * callback to disable the associated port connection. + */ +static inline void i3c_hub_disable_port(struct i3c_master_controller *controller) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller = dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return; + + hub = hub_controller->hub; + + if (hub && hub->ops && hub->ops->disable_port) + hub->ops->disable_port(controller); +} + +/* + * Controller operations used by the virtual controllers created for hub + * target ports. Hub drivers pass this to i3c_master_register_fwnode(). + */ +extern const struct i3c_master_controller_ops i3c_hub_master_ops; + +void i3c_hub_init(struct i3c_hub *hub, + const struct i3c_hub_ops *ops, + struct i3c_device *hub_dev); + +int i3c_hub_reserve_parent_addrslots_from_dt(struct i3c_hub_controller *hubc, + struct device_node *node); +#endif diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index f7ceec2b4477..d58070a72a49 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -536,6 +536,8 @@ struct i3c_master_controller_ops { * Bit 0: SETDASA * Bit 1: SETAASA * All other bits are reserved. + * @instance: Zero-based instance number of the Bus Controller as defined by the + * DisCo specification I3C Target Address (_ADR) Encoding * @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 @@ -573,6 +575,7 @@ struct i3c_master_controller { } boardinfo; struct i3c_bus bus; u8 addr_method; + u8 instance; struct workqueue_struct *wq; struct work_struct hj_work; struct work_struct reg_work; @@ -649,9 +652,18 @@ DEFINE_FREE(i3c_master_dma_unmap_single, void *, int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev, u8 old_dyn_addr); +int i3c_master_send_ccc_cmd(struct i3c_master_controller *master, + struct i3c_ccc_cmd *cmd); +bool i3c_master_supports_ccc_cmd(struct i3c_master_controller *master, + const struct i3c_ccc_cmd *cmd); int i3c_master_set_info(struct i3c_master_controller *master, const struct i3c_device_info *info); +int i3c_master_register_fwnode(struct i3c_master_controller *master, + struct device *parent, + struct fwnode_handle *fwnode, + const struct i3c_master_controller_ops *ops, + bool secondary); int i3c_master_register(struct i3c_master_controller *master, struct device *parent, const struct i3c_master_controller_ops *ops, @@ -775,4 +787,12 @@ void i3c_for_each_bus_locked(int (*fn)(struct i3c_bus *bus, void *data), int i3c_register_notifier(struct notifier_block *nb); int i3c_unregister_notifier(struct notifier_block *nb); +enum i3c_addr_slot_status +i3c_bus_get_addr_slot_status(struct i3c_bus *bus, u16 addr); + +void i3c_bus_set_addr_slot_status(struct i3c_bus *bus, u16 addr, + enum i3c_addr_slot_status status); + +void i3c_bus_maintenance_lock(struct i3c_bus *bus); +void i3c_bus_maintenance_unlock(struct i3c_bus *bus); #endif /* I3C_MASTER_H */ diff --git a/include/linux/platform_data/mipi-i3c-hci.h b/include/linux/platform_data/mipi-i3c-hci.h index ab7395f455f9..6f61d8a02378 100644 --- a/include/linux/platform_data/mipi-i3c-hci.h +++ b/include/linux/platform_data/mipi-i3c-hci.h @@ -3,13 +3,17 @@ #define INCLUDE_PLATFORM_DATA_MIPI_I3C_HCI_H #include +#include /** * struct mipi_i3c_hci_platform_data - Platform-dependent data for mipi_i3c_hci * @base_regs: Register set base address (to support multi-bus instances) + * @instance: Zero-based instance number of the Bus Controller as defined by the + * DisCo specification I3C Target Address (_ADR) Encoding */ struct mipi_i3c_hci_platform_data { void __iomem *base_regs; + u8 instance; }; #endif