[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250212132227.1348374-2-aman.kumarpandey@nxp.com>
Date: Wed, 12 Feb 2025 15:22:27 +0200
From: Aman Kumar Pandey <aman.kumarpandey@....com>
To: linux-kernel@...r.kernel.org,
linux-i3c@...ts.infradead.org,
alexandre.belloni@...tlin.com,
krzk+dt@...nel.org,
robh@...nel.org,
conor+dt@...nel.org,
devicetree@...r.kernel.org
Cc: vikash.bansal@....com,
priyanka.jain@....com,
shashank.rebbapragada@....com,
Frank.Li@....com,
Aman Kumar Pandey <aman.kumarpandey@....com>
Subject: [PATCH 2/2] drivers: i3c: Add driver for NXP P3H2x4x i3c-hub device
P3H2x4x (P3H2440/P3H2441/P3H2840/P3H2841) is multiport I3C hub devices.
It connects to a host CPU via I3C/I2C/SMBus bus on one side and to multiple
peripheral devices on the other side.
P3H2840/ P3H2841 have eight I3C/I2C Target Port.
P3H2440/ P3H2441 have four I3C/I2C Target Port.
This driver can support
1. i3c/i2c communication between host and hub
2. i3c/i2c transparent mode between host and downstream devices.
3. Target ports can be configured as I2C/SMBus mode or I3C.
4. MCTP devices
5. In-band interrupts
Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@....com>
Signed-off-by: Vikash Bansal <vikash.bansal@....com>
---
MAINTAINERS | 1 +
drivers/i3c/Kconfig | 1 +
drivers/i3c/Makefile | 1 +
drivers/i3c/p3h2x4x/Kconfig | 11 +
drivers/i3c/p3h2x4x/Makefile | 4 +
drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub.h | 454 +++++++++
drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_common.c | 941 +++++++++++++++++++
drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_i3c.c | 353 +++++++
drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_smbus.c | 719 ++++++++++++++
include/linux/i3c/device.h | 1 +
10 files changed, 2486 insertions(+)
create mode 100644 drivers/i3c/p3h2x4x/Kconfig
create mode 100644 drivers/i3c/p3h2x4x/Makefile
create mode 100644 drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub.h
create mode 100644 drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_common.c
create mode 100644 drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_i3c.c
create mode 100644 drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_smbus.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 20aa3e987ac5..8e4ec4e3656e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17173,6 +17173,7 @@ M: Aman Kumar Pandey <aman.kumarpandey@....com>
L: linux-kernel@...r.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/i3c/p3h2x4x_i3c_hub.yaml
+F: i3c/p3h2x4x/*
NZXT-KRAKEN2 HARDWARE MONITORING DRIVER
M: Jonas Malaco <jonas@...tocubo.io>
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index 30a441506f61..3160437cc8e1 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -21,4 +21,5 @@ menuconfig I3C
if I3C
source "drivers/i3c/master/Kconfig"
+source "drivers/i3c/p3h2x4x/Kconfig"
endif # I3C
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index 11982efbc6d9..696747cc7f18 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) += p3h2x4x/
diff --git a/drivers/i3c/p3h2x4x/Kconfig b/drivers/i3c/p3h2x4x/Kconfig
new file mode 100644
index 000000000000..b8b18342b065
--- /dev/null
+++ b/drivers/i3c/p3h2x4x/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2024-2025 NXP
+config P3H2X4X_I3C_HUB
+ tristate "P3H2X4X I3C HUB support"
+ depends on I3C
+ select REGMAP_I3C
+ help
+ This enables support for NXP P3H244x/P3H284x I3C HUB. Say Y or M here to use
+ I3C HUB driver to configure I3C HUB device.
+ I3C HUB driver's probe will trigger when I3C device with DCR
+ equals to 0xC2 (HUB device) is detected on the bus.
diff --git a/drivers/i3c/p3h2x4x/Makefile b/drivers/i3c/p3h2x4x/Makefile
new file mode 100644
index 000000000000..214a3eeb62f2
--- /dev/null
+++ b/drivers/i3c/p3h2x4x/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2024-2025 NXP
+p3h2x4x_i3c_hub-y := p3h2x4x_i3c_hub_common.o p3h2x4x_i3c_hub_i3c.o p3h2x4x_i3c_hub_smbus.o
+obj-$(CONFIG_P3H2X4X_I3C_HUB) += p3h2x4x_i3c_hub.o
diff --git a/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub.h b/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub.h
new file mode 100644
index 000000000000..e8c627019556
--- /dev/null
+++ b/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub.h
@@ -0,0 +1,454 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2024-2025 NXP
+ * This header file contain private device structure definition, Reg address and its bit
+ * mapping etc.
+ */
+
+#ifndef P3H2X4X_I3C_HUB_H
+#define P3H2X4X_I3C_HUB_H
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
+#include <linux/mutex.h>
+#include <linux/rwsem.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/of.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_DEV_REG_PROTECTION_CODE 0x10
+#define P3H2x4x_REGISTERS_LOCK_CODE 0x00
+#define P3H2x4x_REGISTERS_UNLOCK_CODE 0x69
+#define P3H2x4x_CP1_REGISTERS_UNLOCK_CODE 0x6A
+
+#define P3H2x4x_CP_CONF 0x11
+#define P3H2x4x_TP_ENABLE 0x12
+#define P3H2x4x_TPn_ENABLE(n) BIT(n)
+
+#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) \
+ (((x) << 0) & P3H2x4x_TP0145_IO_STRENGTH_MASK)
+#define P3H2x4x_TP2367_IO_STRENGTH_MASK GENMASK(3, 2)
+#define P3H2x4x_TP2367_IO_STRENGTH(x) \
+ (((x) << 2) & P3H2x4x_TP2367_IO_STRENGTH_MASK)
+#define P3H2x4x_CP0_IO_STRENGTH_MASK GENMASK(5, 4)
+#define P3H2x4x_CP0_IO_STRENGTH(x) \
+ (((x) << 4) & P3H2x4x_CP0_IO_STRENGTH_MASK)
+#define P3H2x4x_CP1_IO_STRENGTH_MASK GENMASK(7, 6)
+#define P3H2x4x_CP1_IO_STRENGTH(x) \
+ (((x) << 6) & P3H2x4x_CP1_IO_STRENGTH_MASK)
+
+#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) \
+ (((x) << 0) & P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK)
+#define P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK GENMASK(3, 2)
+#define P3H2x4x_CP1_VCCIO_LDO_VOLTAGE(x) \
+ (((x) << 2) & P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK)
+#define P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK GENMASK(5, 4)
+#define P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE(x) \
+ (((x) << 4) & P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK)
+#define P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK GENMASK(7, 6)
+#define P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE(x) \
+ (((x) << 6) & P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK)
+
+#define P3H2x4x_TP_IO_MODE_CONF 0x17
+#define P3H2x4x_TP_SMBUS_AGNT_EN 0x18
+#define P3H2x4x_TPn_SMBUS_MODE_EN(n) BIT(n)
+#define P3H2x4x_TPn_I2C_MODE_EN(n) BIT(n)
+
+#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_TP0145_PULLUP_CONF_MASK GENMASK(7, 6)
+#define P3H2x4x_TP0145_PULLUP_CONF(x) \
+ (((x) << 6) & P3H2x4x_TP0145_PULLUP_CONF_MASK)
+#define P3H2x4x_TP2367_PULLUP_CONF_MASK GENMASK(5, 4)
+#define P3H2x4x_TP2367_PULLUP_CONF(x) \
+ (((x) << 4) & P3H2x4x_TP2367_PULLUP_CONF_MASK)
+
+#define P3H2x4x_CP_IBI_CONF 0x1A
+
+#define P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG 0x1B
+#define P3H2x4x_TPn_IBI_EN(n) BIT(n)
+#define P3H2x4x_ALL_TP_IBI_EN 0xFF
+#define P3H2x4x_ALL_TP_IBI_DIS 0x00
+
+#define P3H2x4x_IBI_MDB_CUSTOM 0x1C
+#define P3H2x4x_JEDEC_CONTEXT_ID 0x1D
+#define P3H2x4x_TP_GPIO_MODE_EN 0x1E
+#define P3H2x4x_TPn_GPIO_MODE_EN(n) BIT(n)
+
+/* 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_TPn_NET_CON(n) BIT(n)
+
+#define P3H2x4x_TP_PULLUP_EN 0x53
+#define P3H2x4x_TPn_PULLUP_EN(n) BIT(n)
+
+#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_DT_LDO_VOLT_NOT_SET 0x04
+
+#define P3H2x4x_LDO_DISABLED 0x00
+#define P3H2x4x_LDO_ENABLED 0x01
+#define P3H2x4x_DT_LDO_NOT_DEFINED 0x02
+
+#define P3H2x4x_IBI_DISABLED 0x00
+#define P3H2x4x_IBI_ENABLED 0x01
+#define P3H2x4x_IBI_NOT_DEFINED 0x02
+
+/* 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_NOT_SET 0x04
+
+#define P3H2x4x_TP_PULLUP_DISABLED 0x00
+#define P3H2x4x_TP_PULLUP_ENABLED 0x01
+#define P3H2x4x_TP_PULLUP_NOT_DEFINED 0x02
+
+#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_IO_STRENGTH_NOT_SET 0x04
+
+#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 P3H2x4x_TP_MODE_NOT_SET 0x04
+
+#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_XFER_SUCCESS 0x01
+#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 10
+
+#define P3H2x4x_HUB_ID 0
+
+/* Reg config for Regmap */
+#define P3H2x4x_REG_BITS 8
+#define P3H2x4x_VAL_BITS 8
+
+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 p3h2x4x_setting {
+ const char *const name;
+ const u8 value;
+};
+
+/* IBI enable/disable settings */
+static const struct p3h2x4x_setting ibi_en_settings[] = {
+ { "disabled", P3H2x4x_IBI_DISABLED },
+ { "enabled", P3H2x4x_IBI_ENABLED },
+};
+
+/* LDO enable/disable settings */
+static const struct p3h2x4x_setting ldo_en_settings[] = {
+ { "disabled", P3H2x4x_LDO_DISABLED },
+ { "enabled", P3H2x4x_LDO_ENABLED },
+};
+
+/* LDO voltage settings */
+static const struct p3h2x4x_setting ldo_volt_settings[] = {
+ { "1.0V", P3H2x4x_LDO_VOLT_1_0V },
+ { "1.1V", P3H2x4x_LDO_VOLT_1_1V },
+ { "1.2V", P3H2x4x_LDO_VOLT_1_2V },
+ { "1.8V", P3H2x4x_LDO_VOLT_1_8V },
+};
+
+/* target port pull-up settings */
+static const struct p3h2x4x_setting pullup_settings[] = {
+ { "250R", P3H2x4x_TP_PULLUP_250R },
+ { "500R", P3H2x4x_TP_PULLUP_500R },
+ { "1000R", P3H2x4x_TP_PULLUP_1000R },
+ { "2000R", P3H2x4x_TP_PULLUP_2000R },
+};
+
+/* target port mode settings */
+static const struct p3h2x4x_setting tp_mode_settings[] = {
+ { "i3c", P3H2x4x_TP_MODE_I3C },
+ { "smbus", P3H2x4x_TP_MODE_SMBUS },
+ { "gpio", P3H2x4x_TP_MODE_GPIO },
+ { "i2c", P3H2x4x_TP_MODE_I2C },
+};
+
+/* pull-up enable/disable settings */
+static const struct p3h2x4x_setting tp_pullup_settings[] = {
+ { "disabled", P3H2x4x_TP_PULLUP_DISABLED },
+ { "enabled", P3H2x4x_TP_PULLUP_ENABLED },
+};
+
+/* IO strenght settings */
+static const struct p3h2x4x_setting io_strength_settings[] = {
+ { "20Ohms", P3H2x4x_IO_STRENGTH_20_OHM },
+ { "30Ohms", P3H2x4x_IO_STRENGTH_30_OHM },
+ { "40Ohms", P3H2x4x_IO_STRENGTH_40_OHM },
+ { "50Ohms", P3H2x4x_IO_STRENGTH_50_OHM },
+};
+
+struct tp_setting {
+ u8 mode;
+ u8 pullup_en;
+ u8 ibi_en;
+ bool always_enable;
+};
+
+/*
+ * struct svc_i3c_i2c_dev_data - Device specific data
+ * @index: Index in the master tables corresponding to this device
+ * @ibi: IBI slot index in the master structure
+ * @ibi_pool: IBI pool associated to this device
+ */
+struct dt_settings {
+ u8 cp0_ldo_en;
+ u8 cp1_ldo_en;
+ u8 tp0145_ldo_en;
+ u8 tp2367_ldo_en;
+ u8 cp0_ldo_volt;
+ u8 cp1_ldo_volt;
+ u8 tp0145_ldo_volt;
+ u8 tp2367_ldo_volt;
+ u8 tp0145_pullup;
+ u8 tp2367_pullup;
+ u8 cp0_io_strength;
+ u8 cp1_io_strength;
+ u8 tp0145_io_strength;
+ u8 tp2367_io_strength;
+ struct tp_setting tp[P3H2x4x_TP_MAX_COUNT];
+};
+
+struct smbus_device {
+ struct i2c_client *client;
+ const char *compatible;
+ int addr;
+ struct list_head list;
+ struct device_node *tp_device_dt_node;
+};
+
+struct tp_bus {
+ bool dt_available; /* bus configuration is available in DT. */
+ bool is_registered; /* bus was registered in the framework. */
+ u8 tp_mask;
+ u8 tp_port;
+ u8 local_dev_list[P3H2x4x_TP_LOCAL_DEV];
+ u8 local_dev_count;
+ struct i3c_master_controller i3c_port_controller;
+ struct i2c_adapter smbus_port_adapter;
+ struct device_node *of_node;
+ struct p3h2x4x *priv;
+ struct list_head tp_device_entry;
+ struct mutex port_mutex;
+};
+
+struct p3h2x4x {
+ struct i3c_device *i3cdev;
+ struct i2c_client *i2cdev;
+ struct i3c_master_controller *driving_master;
+ struct regmap *regmap;
+ struct mutex etx_mutex;
+ struct dt_settings settings;
+ struct tp_bus tp_bus[P3H2x4x_TP_MAX_COUNT];
+ /* Offset for reading HUB's register. */
+ u8 tp_ibi_mask;
+ u8 tp_i3c_bus_mask;
+ u8 tp_always_enable_mask;
+ u8 is_slave_reg;
+ bool is_p3h2x4x_in_i3c;
+};
+
+/*
+ * p3h2x4x_tp_add_downstream_device - prove downstream devices for target port who
+ * configured as SMBus.
+ * @priv: p3h2x4x device structure.
+ * Return: 0 in case of success, a negative EINVAL code if the error.
+ */
+int p3h2x4x_tp_add_downstream_device(struct p3h2x4x *priv);
+
+/*
+ * p3h2x4x_tp_smbus_algo - add i2c adapter for target port who
+ * 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 *priv, int tp);
+
+int p3h2x4x_tp_i3c_algo(struct p3h2x4x *priv, int tp);
+
+/*
+ * 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 /* P3H2X4X_I3C_HUB_H */
diff --git a/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_common.c b/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_common.c
new file mode 100644
index 000000000000..74b86a668f12
--- /dev/null
+++ b/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_common.c
@@ -0,0 +1,941 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024-2025 NXP
+ * This P3H2x4x driver file implements functions for Hub probe and DT parsing.
+ */
+
+#include "p3h2x4x_i3c_hub.h"
+
+struct i3c_ibi_setup p3h2x4x_ibireq = {
+ .handler = p3h2x4x_ibi_handler,
+ .max_payload_len = P3H2x4x_MAX_PAYLOAD_LEN,
+ .num_slots = P3H2x4x_NUM_SLOTS,
+};
+
+struct regmap_config p3h2x4x_regmap_config = {
+ .reg_bits = P3H2x4x_REG_BITS,
+ .val_bits = P3H2x4x_VAL_BITS,
+ };
+
+/* 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", P3H2x4x_HUB_ID },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(i2c, p3h2x4x_i2c_id_table);
+
+static const struct of_device_id p3h2x4x_i2c_of_match[] = {
+ {
+ .compatible = "nxp,p3h2x4x",
+ .data = P3H2x4x_HUB_ID,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, p3h2x4x_i2c_of_match);
+
+static void p3h2x4x_of_get_dt_setting(struct device *dev,
+ const struct device_node *node,
+ const char *setting_name,
+ const struct p3h2x4x_setting settings[],
+ const u8 settings_count, u8 *setting_value)
+{
+ const char *sval;
+ int ret;
+ int i;
+
+ ret = of_property_read_string(node, setting_name, &sval);
+ if (ret) {
+ if (ret != -EINVAL)
+ dev_warn(dev, "No setting or invalid setting for %s, err=%i\n",
+ setting_name, ret);
+ return;
+ }
+
+ for (i = 0; i < settings_count; ++i) {
+ const struct p3h2x4x_setting *const setting = &settings[i];
+
+ if (!strcmp(setting->name, sval)) {
+ *setting_value = setting->value;
+ return;
+ }
+ }
+ dev_warn(dev, "Unknown setting for %s\n", setting_name);
+}
+
+static int p3h2x4x_configure_pullup(struct device *dev)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ u8 mask = 0, value = 0;
+
+ if (priv->settings.tp0145_pullup != P3H2x4x_TP_PULLUP_NOT_SET) {
+ mask |= P3H2x4x_TP0145_PULLUP_CONF_MASK;
+ value |= P3H2x4x_TP0145_PULLUP_CONF(priv->settings.tp0145_pullup);
+ }
+
+ if (priv->settings.tp2367_pullup != P3H2x4x_TP_PULLUP_NOT_SET) {
+ mask |= P3H2x4x_TP2367_PULLUP_CONF_MASK;
+ value |= P3H2x4x_TP2367_PULLUP_CONF(priv->settings.tp2367_pullup);
+ }
+
+ return regmap_update_bits(priv->regmap, P3H2x4x_LDO_AND_PULLUP_CONF,
+ mask, value);
+}
+
+static int p3h2x4x_configure_ldo(struct device *dev)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ u8 ldo_config_mask = 0, ldo_config_val = 0;
+ u8 ldo_disable_mask = 0, ldo_en_val = 0;
+ u32 reg_val;
+ int ret;
+ u8 val;
+
+ /* Enable or Disable LDO's. If there is no DT entry - disable LDO for safety reasons */
+ if (priv->settings.cp0_ldo_en == P3H2x4x_LDO_ENABLED)
+ ldo_en_val |= P3H2x4x_CP0_EN_LDO;
+ if (priv->settings.cp1_ldo_en == P3H2x4x_LDO_ENABLED)
+ ldo_en_val |= P3H2x4x_CP1_EN_LDO;
+ if (priv->settings.tp0145_ldo_en == P3H2x4x_LDO_ENABLED)
+ ldo_en_val |= P3H2x4x_TP0145_EN_LDO;
+ if (priv->settings.tp2367_ldo_en == P3H2x4x_LDO_ENABLED)
+ ldo_en_val |= P3H2x4x_TP2367_EN_LDO;
+
+ /* Get current LDOs configuration */
+ ret = regmap_read(priv->regmap, P3H2x4x_VCCIO_LDO_CONF, ®_val);
+ if (ret)
+ return ret;
+
+ /* LDOs Voltage level (Skip if not defined in the DT)
+ * Set the mask only if there is a change from current value
+ */
+ if (priv->settings.cp0_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET) {
+ val = P3H2x4x_CP0_VCCIO_LDO_VOLTAGE(priv->settings.cp0_ldo_volt);
+ if ((reg_val & P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK) != val) {
+ ldo_config_mask |= P3H2x4x_CP0_VCCIO_LDO_VOLTAGE_MASK;
+ ldo_config_val |= val;
+
+ ldo_disable_mask |= P3H2x4x_CP0_EN_LDO;
+ }
+ }
+ if (priv->settings.cp1_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET) {
+ val = P3H2x4x_CP1_VCCIO_LDO_VOLTAGE(priv->settings.cp1_ldo_volt);
+ if ((reg_val & P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK) != val) {
+ ldo_config_mask |= P3H2x4x_CP1_VCCIO_LDO_VOLTAGE_MASK;
+ ldo_config_val |= val;
+
+ ldo_disable_mask |= P3H2x4x_CP1_EN_LDO;
+ }
+ }
+ if (priv->settings.tp0145_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET) {
+ val = P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE(priv->settings.tp0145_ldo_volt);
+ if ((reg_val & P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK) != val) {
+ ldo_config_mask |= P3H2x4x_TP0145_VCCIO_LDO_VOLTAGE_MASK;
+ ldo_config_val |= val;
+
+ ldo_disable_mask |= P3H2x4x_TP0145_EN_LDO;
+ }
+ }
+ if (priv->settings.tp2367_ldo_volt != P3H2x4x_DT_LDO_VOLT_NOT_SET) {
+ val = P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE(priv->settings.tp2367_ldo_volt);
+ if ((reg_val & P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK) != val) {
+ ldo_config_mask |= P3H2x4x_TP2367_VCCIO_LDO_VOLTAGE_MASK;
+ ldo_config_val |= val;
+
+ ldo_disable_mask |= P3H2x4x_TP2367_EN_LDO;
+ }
+ }
+
+ /*
+ *Update LDO voltage configuration only if value is changed from already existing register
+ * value. It is a good practice to disable the LDO's before making any voltage changes.
+ * Presence of config mask indicates voltage change to be applied.
+ */
+ if (ldo_config_mask) {
+ /* Disable LDO's before making voltage changes */
+ ret = regmap_update_bits(priv->regmap,
+ P3H2x4x_LDO_AND_PULLUP_CONF,
+ ldo_disable_mask, 0);
+ if (ret)
+ return ret;
+
+ /* Update the LDOs configuration */
+ ret = regmap_update_bits(priv->regmap, P3H2x4x_VCCIO_LDO_CONF,
+ ldo_config_mask, ldo_config_val);
+ if (ret)
+ return ret;
+ }
+
+ /* Update the LDOs Enable/disable register. This will enable only LDOs enabled in DT */
+ return regmap_update_bits(priv->regmap, P3H2x4x_LDO_AND_PULLUP_CONF,
+ P3H2x4x_LDO_ENABLE_DISABLE_MASK, ldo_en_val);
+}
+
+static int p3h2x4x_configure_io_strength(struct device *dev)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ u8 mask_all = 0, val_all = 0;
+ u32 reg_val;
+ u8 val;
+ struct dt_settings tmp;
+ int ret;
+
+ /* Get IO strength configuration to figure out what needs to be changed */
+ ret = regmap_read(priv->regmap, P3H2x4x_IO_STRENGTH, ®_val);
+ if (ret)
+ return ret;
+
+ tmp = priv->settings;
+ if (tmp.cp0_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) {
+ val = P3H2x4x_CP0_IO_STRENGTH(tmp.cp0_io_strength);
+ mask_all |= P3H2x4x_CP0_IO_STRENGTH_MASK;
+ val_all |= val;
+ }
+ if (tmp.cp1_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) {
+ val = P3H2x4x_CP1_IO_STRENGTH(tmp.cp1_io_strength);
+ mask_all |= P3H2x4x_CP1_IO_STRENGTH_MASK;
+ val_all |= val;
+ }
+ if (tmp.tp0145_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) {
+ val = P3H2x4x_TP0145_IO_STRENGTH(tmp.tp0145_io_strength);
+ mask_all |= P3H2x4x_TP0145_IO_STRENGTH_MASK;
+ val_all |= val;
+ }
+ if (tmp.tp2367_io_strength != P3H2x4x_IO_STRENGTH_NOT_SET) {
+ val = P3H2x4x_TP2367_IO_STRENGTH(tmp.tp2367_io_strength);
+ mask_all |= P3H2x4x_TP2367_IO_STRENGTH_MASK;
+ val_all |= val;
+ }
+
+ /* Set IO strength if required */
+ return regmap_update_bits(priv->regmap, P3H2x4x_IO_STRENGTH, mask_all, val_all);
+}
+
+static int p3h2x4x_configure_tp(struct device *dev)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ u8 pullup_mask = 0, pullup_val = 0;
+ u8 smbus_mask = 0, smbus_val = 0;
+ u8 gpio_mask = 0, gpio_val = 0;
+ u8 i3c_mask = 0, i3c_val = 0;
+ u8 ibi_mask = 0, ibi_val = 0;
+ u8 i2c_mask = 0, i2c_val = 0;
+ int ret;
+ int i;
+
+ /* TBD: Read type of HUB from register P3H2x4x_DEV_INFO_0 to learn target ports count. */
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) {
+ if (priv->settings.tp[i].mode != P3H2x4x_TP_MODE_NOT_SET) {
+ i3c_mask |= P3H2x4x_TPn_NET_CON(i);
+ smbus_mask |= P3H2x4x_TPn_SMBUS_MODE_EN(i);
+ gpio_mask |= P3H2x4x_TPn_GPIO_MODE_EN(i);
+ i2c_mask |= P3H2x4x_TPn_I2C_MODE_EN(i);
+
+ if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C)
+ i3c_val |= P3H2x4x_TPn_NET_CON(i);
+ else if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS)
+ smbus_val |= P3H2x4x_TPn_SMBUS_MODE_EN(i);
+ else if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_GPIO)
+ gpio_val |= P3H2x4x_TPn_GPIO_MODE_EN(i);
+ else if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_I2C)
+ i2c_val |= P3H2x4x_TPn_I2C_MODE_EN(i);
+ }
+ if (priv->settings.tp[i].pullup_en != P3H2x4x_TP_PULLUP_NOT_DEFINED) {
+ pullup_mask |= P3H2x4x_TPn_PULLUP_EN(i);
+
+ if (priv->settings.tp[i].pullup_en == P3H2x4x_TP_PULLUP_ENABLED)
+ pullup_val |= P3H2x4x_TPn_PULLUP_EN(i);
+ }
+
+ if (priv->settings.tp[i].ibi_en != P3H2x4x_IBI_NOT_DEFINED) {
+ ibi_mask |= P3H2x4x_TPn_IBI_EN(i);
+
+ if (priv->settings.tp[i].ibi_en == P3H2x4x_IBI_ENABLED)
+ ibi_val |= P3H2x4x_TPn_IBI_EN(i);
+ }
+ }
+
+ ret = regmap_update_bits(priv->regmap, P3H2x4x_TP_IO_MODE_CONF, (smbus_mask | i2c_mask),
+ (smbus_val | i2c_val));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->regmap, P3H2x4x_TP_PULLUP_EN, pullup_mask, pullup_val);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, ibi_mask, ibi_val);
+ if (ret)
+ return ret;
+ priv->tp_ibi_mask = ibi_val;
+
+ ret = regmap_update_bits(priv->regmap, P3H2x4x_TP_SMBUS_AGNT_EN, smbus_mask, smbus_val);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(priv->regmap, P3H2x4x_TP_GPIO_MODE_EN, gpio_mask, gpio_val);
+ if (ret)
+ return ret;
+
+ /* Request for HUB Network connection in case any TP is configured in I3C mode */
+ if ((i3c_val) || (i2c_val)) {
+ ret = regmap_write(priv->regmap, P3H2x4x_CP_MUX_SET,
+ P3H2x4x_CONTROLLER_PORT_MUX_REQ);
+ if (ret)
+ return ret;
+ }
+
+ /* Enable TP here in case TP was configured */
+ ret = regmap_update_bits(priv->regmap, P3H2x4x_TP_ENABLE,
+ i3c_mask | smbus_mask | gpio_mask | i2c_mask,
+ i3c_val | smbus_val | gpio_val | i2c_val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int p3h2x4x_configure_smbus_local_dev(struct device *dev)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ u8 target_buffer_page, hub_tp;
+ int ret = 0;
+
+ for (hub_tp = 0; hub_tp < P3H2x4x_TP_MAX_COUNT; hub_tp++) {
+ if ((priv->tp_bus[hub_tp].local_dev_count) &&
+ (priv->settings.tp[hub_tp].mode == P3H2x4x_TP_MODE_SMBUS)) {
+ target_buffer_page = P3H2x4x_TARGET_AGENT_LOCAL_DEV + 4 * hub_tp;
+ ret = regmap_write(priv->regmap, P3H2x4x_PAGE_PTR, target_buffer_page);
+ if (ret) {
+ dev_err(dev, "Failed to configure local device settings\n");
+ break;
+ }
+
+ ret = regmap_bulk_write(priv->regmap,
+ P3H2x4x_CONTROLLER_AGENT_BUFF,
+ priv->tp_bus[hub_tp].local_dev_list,
+ priv->tp_bus[hub_tp].local_dev_count);
+ if (ret) {
+ dev_err(dev, "Failed to add local devices\n");
+ break;
+ }
+ }
+ }
+ regmap_write(priv->regmap, P3H2x4x_PAGE_PTR, 0x00);
+ return ret;
+}
+
+static int p3h2x4x_configure_hw(struct device *dev)
+{
+ int ret;
+
+ ret = p3h2x4x_configure_ldo(dev);
+ if (ret)
+ return ret;
+
+ ret = p3h2x4x_configure_io_strength(dev);
+ if (ret)
+ return ret;
+
+ ret = p3h2x4x_configure_pullup(dev);
+ if (ret)
+ return ret;
+
+ ret = p3h2x4x_configure_smbus_local_dev(dev);
+ if (ret)
+ return ret;
+
+ return p3h2x4x_configure_tp(dev);
+}
+
+static void p3h2x4x_of_get_tp_dt_conf(struct device *dev,
+ const struct device_node *node)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ struct device_node *dev_node;
+ int tp_port;
+
+ for_each_available_child_of_node(node, dev_node) {
+ if (!dev_node->full_name ||
+ (sscanf(dev_node->full_name, "target-port@%d", &tp_port) != 1))
+ continue;
+
+ if (tp_port < P3H2x4x_TP_MAX_COUNT) {
+ priv->tp_bus[tp_port].dt_available = true;
+ priv->tp_bus[tp_port].of_node = dev_node;
+ priv->tp_bus[tp_port].tp_mask = BIT(tp_port);
+ priv->tp_bus[tp_port].priv = priv;
+ priv->tp_bus[tp_port].tp_port = tp_port;
+ }
+ }
+}
+
+/* return true when backend node exist */
+static bool p3h2x4x_is_backend_node_exist(int port, struct p3h2x4x *priv, u32 addr)
+{
+ struct smbus_device *backend = NULL;
+
+ list_for_each_entry(backend,
+ &priv->tp_bus[port].tp_device_entry, list) {
+ if (backend->addr == addr)
+ return true;
+ }
+ return false;
+}
+
+static int p3h2x4x_read_backend_from_p3h2x4x_dts(struct device_node *i3c_node_target,
+ struct p3h2x4x *priv)
+{
+ struct device_node *i3c_node_tp;
+ const char *compatible;
+ int tp_port, ret;
+ u32 addr_dts;
+ struct smbus_device *backend;
+
+ if (sscanf(i3c_node_target->full_name, "target-port@%d", &tp_port) == 0)
+ return -EINVAL;
+
+ if (tp_port > P3H2x4x_TP_MAX_COUNT)
+ return -ERANGE;
+
+ if (tp_port < 0)
+ return -EINVAL;
+
+ INIT_LIST_HEAD(&priv->tp_bus[tp_port].tp_device_entry);
+
+ if (priv->settings.tp[tp_port].mode == P3H2x4x_TP_MODE_I3C)
+ return 0;
+
+ for_each_available_child_of_node(i3c_node_target, i3c_node_tp) {
+
+ ret = of_property_read_u32(i3c_node_tp, "reg", &addr_dts);
+ if (ret)
+ return ret;
+
+ if (p3h2x4x_is_backend_node_exist(tp_port, priv, addr_dts))
+ continue;
+
+ ret = of_property_read_string(i3c_node_tp, "compatible", &compatible);
+ if (ret)
+ return ret;
+
+ backend = kzalloc(sizeof(*backend), GFP_KERNEL);
+ if (!backend)
+ return -ENOMEM;
+
+ backend->addr = addr_dts;
+ backend->compatible = compatible;
+ backend->tp_device_dt_node = i3c_node_tp;
+ backend->client = NULL;
+
+ list_add(&backend->list,
+ &priv->tp_bus[tp_port].tp_device_entry);
+ }
+
+ return 0;
+}
+
+static void p3h2x4x_parse_dt_tp(struct device *dev,
+ const struct device_node *i3c_node_hub,
+ struct p3h2x4x *priv)
+{
+ struct device_node *i3c_node_target;
+ int ret;
+
+ for_each_available_child_of_node(i3c_node_hub, i3c_node_target) {
+ if (!strcmp(i3c_node_target->name, "target-port")) {
+ ret = p3h2x4x_read_backend_from_p3h2x4x_dts(i3c_node_target, priv);
+ if (ret)
+ dev_err(dev, "DTS entry invalid - error %d", ret);
+ }
+ }
+}
+
+static int p3h2x4x_get_tp_local_device_dt_setting(struct device *dev,
+ const struct device_node *node, u32 id)
+{
+ u8 i;
+ u32 local_dev_count, local_dev;
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+
+ if (!of_get_property(node, "local_dev", &local_dev_count))
+ return -EINVAL;
+
+ local_dev_count = local_dev_count / (sizeof(u32));
+
+ if (local_dev_count > P3H2x4x_TP_LOCAL_DEV)
+ return -ERANGE;
+
+ for (i = 0; i < local_dev_count; i++) {
+ if (of_property_read_u32_index(node, "local_dev", i, &local_dev)) {
+ priv->tp_bus[id].local_dev_count = 0;
+ return -EINVAL;
+ }
+ priv->tp_bus[id].local_dev_list[i] = (u8)(local_dev*2);
+ }
+ priv->tp_bus[id].local_dev_count = local_dev_count;
+ return 0;
+}
+
+static void p3h2x4x_get_tp_of_get_setting(struct device *dev,
+ const struct device_node *node,
+ struct tp_setting tp_setting[])
+{
+ struct device_node *tp_node;
+ u32 id;
+
+ for_each_available_child_of_node(node, tp_node) {
+ if (!tp_node->name || of_node_cmp(tp_node->name, "target-port"))
+ continue;
+
+ if (!tp_node->full_name ||
+ (sscanf(tp_node->full_name, "target-port@%u", &id) != 1)) {
+ dev_warn(dev, "Invalid target port node found in DT: %s\n",
+ tp_node->full_name);
+ continue;
+ }
+
+ if (id >= P3H2x4x_TP_MAX_COUNT) {
+ dev_warn(dev, "Invalid target port index found in DT: %i\n", id);
+ continue;
+ }
+ p3h2x4x_of_get_dt_setting(dev, tp_node, "mode", tp_mode_settings,
+ ARRAY_SIZE(tp_mode_settings),
+ &tp_setting[id].mode);
+ p3h2x4x_of_get_dt_setting(dev, tp_node, "pullup_en", tp_pullup_settings,
+ ARRAY_SIZE(tp_pullup_settings),
+ &tp_setting[id].pullup_en);
+ p3h2x4x_of_get_dt_setting(dev, tp_node, "ibi_en", ibi_en_settings,
+ ARRAY_SIZE(ibi_en_settings),
+ &tp_setting[id].ibi_en);
+ tp_setting[id].always_enable =
+ of_property_read_bool(tp_node, "always-enable");
+
+ p3h2x4x_get_tp_local_device_dt_setting(dev, tp_node, id);
+ }
+}
+
+static void p3h2x4x_of_get_p3h2x4x_conf(struct device *dev,
+ const struct device_node *node)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+
+ p3h2x4x_of_get_dt_setting(dev, node, "cp0-ldo-en", ldo_en_settings,
+ ARRAY_SIZE(ldo_en_settings),
+ &priv->settings.cp0_ldo_en);
+ p3h2x4x_of_get_dt_setting(dev, node, "cp1-ldo-en", ldo_en_settings,
+ ARRAY_SIZE(ldo_en_settings),
+ &priv->settings.cp1_ldo_en);
+ p3h2x4x_of_get_dt_setting(dev, node, "tp0145-ldo-en", ldo_en_settings,
+ ARRAY_SIZE(ldo_en_settings),
+ &priv->settings.tp0145_ldo_en);
+ p3h2x4x_of_get_dt_setting(dev, node, "tp2367-ldo-en", ldo_en_settings,
+ ARRAY_SIZE(ldo_en_settings),
+ &priv->settings.tp2367_ldo_en);
+ p3h2x4x_of_get_dt_setting(dev, node, "cp0-ldo-volt", ldo_volt_settings,
+ ARRAY_SIZE(ldo_volt_settings),
+ &priv->settings.cp0_ldo_volt);
+ p3h2x4x_of_get_dt_setting(dev, node, "cp1-ldo-volt", ldo_volt_settings,
+ ARRAY_SIZE(ldo_volt_settings),
+ &priv->settings.cp1_ldo_volt);
+ p3h2x4x_of_get_dt_setting(dev, node, "tp0145-ldo-volt", ldo_volt_settings,
+ ARRAY_SIZE(ldo_volt_settings),
+ &priv->settings.tp0145_ldo_volt);
+ p3h2x4x_of_get_dt_setting(dev, node, "tp2367-ldo-volt", ldo_volt_settings,
+ ARRAY_SIZE(ldo_volt_settings),
+ &priv->settings.tp2367_ldo_volt);
+ p3h2x4x_of_get_dt_setting(dev, node, "tp0145-pullup", pullup_settings,
+ ARRAY_SIZE(pullup_settings),
+ &priv->settings.tp0145_pullup);
+ p3h2x4x_of_get_dt_setting(dev, node, "tp2367-pullup", pullup_settings,
+ ARRAY_SIZE(pullup_settings),
+ &priv->settings.tp2367_pullup);
+ p3h2x4x_of_get_dt_setting(dev, node, "cp0-io-strength", io_strength_settings,
+ ARRAY_SIZE(io_strength_settings),
+ &priv->settings.cp0_io_strength);
+ p3h2x4x_of_get_dt_setting(dev, node, "cp1-io-strength", io_strength_settings,
+ ARRAY_SIZE(io_strength_settings),
+ &priv->settings.cp1_io_strength);
+ p3h2x4x_of_get_dt_setting(dev, node, "tp0145-io-strength", io_strength_settings,
+ ARRAY_SIZE(io_strength_settings),
+ &priv->settings.tp0145_io_strength);
+ p3h2x4x_of_get_dt_setting(dev, node, "tp2367-io-strength", io_strength_settings,
+ ARRAY_SIZE(io_strength_settings),
+ &priv->settings.tp2367_io_strength);
+
+p3h2x4x_get_tp_of_get_setting(dev, node, priv->settings.tp);
+}
+
+static struct device_node *p3h2x4x_get_dt_p3h2x4x_node(struct device_node *node,
+ struct device *dev)
+{
+ struct device_node *hub_node_no_id = NULL;
+ struct device_node *hub_node;
+
+ for_each_available_child_of_node(node, hub_node) {
+ if (strstr(hub_node->name, "hub"))
+ return hub_node;
+ }
+ return hub_node_no_id;
+}
+
+static void p3h2x4x_of_default_configuration(struct device *dev)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ int tp_count;
+
+ priv->settings.cp0_ldo_en = P3H2x4x_DT_LDO_NOT_DEFINED;
+ priv->settings.cp1_ldo_en = P3H2x4x_DT_LDO_NOT_DEFINED;
+ priv->settings.tp0145_ldo_en = P3H2x4x_DT_LDO_NOT_DEFINED;
+ priv->settings.tp2367_ldo_en = P3H2x4x_DT_LDO_NOT_DEFINED;
+ priv->settings.cp0_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET;
+ priv->settings.cp1_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET;
+ priv->settings.tp0145_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET;
+ priv->settings.tp2367_ldo_volt = P3H2x4x_DT_LDO_VOLT_NOT_SET;
+ priv->settings.tp0145_pullup = P3H2x4x_TP_PULLUP_NOT_SET;
+ priv->settings.tp2367_pullup = P3H2x4x_TP_PULLUP_NOT_SET;
+ priv->settings.cp0_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET;
+ priv->settings.cp1_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET;
+ priv->settings.tp0145_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET;
+ priv->settings.tp2367_io_strength = P3H2x4x_IO_STRENGTH_NOT_SET;
+
+ for (tp_count = 0; tp_count < P3H2x4x_TP_MAX_COUNT; ++tp_count) {
+ priv->settings.tp[tp_count].mode = P3H2x4x_TP_MODE_NOT_SET;
+ priv->settings.tp[tp_count].pullup_en = P3H2x4x_TP_PULLUP_NOT_DEFINED;
+ priv->settings.tp[tp_count].ibi_en = P3H2x4x_IBI_DISABLED;
+ }
+}
+
+static int p3h2x4x_device_probe_i3c(struct i3c_device *i3cdev)
+{
+ struct device_node *node = NULL;
+ struct regmap *regmap;
+ struct device *dev = &i3cdev->dev;
+ struct p3h2x4x *priv;
+ char hub_id[64];
+ int ret, i;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->i3cdev = i3cdev;
+ priv->driving_master = i3c_dev_get_master(i3cdev->desc);
+ i3cdev_set_drvdata(i3cdev, priv);
+ sprintf(hub_id, "i3c-hub-device-%d-%llx", i3c_dev_get_master(i3cdev->desc)->bus.id,
+ i3cdev->desc->info.pid);
+ p3h2x4x_of_default_configuration(dev);
+ regmap = devm_regmap_init_i3c(i3cdev, &p3h2x4x_regmap_config);
+ if (IS_ERR(regmap)) {
+ ret = PTR_ERR(regmap);
+ dev_err(dev, "Failed to register I3C HUB regmap\n");
+ goto error;
+ }
+ priv->regmap = regmap;
+ priv->is_p3h2x4x_in_i3c = true;
+
+ mutex_init(&priv->etx_mutex);
+
+ /* Register logic for native SMBus ports */
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++)
+ mutex_init(&priv->tp_bus[i].port_mutex);
+
+ node = p3h2x4x_get_dt_p3h2x4x_node(dev->parent->of_node, dev); /* Find hub node in DT */
+ if (!node) {
+ dev_warn(dev, "No DT entry - running with hardware defaults.\n");
+ } else {
+ of_node_get(node);
+ p3h2x4x_of_get_p3h2x4x_conf(dev, node);
+ p3h2x4x_of_get_tp_dt_conf(dev, node);
+ of_node_put(node);
+ /* Parse DTS to find data on the SMBus target mode */
+ p3h2x4x_parse_dt_tp(dev, node, priv);
+ }
+
+ /* Unlock access to protected registers */
+ ret = regmap_write(priv->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE,
+ P3H2x4x_REGISTERS_UNLOCK_CODE);
+ if (ret) {
+ dev_err(dev, "Failed to unlock HUB's protected registers\n");
+ goto error;
+ }
+
+ ret = p3h2x4x_configure_hw(dev);
+ if (ret) {
+ dev_err(dev, "Failed to configure the HUB\n");
+ goto error;
+ }
+
+ /* Register logic for native SMBus ports */
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) {
+ if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS)
+ ret = p3h2x4x_tp_smbus_algo(priv, i);
+ }
+
+ ret = p3h2x4x_tp_add_downstream_device(priv);
+ if (ret) {
+ dev_err(dev, "Failed to add backend device\n");
+ goto error;
+ }
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) {
+ if (priv->tp_bus[i].dt_available) {
+ if (priv->settings.tp[i].always_enable)
+ priv->tp_always_enable_mask =
+ (priv->tp_always_enable_mask | BIT(i));
+
+ if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C)
+ priv->tp_i3c_bus_mask = (priv->tp_i3c_bus_mask | BIT(i));
+ }
+ }
+
+ /* Register logic for native vertual I3C ports */
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) {
+ if ((priv->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C) &&
+ (!priv->settings.tp[i].always_enable))
+ ret = p3h2x4x_tp_i3c_algo(priv, i);
+ }
+
+ ret = regmap_write(priv->regmap, P3H2x4x_TP_NET_CON_CONF, priv->tp_always_enable_mask);
+ if (ret) {
+ dev_err(dev, "Failed to open Target Port(s)\n");
+ goto error;
+ }
+
+ ret = i3c_master_do_daa(priv->driving_master);
+ if (ret)
+ dev_warn(dev, "Failed to run DAA\n");
+
+
+ /* 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(priv->regmap, P3H2x4x_ONCHIP_TD_AND_SMBUS_AGNT_CONF,
+ P3H2x4x_TARGET_AGENT_DFT_IBI_CONF_MASK,
+ P3H2x4x_TARGET_AGENT_DFT_IBI_CONF);
+ if (ret) {
+ dev_err(dev, "Failed to P3H2x4x_ONCHIP_TD_AND_SMBUS_AGNT_CONF\n");
+ goto error;
+ }
+
+ ret = i3c_device_request_ibi(i3cdev, &p3h2x4x_ibireq);
+ if (ret) {
+ dev_err(dev, "Failed to request IBI\n");
+ goto error;
+ }
+
+ ret = i3c_device_enable_ibi(i3cdev);
+ if (ret) {
+ dev_err(dev, "Failed to Enable IBI\n");
+ goto error;
+ }
+
+ /* Lock access to protected registers */
+ ret = regmap_write(priv->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE,
+ P3H2x4x_REGISTERS_LOCK_CODE);
+ if (ret) {
+ dev_err(dev, "Failed to lock HUB's protected registers\n");
+ goto error;
+ }
+
+ return 0;
+
+error:
+ devm_kfree(dev, priv);
+ return ret;
+}
+
+static void p3h2x4x_device_remove_i3c(struct i3c_device *i3cdev)
+{
+ struct p3h2x4x *priv = i3cdev_get_drvdata(i3cdev);
+ struct i2c_adapter *tp_adap;
+ struct i3c_master_controller *tp_controller;
+ struct smbus_device *backend = NULL;
+ int i;
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) {
+ tp_adap = &priv->tp_bus[i].smbus_port_adapter;
+ tp_controller = &priv->tp_bus[i].i3c_port_controller;
+
+ if (priv->tp_bus[i].is_registered) {
+ if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS) {
+ list_for_each_entry(backend,
+ &priv->tp_bus[i].tp_device_entry,
+ list) {
+ i2c_unregister_device(backend->client);
+ kfree(backend);
+ }
+ i2c_del_adapter(tp_adap);
+ } else if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_I3C) {
+ i3c_master_unregister(tp_controller);
+ }
+ }
+ }
+
+ i3c_device_disable_ibi(i3cdev);
+ i3c_device_free_ibi(i3cdev);
+
+ mutex_destroy(&priv->etx_mutex);
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++)
+ mutex_destroy(&priv->tp_bus[i].port_mutex);
+
+ devm_kfree(&i3cdev->dev, priv);
+}
+
+static int p3h2x4x_device_probe_i2c(struct i2c_client *client)
+{
+ struct device_node *node = NULL;
+ struct regmap *regmap;
+ struct device *dev = &client->dev;
+ struct p3h2x4x *priv;
+ int ret, i;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->i2cdev = client;
+ i2c_set_clientdata(client, priv);
+
+ p3h2x4x_of_default_configuration(dev);
+
+ regmap = devm_regmap_init_i2c(client, &p3h2x4x_regmap_config);
+ if (IS_ERR(regmap)) {
+ ret = PTR_ERR(regmap);
+ dev_err(dev, "Failed to register I3C HUB regmap\n");
+ goto error;
+ }
+ priv->regmap = regmap;
+ priv->is_p3h2x4x_in_i3c = false;
+
+ mutex_init(&priv->etx_mutex);
+
+ /* Register logic for native SMBus ports */
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++)
+ mutex_init(&priv->tp_bus[i].port_mutex);
+
+ node = p3h2x4x_get_dt_p3h2x4x_node(dev->parent->of_node, dev); /* Find hub node in DT */
+ if (!node) {
+ dev_warn(dev, "No DT entry - running with hardware defaults.\n");
+ } else {
+ of_node_get(node);
+ p3h2x4x_of_get_p3h2x4x_conf(dev, node);
+ p3h2x4x_of_get_tp_dt_conf(dev, node);
+ of_node_put(node);
+ /* Parse DTS to find data on the SMBus target mode */
+ p3h2x4x_parse_dt_tp(dev, node, priv);
+ }
+
+ /* Unlock access to protected registers */
+ ret = regmap_write(priv->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE,
+ P3H2x4x_REGISTERS_UNLOCK_CODE);
+ if (ret) {
+ dev_err(dev, "Failed to unlock HUB's protected registers\n");
+ goto error;
+ }
+
+ ret = p3h2x4x_configure_hw(dev);
+ if (ret) {
+ dev_err(dev, "Failed to configure the HUB\n");
+ goto error;
+ }
+
+ /* Register logic for native SMBus ports */
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) {
+ if (priv->settings.tp[i].mode == P3H2x4x_TP_MODE_SMBUS)
+ ret = p3h2x4x_tp_smbus_algo(priv, i);
+ }
+
+ ret = p3h2x4x_tp_add_downstream_device(priv);
+ if (ret) {
+ dev_err(dev, "Failed to add backend device\n");
+ goto error;
+ }
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) {
+ if (priv->tp_bus[i].dt_available) {
+ if (priv->settings.tp[i].always_enable)
+ priv->tp_always_enable_mask =
+ (priv->tp_always_enable_mask | BIT(i));
+ }
+ }
+
+ ret = regmap_write(priv->regmap, P3H2x4x_TP_NET_CON_CONF, priv->tp_always_enable_mask);
+ if (ret) {
+ dev_err(dev, "Failed to open Target Port(s)\n");
+ goto error;
+ }
+
+ /* Lock access to protected registers */
+ ret = regmap_write(priv->regmap, P3H2x4x_DEV_REG_PROTECTION_CODE,
+ P3H2x4x_REGISTERS_LOCK_CODE);
+ if (ret) {
+ dev_err(dev, "Failed to lock HUB's protected registers\n");
+ goto error;
+ }
+ return 0;
+
+error:
+ devm_kfree(dev, priv);
+ return ret;
+}
+
+static void p3h2x4x_device_remove_i2c(struct i2c_client *client)
+{
+ struct p3h2x4x *priv = i2c_get_clientdata(client);
+ struct i2c_adapter *tp_adap;
+ struct smbus_device *backend = NULL;
+ int i;
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) {
+ tp_adap = &priv->tp_bus[i].smbus_port_adapter;
+ if (priv->tp_bus[i].is_registered) {
+ list_for_each_entry(backend, &priv->tp_bus[i].tp_device_entry, list) {
+ i2c_unregister_device(backend->client);
+ kfree(backend);
+ }
+ i2c_del_adapter(tp_adap);
+ }
+ }
+
+ mutex_destroy(&priv->etx_mutex);
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++)
+ mutex_destroy(&priv->tp_bus[i].port_mutex);
+
+ devm_kfree(&client->dev, priv);
+}
+
+static struct i3c_driver p3h2x4x_i3c = {
+ .driver = {
+ .name = "p3h2x4x_i3c_drv",
+ },
+ .id_table = p3h2x4x_i3c_ids,
+ .probe = p3h2x4x_device_probe_i3c,
+ .remove = p3h2x4x_device_remove_i3c,
+};
+
+static struct i2c_driver p3h2x4x_i2c = {
+ .driver = {
+ .name = "p3h2x4x_i2c_drv",
+ .of_match_table = p3h2x4x_i2c_of_match,
+ },
+ .probe = p3h2x4x_device_probe_i2c,
+ .remove = p3h2x4x_device_remove_i2c,
+ .id_table = p3h2x4x_i2c_id_table,
+};
+
+module_i3c_i2c_driver(p3h2x4x_i3c, &p3h2x4x_i2c);
+
+MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@....com>");
+MODULE_AUTHOR("vikash Bansal <vikash.bansal@....com>");
+MODULE_DESCRIPTION("P3H2x4x I3C HUB driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_i3c.c b/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_i3c.c
new file mode 100644
index 000000000000..c7827c4b6f57
--- /dev/null
+++ b/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_i3c.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024-2025 NXP
+ * This P3H2x4x driver file contain functions for I3C virtual Bus creation, connect/disconnect
+ * hub network and read/write.
+ */
+#include "p3h2x4x_i3c_hub.h"
+
+static void p3h2x4x_en_p3h2x4x_ntwk_tp(struct tp_bus *bus)
+{
+ struct p3h2x4x *priv = bus->priv;
+ struct device *dev = i3cdev_to_dev(priv->i3cdev);
+ int ret;
+
+ if (priv->settings.tp[bus->tp_port].always_enable)
+ return;
+
+ ret = regmap_write(priv->regmap, P3H2x4x_TP_NET_CON_CONF,
+ (bus->tp_mask | priv->tp_always_enable_mask));
+ if (ret)
+ dev_warn(dev, "Failed to open Target Port(s)\n");
+}
+
+static void p3h2x4x_dis_p3h2x4x_ntwk_tp(struct tp_bus *bus)
+{
+ struct p3h2x4x *priv = bus->priv;
+ struct device *dev = i3cdev_to_dev(priv->i3cdev);
+ int ret;
+
+ if (priv->settings.tp[bus->tp_port].always_enable)
+ return;
+
+ ret = regmap_write(priv->regmap, P3H2x4x_TP_NET_CON_CONF, priv->tp_always_enable_mask);
+ if (ret)
+ dev_warn(dev, "Failed to close Target Port(s)\n");
+}
+
+static struct tp_bus *p3h2x4x_bus_from_i3c_desc(struct i3c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = i3c_dev_get_master(desc);
+
+ return container_of(controller, struct tp_bus, i3c_port_controller);
+}
+
+static struct tp_bus *p3h2x4x_bus_from_i2c_desc(struct i2c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = i2c_dev_get_master(desc);
+
+ return container_of(controller, struct tp_bus, i3c_port_controller);
+}
+
+static struct i3c_master_controller
+*get_parent_controller(struct i3c_master_controller *controller)
+{
+ struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller);
+
+ return bus->priv->driving_master;
+}
+
+static struct i3c_master_controller
+*get_parent_controller_from_i3c_desc(struct i3c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = i3c_dev_get_master(desc);
+ struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller);
+
+ return bus->priv->driving_master;
+}
+
+static struct i3c_master_controller
+*get_parent_controller_from_i2c_desc(struct i2c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = desc->common.master;
+ struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller);
+
+ return bus->priv->driving_master;
+}
+
+static struct i3c_master_controller
+*update_i3c_i2c_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 void restore_i3c_i2c_desc_parent(struct i3c_i2c_dev_desc *desc,
+ struct i3c_master_controller *parent)
+{
+ desc->master = parent;
+}
+
+static int p3h2x4x_i3c_bus_init(struct i3c_master_controller *controller)
+{
+ struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller);
+
+ controller->this = bus->priv->i3cdev->desc;
+ return 0;
+}
+
+static void p3h2x4x_i3c_bus_cleanup(struct i3c_master_controller *controller)
+{
+ controller->this = NULL;
+}
+
+static int p3h2x4x_attach_i3c_dev(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->attach_i3c_dev(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ return ret;
+}
+
+static int p3h2x4x_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->reattach_i3c_dev(dev, old_dyn_addr);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ return ret;
+}
+
+static void p3h2x4x_detach_i3c_dev(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ parent->ops->detach_i3c_dev(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+}
+
+static int p3h2x4x_do_daa(struct i3c_master_controller *controller)
+{
+ struct i3c_master_controller *parent = get_parent_controller(controller);
+
+ struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller);
+ int ret;
+
+ p3h2x4x_en_p3h2x4x_ntwk_tp(bus);
+ down_write(&parent->bus.lock);
+ ret = parent->ops->do_daa(parent);
+ up_write(&parent->bus.lock);
+ p3h2x4x_dis_p3h2x4x_ntwk_tp(bus);
+
+ return ret;
+}
+
+static bool p3h2x4x_supports_ccc_cmd(struct i3c_master_controller *controller,
+ const struct i3c_ccc_cmd *cmd)
+{
+ struct i3c_master_controller *parent = get_parent_controller(controller);
+
+ return parent->ops->supports_ccc_cmd(parent, cmd);
+}
+
+static int p3h2x4x_send_ccc_cmd(struct i3c_master_controller *controller,
+ struct i3c_ccc_cmd *cmd)
+{
+ struct i3c_master_controller *parent = get_parent_controller(controller);
+ struct tp_bus *bus = container_of(controller, struct tp_bus, i3c_port_controller);
+ int ret;
+
+ if (cmd->id == I3C_CCC_RSTDAA(true))
+ return 0;
+
+ p3h2x4x_en_p3h2x4x_ntwk_tp(bus);
+ ret = parent->ops->send_ccc_cmd(parent, cmd);
+ p3h2x4x_dis_p3h2x4x_ntwk_tp(bus);
+
+ return ret;
+}
+
+static int p3h2x4x_priv_xfers(struct i3c_dev_desc *dev,
+ struct i3c_priv_xfer *xfers, int nxfers)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev);
+ int res;
+
+ p3h2x4x_en_p3h2x4x_ntwk_tp(bus);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ res = parent->ops->priv_xfers(dev, xfers, nxfers);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ p3h2x4x_dis_p3h2x4x_ntwk_tp(bus);
+
+ return res;
+}
+
+static int p3h2x4x_attach_i2c_dev(struct i2c_dev_desc *dev)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->attach_i2c_dev(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ return ret;
+}
+
+static void p3h2x4x_detach_i2c_dev(struct i2c_dev_desc *dev)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ parent->ops->detach_i2c_dev(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+}
+
+static int p3h2x4x_i2c_xfers(struct i2c_dev_desc *dev,
+ const struct i2c_msg *xfers, int nxfers)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i2c_desc(dev);
+ struct tp_bus *bus = p3h2x4x_bus_from_i2c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ p3h2x4x_en_p3h2x4x_ntwk_tp(bus);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->i2c_xfers(dev, xfers, nxfers);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ p3h2x4x_dis_p3h2x4x_ntwk_tp(bus);
+ return ret;
+}
+
+static int p3h2x4x_request_ibi(struct i3c_dev_desc *dev,
+ const struct i3c_ibi_setup *req)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ p3h2x4x_en_p3h2x4x_ntwk_tp(bus);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->request_ibi(dev, req);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ p3h2x4x_dis_p3h2x4x_ntwk_tp(bus);
+ return ret;
+}
+
+static void p3h2x4x_free_ibi(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+
+ p3h2x4x_en_p3h2x4x_ntwk_tp(bus);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ parent->ops->free_ibi(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ p3h2x4x_dis_p3h2x4x_ntwk_tp(bus);
+}
+
+static int p3h2x4x_enable_ibi(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ p3h2x4x_en_p3h2x4x_ntwk_tp(bus);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->enable_ibi(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ p3h2x4x_dis_p3h2x4x_ntwk_tp(bus);
+ return ret;
+}
+
+static int p3h2x4x_disable_ibi(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct tp_bus *bus = p3h2x4x_bus_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ p3h2x4x_en_p3h2x4x_ntwk_tp(bus);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->disable_ibi(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ p3h2x4x_dis_p3h2x4x_ntwk_tp(bus);
+ return ret;
+}
+
+static void p3h2x4x_recycle_ibi_slot(struct i3c_dev_desc *dev,
+ struct i3c_ibi_slot *slot)
+{
+ struct i3c_master_controller *parent = get_parent_controller_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ parent->ops->recycle_ibi_slot(dev, slot);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+}
+
+static const struct i3c_master_controller_ops p3h2x4x_i3c_ops = {
+ .bus_init = p3h2x4x_i3c_bus_init,
+ .bus_cleanup = p3h2x4x_i3c_bus_cleanup,
+ .attach_i3c_dev = p3h2x4x_attach_i3c_dev,
+ .reattach_i3c_dev = p3h2x4x_reattach_i3c_dev,
+ .detach_i3c_dev = p3h2x4x_detach_i3c_dev,
+ .do_daa = p3h2x4x_do_daa,
+ .supports_ccc_cmd = p3h2x4x_supports_ccc_cmd,
+ .send_ccc_cmd = p3h2x4x_send_ccc_cmd,
+ .priv_xfers = p3h2x4x_priv_xfers,
+ .attach_i2c_dev = p3h2x4x_attach_i2c_dev,
+ .detach_i2c_dev = p3h2x4x_detach_i2c_dev,
+ .i2c_xfers = p3h2x4x_i2c_xfers,
+ .request_ibi = p3h2x4x_request_ibi,
+ .free_ibi = p3h2x4x_free_ibi,
+ .enable_ibi = p3h2x4x_enable_ibi,
+ .disable_ibi = p3h2x4x_disable_ibi,
+ .recycle_ibi_slot = p3h2x4x_recycle_ibi_slot,
+};
+
+/**
+ * p3h2x4x_tp_i3c_algo - register i3c master for target port who
+ * 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 *priv, int tp)
+{
+ struct device *dev = i3cdev_to_dev(priv->i3cdev);
+ int ret;
+
+ priv->tp_bus[tp].tp_mask = BIT(tp);
+ dev->of_node = priv->tp_bus[tp].of_node;
+
+ ret = i3c_master_register(&priv->tp_bus[tp].i3c_port_controller,
+ dev, &p3h2x4x_i3c_ops, false);
+ if (ret) {
+ dev_warn(dev, "Failed to register i3c controller for tp %d\n", tp);
+ return -EINVAL;
+ }
+
+ priv->tp_bus[tp].is_registered = true;
+ return 0;
+}
diff --git a/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_smbus.c b/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_smbus.c
new file mode 100644
index 000000000000..8cdbaaf49378
--- /dev/null
+++ b/drivers/i3c/p3h2x4x/p3h2x4x_i3c_hub_smbus.c
@@ -0,0 +1,719 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2024-2025 NXP
+ * This P3H2x4x driver file contain functions for SMBus/I2C virtual Bus creation and read/write.
+ */
+#include "p3h2x4x_i3c_hub.h"
+
+static struct device *i2cdev_to_dev(struct i2c_client *i2cdev)
+{
+ return &i2cdev->dev;
+}
+
+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 device *dev = i3cdev_to_dev(i3cdev);
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ struct smbus_device *backend = NULL;
+
+ u8 target_buffer_page, flag_clear, rx_data, temp, i;
+ u8 slave_rx_buffer[P3H2x4x_SMBUS_TARGET_PAYLOAD_SIZE] = { 0 };
+ 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(priv->regmap, P3H2x4x_PAGE_PTR, target_buffer_page);
+ if (ret)
+ goto ibi_err;
+
+ /* read buffer length */
+ ret = regmap_read(priv->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(dev, "Received message too big for p3h2x4x buffer\n");
+ return;
+ }
+
+ /* read slave address */
+ ret = regmap_read(priv->regmap, P3H2x4x_TARGET_BUFF_ADDRESS, &slave_address);
+ if (ret)
+ goto ibi_err;
+
+ /* read data */
+ if (packet_len) {
+ ret = regmap_bulk_read(priv->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 */
+ list_for_each_entry(backend, &priv->tp_bus[tp].tp_device_entry, list) {
+ if ((slave_address >> 1 == backend->addr) && (priv->is_slave_reg)) {
+ i2c_slave_event(backend->client, I2C_SLAVE_WRITE_REQUESTED,
+ (u8 *)&slave_address);
+
+ for (i = 0; i < packet_len; i++) {
+ rx_data = slave_rx_buffer[i];
+ i2c_slave_event(backend->client, I2C_SLAVE_WRITE_RECEIVED,
+ &rx_data);
+ }
+ i2c_slave_event(backend->client, I2C_SLAVE_STOP, &temp);
+ break;
+ }
+ }
+
+ibi_err:
+ regmap_write(priv->regmap, P3H2x4x_PAGE_PTR, 0x00);
+ regmap_write(priv->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)
+{
+ struct device *dev = i3cdev_to_dev(i3cdev);
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+
+ u32 target_port_status, payload_byte_one, payload_byte_two;
+ u8 i;
+ int ret;
+
+ payload_byte_one = (*(int *)payload->data);
+ payload_byte_two = (*(int *)(payload->data + 4));
+
+ if (!(payload_byte_one & P3H2x4x_SMBUS_AGENT_EVENT_FLAG_STATUS))
+ goto err;
+
+ mutex_lock(&priv->etx_mutex);
+ ret = regmap_write(priv->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, P3H2x4x_ALL_TP_IBI_DIS);
+ if (ret) {
+ dev_err(dev, "Failed to Disable IBI\n");
+ goto err;
+ }
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; ++i) {
+ if ((priv->tp_bus[i].is_registered) && ((payload_byte_two >> i) & 0x01)) {
+ ret = regmap_read(priv->regmap, P3H2x4x_TP0_SMBUS_AGNT_STS + i,
+ &target_port_status);
+ if (ret) {
+ dev_err(dev, "target port read status failed %d\n", ret);
+ goto err;
+ }
+
+ /* 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(dev, "Overflow, reading buffer zero and one\n");
+ break;
+ default:
+ regmap_write(priv->regmap, P3H2x4x_TP0_SMBUS_AGNT_STS + i,
+ BUF_RECEIVED_FLAG_TF_MASK);
+ break;
+ }
+ }
+ }
+err:
+ regmap_write(priv->regmap, P3H2x4x_TP_SMBUS_AGNT_IBI_CONFIG, priv->tp_ibi_mask);
+ mutex_unlock(&priv->etx_mutex);
+}
+
+static int p3h2x4x_read_p3h2x4x_id(struct device *dev)
+{
+ struct p3h2x4x *priv = dev_get_drvdata(dev);
+ u32 reg_val;
+ int ret;
+
+ ret = regmap_read(priv->regmap, P3H2x4x_LDO_AND_CPSEL_STS, ®_val);
+ if (ret) {
+ dev_err(dev, "Failed to read status register\n");
+ return ret;
+ }
+ if (P3H2x4x_CP_SEL_PIN_INPUT_CODE_GET(reg_val) == 3)
+ return 1;
+ else
+ return 0;
+}
+
+
+static void unlock_port_ext_mutex(struct mutex *ext, struct mutex *port)
+{
+ mutex_unlock(ext);
+ mutex_unlock(port);
+}
+
+static void lock_port_ext_mutex(struct mutex *ext, struct mutex *port)
+{
+ mutex_lock(ext);
+ mutex_lock(port);
+}
+
+static int p3h2x4x_read_smbus_transaction_status(struct p3h2x4x *priv,
+ u8 target_port_status, u8 *status, u8 data_length)
+{
+ unsigned int status_read;
+ int ret;
+
+ mutex_unlock(&priv->etx_mutex);
+ fsleep(P3H2x4x_SMBUS_400kHz_TRANSFER_TIMEOUT(data_length));
+ mutex_lock(&priv->etx_mutex);
+
+ ret = regmap_read(priv->regmap, target_port_status, &status_read);
+ if (ret)
+ return ret;
+
+ *status = (u8)status_read;
+
+ if ((*status & P3H2x4x_TP_BUFFER_STATUS_MASK) == P3H2x4x_XFER_SUCCESS)
+ return 0;
+
+ dev_err(&priv->i3cdev->dev, "Status read timeout reached\n");
+ return 0;
+}
+
+/*
+ * 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 *priv,
+ struct i2c_msg *xfers,
+ u8 target_port,
+ u8 nxfers_i, u8 rw, u8 *return_status)
+{
+ u8 transaction_type = P3H2x4x_SMBUS_400kHz;
+ u8 controller_buffer_page = P3H2x4x_CONTROLLER_BUFFER_PAGE + 4 * target_port;
+ int write_length = xfers[nxfers_i].len;
+ int read_length = xfers[nxfers_i].len;
+ u8 target_port_status = P3H2x4x_TP0_SMBUS_AGNT_STS + target_port;
+ u8 addr = xfers[nxfers_i].addr;
+ u8 target_port_code = BIT(target_port);
+ u8 rw_address = 2 * addr;
+ u8 desc[P3H2x4x_SMBUS_DESCRIPTOR_SIZE] = { 0 };
+ u8 status;
+ int ret;
+
+ if (rw) {
+ rw_address |= BIT(0);
+ write_length = 0;
+ } else {
+ read_length = 0;
+ }
+
+ desc[0] = rw_address;
+ desc[1] = transaction_type;
+ desc[2] = write_length;
+ desc[3] = read_length;
+
+ ret = regmap_write(priv->regmap, target_port_status,
+ P3H2x4x_TP_BUFFER_STATUS_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, P3H2x4x_PAGE_PTR, controller_buffer_page);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_write(priv->regmap, P3H2x4x_CONTROLLER_AGENT_BUFF,
+ desc, P3H2x4x_SMBUS_DESCRIPTOR_SIZE);
+ if (ret)
+ return ret;
+
+ if (!rw) {
+ ret = regmap_bulk_write(priv->regmap,
+ P3H2x4x_CONTROLLER_AGENT_BUFF_DATA,
+ xfers[nxfers_i].buf, xfers[nxfers_i].len);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(priv->regmap, P3H2x4x_TP_SMBUS_AGNT_TRANS_START,
+ target_port_code);
+ if (ret)
+ return ret;
+
+ ret = p3h2x4x_read_smbus_transaction_status(priv, target_port_status,
+ &status, (write_length + read_length));
+ if (ret)
+ return ret;
+
+ *return_status = status;
+
+ if (rw) {
+ ret = regmap_bulk_read(priv->regmap,
+ P3H2x4x_CONTROLLER_AGENT_BUFF_DATA,
+ xfers[nxfers_i].buf, xfers[nxfers_i].len);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(priv->regmap, P3H2x4x_PAGE_PTR, 0x00);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * 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;
+ int ret;
+ u8 return_status;
+ u8 msg_count;
+ u8 rw;
+
+ struct device *dev;
+ struct tp_bus *bus =
+ container_of(adap, struct tp_bus, smbus_port_adapter);
+ struct p3h2x4x *priv = bus->priv;
+
+ if (priv->is_p3h2x4x_in_i3c)
+ dev = i3cdev_to_dev(priv->i3cdev);
+ else
+ dev = i2cdev_to_dev(priv->i2cdev);
+
+ lock_port_ext_mutex(&priv->etx_mutex, &bus->port_mutex);
+
+ if (priv->is_p3h2x4x_in_i3c) {
+ ret = i3c_device_disable_ibi(priv->i3cdev);
+ if (ret) {
+ dev_err(dev, "Failed to Disable IBI\n");
+ unlock_port_ext_mutex(&priv->etx_mutex, &bus->port_mutex);
+ return ret;
+ }
+ }
+
+ for (msg_count = 0; msg_count < num; msg_count++) {
+ if (msgs[msg_count].len > P3H2x4x_SMBUS_PAYLOAD_SIZE) {
+ dev_err(&adap->dev,
+ "Message nr. %d not sent - length over %d bytes.\n",
+ msg_count, P3H2x4x_SMBUS_PAYLOAD_SIZE);
+ continue;
+ }
+ rw = msgs[msg_count].flags % 2;
+
+ ret = p3h2x4x_tp_i2c_xfer_msg(priv,
+ msgs,
+ bus->tp_port,
+ msg_count, rw, &return_status);
+
+ if (ret)
+ goto error;
+
+ if (return_status == P3H2x4x_XFER_SUCCESS)
+ ret_sum++;
+ }
+
+error:
+ if (priv->is_p3h2x4x_in_i3c) {
+ ret = i3c_device_enable_ibi(priv->i3cdev);
+ if (ret) {
+ dev_err(dev, "Failed to Enable IBI\n");
+ unlock_port_ext_mutex(&priv->etx_mutex, &bus->port_mutex);
+ return ret;
+ }
+ }
+ unlock_port_ext_mutex(&priv->etx_mutex, &bus->port_mutex);
+ return ret_sum;
+}
+
+static int p3h2x4x_tp_smbus_xfer_msg(struct p3h2x4x *priv,
+ u8 target_port,
+ u8 addr,
+ u8 rw,
+ u8 cmd,
+ int sz,
+ union i2c_smbus_data *data,
+ u8 *return_status)
+{
+ u8 transaction_type = P3H2x4x_SMBUS_400kHz;
+ u8 controller_buffer_page = P3H2x4x_CONTROLLER_BUFFER_PAGE + 4 * target_port;
+ u8 target_port_status = P3H2x4x_TP0_SMBUS_AGNT_STS + target_port;
+ u8 target_port_code = BIT(target_port);
+ u8 rw_address = 2 * addr;
+ u8 desc[P3H2x4x_SMBUS_DESCRIPTOR_SIZE] = { 0 };
+ u8 status;
+ int ret, i;
+ u8 read_length, write_length;
+ u8 buf[I2C_SMBUS_BLOCK_MAX + 2] = {0};
+ struct device *dev;
+
+ if (priv->is_p3h2x4x_in_i3c)
+ dev = i3cdev_to_dev(priv->i3cdev);
+ else
+ dev = i2cdev_to_dev(priv->i2cdev);
+
+ /* Map the size to what the chip understands */
+ switch (sz) {
+ case I2C_SMBUS_QUICK:
+ case I2C_SMBUS_BYTE:
+ if (rw) {
+ buf[0] = data->byte;
+ read_length = ONE_BYTE_SIZE;
+ write_length = 0;
+ rw_address |= BIT(0);
+ } else {
+ buf[0] = cmd;
+ write_length = ONE_BYTE_SIZE;
+ read_length = 0;
+ }
+ break;
+ case I2C_SMBUS_BYTE_DATA:
+ if (rw) { /* read write */
+ buf[0] = cmd;
+ write_length = ONE_BYTE_SIZE;
+ read_length = ONE_BYTE_SIZE;
+ transaction_type |= BIT(0);
+ } else { /* only write */
+ buf[0] = cmd;
+ buf[1] = data->byte;
+ write_length = ONE_BYTE_SIZE + 1;
+ read_length = 0;
+ }
+ break;
+ case I2C_SMBUS_WORD_DATA:
+ if (rw) { /* read write */
+ buf[0] = cmd;
+ write_length = ONE_BYTE_SIZE;
+ read_length = 2;
+ transaction_type |= BIT(0);
+ } else { /* only write */
+ buf[0] = cmd;
+ buf[1] = data->word & 0xff;
+ buf[2] = (data->word & 0xff00) >> 8;
+ write_length = ONE_BYTE_SIZE + 2;
+ read_length = 0;
+ }
+ break;
+ case I2C_SMBUS_BLOCK_DATA:
+ if (rw) { /* read write */
+ buf[0] = cmd;
+ write_length = ONE_BYTE_SIZE;
+ read_length = data->block[0] + 1;
+ transaction_type |= BIT(0);
+ } else { /* only write */
+ buf[0] = cmd;
+ for (i = 0 ; i <= data->block[0]; i++)
+ buf[i+1] = data->block[i];
+
+ write_length = data->block[0] + 2;
+ read_length = 0;
+ }
+ break;
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ if (rw) { /* read write */
+ buf[0] = cmd;
+ write_length = ONE_BYTE_SIZE;
+ read_length = data->block[0];
+ transaction_type |= BIT(0);
+ } else { /* only write */
+ buf[0] = cmd;
+ for (i = 0 ; i < data->block[0]; i++)
+ buf[i+1] = data->block[i+1];
+
+ write_length = data->block[0] + 1;
+ read_length = 0;
+ }
+ break;
+ default:
+ dev_warn(dev, "Unsupported transaction %d\n", sz);
+ break;
+ }
+
+ desc[0] = rw_address;
+ desc[1] = transaction_type;
+ desc[2] = write_length;
+ desc[3] = read_length;
+
+ ret = regmap_write(priv->regmap, target_port_status,
+ P3H2x4x_TP_BUFFER_STATUS_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(priv->regmap, P3H2x4x_PAGE_PTR, controller_buffer_page);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_write(priv->regmap, P3H2x4x_CONTROLLER_AGENT_BUFF,
+ desc, P3H2x4x_SMBUS_DESCRIPTOR_SIZE);
+ if (ret)
+ return ret;
+
+ if (write_length) {
+ ret = regmap_bulk_write(priv->regmap,
+ P3H2x4x_CONTROLLER_AGENT_BUFF_DATA,
+ buf, write_length);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(priv->regmap, P3H2x4x_TP_SMBUS_AGNT_TRANS_START,
+ target_port_code);
+ if (ret)
+ return ret;
+
+ ret = p3h2x4x_read_smbus_transaction_status(priv, target_port_status, &status,
+ (write_length + read_length));
+ if (ret)
+ return ret;
+
+ *return_status = status;
+
+ if (rw) {
+ switch (sz) {
+ case I2C_SMBUS_QUICK:
+ case I2C_SMBUS_BYTE:
+ case I2C_SMBUS_BYTE_DATA:
+ {
+ ret = regmap_bulk_read(priv->regmap,
+ P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + write_length,
+ &data->byte, read_length);
+ break;
+ }
+ case I2C_SMBUS_WORD_DATA:
+ {
+ ret = regmap_bulk_read(priv->regmap,
+ P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + write_length,
+ (u8 *)&data->word, read_length);
+ break;
+ }
+ case I2C_SMBUS_BLOCK_DATA:
+ {
+ ret = regmap_bulk_read(priv->regmap,
+ P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + write_length,
+ data->block, read_length);
+ break;
+ }
+ case I2C_SMBUS_I2C_BLOCK_DATA:
+ {
+ ret = regmap_bulk_read(priv->regmap,
+ P3H2x4x_CONTROLLER_AGENT_BUFF_DATA + write_length,
+ data->block + 1, read_length);
+ break;
+ }
+ default:
+ dev_warn(dev, "Unsupported transaction %d\n", sz);
+ break;
+ }
+
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(priv->regmap, P3H2x4x_PAGE_PTR, 0x00);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static s32 p3h2x4x_tp_smbus_xfer(struct i2c_adapter *adap, u16 addr, unsigned short flags,
+ char read_write, u8 command, int size, union i2c_smbus_data *data)
+{
+ struct tp_bus *bus =
+ container_of(adap, struct tp_bus, smbus_port_adapter);
+
+ struct p3h2x4x *priv = bus->priv;
+ struct device *dev;
+
+ if (priv->is_p3h2x4x_in_i3c)
+ dev = i3cdev_to_dev(priv->i3cdev);
+ else
+ dev = i2cdev_to_dev(priv->i2cdev);
+
+ int ret, ret_status;
+ u8 return_status;
+
+ lock_port_ext_mutex(&priv->etx_mutex, &bus->port_mutex);
+
+ if (priv->is_p3h2x4x_in_i3c) {
+ ret = i3c_device_disable_ibi(priv->i3cdev);
+ if (ret) {
+ dev_err(dev, "Failed to Disable IBI\n");
+ unlock_port_ext_mutex(&priv->etx_mutex, &bus->port_mutex);
+ return ret;
+ }
+ }
+
+ ret_status = p3h2x4x_tp_smbus_xfer_msg(priv,
+ (u8)bus->tp_port,
+ (u8)addr,
+ (u8)read_write,
+ (u8)command,
+ size,
+ data,
+ &return_status);
+
+ if (priv->is_p3h2x4x_in_i3c) {
+ ret = i3c_device_enable_ibi(priv->i3cdev);
+ if (ret) {
+ dev_err(dev, "Failed to Enable IBI\n");
+ unlock_port_ext_mutex(&priv->etx_mutex, &bus->port_mutex);
+ return ret;
+ }
+ }
+ unlock_port_ext_mutex(&priv->etx_mutex, &bus->port_mutex);
+
+ if (ret_status)
+ return ret_status;
+
+ if (return_status == P3H2x4x_XFER_SUCCESS)
+ return 0;
+ else
+ return -EINVAL;
+}
+
+static u32 p3h2x4x_tp_smbus_funcs(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_WORD_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BLOCK_DATA |
+ I2C_FUNC_I2C;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int p3h2x4x_tp_i2c_reg_slave(struct i2c_client *slave)
+{
+ struct tp_bus *bus =
+ container_of(slave->adapter, struct tp_bus, smbus_port_adapter);
+ struct p3h2x4x *priv = bus->priv;
+
+ priv->is_slave_reg = true;
+
+ return 0;
+}
+static int p3h2x4x_tp_i2c_unreg_slave(struct i2c_client *slave)
+{
+ struct tp_bus *bus =
+ container_of(slave->adapter, struct tp_bus, smbus_port_adapter);
+ struct p3h2x4x *priv = bus->priv;
+
+ priv->is_slave_reg = false;
+
+ return 0;
+}
+#endif
+
+/*
+ * I2C algorithm Structure
+ */
+static struct i2c_algorithm p3h2x4x_tp_i2c_algorithm = {
+ .master_xfer = p3h2x4x_tp_i2c_xfer,
+ .smbus_xfer = p3h2x4x_tp_smbus_xfer,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+ .reg_slave = p3h2x4x_tp_i2c_reg_slave,
+ .unreg_slave = p3h2x4x_tp_i2c_unreg_slave,
+#endif
+ .functionality = p3h2x4x_tp_smbus_funcs,
+};
+
+/*
+ * p3h2x4x_tp_add_downstream_device - prove downstream devices for target port who
+ * configured as SMBus.
+ * @priv: p3h2x4x device structure.
+ * Return: 0 in case of success, a negative EINVAL code if the error.
+ */
+int p3h2x4x_tp_add_downstream_device(struct p3h2x4x *priv)
+{
+ struct i2c_board_info smbus_tp_device_info = {0};
+ struct smbus_device *backend = NULL;
+ struct tp_bus *bus;
+ int i;
+ struct device *dev;
+
+ if (priv->is_p3h2x4x_in_i3c)
+ dev = i3cdev_to_dev(priv->i3cdev);
+ else
+ dev = i2cdev_to_dev(priv->i2cdev);
+
+ for (i = 0; i < P3H2x4x_TP_MAX_COUNT; i++) {
+ bus = &priv->tp_bus[i];
+ if (!bus->is_registered)
+ continue;
+
+ list_for_each_entry(backend,
+ &bus->tp_device_entry, list) {
+
+ smbus_tp_device_info.addr = backend->addr;
+ smbus_tp_device_info.flags = I2C_CLIENT_SLAVE;
+ smbus_tp_device_info.of_node = backend->tp_device_dt_node;
+ snprintf(smbus_tp_device_info.type, I2C_NAME_SIZE, backend->compatible);
+ backend->client = i2c_new_client_device(&bus->smbus_port_adapter,
+ &smbus_tp_device_info);
+ if (IS_ERR(backend->client)) {
+ dev_warn(dev, "Error while registering backend\n");
+ return -EINVAL;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * p3h2x4x_tp_smbus_algo - add i2c adapter for target port who
+ * 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 *priv, int tp)
+{
+ int ret;
+ struct device *dev;
+
+ if (priv->is_p3h2x4x_in_i3c)
+ dev = i3cdev_to_dev(priv->i3cdev);
+ else
+ dev = i2cdev_to_dev(priv->i2cdev);
+
+ priv->tp_bus[tp].smbus_port_adapter.owner = THIS_MODULE;
+ priv->tp_bus[tp].smbus_port_adapter.class = I2C_CLASS_HWMON;
+ priv->tp_bus[tp].smbus_port_adapter.algo = &p3h2x4x_tp_i2c_algorithm;
+
+ sprintf(priv->tp_bus[tp].smbus_port_adapter.name, "p3h2x4x-cp-%d.tp-port-%d",
+ p3h2x4x_read_p3h2x4x_id(dev), tp);
+
+ ret = i2c_add_adapter(&priv->tp_bus[tp].smbus_port_adapter);
+ if (ret) {
+ dev_warn(dev, "failled to add adapter for tp %d\n", tp);
+ return -EINVAL;
+ }
+ priv->tp_bus[tp].is_registered = true;
+
+ return 0;
+}
diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
index b674f64d0822..5ab199abb653 100644
--- a/include/linux/i3c/device.h
+++ b/include/linux/i3c/device.h
@@ -77,6 +77,7 @@ struct i3c_priv_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
Powered by blists - more mailing lists