From nobody Sat Jul 25 20:08:04 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 82FD02DB7BD; Tue, 14 Jul 2026 09:26:46 +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=1784021208; cv=none; b=TC2eraIv3OPqvBetFFADMUb/RnYhNJKWodVa9rueK6V3aYNNBQ6gfWs+VkSu8NI0Mc7JDUz3xCSbmGMFW03CQzQFE8QriY9RUzwsiKKMY31RlcsrzCE763YaNSr7aeHrX3M3UIKQwAgxYHpPVn7ZXjHcsQhPAG5quyr1thRYa5k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021208; c=relaxed/simple; bh=uSmgbSzJU6j44fkLAHGrqDcRRBTv/0EvNXJEboNDKW8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=UmCaEwTdasHE8DtXbgJPYm4xY/mWMHZjD5QxgBr6WrkcGjMVx8DjRISpBcFbBkEXSuoopB76a8d0t6ezlk2KtdTLFTAhSeyI5EbgAK3SqNOCtPi0ayGqn0nmC77uXcOR3DyQ/dctIQQ/BRKkbZSw8fm+zL/m9XXCHxXqmO8R8D4= 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 78D281A0086; Tue, 14 Jul 2026 11:21: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 0F7061A006C; Tue, 14 Jul 2026 11:21: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 96AA418000B0; Tue, 14 Jul 2026 17:20: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 v14 1/8] i3c: master: Add APIs for I3C hub support Date: Tue, 14 Jul 2026 14:50:46 +0530 Message-Id: <20260714092053.2461482-2-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260714092053.2461482-1-lakshay.piplani@nxp.com> References: <20260714092053.2461482-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 attach downstream devices temporarily to the parent controller, reserve/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. 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 attach and detach helpers must be called with the bus lock held in write mode. The new helpers are: 1) i3c_master_direct_attach_i3c_dev_locked() 2) i3c_master_direct_detach_i3c_dev_locked() 3) i3c_master_send_ccc_cmd() 4) i3c_master_supports_ccc_cmd() 5) i3c_bus_get_addr_slot_status() 6) i3c_bus_set_addr_slot_status() 7) i3c_bus_maintenance_lock() 8) i3c_bus_maintenance_unlock() 9) i3c_master_register_fwnode() Signed-off-by: Aman Kumar Pandey Signed-off-by: Lakshay Piplani Signed-off-by: Vikash Bansal --- 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 | 229 +++++++++++++++++++++++++++++++++---- include/linux/i3c/master.h | 20 ++++ 2 files changed, 226 insertions(+), 23 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index dbb4d8119b5f..2cb94face156 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -42,10 +42,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 @@ -56,10 +57,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 @@ -391,11 +393,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) @@ -411,11 +421,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) { @@ -1652,6 +1669,83 @@ static int i3c_master_attach_i3c_dev(struct i3c_mast= er_controller *master, return 0; } =20 +/** + * i3c_master_direct_attach_i3c_dev_locked() - Attach an I3C device to a m= aster + * @master: I3C master controller to attach the device to + * @dev: I3C device descriptor representing the device + * + * Attach an I3C device to its master controller after reserving the device + * addresses on the bus. The device must belong to @master. + * + * The master device itself is not passed to the controller driver's + * ->attach_i3c_dev() callback. + * + * Context: Caller must hold @master->bus.lock in write mode. + * + * Return: 0 on success, or a negative error code if address reservation f= ails + * or if the master controller driver's ->attach_i3c_dev() callback fails. + */ +int i3c_master_direct_attach_i3c_dev_locked(struct i3c_master_controller *= master, + struct i3c_dev_desc *dev) +{ + int ret; + + if (WARN_ON(i3c_dev_get_master(dev) !=3D master)) + return -EINVAL; + + ret =3D i3c_master_get_i3c_addrs(dev); + if (ret) + return ret; + + /* 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; + } + } + return 0; +} +EXPORT_SYMBOL_GPL(i3c_master_direct_attach_i3c_dev_locked); + +/** + * i3c_master_direct_detach_i3c_dev_locked() - Detach an I3C device from a= master + * @dev: I3C device descriptor to detach + * + * Detach an addressed I3C device from its master controller. Devices with= out + * either a static or dynamic address are skipped because they were not at= tached + * to the controller. + * + * The master device itself is not passed to the controller driver's + * ->detach_i3c_dev() callback. For other devices, the callback is invoked= when + * provided by the master controller driver. + * + * The controller-private data is cleared and the device addresses are rel= eased + * from the bus address slot table. + * + * Context: Caller must hold the device master's bus lock in write mode. + */ +void i3c_master_direct_detach_i3c_dev_locked(struct i3c_dev_desc *dev) +{ + struct i3c_master_controller *master =3D i3c_dev_get_master(dev); + + /* + * We don't attach devices to the controller until they are + * addressable on the bus, so skip detach for unaddressed devices. + */ + if (!dev->info.static_addr && !dev->info.dyn_addr) + return; + + /* Do not detach the master device itself. */ + if (master->this !=3D dev && master->ops->detach_i3c_dev) + master->ops->detach_i3c_dev(dev); + + dev->common.master_priv =3D NULL; + i3c_master_put_i3c_addrs(dev); +} +EXPORT_SYMBOL_GPL(i3c_master_direct_detach_i3c_dev_locked); + /** * i3c_master_reattach_i3c_dev_locked() - reattach an I3C device with a ne= w address * @dev: I3C device descriptor to reattach @@ -1815,6 +1909,59 @@ i3c_master_register_new_i3c_devs(struct i3c_master_c= ontroller *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 @@ -2998,34 +3145,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 @@ -3037,8 +3181,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; @@ -3150,6 +3300,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 /** diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h index 355e9b3d9ae3..64f4c39793ea 100644 --- a/include/linux/i3c/master.h +++ b/include/linux/i3c/master.h @@ -615,9 +615,21 @@ 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_direct_attach_i3c_dev_locked(struct i3c_master_controller *= master, + struct i3c_dev_desc *dev); +void i3c_master_direct_detach_i3c_dev_locked(struct i3c_dev_desc *dev); +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, @@ -739,4 +751,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 Sat Jul 25 20:08:04 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 DA96043E9DC; Tue, 14 Jul 2026 09:26:51 +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=1784021215; cv=none; b=cK6UO7+jj/RoFLlYI1plY8oXnKYgK4cIf6JsyE9ET7ESJ80wLYkBxVPVvkjC9K223F7Rfw7Y6kAV09posdUmTeb9PSxwEG6uyLsRbjVkmr4+1rA8YtGZHubqy2Yvxs+QinoCDp0LepKdMZBQLUK5tk5NQtC4aRVFE/sOWcIlPQM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021215; c=relaxed/simple; bh=e+PnxNrFcZ9v99XHoxtUNVZ+jjac/BrVBw5roYfXrSQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=pzPlYW0PUqk6WC0bjZG0ZCHmfs9UUNZLWzz0LReOV9A4CI/brmLB4t3dB7f6aCvD3A8d+wobHmGnaVwWYCxHlBsMHO5dV2dYkTvtFoGU7PWS0Jj9tq5m7Gp+KkZaRsCyLoGKyav9vItCFxZ7PGV5+EZUBvK3v5i0IIqPm2CEqSs= 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 92A25200053; Tue, 14 Jul 2026 11:21:01 +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 5ADE320004C; Tue, 14 Jul 2026 11:21:01 +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 05BDE18000B5; Tue, 14 Jul 2026 17:20: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 v14 2/8] i3c: master: Fix IBI request and free cleanup paths Date: Tue, 14 Jul 2026 14:50:47 +0530 Message-Id: <20260714092053.2461482-3-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260714092053.2461482-1-lakshay.piplani@nxp.com> References: <20260714092053.2461482-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" i3c_dev_request_ibi_locked() allocates the generic IBI object and its workqueue before calling the controller request_ibi() callback. If the callback fails, destroy the workqueue before freeing the IBI object. Also, a controller callback may clear dev->ibi while forwarding the request or free operation to another controller. Avoid touching dev->ibi after the callback if it has already been cleared. This prevents a workqueue leak in the request failure path and avoids NULL pointer dereference in the free path when the callback has already released the IBI object. Signed-off-by: Lakshay Piplani Signed-off-by: Vikash Bansal Signed-off-by: Aman Kumar Pandey --- Changes in v14: - Destroy the allocated IBI workqueue when request_ibi() callback fails - Avoid touching dev->ibi after request/free callbacks if a forwarding controller callback has already cleared it --- --- drivers/i3c/master.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c index 2cb94face156..01c6e048485c 100644 --- a/drivers/i3c/master.c +++ b/drivers/i3c/master.c @@ -3499,8 +3499,18 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *= dev, =20 dev->ibi =3D ibi; ret =3D master->ops->request_ibi(dev, req); - if (ret) { - kfree(ibi); + + /* + * The controller callback may have already released and cleared dev->ibi + * when the request is forwarded by a virtual controller. Only clean up t= he + * IBI object if the callback left dev->ibi valid. + */ + if (ret && dev->ibi) { + /* Avoid leaking the workqueue allocated for this IBI request. */ + if (dev->ibi->wq) + destroy_workqueue(dev->ibi->wq); + + kfree(dev->ibi); dev->ibi =3D NULL; } =20 @@ -3540,6 +3550,13 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *de= v) =20 master->ops->free_ibi(dev); =20 + /* + * The controller callback may have already released dev->ibi, for example + * when the request was forwarded by a virtual controller. + */ + if (!dev->ibi) + return; + if (dev->ibi->wq) { destroy_workqueue(dev->ibi->wq); dev->ibi->wq =3D NULL; --=20 2.25.1 From nobody Sat Jul 25 20:08:04 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 DC19143F4DD; Tue, 14 Jul 2026 09:26:51 +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=1784021217; cv=none; b=vGuuVSWkeeLFNJgYjnoOaUETgUQr6OHhAsnP+8XCt2G06W57/AdaBITikBsBGf0nfb493J63evhJlS2LgftCr/9ILzLvGl5cD57JwspdeUIGoEqbWHdpyx7ueBHl1InXguERPkTtUc4jkykIiUA4XN2LbsCLTvyjrJ5eOycFjV4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021217; c=relaxed/simple; bh=S9IGfjmLHY/I9hMhQLU8ehuMkItQC75XwWvyr9Q5u8o=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=jVujItmFixxw8qm6kxkLvSGnx7X/HR5dk0pWbDJtWdlMjIzh06fMGsf/XuATEKNwqkC0xyaJ0oRy50aji7YboomSLGlJmJ9HDor6xeE1VpyrhY9cFvq0br2EPP6+mZLiMbZJ448KxF7HC3BPh6b1gg0lPqtnpb1ZDrnc0M9BKEs= 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 D4CD5200066; Tue, 14 Jul 2026 11:21:02 +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 9BC92200057; Tue, 14 Jul 2026 11:21:02 +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 3BB8F18000B0; Tue, 14 Jul 2026 17:21:01 +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 v14 3/8] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Date: Tue, 14 Jul 2026 14:50:48 +0530 Message-Id: <20260714092053.2461482-4-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260714092053.2461482-1-lakshay.piplani@nxp.com> References: <20260714092053.2461482-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 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 | 291 ++++++++++++++++++ MAINTAINERS | 9 + 2 files changed, 300 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..f7f0752de533 --- /dev/null +++ b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml @@ -0,0 +1,291 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2025 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 I3C/I2C Target Port. + 4. P3H2440/ P3H2441 are 4 port I3C hub with four I3C/I2C Target Port. + Target ports can be configured as I2C/SMBus, I3C or GPIO and connect = to + peripherals. + +properties: + compatible: + const: nxp,p3h2840 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + assigned-address: + maximum: 0x7f + + nxp,tp0145-pullup-ohms: + description: + Selects the pull up resistance for target Port 0/1/4/5, in ohms. + enum: [250, 500, 1000, 2000] + default: 500 + + nxp,tp2367-pullup-ohms: + description: + Selects the pull up resistance for target Port 2/3/6/7, in ohms. + enum: [250, 500, 1000, 2000] + default: 500 + + nxp,cp0-io-strength-ohms: + description: + Selects the IO drive strength for controller Port 0, in ohms. + enum: [20, 30, 40, 50] + default: 20 + + nxp,cp1-io-strength-ohms: + description: + Selects the IO drive strength for controller Port 1, in ohms. + enum: [20, 30, 40, 50] + default: 20 + + nxp,tp0145-io-strength-ohms: + description: + Selects the IO drive strength for target port 0/1/4/5, in ohms. + enum: [20, 30, 40, 50] + default: 20 + + nxp,tp2367-io-strength-ohms: + description: + Selects the IO drive strength for target port 2/3/6/7, in ohms. + enum: [20, 30, 40, 50] + default: 20 + + vcc1-supply: + description: Controller port 0 power supply. + + vcc2-supply: + description: Controller port 1 power supply. + + vcc3-supply: + description: Target port 0/1/4/5 power supply. + + vcc4-supply: + description: Target port 2/3/6/7 power supply. + + regulators: + type: object + additionalProperties: false + + properties: + ldo-cp0: + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + + ldo-cp1: + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + + ldo-tpg0: + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + + ldo-tpg1: + type: object + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + +required: + - reg + +patternProperties: + "^i3c@[0-7]$": + type: object + $ref: /schemas/i3c/i3c.yaml# + unevaluatedProperties: false + + properties: + reg: + description: + The I3C HUB Target Port number. + maximum: 7 + + nxp,pullup-enable: + type: boolean + description: + Enables the on-die pull-up for Target Port. + + required: + - reg + + "^(i2c|smbus)@[0-7]$": + type: object + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + + properties: + reg: + description: + The I3C HUB Target Port number. + 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; + }; + + i2c@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; + }; + + i2c@1 { + reg =3D <0x1>; + #address-cells =3D <1>; + #size-cells =3D <0>; + nxp,pullup-enable; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index c2c6d79275c6..ba65ae5a008b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19303,6 +19303,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 Sat Jul 25 20:08:04 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 DCCA343F4DF; Tue, 14 Jul 2026 09:26:51 +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=1784021215; cv=none; b=lYr8PbAwETL9GTsrDtk/QwnWbL96N9EtBOh+cNhzIAxorPHkJKlnfbTPykTrs50JzHjUW4O0go0Ku/Sx+7rbqThZpbMO+0JNeFox5qB1CnevsRv4/c47vI0+bdWC38jruM/vgvGi7BNlDUKq1k3nyEbP81bC0M3h0e24JDo3bSU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021215; c=relaxed/simple; bh=PEO/zpMGRltw6szxIm5MS8MHeIz3/jB/dmjOihGnfas=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=cyniycfMsovsdpQW/sPj1YEj0NlR4LGlg9zhXxrokXX9hYoy7TPJqPnY+ETayQ0awb31fmpzFAjNIN7+53ZjVVB/3X3rIzkVk1VoSeJwqFB6v9mPxnuhCX1lbNbuLcyypPVXJlzaI3K/6bmg7y/iugGoiZHRXC77N/6Rs3l7duA= 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 14536200029; Tue, 14 Jul 2026 11:21:04 +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 CFEBA20005B; Tue, 14 Jul 2026 11:21:03 +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 7E41218000B5; Tue, 14 Jul 2026 17:21:02 +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 v14 4/8] mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator Date: Tue, 14 Jul 2026 14:50:49 +0530 Message-Id: <20260714092053.2461482-5-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260714092053.2461482-1-lakshay.piplani@nxp.com> References: <20260714092053.2461482-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 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 | 139 ++++++++++++++++++++++++++++++++++++ include/linux/i3c/device.h | 2 + include/linux/mfd/p3h2840.h | 25 +++++++ 6 files changed, 182 insertions(+) create mode 100644 drivers/mfd/p3h2840.c create mode 100644 include/linux/mfd/p3h2840.h diff --git a/MAINTAINERS b/MAINTAINERS index ba65ae5a008b..3420701a75c5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19311,6 +19311,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 7192c9d1d268..405b50c3c77b 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 e75e8045c28a..a284b22c7b13 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..652077408dbd --- /dev/null +++ b/drivers/mfd/p3h2840.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025-2026 NXP + * + * Authors: + * Aman Kumar Pandey + * Vikash Bansal + * Lakshay Piplani + * + * P3H2X4X multi-function device driver. + */ +#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, +}; + +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 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 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,p3h2840", }, + { /* 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 971d53349b6f..1dcfc4cc1cef 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..ba9a38756ecb --- /dev/null +++ b/include/linux/mfd/p3h2840.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2025-2026 NXP + */ + +#ifndef _LINUX_MFD_P3H2840_H +#define _LINUX_MFD_P3H2840_H + +#include +#include + +/* 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 { + struct i3c_device *i3cdev; + struct regmap *regmap; + struct mutex protected_reg_lock; /* Serializes protected register unlock/= lock sequences. */ +}; +#endif /* _LINUX_MFD_P3H2840_H */ --=20 2.25.1 From nobody Sat Jul 25 20:08:04 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 DCDFF43F8A2; Tue, 14 Jul 2026 09:26:51 +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=1784021215; cv=none; b=vGgffAq6mMDTQY7UF9wAgnq8JNDHUGIY6VQhK7d7a3bYmqFeGf8mhtLMnq6XUBffkSknsQepo6UHTElIBNt49li8c29RYk/IVQ9fWTzMwMt301PMskRyNLHQeq+fxICXgGkyCprw/Elb99AH49AVCHv3ljqJJvoeMvwIRe5YMEo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021215; c=relaxed/simple; bh=h9IjwqDoR7mnLZK717GyAXsuQVf2ATVmwtKVzpKdkxQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=KdijqCOh+DxLjwG1OnBD5iCscXRBYfIpXZjhfu3N/X7SkptsfaK97y3qZwRu2WjOnbHhKv6lCqGbK9QLP7fW+rJ3tIdVCZR2tg22ZpdRYTCwprk63Otobd3jh5drh7wwdUPBOALUT1+4UE8xDHIwaji3qHfCsqF814bS/+xNS5I= 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 7BADA200065; Tue, 14 Jul 2026 11:21:05 +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 19276200057; Tue, 14 Jul 2026 11:21:05 +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 B07F618000B0; Tue, 14 Jul 2026 17:21:03 +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 v14 5/8] regulator: p3h2x4x: Add driver for on-die regulators in NXP P3H2x4x i3c hub Date: Tue, 14 Jul 2026 14:50:50 +0530 Message-Id: <20260714092053.2461482-6-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260714092053.2461482-1-lakshay.piplani@nxp.com> References: <20260714092053.2461482-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 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 3420701a75c5..fcb3917aa096 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19312,6 +19312,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 d71dac9436e3..88809f493fd4 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1019,6 +1019,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 35639f3115fd..46f586ccde63 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 Sat Jul 25 20:08:04 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 830643ED5B3; Tue, 14 Jul 2026 09:26:46 +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=1784021209; cv=none; b=bx83oIgMXi7s9MkbUwLl846Eb92HKCOU8AsSONAbqJeZTJrTOd2UiTVj9a46dV34VKdfQVZMQhu4VWQ9mVk3LnBJkvJYLhZ40t4gDDW9ReFo/B3i6As/I4yHcg2UM6oDn6upOCyj6Uerv5oNHU7vh547A6NgQncwItjlMR8ml2c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021209; c=relaxed/simple; bh=u0U+l6q4EAYa0LG8Hr5S9eTNmgsdiREMu8F9oGpx5FQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=dH6LKSK2/ANFqfG/EGXLUTvCVzWe4GqVHeqbRH3BbYfZqUcJ2y+xO3geLN+cMurlDphFqxVD+1pIWlWCkkwixM8D7wdDy5V6rJw1Qp0H4IRGqhiZuAMMb7u9X9PIX6IminQSJylmfiSk8jalSfoEsBpZF+yw9UnTGy2U6EDiOoc= 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 B7D411A003D; Tue, 14 Jul 2026 11:21:06 +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 5345E1A006C; Tue, 14 Jul 2026 11:21:06 +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 F1C5718000B5; Tue, 14 Jul 2026 17:21:04 +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 v14 6/8] i3c: hub: Add support for the I3C interface in the I3C hub Date: Tue, 14 Jul 2026 14:50:51 +0530 Message-Id: <20260714092053.2461482-7-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260714092053.2461482-1-lakshay.piplani@nxp.com> References: <20260714092053.2461482-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 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 | 607 ++++++++++++++++++++++++++++++++++++++++ include/linux/i3c/hub.h | 102 +++++++ 5 files changed, 726 insertions(+) create mode 100644 drivers/i3c/hub.c create mode 100644 include/linux/i3c/hub.h diff --git a/MAINTAINERS b/MAINTAINERS index fcb3917aa096..a0b7fd85f8e3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19311,8 +19311,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..6105bb710be6 --- /dev/null +++ b/drivers/i3c/hub.c @@ -0,0 +1,607 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2026 NXP + * Generic I3C Hub core implementing virtual controller operations. + */ +#include +#include + +#include "internals.h" + +/** + * 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; +} + +/* + * Devices behind the hub are not attached to the virtual bus. + * + * Instead, i3c_hub_master_priv_xfers() temporarily updates the hub dynamic + * address with the target device address before issuing the transfer. Onc= e the + * transfer completes, the hub address is restored back to its original va= lue. + * + * Therefore, no per-device attach, reattach, or detach handling is requir= ed for + * the virtual bus. + */ +static int i3c_hub_attach_i3c_dev(struct i3c_dev_desc *dev) +{ + return 0; +} + +static int i3c_hub_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_a= ddr) +{ + return 0; +} + +static void i3c_hub_detach_i3c_dev(struct i3c_dev_desc *dev) +{ +} + +/** + * 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_dev_desc *hub_dev; + struct i3c_master_controller *parent; + u8 hub_addr, target_addr; + struct i3c_hub *hub; + int ret, xfer_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; + + hub_dev =3D hub->hub_dev->desc; + + parent =3D i3c_dev_get_master(hub_dev); + if (!parent) + return -ENODEV; + + mutex_lock(&hub->lock); + + i3c_hub_enable_port(controller); + + i3c_bus_maintenance_lock(&parent->bus); + + hub_addr =3D hub_dev->info.dyn_addr ? + hub_dev->info.dyn_addr : hub_dev->info.static_addr; + + target_addr =3D dev->info.dyn_addr ? + dev->info.dyn_addr : dev->info.static_addr; + + if (hub_addr !=3D target_addr) { + hub_dev->info.dyn_addr =3D target_addr; + + ret =3D i3c_master_reattach_i3c_dev_locked(hub_dev, hub_addr); + if (ret) { + hub_dev->info.dyn_addr =3D hub_addr; + goto out_unlock_parent; + } + } + + ret =3D i3c_dev_do_xfers_locked(hub_dev, xfers, nxfers, mode); + + if (hub_addr !=3D target_addr) { + hub_dev->info.dyn_addr =3D hub_addr; + + xfer_ret =3D i3c_master_reattach_i3c_dev_locked(hub_dev, + target_addr); + if (!ret) + ret =3D xfer_ret; + } +out_unlock_parent: + i3c_bus_maintenance_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 + * + * Temporarily updates parent controller context to request IBI for a devi= ce + * connected through the hub. + */ +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 *orig_parent; + 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); + + i3c_bus_maintenance_lock(&parent->bus); + + orig_parent =3D i3c_hub_update_desc_parent(&desc->common, parent); + + /* + * Downstream devices are exposed through the virtual hub controller. + * Private transfers only use the hub device temporarily with the + * downstream device's dynamic address; they do not attach the downstream + * device to the parent controller. + * + * Attach the device to the parent controller before requesting IBI so + * the parent can allocate any controller-private IBI state. + */ + ret =3D i3c_master_direct_attach_i3c_dev_locked(parent, desc); + if (ret) { + i3c_hub_update_desc_parent(&desc->common, orig_parent); + i3c_bus_maintenance_unlock(&parent->bus); + return ret; + } + + if (desc->ibi) { + if (desc->ibi->wq) { + destroy_workqueue(desc->ibi->wq); + desc->ibi->wq =3D NULL; + } + + kfree(desc->ibi); + desc->ibi =3D NULL; + } + ret =3D i3c_dev_request_ibi_locked(desc, req); + if (ret) + i3c_master_direct_detach_i3c_dev_locked(desc); + + i3c_hub_update_desc_parent(&desc->common, orig_parent); + + i3c_bus_maintenance_unlock(&parent->bus); + + 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 *orig_parent; + struct i3c_master_controller *parent; + 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_dev) + return; + + parent =3D i3c_dev_get_master(hub->hub_dev->desc); + + mutex_lock(&hub->lock); + i3c_hub_enable_port(controller); + + i3c_bus_maintenance_lock(&parent->bus); + orig_parent =3D i3c_hub_update_desc_parent(&desc->common, parent); + i3c_dev_free_ibi_locked(desc); + + /* + * Drop the temporary parent attachment created for IBI setup. + * The downstream device remains exposed through the virtual hub + * controller. + */ + i3c_master_direct_detach_i3c_dev_locked(desc); + i3c_hub_update_desc_parent(&desc->common, orig_parent); + i3c_bus_maintenance_unlock(&parent->bus); + + i3c_hub_disable_port(controller); + 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 *orig_parent; + 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); + + mutex_lock(&hub->lock); + i3c_hub_enable_port(controller); + + orig_parent =3D i3c_hub_update_desc_parent(&desc->common, parent); + + i3c_bus_maintenance_lock(&parent->bus); + ret =3D i3c_dev_enable_ibi_locked(desc); + i3c_bus_maintenance_unlock(&parent->bus); + + i3c_hub_update_desc_parent(&desc->common, orig_parent); + + 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 *orig_parent; + 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); + + mutex_lock(&hub->lock); + i3c_hub_enable_port(controller); + + orig_parent =3D i3c_hub_update_desc_parent(&desc->common, parent); + + i3c_bus_maintenance_lock(&parent->bus); + ret =3D i3c_dev_disable_ibi_locked(desc); + i3c_bus_maintenance_unlock(&parent->bus); + + i3c_hub_update_desc_parent(&desc->common, orig_parent); + + 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) +{ + /* + * TODO: Forward IBI slot recycling to the parent controller when + * downstream-device IBI support is fully enabled. Current users only + * rely on the hub device IBI path for SMBus-agent events, so no parent + * hardware slot is recycled through this virtual callback currently. + */ +} + +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..8ad2cf3c9d5b --- /dev/null +++ b/include/linux/i3c/hub.h @@ -0,0 +1,102 @@ +/* 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 + +static inline struct i3c_master_controller * +i3c_hub_update_desc_parent(struct i3c_i2c_dev_desc *desc, + struct i3c_master_controller *parent) +{ + struct i3c_master_controller *orig_parent =3D desc->master; + + desc->master =3D parent; + return orig_parent; +} + +/** + * 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 Sat Jul 25 20:08:04 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 8311243D50D; Tue, 14 Jul 2026 09:26:46 +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=1784021209; cv=none; b=hI27g1Nu4wL/JGn+eq/Il9Fqh0h9uMKtnCZpe6YppSgjsi/OvO1WjiLXdPrqvITLbd1Jdx3uJmxjr2pRdcsZ671E9ObFXR4AUVmmxb8ypgdsDqS9U4MzgbSHn02sws2l2TOjh92dSUxU22bHZOekZZU7hHkjTWyBiPY2tHELHVY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784021209; c=relaxed/simple; bh=dDP+4Sgi/5dtKpjKs2tq3p0sNcsBvEIJR5G1XpP/1IQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=I7pS8fn1c17mV3awrKPTjqXvEqIohEl5I/Z2n9YfSnw+WlcNKL6EYR35D+gxjFdvAy38GjHUEZA/3J3iqPibdhRqD8Qmwu4yCrJpy8TBwqzf0vKWtepTFECe35MblTa+zv1rbuF6xZgkVAI+mL82ixDNxTMKdvXzb7HXFoqSUPk= 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 484E51A0045; Tue, 14 Jul 2026 11:21:08 +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 ADF9F1A006D; Tue, 14 Jul 2026 11:21:07 +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 4431E18000B0; Tue, 14 Jul 2026 17:21:06 +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 v14 7/8] i3c: hub: p3h2x4x: Add support for NXP P3H2x4x I3C hub functionality Date: Tue, 14 Jul 2026 14:50:52 +0530 Message-Id: <20260714092053.2461482-8-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260714092053.2461482-1-lakshay.piplani@nxp.com> References: <20260714092053.2461482-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 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 | 327 +++++++++++++++++++ drivers/i3c/hub/p3h2840_i3c_hub_common.c | 379 +++++++++++++++++++++++ drivers/i3c/hub/p3h2840_i3c_hub_i3c.c | 118 +++++++ drivers/i3c/hub/p3h2840_i3c_hub_smbus.c | 322 +++++++++++++++++++ 9 files changed, 1164 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 a0b7fd85f8e3..718c5fca4746 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19312,6 +19312,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..e7ab243bbfff --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub.h @@ -0,0 +1,327 @@ +/* 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 P3H2X4X_TP_MODE_I3C 0x00 +#define P3H2X4X_TP_MODE_SMBUS 0x01 +#define P3H2X4X_TP_MODE_GPIO 0x02 +#define P3H2X4X_TP_MODE_I2C 0x03 + +#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) + +/* Hub SMBus transaction time */ +#define P3H2X4X_SMBUS_400kHz_TRANSFER_TIMEOUT(x) ((20 * (x)) + 80) + +#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, +}; + +struct tp_configuration { + bool pullup_en; + bool ibi_en; + bool always_enable; + int 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..2fd9723da60d --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_common.c @@ -0,0 +1,379 @@ +// 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); + u8 mode =3D 0, smbus =3D 0, pullup =3D 0, target_port =3D 0; + int tp, ret; + + for (tp =3D 0; tp < P3H2X4X_TP_MAX_COUNT; 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; + } + + ret =3D regmap_update_bits(hub->regmap, P3H2X4X_TP_PULLUP_EN, P3H2X4X_TP_= MASK, pullup); + if (ret) + return ret; + + ret =3D regmap_update_bits(hub->regmap, P3H2X4X_TP_IO_MODE_CONF, P3H2X4X_= TP_MASK, mode); + if (ret) + return ret; + + ret =3D regmap_update_bits(hub->regmap, P3H2X4X_TP_SMBUS_AGNT_EN, P3H2X4X= _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, P3H2X4X_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); + 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_TP_MAX_COUNT) { + 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 void p3h2x4x_parse_tp_dt_settings(struct device *dev, + const struct device_node *node, + struct tp_configuration tp_config[]) +{ + u64 id; + + for_each_available_child_of_node_scoped(node, tp_node) { + if (of_property_read_reg(tp_node, 0, &id, NULL)) + continue; + + if (id >=3D P3H2X4X_TP_MAX_COUNT) { + dev_warn(dev, "Invalid target port index found in DT: %lli\n", id); + continue; + } + + if (strcmp(tp_node->name, "i3c") =3D=3D 0) + tp_config[id].mode =3D P3H2X4X_TP_MODE_I3C; + + if (strcmp(tp_node->name, "i2c") =3D=3D 0) + tp_config[id].mode =3D P3H2X4X_TP_MODE_I2C; + + if (strcmp(tp_node->name, "smbus") =3D=3D 0) + tp_config[id].mode =3D P3H2X4X_TP_MODE_SMBUS; + + tp_config[id].pullup_en =3D + of_property_read_bool(tp_node, "nxp,pullup-enable"); + } +} + +static void 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); + + p3h2x4x_parse_tp_dt_settings(dev, node, p3h2x4x_i3c_hub->hub_config.tp_co= nfig); +} + +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_MOD= E_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 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"); + + p3h2x4x_get_hub_dt_conf(dev, node); + 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; + i3cdev_set_drvdata(p3h2x4x->i3cdev, p3h2x4x_i3c_hub); + 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..0e5bf7eb3cf7 --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c @@ -0,0 +1,118 @@ +// 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); + 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_TP_MAX_COUNT; 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..b991c46f3ed5 --- /dev/null +++ b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c @@ -0,0 +1,322 @@ +// 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++) { + if (msgs[msg_count].len > P3H2X4X_SMBUS_PAYLOAD_SIZE) { + dev_err(p3h2x4x_i3c_hub->dev, + "Message nr. %d not sent - length over %d bytes.\n", + msg_count, P3H2X4X_SMBUS_PAYLOAD_SIZE); + return -EINVAL; + } + + 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; +} + +/* + * 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) +{ + u8 tp; + + for (tp =3D 0; tp < P3H2X4X_TP_MAX_COUNT; 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_TP_MAX_COUNT; 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->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]); + + /* Register adapter */ + ret =3D i2c_add_adapter(smbus_adapter); + if (ret) { + p3h2x4x_unregister_smbus_adapters(hub); + return ret; + } + + hub->tp_bus[tp].is_registered =3D true; + hub->hub_config.tp_config[tp].ibi_en =3D false; + hub->tp_bus[tp].tp_smbus_adapter =3D smbus_adapter; + } + + /* + * 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 applicat= ions to + * avoid MCTP transmitters on Target Ports time out when the SMBus agent = buffers + * are not serviced in time by upstream controller and only receives writ= e message + * from its downstream ports. + */ + 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 Sat Jul 25 20:08:04 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 1752D353A93; Tue, 14 Jul 2026 09:21:10 +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=1784020873; cv=none; b=i98CmmSpuT1n35qMX5IW35MinO5sxgoMG8ww2ojRbCae+YEg6jEUTxPD+lRiap0GT9s1m8Y/yJQvVU5SzcWKiVHMNXm7QczSow4Y9VlMkNpt1AfPLl3r9nwe5P7IbFMvpw7z4GhfDvGroB21QWLl1EEVZnJO1syimCO2Mhkvshg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784020873; c=relaxed/simple; bh=BImPZY/VJGjA4sYOkKCa/gDYnzyHTynmuP7Y/hXmz+0=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=SPFOgcMk/jzWblbNS8mfkX1tEnR3n5PQ//MwSAuHqmA9oGGTDns3AkcA663rQ4oPi5UOXcwJ5ipHphXCpvIgTZ9uk/nz2Ngvysoc2seW6cyXlXuj7qanepmHrI1UGOUO9AizQqHKMBwhxlyWvV0rnTGoORTfLDV9VnBDyQgHw2k= 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 7596220006D; Tue, 14 Jul 2026 11:21:09 +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 13A5120004C; Tue, 14 Jul 2026 11:21:09 +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 B4D0718000B5; Tue, 14 Jul 2026 17:21:07 +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 v14 8/8] i3c: hub: p3h2x4x: Add SMBus slave mode support Date: Tue, 14 Jul 2026 14:50:53 +0530 Message-Id: <20260714092053.2461482-9-lakshay.piplani@nxp.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20260714092053.2461482-1-lakshay.piplani@nxp.com> References: <20260714092053.2461482-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 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 | 251 ++++++++++++++++++++++++ 3 files changed, 316 insertions(+), 1 deletion(-) diff --git a/drivers/i3c/hub/p3h2840_i3c_hub.h b/drivers/i3c/hub/p3h2840_i3= c_hub.h index e7ab243bbfff..a7cb83dcf1e8 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 @@ -299,6 +304,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 @@ -324,4 +332,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 0e5bf7eb3cf7..b47859a2dd14 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. @@ -114,5 +132,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 b991c46f3ed5..89e94f551b31 100644 --- a/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c +++ b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c @@ -3,6 +3,7 @@ * Copyright 2025-2026 NXP * This P3H2X4X driver file contain functions for SMBus/I2C virtual Bus cr= eation and read/write. */ +#include #include #include =20 @@ -15,6 +16,168 @@ 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_i3c_hub_dev *p3h2x4x_i3c_hub =3D i3cdev_get_drvdata(i3cdev= ); + 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, ret; + struct i2c_client *client; + + 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_i3c_hub_dev *p3h2x4x_i3c_hub; + u8 payload_byte_one, payload_byte_two; + u32 target_port_status, ret, i; + const u8 *data; + + 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 i3cdev_get_drvdata(i3cdev); + + 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_TP_MAX_COUNT; ++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) @@ -213,11 +376,99 @@ 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 ret, ret2; + + guard(mutex)(&hub->etx_mutex); + + if (!hub->i3cdev || !hub->ibi_ready) + return -EOPNOTSUPP; + + if (bus->tp_smbus_client) + return -EBUSY; + + mutex_lock(&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) + goto out_unlock_mutex; + + ret =3D regmap_set_bits(hub->regmap, + P3H2X4X_TP_SMBUS_AGNT_IBI_CONFIG, + bus->tp_mask); + + /* Lock access to protected registers */ + 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) { + bus->tp_smbus_client =3D slave; + hub->hub_config.tp_config[bus->tp_port].ibi_en =3D true; + } + + return ret; +} + +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 ret, ret2; + + guard(mutex)(&hub->etx_mutex); + + if (bus->tp_smbus_client !=3D slave) + return -EINVAL; + + bus->tp_smbus_client =3D NULL; + hub->hub_config.tp_config[bus->tp_port].ibi_en =3D false; + + mutex_lock(&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) + goto out_unlock_mutex; + + ret =3D regmap_clear_bits(hub->regmap, + P3H2X4X_TP_SMBUS_AGNT_IBI_CONFIG, + bus->tp_mask); + + /* Lock access to protected registers */ + 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; +} +#endif + /* * I2C algorithm Structure */ 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