From nobody Mon Sep 28 21:04:12 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B26533E49C3; Mon, 17 Aug 2026 10:38:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963142; cv=none; b=TfBSIzlEibKIvAwLQHdGSGXu12sGxb2MGIkRAtbZ5jvrhGiwto32ViO6JF3/tDHrmmifoPh6SP2hlgNopuKBT+wwvUAahmlSM3ximTJpqmSMVWLBCCSYBrrcEBHewdKJbJcSYhR3vLqlCSam5TRjXSvzOpCBlrPoJcfG8eTJ2OU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963142; c=relaxed/simple; bh=w3H7qSRDGRNgtz7TcY96qi6Lhs/OlXKC/baNFNDCyWM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=N1fxyo0uVgsuQeFRQZF08URKCxisBiKiAu/yTmkZgVpsUC6TpDhraZlHHsSOfAOf1ABKsMv8JtwJ7QDijrFNkV3O4dHpk6HrT5cecPpYr9MOPEciQsP67hQYZVrNNB608VNi8m0LR3GnfibONmK55tLc/1rhRDzLAOGf0cyh1Ww= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id C828E2000FE; Mon, 17 Aug 2026 12:38:51 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 67DB72001E0; Mon, 17 Aug 2026 12:38:51 +0200 (CEST) Received: from lsv03900.swis.in-blr01.nxp.com (lsv03900.swis.in-blr01.nxp.com [10.12.177.15]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 1F12318000B0; Mon, 17 Aug 2026 18:38:50 +0800 (+08) From: Lakshay Piplani To: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, lee@kernel.org, Frank.Li@nxp.com, lgirdwood@gmail.com Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, aman.kumarpandey@nxp.com, Lakshay Piplani Subject: [PATCH v15 1/8] i3c: master: Add APIs for I3C hub support Date: Mon, 17 Aug 2026 16:08:37 +0530 Message-Id: <20260817103844.2142802-2-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260817103844.2142802-1-lakshay.piplani@nxp.com> References: <20260817103844.2142802-1-lakshay.piplani@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" From: Aman Kumar Pandey Add helpers for attaching and detaching I3C devices and CCC helpers to check CCC support and send CCC commands, address slot helpers to query and update I3C bus address slot state, registering virtual masters with an explicit firmware node, and exposing the bus maintenance lock helpers. These additions prepare for I3C hub support. A hub driver needs to reserve and query parent bus address slots, forward CCC commands, register virtual target port controllers using the target-port firmware node, and serialize operations against the parent bus maintenance lock. The hub also forwards private transfers via i3c_dev_do_xfers_locked() and serializes its IBI and private-transfer paths against the shared lock, so the normal-use lock/unlock pair is exposed alongside the maintenance-lock helpers. i3c_master_register_fwnode() allows virtual I3C masters to register using a firmware node different from their parent device node without temporarily modifying parent->of_node. The new helpers are: 1) i3c_master_send_ccc_cmd() 2) i3c_master_supports_ccc_cmd() 3) i3c_bus_get_addr_slot_status() 4) i3c_bus_set_addr_slot_status() 5) i3c_bus_maintenance_lock() 6) i3c_bus_maintenance_unlock() 7) i3c_master_register_fwnode() 8) i3c_bus_normaluse_lock() 9) i3c_bus_normaluse_unlock() 10) i3c_dev_do_xfers_locked() Signed-off-by: Aman Kumar Pandey Signed-off-by: Lakshay Piplani Signed-off-by: Vikash Bansal --- Changes in v15: - Drop the direct attach and detach helpers that also modified address-slot state - Export these APIs: - i3c_bus_normaluse_lock() - i3c_bus_normaluse_unlock() - i3c_dev_do_xfers_locked() Changes in v14: - Add i3c_master_register_fwnode() to register virtual I3C masters with an explicit firmware node - Export i3c_bus_maintenance_lock() and i3c_bus_maintenance_unlock() - Add runtime PM get/put around i3c_master_send_ccc_cmd() - Make i3c_master_supports_ccc_cmd() return false when the controller does not implement send_ccc_cmd() Changes in v13: - Fix address handling in direct attach by using i3c_master_get_i3c_addrs(= ) and adding rollback on failure to prevent bus address collisions - Fix detach path by clearing master_priv and releasing addresses to avoid= use-after-free and stale state issues - Export address slot helper APIs and add kernel-doc for them Changes in v12: - Add address check in i3c_master_direct_detach_i3c_dev_locked() to skip detach for unaddressed devices. Changes in v11: - Convert i3c_master_supports_ccc_cmd() to return bool and align semantics with CCC support checks used by the I3C core Changes in v10: - Rename i3c_master_direct_attach_i3c_dev and i3c_master_direct_detach_i3c= _dev APIs to *_locked, as these APIs must be called with the bus lock held in write mode Changes in v9: - No change Changes in v8: - No change Changes in v7: - Update commit message to clarify purpose (prepare for I3C hub support) Changes in v6: - Split the patch into two parts: 1) expose the existing API 2) add new APIs. --- --- drivers/i3c/master.c | 155 +++++++++++++++++++++++++++++++------ include/linux/i3c/master.h | 17 ++++ 2 files changed, 149 insertions(+), 23 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index f1be38a640ca..03fb41f0786c 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -44,10 +44,11 @@ static BLOCKING_NOTIFIER_HEAD(i3c_bus_notifier); * logic to rely on I3C device information that could be changed behind th= eir * 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); =20 /** * i3c_bus_maintenance_unlock - Release the bus lock after a maintenance @@ -58,10 +59,11 @@ static void i3c_bus_maintenance_lock(struct i3c_bus *bu= s) * 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); =20 /** * i3c_bus_normaluse_lock - Lock the bus for a normal operation @@ -83,6 +85,7 @@ void i3c_bus_normaluse_lock(struct i3c_bus *bus) { down_read(&bus->lock); } +EXPORT_SYMBOL_GPL(i3c_bus_normaluse_lock); =20 /** * i3c_bus_normaluse_unlock - Release the bus lock after a normal operation @@ -96,6 +99,7 @@ void i3c_bus_normaluse_unlock(struct i3c_bus *bus) { up_read(&bus->lock); } +EXPORT_SYMBOL_GPL(i3c_bus_normaluse_unlock); =20 static struct i3c_master_controller * i3c_bus_to_i3c_master(struct i3c_bus *i3cbus) @@ -385,11 +389,19 @@ i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus= , u16 addr, u32 mask) return status & mask; } =20 -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); =20 static void i3c_bus_set_addr_slot_status_mask(struct i3c_bus *bus, u16 add= r, enum i3c_addr_slot_status status, u32 mask) @@ -405,11 +417,18 @@ static void i3c_bus_set_addr_slot_status_mask(struct = i3c_bus *bus, u16 addr, *ptr |=3D ((unsigned long)status & mask) << (bitpos % BITS_PER_LONG); } =20 -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); =20 static bool i3c_bus_dev_addr_is_avail(struct i3c_bus *bus, u8 addr) { @@ -2548,6 +2567,59 @@ static void i3c_master_reconcile_dyn_addrs(struct i3= c_master_controller *master) } } =20 +/** + * 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 t= he + * 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 =3D i3c_master_rpm_get(master); + if (ret) + return ret; + + i3c_bus_maintenance_lock(&master->bus); + ret =3D 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 @@ -3195,34 +3267,31 @@ static int i3c_master_check_ops(const struct i3c_ma= ster_controller_ops *ops) } =20 /** - * i3c_master_register() - register an I3C master + * i3c_master_register_fwnode() - register an I3C master with a custom fwn= ode * @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: + * This helper is useful for virtual I3C masters whose firmware node is not + * the same as @parent's firmware node. * - * - 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 + * Only OF-backed fwnodes are supported for now, because the I3C core still + * stores the bus node in master->dev.of_node and populates the bus using = OF. * * 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 =3D I3C_BUS_I2C_FM_PLUS_SCL_MAX_RATE; struct i3c_bus *i3cbus =3D i3c_master_get_bus(master); enum i3c_bus_mode mode =3D I3C_BUS_MODE_PURE; + struct device_node *np =3D NULL; struct i2c_dev_boardinfo *i2cbi; int ret; =20 @@ -3234,8 +3303,14 @@ int i3c_master_register(struct i3c_master_controller= *master, if (ret) return ret; =20 + if (fwnode) { + np =3D to_of_node(fwnode); + if (!np) + return -EINVAL; + } + master->dev.parent =3D parent; - master->dev.of_node =3D of_node_get(parent->of_node); + master->dev.of_node =3D of_node_get(np); master->dev.bus =3D &i3c_bus_type; master->dev.type =3D &i3c_masterdev_type; master->dev.release =3D i3c_masterdev_release; @@ -3352,6 +3427,39 @@ int i3c_master_register(struct i3c_master_controller= *master, =20 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, + parent->of_node ? + of_fwnode_handle(parent->of_node) : NULL, + ops, secondary); +} EXPORT_SYMBOL_GPL(i3c_master_register); =20 /** @@ -3412,6 +3520,7 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev,= struct i3c_xfer *xfers, =20 return master->ops->i3c_xfers(dev, xfers, nxfers, mode); } +EXPORT_SYMBOL_GPL(i3c_dev_do_xfers_locked); =20 /** * i3c_dev_disable_ibi_locked() - Disable IBIs coming from a specific devi= ce diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 4d2a68793324..32c686ae5d62 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -627,9 +627,18 @@ DEFINE_FREE(i3c_master_dma_unmap_single, void *, =20 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); =20 +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, @@ -752,4 +761,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); =20 +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 */ --=20 2.25.1 From nobody Mon Sep 28 21:04:12 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5B2F13F86F8; Mon, 17 Aug 2026 10:39:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963142; cv=none; b=I5OJYBB+dgxmpN/Bx2LnnC9nzHnMgMvFgjjQtFEgN0TIYhqWUAkRqLvKb9jemtpQFdBj7QanYIDtW31+ezjK8QsUJZ/L9Cm2p/m7Q21jniI23BJRyp75uau9bhJYcT72AN9DYavqkA2a75/THCNrh6CerlO/HeCI7xMmSHyfEhM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963142; c=relaxed/simple; bh=xpY7iOG/ct+8IJg+zGGbc+vJkoYm7+OoS5E8ODaHUas=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=cPR3cUt+wQXjdnx4sW5dnc/zo7g+bmLuhKYxUg+eiePPWLTmgYnu4n3P14bsBop0x8CZC15IBOvj8+EZ+IsgivfT5Gf39Hl5B4GKjFpmHHm0YZURKFyHEd/Lxe+1kR8cQZPwBO7K6j6WJai/SlY00NI1hKPAhr+sIg6RKk8w/Tg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 9135C2001E7; Mon, 17 Aug 2026 12:38:53 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 2F6642000FD; Mon, 17 Aug 2026 12:38:53 +0200 (CEST) Received: from lsv03900.swis.in-blr01.nxp.com (lsv03900.swis.in-blr01.nxp.com [10.12.177.15]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 6C94D18000B1; Mon, 17 Aug 2026 18:38:51 +0800 (+08) From: Lakshay Piplani To: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, lee@kernel.org, Frank.Li@nxp.com, lgirdwood@gmail.com Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, aman.kumarpandey@nxp.com, Lakshay Piplani Subject: [PATCH v15 2/8] i3c: master: Add controller-only device operation helpers Date: Mon, 17 Aug 2026 16:08:38 +0530 Message-Id: <20260817103844.2142802-3-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260817103844.2142802-1-lakshay.piplani@nxp.com> References: <20260817103844.2142802-1-lakshay.piplani@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" The generic I3C device attach and IBI paths perform both core-level bookkeeping and controller-specific operations. An I3C hub, however, maintains a logical device descriptor on the virtual downstream bus and a separate parent-facing descriptor used by the physical parent controller. Add controller-only helpers that invoke the controller callbacks without updating the bus device list, address-slot state, or generic IBI lifecycle. This allows the hub framework to keep the logical descriptor associated with the virtual controller while using a permanent parent-facing descriptor for operations handled by the physical controller. Add helpers for: - attaching, reattaching, and detaching a device from a controller; - requesting and freeing controller IBI resources; - enabling and disabling IBIs at the controller; - recycling controller IBI slots. Refactor the existing generic core paths to use these helpers, keeping the current behaviour unchanged for regular I3C devices. Also release the generic IBI workqueue and state when the controller request callback fails. The helpers are declared in the I3C internal header because they are intended for use by the generic I3C hub framework rather than by individual controller drivers. Signed-off-by: Lakshay Piplani Signed-off-by: Aman Kumar Pandey Signed-off-by: Vikash Bansal --- Changes in v15: - Rework the patch to introduce controller-only attach, reattach and detach helpers for use by the I3C hub core - Add controller-only helpers for requesting, freeing, enabling, disabling and recycling IBI resources --- --- drivers/i3c/internals.h | 14 +++ drivers/i3c/master.c | 228 ++++++++++++++++++++++++++++++++++------ 2 files changed, 212 insertions(+), 30 deletions(-) diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h index 0f1f3f766623..f56f5f23a394 100644 --- a/drivers/i3c/internals.h +++ b/drivers/i3c/internals.h @@ -22,6 +22,20 @@ 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(struct i3c_dev_desc *dev); +int i3c_master_reattach_i3c_dev_controller(struct i3c_dev_desc *dev, + u8 old_dyn_addr); +void i3c_master_detach_i3c_dev_controller(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 03fb41f0786c..7ae6b3aaa9b4 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -1764,6 +1764,27 @@ static int i3c_master_get_i3c_addrs(struct i3c_dev_d= esc *dev) return -EBUSY; } =20 +/** + * i3c_master_attach_i3c_dev_controller() - Attach device state to control= ler + * @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. + * + * Return: 0 on success, or a negative error code returned by the controll= er. + */ +int i3c_master_attach_i3c_dev_controller(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *master =3D i3c_dev_get_master(dev); + + /* Do not attach the master device itself. */ + if (master->this !=3D dev && master->ops->attach_i3c_dev) + return master->ops->attach_i3c_dev(dev); + + return 0; +} +EXPORT_SYMBOL_GPL(i3c_master_attach_i3c_dev_controller); + static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master, struct i3c_dev_desc *dev) { @@ -1781,12 +1802,10 @@ static int i3c_master_attach_i3c_dev(struct i3c_mas= ter_controller *master, return ret; =20 /* Do not attach the master device itself. */ - if (master->this !=3D dev && master->ops->attach_i3c_dev) { - ret =3D master->ops->attach_i3c_dev(dev); - if (ret) { - i3c_master_put_i3c_addrs(dev); - return ret; - } + ret =3D i3c_master_attach_i3c_dev_controller(dev); + if (ret) { + i3c_master_put_i3c_addrs(dev); + return ret; } =20 list_add_tail(&dev->common.node, &master->bus.devs.i3c); @@ -1794,6 +1813,28 @@ static int i3c_master_attach_i3c_dev(struct i3c_mast= er_controller *master, return 0; } =20 +/** + * i3c_master_reattach_i3c_dev_controller() - Reattach controller device s= tate + * @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. + * + * Return: 0 on success, or a negative error code returned by the controll= er. + */ +int i3c_master_reattach_i3c_dev_controller(struct i3c_dev_desc *dev, + u8 old_dyn_addr) +{ + struct i3c_master_controller *master =3D 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); + /** * i3c_master_reattach_i3c_dev_locked() - reattach an I3C device with a ne= w address * @dev: I3C device descriptor to reattach @@ -1824,25 +1865,36 @@ int i3c_master_reattach_i3c_dev_locked(struct i3c_d= ev_desc *dev, I3C_ADDR_SLOT_FREE); } =20 - if (master->ops->reattach_i3c_dev) { - ret =3D master->ops->reattach_i3c_dev(dev, old_dyn_addr); - if (ret) { - i3c_master_put_i3c_addrs(dev); - return ret; - } + ret =3D i3c_master_reattach_i3c_dev_controller(dev, old_dyn_addr); + if (ret) { + i3c_master_put_i3c_addrs(dev); + return ret; } =20 return 0; } EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev_locked); =20 -static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev) +/** + * i3c_master_detach_i3c_dev_controller() - Detach device state from contr= oller + * @dev: I3C device descriptor + * + * Invoke the current controller's detach callback without releasing addre= ss + * slots or removing the device from the controller's device list. + */ +void i3c_master_detach_i3c_dev_controller(struct i3c_dev_desc *dev) { struct i3c_master_controller *master =3D i3c_dev_get_master(dev); =20 /* Do not detach the master device itself. */ if (master->this !=3D dev && master->ops->detach_i3c_dev) master->ops->detach_i3c_dev(dev); +} +EXPORT_SYMBOL_GPL(i3c_master_detach_i3c_dev_controller); + +static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev) +{ + i3c_master_detach_i3c_dev_controller(dev); =20 i3c_master_put_i3c_addrs(dev); list_del(&dev->common.node); @@ -3201,6 +3253,26 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev, } EXPORT_SYMBOL_GPL(i3c_generic_ibi_alloc_pool); =20 +/** + * 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. + * + * The controller is responsible for synchronizing access to its IBI pool. + */ +void i3c_dev_recycle_ibi_slot_controller(struct i3c_dev_desc *dev, + struct i3c_ibi_slot *slot) +{ + struct i3c_master_controller *master =3D 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 po= ol * @pool: the pool to query an IBI slot on @@ -3522,6 +3594,32 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev= , struct i3c_xfer *xfers, } EXPORT_SYMBOL_GPL(i3c_dev_do_xfers_locked); =20 +/** + * 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 =3D 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 devi= ce * @dev: device on which IBIs should be disabled @@ -3534,14 +3632,9 @@ EXPORT_SYMBOL_GPL(i3c_dev_do_xfers_locked); */ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev) { - struct i3c_master_controller *master; int ret; =20 - if (!dev->ibi) - return -EINVAL; - - master =3D i3c_dev_get_master(dev); - ret =3D master->ops->disable_ibi(dev); + ret =3D i3c_dev_disable_ibi_controller_locked(dev); if (ret) return ret; =20 @@ -3555,6 +3648,32 @@ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *= dev) } EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked); =20 +/** + * 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 =3D 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 @@ -3571,13 +3690,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 =3D i3c_dev_get_master(dev); int ret; =20 - if (!dev->ibi) - return -EINVAL; - - ret =3D master->ops->enable_ibi(dev); + ret =3D i3c_dev_enable_ibi_controller_locked(dev); if (!ret) dev->ibi->enabled =3D true; =20 @@ -3585,6 +3700,35 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *d= ev) } EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_locked); =20 +/** + * i3c_dev_request_ibi_controller_locked() - Request controller IBI resour= ces + * @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 =3D 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 @@ -3600,13 +3744,9 @@ EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_locked); int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev, const struct i3c_ibi_setup *req) { - struct i3c_master_controller *master =3D i3c_dev_get_master(dev); struct i3c_device_ibi_info *ibi; int ret; =20 - if (!master->ops->request_ibi) - return -EOPNOTSUPP; - if (dev->ibi) return -EBUSY; =20 @@ -3627,8 +3767,15 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *= dev, ibi->num_slots =3D req->num_slots; =20 dev->ibi =3D ibi; - ret =3D master->ops->request_ibi(dev, req); + ret =3D 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 =3D NULL; } @@ -3637,6 +3784,27 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *= dev, } EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_locked); =20 +/** + * 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 =3D 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 @@ -3667,7 +3835,7 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev) dev_err(&master->dev, "Failed to disable IBI before freeing\n"); } =20 - master->ops->free_ibi(dev); + i3c_dev_free_ibi_controller_locked(dev); =20 if (dev->ibi->wq) { destroy_workqueue(dev->ibi->wq); --=20 2.25.1 From nobody Mon Sep 28 21:04:12 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 836B63F5BC0; Mon, 17 Aug 2026 10:39:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963144; cv=none; b=d9lqnflVIKerjmrGJesVEFtDK9sYZAiR/e9PBAPhAQsfMhp5y4p/GE1S05mU95NRbnQCcAWUpIgjj679OSmgEMi3UlrAtWa63I4yJZFHqOAyiMN/7ABPuzG8oxXHZBLe+e8lJRDvVJ27M742swp80EYBTxBRbZGlx+9VooSYYiQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963144; c=relaxed/simple; bh=bJvhzSNhyaKMsrt3IEirGeObnEq0RzTor0JqgJZT9rM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=DOfgJ2KGnaN+/WMROpm26n3jehaSnfRY+itMFe1KYt6d2bPqXftQqqagRWPcAQb3FDL4jMG//r0cVphR+uaFy0lkPVpUdXys12G6B1W3mq3VcDyXwcJukMZogRULRA8+lss+niGpWocuMO6yaF3KIOsl8Sux02YwjYy/CYu5a4Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 343E2200008; Mon, 17 Aug 2026 12:38:54 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id F1D192000FD; Mon, 17 Aug 2026 12:38:53 +0200 (CEST) Received: from lsv03900.swis.in-blr01.nxp.com (lsv03900.swis.in-blr01.nxp.com [10.12.177.15]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 9D4AC1800224; Mon, 17 Aug 2026 18:38:52 +0800 (+08) From: Lakshay Piplani To: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, lee@kernel.org, Frank.Li@nxp.com, lgirdwood@gmail.com Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, aman.kumarpandey@nxp.com, Lakshay Piplani Subject: [PATCH v15 3/8] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Date: Mon, 17 Aug 2026 16:08:39 +0530 Message-Id: <20260817103844.2142802-4-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260817103844.2142802-1-lakshay.piplani@nxp.com> References: <20260817103844.2142802-1-lakshay.piplani@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" From: Aman Kumar Pandey Add bindings for the NXP P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) multiport I3C hub family. These devices connect to a host via I3C/I2C/SMBus and allow communication with multiple downstream peripherals. Signed-off-by: Aman Kumar Pandey Signed-off-by: Vikash Bansal Signed-off-by: Lakshay Piplani Reviewed-by: Rob Herring (Arm) Reviewed-by: Frank Li --- Changes in v15: - Add compatible strings for the P3H2440, P3H2441, P3H2840 and P3H2841 variants - Fix the target-port reg schema to describe the single-cell port index Changes in v14: - No change Changes in v13: - Fix mismatch between example unit-address and reg property by correcting= the reg value to match the node name Changes in v12: - No change, added Reviewed-By tag Changes in v11: - No change Changes in v10: - No change, added Reviewed-By tag Changes in v9: - Referenced i3c.yaml and i2c-controller.yaml for child nodes - Dropped unnecessary #address-cells and #size-cells from child nodes Changes in v8: - Add compatible in i3c example Changes in v7: - Fix schema validation issues - Adjust required properties - Add I2C example Changes in v6: - Use a vendor prefix for the attributes Changes in v5: - Removed SW properties: cp0-ldo-microvolt,cp1-ldo-microvolt, tp0145-ldo-microvolt, tp2367-ldo-microvolt - Changed supply entries and its descriptions Changes in v4: - Fixed DT binding check warning - Removed SW properties: ibi-enable, local-dev, and always-enable Changes in v3: - Added MFD (Multi-Function Device) support for I3C hub and on-die regulat= or - Added Regulator supply node Changes in v2: - Fixed DT binding check warning - Revised logic for parsing DTS nodes --- --- .../devicetree/bindings/i3c/nxp,p3h2840.yaml | 297 ++++++++++++++++++ MAINTAINERS | 9 + 2 files changed, 306 insertions(+) create mode 100644 Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml diff --git a/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml b/Docum= entation/devicetree/bindings/i3c/nxp,p3h2840.yaml new file mode 100644 index 000000000000..6b84ccd8ace0 --- /dev/null +++ b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml @@ -0,0 +1,297 @@ +# 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 mult= iple + 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. + +properties: + compatible: + enum: + - nxp,p3h2440 + - nxp,p3h2441 + - nxp,p3h2840 + - nxp,p3h2841 + + 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 + +patternProperties: + "^i3c@[0-7]$": + type: object + $ref: /schemas/i3c/i3c.yaml# + unevaluatedProperties: false + + properties: + reg: + description: + The I3C HUB Target Port number. + items: + - 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: + - maximum: 7 + + nxp,pullup-enable: + type: boolean + description: + Enables the on-die pull-up for Target Port. + + required: + - reg + +unevaluatedProperties: false + +examples: + - | + i3c { + #address-cells =3D <3>; + #size-cells =3D <0>; + + hub@70,236153000c2 { + reg =3D <0x70 0x236 0x153000c2>; + compatible =3D "nxp,p3h2840"; + #address-cells =3D <1>; + #size-cells =3D <0>; + assigned-address =3D <0x50>; + + nxp,tp0145-pullup-ohms =3D <1000>; + nxp,tp2367-pullup-ohms =3D <1000>; + nxp,cp0-io-strength-ohms =3D <50>; + nxp,cp1-io-strength-ohms =3D <50>; + nxp,tp0145-io-strength-ohms =3D <50>; + nxp,tp2367-io-strength-ohms =3D <50>; + vcc3-supply =3D <®_tpg0>; + vcc4-supply =3D <®_tpg1>; + + regulators { + reg_cp0: ldo-cp0 { + regulator-name =3D "ldo-cp0"; + regulator-min-microvolt =3D <1800000>; + regulator-max-microvolt =3D <1800000>; + }; + + reg_cp1: ldo-cp1 { + regulator-name =3D "ldo-cp1"; + regulator-min-microvolt =3D <1800000>; + regulator-max-microvolt =3D <1800000>; + }; + + reg_tpg0: ldo-tpg0 { + regulator-name =3D "ldo-tpg0"; + regulator-min-microvolt =3D <1800000>; + regulator-max-microvolt =3D <1800000>; + }; + + reg_tpg1: ldo-tpg1 { + regulator-name =3D "ldo-tpg1"; + regulator-min-microvolt =3D <1800000>; + regulator-max-microvolt =3D <1800000>; + }; + }; + + smbus@0 { + reg =3D <0x0>; + #address-cells =3D <1>; + #size-cells =3D <0>; + nxp,pullup-enable; + }; + + smbus@1 { + reg =3D <0x1>; + #address-cells =3D <1>; + #size-cells =3D <0>; + nxp,pullup-enable; + }; + + i3c@2 { + reg =3D <0x2>; + #address-cells =3D <3>; + #size-cells =3D <0>; + nxp,pullup-enable; + }; + }; + }; + + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + hub@70 { + reg =3D <0x70>; + compatible =3D "nxp,p3h2840"; + #address-cells =3D <1>; + #size-cells =3D <0>; + + nxp,tp0145-pullup-ohms =3D <1000>; + nxp,tp2367-pullup-ohms =3D <1000>; + nxp,cp0-io-strength-ohms =3D <50>; + nxp,cp1-io-strength-ohms =3D <50>; + nxp,tp0145-io-strength-ohms =3D <50>; + nxp,tp2367-io-strength-ohms =3D <50>; + vcc3-supply =3D <®_tpg0_i2c>; + vcc4-supply =3D <®_tpg1_i2c>; + + regulators { + reg_cp0_i2c: ldo-cp0 { + regulator-name =3D "ldo-cp0"; + regulator-min-microvolt =3D <1800000>; + regulator-max-microvolt =3D <1800000>; + }; + + reg_cp1_i2c: ldo-cp1 { + regulator-name =3D "ldo-cp1"; + regulator-min-microvolt =3D <1800000>; + regulator-max-microvolt =3D <1800000>; + }; + + reg_tpg0_i2c: ldo-tpg0 { + regulator-name =3D "ldo-tpg0"; + regulator-min-microvolt =3D <1800000>; + regulator-max-microvolt =3D <1800000>; + }; + + reg_tpg1_i2c: ldo-tpg1 { + regulator-name =3D "ldo-tpg1"; + regulator-min-microvolt =3D <1800000>; + regulator-max-microvolt =3D <1800000>; + }; + }; + + smbus@0 { + reg =3D <0x0>; + #address-cells =3D <1>; + #size-cells =3D <0>; + nxp,pullup-enable; + }; + + smbus@1 { + reg =3D <0x1>; + #address-cells =3D <1>; + #size-cells =3D <0>; + nxp,pullup-enable; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 8014b9f8253e..955b6d0c843d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19544,6 +19544,15 @@ S: Maintained F: Documentation/devicetree/bindings/ptp/nxp,ptp-netc.yaml F: drivers/ptp/ptp_netc.c =20 +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 --=20 2.25.1 From nobody Mon Sep 28 21:04:12 2026 Received: from inva020.nxp.com (inva020.nxp.com [92.121.34.13]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 804153F8882; Mon, 17 Aug 2026 10:39:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.13 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963150; cv=none; b=UixeeXFsvX3Tu+FxOmL0SOujeq/kOAkQ8rLemlFZuK5+vsEU6SbkGh9GUjVLOP7QMWcS1hWWa1UUiDQdhmLHto7q0/a3unDSGR9FDULwMrVuiFy6zxSl+lmntL374zwh7Cl1GEXmYcHAzRKOv5xPO9DXxs4yTq8UoEZWI95YBII= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963150; c=relaxed/simple; bh=Pe6K4SYasazYJuTauQKjNlzOoGVj7ddM3XG5CAH2hx8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=TboSTE4Vd2bTNcqwVxT3OdJLkMNpwu7jvgV42ceHmlD2ljNm1AReKeUas7ed3Zmf+5jMDvGkIGfLp1IbyGOCLXGddoUW9TN3s8Os5r0/rY/hIrbDPWiTEDmrmTlb8j/S3xkckRpVWHUdJvyGLQFVxo2t/b6ckMykteyJ7EoHXqQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.13 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva020.nxp.com (localhost [127.0.0.1]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 5E4F61A0004; Mon, 17 Aug 2026 12:38:55 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 25C561A0106; Mon, 17 Aug 2026 12:38:55 +0200 (CEST) Received: from lsv03900.swis.in-blr01.nxp.com (lsv03900.swis.in-blr01.nxp.com [10.12.177.15]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id CF2B31800226; Mon, 17 Aug 2026 18:38:53 +0800 (+08) From: Lakshay Piplani To: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, lee@kernel.org, Frank.Li@nxp.com, lgirdwood@gmail.com Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, aman.kumarpandey@nxp.com, Lakshay Piplani Subject: [PATCH v15 4/8] mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator Date: Mon, 17 Aug 2026 16:08:40 +0530 Message-Id: <20260817103844.2142802-5-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260817103844.2142802-1-lakshay.piplani@nxp.com> References: <20260817103844.2142802-1-lakshay.piplani@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" From: Aman Kumar Pandey Add core MFD support for the NXP P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) family of multiport I3C hub devices. These devices connect to a host via I3C/I2C/SMBus and expose multiple downstream target ports. Signed-off-by: Aman Kumar Pandey Signed-off-by: Vikash Bansal Signed-off-by: Lakshay Piplani Reviewed-by: Frank Li --- Changes in v15: - Read the device capability register to determine whether the device has four or eight target ports - Store the detected target-port count in the shared MFD data - Add OF match entries for all P3H2440, P3H2441, P3H2840 and P3H2841 variants - Add a shared hub-context pointer for use by the IBI handler without overwriting the MFD parent's driver data Changes in v14: - Clean up MFD driver comments, formatting, variable names and author information - Define and document the I3C hub DCR value in hexadecimal form - Use direct regmap bit-width values instead of local macros - Add a shared protected_reg_lock for protected register accesses across P3H2X4X child drivers Changes in v13: - Use i3c_device helpers (i3cdev_to_dev()) instead of direct struct access to maintain API abstraction - Avoid including internal I3C headers and use public device headers inste= ad - Ensure proper device matching by relying on manufacturer ID checks in pr= obe Changes in v12: - No change, added Reviewed-By tag Changes in v11: - Use MFD_CELL_NAME() for child device registration - Rename local variables for consistency - Rename driver names to follow subsystem conventions: - Use '-' instead of '_' in driver names - Drop the "_drv" suffix from driver names Changes in v10: - Drop redundant is_p3h2x4x_in_i3c flag Changes in v9: - Renamed macros to follow consistent uppercase naming conventions - Made REGMAP selects in the P3H2X4X MFD Kconfig conditional, to avoid I3C/I2C dependency issues Changes in v8: - No change Changes in v7: - Use new config I3C_OR_I2C Changes in v6: - No change Changes in v5: - Corrected the ordering in the Makefile and Kconfig for MFD_P3H2X4X - Updated dev_err_probe() for regmap_init failure. - Updated module description Changes in v4: - Split the driver into three separate patches(mfd, regulator and I3C hub) - Added support for NXP P3H2x4x MFD functionality --- --- MAINTAINERS | 2 + drivers/mfd/Kconfig | 13 +++ drivers/mfd/Makefile | 1 + drivers/mfd/p3h2840.c | 168 ++++++++++++++++++++++++++++++++++++ include/linux/i3c/device.h | 2 + include/linux/mfd/p3h2840.h | 41 +++++++++ 6 files changed, 227 insertions(+) create mode 100644 drivers/mfd/p3h2840.c create mode 100644 include/linux/mfd/p3h2840.h diff --git a/MAINTAINERS b/MAINTAINERS index 955b6d0c843d..76861839f579 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19552,6 +19552,8 @@ L: linux-kernel@vger.kernel.org L: linux-i3c@lists.infradead.org S: Maintained F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml +F: drivers/mfd/p3h2840.c +F: include/linux/mfd/p3h2840.h =20 NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER M: Woodrow Douglass diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 763ce6a34782..fc56407be00a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -617,6 +617,19 @@ config MFD_MX25_TSADC i.MX25 processors. They consist of a conversion queue for general purpose ADC and a queue for Touchscreens. =20 +config MFD_P3H2X4X + tristate "NXP P3H2X4X I3C Hub Device" + depends on I3C_OR_I2C + select MFD_CORE + select REGMAP_I3C if I3C + select REGMAP_I2C if I2C + help + Enable Support for NXP P3H244x/P3H284x I3C HUB device using I3C/I2C + communication interface. + + This driver provides support for I3C hub and regulator, each subdriver + can be enabled independently depending on the required functionality. + config MFD_PF1550 tristate "NXP PF1550 PMIC Support" depends on I2C=3Dy && OF diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index dd4bb7e77c33..93db86851152 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -122,6 +122,7 @@ obj-$(CONFIG_MFD_MC13XXX) +=3D mc13xxx-core.o obj-$(CONFIG_MFD_MC13XXX_SPI) +=3D mc13xxx-spi.o obj-$(CONFIG_MFD_MC13XXX_I2C) +=3D mc13xxx-i2c.o =20 +obj-$(CONFIG_MFD_P3H2X4X) +=3D p3h2840.o obj-$(CONFIG_MFD_PF1550) +=3D pf1550.o =20 obj-$(CONFIG_MFD_NCT6694) +=3D nct6694.o diff --git a/drivers/mfd/p3h2840.c b/drivers/mfd/p3h2840.c new file mode 100644 index 000000000000..6c497d84e2bd --- /dev/null +++ b/drivers/mfd/p3h2840.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025-2026 NXP + * + * Authors: + * Aman Kumar Pandey + * Vikash Bansal + * Lakshay Piplani + * + * NXP P3H2x4x multi-port I3C hub. + */ +#include +#include +#include +#include +#include + +static const struct mfd_cell p3h2x4x_devs[] =3D { + MFD_CELL_NAME("p3h2x4x-regulator"), + MFD_CELL_NAME("p3h2x4x-i3c-hub"), +}; + +static const struct regmap_config p3h2x4x_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D 0xFF, +}; + +/* Read port count from the device capability register (4- or 8-port varia= nt). */ +static int p3h2x4x_read_num_target_ports(struct device *dev, + struct p3h2x4x *ddata) +{ + unsigned int val; + int ret; + + ret =3D regmap_read(ddata->regmap, P3H2X4X_DEV_CAPAB, &val); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read device capability\n"); + + ddata->num_target_ports =3D (val & P3H2X4X_TARGET_PORT_COUNT) ? + P3H2X4X_TARGET_PORTS_8 : P3H2X4X_TARGET_PORTS_4; + + return 0; +} + +static int p3h2x4x_device_probe_i3c(struct i3c_device *i3cdev) +{ + struct device *dev =3D i3cdev_to_dev(i3cdev); + struct i3c_device_info devinfo; + struct p3h2x4x *ddata; + int ret; + + i3c_device_get_info(i3cdev, &devinfo); + + if (I3C_PID_MANUF_ID(devinfo.pid) !=3D I3C_MANUF_ID_NXP) + return -ENODEV; + + ddata =3D devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ret =3D devm_mutex_init(dev, &ddata->protected_reg_lock); + if (ret) + return ret; + + i3cdev_set_drvdata(i3cdev, ddata); + + ddata->regmap =3D devm_regmap_init_i3c(i3cdev, &p3h2x4x_regmap_config); + if (IS_ERR(ddata->regmap)) + return dev_err_probe(dev, PTR_ERR(ddata->regmap), + "Failed to register HUB regmap\n"); + + /* The hub child driver retrieves information from i3cdev. */ + ddata->i3cdev =3D i3cdev; + + ret =3D p3h2x4x_read_num_target_ports(dev, ddata); + if (ret) + return ret; + + ret =3D devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, + p3h2x4x_devs, ARRAY_SIZE(p3h2x4x_devs), + NULL, 0, NULL); + if (ret) + return dev_err_probe(dev, ret, "Failed to add sub devices\n"); + + return 0; +} + +static int p3h2x4x_device_probe_i2c(struct i2c_client *client) +{ + struct p3h2x4x *ddata; + int ret; + + ddata =3D devm_kzalloc(&client->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ret =3D devm_mutex_init(&client->dev, &ddata->protected_reg_lock); + if (ret) + return ret; + + i2c_set_clientdata(client, ddata); + + ddata->regmap =3D devm_regmap_init_i2c(client, &p3h2x4x_regmap_config); + if (IS_ERR(ddata->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(ddata->regmap), + "Failed to register HUB regmap\n"); + + ddata->i3cdev =3D NULL; + + ret =3D p3h2x4x_read_num_target_ports(&client->dev, ddata); + if (ret) + return ret; + + ret =3D devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO, + p3h2x4x_devs, ARRAY_SIZE(p3h2x4x_devs), + NULL, 0, NULL); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to add sub devices\n"); + + return 0; +} + +static const struct i3c_device_id p3h2x4x_i3c_ids[] =3D { + I3C_CLASS(I3C_DCR_HUB, NULL), + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(i3c, p3h2x4x_i3c_ids); + +static const struct i2c_device_id p3h2x4x_i2c_id_table[] =3D { + { "nxp-i3c-hub" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, p3h2x4x_i2c_id_table); + +static const struct of_device_id p3h2x4x_i2c_of_match[] =3D { + { .compatible =3D "nxp,p3h2440", }, + { .compatible =3D "nxp,p3h2441", }, + { .compatible =3D "nxp,p3h2840", }, + { .compatible =3D "nxp,p3h2841", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, p3h2x4x_i2c_of_match); + +static struct i3c_driver p3h2x4x_i3c =3D { + .driver =3D { + .name =3D "p3h2x4x-i3c", + }, + .probe =3D p3h2x4x_device_probe_i3c, + .id_table =3D p3h2x4x_i3c_ids, +}; + +static struct i2c_driver p3h2x4x_i2c =3D { + .driver =3D { + .name =3D "p3h2x4x-i2c", + .of_match_table =3D p3h2x4x_i2c_of_match, + }, + .probe =3D p3h2x4x_device_probe_i2c, + .id_table =3D p3h2x4x_i2c_id_table, +}; +module_i3c_i2c_driver(p3h2x4x_i3c, &p3h2x4x_i2c); + +MODULE_AUTHOR("Aman Kumar Pandey "); +MODULE_AUTHOR("Vikash Bansal "); +MODULE_AUTHOR("Lakshay Piplani "); +MODULE_DESCRIPTION("NXP P3H2X4X I3C HUB multi function driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h index 0f065b883ee0..d008688c96f6 100644 --- a/include/linux/i3c/device.h +++ b/include/linux/i3c/device.h @@ -82,9 +82,11 @@ struct i3c_xfer { /** * enum i3c_dcr - I3C DCR values * @I3C_DCR_GENERIC_DEVICE: generic I3C device + * @I3C_DCR_HUB: I3C hub device */ enum i3c_dcr { I3C_DCR_GENERIC_DEVICE =3D 0, + I3C_DCR_HUB =3D 0xC2, }; =20 #define I3C_PID_MANUF_ID(pid) (((pid) & GENMASK_ULL(47, 33)) >> 33) diff --git a/include/linux/mfd/p3h2840.h b/include/linux/mfd/p3h2840.h new file mode 100644 index 000000000000..eb4792de5440 --- /dev/null +++ b/include/linux/mfd/p3h2840.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025-2026 NXP + */ + +#ifndef _LINUX_MFD_P3H2840_H +#define _LINUX_MFD_P3H2840_H + +#include +#include +#include + +/* Device Information Registers */ +#define P3H2X4X_DEV_CAPAB 0x0a +#define P3H2X4X_TARGET_PORT_COUNT BIT(3) + +/* Downstream target port counts per variant. */ +#define P3H2X4X_TARGET_PORTS_4 4 +#define P3H2X4X_TARGET_PORTS_8 8 + +/* Device Configuration Registers */ +#define P3H2X4X_DEV_REG_PROTECTION_CODE 0x10 +#define P3H2X4X_REGISTERS_LOCK_CODE 0x00 +#define P3H2X4X_REGISTERS_UNLOCK_CODE 0x69 +#define P3H2X4X_CP1_REGISTERS_UNLOCK_CODE 0x6a + +#define I3C_MANUF_ID_NXP 0x011b + +struct p3h2x4x_i3c_hub_dev; + +struct p3h2x4x { + struct i3c_device *i3cdev; + struct regmap *regmap; + /* Number of downstream target ports (4 or 8). */ + u8 num_target_ports; + /* Serializes protected register unlock/lock sequences across MFD childre= n. */ + struct mutex protected_reg_lock; + /* Hub context for the IBI handler to reach hub state via the parent i3cd= ev. */ + struct p3h2x4x_i3c_hub_dev *i3c_hub_priv; +}; +#endif /* _LINUX_MFD_P3H2840_H */ --=20 2.25.1 From nobody Mon Sep 28 21:04:12 2026 Received: from inva020.nxp.com (inva020.nxp.com [92.121.34.13]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 706C83F8ED3; Mon, 17 Aug 2026 10:39:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.13 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963155; cv=none; b=YkB4jTdoadcz14WcdmovN9fFJB/OaPNyqIfmBpzZu4T7gI7VbbHdrPmXUDmqS6c2CPNpqoFOf/Fxh91L6WRgacgI1CQ3OtHV9jX2/5UfGeGaEAoM31lSXwi1fuc7DIHTU0OCHEcxIXjW2u9+oupuNhOqoglYHgOICptRCx8ajBA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963155; c=relaxed/simple; bh=BNW1XWoSyIp4rHMIeSe5ApGGiaWZzSof2a5w9bViTKY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=rucZmBOeY8b9KGWxoCBGEXxheojOUlawkHFiMix4yg+qoCkKCXoD2u52yCbyCxQkNEXgLbldd6zH0MxLfMsnQS+SAhmCHTpfyqtfXybZEyVqRH+jqa4EnThvHAodoJWvO8Up6QJ/bd7IbQwb/7lV+duPS2y2U35l5uThGqgZe0M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.13 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva020.nxp.com (localhost [127.0.0.1]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 46DFA1A0005; Mon, 17 Aug 2026 12:38:57 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id D95341A01F9; Mon, 17 Aug 2026 12:38:56 +0200 (CEST) Received: from lsv03900.swis.in-blr01.nxp.com (lsv03900.swis.in-blr01.nxp.com [10.12.177.15]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 846F418000B0; Mon, 17 Aug 2026 18:38:55 +0800 (+08) From: Lakshay Piplani To: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, lee@kernel.org, Frank.Li@nxp.com, lgirdwood@gmail.com Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, aman.kumarpandey@nxp.com, Lakshay Piplani , Frank Li Subject: [PATCH v15 5/8] regulator: p3h2x4x: Add driver for on-die regulators in NXP P3H2x4x i3c hub Date: Mon, 17 Aug 2026 16:08:41 +0530 Message-Id: <20260817103844.2142802-6-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260817103844.2142802-1-lakshay.piplani@nxp.com> References: <20260817103844.2142802-1-lakshay.piplani@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" From: Aman Kumar Pandey The NXP P3H2x4x family integrates on-die regulators alongside I3C hub functionality. This driver registers the regulators using the MFD framework and exposes them via the regulator subsystem. Signed-off-by: Aman Kumar Pandey Signed-off-by: Vikash Bansal Signed-off-by: Lakshay Piplani Reviewed-by: Frank Li Reviewed-by: Mark Brown --- Changes in v15: - No change Changes in v14: - Use the shared MFD protected_reg_lock instead of a regulator-private mut= ex to serialize protected register access across P3H2X4X child drivers - Rework protected register handling into explicit unprotect/protect helpe= rs and restore the original protection state after each regulator operation - Ensure protected registers are restored even if enable, disable or set_voltage_sel fails Changes in v13: - Serialize register unlock/modify/lock sequences using a device-level mut= ex to avoid races between regulators. - Ensure proper module auto-loading by adding platform device ID table. - Improve error handling in register guard paths to avoid operating on loc= ked hub. Changes in v12: - No change Changes in v11: - No change, added Reviewed-By tag Changes in v10: - No change Changes in v9: - No change Changes in v8: - No change Changes in v7: - No change, added Reviewed-By tag Changes in v6: - Use DEFINE_LOCK_GUARD_1 for reg lock/unlock Changes in v5: - Updated dev_err_probe() for regmap_init failure. - Updated module description Changes in v4: - Split the driver into three separate patches (mfd, regulator and I3C hub) - Introduced driver for on-die regulators in NXP P3H2x4x I3C hub --- --- MAINTAINERS | 1 + drivers/regulator/Kconfig | 10 + drivers/regulator/Makefile | 1 + drivers/regulator/p3h2840_i3c_hub_regulator.c | 284 ++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 drivers/regulator/p3h2840_i3c_hub_regulator.c diff --git a/MAINTAINERS b/MAINTAINERS index 76861839f579..448c5017f665 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19553,6 +19553,7 @@ L: linux-i3c@lists.infradead.org S: Maintained F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml F: drivers/mfd/p3h2840.c +F: drivers/regulator/p3h2840_i3c_hub_regulator.c F: include/linux/mfd/p3h2840.h =20 NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index a54a549196fe..d0c6c5b65557 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1020,6 +1020,16 @@ config REGULATOR_MTK_DVFSRC of Mediatek. It allows for voting on regulator state between multiple users. =20 +config REGULATOR_P3H2X4X + tristate "NXP P3H2X4X regulator support" + depends on MFD_P3H2X4X + help + This driver provides support for the voltage regulators of the + P3H244x/P3H284x multi-function I3C Hub device. + + Say M here if you want to include support for this regulator as + a module. The module will be named "p3h2840_i3c_hub_regulator". + config REGULATOR_PALMAS tristate "TI Palmas PMIC Regulators" depends on MFD_PALMAS diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 134eee274dbf..e3e40e3ffd11 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -128,6 +128,7 @@ obj-$(CONFIG_REGULATOR_QCOM_RPMH) +=3D qcom-rpmh-regula= tor.o obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) +=3D qcom_smd-regulator.o obj-$(CONFIG_REGULATOR_QCOM_SPMI) +=3D qcom_spmi-regulator.o obj-$(CONFIG_REGULATOR_QCOM_USB_VBUS) +=3D qcom_usb_vbus-regulator.o +obj-$(CONFIG_REGULATOR_P3H2X4X) +=3D p3h2840_i3c_hub_regulator.o obj-$(CONFIG_REGULATOR_PALMAS) +=3D palmas-regulator.o obj-$(CONFIG_REGULATOR_PCA9450) +=3D pca9450-regulator.o obj-$(CONFIG_REGULATOR_PF0900) +=3D pf0900-regulator.o diff --git a/drivers/regulator/p3h2840_i3c_hub_regulator.c b/drivers/regula= tor/p3h2840_i3c_hub_regulator.c new file mode 100644 index 000000000000..645d46ad7c2f --- /dev/null +++ b/drivers/regulator/p3h2840_i3c_hub_regulator.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025-2026 NXP + * NXP P3H2X4X regulator driver file contain functions for enable/disable = regulator + * and voltage set/get. + */ +#include +#include +#include +#include +#include +#include +#include + +#define P3H2X4X_LDO_AND_PULLUP_CONF 0x19 +#define P3H2X4X_LDO_ENABLE_DISABLE_MASK GENMASK(3, 0) +#define P3H2X4X_CP0_EN_LDO BIT(0) +#define P3H2X4X_CP1_EN_LDO BIT(1) +#define P3H2X4X_TP0145_EN_LDO BIT(2) +#define P3H2X4X_TP2367_EN_LDO BIT(3) + +#define P3H2X4X_NET_OPER_MODE_CONF 0x15 +#define P3H2X4X_VCCIO_LDO_CONF 0x16 +#define P3H2X4X_CP0_VCCIO_LDO_VOLTAGE_MASK GENMASK(1, 0) +#define P3H2X4X_CP0_VCCIO_LDO_VOLTAGE(x) \ + FIELD_PREP(P3H2X4X_CP0_VCCIO_LDO_VOLTAGE_MASK, x) +#define P3H2X4X_CP1_VCCIO_LDO_VOLTAGE_MASK GENMASK(3, 2) +#define P3H2X4X_CP1_VCCIO_LDO_VOLTAGE(x) \ + FIELD_PREP(P3H2X4X_CP1_VCCIO_LDO_VOLTAGE_MASK, x) +#define P3H2X4X_TP0145_VCCIO_LDO_VOLTAGE_MASK GENMASK(5, 4) +#define P3H2X4X_TP0145_VCCIO_LDO_VOLTAGE(x) \ + FIELD_PREP(P3H2X4X_TP0145_VCCIO_LDO_VOLTAGE_MASK, x) +#define P3H2X4X_TP2367_VCCIO_LDO_VOLTAGE_MASK GENMASK(7, 6) +#define P3H2X4X_TP2367_VCCIO_LDO_VOLTAGE(x) \ + FIELD_PREP(P3H2X4X_TP2367_VCCIO_LDO_VOLTAGE_MASK, x) +#define P3H2X4X_LDO_COUNT 4 + +struct p3h2x4x_regulator_dev { + struct regulator_dev *rp3h2x4x_dev[P3H2X4X_LDO_COUNT]; + struct p3h2x4x *p3h2x4x; + struct regmap *regmap; +}; + +struct p3h2x4x_reg_state { + unsigned int orig; + bool restore; +}; + +static void p3h2x4x_reg_guard_enter(struct regulator_dev *rdev) +{ + struct p3h2x4x_regulator_dev *priv =3D rdev_get_drvdata(rdev); + + mutex_lock(&priv->p3h2x4x->protected_reg_lock); +} + +static void p3h2x4x_reg_guard_exit(struct regulator_dev *rdev) +{ + struct p3h2x4x_regulator_dev *priv =3D rdev_get_drvdata(rdev); + + mutex_unlock(&priv->p3h2x4x->protected_reg_lock); +} + +DEFINE_LOCK_GUARD_1(p3h2x4x_reg, struct regulator_dev, + p3h2x4x_reg_guard_enter(_T->lock), + p3h2x4x_reg_guard_exit(_T->lock)); + +static int p3h2x4x_reg_unprotect(struct regulator_dev *rdev, + struct p3h2x4x_reg_state *state) +{ + int ret; + + state->restore =3D false; + + ret =3D regmap_read(rdev->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + &state->orig); + if (ret) + return ret; + + if (state->orig =3D=3D P3H2X4X_REGISTERS_UNLOCK_CODE) + return 0; + + ret =3D regmap_write(rdev->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_UNLOCK_CODE); + if (ret) + return ret; + + state->restore =3D true; + + return 0; +} + +static int p3h2x4x_reg_protect(struct regulator_dev *rdev, + struct p3h2x4x_reg_state *state) +{ + if (!state->restore) + return 0; + + return regmap_write(rdev->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + state->orig); +} + +static int p3h2x4x_regulator_enable(struct regulator_dev *rdev) +{ + struct p3h2x4x_reg_state state; + int ret, ret2; + + guard(p3h2x4x_reg)(rdev); + + ret =3D p3h2x4x_reg_unprotect(rdev, &state); + if (ret) + return ret; + + ret =3D regulator_enable_regmap(rdev); + ret2 =3D p3h2x4x_reg_protect(rdev, &state); + + return ret ? ret : ret2; +} + +static int p3h2x4x_regulator_disable(struct regulator_dev *rdev) +{ + struct p3h2x4x_reg_state state; + int ret, ret2; + + guard(p3h2x4x_reg)(rdev); + + ret =3D p3h2x4x_reg_unprotect(rdev, &state); + if (ret) + return ret; + + ret =3D regulator_disable_regmap(rdev); + ret2 =3D p3h2x4x_reg_protect(rdev, &state); + + return ret ? ret : ret2; +} + +static int p3h2x4x_regulator_set_voltage_sel(struct regulator_dev *rdev, + unsigned int sel) +{ + struct p3h2x4x_reg_state state; + int ret, ret2; + + guard(p3h2x4x_reg)(rdev); + + ret =3D p3h2x4x_reg_unprotect(rdev, &state); + if (ret) + return ret; + + ret =3D regulator_set_voltage_sel_regmap(rdev, sel); + ret2 =3D p3h2x4x_reg_protect(rdev, &state); + + return ret ? ret : ret2; +} + +static const struct regulator_ops p3h2x4x_ldo_ops =3D { + .list_voltage =3D regulator_list_voltage_table, + .map_voltage =3D regulator_map_voltage_iterate, + .set_voltage_sel =3D p3h2x4x_regulator_set_voltage_sel, + .get_voltage_sel =3D regulator_get_voltage_sel_regmap, + .enable =3D p3h2x4x_regulator_enable, + .disable =3D p3h2x4x_regulator_disable, + .is_enabled =3D regulator_is_enabled_regmap, +}; + +static const unsigned int p3h2x4x_voltage_table[] =3D { + 1000000, + 1100000, + 1200000, + 1800000, +}; + +static struct regulator_desc p3h2x4x_regulators[] =3D { + { + .name =3D "ldo-cp0", + .of_match =3D of_match_ptr("ldo-cp0"), + .regulators_node =3D of_match_ptr("regulators"), + .volt_table =3D p3h2x4x_voltage_table, + .n_voltages =3D ARRAY_SIZE(p3h2x4x_voltage_table), + .ops =3D &p3h2x4x_ldo_ops, + .type =3D REGULATOR_VOLTAGE, + .owner =3D THIS_MODULE, + .enable_reg =3D P3H2X4X_LDO_AND_PULLUP_CONF, + .enable_mask =3D P3H2X4X_CP0_EN_LDO, + .vsel_reg =3D P3H2X4X_VCCIO_LDO_CONF, + .vsel_mask =3D P3H2X4X_CP0_VCCIO_LDO_VOLTAGE_MASK, + }, + { + .name =3D "ldo-cp1", + .of_match =3D of_match_ptr("ldo-cp1"), + .regulators_node =3D of_match_ptr("regulators"), + .volt_table =3D p3h2x4x_voltage_table, + .n_voltages =3D ARRAY_SIZE(p3h2x4x_voltage_table), + .ops =3D &p3h2x4x_ldo_ops, + .type =3D REGULATOR_VOLTAGE, + .owner =3D THIS_MODULE, + .enable_reg =3D P3H2X4X_LDO_AND_PULLUP_CONF, + .enable_mask =3D P3H2X4X_CP1_EN_LDO, + .vsel_reg =3D P3H2X4X_VCCIO_LDO_CONF, + .vsel_mask =3D P3H2X4X_CP1_VCCIO_LDO_VOLTAGE_MASK, + }, + { + .name =3D "ldo-tpg0", + .of_match =3D of_match_ptr("ldo-tpg0"), + .regulators_node =3D of_match_ptr("regulators"), + .volt_table =3D p3h2x4x_voltage_table, + .n_voltages =3D ARRAY_SIZE(p3h2x4x_voltage_table), + .ops =3D &p3h2x4x_ldo_ops, + .type =3D REGULATOR_VOLTAGE, + .owner =3D THIS_MODULE, + .enable_reg =3D P3H2X4X_LDO_AND_PULLUP_CONF, + .enable_mask =3D P3H2X4X_TP0145_EN_LDO, + .vsel_reg =3D P3H2X4X_VCCIO_LDO_CONF, + .vsel_mask =3D P3H2X4X_TP0145_VCCIO_LDO_VOLTAGE_MASK, + }, + { + .name =3D "ldo-tpg1", + .of_match =3D of_match_ptr("ldo-tpg1"), + .regulators_node =3D of_match_ptr("regulators"), + .volt_table =3D p3h2x4x_voltage_table, + .n_voltages =3D ARRAY_SIZE(p3h2x4x_voltage_table), + .ops =3D &p3h2x4x_ldo_ops, + .type =3D REGULATOR_VOLTAGE, + .owner =3D THIS_MODULE, + .enable_reg =3D P3H2X4X_LDO_AND_PULLUP_CONF, + .enable_mask =3D P3H2X4X_TP2367_EN_LDO, + .vsel_reg =3D P3H2X4X_VCCIO_LDO_CONF, + .vsel_mask =3D P3H2X4X_TP2367_VCCIO_LDO_VOLTAGE_MASK, + }, +}; + +static int p3h2x4x_regulator_probe(struct platform_device *pdev) +{ + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(pdev->dev.parent); + struct p3h2x4x_regulator_dev *p3h2x4x_regulator; + struct regulator_config rcfg =3D { }; + struct device *dev =3D &pdev->dev; + struct regulator_dev *rdev; + int i; + + p3h2x4x_regulator =3D devm_kzalloc(dev, sizeof(*p3h2x4x_regulator), GFP_K= ERNEL); + if (!p3h2x4x_regulator) + return -ENOMEM; + + p3h2x4x_regulator->p3h2x4x =3D p3h2x4x; + p3h2x4x_regulator->regmap =3D p3h2x4x->regmap; + + platform_set_drvdata(pdev, p3h2x4x_regulator); + + device_set_of_node_from_dev(dev, dev->parent); + + rcfg.dev =3D dev; + rcfg.regmap =3D p3h2x4x_regulator->regmap; + rcfg.driver_data =3D p3h2x4x_regulator; + + for (i =3D 0; i < ARRAY_SIZE(p3h2x4x_regulators); i++) { + rdev =3D devm_regulator_register(&pdev->dev, &p3h2x4x_regulators[i], &rc= fg); + if (IS_ERR(rdev)) + return dev_err_probe(dev, PTR_ERR(rdev), "Failed to register %s\n", + p3h2x4x_regulators[i].name); + p3h2x4x_regulator->rp3h2x4x_dev[i] =3D rdev; + } + return 0; +} + +static const struct platform_device_id p3h2x4x_regulator_id[] =3D { + { "p3h2x4x-regulator" }, + { } +}; +MODULE_DEVICE_TABLE(platform, p3h2x4x_regulator_id); + +static struct platform_driver p3h2x4x_regulator_driver =3D { + .driver =3D { + .name =3D "p3h2x4x-regulator", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .probe =3D p3h2x4x_regulator_probe, + .id_table =3D p3h2x4x_regulator_id, +}; +module_platform_driver(p3h2x4x_regulator_driver); + +MODULE_AUTHOR("Aman Kumar Pandey "); +MODULE_AUTHOR("Vikash Bansal "); +MODULE_AUTHOR("Lakshay Piplani "); +MODULE_DESCRIPTION("NXP P3H2X4X I3C HUB Regulator driver"); +MODULE_LICENSE("GPL"); --=20 2.25.1 From nobody Mon Sep 28 21:04:12 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 44AF23F823E; Mon, 17 Aug 2026 10:39:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963143; cv=none; b=QTaVIYuhj2sVIy+uw9dNgteyzyCmxrFmNf15AMv6pnvpjYxuBAvxvbPBnH/OovMpP6hz230D85GHgCvgwbMUyVPl3wkFA2qAnVlyGb/E9GWCZzd/EXGfntp7DOhPWK/buRR5U0W/MIAJAbw9gIcTipOJ99jvjt5SlOBHJ9SoZ6E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963143; c=relaxed/simple; bh=D+UIAw6vFjX2sSUeQhQYMbpi9pWgGlSiN2zO2mkmypg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=txstYFOK4/zmTsW5OybFAG25pvOyNpjuxLvPlkuAgPK/oz35ucEqvFzZAhhb7UnHtmY2DGd94PTNUGIkRmdBqHT6scdwfbInxFw9B2LnYX9cYZnnvStev26NcquNnRWmsjxhEvOOusvLfLkTHlIEyUotPROIwzuFtye0I+Z1kOM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 8FBA12000FD; Mon, 17 Aug 2026 12:38:58 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 2F5CC2001E0; Mon, 17 Aug 2026 12:38:58 +0200 (CEST) Received: from lsv03900.swis.in-blr01.nxp.com (lsv03900.swis.in-blr01.nxp.com [10.12.177.15]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id D01521800224; Mon, 17 Aug 2026 18:38:56 +0800 (+08) From: Lakshay Piplani To: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, lee@kernel.org, Frank.Li@nxp.com, lgirdwood@gmail.com Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, aman.kumarpandey@nxp.com, Lakshay Piplani Subject: [PATCH v15 6/8] i3c: hub: Add support for the I3C interface in the I3C hub Date: Mon, 17 Aug 2026 16:08:42 +0530 Message-Id: <20260817103844.2142802-7-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260817103844.2142802-1-lakshay.piplani@nxp.com> References: <20260817103844.2142802-1-lakshay.piplani@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" Add virtual I3C bus support for the hub and provide interface to enable or disable downstream ports. Signed-off-by: Aman Kumar Pandey Signed-off-by: Vikash Bansal Signed-off-by: Lakshay Piplani --- Changes in v15: - Replace temporary descriptor reparenting and hub-address switching with a permanent parent-facing descriptor for each downstream I3C device - Keep the logical device descriptor associated with the virtual hub controller while using the parent-facing descriptor for physical controller operations - Implement downstream device attach, reattach and detach using the new controller-only helpers - Forward IBI slot recycling to the physical parent controller - Remove the helper that temporarily changed the logical descriptor's master pointer Changes in v14: - Add hub route serialization around DAA, CCC, private transfer and IBI pa= ths - Add comments explaining that downstream devices behind hub target ports share the parent controller address space - Use i3c_bus_maintenance_lock()/unlock() instead of raw parent bus lock operations - Hold the parent maintenance lock across temporary hub address reattach, transfer and address restore - Rework IBI request/free forwarding, so downstream devices are temporarily attached to the parent controller for IBI setup - Clean up the preallocated virtual IBI object and workqueue before forwar= ding the IBI request to the parent controller - Avoid NULL dereference in the core IBI free path when the forwarding callback has already cleared dev->ibi Changes in v13: - Reserve parent I3C bus address slots for downstream devices with matchin= g static and assigned addresses by parsing target-port DT nodes - Keep broadcast RSTDAA blocked and add explanatory comment - Add TODO for empty IBI slot recycle callback - Add comments for intentional no-op callbacks Changes in v12: - Drop redundant depends on I3C from config I3C_HUB - Return -EOPNOTSUPP for unsupported I2C transfers instead of reporting success. Changes in v11: - Fix virtual hub address reattach handling - Fix parent bus locking during address reattach operations - Improve transfer and IBI request error handling Changes in v10: - Remove unnecessary ibi_lock handling in request/enable/disable/free IBI APIs - Remove redundant parent pointer from struct i3c_hub and derive upstream master from hub_dev Changes in v9: - No change Changes in v8: - No change Changes in v7: - Convert Kconfig option to tristate - Fix signedness issue in return value - Fix kernel-doc warnings Changes in v6: - Add support for the generic I3C interface in the I3C Hub --- --- MAINTAINERS | 2 + drivers/i3c/Kconfig | 14 + drivers/i3c/Makefile | 1 + drivers/i3c/hub.c | 675 ++++++++++++++++++++++++++++++++++++++++ include/linux/i3c/hub.h | 92 ++++++ 5 files changed, 784 insertions(+) create mode 100644 drivers/i3c/hub.c create mode 100644 include/linux/i3c/hub.h diff --git a/MAINTAINERS b/MAINTAINERS index 448c5017f665..11f231ce05cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19552,8 +19552,10 @@ L: linux-kernel@vger.kernel.org L: linux-i3c@lists.infradead.org S: Maintained F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml +F: drivers/i3c/hub.c F: drivers/mfd/p3h2840.c F: drivers/regulator/p3h2840_i3c_hub_regulator.c +F: include/linux/i3c/hub.h F: include/linux/mfd/p3h2840.h =20 NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER 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 =20 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 =20 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 :=3D device.o master.o obj-$(CONFIG_I3C) +=3D i3c.o obj-$(CONFIG_I3C) +=3D master/ +obj-$(CONFIG_I3C_HUB) +=3D hub.o diff --git a/drivers/i3c/hub.c b/drivers/i3c/hub.c new file mode 100644 index 000000000000..04726393004b --- /dev/null +++ b/drivers/i3c/hub.c @@ -0,0 +1,675 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 NXP + * Generic I3C Hub core implementing virtual controller operations. + */ +#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 ra= cing + * concurrent readers on the virtual bus. + */ +struct i3c_hub_dev_data { + struct i3c_dev_desc parent_desc; +}; + +/** + * 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 *controlle= r) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub =3D hub_controller->hub; + + if (!hub->hub_dev) + return -ENODEV; + + controller->this =3D hub->hub_dev->desc; + return 0; +} + +static void i3c_hub_master_bus_cleanup(struct i3c_master_controller *contr= oller) +{ + controller->this =3D NULL; +} + +static int i3c_hub_attach_i3c_dev(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *controller =3D 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 =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub =3D hub_controller->hub; + if (!hub->hub_dev) + return -ENODEV; + + parent =3D i3c_dev_get_master(hub->hub_dev->desc); + if (!parent) + return -ENODEV; + + data =3D kzalloc_obj(*data); + if (!data) + return -ENOMEM; + + /* Fix the parent-facing descriptor's master to the physical parent. */ + INIT_LIST_HEAD(&data->parent_desc.common.node); + mutex_init(&data->parent_desc.ibi_lock); + + data->parent_desc.common.master =3D parent; + data->parent_desc.info =3D dev->info; + + i3c_bus_maintenance_lock(&parent->bus); + ret =3D i3c_master_attach_i3c_dev_controller(&data->parent_desc); + i3c_bus_maintenance_unlock(&parent->bus); + if (ret) { + mutex_destroy(&data->parent_desc.ibi_lock); + kfree(data); + return ret; + } + + /* + * The logical descriptor stores the hub-private data, while the + * parent-facing descriptor stores the physical controller's private + * 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 =3D i3c_dev_get_master_data(dev); + struct i3c_master_controller *parent; + int ret; + + if (!data) + return -ENODEV; + + parent =3D i3c_dev_get_master(&data->parent_desc); + if (!parent) + return -ENODEV; + + /* Re-sync device information after the address change. */ + data->parent_desc.info =3D dev->info; + + i3c_bus_maintenance_lock(&parent->bus); + ret =3D i3c_master_reattach_i3c_dev_controller(&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 =3D i3c_dev_get_master_data(dev); + struct i3c_master_controller *parent; + + if (!data) + return; + + parent =3D i3c_dev_get_master(&data->parent_desc); + + /* + * The generic IBI lifecycle must be released before detaching the + * physical controller state. + */ + WARN_ON(data->parent_desc.ibi); + + if (parent) { + i3c_bus_maintenance_lock(&parent->bus); + i3c_master_detach_i3c_dev_controller(&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 =3D i3c_dev_get_master(hub->hub_dev->desc); + if (!parent) + return -ENODEV; + + 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. + */ + ret =3D 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 =3D 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. + */ +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 =3D i3c_dev_get_master(hub->hub_dev->desc); + if (!parent) + return -ENODEV; + + mutex_lock(&hub->lock); + i3c_hub_enable_port(controller); + ret =3D 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.) + * + * Handles address adjustment and forwards private transfers through the h= ub + * device. + */ +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 =3D 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 =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub =3D hub_controller->hub; + + data =3D i3c_dev_get_master_data(dev); + if (!data) + return -ENODEV; + + parent =3D i3c_dev_get_master(&data->parent_desc); + if (!parent) + return -ENODEV; + + mutex_lock(&hub->lock); + + /* + * Parent-facing device information may be refreshed before IBI + * resources are requested; once IBI resources are requested, the + * information remains immutable while the parent controller may use + * the descriptor asynchronously. + */ + if (!data->parent_desc.ibi) + data->parent_desc.info =3D dev->info; + + i3c_hub_enable_port(controller); + + i3c_bus_normaluse_lock(&parent->bus); + ret =3D 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 =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub =3D hub_controller->hub; + + return i3c_hub_do_daa(hub, controller); +} + +static int i3c_hub_master_send_ccc_cmd(struct i3c_master_controller *contr= oller, + struct i3c_ccc_cmd *cmd) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub =3D 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 =3D=3D 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 =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return false; + + hub =3D 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 descrip= tor + * and requests IBI for a device connected through the hub. The parent-fac= ing + * descriptor references the same IBI object so the physical controller us= es + * 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 =3D 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 =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub =3D hub_controller->hub; + + data =3D i3c_dev_get_master_data(desc); + if (!data) + return -ENODEV; + + parent =3D 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 =3D desc->info; + data->parent_desc.dev =3D desc->dev; + data->parent_desc.ibi =3D desc->ibi; + mutex_unlock(&hub->lock); + + i3c_bus_normaluse_lock(&parent->bus); + ret =3D 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 =3D NULL; + data->parent_desc.dev =3D NULL; + mutex_unlock(&hub->lock); + } + + return ret; +} + +static void i3c_hub_free_ibi(struct i3c_dev_desc *desc) +{ + struct i3c_master_controller *controller =3D 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 =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return; + + hub =3D hub_controller->hub; + + data =3D i3c_dev_get_master_data(desc); + if (!data || !data->parent_desc.ibi) + return; + + parent =3D 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 =3D NULL; + data->parent_desc.dev =3D 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 pare= nt + * controller. + */ +static int i3c_hub_enable_ibi(struct i3c_dev_desc *desc) +{ + struct i3c_master_controller *controller =3D 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 =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub =3D hub_controller->hub; + + data =3D i3c_dev_get_master_data(desc); + if (!data || !data->parent_desc.ibi) + return -ENODEV; + + parent =3D 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 =3D 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 par= ent + * controller. + */ +static int i3c_hub_disable_ibi(struct i3c_dev_desc *desc) +{ + struct i3c_master_controller *controller =3D 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 =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return -ENODEV; + + hub =3D hub_controller->hub; + + data =3D i3c_dev_get_master_data(desc); + if (!data || !data->parent_desc.ibi) + return -ENODEV; + + parent =3D 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 =3D 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 =3D i3c_dev_get_master_data(desc); + + if (!data) + return; + + i3c_dev_recycle_ibi_slot_controller(&data->parent_desc, slot); +} + +static const struct i3c_master_controller_ops i3c_hub_master_ops_data =3D { + .bus_init =3D i3c_hub_master_bus_init, + .bus_cleanup =3D i3c_hub_master_bus_cleanup, + .attach_i3c_dev =3D i3c_hub_attach_i3c_dev, + .reattach_i3c_dev =3D i3c_hub_reattach_i3c_dev, + .detach_i3c_dev =3D i3c_hub_detach_i3c_dev, + .do_daa =3D i3c_hub_master_do_daa, + .supports_ccc_cmd =3D i3c_hub_master_supports_ccc_cmd, + .send_ccc_cmd =3D i3c_hub_master_send_ccc_cmd, + .i3c_xfers =3D i3c_hub_master_priv_xfers, + .attach_i2c_dev =3D i3c_hub_attach_i2c_dev, + .detach_i2c_dev =3D i3c_hub_detach_i2c_dev, + .i2c_xfers =3D i3c_hub_i2c_xfers, + .request_ibi =3D i3c_hub_request_ibi, + .free_ibi =3D i3c_hub_free_ibi, + .enable_ibi =3D i3c_hub_enable_ibi, + .disable_ibi =3D i3c_hub_disable_ibi, + .recycle_ibi_slot =3D i3c_hub_recycle_ibi_slot, +}; + +/** + * i3c_hub_init() - Initialize hub context + * @hub: Hub instance + * @ops: Vendor callbacks + * @hub_dev: I3C hub device + */ +void i3c_hub_init(struct i3c_hub *hub, + const struct i3c_hub_ops *ops, + struct i3c_device *hub_dev) +{ + hub->ops =3D ops; + hub->hub_dev =3D hub_dev; + mutex_init(&hub->lock); +} +EXPORT_SYMBOL_GPL(i3c_hub_init); + +const struct i3c_master_controller_ops *i3c_hub_master_ops(void) +{ + return &i3c_hub_master_ops_data; +} +EXPORT_SYMBOL_GPL(i3c_hub_master_ops); + +/** + * 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 downstream I3C devices that keep t= he + * same static and assigned dynamic address, so parent DAA does not reuse = them. + * + * Return: 0 on success, or a negative error code. + */ +int i3c_hub_reserve_parent_addrslots_from_dt(struct i3c_hub_controller *hu= bc, + struct device_node *node) +{ + struct i3c_master_controller *parent =3D hubc->parent; + enum i3c_addr_slot_status status; + u32 assigned_addr; + u8 static_addr; + u32 reg[3]; + int ret; + + if (!parent || !node) + return -ENODEV; + + for_each_available_child_of_node_scoped(node, child) { + ret =3D of_property_read_variable_u32_array(child, "reg", reg, 1, 3); + if (ret < 0) + continue; + + ret =3D of_property_read_u32(child, "assigned-address", &assigned_addr); + if (ret) + continue; + + static_addr =3D reg[0]; + + if (!static_addr || !assigned_addr) + continue; + + if (static_addr !=3D assigned_addr) + continue; + + i3c_bus_maintenance_lock(&parent->bus); + status =3D i3c_bus_get_addr_slot_status(&parent->bus, + assigned_addr); + if (status =3D=3D 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/include/linux/i3c/hub.h b/include/linux/i3c/hub.h new file mode 100644 index 000000000000..90f4be0fdfba --- /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 + */ +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 *contr= oller) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return; + + hub =3D 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 *cont= roller) +{ + struct i3c_hub_controller *hub_controller; + struct i3c_hub *hub; + + hub_controller =3D dev_get_drvdata(&controller->dev); + if (!hub_controller || !hub_controller->hub) + return; + + hub =3D hub_controller->hub; + + if (hub && hub->ops && hub->ops->disable_port) + hub->ops->disable_port(controller); +} + +/** + * i3c_hub_master_ops() - Return virtual controller ops for hub ports + * + * Provides i3c_master_controller_ops used by controllers created for hub + * ports. + */ +const struct i3c_master_controller_ops *i3c_hub_master_ops(void); + +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 *hu= bc, + struct device_node *node); +#endif --=20 2.25.1 From nobody Mon Sep 28 21:04:12 2026 Received: from inva020.nxp.com (inva020.nxp.com [92.121.34.13]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6C9593F8899; Mon, 17 Aug 2026 10:39:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.13 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963156; cv=none; b=d5EwaLXBmtaV8oUS5TdwMvkSZblkCPLLgFWc4YTwP8F//L9I9q/2CvNqvMZvjn6a55BtAEGqoTlSN+JhB7X7S/EtPw3K9SXBeHKO4UZQ8jvTHZqteNKW8X9ffqYMNclTRVQypXMsVK9uaIMDJIu8jJojzpPEJ2gFxXNhPTOHzJE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963156; c=relaxed/simple; bh=Q8ZFSRu7EH/5WKF30xL0J/JX6erdw64NtTzHAaIzptY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=KOyOcfMLq9/M57PzMIsUDYSp2Im0hpC1bSM7gsVMH5crP2xM0/5UP9Mxw0/IJaQiU9prlA6mlDHRD7GKjCHHZuIEoPcbVSkkfcloqJrwhDQWIbiAFvvK/ctpr8d/pU4RuoAalCyS3906rPCQne0bgwlPcZ4s20WlPoTJaj9YuFo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.13 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva020.nxp.com (localhost [127.0.0.1]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 112401A0202; Mon, 17 Aug 2026 12:39:00 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 799B21A00FE; Mon, 17 Aug 2026 12:38:59 +0200 (CEST) Received: from lsv03900.swis.in-blr01.nxp.com (lsv03900.swis.in-blr01.nxp.com [10.12.177.15]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 1E8DE18000B0; Mon, 17 Aug 2026 18:38:58 +0800 (+08) From: Lakshay Piplani To: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, lee@kernel.org, Frank.Li@nxp.com, lgirdwood@gmail.com Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, aman.kumarpandey@nxp.com, Lakshay Piplani Subject: [PATCH v15 7/8] i3c: hub: p3h2x4x: Add support for NXP P3H2x4x I3C hub functionality Date: Mon, 17 Aug 2026 16:08:43 +0530 Message-Id: <20260817103844.2142802-8-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260817103844.2142802-1-lakshay.piplani@nxp.com> References: <20260817103844.2142802-1-lakshay.piplani@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" From: Aman Kumar Pandey Add I3C hub functionality for the NXP P3H2x4x family of multiport hubs. These devices support downstream target ports that can be configured as I3C, I2C, or SMBus. This driver enables: - I3C/I2C communication between host and hub - Transparent communication with downstream devices - Target port configuration (I3C/I2C/SMBus) P3H2440/P3H2441 support 4 target ports. P3H2840/P3H2841 support 8 target ports. Signed-off-by: Aman Kumar Pandey Signed-off-by: Vikash Bansal Signed-off-by: Lakshay Piplani --- Changes in v15: - Use the target-port count detected by the MFD parent and only configure registers and ports implemented by the selected device variant - Validate target-port indices against the detected number of ports - Preserve the MFD parent's driver data and publish the hub context through the shared MFD structure, with managed cleanup - Correct the SMBus transfer timeout calculation for 400 kHz operation - Use I2C adapter quirks to enforce the maximum read and write payload lengths Changes in v14: - Replace temporary parent dev->of_node reassignment with i3c_master_register_fwnode() - Use the shared MFD protected_reg_lock for protected hub configuration writes - Fix SMBus polling interval calculation to avoid oversleeping the computed transaction timeout - Change SMBus transfer loop counters from u8 to int - Clean up already registered SMBus adapters on adapter allocation or registration failure Changes in v13: - Fix SMBus transaction handling by replacing fixed delay with polling (re= ad_poll_timeout) to avoid premature reads and data corruption - Fix DT and of_node handling: prevent duplicate target-port node leaks an= d avoid corrupting parent dev->of_node by restoring it after registration - Add proper cleanup using devm actions (relock registers, release DT node= s, unregister adapters) and fix minor comment mismatch Changes in v12: - Fix target-port configuration register updates - Correct default pull-up and drive-strength values - Improve OF node and SMBus adapter cleanup - Remove dead code and simplify cleanup by relying on devm-managed resourc= es Changes in v11: - Fix IBI resource cleanup on error paths - Fix adapter unregister cleanup handling Changes in v10: - Split SMBus target/slave mode support, including IBI and MCTP receive handling, into a separate patch Changes in v9: - Added CONFIG_I2C_SLAVE guards where necessary to avoid build issues when I2C slave support is disabled. Changes in v8: - No change Changes in v7: - Remove CONFIG_I2C_SLAVE guards - Use Kernel API find_closest instead of custom helper - Use devm_regulator_get_enable_optional() - Fix kernel-doc warnings Changes in v6: - Remove generic I3C code and keep reg dependent code only. Changes in v5: - Updated supply names. Changes in v4: - Split the driver into three separate patches (mfd, regulator and I3C hub) - Added support for NXP P3H2x4x I3C hub functionality - Integrated hub driver with its on-die regulator Changes in v3: - Added MFD (Multi-Function Device) support for I3C hub and on-die regulat= or Changes in v2: - Refined coding style and incorporated review feedback - Updated directory structure - Revised logic for parsing DTS nodes --- --- MAINTAINERS | 1 + drivers/i3c/Kconfig | 1 + drivers/i3c/Makefile | 1 + drivers/i3c/hub/Kconfig | 11 + drivers/i3c/hub/Makefile | 4 + drivers/i3c/hub/p3h2840_i3c_hub.h | 332 ++++++++++++++++++ drivers/i3c/hub/p3h2840_i3c_hub_common.c | 428 +++++++++++++++++++++++ drivers/i3c/hub/p3h2840_i3c_hub_i3c.c | 119 +++++++ drivers/i3c/hub/p3h2840_i3c_hub_smbus.c | 337 ++++++++++++++++++ 9 files changed, 1234 insertions(+) create mode 100644 drivers/i3c/hub/Kconfig create mode 100644 drivers/i3c/hub/Makefile create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub.h create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_common.c create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_i3c.c create mode 100644 drivers/i3c/hub/p3h2840_i3c_hub_smbus.c diff --git a/MAINTAINERS b/MAINTAINERS index 11f231ce05cf..8e0e38e5b28d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19553,6 +19553,7 @@ L: linux-i3c@lists.infradead.org S: Maintained F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml F: drivers/i3c/hub.c +F: drivers/i3c/hub/* F: drivers/mfd/p3h2840.c F: drivers/regulator/p3h2840_i3c_hub_regulator.c F: include/linux/i3c/hub.h diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig index 7536f3740c94..81d5ebd8025c 100644 --- a/drivers/i3c/Kconfig +++ b/drivers/i3c/Kconfig @@ -35,6 +35,7 @@ config I3C_HUB =20 Say Y here if your platform includes an I3C hub device =20 +source "drivers/i3c/hub/Kconfig" endif # I3C =20 config I3C_OR_I2C diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile index 9ddee56a6338..2950820db9ea 100644 --- a/drivers/i3c/Makefile +++ b/drivers/i3c/Makefile @@ -3,3 +3,4 @@ i3c-y :=3D device.o master.o obj-$(CONFIG_I3C) +=3D i3c.o obj-$(CONFIG_I3C) +=3D master/ obj-$(CONFIG_I3C_HUB) +=3D hub.o +obj-$(CONFIG_I3C_HUB) +=3D hub/ diff --git a/drivers/i3c/hub/Kconfig b/drivers/i3c/hub/Kconfig new file mode 100644 index 000000000000..816879fda326 --- /dev/null +++ b/drivers/i3c/hub/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2025 NXP +config P3H2X4X_I3C_HUB + tristate "NXP P3H2X4X I3C HUB support" + depends on MFD_P3H2X4X + select I3C_HUB + help + This enables support for NXP P3H244x/P3H284x I3C HUB. These hubs + connect to a host via I3C/I2C/SMBus and allow communication with + multiple downstream peripherals. Say Y or M here to enable the + P3H2x4x I3C HUB driver. diff --git a/drivers/i3c/hub/Makefile b/drivers/i3c/hub/Makefile new file mode 100644 index 000000000000..9dbd8a7b4184 --- /dev/null +++ b/drivers/i3c/hub/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2025 NXP +p3h2840_i3c_hub-y :=3D p3h2840_i3c_hub_common.o p3h2840_i3c_hub_i3c.o p3h2= 840_i3c_hub_smbus.o +obj-$(CONFIG_P3H2X4X_I3C_HUB) +=3D p3h2840_i3c_hub.o diff --git a/drivers/i3c/hub/p3h2840_i3c_hub.h b/drivers/i3c/hub/p3h2840_i3= c_hub.h new file mode 100644 index 000000000000..5e1272bfdc43 --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub.h @@ -0,0 +1,332 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025-2026 NXP + * Private definitions for the NXP P3H2X4X I3C hub driver. + */ + +#ifndef P3H2840_I3C_HUB_H +#define P3H2840_I3C_HUB_H + +#include +#include +#include +#include +#include +#include +#include + +/* I3C HUB REGISTERS */ + +/* Device Information Registers */ +#define P3H2X4X_DEV_INFO_0 0x00 +#define P3H2X4X_DEV_INFO_1 0x01 +#define P3H2X4X_PID_5 0x02 +#define P3H2X4X_PID_4 0x03 +#define P3H2X4X_PID_3 0x04 +#define P3H2X4X_PID_2 0x05 +#define P3H2X4X_PID_1 0x06 +#define P3H2X4X_PID_0 0x07 +#define P3H2X4X_BCR 0x08 +#define P3H2X4X_DCR 0x09 +#define P3H2X4X_DEV_CAPAB 0x0a +#define P3H2X4X_DEV_REV 0x0b + +/* Device Configuration Registers */ +#define P3H2X4X_CP_CONF 0x11 +#define P3H2X4X_TP_ENABLE 0x12 + +#define P3H2X4X_DEV_CONF 0x13 +#define P3H2X4X_IO_STRENGTH 0x14 +#define P3H2X4X_TP0145_IO_STRENGTH_MASK GENMASK(1, 0) +#define P3H2X4X_TP0145_IO_STRENGTH(x) \ + FIELD_PREP(P3H2X4X_TP0145_IO_STRENGTH_MASK, x) +#define P3H2X4X_TP2367_IO_STRENGTH_MASK GENMASK(3, 2) +#define P3H2X4X_TP2367_IO_STRENGTH(x) \ + FIELD_PREP(P3H2X4X_TP2367_IO_STRENGTH_MASK, x) +#define P3H2X4X_CP0_IO_STRENGTH_MASK GENMASK(5, 4) +#define P3H2X4X_CP0_IO_STRENGTH(x) \ + FIELD_PREP(P3H2X4X_CP0_IO_STRENGTH_MASK, x) +#define P3H2X4X_CP1_IO_STRENGTH_MASK GENMASK(7, 6) +#define P3H2X4X_CP1_IO_STRENGTH(x) \ + FIELD_PREP(P3H2X4X_CP1_IO_STRENGTH_MASK, x) +#define P3H2X4X_IO_STRENGTH_MASK GENMASK(7, 0) + +#define P3H2X4X_TP_IO_MODE_CONF 0x17 +#define P3H2X4X_TP_SMBUS_AGNT_EN 0x18 + +#define P3H2X4X_LDO_AND_PULLUP_CONF 0x19 + +#define P3H2X4X_TP0145_PULLUP_CONF_MASK GENMASK(7, 6) +#define P3H2X4X_TP0145_PULLUP_CONF(x) \ + FIELD_PREP(P3H2X4X_TP0145_PULLUP_CONF_MASK, x) +#define P3H2X4X_TP2367_PULLUP_CONF_MASK GENMASK(5, 4) +#define P3H2X4X_TP2367_PULLUP_CONF(x) \ + FIELD_PREP(P3H2X4X_TP2367_PULLUP_CONF_MASK, x) +#define P3H2X4X_PULLUP_CONF_MASK GENMASK(7, 4) + +#define P3H2X4X_CP_IBI_CONF 0x1a + +#define P3H2X4X_TP_SMBUS_AGNT_IBI_CONFIG 0x1b + +#define P3H2X4X_IBI_MDB_CUSTOM 0x1c +#define P3H2X4X_JEDEC_CONTEXT_ID 0x1d +#define P3H2X4X_TP_GPIO_MODE_EN 0x1e + +/* Device Status and IBI Registers */ +#define P3H2X4X_DEV_AND_IBI_STS 0x20 +#define P3H2X4X_TP_SMBUS_AGNT_IBI_STS 0x21 +#define P3H2X4X_SMBUS_AGENT_EVENT_FLAG_STATUS BIT(4) + +/* Controller Port Control/Status Registers */ +#define P3H2X4X_CP_MUX_SET 0x38 +#define P3H2X4X_CONTROLLER_PORT_MUX_REQ BIT(0) +#define P3H2X4X_CP_MUX_STS 0x39 +#define P3H2X4X_CONTROLLER_PORT_MUX_CONNECTION_STATUS BIT(0) + +/* Target Ports Control Registers */ +#define P3H2X4X_TP_SMBUS_AGNT_TRANS_START 0x50 +#define P3H2X4X_TP_NET_CON_CONF 0x51 + +#define P3H2X4X_TP_PULLUP_EN 0x53 + +#define P3H2X4X_TP_SCL_OUT_EN 0x54 +#define P3H2X4X_TP_SDA_OUT_EN 0x55 +#define P3H2X4X_TP_SCL_OUT_LEVEL 0x56 +#define P3H2X4X_TP_SDA_OUT_LEVEL 0x57 +#define P3H2X4X_TP_IN_DETECT_MODE_CONF 0x58 +#define P3H2X4X_TP_SCL_IN_DETECT_IBI_EN 0x59 +#define P3H2X4X_TP_SDA_IN_DETECT_IBI_EN 0x5a + +/* Target Ports Status Registers */ +#define P3H2X4X_TP_SCL_IN_LEVEL_STS 0x60 +#define P3H2X4X_TP_SDA_IN_LEVEL_STS 0x61 +#define P3H2X4X_TP_SCL_IN_DETECT_FLG 0x62 +#define P3H2X4X_TP_SDA_IN_DETECT_FLG 0x63 + +/* SMBus Agent Configuration and Status Registers */ +#define P3H2X4X_TP0_SMBUS_AGNT_STS 0x64 +#define P3H2X4X_TP1_SMBUS_AGNT_STS 0x65 +#define P3H2X4X_TP2_SMBUS_AGNT_STS 0x66 +#define P3H2X4X_TP3_SMBUS_AGNT_STS 0x67 +#define P3H2X4X_TP4_SMBUS_AGNT_STS 0x68 +#define P3H2X4X_TP5_SMBUS_AGNT_STS 0x69 +#define P3H2X4X_TP6_SMBUS_AGNT_STS 0x6a +#define P3H2X4X_TP7_SMBUS_AGNT_STS 0x6b +#define P3H2X4X_ONCHIP_TD_AND_SMBUS_AGNT_CONF 0x6c + +/* buf receive flag set */ +#define P3H2X4X_TARGET_BUF_CA_TF BIT(0) +#define P3H2X4X_TARGET_BUF_0_RECEIVE BIT(1) +#define P3H2X4X_TARGET_BUF_1_RECEIVE BIT(2) +#define P3H2X4X_TARGET_BUF_0_1_RECEIVE GENMASK(2, 1) +#define P3H2X4X_TARGET_BUF_OVRFL GENMASK(3, 1) +#define BUF_RECEIVED_FLAG_MASK GENMASK(3, 1) +#define BUF_RECEIVED_FLAG_TF_MASK GENMASK(3, 0) + +#define P3H2X4X_TARGET_AGENT_LOCAL_DEV 0x11 +#define P3H2X4X_TARGET_BUFF_0_PAGE 0x12 +#define P3H2X4X_TARGET_BUFF_1_PAGE 0x13 + +/* Special Function Registers */ +#define P3H2X4X_LDO_AND_CPSEL_STS 0x79 +#define P3H2X4X_CP_SDA1_LEVEL BIT(7) +#define P3H2X4X_CP_SCL1_LEVEL BIT(6) + +#define P3H2X4X_CP_SEL_PIN_INPUT_CODE_MASK GENMASK(5, 4) +#define P3H2X4X_CP_SEL_PIN_INPUT_CODE_GET(x) \ + (((x) & P3H2X4X_CP_SEL_PIN_INPUT_CODE_MASK) >> 4) +#define P3H2X4X_CP_SDA1_SCL1_PINS_CODE_MASK GENMASK(7, 6) +#define P3H2X4X_CP_SDA1_SCL1_PINS_CODE_GET(x) \ + (((x) & P3H2X4X_CP_SDA1_SCL1_PINS_CODE_MASK) >> 6) +#define P3H2X4X_VCCIO1_PWR_GOOD BIT(3) +#define P3H2X4X_VCCIO0_PWR_GOOD BIT(2) +#define P3H2X4X_CP1_VCCIO_PWR_GOOD BIT(1) +#define P3H2X4X_CP0_VCCIO_PWR_GOOD BIT(0) + +#define P3H2X4X_BUS_RESET_SCL_TIMEOUT 0x7a +#define P3H2X4X_ONCHIP_TD_PROTO_ERR_FLG 0x7b +#define P3H2X4X_DEV_CMD 0x7c +#define P3H2X4X_ONCHIP_TD_STS 0x7d +#define P3H2X4X_ONCHIP_TD_ADDR_CONF 0x7e +#define P3H2X4X_PAGE_PTR 0x7f + +/* Paged Transaction Registers */ +#define P3H2X4X_CONTROLLER_BUFFER_PAGE 0x10 +#define P3H2X4X_CONTROLLER_AGENT_BUFF 0x80 +#define P3H2X4X_CONTROLLER_AGENT_BUFF_DATA 0x84 + +#define P3H2X4X_TARGET_BUFF_LENGTH 0x80 +#define P3H2X4X_TARGET_BUFF_ADDRESS 0x81 +#define P3H2X4X_TARGET_BUFF_DATA 0x82 + +#define P3H2X4X_TP_MAX_COUNT 0x08 +#define P3H2X4X_CP_MAX_COUNT 0x02 +#define P3H2X4X_TP_LOCAL_DEV 0x08 + +/* LDO Disable/Enable DT settings */ +#define P3H2X4X_LDO_VOLT_1_0V 0x00 +#define P3H2X4X_LDO_VOLT_1_1V 0x01 +#define P3H2X4X_LDO_VOLT_1_2V 0x02 +#define P3H2X4X_LDO_VOLT_1_8V 0x03 + +#define P3H2X4X_LDO_DISABLED 0x00 +#define P3H2X4X_LDO_ENABLED 0x01 + +#define P3H2X4X_IBI_DISABLED 0x00 +#define P3H2X4X_IBI_ENABLED 0x01 + +#define P3H2X4X_TP_PULLUP_DISABLED 0x00 +#define P3H2X4X_TP_PULLUP_ENABLED 0x01 + +#define ONE_BYTE_SIZE 0x01 + +/* holding SDA low when both SMBus Target Agent received data buffers are = full. + * This feature can be used as a flow-control mechanism for MCTP applicati= ons to + * avoid MCTP transmitters on Target Ports time out when the SMBus agent b= uffers + * are not serviced in time by upstream controller and only receives write= message + * from its downstream ports. + * SMBUS_AGENT_TX_RX_LOOPBACK_EN/TARGET_AGENT_BUF_FULL_SDA_LOW_EN + */ + +#define P3H2X4X_TARGET_AGENT_DFT_IBI_CONF 0x20 +#define P3H2X4X_TARGET_AGENT_DFT_IBI_CONF_MASK 0x21 + +/* Transaction status checking mask */ +#define P3H2X4X_SMBUS_TRANSACTION_FINISH_FLAG 1 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_SHIFT 4 + +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_OK 0 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_ADDR_NAK 1 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_DATA_NAK 2 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_WTR_NAK 3 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_SYNC_RCV 4 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_SYNC_RCVCLR 5 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_FAULT 6 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_ARB_LOSS 7 +#define P3H2X4X_SMBUS_CNTRL_STATUS_TXN_SCL_TO 8 + +#define P3H2X4X_TP_BUFFER_STATUS_MASK 0x0f +#define P3H2X4X_TP_TRANSACTION_CODE_MASK 0xf0 + +/* SMBus transaction types fields */ +#define P3H2X4X_SMBUS_400kHz BIT(2) + +/* SMBus polling */ +#define P3H2X4X_SMBUS_POLL_COUNT 10 +#define P3H2X4X_SMBUS_POLL_INTERVAL_MIN_US 20 +#define P3H2X4X_SMBUS_POLL_INTERVAL_MAX_US 150 + +/* Hub buffer size */ +#define P3H2X4X_CONTROLLER_BUFFER_SIZE 88 +#define P3H2X4X_TARGET_BUFFER_SIZE 80 +#define P3H2X4X_SMBUS_DESCRIPTOR_SIZE 4 +#define P3H2X4X_SMBUS_PAYLOAD_SIZE \ + (P3H2X4X_CONTROLLER_BUFFER_SIZE - P3H2X4X_SMBUS_DESCRIPTOR_SIZE) +#define P3H2X4X_SMBUS_TARGET_PAYLOAD_SIZE (P3H2X4X_TARGET_BUFFER_SIZE - 2) + +/* + * At 400 kHz, one 9-bit I2C byte takes 22.5 us. Round this + * up to 23 us per payload byte and add 100 us for the address + * byte, bus overhead and controller processing time. + */ +#define P3H2X4X_SMBUS_400kHz_TRANSFER_TIMEOUT(x) ((23 * (x)) + 100) + +#define P3H2X4X_NO_PAGE_PER_TP 4 + +#define P3H2X4X_MAX_PAYLOAD_LEN 2 +#define P3H2X4X_NUM_SLOTS 6 + +#define P3H2X4X_HUB_ID 0 + +#define P3H2X4X_SET_BIT(n) BIT(n) + +#define P3H2X4X_TP_MASK GENMASK(P3H2X4X_TP_MAX_COUNT - 1, 0) + +#define P3H2X4X_DFT_TP_PULLUP_OHMS 500 +#define P3H2X4X_DFT_IO_STRENGTH_OHMS 20 + +enum p3h2x4x_tp { + TP_0, + TP_1, + TP_2, + TP_3, + TP_4, + TP_5, + TP_6, + TP_7, +}; + +enum p3h2x4x_rcv_buf { + RCV_BUF_0, + RCV_BUF_1, + RCV_BUF_OF, +}; + +enum p3h2x4x_tp_mode { + P3H2X4X_TP_MODE_I3C, + P3H2X4X_TP_MODE_SMBUS, +}; + +struct tp_configuration { + bool pullup_en; + bool ibi_en; + bool always_enable; + enum p3h2x4x_tp_mode mode; +}; + +struct hub_configuration { + int tp0145_pullup; + int tp2367_pullup; + int cp0_io_strength; + int cp1_io_strength; + int tp0145_io_strength; + int tp2367_io_strength; + struct tp_configuration tp_config[P3H2X4X_TP_MAX_COUNT]; +}; + +struct tp_bus { + bool is_registered; /* bus was registered in the framework. */ + u8 tp_mask; + u8 tp_port; + struct mutex port_mutex; /* per port mutex */ + struct device_node *of_node; + struct i2c_client *tp_smbus_client; + struct i2c_adapter *tp_smbus_adapter; + struct i3c_hub_controller hub_controller; + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub; +}; + +struct p3h2x4x_i3c_hub_dev { + struct device *dev; + struct regmap *regmap; + struct mutex etx_mutex; /* all port mutex */ + struct i3c_device *i3cdev; + struct i2c_client *i2c_client; + struct hub_configuration hub_config; + struct tp_bus tp_bus[P3H2X4X_TP_MAX_COUNT]; + struct i3c_hub *hub; +}; + +/** + * p3h2x4x_unregister_smbus_adapters() - unregister SMBus adapters + * @hub: P3H2x4x hub device + */ +void p3h2x4x_unregister_smbus_adapters(struct p3h2x4x_i3c_hub_dev *hub); + +/** + * p3h2x4x_tp_smbus_algo - add i2c adapter for target port configured as S= MBus. + * @p3h2x4x_i3c_hub: P3H2x4x hub device. + * + * Return: 0 in case of success, negative error code on failure. + */ +int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub); + +/** + * p3h2x4x_tp_i3c_algo - register i3c controller for target port configure= d as I3C. + * @p3h2x4x_i3c_hub: P3H2x4x hub device. + * + * Return: 0 in case of success, negative error code on failure. + */ +int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub); + +#endif /* P3H2840_I3C_HUB_H */ diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_common.c b/drivers/i3c/hub/p3h= 2840_i3c_hub_common.c new file mode 100644 index 000000000000..3bf79f4692f5 --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_common.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025-2026 NXP + * This P3H2X4X driver file implements functions for Hub probe and DT pars= ing. + */ + +#include +#include +#include +#include +#include + +#include "p3h2840_i3c_hub.h" + +/* LDO voltage DT settings */ +#define P3H2X4X_DT_LDO_VOLT_1_0V 1000000 +#define P3H2X4X_DT_LDO_VOLT_1_1V 1100000 +#define P3H2X4X_DT_LDO_VOLT_1_2V 1200000 +#define P3H2X4X_DT_LDO_VOLT_1_8V 1800000 + +static const int p3h2x4x_pullup_tbl[] =3D { + 250, 500, 1000, 2000 +}; + +static const int p3h2x4x_io_strength_tbl[] =3D { + 20, 30, 40, 50 +}; + +static u8 p3h2x4x_pullup_dt_to_reg(int dt_value) +{ + return find_closest(dt_value, p3h2x4x_pullup_tbl, + ARRAY_SIZE(p3h2x4x_pullup_tbl)); +} + +static u8 p3h2x4x_io_strength_dt_to_reg(int dt_value) +{ + return find_closest(dt_value, p3h2x4x_io_strength_tbl, + ARRAY_SIZE(p3h2x4x_io_strength_tbl)); +} + +static int p3h2x4x_configure_pullup(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub =3D dev_get_drvdata(dev); + u8 pullup; + + pullup =3D P3H2X4X_TP0145_PULLUP_CONF(p3h2x4x_pullup_dt_to_reg + (p3h2x4x_i3c_hub->hub_config.tp0145_pullup)); + + pullup |=3D P3H2X4X_TP2367_PULLUP_CONF(p3h2x4x_pullup_dt_to_reg + (p3h2x4x_i3c_hub->hub_config.tp2367_pullup)); + + return regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2X4X_LDO_AND_PULLUP= _CONF, + P3H2X4X_PULLUP_CONF_MASK, pullup); +} + +static int p3h2x4x_configure_io_strength(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub =3D dev_get_drvdata(dev); + u8 io_strength; + + io_strength =3D P3H2X4X_CP0_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg + (p3h2x4x_i3c_hub->hub_config.cp0_io_strength)); + + io_strength |=3D P3H2X4X_CP1_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg + (p3h2x4x_i3c_hub->hub_config.cp1_io_strength)); + + io_strength |=3D P3H2X4X_TP0145_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg + (p3h2x4x_i3c_hub->hub_config.tp0145_io_strength)); + + io_strength |=3D P3H2X4X_TP2367_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg + (p3h2x4x_i3c_hub->hub_config.tp2367_io_strength)); + + return regmap_update_bits(p3h2x4x_i3c_hub->regmap, P3H2X4X_IO_STRENGTH, + P3H2X4X_IO_STRENGTH_MASK, io_strength); +} + +static int p3h2x4x_configure_ldo(struct device *dev) +{ + static const char * const supplies[] =3D { + "vcc1", + "vcc2", + "vcc3", + "vcc4" + }; + int ret, i; + + for (i =3D 0; i < ARRAY_SIZE(supplies); i++) { + ret =3D devm_regulator_get_enable_optional(dev, supplies[i]); + if (ret =3D=3D -EPROBE_DEFER) + return -EPROBE_DEFER; + + if (ret && ret !=3D -ENODEV) + dev_warn(dev, "Failed to enable %s (%d)\n", + supplies[i], ret); + } + + /* This delay is required for the regulator to stabilize its output volta= ge */ + fsleep(5000); + + return 0; +} + +static int p3h2x4x_configure_tp(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *hub =3D dev_get_drvdata(dev); + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(dev->parent); + u8 mode =3D 0, smbus =3D 0, pullup =3D 0, target_port =3D 0; + u8 tp_mask; + int tp, ret; + + for (tp =3D 0; tp < p3h2x4x->num_target_ports; tp++) { + pullup |=3D hub->hub_config.tp_config[tp].pullup_en ? P3H2X4X_SET_BIT(tp= ) : 0; + mode |=3D (hub->hub_config.tp_config[tp].mode !=3D P3H2X4X_TP_MODE_I3C) ? + P3H2X4X_SET_BIT(tp) : 0; + smbus |=3D (hub->hub_config.tp_config[tp].mode =3D=3D P3H2X4X_TP_MODE_SM= BUS) ? + P3H2X4X_SET_BIT(tp) : 0; + target_port |=3D (hub->tp_bus[tp].tp_mask =3D=3D P3H2X4X_SET_BIT(tp)) ? + hub->tp_bus[tp].tp_mask : 0; + } + + /* Only touch the bits for the target ports this variant provides. */ + tp_mask =3D GENMASK(p3h2x4x->num_target_ports - 1, 0); + + ret =3D regmap_update_bits(hub->regmap, P3H2X4X_TP_PULLUP_EN, tp_mask, pu= llup); + if (ret) + return ret; + + ret =3D regmap_update_bits(hub->regmap, P3H2X4X_TP_IO_MODE_CONF, tp_mask,= mode); + if (ret) + return ret; + + ret =3D regmap_update_bits(hub->regmap, P3H2X4X_TP_SMBUS_AGNT_EN, tp_mask= , smbus); + if (ret) + return ret; + + if (target_port & ~smbus) { + ret =3D regmap_write(hub->regmap, P3H2X4X_CP_MUX_SET, + P3H2X4X_CONTROLLER_PORT_MUX_REQ); + if (ret) + return ret; + } + + return regmap_update_bits(hub->regmap, P3H2X4X_TP_ENABLE, tp_mask, target= _port); +} + +static int p3h2x4x_configure_hw(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *hub =3D dev_get_drvdata(dev); + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(dev->parent); + int ret, ret2; + + ret =3D p3h2x4x_configure_ldo(dev); + if (ret) + return ret; + + /* Protect the unlock-modify-lock sequence with the shared MFD lock */ + mutex_lock(&p3h2x4x->protected_reg_lock); + + ret =3D regmap_write(hub->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_UNLOCK_CODE); + if (ret) + goto out_unlock_mutex; + + ret =3D p3h2x4x_configure_pullup(dev); + if (ret) + goto out_lock; + + ret =3D p3h2x4x_configure_io_strength(dev); + if (ret) + goto out_lock; + + ret =3D p3h2x4x_configure_tp(dev); + if (ret) + goto out_lock; + +out_lock: + ret2 =3D regmap_write(hub->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_LOCK_CODE); + if (!ret && ret2) + ret =3D ret2; + +out_unlock_mutex: + mutex_unlock(&p3h2x4x->protected_reg_lock); + return ret; +} + +static void p3h2x4x_get_target_port_dt_conf(struct device *dev, + const struct device_node *node) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub =3D dev_get_drvdata(dev); + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(dev->parent); + u64 tp_port; + + for_each_available_child_of_node_scoped(node, dev_node) { + if (of_property_read_reg(dev_node, 0, &tp_port, NULL)) + continue; + + if (tp_port < p3h2x4x->num_target_ports) { + if (p3h2x4x_i3c_hub->tp_bus[tp_port].of_node) { + dev_warn(dev, "Duplicate target port %llu in DT\n", tp_port); + continue; + } + + p3h2x4x_i3c_hub->tp_bus[tp_port].of_node =3D of_node_get(dev_node); + p3h2x4x_i3c_hub->tp_bus[tp_port].tp_mask =3D P3H2X4X_SET_BIT(tp_port); + p3h2x4x_i3c_hub->tp_bus[tp_port].p3h2x4x_i3c_hub =3D p3h2x4x_i3c_hub; + p3h2x4x_i3c_hub->tp_bus[tp_port].tp_port =3D tp_port; + } + } +} + +static int p3h2x4x_parse_tp_dt_settings(struct device *dev, + const struct device_node *node, + struct tp_configuration tp_config[]) +{ + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(dev->parent); + u64 id; + int ret; + + for_each_available_child_of_node_scoped(node, tp_node) { + enum p3h2x4x_tp_mode mode; + + /* + * Only "i3c" and "smbus" children describe target ports. Skip any + * other child (for example the MFD "regulators" container), which + * has no "reg" property. + */ + if (of_node_name_eq(tp_node, "i3c")) + mode =3D P3H2X4X_TP_MODE_I3C; + else if (of_node_name_eq(tp_node, "smbus")) + mode =3D P3H2X4X_TP_MODE_SMBUS; + else + continue; + + ret =3D of_property_read_reg(tp_node, 0, &id, NULL); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read reg for %pOF\n", + tp_node); + + if (id >=3D p3h2x4x->num_target_ports) + return dev_err_probe(dev, -EINVAL, + "Invalid target port index %llu\n", + id); + + tp_config[id].mode =3D mode; + tp_config[id].pullup_en =3D + of_property_read_bool(tp_node, "nxp,pullup-enable"); + } + + return 0; +} + +static int p3h2x4x_get_hub_dt_conf(struct device *dev, + const struct device_node *node) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub =3D dev_get_drvdata(dev); + + of_property_read_u32(node, "nxp,tp0145-pullup-ohms", + &p3h2x4x_i3c_hub->hub_config.tp0145_pullup); + of_property_read_u32(node, "nxp,tp2367-pullup-ohms", + &p3h2x4x_i3c_hub->hub_config.tp2367_pullup); + of_property_read_u32(node, "nxp,cp0-io-strength-ohms", + &p3h2x4x_i3c_hub->hub_config.cp0_io_strength); + of_property_read_u32(node, "nxp,cp1-io-strength-ohms", + &p3h2x4x_i3c_hub->hub_config.cp1_io_strength); + of_property_read_u32(node, "nxp,tp0145-io-strength-ohms", + &p3h2x4x_i3c_hub->hub_config.tp0145_io_strength); + of_property_read_u32(node, "nxp,tp2367-io-strength-ohms", + &p3h2x4x_i3c_hub->hub_config.tp2367_io_strength); + + return p3h2x4x_parse_tp_dt_settings(dev, node, + p3h2x4x_i3c_hub->hub_config.tp_config); +} + +static void p3h2x4x_default_configuration(struct device *dev) +{ + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub =3D dev_get_drvdata(dev); + int tp_count; + + p3h2x4x_i3c_hub->hub_config.tp0145_pullup =3D P3H2X4X_DFT_TP_PULLUP_OHMS; + p3h2x4x_i3c_hub->hub_config.tp2367_pullup =3D P3H2X4X_DFT_TP_PULLUP_OHMS; + p3h2x4x_i3c_hub->hub_config.cp0_io_strength =3D P3H2X4X_DFT_IO_STRENGTH_O= HMS; + p3h2x4x_i3c_hub->hub_config.cp1_io_strength =3D P3H2X4X_DFT_IO_STRENGTH_O= HMS; + p3h2x4x_i3c_hub->hub_config.tp0145_io_strength =3D P3H2X4X_DFT_IO_STRENGT= H_OHMS; + p3h2x4x_i3c_hub->hub_config.tp2367_io_strength =3D P3H2X4X_DFT_IO_STRENGT= H_OHMS; + + for (tp_count =3D 0; tp_count < P3H2X4X_TP_MAX_COUNT; ++tp_count) + p3h2x4x_i3c_hub->hub_config.tp_config[tp_count].mode =3D P3H2X4X_TP_MODE= _I3C; +} + +static void p3h2x4x_unregister_smbus_adapters_action(void *data) +{ + p3h2x4x_unregister_smbus_adapters(data); +} + +static void p3h2x4x_put_target_port_of_nodes(void *data) +{ + struct p3h2x4x_i3c_hub_dev *hub =3D data; + int tp; + + for (tp =3D 0; tp < P3H2X4X_TP_MAX_COUNT; tp++) { + of_node_put(hub->tp_bus[tp].of_node); + hub->tp_bus[tp].of_node =3D NULL; + } +} + +static void p3h2x4x_clear_i3c_hub_priv(void *data) +{ + struct p3h2x4x *p3h2x4x =3D data; + + /* Drop the IBI handler backpointer; see the ordering note at the registr= ation site. */ + p3h2x4x->i3c_hub_priv =3D NULL; +} + +static int p3h2x4x_i3c_hub_probe(struct platform_device *pdev) +{ + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(pdev->dev.parent); + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub; + struct device *dev =3D &pdev->dev; + struct device_node *node; + int ret, i; + + p3h2x4x_i3c_hub =3D devm_kzalloc(dev, sizeof(*p3h2x4x_i3c_hub), GFP_KERNE= L); + if (!p3h2x4x_i3c_hub) + return -ENOMEM; + + p3h2x4x_i3c_hub->regmap =3D p3h2x4x->regmap; + p3h2x4x_i3c_hub->dev =3D dev; + + platform_set_drvdata(pdev, p3h2x4x_i3c_hub); + device_set_of_node_from_dev(dev, dev->parent); + + p3h2x4x_default_configuration(dev); + + ret =3D devm_mutex_init(dev, &p3h2x4x_i3c_hub->etx_mutex); + if (ret) + return ret; + + for (i =3D 0; i < P3H2X4X_TP_MAX_COUNT; i++) { + ret =3D devm_mutex_init(dev, &p3h2x4x_i3c_hub->tp_bus[i].port_mutex); + if (ret) + return ret; + } + + /* get hub node from DT */ + node =3D dev_of_node(dev); + if (!node) + return dev_err_probe(dev, -ENODEV, "No Device Tree entry found\n"); + + ret =3D p3h2x4x_get_hub_dt_conf(dev, node); + if (ret) + return ret; + + p3h2x4x_get_target_port_dt_conf(dev, node); + + ret =3D devm_add_action_or_reset(dev, + p3h2x4x_put_target_port_of_nodes, + p3h2x4x_i3c_hub); + if (ret) + return ret; + + ret =3D p3h2x4x_configure_hw(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure the HUB\n"); + + /* Register virtual I3C master controllers for I3C target ports */ + if (p3h2x4x->i3cdev) { + p3h2x4x_i3c_hub->i3cdev =3D p3h2x4x->i3cdev; + /* + * Publish the hub context in the MFD parent struct rather than + * via i3cdev_set_drvdata(), which would overwrite the parent's + * drvdata (struct p3h2x4x) that the IBI handler and other MFD + * callbacks rely on. Publish it before p3h2x4x_tp_i3c_algo() + * enables IBI, since the IBI handler dereferences it. + */ + p3h2x4x->i3c_hub_priv =3D p3h2x4x_i3c_hub; + + /* + * Register the clear action before enabling IBI so that, on the + * devm LIFO unwind (probe failure or removal), the pointer is + * cleared only after IBI has been disabled and freed. + */ + ret =3D devm_add_action_or_reset(dev, p3h2x4x_clear_i3c_hub_priv, + p3h2x4x); + if (ret) + return ret; + + ret =3D p3h2x4x_tp_i3c_algo(p3h2x4x_i3c_hub); + if (ret) + return dev_err_probe(dev, ret, "Failed to register i3c bus\n"); + } + + /* Register virtual I2C adapters for SMBus target ports */ + ret =3D p3h2x4x_tp_smbus_algo(p3h2x4x_i3c_hub); + if (ret) + return dev_err_probe(dev, ret, "Failed to add i2c adapter\n"); + + ret =3D devm_add_action_or_reset(dev, + p3h2x4x_unregister_smbus_adapters_action, + p3h2x4x_i3c_hub); + if (ret) + return ret; + + return 0; +} + +static const struct platform_device_id p3h2x4x_i3c_hub_id[] =3D { + { "p3h2x4x-i3c-hub" }, + { } +}; +MODULE_DEVICE_TABLE(platform, p3h2x4x_i3c_hub_id); + +static struct platform_driver p3h2x4x_i3c_hub_driver =3D { + .driver =3D { + .name =3D "p3h2x4x-i3c-hub", + .probe_type =3D PROBE_PREFER_ASYNCHRONOUS, + }, + .probe =3D p3h2x4x_i3c_hub_probe, + .id_table =3D p3h2x4x_i3c_hub_id, +}; +module_platform_driver(p3h2x4x_i3c_hub_driver); + +MODULE_AUTHOR("Aman Kumar Pandey "); +MODULE_AUTHOR("Vikash Bansal "); +MODULE_AUTHOR("Lakshay Piplani "); +MODULE_DESCRIPTION("P3H2X4X I3C HUB driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c b/drivers/i3c/hub/p3h284= 0_i3c_hub_i3c.c new file mode 100644 index 000000000000..22c355f60bbf --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025-2026 NXP + * This P3H2X4X driver file contain functions for I3C virtual Bus creation= , connect/disconnect + * hub network and read/write. + */ +#include +#include +#include + +#include "p3h2840_i3c_hub.h" + +static inline struct tp_bus * +p3h2x4x_bus_from_controller(struct i3c_master_controller *controller) +{ + struct i3c_hub_controller *hub_controller; + + hub_controller =3D container_of(controller, struct i3c_hub_controller, co= ntroller); + + return container_of(hub_controller, struct tp_bus, hub_controller); +} + +static void p3h2x4x_hub_enable_port(struct i3c_master_controller *controll= er) +{ + struct tp_bus *bus =3D p3h2x4x_bus_from_controller(controller); + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub =3D bus->p3h2x4x_i3c_hub; + + if (p3h2x4x_i3c_hub->hub_config.tp_config[bus->tp_port].always_enable) + return; + + regmap_set_bits(p3h2x4x_i3c_hub->regmap, P3H2X4X_TP_NET_CON_CONF, bus->tp= _mask); +} + +static void p3h2x4x_hub_disable_port(struct i3c_master_controller *control= ler) +{ + struct tp_bus *bus =3D p3h2x4x_bus_from_controller(controller); + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub =3D bus->p3h2x4x_i3c_hub; + + if (p3h2x4x_i3c_hub->hub_config.tp_config[bus->tp_port].always_enable) + return; + + regmap_clear_bits(p3h2x4x_i3c_hub->regmap, P3H2X4X_TP_NET_CON_CONF, bus->= tp_mask); +} + +static const struct i3c_hub_ops p3h2x4x_hub_ops =3D { + .enable_port =3D p3h2x4x_hub_enable_port, + .disable_port =3D p3h2x4x_hub_disable_port, +}; + +static void p3h2x4x_unregister_i3c_master(void *data) +{ + struct i3c_master_controller *controller =3D data; + + i3c_master_unregister(controller); +} + +/** + * p3h2x4x_tp_i3c_algo - Register I3C virtual masters for I3C target ports. + * @p3h2x4x_hub: p3h2x4x device structure. + * Return: 0 in case of success, negative error code on failure. + */ +int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_hub) +{ + struct i3c_master_controller *parent =3D i3c_dev_get_master(p3h2x4x_hub->= i3cdev->desc); + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(p3h2x4x_hub->dev->parent); + u8 tp, ntwk_mask =3D 0; + int ret; + + p3h2x4x_hub->hub =3D devm_kzalloc(p3h2x4x_hub->dev, + sizeof(*p3h2x4x_hub->hub), + GFP_KERNEL); + + if (!p3h2x4x_hub->hub) + return -ENOMEM; + + i3c_hub_init(p3h2x4x_hub->hub, + &p3h2x4x_hub_ops, + p3h2x4x_hub->i3cdev); + + for (tp =3D 0; tp < p3h2x4x->num_target_ports; tp++) { + if (!p3h2x4x_hub->tp_bus[tp].of_node || + p3h2x4x_hub->hub_config.tp_config[tp].mode !=3D P3H2X4X_TP_MODE_I3C) + continue; + + struct i3c_hub_controller *hub_controller =3D + &p3h2x4x_hub->tp_bus[tp].hub_controller; + struct i3c_master_controller *controller =3D &hub_controller->controller; + + hub_controller->parent =3D parent; + hub_controller->hub =3D p3h2x4x_hub->hub; + + dev_set_drvdata(&controller->dev, hub_controller); + + ret =3D i3c_hub_reserve_parent_addrslots_from_dt(hub_controller, + p3h2x4x_hub->tp_bus[tp].of_node); + if (ret) + return ret; + + ret =3D i3c_master_register_fwnode(controller, + p3h2x4x_hub->dev, + of_fwnode_handle(p3h2x4x_hub->tp_bus[tp].of_node), + i3c_hub_master_ops(), + false); + + if (ret) + return ret; + + ret =3D devm_add_action_or_reset(p3h2x4x_hub->dev, + p3h2x4x_unregister_i3c_master, + controller); + if (ret) + return ret; + + ntwk_mask |=3D p3h2x4x_hub->tp_bus[tp].tp_mask; + p3h2x4x_hub->tp_bus[tp].is_registered =3D true; + p3h2x4x_hub->hub_config.tp_config[tp].always_enable =3D true; + } + return regmap_write(p3h2x4x_hub->regmap, P3H2X4X_TP_NET_CON_CONF, ntwk_ma= sk); +} diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c b/drivers/i3c/hub/p3h2= 840_i3c_hub_smbus.c new file mode 100644 index 000000000000..f6d4ade506e2 --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025-2026 NXP + * This P3H2X4X driver file contain functions for SMBus/I2C virtual Bus cr= eation and read/write. + */ +#include +#include + +#include "p3h2840_i3c_hub.h" + +enum p3h2x4x_smbus_desc_idx { + P3H2X4X_DESC_ADDR, + P3H2X4X_DESC_TYPE, + P3H2X4X_DESC_WRITE_LEN, + P3H2X4X_DESC_READ_LEN, +}; + +static int p3h2x4x_read_smbus_transaction_status(struct p3h2x4x_i3c_hub_de= v *hub, + u8 target_port_status, + u8 data_length) +{ + unsigned int timeout_us, sleep_us; + u32 status_read; + u8 status; + int ret; + + timeout_us =3D P3H2X4X_SMBUS_400kHz_TRANSFER_TIMEOUT(data_length); + sleep_us =3D clamp(timeout_us / P3H2X4X_SMBUS_POLL_COUNT, + P3H2X4X_SMBUS_POLL_INTERVAL_MIN_US, + P3H2X4X_SMBUS_POLL_INTERVAL_MAX_US); + + ret =3D regmap_read_poll_timeout(hub->regmap, target_port_status, + status_read, + status_read & P3H2X4X_SMBUS_TRANSACTION_FINISH_FLAG, + sleep_us, + timeout_us); + if (ret) + return ret; + + status =3D (u8)status_read; + + status =3D (status & P3H2X4X_TP_TRANSACTION_CODE_MASK) + >> P3H2X4X_SMBUS_CNTRL_STATUS_TXN_SHIFT; + + switch (status) { + case P3H2X4X_SMBUS_CNTRL_STATUS_TXN_OK: + return 0; + case P3H2X4X_SMBUS_CNTRL_STATUS_TXN_ADDR_NAK: + return -ENXIO; + case P3H2X4X_SMBUS_CNTRL_STATUS_TXN_DATA_NAK: + return -EIO; + case P3H2X4X_SMBUS_CNTRL_STATUS_TXN_SCL_TO: + return -ETIMEDOUT; + case P3H2X4X_SMBUS_CNTRL_STATUS_TXN_ARB_LOSS: + return -EAGAIN; + default: + return -EIO; + } +} + +/* + * p3h2x4x_tp_i2c_xfer_msg() - This starts a SMBus write transaction by wr= iting a descriptor + * and a message to the p3h2x4x registers. Controller buffer page is deter= mined by multiplying the + * target port index by four and adding the base page number to it. + */ +static int p3h2x4x_tp_i2c_xfer_msg(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c= _hub, + struct i2c_msg *xfers, + u8 target_port, + int nxfers_i, u8 rw) +{ + u8 controller_buffer_page =3D P3H2X4X_CONTROLLER_BUFFER_PAGE + 4 * target= _port; + u8 target_port_status =3D P3H2X4X_TP0_SMBUS_AGNT_STS + target_port; + u8 desc[P3H2X4X_SMBUS_DESCRIPTOR_SIZE] =3D { 0 }; + u8 transaction_type =3D P3H2X4X_SMBUS_400kHz; + int write_length, read_length; + u8 addr =3D xfers[nxfers_i].addr; + u8 rw_address =3D 2 * addr; + int ret, ret2; + + if (rw =3D=3D 2) { /* write and read */ + write_length =3D xfers[nxfers_i].len; + read_length =3D xfers[nxfers_i + 1].len; + } else if (rw =3D=3D 1) { + rw_address |=3D P3H2X4X_SET_BIT(0); + write_length =3D 0; + read_length =3D xfers[nxfers_i].len; + } else { + write_length =3D xfers[nxfers_i].len; + read_length =3D 0; + } + + desc[P3H2X4X_DESC_ADDR] =3D rw_address; + if (rw =3D=3D 2) + desc[P3H2X4X_DESC_TYPE] =3D transaction_type | P3H2X4X_SET_BIT(0); + else + desc[P3H2X4X_DESC_TYPE] =3D transaction_type; + desc[P3H2X4X_DESC_WRITE_LEN] =3D write_length; + desc[P3H2X4X_DESC_READ_LEN] =3D read_length; + + ret =3D regmap_write(p3h2x4x_i3c_hub->regmap, target_port_status, + P3H2X4X_TP_BUFFER_STATUS_MASK); + if (ret) + goto out; + + ret =3D regmap_write(p3h2x4x_i3c_hub->regmap, P3H2X4X_PAGE_PTR, controlle= r_buffer_page); + + if (ret) + goto out; + + ret =3D regmap_bulk_write(p3h2x4x_i3c_hub->regmap, P3H2X4X_CONTROLLER_AGE= NT_BUFF, + desc, P3H2X4X_SMBUS_DESCRIPTOR_SIZE); + + if (ret) + goto out; + + if (!(rw % 2) && xfers[nxfers_i].len) { + ret =3D regmap_bulk_write(p3h2x4x_i3c_hub->regmap, + P3H2X4X_CONTROLLER_AGENT_BUFF_DATA, + xfers[nxfers_i].buf, xfers[nxfers_i].len); + if (ret) + goto out; + } + + ret =3D regmap_write(p3h2x4x_i3c_hub->regmap, P3H2X4X_TP_SMBUS_AGNT_TRANS= _START, + p3h2x4x_i3c_hub->tp_bus[target_port].tp_mask); + + if (ret) + goto out; + + ret =3D p3h2x4x_read_smbus_transaction_status(p3h2x4x_i3c_hub, + target_port_status, + (write_length + read_length)); + if (ret) + goto out; + + if (rw) { + if (rw =3D=3D 2) + nxfers_i +=3D 1; + + if (xfers[nxfers_i].len) { + ret =3D regmap_bulk_read(p3h2x4x_i3c_hub->regmap, + P3H2X4X_CONTROLLER_AGENT_BUFF_DATA + write_length, + xfers[nxfers_i].buf, xfers[nxfers_i].len); + if (ret) + goto out; + } + } +out: + ret2 =3D regmap_write(p3h2x4x_i3c_hub->regmap, + P3H2X4X_PAGE_PTR, 0x00); + if (!ret && ret2) + ret =3D ret2; + + return ret; +} + +/* + * This function will be called whenever you call I2C read, write APIs like + * i2c_master_send(), i2c_master_recv() etc. + */ +static s32 p3h2x4x_tp_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *m= sgs, int num) +{ + int ret_sum =3D 0, ret, msg_count; + u8 rw; + + struct tp_bus *bus =3D i2c_get_adapdata(adap); + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub =3D bus->p3h2x4x_i3c_hub; + + guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex); + guard(mutex)(&bus->port_mutex); + + for (msg_count =3D 0; msg_count < num; msg_count++) { + rw =3D (msgs[msg_count].flags & I2C_M_RD) ? 1 : 0; + if (!rw) { + /* If a write message is immediately followed by a read message to + * the same address, consider combining them into a single transaction. + */ + if (msg_count + 1 < num && + msgs[msg_count].addr =3D=3D msgs[msg_count + 1].addr && + (msgs[msg_count + 1].flags & I2C_M_RD)) { + if (msgs[msg_count].len + msgs[msg_count + 1].len > + P3H2X4X_SMBUS_PAYLOAD_SIZE) + return -EINVAL; + + rw =3D 2; + msg_count +=3D 1; + ret_sum +=3D 1; + } + } + + ret =3D p3h2x4x_tp_i2c_xfer_msg(p3h2x4x_i3c_hub, + msgs, + bus->tp_port, + (rw =3D=3D 2) ? (msg_count - 1) : msg_count, + rw); + if (ret) + return ret; + + ret_sum++; + } + return ret_sum; +} + +static u32 p3h2x4x_tp_smbus_funcs(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BLOCK_DATA; +} + +static const struct i2c_adapter_quirks p3h2x4x_tp_i2c_quirks =3D { + .max_read_len =3D P3H2X4X_SMBUS_PAYLOAD_SIZE, + .max_write_len =3D P3H2X4X_SMBUS_PAYLOAD_SIZE, +}; + +/* + * I2C algorithm Structure + */ +static struct i2c_algorithm p3h2x4x_tp_i2c_algorithm =3D { + .master_xfer =3D p3h2x4x_tp_i2c_xfer, + .functionality =3D p3h2x4x_tp_smbus_funcs, +}; + +void p3h2x4x_unregister_smbus_adapters(struct p3h2x4x_i3c_hub_dev *hub) +{ + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(hub->dev->parent); + u8 tp; + + for (tp =3D 0; tp < p3h2x4x->num_target_ports; tp++) { + if (!hub->tp_bus[tp].tp_smbus_adapter) + continue; + + i2c_del_adapter(hub->tp_bus[tp].tp_smbus_adapter); + + guard(mutex)(&hub->etx_mutex); + hub->tp_bus[tp].tp_smbus_adapter =3D NULL; + hub->tp_bus[tp].is_registered =3D false; + } +} + +/** + * p3h2x4x_tp_smbus_algo - Register I2C adapters for SMBus target ports. + * @hub: p3h2x4x device structure. + * Return: 0 in case of success, negative error code on failure. + */ +int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *hub) +{ + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(hub->dev->parent); + int ret, ret2; + u8 tp; + + mutex_lock(&p3h2x4x->protected_reg_lock); + + ret =3D regmap_write(hub->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_UNLOCK_CODE); + if (ret) + goto out_unlock_mutex; + + ret =3D regmap_write(hub->regmap, P3H2X4X_TP_SMBUS_AGNT_IBI_CONFIG, P3H2X= 4X_IBI_DISABLED); + + ret2 =3D regmap_write(hub->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_LOCK_CODE); + if (!ret && ret2) + ret =3D ret2; + +out_unlock_mutex: + mutex_unlock(&p3h2x4x->protected_reg_lock); + if (ret) + return ret; + + for (tp =3D 0; tp < p3h2x4x->num_target_ports; tp++) { + if (!hub->tp_bus[tp].of_node || + hub->hub_config.tp_config[tp].mode !=3D P3H2X4X_TP_MODE_SMBUS) + continue; + + /* Allocate adapter */ + struct i2c_adapter *smbus_adapter =3D + devm_kzalloc(hub->dev, sizeof(*smbus_adapter), GFP_KERNEL); + if (!smbus_adapter) { + p3h2x4x_unregister_smbus_adapters(hub); + return -ENOMEM; + } + + /* Initialize adapter */ + smbus_adapter->owner =3D THIS_MODULE; + smbus_adapter->class =3D I2C_CLASS_HWMON; + smbus_adapter->algo =3D &p3h2x4x_tp_i2c_algorithm; + smbus_adapter->quirks =3D &p3h2x4x_tp_i2c_quirks; + smbus_adapter->dev.parent =3D hub->dev; + smbus_adapter->dev.of_node =3D hub->tp_bus[tp].of_node; + snprintf(smbus_adapter->name, sizeof(smbus_adapter->name), + "p3h2x4x-i3c-hub.tp-port-%d", tp); + + i2c_set_adapdata(smbus_adapter, &hub->tp_bus[tp]); + + /* + * Publish the callback-visible state before i2c_add_adapter(), + * which can synchronously probe a DT slave and invoke + * reg_slave() that inspects is_registered/tp_smbus_client and + * sets ibi_en. Seeding defaults here keeps reg_slave()'s view + * consistent and avoids clobbering its ibi_en update. Do not + * hold etx_mutex across the call, since reg_slave() also takes it. + */ + scoped_guard(mutex, &hub->etx_mutex) { + hub->tp_bus[tp].tp_smbus_adapter =3D smbus_adapter; + hub->tp_bus[tp].tp_smbus_client =3D NULL; + hub->tp_bus[tp].is_registered =3D true; + hub->hub_config.tp_config[tp].ibi_en =3D false; + } + + /* Register adapter */ + ret =3D i2c_add_adapter(smbus_adapter); + if (ret) { + scoped_guard(mutex, &hub->etx_mutex) { + hub->tp_bus[tp].is_registered =3D false; + hub->tp_bus[tp].tp_smbus_adapter =3D NULL; + } + p3h2x4x_unregister_smbus_adapters(hub); + return ret; + } + } + + /* + * Configure the SMBus Target Agents to hold SDA low when both of a + * port's received-data buffers are full. This provides flow control + * for MCTP: it prevents MCTP transmitters on the target ports from + * timing out when the upstream controller does not service the agent + * buffers in time and the port only receives write messages. + */ + ret =3D regmap_update_bits(hub->regmap, P3H2X4X_ONCHIP_TD_AND_SMBUS_AGNT_= CONF, + P3H2X4X_TARGET_AGENT_DFT_IBI_CONF_MASK, + P3H2X4X_TARGET_AGENT_DFT_IBI_CONF); + if (ret) { + p3h2x4x_unregister_smbus_adapters(hub); + return ret; + } + + return 0; +} --=20 2.25.1 From nobody Mon Sep 28 21:04:12 2026 Received: from inva020.nxp.com (inva020.nxp.com [92.121.34.13]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CDAD83F8891; Mon, 17 Aug 2026 10:39:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.13 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963146; cv=none; b=RsGiW+Ry/tJIR1euisagQvaK37X52Fvmr07+2WlnBqkYhDyy2rNXN7POyVRrUl70N0GBiHiIptO6ucVPjN15bh4QT8hjsAh3U2gK+Apz5kkSB25dlTQEKfFqRCOWZ/9WlcBYkI8qOzXIDp72+TXL6FQhFnwv8R7CyT5immv4hSA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786963146; c=relaxed/simple; bh=r7IdIWfCr3zuMjCN6oT2SXaCNqVrASPhsSDHBFnb8rs=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=dLgO8w3qT3I+sbxqpadDQ6Qj/95+E/AzsccJ94V3clDzTUFtoYEzbg7JiLPgA5RRU4J+QVL5All4gO8yZXx+3OMI9nn7yiJ2hh7FVxxm99BXmkZ07S6Z11ybzU+ZS0xEttJ8iTS20RS+e3aBRk/e0aZwHsj9+VB9YKiUVizsB4U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.13 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva020.nxp.com (localhost [127.0.0.1]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 4DAAD1A0106; Mon, 17 Aug 2026 12:39:01 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id DF6601A00FE; Mon, 17 Aug 2026 12:39:00 +0200 (CEST) Received: from lsv03900.swis.in-blr01.nxp.com (lsv03900.swis.in-blr01.nxp.com [10.12.177.15]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 9467418000BF; Mon, 17 Aug 2026 18:38:59 +0800 (+08) From: Lakshay Piplani To: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org, alexandre.belloni@bootlin.com, krzk+dt@kernel.org, robh@kernel.org, conor+dt@kernel.org, devicetree@vger.kernel.org, broonie@kernel.org, lee@kernel.org, Frank.Li@nxp.com, lgirdwood@gmail.com Cc: vikash.bansal@nxp.com, priyanka.jain@nxp.com, aman.kumarpandey@nxp.com, Lakshay Piplani Subject: [PATCH v15 8/8] i3c: hub: p3h2x4x: Add SMBus slave mode support Date: Mon, 17 Aug 2026 16:08:44 +0530 Message-Id: <20260817103844.2142802-9-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260817103844.2142802-1-lakshay.piplani@nxp.com> References: <20260817103844.2142802-1-lakshay.piplani@nxp.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Virus-Scanned: ClamAV using ClamSMTP Content-Type: text/plain; charset="utf-8" Add SMBus slave mode support for the P3H2x4x hub SMBus target ports. The hub SMBus slave agent can receive downstream payloads into target buffers and report receive events through IBI. Add CONFIG_I2C_SLAVE to support the receive path and forward the received payloads to the registered I2C slave client through i2c_slave_event(). Signed-off-by: Lakshay Piplani Signed-off-by: Aman Kumar Pandey Signed-off-by: Vikash Bansal --- Changes in v15: - Retrieve the hub context through the shared MFD data instead of replacing the I3C device's parent driver data - Rework SMBus slave registration and unregistration to use the shared protected-register lock - Report protected-register relock failures separately from the original enable or disable operation Changes in v14: - Clear receive-buffer flags even on SMBus receive error paths to avoid repeated IBI storms - Decode receive-buffer status using FIELD_GET() - Fix overflow status value and explicitly clear overflow after reading bo= th target buffers Changes in v13: - Make IBI setup optional and robust: avoid probe failure when IBI is unsu= pported and add proper cleanup using devm actions - Fix SMBus slave receive path: avoid over-clearing buffer status, handle = unregistered ports, and ensure correct event delivery - Improve safety by adding proper locking around shared state Changes in v12: - Add devm cleanup for IBI request/enable path - Fix NULL pointer dereference before tp_smbus_client check - Clear tp_smbus_client before disabling SMBus-agent IBI in unreg_slave() Changes in v11: - Improve SMBus slave mode payload validation and parsing Changes in v10: - Split SMBus slave mode support into a separate patch --- --- drivers/i3c/hub/p3h2840_i3c_hub.h | 18 ++ drivers/i3c/hub/p3h2840_i3c_hub_i3c.c | 48 +++- drivers/i3c/hub/p3h2840_i3c_hub_smbus.c | 286 ++++++++++++++++++++++++ 3 files changed, 351 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/hub/p3h2840_i3c_hub.h b/drivers/i3c/hub/p3h2840_i3= c_hub.h index 5e1272bfdc43..bc2d6cd373ab 100644 --- a/drivers/i3c/hub/p3h2840_i3c_hub.h +++ b/drivers/i3c/hub/p3h2840_i3c_hub.h @@ -123,6 +123,11 @@ #define BUF_RECEIVED_FLAG_MASK GENMASK(3, 1) #define BUF_RECEIVED_FLAG_TF_MASK GENMASK(3, 0) =20 +#define P3H2X4X_TARGET_BUF_0_RECEIVE_VAL 1 +#define P3H2X4X_TARGET_BUF_1_RECEIVE_VAL 2 +#define P3H2X4X_TARGET_BUF_0_1_RECEIVE_VAL 3 +#define P3H2X4X_TARGET_BUF_OVRFL_VAL 7 + #define P3H2X4X_TARGET_AGENT_LOCAL_DEV 0x11 #define P3H2X4X_TARGET_BUFF_0_PAGE 0x12 #define P3H2X4X_TARGET_BUFF_1_PAGE 0x13 @@ -304,6 +309,9 @@ struct p3h2x4x_i3c_hub_dev { struct i2c_client *i2c_client; struct hub_configuration hub_config; struct tp_bus tp_bus[P3H2X4X_TP_MAX_COUNT]; +#if IS_ENABLED(CONFIG_I2C_SLAVE) + bool ibi_ready; +#endif struct i3c_hub *hub; }; =20 @@ -329,4 +337,14 @@ int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *= p3h2x4x_i3c_hub); */ int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub); =20 +/** + * p3h2x4x_ibi_handler - IBI handler. + * @i3cdev: i3c device. + * @payload: two byte IBI payload data. + */ +#if IS_ENABLED(CONFIG_I2C_SLAVE) +void p3h2x4x_ibi_handler(struct i3c_device *i3cdev, + const struct i3c_ibi_payload *payload); +#endif + #endif /* P3H2840_I3C_HUB_H */ diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c b/drivers/i3c/hub/p3h284= 0_i3c_hub_i3c.c index 22c355f60bbf..1ba8b8748f73 100644 --- a/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c +++ b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c @@ -10,6 +10,14 @@ =20 #include "p3h2840_i3c_hub.h" =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static const struct i3c_ibi_setup p3h2x4x_ibireq =3D { + .handler =3D p3h2x4x_ibi_handler, + .max_payload_len =3D P3H2X4X_MAX_PAYLOAD_LEN, + .num_slots =3D P3H2X4X_NUM_SLOTS, +}; +#endif + static inline struct tp_bus * p3h2x4x_bus_from_controller(struct i3c_master_controller *controller) { @@ -54,6 +62,16 @@ static void p3h2x4x_unregister_i3c_master(void *data) i3c_master_unregister(controller); } =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static void p3h2x4x_free_ibi(void *data) +{ + struct i3c_device *i3cdev =3D data; + + i3c_device_disable_ibi(i3cdev); + i3c_device_free_ibi(i3cdev); +} +#endif + /** * p3h2x4x_tp_i3c_algo - Register I3C virtual masters for I3C target ports. * @p3h2x4x_hub: p3h2x4x device structure. @@ -115,5 +133,33 @@ int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3= h2x4x_hub) p3h2x4x_hub->tp_bus[tp].is_registered =3D true; p3h2x4x_hub->hub_config.tp_config[tp].always_enable =3D true; } - return regmap_write(p3h2x4x_hub->regmap, P3H2X4X_TP_NET_CON_CONF, ntwk_ma= sk); +#if IS_ENABLED(CONFIG_I2C_SLAVE) + ret =3D i3c_device_request_ibi(p3h2x4x_hub->i3cdev, &p3h2x4x_ibireq); + if (ret) { + dev_warn(p3h2x4x_hub->dev, + "IBI not available, SMBus slave mode disabled\n"); + p3h2x4x_hub->ibi_ready =3D false; + } else { + ret =3D i3c_device_enable_ibi(p3h2x4x_hub->i3cdev); + if (ret) { + i3c_device_free_ibi(p3h2x4x_hub->i3cdev); + dev_warn(p3h2x4x_hub->dev, + "Failed to enable IBI, SMBus slave mode disabled\n"); + p3h2x4x_hub->ibi_ready =3D false; + } else { + p3h2x4x_hub->ibi_ready =3D true; + + ret =3D devm_add_action_or_reset(p3h2x4x_hub->dev, + p3h2x4x_free_ibi, + p3h2x4x_hub->i3cdev); + if (ret) { + p3h2x4x_hub->ibi_ready =3D false; + return ret; + } + } + } +#endif + ret =3D regmap_write(p3h2x4x_hub->regmap, P3H2X4X_TP_NET_CON_CONF, ntwk_m= ask); + + return ret; } diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c b/drivers/i3c/hub/p3h2= 840_i3c_hub_smbus.c index f6d4ade506e2..c095ae524e66 100644 --- a/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c +++ b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c @@ -3,6 +3,8 @@ * Copyright 2025-2026 NXP * This P3H2X4X driver file contain functions for SMBus/I2C virtual Bus cr= eation and read/write. */ +#include +#include #include #include =20 @@ -15,6 +17,177 @@ enum p3h2x4x_smbus_desc_idx { P3H2X4X_DESC_READ_LEN, }; =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static void p3h2x4x_read_smbus_agent_rx_buf(struct i3c_device *i3cdev, enu= m p3h2x4x_rcv_buf rfbuf, + enum p3h2x4x_tp tp) +{ + struct p3h2x4x *p3h2x4x =3D i3cdev_get_drvdata(i3cdev); + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub; + u8 slave_rx_buffer[P3H2X4X_SMBUS_TARGET_PAYLOAD_SIZE] =3D { 0 }; + u8 target_buffer_page, flag_clear, temp =3D 0, i, addr; + u32 packet_len, slave_address; + struct i2c_client *client; + int ret; + + if (!p3h2x4x || !p3h2x4x->i3c_hub_priv) + return; + + p3h2x4x_i3c_hub =3D p3h2x4x->i3c_hub_priv; + + switch (rfbuf) { + case RCV_BUF_0: + target_buffer_page =3D P3H2X4X_TARGET_BUFF_0_PAGE; + flag_clear =3D P3H2X4X_TARGET_BUF_0_RECEIVE; + break; + case RCV_BUF_1: + target_buffer_page =3D P3H2X4X_TARGET_BUFF_1_PAGE; + flag_clear =3D P3H2X4X_TARGET_BUF_1_RECEIVE; + break; + default: + return; + } + + target_buffer_page +=3D P3H2X4X_NO_PAGE_PER_TP * tp; + + ret =3D regmap_write(p3h2x4x_i3c_hub->regmap, P3H2X4X_PAGE_PTR, target_bu= ffer_page); + if (ret) + goto ibi_err; + + /* read buffer length */ + ret =3D regmap_read(p3h2x4x_i3c_hub->regmap, P3H2X4X_TARGET_BUFF_LENGTH, = &packet_len); + if (ret) + goto ibi_err; + + if (packet_len) + packet_len =3D packet_len - 1; + + if (packet_len > P3H2X4X_SMBUS_TARGET_PAYLOAD_SIZE) { + dev_err(&i3cdev->dev, "Received message too big for p3h2x4x buffer\n"); + goto ibi_err; + } + + /* read slave address */ + ret =3D regmap_read(p3h2x4x_i3c_hub->regmap, P3H2X4X_TARGET_BUFF_ADDRESS,= &slave_address); + if (ret) + goto ibi_err; + + /* read data */ + if (packet_len) { + ret =3D regmap_bulk_read(p3h2x4x_i3c_hub->regmap, P3H2X4X_TARGET_BUFF_DA= TA, + slave_rx_buffer, packet_len); + if (ret) + goto ibi_err; + } + + client =3D p3h2x4x_i3c_hub->tp_bus[tp].tp_smbus_client; + if (!client) + goto ibi_err; + + /* notify slave driver about received data */ + if ((client->addr & 0x7f) =3D=3D (slave_address >> 1)) { + addr =3D slave_address >> 1; + i2c_slave_event(client, + I2C_SLAVE_WRITE_REQUESTED, &addr); + for (i =3D 0; i < packet_len; i++) { + temp =3D slave_rx_buffer[i]; + i2c_slave_event(client, + I2C_SLAVE_WRITE_RECEIVED, &temp); + } + i2c_slave_event(client, I2C_SLAVE_STOP, &temp); + } + +ibi_err: + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2X4X_PAGE_PTR, 0x00); + + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2X4X_TP0_SMBUS_AGNT_STS + tp, fl= ag_clear); +} + +/** + * p3h2x4x_ibi_handler - IBI handler. + * @i3cdev: i3c device. + * @payload: two byte IBI payload data. + * + */ +void p3h2x4x_ibi_handler(struct i3c_device *i3cdev, + const struct i3c_ibi_payload *payload) +{ + struct p3h2x4x *p3h2x4x =3D i3cdev_get_drvdata(i3cdev); + struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub; + u8 payload_byte_one, payload_byte_two; + u32 target_port_status; + const u8 *data; + int ret, i; + + if (!payload || payload->len < P3H2X4X_MAX_PAYLOAD_LEN) + return; + + data =3D payload->data; + payload_byte_one =3D data[0]; + + if (!(payload_byte_one & P3H2X4X_SMBUS_AGENT_EVENT_FLAG_STATUS)) + return; + + p3h2x4x_i3c_hub =3D p3h2x4x ? p3h2x4x->i3c_hub_priv : NULL; + + if (!p3h2x4x_i3c_hub || !p3h2x4x_i3c_hub->regmap) + return; + + payload_byte_two =3D data[1]; + guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex); + + for (i =3D 0; i < p3h2x4x->num_target_ports; ++i) { + if (!((payload_byte_two >> i) & 0x01)) + continue; + + if (!p3h2x4x_i3c_hub->tp_bus[i].is_registered) { + dev_dbg(&i3cdev->dev, "IBI for unregistered SMBus port %u\n", i); + regmap_write(p3h2x4x_i3c_hub->regmap, + P3H2X4X_TP0_SMBUS_AGNT_STS + i, + BUF_RECEIVED_FLAG_TF_MASK); + continue; + } + + ret =3D regmap_read(p3h2x4x_i3c_hub->regmap, P3H2X4X_TP0_SMBUS_AGNT_STS = + i, + &target_port_status); + if (ret) { + dev_err(&i3cdev->dev, "target port read status failed %d\n", ret); + continue; + } + + if (target_port_status & P3H2X4X_TARGET_BUF_CA_TF) + regmap_write(p3h2x4x_i3c_hub->regmap, + P3H2X4X_TP0_SMBUS_AGNT_STS + i, + P3H2X4X_TARGET_BUF_CA_TF); + + /* process data receive buffer */ + switch (FIELD_GET(BUF_RECEIVED_FLAG_MASK, target_port_status)) { + case P3H2X4X_TARGET_BUF_0_RECEIVE_VAL: + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i); + break; + case P3H2X4X_TARGET_BUF_1_RECEIVE_VAL: + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i); + break; + case P3H2X4X_TARGET_BUF_0_1_RECEIVE_VAL: + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i); + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i); + break; + case P3H2X4X_TARGET_BUF_OVRFL_VAL: + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i); + p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i); + regmap_write(p3h2x4x_i3c_hub->regmap, P3H2X4X_TP0_SMBUS_AGNT_STS + i, + P3H2X4X_TARGET_BUF_OVRFL); + dev_err(&i3cdev->dev, "Overflow, reading buffer zero and one\n"); + break; + default: + regmap_write(p3h2x4x_i3c_hub->regmap, + P3H2X4X_TP0_SMBUS_AGNT_STS + i, + target_port_status & BUF_RECEIVED_FLAG_MASK); + break; + } + } +} +#endif + static int p3h2x4x_read_smbus_transaction_status(struct p3h2x4x_i3c_hub_de= v *hub, u8 target_port_status, u8 data_length) @@ -206,6 +379,115 @@ static u32 p3h2x4x_tp_smbus_funcs(struct i2c_adapter = *adapter) return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BLOCK_DATA; } =20 +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static int p3h2x4x_tp_i2c_reg_slave(struct i2c_client *slave) +{ + struct tp_bus *bus =3D i2c_get_adapdata(slave->adapter); + struct p3h2x4x_i3c_hub_dev *hub =3D bus->p3h2x4x_i3c_hub; + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(hub->dev->parent); + int relock_ret =3D 0; + int ret =3D 0; + + guard(mutex)(&hub->etx_mutex); + + if (!hub->i3cdev || !hub->ibi_ready) + return -EOPNOTSUPP; + + if (bus->tp_smbus_client) + return -EBUSY; + + scoped_guard(mutex, &p3h2x4x->protected_reg_lock) { + /* Unlock access to protected registers */ + ret =3D regmap_write(hub->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_UNLOCK_CODE); + if (ret) + break; + + ret =3D regmap_set_bits(hub->regmap, + P3H2X4X_TP_SMBUS_AGNT_IBI_CONFIG, + bus->tp_mask); + + /* Lock access to protected registers */ + relock_ret =3D regmap_write(hub->regmap, + P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_LOCK_CODE); + } + + /* + * A relock failure does not undo the IBI enable, so report it + * separately and keep the callback result tied to the IBI operation. + */ + if (relock_ret) + dev_err(hub->dev, + "failed to restore protected register lock: %d\n", + relock_ret); + + if (ret) + return ret; + + /* + * Publish the software state only after the hardware IBI has been + * enabled successfully. + */ + bus->tp_smbus_client =3D slave; + hub->hub_config.tp_config[bus->tp_port].ibi_en =3D true; + + return 0; +} + +static int p3h2x4x_tp_i2c_unreg_slave(struct i2c_client *slave) +{ + struct tp_bus *bus =3D i2c_get_adapdata(slave->adapter); + struct p3h2x4x_i3c_hub_dev *hub =3D bus->p3h2x4x_i3c_hub; + struct p3h2x4x *p3h2x4x =3D dev_get_drvdata(hub->dev->parent); + int relock_ret =3D 0; + int ret =3D 0; + + guard(mutex)(&hub->etx_mutex); + + if (bus->tp_smbus_client !=3D slave) + return -EINVAL; + + scoped_guard(mutex, &p3h2x4x->protected_reg_lock) { + /* Unlock access to protected registers */ + ret =3D regmap_write(hub->regmap, P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_UNLOCK_CODE); + if (ret) + break; + + ret =3D regmap_clear_bits(hub->regmap, + P3H2X4X_TP_SMBUS_AGNT_IBI_CONFIG, + bus->tp_mask); + + /* Lock access to protected registers */ + relock_ret =3D regmap_write(hub->regmap, + P3H2X4X_DEV_REG_PROTECTION_CODE, + P3H2X4X_REGISTERS_LOCK_CODE); + } + + /* + * A relock failure does not re-enable the IBI, so report it + * separately and keep the callback result tied to the IBI operation. + */ + if (relock_ret) + dev_err(hub->dev, + "failed to restore protected register lock: %d\n", + relock_ret); + + if (ret) + return ret; + + /* + * Clear the software state only after the hardware IBI has been + * disabled successfully. + */ + bus->tp_smbus_client =3D NULL; + hub->hub_config.tp_config[bus->tp_port].ibi_en =3D false; + + return 0; +} +#endif + static const struct i2c_adapter_quirks p3h2x4x_tp_i2c_quirks =3D { .max_read_len =3D P3H2X4X_SMBUS_PAYLOAD_SIZE, .max_write_len =3D P3H2X4X_SMBUS_PAYLOAD_SIZE, @@ -216,6 +498,10 @@ static const struct i2c_adapter_quirks p3h2x4x_tp_i2c_= quirks =3D { */ static struct i2c_algorithm p3h2x4x_tp_i2c_algorithm =3D { .master_xfer =3D p3h2x4x_tp_i2c_xfer, +#if IS_ENABLED(CONFIG_I2C_SLAVE) + .reg_slave =3D p3h2x4x_tp_i2c_reg_slave, + .unreg_slave =3D p3h2x4x_tp_i2c_unreg_slave, +#endif .functionality =3D p3h2x4x_tp_smbus_funcs, }; =20 --=20 2.25.1