* [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver
@ 2026-03-19 11:24 Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 1/7] i3c: master: Expose the APIs to support I3C hub Lakshay Piplani
` (6 more replies)
0 siblings, 7 replies; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-19 11:24 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee, Frank.Li, lgirdwood
Cc: vikash.bansal, priyanka.jain, aman.kumarpandey, lakshay.piplani
This series adds a driver for the NXP P3H2x4x family of multiport I3C hub
devices.
This is an MFD driver integrating I3C hub and on-die regulators.
The series introduces:
- Core I3C master enhancements required for hub support
- Generic I3C hub framework
- MFD and regulator drivers for P3H2x4x
- P3H2x4x I3C hub driver built on top of the generic layer
Changes in v7:
- Fix kernel-doc warnings across I3C core and hub code
- Rework DT binding schema and examples to pass dt_binding_check
- Update MFD Kconfig to use I3C_OR_I2C
- Convert CONFIG_I3C_HUB to tristate
- Remove unnecessary CONFIG_I2C_SLAVE guards
- Replace custom helpers with find_closest()
- Use devm_regulator_get_enable_optional()
- Link to v6: https://lore.kernel.org/linux-i3c/64c5070c-aa9e-427a-933e-91e168f0510c@kernel.org/T/#u
Changes in v6:
- Update DT binding with vendor-prefixed properties
- Add generic I3C hub support
- Remove generic code from P3H2x4x driver
- Link to v5: https://lore.kernel.org/linux-i3c/20260206120121.856471-1-aman.kumarpandey@nxp.com/T/#u
Changes in v5:
- Update supply naming and descriptions
- Improve MFD Kconfig/Makefile ordering
- Link to v4: https://lore.kernel.org/linux-i3c/20260113114529.1692213-2-aman.kumarpandey@nxp.com/T/#u
Changes in v4:
- Split driver into MFD, regulator and I3C hub parts
- Update I3C master for hub support
- Fix DT binding issues
- Link to v3: https://lore.kernel.org/linux-i3c/20250811-bittern-of-abstract-prestige-aaeda9@kuoka/T/#u
Changes in v3:
- Add MFD support for hub and regulators
- Add regulator integration
- Link to v2: https://lore.kernel.org/linux-i3c/17145d2f-5d07-4939-8381-74e27cde303c@kernel.org/T/#u
Changes in v2:
- Fix DT binding warnings
- Refine DT parsing logic
- Link to v1: https://lore.kernel.org/linux-i3c/822d6dca-b2c6-4439-ade5-219620ebc435@kernel.org/T/#u
Aman Kumar Pandey (6):
i3c: master: Expose the APIs to support I3C hub
i3c: master: Add the APIs to support I3C hub
dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support
mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator
regulator: p3h2x4x: Add driver for on-die regulators in NXP P3H2x4x
i3c hub
i3c: hub: p3h2x4x: Add support for NXP P3H2x4x I3C hub functionality
Lakshay Piplani (1):
i3c: hub: Add support for the I3C interface in the I3C hub
.../devicetree/bindings/i3c/nxp,p3h2840.yaml | 303 ++++++++++++
MAINTAINERS | 15 +
drivers/i3c/Kconfig | 16 +
drivers/i3c/Makefile | 2 +
drivers/i3c/hub.c | 460 ++++++++++++++++++
drivers/i3c/hub/Kconfig | 11 +
drivers/i3c/hub/Makefile | 4 +
drivers/i3c/hub/p3h2840_i3c_hub.h | 334 +++++++++++++
drivers/i3c/hub/p3h2840_i3c_hub_common.c | 349 +++++++++++++
drivers/i3c/hub/p3h2840_i3c_hub_i3c.c | 132 +++++
drivers/i3c/hub/p3h2840_i3c_hub_smbus.c | 422 ++++++++++++++++
drivers/i3c/master.c | 173 ++++++-
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/p3h2840.c | 125 +++++
drivers/regulator/Kconfig | 10 +
drivers/regulator/Makefile | 1 +
drivers/regulator/p3h2840_i3c_hub_regulator.c | 219 +++++++++
include/linux/i3c/device.h | 1 +
include/linux/i3c/hub.h | 107 ++++
include/linux/i3c/master.h | 10 +
include/linux/mfd/p3h2840.h | 27 +
22 files changed, 2733 insertions(+), 2 deletions(-)
create mode 100644 Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
create mode 100644 drivers/i3c/hub.c
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
create mode 100644 drivers/mfd/p3h2840.c
create mode 100644 drivers/regulator/p3h2840_i3c_hub_regulator.c
create mode 100644 include/linux/i3c/hub.h
create mode 100644 include/linux/mfd/p3h2840.h
--
2.25.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v7 1/7] i3c: master: Expose the APIs to support I3C hub
2026-03-19 11:24 [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver Lakshay Piplani
@ 2026-03-19 11:24 ` Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 2/7] i3c: master: Add " Lakshay Piplani
` (5 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-19 11:24 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee, Frank.Li, lgirdwood
Cc: vikash.bansal, priyanka.jain, aman.kumarpandey, lakshay.piplani
From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
The following APIs were already declared in i3c_internals.h but were
missing EXPORT_SYMBOL_GPL() in their implementation, making them
inaccessible to modular drivers such as the I3C hub driver:
1) i3c_dev_enable_ibi_locked()
2) i3c_dev_disable_ibi_locked()
3) i3c_dev_request_ibi_locked()
4) i3c_dev_free_ibi_locked()
i3c_master_reattach_i3c_dev() is declared in include/linux/i3c/master.h
and exported via EXPORT_SYMBOL_GPL() to make it accessible to the I3C
hub driver.
Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
---
Changes in v7:
- Fix kernel-doc warnings for *_locked() APIs
- Clarify API exposure in commit message
Changes in v6:
- Split the patch into two parts:
1) expose the existing API
2) add new APIs.
Changes in v5:
- No change
Changes in v4:
- Updated I3C master to handle hub support
---
---
drivers/i3c/master.c | 70 ++++++++++++++++++++++++++++++++++++--
include/linux/i3c/master.h | 2 ++
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 9e6be49bebb2..2a12fbb67ba3 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -1619,8 +1619,23 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
return 0;
}
-static int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
- u8 old_dyn_addr)
+/**
+ * i3c_master_reattach_i3c_dev() - reattach an I3C device with a new address
+ * @dev: I3C device descriptor to reattach
+ * @old_dyn_addr: previous dynamic address of the device
+ *
+ * This function reattaches an existing I3C device to the bus when its dynamic
+ * address has changed. It updates the bus address slot status accordingly:
+ * - Marks the new dynamic address as occupied by an I3C device.
+ * - Frees the old dynamic address slot if applicable.
+ *
+ * This function must be called with the bus lock held in write mode.
+ *
+ * Return: 0 on success, or a negative error code if reattachment fails
+ * (e.g. -EBUSY if the new address slot is not free).
+ */
+int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
+ u8 old_dyn_addr)
{
struct i3c_master_controller *master = i3c_dev_get_master(dev);
int ret;
@@ -1644,6 +1659,7 @@ static int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
return 0;
}
+EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev);
static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
{
@@ -3168,6 +3184,16 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
return master->ops->i3c_xfers(dev, xfers, nxfers, mode);
}
+/**
+ * i3c_dev_disable_ibi_locked() - Disable IBIs coming from a specific device
+ * @dev: device on which IBIs should be disabled
+ *
+ * This function disable IBIs coming from a specific device and wait for
+ * all pending IBIs to be processed.
+ *
+ * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held.
+ * Return: 0 in case of success, a negative error core otherwise.
+ */
int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *master;
@@ -3189,7 +3215,22 @@ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
return 0;
}
+EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked);
+/**
+ * i3c_dev_enable_ibi_locked() - Enable IBIs from a specific device (lock held)
+ * @dev: device on which IBIs should be enabled
+ *
+ * This function enable IBIs coming from a specific device and wait for
+ * all pending IBIs to be processed. This should be called on a device
+ * where i3c_device_request_ibi() has succeeded.
+ *
+ * Note that IBIs from this device might be received before this function
+ * returns to its caller.
+ *
+ * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held.
+ * Return: 0 on success, or a negative error code on failure.
+ */
int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *master = i3c_dev_get_master(dev);
@@ -3204,7 +3245,20 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev)
return ret;
}
+EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_locked);
+/**
+ * i3c_dev_request_ibi_locked() - Request an IBI
+ * @dev: device for which we should enable IBIs
+ * @req: setup requested for this IBI
+ *
+ * This function is responsible for pre-allocating all resources needed to
+ * process IBIs coming from @dev. When this function returns, the IBI is not
+ * enabled until i3c_device_enable_ibi() is called.
+ *
+ * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held.
+ * Return: 0 in case of success, a negative error core otherwise.
+ */
int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
const struct i3c_ibi_setup *req)
{
@@ -3243,7 +3297,18 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
return ret;
}
+EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_locked);
+/**
+ * i3c_dev_free_ibi_locked() - Free all resources needed for IBI handling
+ * @dev: device on which you want to release IBI resources
+ *
+ * This function is responsible for de-allocating resources previously
+ * allocated by i3c_device_request_ibi(). It should be called after disabling
+ * IBIs with i3c_device_disable_ibi().
+ *
+ * Context: Must be called with mutex_lock(&dev->desc->ibi_lock) held.
+ */
void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *master = i3c_dev_get_master(dev);
@@ -3274,6 +3339,7 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
kfree(dev->ibi);
dev->ibi = NULL;
}
+EXPORT_SYMBOL_GPL(i3c_dev_free_ibi_locked);
static int __init i3c_init(void)
{
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 592b646f6134..aeccec171e64 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -613,6 +613,8 @@ void i3c_master_dma_unmap_single(struct i3c_dma *dma_xfer);
DEFINE_FREE(i3c_master_dma_unmap_single, void *,
if (_T) i3c_master_dma_unmap_single(_T))
+int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
+ u8 old_dyn_addr);
int i3c_master_set_info(struct i3c_master_controller *master,
const struct i3c_device_info *info);
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 2/7] i3c: master: Add the APIs to support I3C hub
2026-03-19 11:24 [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 1/7] i3c: master: Expose the APIs to support I3C hub Lakshay Piplani
@ 2026-03-19 11:24 ` Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 3/7] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Lakshay Piplani
` (4 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-19 11:24 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee, Frank.Li, lgirdwood
Cc: vikash.bansal, priyanka.jain, aman.kumarpandey, lakshay.piplani
From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
Add simple helpers to direct attach/detach I3C devices and two CCC helpers
to check CCC support and send CCC commands. These additions prepare the
master core for I3C hub support.
1) i3c_master_direct_attach_i3c_dev()
2) i3c_master_direct_detach_i3c_dev()
3) i3c_master_send_ccc_cmd()
4) i3c_master_supports_ccc_cmd()
Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
---
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 | 103 +++++++++++++++++++++++++++++++++++++
include/linux/i3c/master.h | 8 +++
2 files changed, 111 insertions(+)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 2a12fbb67ba3..500afd8296d1 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -1619,6 +1619,59 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
return 0;
}
+/**
+ * i3c_master_direct_attach_i3c_dev() - attach an I3C device to a master
+ * @master: I3C master controller to attach the device to
+ * @dev: I3C device descriptor representing the device
+ *
+ * This function attaches an I3C device to its master controller once the
+ * device has a valid address on the bus. Devices without
+ * an assigned address are ignored. The master device itself is never
+ * attached through this bus.
+ *
+ * Return: 0 on success, or a negative error code if the attach operation
+ * fails in the master controller driver.
+ */
+int i3c_master_direct_attach_i3c_dev(struct i3c_master_controller *master,
+ struct i3c_dev_desc *dev)
+{
+ int ret = 0;
+
+ /*
+ * We don't attach devices to the controller until they are
+ * addressable on the bus.
+ */
+
+ if (!dev->info.static_addr && !dev->info.dyn_addr)
+ return -EINVAL;
+
+ /* Do not attach the master device itself. */
+ if (master->this != dev && master->ops->attach_i3c_dev)
+ ret = master->ops->attach_i3c_dev(dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_master_direct_attach_i3c_dev);
+
+/**
+ * i3c_master_direct_detach_i3c_dev - Detach an I3C device from its master
+ * @dev: I3C device descriptor to be detached
+ *
+ * This function detaches an I3C device from its master controller.
+ * It ensures that the master itself is not detached. If the device is not
+ * the master and the master controller provides a detach operation,
+ * the detach callback is invoked to perform the actual removal.
+ */
+void i3c_master_direct_detach_i3c_dev(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+
+ /* Do not detach the master device itself. */
+ if (master->this != dev && master->ops->detach_i3c_dev)
+ master->ops->detach_i3c_dev(dev);
+}
+EXPORT_SYMBOL_GPL(i3c_master_direct_detach_i3c_dev);
+
/**
* i3c_master_reattach_i3c_dev() - reattach an I3C device with a new address
* @dev: I3C device descriptor to reattach
@@ -1783,6 +1836,56 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master)
}
}
+/**
+ * i3c_master_supports_ccc_cmd() - check CCC command support
+ * @master: I3C master controller
+ * @cmd: CCC command to verify
+ *
+ * This function verifies whether the given I3C master controller supports
+ * the specified Common Command Code (CCC).
+ *
+ * Return: 0 if the CCC command is supported and executed successfully,
+ * -EINVAL if arguments are invalid,
+ * -EOPNOTSUPP if the master does not support CCC commands,
+ * or another negative error code from the master's operation.
+ */
+int i3c_master_supports_ccc_cmd(struct i3c_master_controller *master,
+ const struct i3c_ccc_cmd *cmd)
+{
+ if (!cmd || !master)
+ return -EINVAL;
+
+ if (!master->ops->supports_ccc_cmd)
+ return -EOPNOTSUPP;
+
+ return master->ops->supports_ccc_cmd(master, cmd);
+}
+EXPORT_SYMBOL_GPL(i3c_master_supports_ccc_cmd);
+
+/**
+ * i3c_master_send_ccc_cmd() - send a CCC command
+ * @master: I3C master controller issuing the command
+ * @cmd: CCC command to be sent
+ *
+ * This function sends a Common Command Code (CCC) command to devices on the
+ * I3C bus. It acquires the bus maintenance lock, executes the command, and
+ * then releases the lock to ensure safe access to the bus.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int i3c_master_send_ccc_cmd(struct i3c_master_controller *master,
+ struct i3c_ccc_cmd *cmd)
+{
+ int ret;
+
+ i3c_bus_maintenance_lock(&master->bus);
+ ret = i3c_master_send_ccc_cmd_locked(master, cmd);
+ i3c_bus_maintenance_unlock(&master->bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(i3c_master_send_ccc_cmd);
+
/**
* i3c_master_do_daa_ext() - Dynamic Address Assignment (extended version)
* @master: controller
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index aeccec171e64..c00f0918394c 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -615,6 +615,14 @@ DEFINE_FREE(i3c_master_dma_unmap_single, void *,
int i3c_master_reattach_i3c_dev(struct i3c_dev_desc *dev,
u8 old_dyn_addr);
+int i3c_master_direct_attach_i3c_dev(struct i3c_master_controller *master,
+ struct i3c_dev_desc *dev);
+void i3c_master_direct_detach_i3c_dev(struct i3c_dev_desc *dev);
+int i3c_master_send_ccc_cmd(struct i3c_master_controller *master,
+ struct i3c_ccc_cmd *cmd);
+
+int 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);
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 3/7] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support
2026-03-19 11:24 [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 1/7] i3c: master: Expose the APIs to support I3C hub Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 2/7] i3c: master: Add " Lakshay Piplani
@ 2026-03-19 11:24 ` Lakshay Piplani
2026-03-22 23:35 ` Rob Herring
2026-03-19 11:24 ` [PATCH v7 4/7] mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator Lakshay Piplani
` (3 subsequent siblings)
6 siblings, 1 reply; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-19 11:24 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee, Frank.Li, lgirdwood
Cc: vikash.bansal, priyanka.jain, aman.kumarpandey, lakshay.piplani
From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
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 <aman.kumarpandey@nxp.com>
Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
---
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 regulator
- Added Regulator supply node
Changes in v2:
- Fixed DT binding check warning
- Revised logic for parsing DTS nodes
---
---
.../devicetree/bindings/i3c/nxp,p3h2840.yaml | 303 ++++++++++++++++++
MAINTAINERS | 8 +
2 files changed, 311 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
diff --git a/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
new file mode 100644
index 000000000000..495e0e4c1ae1
--- /dev/null
+++ b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
@@ -0,0 +1,303 @@
+# 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 <aman.kumarpandey@nxp.com>
+ - Vikash Bansal <vikash.bansal@nxp.com>
+
+description: |
+ P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) is a family of multiport I3C
+ hub devices that connect to:-
+ 1. A host CPU via I3C/I2C/SMBus bus on upstream side and connect to multiple
+ peripheral devices on the downstream side.
+ 2. Have two Controller Ports which can support either
+ I2C/SMBus or I3C buses and connect to a CPU, BMC or SOC.
+ 3. P3H2840/ P3H2841 are 8 port I3C hub with eight 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
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ description:
+ The I3C HUB Target Port number.
+ maximum: 7
+
+ '#address-cells':
+ const: 3
+
+ '#size-cells':
+ const: 0
+
+ nxp,pullup-enable:
+ type: boolean
+ description:
+ Enables the on-die pull-up for Target Port.
+
+ required:
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+
+ "^(i2c|smbus)@[0-7]$":
+ type: object
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ description:
+ The I3C HUB Target Port number.
+ maximum: 7
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ nxp,pullup-enable:
+ type: boolean
+ description:
+ Enables the on-die pull-up for Target Port.
+
+ required:
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ i3c {
+ #address-cells = <3>;
+ #size-cells = <0>;
+
+ hub@70,236153000c2 {
+ reg = <0x70 0x236 0x3000c2>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ assigned-address = <0x50>;
+
+ nxp,tp0145-pullup-ohms = <1000>;
+ nxp,tp2367-pullup-ohms = <1000>;
+ nxp,cp0-io-strength-ohms = <50>;
+ nxp,cp1-io-strength-ohms = <50>;
+ nxp,tp0145-io-strength-ohms = <50>;
+ nxp,tp2367-io-strength-ohms = <50>;
+ vcc3-supply = <®_tpg0>;
+ vcc4-supply = <®_tpg1>;
+
+ regulators {
+ reg_cp0: ldo-cp0 {
+ regulator-name = "ldo-cp0";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ reg_cp1: ldo-cp1 {
+ regulator-name = "ldo-cp1";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ reg_tpg0: ldo-tpg0 {
+ regulator-name = "ldo-tpg0";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ reg_tpg1: ldo-tpg1 {
+ regulator-name = "ldo-tpg1";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+ };
+
+ smbus@0 {
+ reg = <0x0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ nxp,pullup-enable;
+ };
+
+ i2c@1 {
+ reg = <0x1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ nxp,pullup-enable;
+ };
+
+ i3c@2 {
+ reg = <0x2>;
+ #address-cells = <3>;
+ #size-cells = <0>;
+ nxp,pullup-enable;
+ };
+ };
+ };
+
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ hub@70 {
+ reg = <0x70>;
+ compatible = "nxp,p3h2840";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ nxp,tp0145-pullup-ohms = <1000>;
+ nxp,tp2367-pullup-ohms = <1000>;
+ nxp,cp0-io-strength-ohms = <50>;
+ nxp,cp1-io-strength-ohms = <50>;
+ nxp,tp0145-io-strength-ohms = <50>;
+ nxp,tp2367-io-strength-ohms = <50>;
+ vcc3-supply = <®_tpg0_i2c>;
+ vcc4-supply = <®_tpg1_i2c>;
+
+ regulators {
+ reg_cp0_i2c: ldo-cp0 {
+ regulator-name = "ldo-cp0";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ reg_cp1_i2c: ldo-cp1 {
+ regulator-name = "ldo-cp1";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ reg_tpg0_i2c: ldo-tpg0 {
+ regulator-name = "ldo-tpg0";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+
+ reg_tpg1_i2c: ldo-tpg1 {
+ regulator-name = "ldo-tpg1";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ };
+ };
+
+ smbus@0 {
+ reg = <0x0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ nxp,pullup-enable;
+ };
+
+ i2c@1 {
+ reg = <0x1>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ nxp,pullup-enable;
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 96ea84948d76..c72976dbdf31 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19094,6 +19094,14 @@ S: Maintained
F: Documentation/devicetree/bindings/ptp/nxp,ptp-netc.yaml
F: drivers/ptp/ptp_netc.c
+NXP P3H2X4X I3C-HUB DRIVER
+M: Vikash Bansal <vikash.bansal@nxp.com>
+M: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
+L: linux-kernel@vger.kernel.org
+L: linux-i3c-owner@lists.infradead.org
+S: Maintained
+F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
+
NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER
M: Woodrow Douglass <wdouglass@carnegierobotics.com>
S: Maintained
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 4/7] mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator
2026-03-19 11:24 [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver Lakshay Piplani
` (2 preceding siblings ...)
2026-03-19 11:24 ` [PATCH v7 3/7] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Lakshay Piplani
@ 2026-03-19 11:24 ` Lakshay Piplani
2026-03-26 11:58 ` Lee Jones
2026-03-19 11:24 ` [PATCH v7 5/7] regulator: p3h2x4x: Add driver for on-die regulators in NXP P3H2x4x i3c hub Lakshay Piplani
` (2 subsequent siblings)
6 siblings, 1 reply; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-19 11:24 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee, Frank.Li, lgirdwood
Cc: vikash.bansal, priyanka.jain, aman.kumarpandey, lakshay.piplani
From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
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 <aman.kumarpandey@nxp.com>
Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
---
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 | 125 ++++++++++++++++++++++++++++++++++++
include/linux/mfd/p3h2840.h | 27 ++++++++
5 files changed, 168 insertions(+)
create mode 100644 drivers/mfd/p3h2840.c
create mode 100644 include/linux/mfd/p3h2840.h
diff --git a/MAINTAINERS b/MAINTAINERS
index c72976dbdf31..4f67fe64c833 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19101,6 +19101,8 @@ L: linux-kernel@vger.kernel.org
L: linux-i3c-owner@lists.infradead.org
S: Maintained
F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
+F: drivers/mfd/p3h2840.c
+F: include/linux/mfd/p3h2840.h
NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER
M: Woodrow Douglass <wdouglass@carnegierobotics.com>
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 7192c9d1d268..14f4736683a1 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.
+config MFD_P3H2X4X
+ tristate "NXP P3H2X4X I3C Hub Device"
+ depends on I3C_OR_I2C
+ select MFD_CORE
+ select REGMAP_I3C
+ select REGMAP_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=y && OF
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e75e8045c28a..aaadf50fedf4 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -122,6 +122,7 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o
obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
+obj-$(CONFIG_MFD_P3H2X4X) += p3h2840.o
obj-$(CONFIG_MFD_PF1550) += pf1550.o
obj-$(CONFIG_MFD_NCT6694) += nct6694.o
diff --git a/drivers/mfd/p3h2840.c b/drivers/mfd/p3h2840.c
new file mode 100644
index 000000000000..65090a09f7c6
--- /dev/null
+++ b/drivers/mfd/p3h2840.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 NXP
+ * P3H2x4x i3c hub and regulator device.
+ */
+
+#include <linux/i3c/master.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/p3h2840.h>
+#include <linux/regmap.h>
+
+static const struct mfd_cell p3h2x4x_devs[] = {
+ {
+ .name = "p3h2x4x-regulator",
+ },
+ {
+ .name = "p3h2x4x-i3c-hub",
+ },
+};
+
+static const struct regmap_config p3h2x4x_regmap_config = {
+ .reg_bits = P3H2x4x_REG_BITS,
+ .val_bits = P3H2x4x_VAL_BITS,
+ .max_register = 0xFF,
+};
+
+static int p3h2x4x_device_probe_i3c(struct i3c_device *i3cdev)
+{
+ struct p3h2x4x_dev *p3h2x4x;
+ int ret;
+
+ p3h2x4x = devm_kzalloc(&i3cdev->dev, sizeof(*p3h2x4x), GFP_KERNEL);
+ if (!p3h2x4x)
+ return -ENOMEM;
+
+ i3cdev_set_drvdata(i3cdev, p3h2x4x);
+
+ p3h2x4x->regmap = devm_regmap_init_i3c(i3cdev, &p3h2x4x_regmap_config);
+ if (IS_ERR(p3h2x4x->regmap))
+ return dev_err_probe(&i3cdev->dev, PTR_ERR(p3h2x4x->regmap),
+ "Failed to register I3C HUB regmap\n");
+
+ p3h2x4x->is_p3h2x4x_in_i3c = true;
+ p3h2x4x->i3cdev = i3cdev;
+
+ ret = devm_mfd_add_devices(&i3cdev->dev, PLATFORM_DEVID_NONE,
+ p3h2x4x_devs, ARRAY_SIZE(p3h2x4x_devs),
+ NULL, 0, NULL);
+ if (ret)
+ return dev_err_probe(&i3cdev->dev, ret, "Failed to add sub devices\n");
+
+ return 0;
+}
+
+static int p3h2x4x_device_probe_i2c(struct i2c_client *client)
+{
+ struct p3h2x4x_dev *p3h2x4x;
+ int ret;
+
+ p3h2x4x = devm_kzalloc(&client->dev, sizeof(*p3h2x4x), GFP_KERNEL);
+ if (!p3h2x4x)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, p3h2x4x);
+
+ p3h2x4x->regmap = devm_regmap_init_i2c(client, &p3h2x4x_regmap_config);
+ if (IS_ERR(p3h2x4x->regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(p3h2x4x->regmap),
+ "Failed to register I3C HUB regmap\n");
+
+ p3h2x4x->is_p3h2x4x_in_i3c = false;
+
+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
+ 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;
+}
+
+/* p3h2x4x ids (i3c) */
+static const struct i3c_device_id p3h2x4x_i3c_ids[] = {
+ I3C_CLASS(I3C_DCR_HUB, NULL),
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i3c, p3h2x4x_i3c_ids);
+
+/* p3h2x4x ids (i2c) */
+static const struct i2c_device_id p3h2x4x_i2c_id_table[] = {
+ { "nxp-i3c-hub" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, p3h2x4x_i2c_id_table);
+
+static const struct of_device_id p3h2x4x_i2c_of_match[] = {
+ { .compatible = "nxp,p3h2840", },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, p3h2x4x_i2c_of_match);
+static struct i3c_driver p3h2x4x_i3c = {
+ .driver = {
+ .name = "p3h2x4x_i3c_drv",
+ },
+ .probe = p3h2x4x_device_probe_i3c,
+ .id_table = p3h2x4x_i3c_ids,
+};
+
+static struct i2c_driver p3h2x4x_i2c = {
+ .driver = {
+ .name = "p3h2x4x_i2c_drv",
+ .of_match_table = p3h2x4x_i2c_of_match,
+ },
+ .probe = p3h2x4x_device_probe_i2c,
+ .id_table = p3h2x4x_i2c_id_table,
+};
+
+module_i3c_i2c_driver(p3h2x4x_i3c, &p3h2x4x_i2c);
+
+MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>");
+MODULE_AUTHOR("Vikash Bansal <vikash.bansal@nxp.com>");
+MODULE_DESCRIPTION("P3H2x4x I3C HUB multi function driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/p3h2840.h b/include/linux/mfd/p3h2840.h
new file mode 100644
index 000000000000..cba6fa516d1e
--- /dev/null
+++ b/include/linux/mfd/p3h2840.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2025 NXP
+ * This header file contain private Reg address and its bit mapping etc.
+ */
+
+#ifndef _LINUX_MFD_P3H2840_H
+#define _LINUX_MFD_P3H2840_H
+
+#include <linux/types.h>
+
+/* 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
+
+/* Reg config for Regmap */
+#define P3H2x4x_REG_BITS 8
+#define P3H2x4x_VAL_BITS 8
+
+struct p3h2x4x_dev {
+ struct i3c_device *i3cdev;
+ struct regmap *regmap;
+ bool is_p3h2x4x_in_i3c;
+};
+#endif /* _LINUX_MFD_P3H2840_H */
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 5/7] regulator: p3h2x4x: Add driver for on-die regulators in NXP P3H2x4x i3c hub
2026-03-19 11:24 [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver Lakshay Piplani
` (3 preceding siblings ...)
2026-03-19 11:24 ` [PATCH v7 4/7] mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator Lakshay Piplani
@ 2026-03-19 11:24 ` Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 6/7] i3c: hub: Add support for the I3C interface in the I3C hub Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 7/7] i3c: hub: p3h2x4x: Add support for NXP P3H2x4x I3C hub functionality Lakshay Piplani
6 siblings, 0 replies; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-19 11:24 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee, Frank.Li, lgirdwood
Cc: vikash.bansal, priyanka.jain, aman.kumarpandey, lakshay.piplani,
Frank Li
From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
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 <aman.kumarpandey@nxp.com>
Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
Reviewed-by: Frank Li <frank.li@nxp.com>
---
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 | 219 ++++++++++++++++++
4 files changed, 231 insertions(+)
create mode 100644 drivers/regulator/p3h2840_i3c_hub_regulator.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 4f67fe64c833..7d142a1a8fb7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19102,6 +19102,7 @@ L: linux-i3c-owner@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
NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index d10b6f9243d5..290f441b7324 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.
+config REGULATOR_P3H2X4X
+ tristate "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) += qcom-rpmh-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_USB_VBUS) += qcom_usb_vbus-regulator.o
+obj-$(CONFIG_REGULATOR_P3H2X4X) += p3h2840_i3c_hub_regulator.o
obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
obj-$(CONFIG_REGULATOR_PCA9450) += pca9450-regulator.o
obj-$(CONFIG_REGULATOR_PF0900) += pf0900-regulator.o
diff --git a/drivers/regulator/p3h2840_i3c_hub_regulator.c b/drivers/regulator/p3h2840_i3c_hub_regulator.c
new file mode 100644
index 000000000000..b2d31ecd5f19
--- /dev/null
+++ b/drivers/regulator/p3h2840_i3c_hub_regulator.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 NXP
+ * This P3H2x4x driver file contain functions for enable/disable regulator and voltage set/get.
+ */
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/mfd/p3h2840.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+
+#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 regmap *regmap;
+};
+
+struct p3h2x4x_reg_state {
+ unsigned int orig;
+ bool restore;
+};
+
+static void p3h2x4x_reg_guard_enter(struct regulator_dev *rdev,
+ struct p3h2x4x_reg_state *state)
+{
+ state->restore = false;
+
+ if (regmap_read(rdev->regmap,
+ P3H2x4x_DEV_REG_PROTECTION_CODE,
+ &state->orig))
+ return;
+
+ if (state->orig != P3H2x4x_REGISTERS_UNLOCK_CODE) {
+ regmap_write(rdev->regmap,
+ P3H2x4x_DEV_REG_PROTECTION_CODE,
+ P3H2x4x_REGISTERS_UNLOCK_CODE);
+ state->restore = true;
+ }
+}
+
+static void p3h2x4x_reg_guard_exit(struct regulator_dev *rdev,
+ struct p3h2x4x_reg_state *state)
+{
+ if (state->restore)
+ regmap_write(rdev->regmap,
+ P3H2x4x_DEV_REG_PROTECTION_CODE,
+ state->orig);
+}
+
+DEFINE_LOCK_GUARD_1(p3h2x4x_reg, struct regulator_dev,
+ p3h2x4x_reg_guard_enter(_T->lock, &_T->state),
+ p3h2x4x_reg_guard_exit(_T->lock, &_T->state),
+ struct p3h2x4x_reg_state state);
+
+static int p3h2x4x_regulator_enable(struct regulator_dev *rdev)
+{
+ guard(p3h2x4x_reg)(rdev);
+ return regulator_enable_regmap(rdev);
+}
+
+static int p3h2x4x_regulator_disable(struct regulator_dev *rdev)
+{
+ guard(p3h2x4x_reg)(rdev);
+ return regulator_disable_regmap(rdev);
+}
+
+static int p3h2x4x_regulator_set_voltage_sel(struct regulator_dev *rdev,
+ unsigned int sel)
+{
+ guard(p3h2x4x_reg)(rdev);
+ return regulator_set_voltage_sel_regmap(rdev, sel);
+}
+
+static const struct regulator_ops p3h2x4x_ldo_ops = {
+ .list_voltage = regulator_list_voltage_table,
+ .map_voltage = regulator_map_voltage_iterate,
+ .set_voltage_sel = p3h2x4x_regulator_set_voltage_sel,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .enable = p3h2x4x_regulator_enable,
+ .disable = p3h2x4x_regulator_disable,
+ .is_enabled = regulator_is_enabled_regmap,
+};
+
+static const unsigned int p3h2x4x_voltage_table[] = {
+ 1000000,
+ 1100000,
+ 1200000,
+ 1800000,
+};
+
+static struct regulator_desc p3h2x4x_regulators[] = {
+ {
+ .name = "ldo-cp0",
+ .of_match = of_match_ptr("ldo-cp0"),
+ .regulators_node = of_match_ptr("regulators"),
+ .volt_table = p3h2x4x_voltage_table,
+ .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table),
+ .ops = &p3h2x4x_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF,
+ .enable_mask = P3H2x4x_CP0_EN_LDO,
+ .vsel_reg = P3H2x4x_VCCIO_LDO_CONF,
+ .vsel_mask = P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK,
+ },
+ {
+ .name = "ldo-cp1",
+ .of_match = of_match_ptr("ldo-cp1"),
+ .regulators_node = of_match_ptr("regulators"),
+ .volt_table = p3h2x4x_voltage_table,
+ .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table),
+ .ops = &p3h2x4x_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF,
+ .enable_mask = P3H2x4x_CP1_EN_LDO,
+ .vsel_reg = P3H2x4x_VCCIO_LDO_CONF,
+ .vsel_mask = P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK,
+ },
+ {
+ .name = "ldo-tpg0",
+ .of_match = of_match_ptr("ldo-tpg0"),
+ .regulators_node = of_match_ptr("regulators"),
+ .volt_table = p3h2x4x_voltage_table,
+ .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table),
+ .ops = &p3h2x4x_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF,
+ .enable_mask = P3H2x4x_TP0145_EN_LDO,
+ .vsel_reg = P3H2x4x_VCCIO_LDO_CONF,
+ .vsel_mask = P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK,
+ },
+ {
+ .name = "ldo-tpg1",
+ .of_match = of_match_ptr("ldo-tpg1"),
+ .regulators_node = of_match_ptr("regulators"),
+ .volt_table = p3h2x4x_voltage_table,
+ .n_voltages = ARRAY_SIZE(p3h2x4x_voltage_table),
+ .ops = &p3h2x4x_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .enable_reg = P3H2x4x_LDO_AND_PULLUP_CONF,
+ .enable_mask = P3H2x4x_TP2367_EN_LDO,
+ .vsel_reg = P3H2x4x_VCCIO_LDO_CONF,
+ .vsel_mask = P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK,
+ },
+};
+
+static int p3h2x4x_regulator_probe(struct platform_device *pdev)
+{
+ struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent);
+ struct p3h2x4x_regulator_dev *p3h2x4x_regulator;
+ struct regulator_config rcfg = { };
+ struct device *dev = &pdev->dev;
+ struct regulator_dev *rdev;
+ int i;
+
+ p3h2x4x_regulator = devm_kzalloc(dev, sizeof(*p3h2x4x_regulator), GFP_KERNEL);
+ if (!p3h2x4x_regulator)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, p3h2x4x_regulator);
+
+ p3h2x4x_regulator->regmap = p3h2x4x->regmap;
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ rcfg.dev = dev;
+ rcfg.dev->of_node = dev->of_node;
+ rcfg.regmap = p3h2x4x_regulator->regmap;
+ rcfg.driver_data = p3h2x4x_regulator;
+
+ for (i = 0; i < ARRAY_SIZE(p3h2x4x_regulators); i++) {
+ rdev = devm_regulator_register(&pdev->dev, &p3h2x4x_regulators[i], &rcfg);
+ 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] = rdev;
+ }
+ return 0;
+}
+
+static struct platform_driver p3h2x4x_regulator_driver = {
+ .driver = {
+ .name = "p3h2x4x-regulator",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = p3h2x4x_regulator_probe,
+};
+module_platform_driver(p3h2x4x_regulator_driver);
+
+MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>");
+MODULE_AUTHOR("Vikash Bansal <vikash.bansal@nxp.com>");
+MODULE_DESCRIPTION("P3H2x4x I3C HUB Regulator driver");
+MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 6/7] i3c: hub: Add support for the I3C interface in the I3C hub
2026-03-19 11:24 [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver Lakshay Piplani
` (4 preceding siblings ...)
2026-03-19 11:24 ` [PATCH v7 5/7] regulator: p3h2x4x: Add driver for on-die regulators in NXP P3H2x4x i3c hub Lakshay Piplani
@ 2026-03-19 11:24 ` Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 7/7] i3c: hub: p3h2x4x: Add support for NXP P3H2x4x I3C hub functionality Lakshay Piplani
6 siblings, 0 replies; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-19 11:24 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee, Frank.Li, lgirdwood
Cc: vikash.bansal, priyanka.jain, aman.kumarpandey, lakshay.piplani
Add virtual I3C bus support for the hub and provide interface to enable
or disable downstream ports.
Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
Signed-off-by: Lakshay Piplani <lakshay.piplani@nxp.com>
---
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 | 3 +
drivers/i3c/Kconfig | 15 ++
drivers/i3c/Makefile | 1 +
drivers/i3c/hub.c | 460 ++++++++++++++++++++++++++++++++++++++++
include/linux/i3c/hub.h | 107 ++++++++++
5 files changed, 586 insertions(+)
create mode 100644 drivers/i3c/hub.c
create mode 100644 include/linux/i3c/hub.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 7d142a1a8fb7..028ffc4494d0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19097,12 +19097,15 @@ F: drivers/ptp/ptp_netc.c
NXP P3H2X4X I3C-HUB DRIVER
M: Vikash Bansal <vikash.bansal@nxp.com>
M: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
+M: Lakshay Piplani <lakshay.piplani@nxp.com>
L: linux-kernel@vger.kernel.org
L: linux-i3c-owner@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
NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index 626c54b386d5..65304b416bb4 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -21,6 +21,21 @@ menuconfig I3C
if I3C
source "drivers/i3c/master/Kconfig"
+
+config I3C_HUB
+ tristate "I3C Hub Support"
+ depends on I3C
+ help
+ Enable support for the I3C interface in hub devices.
+
+ This option adds virtual I3C bus support for hubs by creating
+ virtual master controllers for downstream ports and forwarding
+ bus operations through the hub device. It also provides an
+ interface used by hub drivers to enable or disable downstream
+ ports during bus transactions.
+
+ Say Y here if your platform includes an I3C hub device
+
endif # I3C
config I3C_OR_I2C
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index 11982efbc6d9..9ddee56a6338 100644
--- a/drivers/i3c/Makefile
+++ b/drivers/i3c/Makefile
@@ -2,3 +2,4 @@
i3c-y := device.o master.o
obj-$(CONFIG_I3C) += i3c.o
obj-$(CONFIG_I3C) += master/
+obj-$(CONFIG_I3C_HUB) += hub.o
diff --git a/drivers/i3c/hub.c b/drivers/i3c/hub.c
new file mode 100644
index 000000000000..67c5d2695a61
--- /dev/null
+++ b/drivers/i3c/hub.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2026 NXP
+ * Generic I3C Hub core implementing virtual controller operations.
+ */
+#include <linux/i3c/device.h>
+#include <linux/i3c/hub.h>
+
+#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 *controller)
+{
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_hub *hub;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return -ENODEV;
+
+ hub = hub_controller->hub;
+
+ if (!hub->hub_dev)
+ return -ENODEV;
+
+ controller->this = hub->hub_dev->desc;
+ return 0;
+}
+
+static void i3c_hub_master_bus_cleanup(struct i3c_master_controller *controller)
+{
+ controller->this = NULL;
+}
+
+static int i3c_hub_attach_i3c_dev(struct i3c_dev_desc *dev)
+{
+ return 0;
+}
+
+static int i3c_hub_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr)
+{
+ 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)
+{
+ int ret;
+
+ if (!hub || !hub->parent)
+ return -ENODEV;
+
+ i3c_hub_enable_port(controller);
+ ret = i3c_master_do_daa(hub->parent);
+ i3c_hub_disable_port(controller);
+
+ return ret;
+}
+
+static bool i3c_hub_supports_ccc_cmd(struct i3c_hub *hub,
+ const struct i3c_ccc_cmd *cmd)
+{
+ return i3c_master_supports_ccc_cmd(hub->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)
+{
+ int ret;
+
+ if (!hub || !hub->parent)
+ return -ENODEV;
+
+ i3c_hub_enable_port(controller);
+ ret = i3c_master_send_ccc_cmd(hub->parent, cmd);
+ i3c_hub_disable_port(controller);
+
+ 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 hub
+ * 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 = i3c_dev_get_master(dev);
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_dev_desc *hub_dev;
+ u8 hub_addr, target_addr;
+ struct i3c_hub *hub;
+ int ret;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return -ENODEV;
+
+ hub = hub_controller->hub;
+
+ if (!hub->hub_dev)
+ return -ENODEV;
+
+ hub_dev = hub->hub_dev->desc;
+
+ i3c_hub_enable_port(controller);
+
+ hub_addr = hub_dev->info.dyn_addr ?
+ hub_dev->info.dyn_addr : hub_dev->info.static_addr;
+
+ target_addr = dev->info.dyn_addr ?
+ dev->info.dyn_addr : dev->info.static_addr;
+
+ if (hub_addr != target_addr) {
+ hub_dev->info.dyn_addr = target_addr;
+ ret = i3c_master_reattach_i3c_dev(hub_dev, target_addr);
+ if (ret)
+ goto disable;
+ }
+
+ ret = i3c_device_do_xfers(hub->hub_dev, xfers, nxfers, mode);
+
+ if (hub_addr != target_addr) {
+ hub_dev->info.dyn_addr = hub_addr;
+ ret |= i3c_master_reattach_i3c_dev(hub_dev, hub_addr);
+ }
+
+disable:
+ i3c_hub_disable_port(controller);
+ return ret;
+}
+
+static int i3c_hub_attach_i2c_dev(struct i2c_dev_desc *dev)
+{
+ return 0;
+}
+
+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 0;
+}
+
+static int i3c_hub_master_do_daa(struct i3c_master_controller *controller)
+{
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_hub *hub;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return -ENODEV;
+
+ hub = hub_controller->hub;
+
+ return i3c_hub_do_daa(hub, controller);
+}
+
+static int i3c_hub_master_send_ccc_cmd(struct i3c_master_controller *controller,
+ struct i3c_ccc_cmd *cmd)
+{
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_hub *hub;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return -ENODEV;
+
+ hub = hub_controller->hub;
+
+ if (!hub->parent)
+ return -ENODEV;
+
+ if (cmd->id == I3C_CCC_RSTDAA(true))
+ return 0;
+
+ return i3c_hub_send_ccc_cmd(hub, controller, cmd);
+}
+
+static bool i3c_hub_master_supports_ccc_cmd(struct i3c_master_controller *controller,
+ const struct i3c_ccc_cmd *cmd)
+{
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_hub *hub;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return false;
+
+ hub = hub_controller->hub;
+
+ return i3c_hub_supports_ccc_cmd(hub, cmd);
+}
+
+/**
+ * i3c_hub_request_ibi() - Request IBI through parent controller
+ * @desc: Target device descriptor
+ * @req: IBI setup
+ *
+ * Temporarily updates parent controller context to request IBI for a device
+ * 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 = 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 = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return -ENODEV;
+
+ hub = hub_controller->hub;
+
+ if (!hub->parent)
+ return -ENODEV;
+
+ parent = hub->parent;
+
+ orig_parent = i3c_hub_update_desc_parent(&desc->common, parent);
+
+ ret = i3c_master_direct_attach_i3c_dev(parent, desc);
+ if (ret) {
+ i3c_hub_restore_desc_parent(&desc->common, orig_parent);
+ return ret;
+ }
+
+ mutex_unlock(&desc->ibi_lock);
+ kfree(desc->ibi);
+ desc->ibi = NULL;
+ ret = i3c_dev_request_ibi_locked(desc, req);
+ mutex_lock(&desc->ibi_lock);
+
+ i3c_hub_restore_desc_parent(&desc->common, orig_parent);
+
+ return ret;
+}
+
+static void i3c_hub_free_ibi(struct i3c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = i3c_dev_get_master(desc);
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_master_controller *orig_parent;
+ struct i3c_master_controller *parent;
+ struct i3c_hub *hub;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return;
+
+ hub = hub_controller->hub;
+
+ parent = hub->parent;
+
+ i3c_hub_enable_port(controller);
+
+ orig_parent = i3c_hub_update_desc_parent(&desc->common, parent);
+ i3c_master_direct_detach_i3c_dev(desc);
+ mutex_unlock(&desc->ibi_lock);
+ i3c_dev_free_ibi_locked(desc);
+ mutex_lock(&desc->ibi_lock);
+ i3c_hub_restore_desc_parent(&desc->common, orig_parent);
+
+ i3c_hub_disable_port(controller);
+}
+
+/**
+ * i3c_hub_enable_ibi() - Enable IBI via hub port
+ * @desc: Target device descriptor
+ *
+ * Enables port connection and forwards the IBI enable request to the parent
+ * controller.
+ */
+static int i3c_hub_enable_ibi(struct i3c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = i3c_dev_get_master(desc);
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_master_controller *orig_parent;
+ struct i3c_master_controller *parent;
+ struct i3c_hub *hub;
+ int ret;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return -ENODEV;
+
+ hub = hub_controller->hub;
+
+ if (!hub->parent)
+ return -ENODEV;
+
+ parent = hub->parent;
+
+ i3c_hub_enable_port(controller);
+
+ orig_parent = i3c_hub_update_desc_parent(&desc->common, parent);
+
+ down_write(&parent->bus.lock);
+ mutex_unlock(&desc->ibi_lock);
+ ret = i3c_dev_enable_ibi_locked(desc);
+ mutex_lock(&desc->ibi_lock);
+ up_write(&parent->bus.lock);
+
+ i3c_hub_restore_desc_parent(&desc->common, orig_parent);
+
+ i3c_hub_disable_port(controller);
+
+ return ret;
+}
+
+/**
+ * i3c_hub_disable_ibi() - Disable IBI via hub port
+ * @desc: Target device descriptor
+ *
+ * Enables port connection and forwards the IBI disable request to the parent
+ * controller.
+ */
+static int i3c_hub_disable_ibi(struct i3c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = i3c_dev_get_master(desc);
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_master_controller *orig_parent;
+ struct i3c_master_controller *parent;
+ struct i3c_hub *hub;
+ int ret;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return -ENODEV;
+
+ hub = hub_controller->hub;
+
+ if (!hub->parent)
+ return -ENODEV;
+
+ parent = hub->parent;
+
+ i3c_hub_enable_port(controller);
+
+ orig_parent = i3c_hub_update_desc_parent(&desc->common, parent);
+
+ down_write(&parent->bus.lock);
+ mutex_unlock(&desc->ibi_lock);
+ ret = i3c_dev_disable_ibi_locked(desc);
+ mutex_lock(&desc->ibi_lock);
+ up_write(&parent->bus.lock);
+
+ i3c_hub_restore_desc_parent(&desc->common, orig_parent);
+
+ i3c_hub_disable_port(controller);
+
+ return ret;
+}
+
+static void i3c_hub_recycle_ibi_slot(struct i3c_dev_desc *desc,
+ struct i3c_ibi_slot *slot)
+{
+}
+
+static const struct i3c_master_controller_ops i3c_hub_master_ops_data = {
+ .bus_init = i3c_hub_master_bus_init,
+ .bus_cleanup = i3c_hub_master_bus_cleanup,
+ .attach_i3c_dev = i3c_hub_attach_i3c_dev,
+ .reattach_i3c_dev = i3c_hub_reattach_i3c_dev,
+ .detach_i3c_dev = i3c_hub_detach_i3c_dev,
+ .do_daa = i3c_hub_master_do_daa,
+ .supports_ccc_cmd = i3c_hub_master_supports_ccc_cmd,
+ .send_ccc_cmd = i3c_hub_master_send_ccc_cmd,
+ .i3c_xfers = i3c_hub_master_priv_xfers,
+ .attach_i2c_dev = i3c_hub_attach_i2c_dev,
+ .detach_i2c_dev = i3c_hub_detach_i2c_dev,
+ .i2c_xfers = i3c_hub_i2c_xfers,
+ .request_ibi = i3c_hub_request_ibi,
+ .free_ibi = i3c_hub_free_ibi,
+ .enable_ibi = i3c_hub_enable_ibi,
+ .disable_ibi = i3c_hub_disable_ibi,
+ .recycle_ibi_slot = i3c_hub_recycle_ibi_slot,
+};
+
+/**
+ * i3c_hub_init() - Initialize hub context
+ * @hub: Hub instance
+ * @parent: Parent I3C master controller
+ * @ops: Vendor callbacks
+ * @hub_dev: I3C hub device
+ */
+struct i3c_hub *i3c_hub_init(struct i3c_master_controller *parent,
+ const struct i3c_hub_ops *ops,
+ struct i3c_device *hub_dev)
+{
+ struct i3c_hub *hub;
+
+ hub = devm_kzalloc(&hub_dev->dev,
+ sizeof(*hub),
+ GFP_KERNEL);
+
+ if (!hub)
+ return ERR_PTR(-ENOMEM);
+
+ hub->parent = parent;
+ hub->ops = ops;
+ hub->hub_dev = hub_dev;
+
+ return hub;
+}
+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);
+
+MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>");
+MODULE_AUTHOR("Vikash Bansal <vikash.bansal@nxp.com>");
+MODULE_AUTHOR("Lakshay Piplani <lakshay.piplani@nxp.com>");
+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..b685d4d3cc7e
--- /dev/null
+++ b/include/linux/i3c/hub.h
@@ -0,0 +1,107 @@
+/* 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 <linux/i3c/master.h>
+
+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 = desc->master;
+
+ desc->master = parent;
+ return orig_parent;
+}
+
+static inline void
+i3c_hub_restore_desc_parent(struct i3c_i2c_dev_desc *desc,
+ struct i3c_master_controller *parent)
+{
+ desc->master = parent;
+}
+
+/**
+ * struct i3c_hub - Generic I3C hub context
+ * @parent: Parent I3C master controller
+ * @ops: Vendor callbacks for port connection control
+ * @hub_dev: I3C device representing the hub on the parent bus
+ */
+struct i3c_hub {
+ struct i3c_master_controller *parent;
+ const struct i3c_hub_ops *ops;
+ struct i3c_device *hub_dev;
+};
+
+struct i3c_hub_controller {
+ struct i3c_master_controller *parent;
+ struct i3c_master_controller controller;
+ struct i3c_hub *hub;
+};
+
+struct i3c_hub_ops {
+ void (*enable_port)(struct i3c_master_controller *controller);
+ void (*disable_port)(struct i3c_master_controller *controller);
+};
+
+/**
+ * i3c_hub_enable_port() - Enable hub connection for a controller
+ * @controller: Virtual controller representing a hub port
+ *
+ * Retrieves hub context from controller drvdata and invokes the vendor
+ * callback to enable the associated port connection.
+ */
+static inline void i3c_hub_enable_port(struct i3c_master_controller *controller)
+{
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_hub *hub;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return;
+
+ hub = hub_controller->hub;
+
+ if (hub && hub->ops && hub->ops->enable_port)
+ hub->ops->enable_port(controller);
+}
+
+/**
+ * i3c_hub_disable_port() - Disable hub connection for a controller
+ * @controller: Virtual controller representing a hub port
+ *
+ * Retrieves hub context from controller drvdata and invokes the vendor
+ * callback to disable the associated port connection.
+ */
+static inline void i3c_hub_disable_port(struct i3c_master_controller *controller)
+{
+ struct i3c_hub_controller *hub_controller;
+ struct i3c_hub *hub;
+
+ hub_controller = dev_get_drvdata(&controller->dev);
+ if (!hub_controller || !hub_controller->hub)
+ return;
+
+ hub = hub_controller->hub;
+
+ if (hub && hub->ops && hub->ops->disable_port)
+ hub->ops->disable_port(controller);
+}
+
+/**
+ * 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);
+
+struct i3c_hub *i3c_hub_init(struct i3c_master_controller *parent,
+ const struct i3c_hub_ops *ops,
+ struct i3c_device *hub_dev);
+
+#endif
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v7 7/7] i3c: hub: p3h2x4x: Add support for NXP P3H2x4x I3C hub functionality
2026-03-19 11:24 [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver Lakshay Piplani
` (5 preceding siblings ...)
2026-03-19 11:24 ` [PATCH v7 6/7] i3c: hub: Add support for the I3C interface in the I3C hub Lakshay Piplani
@ 2026-03-19 11:24 ` Lakshay Piplani
6 siblings, 0 replies; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-19 11:24 UTC (permalink / raw)
To: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, lee, Frank.Li, lgirdwood
Cc: vikash.bansal, priyanka.jain, aman.kumarpandey, lakshay.piplani
From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
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)
- MCTP device support
- In-band interrupt handling
P3H2440/P3H2441 support 4 target ports.
P3H2840/P3H2841 support 8 target ports.
Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
Signed-off-by: Lakshay Piplani <lakshay.piplani@nxp.com>
---
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 regulator
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 | 334 ++++++++++++++++++
drivers/i3c/hub/p3h2840_i3c_hub_common.c | 349 +++++++++++++++++++
drivers/i3c/hub/p3h2840_i3c_hub_i3c.c | 132 +++++++
drivers/i3c/hub/p3h2840_i3c_hub_smbus.c | 422 +++++++++++++++++++++++
include/linux/i3c/device.h | 1 +
10 files changed, 1256 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 028ffc4494d0..4a8902425916 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19103,6 +19103,7 @@ L: linux-i3c-owner@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 65304b416bb4..74727d614492 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -36,6 +36,7 @@ config I3C_HUB
Say Y here if your platform includes an I3C hub device
+source "drivers/i3c/hub/Kconfig"
endif # I3C
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 := device.o master.o
obj-$(CONFIG_I3C) += i3c.o
obj-$(CONFIG_I3C) += master/
obj-$(CONFIG_I3C_HUB) += hub.o
+obj-$(CONFIG_I3C_HUB) += hub/
diff --git a/drivers/i3c/hub/Kconfig b/drivers/i3c/hub/Kconfig
new file mode 100644
index 000000000000..93eca168e764
--- /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 "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. The Say Y or M here to use I3C
+ HUB driver to configure I3C HUB device.
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 := p3h2840_i3c_hub_common.o p3h2840_i3c_hub_i3c.o p3h2840_i3c_hub_smbus.o
+obj-$(CONFIG_P3H2X4X_I3C_HUB) += p3h2840_i3c_hub.o
diff --git a/drivers/i3c/hub/p3h2840_i3c_hub.h b/drivers/i3c/hub/p3h2840_i3c_hub.h
new file mode 100644
index 000000000000..dcb83b534ef4
--- /dev/null
+++ b/drivers/i3c/hub/p3h2840_i3c_hub.h
@@ -0,0 +1,334 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2025-2026 NXP
+ * This header file contain private device structure definition.
+ */
+
+#ifndef P3H2840_I3C_HUB_H
+#define P3H2840_I3C_HUB_H
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/i3c/device.h>
+#include <linux/i3c/hub.h>
+#include <linux/i3c/master.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regmap.h>
+
+/* 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
+
+/* Pullup selection DT settings */
+#define P3H2x4x_TP_PULLUP_250R 0x00
+#define P3H2x4x_TP_PULLUP_500R 0x01
+#define P3H2x4x_TP_PULLUP_1000R 0x02
+#define P3H2x4x_TP_PULLUP_2000R 0x03
+
+#define P3H2x4x_TP_PULLUP_DISABLED 0x00
+#define P3H2x4x_TP_PULLUP_ENABLED 0x01
+
+#define P3H2x4x_IO_STRENGTH_20_OHM 0x00
+#define P3H2x4x_IO_STRENGTH_30_OHM 0x01
+#define P3H2x4x_IO_STRENGTH_40_OHM 0x02
+#define P3H2x4x_IO_STRENGTH_50_OHM 0x03
+
+#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 applications 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 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_POLLING_ROLL_PERIOD_MS 10
+
+/* 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)
+
+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_tp_smbus_algo - add i2c adapter for target port configured as SMBus.
+ * @priv: p3h2x4x device structure.
+ * @tp: target port.
+ * Return: 0 in case of success, a negative EINVAL code if the error.
+ */
+int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub);
+
+/**
+ * p3h2x4x_tp_i3c_algo - register i3c controller for target port configured as I3C.
+ * @priv: p3h2x4x device structure.
+ * @tp: target port.
+ * Return: 0 in case of success, a negative EINVAL code if the error.
+ */
+int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub);
+
+/**
+ * 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);
+#endif /* P3H2840_I3C_HUB_H */
diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_common.c b/drivers/i3c/hub/p3h2840_i3c_hub_common.c
new file mode 100644
index 000000000000..4675c794b349
--- /dev/null
+++ b/drivers/i3c/hub/p3h2840_i3c_hub_common.c
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025-2026 NXP
+ * This P3H2x4x driver file implements functions for Hub probe and DT parsing.
+ */
+
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/p3h2840.h>
+#include <linux/util_macros.h>
+
+#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[] = {
+ 250, 500, 1000, 2000
+};
+
+static const int p3h2x4x_io_strength_tbl[] = {
+ 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 = dev_get_drvdata(dev);
+ u8 pullup;
+
+ pullup = P3H2x4x_TP0145_PULLUP_CONF(p3h2x4x_pullup_dt_to_reg
+ (p3h2x4x_i3c_hub->hub_config.tp0145_pullup));
+
+ pullup |= 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 = dev_get_drvdata(dev);
+ u8 io_strength;
+
+ io_strength = P3H2x4x_CP0_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg
+ (p3h2x4x_i3c_hub->hub_config.cp0_io_strength));
+
+ io_strength |= P3H2x4x_CP1_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg
+ (p3h2x4x_i3c_hub->hub_config.cp1_io_strength));
+
+ io_strength |= P3H2x4x_TP0145_IO_STRENGTH(p3h2x4x_io_strength_dt_to_reg
+ (p3h2x4x_i3c_hub->hub_config.tp0145_io_strength));
+
+ io_strength |= 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[] = {
+ "vcc1",
+ "vcc2",
+ "vcc3",
+ "vcc4"
+ };
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(supplies); i++) {
+ ret = devm_regulator_get_enable_optional(dev, supplies[i]);
+ if (ret && ret != -ENODEV)
+ dev_warn(dev, "Failed to enable %s (%d)\n",
+ supplies[i], ret);
+ }
+
+ /* This delay is required for the regulator to stabilize its output voltage */
+ mdelay(5);
+
+ return 0;
+}
+
+static int p3h2x4x_configure_tp(struct device *dev)
+{
+ struct p3h2x4x_i3c_hub_dev *hub = dev_get_drvdata(dev);
+ u8 mode = 0, smbus = 0, pullup = 0, target_port = 0;
+ int tp, ret;
+
+ for (tp = 0; tp < P3H2x4x_TP_MAX_COUNT; tp++) {
+ pullup |= hub->hub_config.tp_config[tp].pullup_en ? P3H2x4x_SET_BIT(tp) : 0;
+ mode |= (hub->hub_config.tp_config[tp].mode != P3H2x4x_TP_MODE_I3C) ?
+ P3H2x4x_SET_BIT(tp) : 0;
+ smbus |= (hub->hub_config.tp_config[tp].mode == P3H2x4x_TP_MODE_SMBUS) ?
+ P3H2x4x_SET_BIT(tp) : 0;
+ target_port |= (hub->tp_bus[tp].tp_mask == P3H2x4x_SET_BIT(tp)) ?
+ hub->tp_bus[tp].tp_mask : 0;
+ }
+
+ ret = regmap_update_bits(hub->regmap, P3H2x4x_TP_PULLUP_EN, pullup, pullup);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(hub->regmap, P3H2x4x_TP_IO_MODE_CONF, mode, mode);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(hub->regmap, P3H2x4x_TP_SMBUS_AGNT_EN, smbus, smbus);
+ if (ret)
+ return ret;
+
+ if (target_port & ~smbus) {
+ ret = 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, target_port, target_port);
+}
+
+static int p3h2x4x_configure_hw(struct device *dev)
+{
+ int ret;
+
+ ret = p3h2x4x_configure_ldo(dev);
+ if (ret)
+ return ret;
+
+ ret = p3h2x4x_configure_pullup(dev);
+ if (ret)
+ return ret;
+
+ ret = p3h2x4x_configure_io_strength(dev);
+ if (ret)
+ return ret;
+
+ return p3h2x4x_configure_tp(dev);
+}
+
+static void p3h2x4x_get_target_port_dt_conf(struct device *dev,
+ const struct device_node *node)
+{
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = 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) {
+ p3h2x4x_i3c_hub->tp_bus[tp_port].of_node = dev_node;
+ p3h2x4x_i3c_hub->tp_bus[tp_port].tp_mask = P3H2x4x_SET_BIT(tp_port);
+ p3h2x4x_i3c_hub->tp_bus[tp_port].p3h2x4x_i3c_hub = p3h2x4x_i3c_hub;
+ p3h2x4x_i3c_hub->tp_bus[tp_port].tp_port = 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 >= P3H2x4x_TP_MAX_COUNT) {
+ dev_warn(dev, "Invalid target port index found in DT: %lli\n", id);
+ continue;
+ }
+
+ if (strcmp(tp_node->name, "i3c") == 0)
+ tp_config[id].mode = P3H2x4x_TP_MODE_I3C;
+
+ if (strcmp(tp_node->name, "i2c") == 0)
+ tp_config[id].mode = P3H2x4x_TP_MODE_I2C;
+
+ if (strcmp(tp_node->name, "smbus") == 0)
+ tp_config[id].mode = P3H2x4x_TP_MODE_SMBUS;
+
+ tp_config[id].pullup_en =
+ 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 = 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_config);
+}
+
+static void p3h2x4x_default_configuration(struct device *dev)
+{
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(dev);
+ int tp_count;
+
+ p3h2x4x_i3c_hub->hub_config.tp0145_pullup = P3H2x4x_TP_PULLUP_500R;
+ p3h2x4x_i3c_hub->hub_config.tp2367_pullup = P3H2x4x_TP_PULLUP_500R;
+ p3h2x4x_i3c_hub->hub_config.cp0_io_strength = P3H2x4x_IO_STRENGTH_20_OHM;
+ p3h2x4x_i3c_hub->hub_config.cp1_io_strength = P3H2x4x_IO_STRENGTH_20_OHM;
+ p3h2x4x_i3c_hub->hub_config.tp0145_io_strength = P3H2x4x_IO_STRENGTH_20_OHM;
+ p3h2x4x_i3c_hub->hub_config.tp2367_io_strength = P3H2x4x_IO_STRENGTH_20_OHM;
+
+ for (tp_count = 0; tp_count < P3H2x4x_TP_MAX_COUNT; ++tp_count)
+ p3h2x4x_i3c_hub->hub_config.tp_config[tp_count].mode = P3H2x4x_TP_MODE_I3C;
+}
+
+static int p3h2x4x_i3c_hub_probe(struct platform_device *pdev)
+{
+ struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent);
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub;
+ struct device *dev = &pdev->dev;
+ struct device_node *node;
+ int ret, i;
+
+ p3h2x4x_i3c_hub = devm_kzalloc(dev, sizeof(*p3h2x4x_i3c_hub), GFP_KERNEL);
+ if (!p3h2x4x_i3c_hub)
+ return -ENOMEM;
+
+ p3h2x4x_i3c_hub->regmap = p3h2x4x->regmap;
+ p3h2x4x_i3c_hub->dev = dev;
+
+ platform_set_drvdata(pdev, p3h2x4x_i3c_hub);
+ device_set_of_node_from_dev(dev, dev->parent);
+
+ p3h2x4x_default_configuration(dev);
+
+ ret = devm_mutex_init(dev, &p3h2x4x_i3c_hub->etx_mutex);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) {
+ ret = devm_mutex_init(dev, &p3h2x4x_i3c_hub->tp_bus[i].port_mutex);
+ if (ret)
+ return ret;
+ }
+
+ /* get hub node from DT */
+ node = dev->of_node;
+ 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);
+
+ /* Unlock access to protected registers */
+ ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE,
+ P3H2x4x_REGISTERS_UNLOCK_CODE);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to unlock HUB's protected registers\n");
+
+ ret = p3h2x4x_configure_hw(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to configure the HUB\n");
+
+ /* Register logic for native vertual I3C ports */
+ if (p3h2x4x->is_p3h2x4x_in_i3c) {
+ p3h2x4x_i3c_hub->i3cdev = p3h2x4x->i3cdev;
+ ret = p3h2x4x_tp_i3c_algo(p3h2x4x_i3c_hub);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register i3c bus\n");
+ }
+
+ /* Register logic for native SMBus ports */
+ ret = p3h2x4x_tp_smbus_algo(p3h2x4x_i3c_hub);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add i2c adapter\n");
+
+ /* Lock access to protected registers */
+ ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE,
+ P3H2x4x_REGISTERS_LOCK_CODE);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to lock HUB's protected registers\n");
+
+ return 0;
+}
+
+static void p3h2x4x_i3c_hub_remove(struct platform_device *pdev)
+{
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = platform_get_drvdata(pdev);
+ struct p3h2x4x_dev *p3h2x4x = dev_get_drvdata(pdev->dev.parent);
+ u8 i;
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) {
+ if (!p3h2x4x_i3c_hub->tp_bus[i].is_registered)
+ continue;
+
+ if (p3h2x4x_i3c_hub->hub_config.tp_config[i].mode == P3H2x4x_TP_MODE_SMBUS)
+ i2c_del_adapter(p3h2x4x_i3c_hub->tp_bus[i].tp_smbus_adapter);
+ else if (p3h2x4x_i3c_hub->hub_config.tp_config[i].mode == P3H2x4x_TP_MODE_I3C)
+ i3c_master_unregister(&p3h2x4x_i3c_hub->tp_bus[i]
+ .hub_controller.controller);
+ }
+
+ if (p3h2x4x->is_p3h2x4x_in_i3c) {
+ i3c_device_disable_ibi(p3h2x4x->i3cdev);
+ i3c_device_free_ibi(p3h2x4x->i3cdev);
+ }
+}
+
+static struct platform_driver p3h2x4x_i3c_hub_driver = {
+ .driver = {
+ .name = "p3h2x4x-i3c-hub",
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = p3h2x4x_i3c_hub_probe,
+ .remove = p3h2x4x_i3c_hub_remove,
+};
+module_platform_driver(p3h2x4x_i3c_hub_driver);
+
+MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>");
+MODULE_AUTHOR("Vikash Bansal <vikash.bansal@nxp.com>");
+MODULE_AUTHOR("Lakshay Piplani <lakshay.piplani@nxp.com>");
+MODULE_DESCRIPTION("P3H2x4x I3C HUB driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c
new file mode 100644
index 000000000000..d23a48c023e2
--- /dev/null
+++ b/drivers/i3c/hub/p3h2840_i3c_hub_i3c.c
@@ -0,0 +1,132 @@
+// 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 <linux/i3c/hub.h>
+#include <linux/mfd/p3h2840.h>
+#include <linux/regmap.h>
+
+#include "p3h2840_i3c_hub.h"
+
+static const struct i3c_ibi_setup p3h2x4x_ibireq = {
+ .handler = p3h2x4x_ibi_handler,
+ .max_payload_len = P3H2x4x_MAX_PAYLOAD_LEN,
+ .num_slots = P3H2x4x_NUM_SLOTS,
+};
+
+static inline struct tp_bus *
+p3h2x4x_bus_from_controller(struct i3c_master_controller *controller)
+{
+ struct i3c_hub_controller *hub_controller;
+
+ hub_controller = container_of(controller, struct i3c_hub_controller, controller);
+
+ return container_of(hub_controller, struct tp_bus, hub_controller);
+}
+
+static void p3h2x4x_hub_enable_port(struct i3c_master_controller *controller)
+{
+ struct tp_bus *bus = p3h2x4x_bus_from_controller(controller);
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = 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 *controller)
+{
+ struct tp_bus *bus = p3h2x4x_bus_from_controller(controller);
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = 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 = {
+ .enable_port = p3h2x4x_hub_enable_port,
+ .disable_port = p3h2x4x_hub_disable_port,
+};
+
+static void p3h2x4x_unregister_i3c_master(void *data)
+{
+ struct i3c_master_controller *controller = data;
+
+ i3c_master_unregister(controller);
+}
+
+/**
+ * p3h2x4x_tp_i3c_algo - register i3c master for target port who
+ * configured as i3c.
+ * @p3h2x4x_hub: p3h2x4x device structure.
+ * Return: 0 in case of success, negative error code on failur.
+ */
+int p3h2x4x_tp_i3c_algo(struct p3h2x4x_i3c_hub_dev *p3h2x4x_hub)
+{
+ struct i3c_master_controller *parent = i3c_dev_get_master(p3h2x4x_hub->i3cdev->desc);
+ u8 tp, ntwk_mask = 0;
+ int ret;
+
+ p3h2x4x_hub->hub = i3c_hub_init(parent,
+ &p3h2x4x_hub_ops,
+ p3h2x4x_hub->i3cdev);
+
+ if (IS_ERR(p3h2x4x_hub->hub))
+ return PTR_ERR(p3h2x4x_hub->hub);
+
+ for (tp = 0; tp < P3H2x4x_TP_MAX_COUNT; tp++) {
+ if (!p3h2x4x_hub->tp_bus[tp].of_node ||
+ p3h2x4x_hub->hub_config.tp_config[tp].mode != P3H2x4x_TP_MODE_I3C)
+ continue;
+
+ /* Assign DT node for this TP */
+ p3h2x4x_hub->dev->of_node = p3h2x4x_hub->tp_bus[tp].of_node;
+
+ struct i3c_hub_controller *hub_controller =
+ &p3h2x4x_hub->tp_bus[tp].hub_controller;
+ struct i3c_master_controller *controller = &hub_controller->controller;
+
+ hub_controller->parent = parent;
+ hub_controller->hub = p3h2x4x_hub->hub;
+
+ dev_set_drvdata(&controller->dev, hub_controller);
+
+ ret = i3c_master_register(controller,
+ p3h2x4x_hub->dev,
+ i3c_hub_master_ops(),
+ false);
+
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(p3h2x4x_hub->dev,
+ p3h2x4x_unregister_i3c_master,
+ controller);
+ if (ret)
+ return ret;
+
+ /* Perform DAA */
+ ret = i3c_master_do_daa(parent);
+ if (ret)
+ return ret;
+
+ ntwk_mask |= p3h2x4x_hub->tp_bus[tp].tp_mask;
+ p3h2x4x_hub->tp_bus[tp].is_registered = true;
+ p3h2x4x_hub->hub_config.tp_config[tp].always_enable = true;
+ }
+
+ ret = i3c_device_request_ibi(p3h2x4x_hub->i3cdev, &p3h2x4x_ibireq);
+ if (ret)
+ return ret;
+
+ ret = i3c_device_enable_ibi(p3h2x4x_hub->i3cdev);
+ if (ret)
+ return ret;
+
+ return regmap_write(p3h2x4x_hub->regmap, P3H2x4x_TP_NET_CON_CONF, ntwk_mask);
+}
diff --git a/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c
new file mode 100644
index 000000000000..ceee6e63fbda
--- /dev/null
+++ b/drivers/i3c/hub/p3h2840_i3c_hub_smbus.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2025 NXP
+ * This P3H2x4x driver file contain functions for SMBus/I2C virtual Bus creation and read/write.
+ */
+#include <linux/mfd/p3h2840.h>
+#include <linux/regmap.h>
+
+#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 void p3h2x4x_read_smbus_agent_rx_buf(struct i3c_device *i3cdev, enum p3h2x4x_rcv_buf rfbuf,
+ enum p3h2x4x_tp tp, bool is_of)
+{
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = dev_get_drvdata(&i3cdev->dev);
+ u8 slave_rx_buffer[P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE] = { 0 };
+ u8 target_buffer_page, flag_clear = 0x0f, temp, i;
+ u32 packet_len, slave_address, ret;
+
+ target_buffer_page = (((rfbuf) ? P3H2x4x_TARGET_BUFF_1_PAGE : P3H2x4x_TARGET_BUFF_0_PAGE)
+ + (P3H2x4x_NO_PAGE_PER_TP * tp));
+ ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, target_buffer_page);
+ if (ret)
+ goto ibi_err;
+
+ /* read buffer length */
+ ret = regmap_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_LENGTH, &packet_len);
+ if (ret)
+ goto ibi_err;
+
+ if (packet_len)
+ packet_len = packet_len - 1;
+
+ if (packet_len > P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE) {
+ dev_err(&i3cdev->dev, "Received message too big for p3h2x4x buffer\n");
+ return;
+ }
+
+ /* read slave address */
+ ret = regmap_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_ADDRESS, &slave_address);
+ if (ret)
+ goto ibi_err;
+
+ /* read data */
+ if (packet_len) {
+ ret = regmap_bulk_read(p3h2x4x_i3c_hub->regmap, P3H2x4x_TARGET_BUFF_DATA,
+ slave_rx_buffer, packet_len);
+ if (ret)
+ goto ibi_err;
+ }
+
+ if (is_of)
+ flag_clear = BUF_RECEIVED_FLAG_TF_MASK;
+ else
+ flag_clear = (((rfbuf == RCV_BUF_0) ? P3H2x4x_TARGET_BUF_0_RECEIVE :
+ P3H2x4x_TARGET_BUF_1_RECEIVE));
+
+ /* notify slave driver about received data */
+ if ((p3h2x4x_i3c_hub->tp_bus[tp].tp_smbus_client->addr & 0x7f) == (slave_address >> 1)) {
+ i2c_slave_event(p3h2x4x_i3c_hub->tp_bus[tp].tp_smbus_client,
+ I2C_SLAVE_WRITE_REQUESTED, (u8 *)&slave_address);
+ for (i = 0; i < packet_len; i++) {
+ temp = slave_rx_buffer[i];
+ i2c_slave_event(p3h2x4x_i3c_hub->tp_bus[tp].tp_smbus_client,
+ I2C_SLAVE_WRITE_RECEIVED, &temp);
+ }
+ i2c_slave_event(p3h2x4x_i3c_hub->tp_bus[tp].tp_smbus_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, flag_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)
+{
+ u32 target_port_status, payload_byte_one, payload_byte_two;
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub;
+ u32 ret, i;
+
+ payload_byte_one = (*(int *)payload->data);
+
+ if (!(payload_byte_one & P3H2x4x_SMBUS_AGENT_EVENT_FLAG_STATUS))
+ return;
+
+ p3h2x4x_i3c_hub = dev_get_drvdata(&i3cdev->dev);
+
+ if (!p3h2x4x_i3c_hub || !p3h2x4x_i3c_hub->regmap)
+ return;
+
+ payload_byte_two = (*(int *)(payload->data + 4));
+ guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex);
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) {
+ if (p3h2x4x_i3c_hub->tp_bus[i].is_registered && (payload_byte_two >> i) & 0x01) {
+ ret = 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);
+ return;
+ }
+
+ /* process data receive buffer */
+ switch (target_port_status & BUF_RECEIVED_FLAG_MASK) {
+ case P3H2x4x_TARGET_BUF_CA_TF:
+ break;
+ case P3H2x4x_TARGET_BUF_0_RECEIVE:
+ p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false);
+ break;
+ case P3H2x4x_TARGET_BUF_1_RECEIVE:
+ p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, false);
+ break;
+ case P3H2x4x_TARGET_BUF_0_1_RECEIVE:
+ p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false);
+ p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, false);
+ break;
+ case P3H2x4x_TARGET_BUF_OVRFL:
+ p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_0, i, false);
+ p3h2x4x_read_smbus_agent_rx_buf(i3cdev, RCV_BUF_1, i, true);
+ 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,
+ BUF_RECEIVED_FLAG_TF_MASK);
+ break;
+ }
+ }
+ }
+}
+
+static int p3h2x4x_read_smbus_transaction_status(struct p3h2x4x_i3c_hub_dev *hub,
+ u8 target_port_status,
+ u8 data_length)
+{
+ u32 status_read;
+ u8 status;
+ int ret;
+
+ mutex_unlock(&hub->etx_mutex);
+ fsleep(P3H2x4x_SMBUS_400kHz_TRANSFER_TIMEOUT(data_length));
+ mutex_lock(&hub->etx_mutex);
+
+ ret = regmap_read(hub->regmap, target_port_status, &status_read);
+ if (ret)
+ return ret;
+
+ status = (u8)status_read;
+
+ status = (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 writing a descriptor
+ * and a message to the p3h2x4x registers. Controller buffer page is determined 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,
+ u8 nxfers_i, u8 rw)
+{
+ u8 controller_buffer_page = P3H2x4x_CONTROLLER_BUFFER_PAGE + 4 * target_port;
+ u8 target_port_status = P3H2x4x_TP0_SMBUS_AGNT_STS + target_port;
+ u8 desc[P3H2x4x_SMBUS_DESCRIPTOR_SIZE] = { 0 };
+ u8 transaction_type = P3H2x4x_SMBUS_400kHz;
+ int write_length, read_length;
+ u8 addr = xfers[nxfers_i].addr;
+ u8 rw_address = 2 * addr;
+ int ret, ret2;
+
+ if (rw == 2) { /* write and read */
+ write_length = xfers[nxfers_i].len;
+ read_length = xfers[nxfers_i + 1].len;
+ } else if (rw == 1) {
+ rw_address |= P3H2x4x_SET_BIT(0);
+ write_length = 0;
+ read_length = xfers[nxfers_i].len;
+ } else {
+ write_length = xfers[nxfers_i].len;
+ read_length = 0;
+ }
+
+ desc[P3H2X4X_DESC_ADDR] = rw_address;
+ if (rw == 2)
+ desc[P3H2X4X_DESC_TYPE] = transaction_type | P3H2x4x_SET_BIT(0);
+ else
+ desc[P3H2X4X_DESC_TYPE] = transaction_type;
+ desc[P3H2X4X_DESC_WRITE_LEN] = write_length;
+ desc[P3H2X4X_DESC_READ_LEN] = read_length;
+
+ ret = regmap_write(p3h2x4x_i3c_hub->regmap, target_port_status,
+ P3H2x4x_TP_BUFFER_STATUS_MASK);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_PAGE_PTR, controller_buffer_page);
+
+ if (ret)
+ goto out;
+
+ ret = regmap_bulk_write(p3h2x4x_i3c_hub->regmap, P3H2x4x_CONTROLLER_AGENT_BUFF,
+ desc, P3H2x4x_SMBUS_DESCRIPTOR_SIZE);
+
+ if (ret)
+ goto out;
+
+ if (!(rw % 2)) {
+ ret = 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 = 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 = p3h2x4x_read_smbus_transaction_status(p3h2x4x_i3c_hub,
+ target_port_status,
+ (write_length + read_length));
+ if (ret)
+ goto out;
+
+ if (rw) {
+ if (rw == 2)
+ nxfers_i += 1;
+
+ ret = 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 = regmap_write(p3h2x4x_i3c_hub->regmap,
+ P3H2x4x_PAGE_PTR, 0x00);
+ if (!ret && ret2)
+ ret = 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 *msgs, int num)
+{
+ int ret_sum = 0, ret;
+ u8 msg_count, rw;
+
+ struct tp_bus *bus = i2c_get_adapdata(adap);
+ struct p3h2x4x_i3c_hub_dev *p3h2x4x_i3c_hub = bus->p3h2x4x_i3c_hub;
+
+ guard(mutex)(&p3h2x4x_i3c_hub->etx_mutex);
+ guard(mutex)(&bus->port_mutex);
+
+ for (msg_count = 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);
+ continue;
+ }
+
+ rw = (msgs[msg_count].flags & I2C_M_RD) ? 1 : 0;
+ if (!rw) {
+ /* If a read message is immediately followed by a write message to
+ * the same address, consider combining them into a single transaction.
+ */
+ if (msg_count + 1 < num &&
+ msgs[msg_count].addr == msgs[msg_count + 1].addr &&
+ (msgs[msg_count + 1].flags & I2C_M_RD)) {
+ rw = 2;
+ msg_count += 1;
+ ret_sum += 1;
+ }
+ }
+
+ ret = p3h2x4x_tp_i2c_xfer_msg(p3h2x4x_i3c_hub,
+ msgs,
+ bus->tp_port,
+ (rw == 2) ? (msg_count - 1) : msg_count,
+ rw);
+ if (ret)
+ return ret;
+
+ ret_sum++;
+ }
+ return ret_sum;
+}
+
+static u32 p3h2x4x_tp_smbus_funcs(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static int p3h2x4x_tp_i2c_reg_slave(struct i2c_client *slave)
+{
+ struct tp_bus *bus = i2c_get_adapdata(slave->adapter);
+
+ if (bus->tp_smbus_client)
+ return -EBUSY;
+
+ bus->tp_smbus_client = slave;
+
+ return 0;
+}
+
+static int p3h2x4x_tp_i2c_unreg_slave(struct i2c_client *slave)
+{
+ struct tp_bus *bus = i2c_get_adapdata(slave->adapter);
+
+ bus->tp_smbus_client = NULL;
+
+ return 0;
+}
+
+/*
+ * I2C algorithm Structure
+ */
+static struct i2c_algorithm p3h2x4x_tp_i2c_algorithm = {
+ .master_xfer = p3h2x4x_tp_i2c_xfer,
+ .reg_slave = p3h2x4x_tp_i2c_reg_slave,
+ .unreg_slave = p3h2x4x_tp_i2c_unreg_slave,
+ .functionality = p3h2x4x_tp_smbus_funcs,
+};
+
+/**
+ * p3h2x4x_tp_smbus_algo - add i2c adapter for target port who
+ * configured as SMBus.
+ * @hub: p3h2x4x device structure.
+ * Return: 0 in case of success, negative error code on failur.
+ */
+int p3h2x4x_tp_smbus_algo(struct p3h2x4x_i3c_hub_dev *hub)
+{
+ u8 tp, ibi_mask = 0;
+ int ret;
+
+ for (tp = 0; tp < P3H2x4x_TP_MAX_COUNT; tp++) {
+ if (!hub->tp_bus[tp].of_node ||
+ hub->hub_config.tp_config[tp].mode != P3H2x4x_TP_MODE_SMBUS)
+ continue;
+
+ /* Allocate adapter */
+ struct i2c_adapter *smbus_adapter =
+ devm_kzalloc(hub->dev, sizeof(*smbus_adapter), GFP_KERNEL);
+ if (!smbus_adapter)
+ return -ENOMEM;
+
+ /* Initialize adapter */
+ smbus_adapter->owner = THIS_MODULE;
+ smbus_adapter->class = I2C_CLASS_HWMON;
+ smbus_adapter->algo = &p3h2x4x_tp_i2c_algorithm;
+ smbus_adapter->dev.parent = hub->dev;
+ smbus_adapter->dev.of_node = 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 = i2c_add_adapter(smbus_adapter);
+ if (ret) {
+ devm_kfree(hub->dev, smbus_adapter);
+ return ret;
+ }
+
+ ibi_mask |= hub->tp_bus[tp].tp_mask;
+ hub->tp_bus[tp].is_registered = true;
+ hub->hub_config.tp_config[tp].ibi_en = true;
+ hub->tp_bus[tp].tp_smbus_adapter = 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 applications 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 write message
+ * from its downstream ports.
+ */
+ ret = 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)
+ return ret;
+
+ return regmap_write(hub->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, ibi_mask);
+}
diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
index 971d53349b6f..6188082599dd 100644
--- a/include/linux/i3c/device.h
+++ b/include/linux/i3c/device.h
@@ -85,6 +85,7 @@ struct i3c_xfer {
*/
enum i3c_dcr {
I3C_DCR_GENERIC_DEVICE = 0,
+ I3C_DCR_HUB = 194,
};
#define I3C_PID_MANUF_ID(pid) (((pid) & GENMASK_ULL(47, 33)) >> 33)
--
2.25.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v7 3/7] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support
2026-03-19 11:24 ` [PATCH v7 3/7] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Lakshay Piplani
@ 2026-03-22 23:35 ` Rob Herring
2026-03-23 4:38 ` [EXT] " Lakshay Piplani
0 siblings, 1 reply; 11+ messages in thread
From: Rob Herring @ 2026-03-22 23:35 UTC (permalink / raw)
To: Lakshay Piplani
Cc: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, conor+dt,
devicetree, broonie, lee, Frank.Li, lgirdwood, vikash.bansal,
priyanka.jain, aman.kumarpandey
On Thu, Mar 19, 2026 at 04:54:37PM +0530, Lakshay Piplani wrote:
> From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
>
> 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 <aman.kumarpandey@nxp.com>
> Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
>
> ---
> 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 regulator
> - Added Regulator supply node
>
> Changes in v2:
> - Fixed DT binding check warning
> - Revised logic for parsing DTS nodes
> ---
> ---
> .../devicetree/bindings/i3c/nxp,p3h2840.yaml | 303 ++++++++++++++++++
> MAINTAINERS | 8 +
> 2 files changed, 311 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
>
> diff --git a/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
> new file mode 100644
> index 000000000000..495e0e4c1ae1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
> @@ -0,0 +1,303 @@
> +# 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 <aman.kumarpandey@nxp.com>
> + - Vikash Bansal <vikash.bansal@nxp.com>
> +
> +description: |
> + P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) is a family of multiport I3C
> + hub devices that connect to:-
> + 1. A host CPU via I3C/I2C/SMBus bus on upstream side and connect to multiple
> + peripheral devices on the downstream side.
> + 2. Have two Controller Ports which can support either
> + I2C/SMBus or I3C buses and connect to a CPU, BMC or SOC.
> + 3. P3H2840/ P3H2841 are 8 port I3C hub with eight 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
> + unevaluatedProperties: false
> +
> + properties:
> + reg:
> + description:
> + The I3C HUB Target Port number.
> + maximum: 7
> +
> + '#address-cells':
> + const: 3
> +
> + '#size-cells':
> + const: 0
> +
> + nxp,pullup-enable:
> + type: boolean
> + description:
> + Enables the on-die pull-up for Target Port.
> +
> + required:
> + - reg
> + - "#address-cells"
> + - "#size-cells"
> +
> + "^(i2c|smbus)@[0-7]$":
> + type: object
> + unevaluatedProperties: false
> +
> + properties:
> + reg:
> + description:
> + The I3C HUB Target Port number.
> + maximum: 7
> +
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 0
> +
> + nxp,pullup-enable:
> + type: boolean
> + description:
> + Enables the on-die pull-up for Target Port.
> +
> + required:
> + - reg
> + - "#address-cells"
> + - "#size-cells"
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + i3c {
> + #address-cells = <3>;
> + #size-cells = <0>;
> +
> + hub@70,236153000c2 {
> + reg = <0x70 0x236 0x3000c2>;
Once again, you must have a compatible. "The device is discoverable" is
not an argument. If it is discoverable, then don't put the device in DT
in the first place.
Rob
^ permalink raw reply [flat|nested] 11+ messages in thread
* RE: [EXT] Re: [PATCH v7 3/7] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support
2026-03-22 23:35 ` Rob Herring
@ 2026-03-23 4:38 ` Lakshay Piplani
0 siblings, 0 replies; 11+ messages in thread
From: Lakshay Piplani @ 2026-03-23 4:38 UTC (permalink / raw)
To: Rob Herring
Cc: linux-kernel@vger.kernel.org, linux-i3c@lists.infradead.org,
alexandre.belloni@bootlin.com, krzk+dt@kernel.org,
conor+dt@kernel.org, devicetree@vger.kernel.org,
broonie@kernel.org, lee@kernel.org, Frank Li, lgirdwood@gmail.com,
Vikash Bansal, Priyanka Jain, Aman Kumar Pandey
> -----Original Message-----
> From: Rob Herring <robh@kernel.org>
> Sent: Monday, March 23, 2026 5:05 AM
> To: Lakshay Piplani <lakshay.piplani@nxp.com>
> Cc: linux-kernel@vger.kernel.org; linux-i3c@lists.infradead.org;
> alexandre.belloni@bootlin.com; krzk+dt@kernel.org; conor+dt@kernel.org;
> devicetree@vger.kernel.org; broonie@kernel.org; lee@kernel.org; Frank Li
> <frank.li@nxp.com>; lgirdwood@gmail.com; Vikash Bansal
> <vikash.bansal@nxp.com>; Priyanka Jain <priyanka.jain@nxp.com>; Aman
> Kumar Pandey <aman.kumarpandey@nxp.com>
> Subject: [EXT] Re: [PATCH v7 3/7] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub
> support
>
> Caution: This is an external email. Please take care when clicking links or
> opening attachments. When in doubt, report the message using the 'Report
> this email' button
>
>
> On Thu, Mar 19, 2026 at 04:54:37PM +0530, Lakshay Piplani wrote:
> > From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
> >
> > 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 <aman.kumarpandey@nxp.com>
> > Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
> >
> > ---
> > 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
> > regulator
> > - Added Regulator supply node
> >
> > Changes in v2:
> > - Fixed DT binding check warning
> > - Revised logic for parsing DTS nodes
> > ---
> > ---
> > .../devicetree/bindings/i3c/nxp,p3h2840.yaml | 303 ++++++++++++++++++
> > MAINTAINERS | 8 +
> > 2 files changed, 311 insertions(+)
> > create mode 100644
> > Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
> > b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
> > new file mode 100644
> > index 000000000000..495e0e4c1ae1
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
> > @@ -0,0 +1,303 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) # Copyright
> > +2025 NXP %YAML 1.2
> > +---
> > +$id:
> > +https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevi
> >
> +cetree.org%2Fschemas%2Fi3c%2Fnxp%2Cp3h2840.yaml%23&data=05%7C02
> %7Clak
> >
> +shay.piplani%40nxp.com%7C41cb77ab9e1a4bd9525208de886bab1b%7C686
> ea1d3b
> >
> +c2b4c6fa92cd99c5c301635%7C0%7C0%7C639098193138487662%7CUnknow
> n%7CTWFp
> >
> +bGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4
> zMiIsI
> >
> +kFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=ofJ5OTL%2Fn
> kxuq1Ky
> > +bU5RTJtwo4yMNFHvObf2iTFnaSg%3D&reserved=0
> > +$schema:
> > +https://eur01.safelinks.protection.outlook.com/?url=http%3A%2F%2Fdevi
> > +cetree.org%2Fmeta-
> schemas%2Fcore.yaml%23&data=05%7C02%7Clakshay.pipla
> >
> +ni%40nxp.com%7C41cb77ab9e1a4bd9525208de886bab1b%7C686ea1d3bc2
> b4c6fa92
> >
> +cd99c5c301635%7C0%7C0%7C639098193138518717%7CUnknown%7CTWFp
> bGZsb3d8ey
> >
> +JFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoi
> TWF
> >
> +pbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=Oy7On%2FMYpy93DRw
> O243lEvJ%2F
> > +oCfusLv0j2e0S93cNnk%3D&reserved=0
> > +
> > +title: NXP P3H2X4X I3C HUB
> > +
> > +maintainers:
> > + - Aman Kumar Pandey <aman.kumarpandey@nxp.com>
> > + - Vikash Bansal <vikash.bansal@nxp.com>
> > +
> > +description: |
> > + P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) is a family of multiport
> > +I3C
> > + hub devices that connect to:-
> > + 1. A host CPU via I3C/I2C/SMBus bus on upstream side and connect to
> multiple
> > + peripheral devices on the downstream side.
> > + 2. Have two Controller Ports which can support either
> > + I2C/SMBus or I3C buses and connect to a CPU, BMC or SOC.
> > + 3. P3H2840/ P3H2841 are 8 port I3C hub with eight 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
> > + unevaluatedProperties: false
> > +
> > + properties:
> > + reg:
> > + description:
> > + The I3C HUB Target Port number.
> > + maximum: 7
> > +
> > + '#address-cells':
> > + const: 3
> > +
> > + '#size-cells':
> > + const: 0
> > +
> > + nxp,pullup-enable:
> > + type: boolean
> > + description:
> > + Enables the on-die pull-up for Target Port.
> > +
> > + required:
> > + - reg
> > + - "#address-cells"
> > + - "#size-cells"
> > +
> > + "^(i2c|smbus)@[0-7]$":
> > + type: object
> > + unevaluatedProperties: false
> > +
> > + properties:
> > + reg:
> > + description:
> > + The I3C HUB Target Port number.
> > + maximum: 7
> > +
> > + '#address-cells':
> > + const: 1
> > +
> > + '#size-cells':
> > + const: 0
> > +
> > + nxp,pullup-enable:
> > + type: boolean
> > + description:
> > + Enables the on-die pull-up for Target Port.
> > +
> > + required:
> > + - reg
> > + - "#address-cells"
> > + - "#size-cells"
> > +
> > +unevaluatedProperties: false
> > +
> > +examples:
> > + - |
> > + i3c {
> > + #address-cells = <3>;
> > + #size-cells = <0>;
> > +
> > + hub@70,236153000c2 {
> > + reg = <0x70 0x236 0x3000c2>;
>
> Once again, you must have a compatible. "The device is discoverable" is not
> an argument. If it is discoverable, then don't put the device in DT in the first
> place.
>
> Rob
Hi Rob,
Thanks for pointing it out.
I've added the missing compatible in i3c example; will include it in v8.
Regards
Lakshay
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v7 4/7] mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator
2026-03-19 11:24 ` [PATCH v7 4/7] mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator Lakshay Piplani
@ 2026-03-26 11:58 ` Lee Jones
0 siblings, 0 replies; 11+ messages in thread
From: Lee Jones @ 2026-03-26 11:58 UTC (permalink / raw)
To: Lakshay Piplani
Cc: linux-kernel, linux-i3c, alexandre.belloni, krzk+dt, robh,
conor+dt, devicetree, broonie, Frank.Li, lgirdwood, vikash.bansal,
priyanka.jain, aman.kumarpandey
On Thu, 19 Mar 2026, Lakshay Piplani wrote:
> From: Aman Kumar Pandey <aman.kumarpandey@nxp.com>
>
> 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 <aman.kumarpandey@nxp.com>
> Signed-off-by: Vikash Bansal <vikash.bansal@nxp.com>
>
> ---
> 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
> ---
> ---
One is enough.
> MAINTAINERS | 2 +
> drivers/mfd/Kconfig | 13 ++++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/p3h2840.c | 125 ++++++++++++++++++++++++++++++++++++
> include/linux/mfd/p3h2840.h | 27 ++++++++
> 5 files changed, 168 insertions(+)
> create mode 100644 drivers/mfd/p3h2840.c
> create mode 100644 include/linux/mfd/p3h2840.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c72976dbdf31..4f67fe64c833 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -19101,6 +19101,8 @@ L: linux-kernel@vger.kernel.org
> L: linux-i3c-owner@lists.infradead.org
> S: Maintained
> F: Documentation/devicetree/bindings/i3c/nxp,p3h2840.yaml
> +F: drivers/mfd/p3h2840.c
> +F: include/linux/mfd/p3h2840.h
>
> NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER
> M: Woodrow Douglass <wdouglass@carnegierobotics.com>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 7192c9d1d268..14f4736683a1 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.
>
> +config MFD_P3H2X4X
MFD_NXP_P3H2X4X
> + tristate "NXP P3H2X4X I3C Hub Device"
> + depends on I3C_OR_I2C
> + select MFD_CORE
> + select REGMAP_I3C
> + select REGMAP_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=y && OF
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index e75e8045c28a..aaadf50fedf4 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -122,6 +122,7 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
> obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o
> obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
>
> +obj-$(CONFIG_MFD_P3H2X4X) += p3h2840.o
There is a mismatch in the naming here.
Are we expressing the family or the specific device?
> obj-$(CONFIG_MFD_PF1550) += pf1550.o
>
> obj-$(CONFIG_MFD_NCT6694) += nct6694.o
> diff --git a/drivers/mfd/p3h2840.c b/drivers/mfd/p3h2840.c
> new file mode 100644
> index 000000000000..65090a09f7c6
> --- /dev/null
> +++ b/drivers/mfd/p3h2840.c
> @@ -0,0 +1,125 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2025 NXP
Needs updating.
Author(s)?
> + * P3H2x4x i3c hub and regulator device.
> + */
Could this comment be a little more descriptive? Something like:
"Core driver for the NXP P3H2x4x family of I3C hubs."
Drop the regular part.
> +#include <linux/i3c/master.h>
> +#include <linux/i2c.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/p3h2840.h>
> +#include <linux/regmap.h>
Please sort your `#include` directives alphabetically.
> +static const struct mfd_cell p3h2x4x_devs[] = {
> + {
> + .name = "p3h2x4x-regulator",
> + },
> + {
> + .name = "p3h2x4x-i3c-hub",
> + },
> +};
MFD_CELL_NAME()
> +static const struct regmap_config p3h2x4x_regmap_config = {
> + .reg_bits = P3H2x4x_REG_BITS,
> + .val_bits = P3H2x4x_VAL_BITS,
> + .max_register = 0xFF,
> +};
> +
> +static int p3h2x4x_device_probe_i3c(struct i3c_device *i3cdev)
> +{
> + struct p3h2x4x_dev *p3h2x4x;
> + int ret;
> +
> + p3h2x4x = devm_kzalloc(&i3cdev->dev, sizeof(*p3h2x4x), GFP_KERNEL);
> + if (!p3h2x4x)
> + return -ENOMEM;
> +
> + i3cdev_set_drvdata(i3cdev, p3h2x4x);
No point in setting this until after its populated.
> + p3h2x4x->regmap = devm_regmap_init_i3c(i3cdev, &p3h2x4x_regmap_config);
> + if (IS_ERR(p3h2x4x->regmap))
> + return dev_err_probe(&i3cdev->dev, PTR_ERR(p3h2x4x->regmap),
> + "Failed to register I3C HUB regmap\n");
This error message appears to be a copy-paste from the I2C probe function.
Perhaps a more generic message like "Failed to initialise regmap" would be
more appropriate for both contexts.
> +
> + p3h2x4x->is_p3h2x4x_in_i3c = true;
This is superfluous. Just check `p3h2x4x->i3cdev`.
> + p3h2x4x->i3cdev = i3cdev;
> +
> + ret = devm_mfd_add_devices(&i3cdev->dev, PLATFORM_DEVID_NONE,
> + p3h2x4x_devs, ARRAY_SIZE(p3h2x4x_devs),
> + NULL, 0, NULL);
> + if (ret)
> + return dev_err_probe(&i3cdev->dev, ret, "Failed to add sub devices\n");
> +
> + return 0;
> +}
> +
> +static int p3h2x4x_device_probe_i2c(struct i2c_client *client)
> +{
> + struct p3h2x4x_dev *p3h2x4x;
> + int ret;
> +
> + p3h2x4x = devm_kzalloc(&client->dev, sizeof(*p3h2x4x), GFP_KERNEL);
> + if (!p3h2x4x)
> + return -ENOMEM;
> +
> + i2c_set_clientdata(client, p3h2x4x);
As above.
> + p3h2x4x->regmap = devm_regmap_init_i2c(client, &p3h2x4x_regmap_config);
> + if (IS_ERR(p3h2x4x->regmap))
> + return dev_err_probe(&client->dev, PTR_ERR(p3h2x4x->regmap),
> + "Failed to register I3C HUB regmap\n");
I2C
> +
> + p3h2x4x->is_p3h2x4x_in_i3c = false;
As above.
> + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
> + 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;
> +}
These two probe functions, `p3h2x4x_device_probe_i3c` and
`p3h2x4x_device_probe_i2c`, are almost identical. Could they be refactored
into a single common probe function to reduce code duplication?
The bus-specific wrappers could handle the regmap initialisation and then
call a shared `p3h2x4x_probe(struct device *dev, struct regmap *regmap)`
function.
> +
> +/* p3h2x4x ids (i3c) */
> +static const struct i3c_device_id p3h2x4x_i3c_ids[] = {
> + I3C_CLASS(I3C_DCR_HUB, NULL),
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(i3c, p3h2x4x_i3c_ids);
> +
> +/* p3h2x4x ids (i2c) */
> +static const struct i2c_device_id p3h2x4x_i2c_id_table[] = {
> + { "nxp-i3c-hub" },
This is an MFD core driver, not just a hub driver. Perhaps a more suitable
name would be "p3h2x4x"?
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(i2c, p3h2x4x_i2c_id_table);
> +
> +static const struct of_device_id p3h2x4x_i2c_of_match[] = {
> + { .compatible = "nxp,p3h2840", },
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, p3h2x4x_i2c_of_match);
This should be adjacent to the struct above.
> +static struct i3c_driver p3h2x4x_i3c = {
> + .driver = {
> + .name = "p3h2x4x_i3c_drv",
"_drv" should be removed.
> + },
> + .probe = p3h2x4x_device_probe_i3c,
> + .id_table = p3h2x4x_i3c_ids,
> +};
> +
> +static struct i2c_driver p3h2x4x_i2c = {
> + .driver = {
> + .name = "p3h2x4x_i2c_drv",
As above.
> + .of_match_table = p3h2x4x_i2c_of_match,
> + },
> + .probe = p3h2x4x_device_probe_i2c,
> + .id_table = p3h2x4x_i2c_id_table,
> +};
> +
> +module_i3c_i2c_driver(p3h2x4x_i3c, &p3h2x4x_i2c);
> +
> +MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>");
> +MODULE_AUTHOR("Vikash Bansal <vikash.bansal@nxp.com>");
Stick to having one of these and put two in the header comment.
> +MODULE_DESCRIPTION("P3H2x4x I3C HUB multi function driver");
This could be more precise. How about:
"NXP P3H2x4x I3C Hub core driver"
Drop all mentions of MFD. It's not a real thing.
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/p3h2840.h b/include/linux/mfd/p3h2840.h
> new file mode 100644
> index 000000000000..cba6fa516d1e
> --- /dev/null
> +++ b/include/linux/mfd/p3h2840.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright 2025 NXP
> + * This header file contain private Reg address and its bit mapping etc.
> + */
The comment is not a complete sentence and states the obvious. Please consider
something more descriptive, for example:
"Register definitions for the NXP P3H2x4x I3C Hub."
> +
> +#ifndef _LINUX_MFD_P3H2840_H
> +#define _LINUX_MFD_P3H2840_H
If the filenames are changed to `p3h2x4x.h` as suggested earlier, this
header guard will need to be updated to `_LINUX_MFD_P3H2X4X_H`.
> +
> +#include <linux/types.h>
> +
> +/* 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
> +
> +/* Reg config for Regmap */
> +#define P3H2x4x_REG_BITS 8
> +#define P3H2x4x_VAL_BITS 8
> +
> +struct p3h2x4x_dev {
> + struct i3c_device *i3cdev;
> + struct regmap *regmap;
> + bool is_p3h2x4x_in_i3c;
> +};
Why are we storing i3c_device, but not i2c_device_id?
--
Lee Jones [李琼斯]
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-03-26 11:58 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-19 11:24 [PATCH v7 0/7] Add support for NXP P3H2x4x I3C hub driver Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 1/7] i3c: master: Expose the APIs to support I3C hub Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 2/7] i3c: master: Add " Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 3/7] dt-bindings: i3c: Add NXP P3H2x4x i3c-hub support Lakshay Piplani
2026-03-22 23:35 ` Rob Herring
2026-03-23 4:38 ` [EXT] " Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 4/7] mfd: p3h2x4x: Add driver for NXP P3H2x4x i3c hub and on-die regulator Lakshay Piplani
2026-03-26 11:58 ` Lee Jones
2026-03-19 11:24 ` [PATCH v7 5/7] regulator: p3h2x4x: Add driver for on-die regulators in NXP P3H2x4x i3c hub Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 6/7] i3c: hub: Add support for the I3C interface in the I3C hub Lakshay Piplani
2026-03-19 11:24 ` [PATCH v7 7/7] i3c: hub: p3h2x4x: Add support for NXP P3H2x4x I3C hub functionality Lakshay Piplani
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox