public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB
@ 2026-04-30 12:13 zain_zhou
  2026-04-30 12:13 ` [PATCH 2/2] staging: i3c: add Realtek RTS490x I3C HUB driver zain_zhou
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: zain_zhou @ 2026-04-30 12:13 UTC (permalink / raw)
  To: linux-staging, linux-i3c, devicetree
  Cc: gregkh, alexandre.belloni, Frank.Li, robh, krzk+dt, conor+dt,
	linusw, brgl, linux-gpio, linux-kernel, zain_zhou

From: zain_zhou <zain_zhou@realsil.com.cn>

Add DT binding schema for Realtek RTS490x series I3C HUB devices.

The binding describes configuration properties for:
  - LDO enable/disable and voltage level per port group
  - Pull-up resistance per port group
  - IO driver strength per port
  - Per target-port mode (I3C/SMBus/GPIO/disabled), pull-up,
    IO mode, SMBus clock frequency and polling interval
  - Hub network always-I3C mode
  - Hardware identification via CSEL pin (id) and CP1 pins (id-cp1)

Signed-off-by: zain_zhou <zain_zhou@realsil.com.cn>
---
 .../bindings/i3c/realtek,rts490x-i3c-hub.yaml | 410 ++++++++++++++++++
 MAINTAINERS                                   |   6 +
 2 files changed, 416 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml

diff --git a/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml b/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml
new file mode 100644
index 000000000000..30295eefee89
--- /dev/null
+++ b/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml
@@ -0,0 +1,410 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/i3c/realtek,rts490x-i3c-hub.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: I3C HUB
+
+maintainers:
+  - zain_zhou <zain_zhou@realsil.com.cn>
+
+description: |
+  I3C HUB is smart device which provides multiple functionality:
+  * enabling voltage compatibility across I3C Controller and Target devices,
+  * bus capacitance isolation
+  * address conflict isolation
+  * I3C port expansion
+  * two controllers in a single I3C bus
+  * I3C and SMBus device compatibility
+  * GPIO expansion
+
+  Having such big number of features, there is a need to have some DT knobs to tell the I3C HUB
+  driver which features shall be enabled and how they shall be configured. I3C HUB driver read,
+  validate DT knobs and set corresponding registers with the right way to satisfy user requests from
+  DT.
+
+  All the DT properties for I3C HUB are located under dedicated (for I3C HUB) DT entry. I3C HUB DT
+  entry structure is aligned with regular I3C device DT entry described in i3c.yaml.
+
+allOf:
+  - $ref: i3c.yaml#
+
+properties:
+  $nodename:
+    pattern: "^hub@0,0$"
+
+  cp0-ldo-en:
+    enum:
+      - disabled
+      - enabled
+    description: |
+      I3C HUB Controller Port 0 LDO disabling/enabling setting. If enabled, voltage produced by
+      on-die LDO will be available externally on dedicated pin. This option could be used to supply
+      external pull-up resistors or for any other purpose which does not cross LDO capabilities.
+
+      This property is optional. If not provided, LDO will be disabled.
+
+  cp1-ldo-en:
+    enum:
+      - disabled
+      - enabled
+    description: |
+      I3C HUB Controller Port 1 LDO disabling/enabling setting. If enabled, voltage produced by
+      on-die LDO will be available externally on dedicated pin. This option could be used to supply
+      external pull-up resistors or for any other purpose which does not cross LDO capabilities.
+
+      This property is optional. If not provided, LDO will be disabled.
+
+  tp0145-ldo-en:
+    enum:
+      - disabled
+      - enabled
+    description: |
+      I3C HUB Target Ports 0/1/4/5 LDO disabling/enabling setting. If enabled, voltage produced by
+      on-die LDO will be available externally on dedicated pin. This option could be used to supply
+      external pull-up resistors or for any other purpose which does not cross LDO capabilities.
+
+      This property is optional. If not provided, LDO will be disabled.
+
+  tp2367-ldo-en:
+    enum:
+      - disabled
+      - enabled
+    description: |
+      I3C HUB Target Ports 2/3/6/7 LDO disabling/enabling setting. If enabled, voltage produced by
+      on-die LDO will be available externally on dedicated pin. This option could be used to supply
+      external pull-up resistors or for any other purpose which does not cross LDO capabilities.
+
+      This property is optional. If not provided, LDO will be disabled.
+
+  cp0-ldo-volt:
+    enum:
+      - 1.0V
+      - 1.1V
+      - 1.2V
+      - 1.8V
+    description: |
+      I3C HUB Controller Port 0 LDO setting to control the Controller Port 1 voltage level. This
+      property is optional.
+
+      If not provided, LDO configuration is not modified in I3C HUB.
+
+  cp1-ldo-volt:
+    enum:
+      - 1.0V
+      - 1.1V
+      - 1.2V
+      - 1.8V
+    description: |
+      I3C HUB Controller Port 1 LDO setting to control the Controller Port 1 voltage level. This
+      property is optional.
+
+      If not provided, LDO configuration is not modified in I3C HUB.
+
+  tp0145-ldo-volt:
+    enum:
+      - disabled
+      - 1.0V
+      - 1.1V
+      - 1.2V
+      - 1.8V
+    description: |
+      I3C HUB Target Ports 0/1/4/5 LDO setting to control the Target Ports 0/1/4/5 voltage level.
+
+      If not provided, LDO configuration is not modified in I3C HUB.
+
+  tp2367-ldo-volt:
+    enum:
+      - disabled
+      - 1.0V
+      - 1.1V
+      - 1.2V
+      - 1.8V
+    description: |
+      I3C HUB Target Ports 2/3/6/7 LDO setting to control the Target Ports 2/3/6/7 voltage level.
+
+      If not provided, LDO configuration is not modified in I3C HUB.
+
+  tp0145-pullup:
+    enum:
+      - disabled
+      - 250R
+      - 500R
+      - 1k
+      - 2k
+    description: |
+      I3C HUB Target Ports 0/1/4/5 pull-up setting to control the Target Ports 0/1/4/5 pull-up
+      resistance level.
+
+      This property is optional. If not provided, pull-up configuration is not modified in I3C HUB.
+
+  tp2367-pullup:
+    enum:
+      - disabled
+      - 250R
+      - 500R
+      - 1k
+      - 2k
+    description: |
+      I3C HUB Target Ports 2/3/6/7 pull-up setting to control the Target Ports 2/3/6/7 pull-up
+      resistance level.
+
+      This property is optional. If not provided, pull-up configuration is not modified in I3C HUB.
+
+  cp0-io-strength:
+    enum:
+      - 20Ohms
+      - 30Ohms
+      - 40Ohms
+      - 50Ohms
+    description: |
+      I3C HUB Controller Port 0 IO strength setting to control the Controller Port 0 output driver
+      strength.
+
+      This property is optional. If not provided, IO strength configuration is not modified in I3C
+      HUB.
+
+  cp1-io-strength:
+    enum:
+      - 20Ohms
+      - 30Ohms
+      - 40Ohms
+      - 50Ohms
+    description: |
+      I3C HUB Controller Port 1 IO strength setting to control the Controller Port 1 output driver
+      strength.
+
+      This property is optional. If not provided, IO strength configuration is not modified in I3C
+      HUB.
+
+  tp0145-io-strength:
+    enum:
+      - 20Ohms
+      - 30Ohms
+      - 40Ohms
+      - 50Ohms
+    description: |
+      I3C HUB Target Ports 0/1/4/5 IO strength setting to control the Target Ports 0/1/4/5 output
+      driver strength.
+
+      This property is optional. If not provided, IO strength configuration is not modified in I3C
+      HUB.
+
+  tp2367-io-strength:
+    enum:
+      - 20Ohms
+      - 30Ohms
+      - 40Ohms
+      - 50Ohms
+    description: |
+      I3C HUB Target Ports 2/3/6/7 IO strength setting to control the Target Ports 2/3/6/7 output
+      driver strength.
+
+      This property is optional. If not provided, IO strength configuration is not modified in I3C
+      HUB.
+
+  id:
+    enum:
+      - 0
+      - 1
+      - 3
+    description: |
+      I3C HUB ID based on CSEL pin. There are three possible values:
+      0 - CP0 is selected as primary Controller Port
+      1 - Primary Controller Port is selected by software by writing the REG#56
+      3 - CP1 is selected as primary Controller Port
+
+      I3C HUB driver reads CSEL pin status (REG#121[5:4]) and tries to find DT node with matching
+      value in 'id' property.
+
+      This property is optional. If not provided, DT node can only be used by the I3C HUB driver if
+      there is no others with matching 'id' or 'id-cp1'. If there is a multiple DT nodes with no
+      'id' property - the first one will be chosen by I3C HUB driver. If there is a multiple DT
+      nodes with matching 'id' property - the first one will be chosen by I3C HUB driver.
+
+      If both 'id' and 'id-cp1' are available, DT node will chosen only when both values match those
+      read from I3C HUB.
+
+  id-cp1:
+    enum:
+      - 0
+      - 1
+      - 2
+      - 3
+    description: |
+      I3C HUB ID based on CP1 SDA and SCL pins state probed during power on.
+
+      I3C HUB driver reads CP1 SDA and SCL pin status and tries to find DT node with matching value
+      in 'id-cp1' property.
+
+      This property is optional. If not provided, DT node can only be used by the I3C HUB
+      driver if there is no others with matching 'id' or 'id-cp1'. If there is a multiple DT nodes
+      with no 'id-cp1' property - the first one will be chosen by I3C HUB driver. If there is a
+      multiple DT nodes with matching 'id-cp1' property - the first one will be chosen by I3C HUB
+      driver.
+
+      If both 'id' and 'id-cp1' are available, DT node will chosen only when both values match those
+      read from I3C HUB.
+
+patternProperties:
+  "@[0-9]$":
+    type: object
+    description: |
+      I3C HUB Target Port child, should be named: target-port@<target-port-id>
+
+    properties:
+      mode:
+        enum:
+          - disabled
+          - i3c
+          - smbus
+          - gpio
+        description: |
+          I3C HUB Target Port mode setting to control Target Port functionality.
+
+          This property is optional. If not provided, Target Port mode configuration is not modified
+          in I3C HUB.
+
+      pullup:
+        enum:
+          - disabled
+          - enabled
+        description: |
+          I3C HUB Target Port pull-up setting to disable/enable Target Port pull-up.
+
+          This property is optional. If not provided, Target Port pull-up configuration is not
+          modified in I3C HUB.
+
+      always-enable:
+        type: boolean
+        description: |
+          I3C HUB Target Port settings to control the port enable/disable policy.
+
+          This property is optional. If not provided, Target Port is enabled only on accessing to
+          the devices connected to it and the port is disabled automatically after the accessing
+          is done. If provided, the Target Port is always enabled.
+
+      polling-interval-ms:
+        type: uint32
+        description: |
+          I3C HUB Target Port SMBus polling interval in milliseconds.
+
+          This property is optional. If not provided or set to 0, polling is disabled and the driver
+          uses IBI (In-Band Interrupts). If provided with a positive value, polling is enabled for
+          this Target Port with the given period.Note: this positive value only affects the SMBus
+          target agent polling. The SMBus controller agent polling interval is computed dynamically
+          from clock and data length.
+
+      clock-frequency:
+        type: uint32
+        enum:
+          - 100000
+          - 200000
+          - 400000
+          - 1000000
+        description: |
+          I3C HUB Target Port SMBus clock frequency in Hz.
+
+          This property follows the standard I2C 'clock-frequency' semantics.
+          Applies only when the Target Port mode is set to "smbus".
+          Optional; if not provided, the driver uses 400000 Hz by default.
+
+additionalProperties: true
+
+examples:
+  - |
+    i3c-master@d040000 {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      hub@0,0 {
+        cp0-ldo-en = "disabled";
+        cp1-ldo-en = "enabled";
+        cp0-ldo-volt = "1.0V";
+        cp1-ldo-volt = "1.1V";
+        tp0145-ldo-en = "enabled";
+        tp2367-ldo-en = "disabled";
+        tp0145-ldo-volt = "1.2V";
+        tp2367-ldo-volt = "1.8V";
+        tp0145-pullup = "2k";
+        tp2367-pullup = "500R";
+        tp0145-io-strength = "50Ohms";
+        tp2367-io-strength = "30Ohms";
+        cp0-io-strength = "20Ohms";
+        cp1-io-strength = "40Ohms";
+
+        target-port@0 {
+          mode = "i3c";
+          pullup = "enabled";
+          always_enable;
+        };
+        target-port@1 {
+          mode = "smbus";
+          pullup = "enabled";
+          clock-frequency = <1000000>;
+          polling-interval-ms = <10>;
+          backend@10{
+              compatible = "i2c-slave-mqueue";
+              reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>;
+          };
+        };
+        target-port@2 {
+          mode = "gpio";
+          pullup = "disabled";
+        };
+        target-port@3 {
+          mode = "disabled";
+          pullup = "disabled";
+        };
+      };
+    };
+
+  - |
+    i3c-master@d040000 {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      hub@70,3C000000100 {
+        reg = <0x70 0x3C0 0x00000100>;
+        assigned-address = <0x70>;
+        dcr = <0xC2>;
+
+        cp0-ldo-en = "disabled";
+        cp1-ldo-en = "enabled";
+        cp0-ldo-volt = "1.0V";
+        cp1-ldo-volt = "1.1V";
+        tp0145-ldo-en = "enabled";
+        tp2367-ldo-en = "disabled";
+        tp0145-ldo-volt = "1.2V";
+        tp2367-ldo-volt = "1.8V";
+        tp0145-pullup = "2k";
+        tp2367-pullup = "500R";
+        tp0145-io-strength = "50Ohms";
+        tp2367-io-strength = "30Ohms";
+        cp0-io-strength = "20Ohms";
+        cp1-io-strength = "40Ohms";
+
+        target-port@0 {
+          mode = "i3c";
+          pullup = "enabled";
+          always-enable;
+        };
+        target-port@1 {
+          mode = "smbus";
+          pullup = "enabled";
+          backend@12{
+              compatible = "i2c-slave-mqueue";
+              reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>;
+          };
+        };
+        target-port@2 {
+          mode = "gpio";
+          pullup = "disabled";
+        };
+        target-port@3 {
+          mode = "disabled";
+          pullup = "disabled";
+        };
+      };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 2fb1c75afd16..71ee5071ac0f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12214,6 +12214,12 @@ S:	Supported
 F:	Documentation/devicetree/bindings/i3c/renesas,i3c.yaml
 F:	drivers/i3c/master/renesas-i3c.c
 
+I3C HUB DRIVER FOR REALTEK RTS490X
+M:	zain_zhou <zain_zhou@realsil.com.cn>
+S:	Maintained
+F:	Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml
+F:	drivers/staging/rts490x/
+
 I3C DRIVER FOR SYNOPSYS DESIGNWARE
 S:	Orphan
 F:	Documentation/devicetree/bindings/i3c/snps,dw-i3c-master.yaml
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/2] staging: i3c: add Realtek RTS490x I3C HUB driver
  2026-04-30 12:13 [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB zain_zhou
@ 2026-04-30 12:13 ` zain_zhou
  2026-04-30 13:41 ` [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB Rob Herring (Arm)
  2026-05-01  9:37 ` Krzysztof Kozlowski
  2 siblings, 0 replies; 4+ messages in thread
From: zain_zhou @ 2026-04-30 12:13 UTC (permalink / raw)
  To: linux-staging, linux-i3c, devicetree
  Cc: gregkh, alexandre.belloni, Frank.Li, robh, krzk+dt, conor+dt,
	linusw, brgl, linux-gpio, linux-kernel, zain_zhou

From: zain_zhou <zain_zhou@realsil.com.cn>

Add driver for Realtek RTS490x series I3C HUB devices (RTS4900,
RTS4901, RTS4902, RTS4903, RTS4904, RTS4906).

The I3C HUB is a smart device that provides:
  - voltage compatibility across I3C Controller and Target devices
  - bus capacitance isolation
  - address conflict isolation
  - I3C port expansion (up to 8 target ports)
  - dual controller port support
  - I3C and SMBus device compatibility
  - GPIO expansion via target ports

The driver supports:
  - Device Tree based configuration of LDO, pull-up, IO strength
    and per-port mode (I3C/SMBus/GPIO/disabled)
  - Logical I3C bus registration per target port
  - SMBus agent functionality with IBI and polling modes
  - GPIO chip with IRQ support
  - DebugFS interface for register access and DT config inspection
  - IBI (In-Band Interrupt) handling

The driver is placed in staging as it has known issues to be resolved
before mainlining; see drivers/staging/rts490x/TODO for details.

Signed-off-by: zain_zhou <zain_zhou@realsil.com.cn>
---
 drivers/staging/Kconfig                    |    2 +
 drivers/staging/Makefile                   |    1 +
 drivers/staging/rts490x/Kconfig            |   16 +
 drivers/staging/rts490x/Makefile           |    2 +
 drivers/staging/rts490x/TODO               |   19 +
 drivers/staging/rts490x/rts490xa-i3c-hub.c | 3162 ++++++++++++++++++++
 6 files changed, 3202 insertions(+)
 create mode 100644 drivers/staging/rts490x/Kconfig
 create mode 100644 drivers/staging/rts490x/Makefile
 create mode 100644 drivers/staging/rts490x/TODO
 create mode 100644 drivers/staging/rts490x/rts490xa-i3c-hub.c

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 2f92cd698bef..f14869cf5af5 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -48,4 +48,6 @@ source "drivers/staging/axis-fifo/Kconfig"
 
 source "drivers/staging/vme_user/Kconfig"
 
+source "drivers/staging/rts490x/Kconfig"
+
 endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index f5b8876aa536..59044d547bf7 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_MOST)		+= most/
 obj-$(CONFIG_GREYBUS)		+= greybus/
 obj-$(CONFIG_BCM2835_VCHIQ)	+= vc04_services/
 obj-$(CONFIG_XIL_AXIS_FIFO)	+= axis-fifo/
+obj-$(CONFIG_RTS490X_I3C_HUB)	+= rts490x/
diff --git a/drivers/staging/rts490x/Kconfig b/drivers/staging/rts490x/Kconfig
new file mode 100644
index 000000000000..282865d1fed9
--- /dev/null
+++ b/drivers/staging/rts490x/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config RTS490X_I3C_HUB
+	tristate "Realtek RTS490x I3C HUB driver"
+	depends on I3C
+	depends on REGMAP_I3C
+	select GPIOLIB
+	help
+	  Support for Realtek RTS490x series I3C HUB devices (RTS4900,
+	  RTS4901, RTS4902, RTS4903, RTS4904, RTS4906).
+
+	  The I3C HUB provides port expansion, voltage level translation,
+	  bus capacitance isolation, address conflict isolation, SMBus
+	  agent functionality and GPIO expansion.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rts490xa-i3c-hub.
diff --git a/drivers/staging/rts490x/Makefile b/drivers/staging/rts490x/Makefile
new file mode 100644
index 000000000000..4b1d7640fb82
--- /dev/null
+++ b/drivers/staging/rts490x/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_RTS490X_I3C_HUB)	+= rts490xa-i3c-hub.o
diff --git a/drivers/staging/rts490x/TODO b/drivers/staging/rts490x/TODO
new file mode 100644
index 000000000000..0be2d7693d68
--- /dev/null
+++ b/drivers/staging/rts490x/TODO
@@ -0,0 +1,19 @@
+TODO list for rts490xa-i3c-hub staging driver
+==============================================
+
+- Move driver out of staging once the following are addressed:
+  - Add proper DT binding schema validation (dt-schema)
+  - Clean up open-coded OF property parsing; use device_property_* APIs
+    instead of of_property_read_* where possible
+  - Remove use of full_name / sscanf for node name parsing; use
+    of_node_name_eq() and fwnode helpers instead
+  - Replace global mutex (i3c_hub_regmap_mutex) with per-device locking
+  - Add kernel-doc comments for all exported/public functions
+  - Resolve TODO comment in i3c_hub_hw_configure_tp() regarding MUX
+    connection verification
+  - Remove TBD comment in i3c_hub_probe() regarding DEV_CMD security lock
+  - Review and fix potential locking issues in i3c_hub_delayed_work()
+    when registering logical buses
+  - Fix error handling in i3c_hub_delayed_work(): early return on failure
+    does not unregister already-registered logical buses, causing resource
+    leak; needs proper cleanup on error path
diff --git a/drivers/staging/rts490x/rts490xa-i3c-hub.c b/drivers/staging/rts490x/rts490xa-i3c-hub.c
new file mode 100644
index 000000000000..b557cfe89013
--- /dev/null
+++ b/drivers/staging/rts490x/rts490xa-i3c-hub.c
@@ -0,0 +1,3162 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 - 2023 Intel Corporation. */
+/* Copyright (c) 2025 Realtek Semiconductor Corp. */
+
+#include <linux/bits.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/bitfield.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/list.h>
+#include <linux/gpio/driver.h>
+
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
+
+#define I3C_HUB_TP_MAX_COUNT 0x08
+
+#define I3C_DCR_HUB 0xC2
+
+#define GPIO_BANK_SZ  0x02
+#define GPIO_MAX_BANK I3C_HUB_TP_MAX_COUNT
+
+/* I3C HUB REGISTERS */
+
+/*
+ * In this driver Controller - Target convention is used. All the abbreviations are
+ * based on this convention. For instance: CP - Controller Port, TP - Target Port.
+ */
+
+/* Device Information Registers */
+#define I3C_HUB_DEV_INFO_0 0x00
+#define I3C_HUB_DEV_INFO_1 0x01
+#define I3C_HUB_PID_5	   0x02
+#define I3C_HUB_PID_4	   0x03
+#define I3C_HUB_PID_3	   0x04
+#define I3C_HUB_PID_2	   0x05
+#define I3C_HUB_PID_1	   0x06
+#define I3C_HUB_PID_0	   0x07
+#define I3C_HUB_BCR	   0x08
+#define I3C_HUB_DCR	   0x09
+#define I3C_HUB_DEV_CAPAB  0x0A
+
+#define I3C_HUB_DEV_REV	   0x0B
+#define I3C_HUB_DEV_REV_LDO_MASK   GENMASK(7, 6)
+#define I3C_HUB_DEV_REV_LDO_GET(x) FIELD_GET(I3C_HUB_DEV_REV_LDO_MASK, (x))
+
+/* Device Configuration Registers */
+#define I3C_HUB_PROTECTION_CODE	  0x10
+#define REGISTERS_LOCK_CODE	  0x00
+#define REGISTERS_UNLOCK_CODE	  0x69
+#define CP1_REGISTERS_UNLOCK_CODE 0x6A
+
+#define I3C_HUB_CP_CONF	  0x11
+#define I3C_HUB_TP_ENABLE 0x12
+#define TPn_ENABLE(n)	  BIT(n)
+
+#define I3C_HUB_DEV_CONF	0x13
+#define I3C_HUB_IO_STRENGTH	0x14
+#define TP0145_IO_STRENGTH_MASK GENMASK(1, 0)
+#define TP0145_IO_STRENGTH(x)	(((x) << 0) & TP0145_IO_STRENGTH_MASK)
+#define TP2367_IO_STRENGTH_MASK GENMASK(3, 2)
+#define TP2367_IO_STRENGTH(x)	(((x) << 2) & TP2367_IO_STRENGTH_MASK)
+#define CP0_IO_STRENGTH_MASK	GENMASK(5, 4)
+#define CP0_IO_STRENGTH(x)	(((x) << 4) & CP0_IO_STRENGTH_MASK)
+#define CP1_IO_STRENGTH_MASK	GENMASK(7, 6)
+#define CP1_IO_STRENGTH(x)	(((x) << 6) & CP1_IO_STRENGTH_MASK)
+#define IO_STRENGTH_20_OHM	0x00
+#define IO_STRENGTH_30_OHM	0x01
+#define IO_STRENGTH_40_OHM	0x02
+#define IO_STRENGTH_50_OHM	0x03
+
+#define I3C_HUB_NET_OPER_MODE_CONF 0x15
+#define I3C_HUB_NET_ALWAYS_I3C_EN  BIT(5)
+
+#define I3C_HUB_LDO_CONF	   0x16
+#define CP0_LDO_VOLTAGE_MASK	   GENMASK(1, 0)
+#define CP0_LDO_VOLTAGE(x)	   (((x) << 0) & CP0_LDO_VOLTAGE_MASK)
+#define CP1_LDO_VOLTAGE_MASK	   GENMASK(3, 2)
+#define CP1_LDO_VOLTAGE(x)	   (((x) << 2) & CP1_LDO_VOLTAGE_MASK)
+#define TP0145_LDO_VOLTAGE_MASK	   GENMASK(5, 4)
+#define TP0145_LDO_VOLTAGE(x)	   (((x) << 4) & TP0145_LDO_VOLTAGE_MASK)
+#define TP2367_LDO_VOLTAGE_MASK	   GENMASK(7, 6)
+#define TP2367_LDO_VOLTAGE(x)	   (((x) << 6) & TP2367_LDO_VOLTAGE_MASK)
+#define LDO_VOLTAGE_1_0V	   0x00
+#define LDO_VOLTAGE_1_1V	   0x01
+#define LDO_VOLTAGE_1_2V	   0x02
+#define LDO_VOLTAGE_1_8V	   0x03
+
+#define I3C_HUB_TP_IO_MODE_CONF	 0x17
+#define TPn_IO_MODE_CON(n)	 BIT(n)
+#define I3C_HUB_TP_SMBUS_AGNT_EN 0x18
+#define TPn_SMBUS_MODE_EN(n)	 BIT(n)
+
+#define I3C_HUB_LDO_AND_PULLUP_CONF 0x19
+#define LDO_ENABLE_DISABLE_MASK	    GENMASK(3, 0)
+#define CP0_LDO_EN		    BIT(0)
+#define CP1_LDO_EN		    BIT(1)
+/*
+ * I3C HUB does not provide a way to control LDO or pull-up for individual ports. It is possible
+ * for group of ports TP0/TP1/TP4/TP5 and TP2/TP3/TP6/TP7.
+ */
+#define TP0145_LDO_EN		    BIT(2)
+#define TP2367_LDO_EN		    BIT(3)
+#define TP0145_PULLUP_CONF_MASK	    GENMASK(7, 6)
+#define TP0145_PULLUP_CONF(x)	    (((x) << 6) & TP0145_PULLUP_CONF_MASK)
+#define TP2367_PULLUP_CONF_MASK	    GENMASK(5, 4)
+#define TP2367_PULLUP_CONF(x)	    (((x) << 4) & TP2367_PULLUP_CONF_MASK)
+#define PULLUP_250R		    0x00
+#define PULLUP_500R		    0x01
+#define PULLUP_1K		    0x02
+#define PULLUP_2K		    0x03
+
+#define I3C_HUB_CP_IBI_CONF	 0x1A
+#define I3C_HUB_TP_IBI_CONF	 0x1B
+#define I3C_HUB_IBI_MDB_CUSTOM	 0x1C
+#define I3C_HUB_JEDEC_CONTEXT_ID 0x1D
+#define I3C_HUB_TP_GPIO_MODE_EN	 0x1E
+#define TPn_GPIO_MODE_EN(n)	 BIT(n)
+
+/* Device Status and IBI Registers */
+#define I3C_HUB_DEV_AND_IBI_STS	      0x20
+#define PEC_ERROR_FLAG		      BIT(0)
+#define PARITY_ERROR_FLAG	      BIT(1)
+#define CONTROLLER_MSG_PENDING_FLAG   BIT(2)
+#define TP_IO_FLAG_STATUS	      BIT(3)
+#define SMBUS_AGENT_EVENT_FLAG_STATUS BIT(4)
+
+#define I3C_HUB_TP_SMBUS_AGNT_IBI_STS 0x21
+
+/* Controller Port Control/Status Registers */
+#define I3C_HUB_CP_MUX_SET		      0x38
+#define CONTROLLER_PORT_MUX_REQ		      BIT(0)
+#define I3C_HUB_CP_MUX_STS		      0x39
+#define CONTROLLER_PORT_MUX_CONNECTION_STATUS BIT(0)
+
+/* Target Dynamic Address Assignment Flag Registers */
+#define I3C_HUB_TARGET_DA_FLAG_BYTE_BASE  0x40
+#define I3C_HUB_TARGET_DA_FLAG_BYTE_COUNT 16
+
+/* Target Ports Control Registers */
+#define I3C_HUB_TP_SMBUS_AGNT_TRANS_START 0x50
+#define I3C_HUB_TP_NET_CON_CONF		  0x51
+#define TPn_NET_CON(n)			  BIT(n)
+
+#define I3C_HUB_TP_PULLUP_EN 0x53
+#define TPn_PULLUP_EN(n)     BIT(n)
+
+#define I3C_HUB_TP_SCL_OUT_EN	 0x54
+#define I3C_HUB_TP_SDA_OUT_EN	 0x55
+#define I3C_HUB_TP_SCL_OUT_LEVEL 0x56
+#define I3C_HUB_TP_SDA_OUT_LEVEL 0x57
+
+#define I3C_HUB_TP_IN_DETECT_MODE_CONF 0x58
+#define SCL0145_IO_IN_DET_CFG_MASK     GENMASK(1, 0)
+#define SCL0145_IO_IN_DET_CFG(x)       (((x) << 0) & SCL0145_IO_IN_DET_CFG_MASK)
+#define SDA0145_IO_IN_DET_CFG_MASK     GENMASK(3, 2)
+#define SDA0145_IO_IN_DET_CFG(x)       (((x) << 2) & SDA0145_IO_IN_DET_CFG_MASK)
+#define SCL2367_IO_IN_DET_CFG_MASK     GENMASK(5, 4)
+#define SCL2367_IO_IN_DET_CFG(x)       (((x) << 4) & SCL2367_IO_IN_DET_CFG_MASK)
+#define SDA2367_IO_IN_DET_CFG_MASK     GENMASK(7, 6)
+#define SDA2367_IO_IN_DET_CFG(x)       (((x) << 6) & SDA2367_IO_IN_DET_CFG_MASK)
+
+#define I3C_HUB_TP_SCL_IN_DETECT_IBI_EN 0x59
+#define I3C_HUB_TP_SDA_IN_DETECT_IBI_EN 0x5A
+
+/* Target Ports Status Registers */
+#define I3C_HUB_TP_SCL_IN_LEVEL_STS  0x60
+#define I3C_HUB_TP_SDA_IN_LEVEL_STS  0x61
+#define I3C_HUB_TP_SCL_IN_DETECT_FLG 0x62
+#define I3C_HUB_TP_SDA_IN_DETECT_FLG 0x63
+
+/* SMBus Agent Configuration and Status Registers */
+#define I3C_HUB_TP0_SMBUS_AGNT_STS	      0x64
+#define I3C_HUB_TP1_SMBUS_AGNT_STS	      0x65
+#define I3C_HUB_TP2_SMBUS_AGNT_STS	      0x66
+#define I3C_HUB_TP3_SMBUS_AGNT_STS	      0x67
+#define I3C_HUB_TP4_SMBUS_AGNT_STS	      0x68
+#define I3C_HUB_TP5_SMBUS_AGNT_STS	      0x69
+#define I3C_HUB_TP6_SMBUS_AGNT_STS	      0x6A
+#define I3C_HUB_TP7_SMBUS_AGNT_STS	      0x6B
+
+#define I3C_HUB_ONCHIP_TD_AND_SMBUS_AGNT_CONF 0x6C
+#define TARGET_AGENT_BUF_FULL_SDA_LOW_EN      BIT(5)
+
+/* Transaction status checking mask */
+#define I3C_HUB_CONTROLLER_AGENT_STATUS_MASK		   (0xF0 | BIT(0))
+#define I3C_HUB_CONTROLLER_AGENT_FINISH_FLAG		   BIT(0)
+/* SMBus Controller Agent Return Codes */
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_SHIFT		   4
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_SUCCESS	   0x0
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_ADDRESS_NACK	   0x1
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_DEVICE_BUSY	   0x2
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_READ_NOT_READY   0x3
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_SYNC_RECOVERED   0x4
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_SYNC_BUS_CLEAR   0x5
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_BUS_FAULT	   0x6
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_ARBITRATION_LOST 0x7
+#define I3C_HUB_CONTROLLER_AGENT_RET_CODE_SCL_TIMEOUT	   0x8
+
+#define I3C_HUB_TARGET_BUF_STATUS_MASK GENMASK(3, 1)
+#define I3C_HUB_TARGET_BUF_0_RECEIVE   BIT(1)
+#define I3C_HUB_TARGET_BUF_1_RECEIVE   BIT(2)
+#define I3C_HUB_TARGET_BUF_OVRFL       BIT(3)
+
+/* Special Function Registers */
+#define I3C_HUB_LDO_AND_CPSEL_STS     0x79
+#define CP_SDA1_LEVEL		      BIT(7)
+#define CP_SCL1_LEVEL		      BIT(6)
+#define CP_SEL_PIN_INPUT_CODE_MASK    GENMASK(5, 4)
+#define CP_SEL_PIN_INPUT_CODE_GET(x)  (((x) & CP_SEL_PIN_INPUT_CODE_MASK) >> 4)
+#define CP_SDA1_SCL1_PINS_CODE_MASK   GENMASK(7, 6)
+#define CP_SDA1_SCL1_PINS_CODE_GET(x) (((x) & CP_SDA1_SCL1_PINS_CODE_MASK) >> 6)
+#define VCCIO1_PWR_GOOD		      BIT(3)
+#define VCCIO0_PWR_GOOD		      BIT(2)
+#define CP1_VCCIO_PWR_GOOD	      BIT(1)
+#define CP0_VCCIO_PWR_GOOD	      BIT(0)
+
+#define I3C_HUB_BUS_RESET_SCL_TIMEOUT	0x7A
+#define I3C_HUB_ONCHIP_TD_PROTO_ERR_FLG 0x7B
+#define I3C_HUB_DEV_CMD			0x7C
+#define I3C_HUB_ONCHIP_TD_STS		0x7D
+#define I3C_HUB_ONCHIP_TD_ADDR_CONF	0x7E
+#define I3C_HUB_PAGE_PTR		0x7F
+
+/* LDO Disable/Enable DT settings */
+#define I3C_HUB_DT_LDO_DISABLED	   0x00
+#define I3C_HUB_DT_LDO_ENABLED	   0x01
+#define I3C_HUB_DT_LDO_NOT_DEFINED 0xFF
+
+/* LDO Voltage DT settings */
+#define I3C_HUB_DT_LDO_VOLT_1_0V	0x00
+#define I3C_HUB_DT_LDO_VOLT_1_1V	0x01
+#define I3C_HUB_DT_LDO_VOLT_1_2V	0x02
+#define I3C_HUB_DT_LDO_VOLT_1_8V	0x03
+#define I3C_HUB_DT_LDO_VOLT_NOT_DEFINED 0xFF
+
+/* Paged Transaction Registers */
+#define I3C_HUB_CONTROLLER_BUFFER_PAGE	   0x10
+#define I3C_HUB_CONTROLLER_AGENT_BUFF	   0x80
+#define I3C_HUB_CONTROLLER_AGENT_BUFF_DATA 0x84
+#define I3C_HUB_TARGET_BUFF_LENGTH	   0x80
+#define I3C_HUB_TARGET_BUFF_ADDRESS	   0x81
+#define I3C_HUB_TARGET_BUFF_DATA	   0x82
+
+/* Pull-up DT settings */
+#define I3C_HUB_DT_PULLUP_DISABLED    0x00
+#define I3C_HUB_DT_PULLUP_250R	      0x01
+#define I3C_HUB_DT_PULLUP_500R	      0x02
+#define I3C_HUB_DT_PULLUP_1K	      0x03
+#define I3C_HUB_DT_PULLUP_2K	      0x04
+#define I3C_HUB_DT_PULLUP_NOT_DEFINED 0xFF
+
+/* TP DT setting */
+#define I3C_HUB_DT_TP_MODE_DISABLED    0x00
+#define I3C_HUB_DT_TP_MODE_I3C	       0x01
+#define I3C_HUB_DT_TP_MODE_SMBUS       0x02
+#define I3C_HUB_DT_TP_MODE_GPIO	       0x03
+#define I3C_HUB_DT_TP_MODE_NOT_DEFINED 0xFF
+
+/* TP pull-up status */
+#define I3C_HUB_DT_TP_PULLUP_DISABLED	 0x00
+#define I3C_HUB_DT_TP_PULLUP_ENABLED	 0x01
+#define I3C_HUB_DT_TP_PULLUP_NOT_DEFINED 0xFF
+
+/* TP IO mode */
+#define I3C_HUB_DT_TP_IO_MODE_OD_PP	  0x00
+#define I3C_HUB_DT_TP_IO_MODE_OD	  0x01
+#define I3C_HUB_DT_TP_IO_MODE_NOT_DEFINED 0xFF
+
+/* CP/TP IO strength */
+#define I3C_HUB_DT_IO_STRENGTH_20_OHM	   0x00
+#define I3C_HUB_DT_IO_STRENGTH_30_OHM	   0x01
+#define I3C_HUB_DT_IO_STRENGTH_40_OHM	   0x02
+#define I3C_HUB_DT_IO_STRENGTH_50_OHM	   0x03
+#define I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED 0xFF
+
+/* SMBus transaction types fields */
+#define I3C_HUB_SMBUS_100kHz  0x00
+#define I3C_HUB_SMBUS_200kHz  BIT(1)
+#define I3C_HUB_SMBUS_400kHz  BIT(2)
+#define I3C_HUB_SMBUS_1000kHz (BIT(1) | BIT(2))
+
+/* SMBus xfer type for i3c_hub_smbus_msg xfer_type parameter */
+#define I3C_HUB_SMBUS_XFER_WRITE 0
+#define I3C_HUB_SMBUS_XFER_READ	 1
+#define I3C_HUB_SMBUS_XFER_WR_RD 2
+
+/* Hub buffer size */
+#define I3C_HUB_CONTROLLER_BUFFER_SIZE 88
+#define I3C_HUB_TARGET_BUFFER_SIZE     80
+#define I3C_HUB_SMBUS_DESCRIPTOR_SIZE  4
+#define I3C_HUB_SMBUS_PAYLOAD_SIZE \
+	(I3C_HUB_CONTROLLER_BUFFER_SIZE - I3C_HUB_SMBUS_DESCRIPTOR_SIZE)
+#define I3C_HUB_SMBUS_TARGET_PAYLOAD_SIZE (I3C_HUB_TARGET_BUFFER_SIZE - 2)
+
+/* Hub SMBus status register read interval (microseconds, ceil) */
+#define I3C_HUB_SMBUS_STATUS_READ_INTERVAL_US_CEIL(len, clk_khz) \
+	DIV_ROUND_UP(1000U * 9U * (u32)(len), (u32)(clk_khz))
+
+/* ID Extraction */
+#define I3C_HUB_ID_CP_SDA_SCL 0x00
+#define I3C_HUB_ID_CP_SEL     0x01
+
+/* IBI */
+#define IBI_MAX_PAYLOAD_LEN 2
+#define IBI_SLOT_NUMS	    6
+
+#define I3C_HUB_IO_CTRL_PAGE			0x81
+#define I3C_HUB_CFG_TP_SCL_L_ACK_CLK		0xDB
+#define I3C_HUB_CFG_TP_SCL_L_ACK_CLK_EN		BIT(6)
+#define I3C_HUB_CFG_TP_SCL_L_ACK_CLK_COUNT_MASK GENMASK(5, 0)
+#define I3C_HUB_CFG_TP_SCL_L_ACK_CLK_COUNT_VAL	0x18
+
+#define I3C_HUB_CFG_TP_SCL_H_ACK_CLK		0xDC
+#define I3C_HUB_CFG_TP_SCL_H_ACK_CLK_EN		BIT(4)
+#define I3C_HUB_CFG_TP_SCL_H_ACK_CLK_COUNT_MASK GENMASK(3, 0)
+#define I3C_HUB_CFG_TP_SCL_H_ACK_CLK_COUNT_VAL(x) \
+	((x) & I3C_HUB_CFG_TP_SCL_H_ACK_CLK_COUNT_MASK)
+
+#define I3C_HUB_EFUSE_PAGE	  0x7B
+#define I3C_HUB_EFUSE_OFFSET_A0	  0xA0
+#define I3C_HUB_FAST_RSON_EN	  BIT(5)
+#define I3C_HUB_EFUSE_OFFSET_A3	  0xA3
+#define I3C_HUB_FAST_DRV_LOOP_DIS BIT(5)
+
+#define I3C_HUB_EFUSE_OFFSET_9D 0x9D
+#define I3C_HUB_TP_OD_VOL_LEVEL BIT(0)
+#define I3C_HUB_TP_OD_VREF	BIT(1)
+
+#define I3C_HUB_EFUSE_OFFSET_9E		  0x9E
+#define I3C_HUB_FAST_DRV_H_ADD_CYCLE_MASK GENMASK(5, 4)
+#define I3C_HUB_FAST_DRV_H_ADD_CYCLE_VAL(x) \
+	(((x) << 4) & I3C_HUB_FAST_DRV_H_ADD_CYCLE_MASK)
+#define I3C_HUB_IBI_ACK_RD_CYCLE_MASK	  GENMASK(3, 0)
+#define I3C_HUB_IBI_ACK_RD_CYCLE_VAL	  (5)
+
+struct i3c_hub_dev_info {
+	const char *model;
+	u16 part_id;
+	u8 n_ports;
+};
+
+static const struct i3c_hub_dev_info i3c_hub_dev_info_unknown = {
+	.model = "Unknown",
+	.part_id = 0,
+	.n_ports = 8,
+};
+
+static const struct i3c_hub_dev_info i3c_hub_dev_info_table[] = {
+	{ "RTS4900", 0x4000, 4 }, { "RTS4901", 0x4100, 4 },
+	{ "RTS4902", 0x8000, 8 }, { "RTS4903", 0x8100, 8 },
+	{ "RTS4904", 0x4001, 4 }, { "RTS4906", 0x8001, 8 }
+};
+
+struct tp_setting {
+	u8 mode;
+	u8 pullup_en;
+	u8 io_mode;
+	bool always_enable;
+	u32 poll_interval_ms;
+	u32 clock_frequency;
+};
+
+struct dt_settings {
+	u8 cp0_ldo_en;
+	u8 cp1_ldo_en;
+	u8 cp0_ldo_volt;
+	u8 cp1_ldo_volt;
+	u8 tp0145_ldo_en;
+	u8 tp2367_ldo_en;
+	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[I3C_HUB_TP_MAX_COUNT];
+	bool hub_net_always_i3c;
+	u8 tp_scl_h_ack_cycles;
+	bool handshake_optimize;
+	u8 fast_drv_h_add_cycles;
+	bool fast_rson_en;
+	bool tp_od_vol_optimize;
+	bool tp_od_vref_optimize;
+};
+
+struct smbus_backend {
+	struct i2c_client *client;
+	struct list_head list;
+};
+
+struct i2c_adapter_group {
+	struct i2c_adapter i2c;
+	u8 tp_mask;
+	u8 tp_port;
+	u8 used;
+	struct device_node *of_node;
+	struct i3c_hub *priv;
+	struct mutex mutex;
+
+	struct delayed_work delayed_work_polling;
+	struct list_head backend_entry;
+	u8 last_processed_buf;
+
+	u8 status;
+	struct completion completion;
+};
+
+struct logical_bus {
+	bool available; /* Logical bus configuration is available in DT. */
+	bool registered; /* Logical bus was registered in the framework. */
+	u8 tp_id;
+	u8 tp_map;
+	struct i3c_master_controller controller;
+	struct device_node *of_node;
+	struct i3c_hub *priv;
+};
+
+struct hub_gpio {
+	struct gpio_chip chip;
+	int tp[GPIO_MAX_BANK];
+	s8 port_to_index[I3C_HUB_TP_MAX_COUNT];
+	int nums;
+	struct irq_chip irq_chip;
+	struct mutex irq_mutex;
+};
+
+struct i3c_hub {
+	struct i3c_device *i3cdev;
+	struct i3c_master_controller *driving_master;
+	struct regmap *regmap;
+	const struct i3c_hub_dev_info *dev_info;
+	struct dt_settings settings;
+	struct delayed_work delayed_work;
+	int hub_pin_sel_id;
+	int hub_pin_cp1_id;
+	int hub_dt_sel_id;
+	int hub_dt_cp1_id;
+
+	struct logical_bus logical_bus[I3C_HUB_TP_MAX_COUNT];
+	struct i2c_adapter_group smbus_port_adapter[I3C_HUB_TP_MAX_COUNT];
+	u8 smbus_ibi_en_mask;
+	struct mutex page_mutex;
+
+	/* Offset for reading HUB's register. */
+	u8 reg_addr;
+	struct dentry *debug_dir;
+	struct hub_gpio gpio;
+};
+
+struct hub_setting {
+	const char *const name;
+	const u8 value;
+};
+
+static const struct hub_setting ldo_en_settings[] = {
+	{ "disabled", I3C_HUB_DT_LDO_DISABLED },
+	{ "enabled", I3C_HUB_DT_LDO_ENABLED },
+};
+
+static const struct hub_setting ldo_volt_settings[] = {
+	{ "1.0V", I3C_HUB_DT_LDO_VOLT_1_0V },
+	{ "1.1V", I3C_HUB_DT_LDO_VOLT_1_1V },
+	{ "1.2V", I3C_HUB_DT_LDO_VOLT_1_2V },
+	{ "1.8V", I3C_HUB_DT_LDO_VOLT_1_8V },
+};
+
+static const struct hub_setting pullup_settings[] = {
+	{ "disabled", I3C_HUB_DT_PULLUP_DISABLED },
+	{ "250R", I3C_HUB_DT_PULLUP_250R },
+	{ "500R", I3C_HUB_DT_PULLUP_500R },
+	{ "1k", I3C_HUB_DT_PULLUP_1K },
+	{ "2k", I3C_HUB_DT_PULLUP_2K },
+};
+
+static const struct hub_setting tp_mode_settings[] = {
+	{ "disabled", I3C_HUB_DT_TP_MODE_DISABLED },
+	{ "i3c", I3C_HUB_DT_TP_MODE_I3C },
+	{ "smbus", I3C_HUB_DT_TP_MODE_SMBUS },
+	{ "gpio", I3C_HUB_DT_TP_MODE_GPIO },
+};
+
+static const struct hub_setting tp_pullup_settings[] = {
+	{ "disabled", I3C_HUB_DT_TP_PULLUP_DISABLED },
+	{ "enabled", I3C_HUB_DT_TP_PULLUP_ENABLED },
+};
+
+static const struct hub_setting tp_io_mode_settings[] = {
+	{ "od-pp", I3C_HUB_DT_TP_IO_MODE_OD_PP },
+	{ "od", I3C_HUB_DT_TP_IO_MODE_OD },
+};
+
+static const struct hub_setting io_strength_settings[] = {
+	{ "20Ohms", I3C_HUB_DT_IO_STRENGTH_20_OHM },
+	{ "30Ohms", I3C_HUB_DT_IO_STRENGTH_30_OHM },
+	{ "40Ohms", I3C_HUB_DT_IO_STRENGTH_40_OHM },
+	{ "50Ohms", I3C_HUB_DT_IO_STRENGTH_50_OHM },
+};
+
+/* Global mutex for serializing regmap access across all i3c hubs. */
+static DEFINE_MUTEX(i3c_hub_regmap_mutex);
+
+static u8 i3c_hub_ldo_dt_to_reg(u8 dt_value)
+{
+	switch (dt_value) {
+	case I3C_HUB_DT_LDO_VOLT_1_1V:
+		return LDO_VOLTAGE_1_1V;
+	case I3C_HUB_DT_LDO_VOLT_1_2V:
+		return LDO_VOLTAGE_1_2V;
+	case I3C_HUB_DT_LDO_VOLT_1_8V:
+		return LDO_VOLTAGE_1_8V;
+	default:
+		return LDO_VOLTAGE_1_0V;
+	}
+}
+
+static u8 i3c_hub_pullup_dt_to_reg(u8 dt_value)
+{
+	switch (dt_value) {
+	case I3C_HUB_DT_PULLUP_250R:
+		return PULLUP_250R;
+	case I3C_HUB_DT_PULLUP_500R:
+		return PULLUP_500R;
+	case I3C_HUB_DT_PULLUP_1K:
+		return PULLUP_1K;
+	default:
+		return PULLUP_2K;
+	}
+}
+
+static u8 i3c_hub_io_strength_dt_to_reg(u8 dt_value)
+{
+	switch (dt_value) {
+	case I3C_HUB_DT_IO_STRENGTH_50_OHM:
+		return IO_STRENGTH_50_OHM;
+	case I3C_HUB_DT_IO_STRENGTH_40_OHM:
+		return IO_STRENGTH_40_OHM;
+	case I3C_HUB_DT_IO_STRENGTH_30_OHM:
+		return IO_STRENGTH_30_OHM;
+	default:
+		return IO_STRENGTH_20_OHM;
+	}
+}
+
+static void i3c_hub_of_get_setting(struct device *dev,
+				   const struct device_node *node,
+				   const char *setting_name,
+				   const struct hub_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) {
+		/* Lack of property is not considered as a problem. */
+		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 hub_setting *const setting = &settings[i];
+
+		if (!strcmp(setting->name, sval)) {
+			*setting_value = setting->value;
+			return;
+		}
+	}
+	dev_warn(dev, "Unknown setting for %s: '%s'\n", setting_name, sval);
+}
+
+static bool i3c_hub_smbus_validate_clock_frequency(u32 hz)
+{
+	switch (hz) {
+	case 100000:
+	case 200000:
+	case 400000:
+	case 1000000:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static inline u8 i3c_hub_smbus_rate_bits_from_hz(u32 hz)
+{
+	switch (hz) {
+	case 100000:
+		return I3C_HUB_SMBUS_100kHz;
+	case 200000:
+		return I3C_HUB_SMBUS_200kHz;
+	case 1000000:
+		return I3C_HUB_SMBUS_1000kHz;
+	default:
+		return I3C_HUB_SMBUS_400kHz;
+	}
+}
+
+static void i3c_hub_tp_of_get_setting(struct device *dev,
+				      const struct device_node *node,
+				      struct tp_setting tp_setting[])
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+	struct device_node *tp_node;
+	u32 id, val;
+
+	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 >= priv->dev_info->n_ports) {
+			dev_warn(dev,
+				 "Invalid target port index found in DT: %i\n",
+				 id);
+			continue;
+		}
+
+		priv->smbus_port_adapter[id].of_node = tp_node;
+		i3c_hub_of_get_setting(dev, tp_node, "mode", tp_mode_settings,
+				       ARRAY_SIZE(tp_mode_settings),
+				       &tp_setting[id].mode);
+		i3c_hub_of_get_setting(dev, tp_node, "pullup",
+				       tp_pullup_settings,
+				       ARRAY_SIZE(tp_pullup_settings),
+				       &tp_setting[id].pullup_en);
+		i3c_hub_of_get_setting(dev, tp_node, "io-mode",
+				       tp_io_mode_settings,
+				       ARRAY_SIZE(tp_io_mode_settings),
+				       &tp_setting[id].io_mode);
+		tp_setting[id].always_enable =
+			of_property_read_bool(tp_node, "always-enable");
+		if (!of_property_read_u32(tp_node, "polling-interval-ms", &val))
+			tp_setting[id].poll_interval_ms = val;
+
+		if (!of_property_read_u32(tp_node, "clock-frequency", &val)) {
+			if (i3c_hub_smbus_validate_clock_frequency(val))
+				tp_setting[id].clock_frequency = val;
+			else
+				dev_warn(
+					dev,
+					"Unsupported TP%d smbus clock-frequency: %u Hz, using default %u Hz\n",
+					id, val,
+					tp_setting[id].clock_frequency);
+		}
+	}
+}
+
+static void i3c_hub_of_get_conf_static(struct device *dev,
+				       const struct device_node *node)
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+	u8 val = 0;
+
+	i3c_hub_of_get_setting(dev, node, "cp0-ldo-en", ldo_en_settings,
+			       ARRAY_SIZE(ldo_en_settings),
+			       &priv->settings.cp0_ldo_en);
+	i3c_hub_of_get_setting(dev, node, "cp1-ldo-en", ldo_en_settings,
+			       ARRAY_SIZE(ldo_en_settings),
+			       &priv->settings.cp1_ldo_en);
+	i3c_hub_of_get_setting(dev, node, "cp0-ldo-volt", ldo_volt_settings,
+			       ARRAY_SIZE(ldo_volt_settings),
+			       &priv->settings.cp0_ldo_volt);
+	i3c_hub_of_get_setting(dev, node, "cp1-ldo-volt", ldo_volt_settings,
+			       ARRAY_SIZE(ldo_volt_settings),
+			       &priv->settings.cp1_ldo_volt);
+	i3c_hub_of_get_setting(dev, node, "tp0145-ldo-en", ldo_en_settings,
+			       ARRAY_SIZE(ldo_en_settings),
+			       &priv->settings.tp0145_ldo_en);
+	i3c_hub_of_get_setting(dev, node, "tp2367-ldo-en", ldo_en_settings,
+			       ARRAY_SIZE(ldo_en_settings),
+			       &priv->settings.tp2367_ldo_en);
+	i3c_hub_of_get_setting(dev, node, "tp0145-ldo-volt", ldo_volt_settings,
+			       ARRAY_SIZE(ldo_volt_settings),
+			       &priv->settings.tp0145_ldo_volt);
+	i3c_hub_of_get_setting(dev, node, "tp2367-ldo-volt", ldo_volt_settings,
+			       ARRAY_SIZE(ldo_volt_settings),
+			       &priv->settings.tp2367_ldo_volt);
+	i3c_hub_of_get_setting(dev, node, "tp0145-pullup", pullup_settings,
+			       ARRAY_SIZE(pullup_settings),
+			       &priv->settings.tp0145_pullup);
+	i3c_hub_of_get_setting(dev, node, "tp2367-pullup", pullup_settings,
+			       ARRAY_SIZE(pullup_settings),
+			       &priv->settings.tp2367_pullup);
+	i3c_hub_of_get_setting(dev, node, "cp0-io-strength",
+			       io_strength_settings,
+			       ARRAY_SIZE(io_strength_settings),
+			       &priv->settings.cp0_io_strength);
+	i3c_hub_of_get_setting(dev, node, "cp1-io-strength",
+			       io_strength_settings,
+			       ARRAY_SIZE(io_strength_settings),
+			       &priv->settings.cp1_io_strength);
+	i3c_hub_of_get_setting(dev, node, "tp0145-io-strength",
+			       io_strength_settings,
+			       ARRAY_SIZE(io_strength_settings),
+			       &priv->settings.tp0145_io_strength);
+	i3c_hub_of_get_setting(dev, node, "tp2367-io-strength",
+			       io_strength_settings,
+			       ARRAY_SIZE(io_strength_settings),
+			       &priv->settings.tp2367_io_strength);
+
+	priv->settings.hub_net_always_i3c =
+		of_property_read_bool(node, "hub-net-always-i3c");
+
+	if (!of_property_read_u8(node, "tp-scl-h-ack-cycles", &val))
+		priv->settings.tp_scl_h_ack_cycles = val;
+
+	i3c_hub_tp_of_get_setting(dev, node, priv->settings.tp);
+
+	priv->settings.handshake_optimize =
+		of_property_read_bool(node, "handshake-optimize");
+
+	if (!of_property_read_u8(node, "fast-drv-h-add-cycles", &val))
+		priv->settings.fast_drv_h_add_cycles = val;
+
+	priv->settings.fast_rson_en =
+		of_property_read_bool(node, "fast-rson-en");
+
+	priv->settings.tp_od_vol_optimize =
+		of_property_read_bool(node, "tp-od-vol-optimize");
+
+	priv->settings.tp_od_vref_optimize =
+		of_property_read_bool(node, "tp-od-vref-optimize");
+}
+
+static const struct i3c_hub_dev_info *
+i3c_hub_lookup_dev_info(struct i3c_hub *priv)
+{
+	int i, ret;
+	u16 part_id = 0;
+	u32 val = 0;
+
+	ret = regmap_read(priv->regmap, I3C_HUB_DEV_INFO_0, &val);
+	if (ret)
+		return ERR_PTR(ret);
+
+	part_id = (val & 0xFF) << 8;
+
+	ret = regmap_read(priv->regmap, I3C_HUB_DEV_REV, &val);
+	if (ret)
+		return ERR_PTR(ret);
+
+	part_id |= I3C_HUB_DEV_REV_LDO_GET(val);
+
+	for (i = 0; i < ARRAY_SIZE(i3c_hub_dev_info_table); i++) {
+		if (i3c_hub_dev_info_table[i].part_id == part_id)
+			return &i3c_hub_dev_info_table[i];
+	}
+	return &i3c_hub_dev_info_unknown;
+}
+
+static void i3c_hub_of_default_configuration(struct device *dev)
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+	int id;
+
+	priv->settings.cp0_ldo_en = I3C_HUB_DT_LDO_NOT_DEFINED;
+	priv->settings.cp1_ldo_en = I3C_HUB_DT_LDO_NOT_DEFINED;
+	priv->settings.cp0_ldo_volt = I3C_HUB_DT_LDO_VOLT_NOT_DEFINED;
+	priv->settings.cp1_ldo_volt = I3C_HUB_DT_LDO_VOLT_NOT_DEFINED;
+	priv->settings.tp0145_ldo_en = I3C_HUB_DT_LDO_NOT_DEFINED;
+	priv->settings.tp2367_ldo_en = I3C_HUB_DT_LDO_NOT_DEFINED;
+	priv->settings.tp0145_ldo_volt = I3C_HUB_DT_LDO_VOLT_NOT_DEFINED;
+	priv->settings.tp2367_ldo_volt = I3C_HUB_DT_LDO_VOLT_NOT_DEFINED;
+	priv->settings.tp0145_pullup = I3C_HUB_DT_PULLUP_NOT_DEFINED;
+	priv->settings.tp2367_pullup = I3C_HUB_DT_PULLUP_NOT_DEFINED;
+	priv->settings.cp0_io_strength = I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED;
+	priv->settings.cp1_io_strength = I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED;
+	priv->settings.tp0145_io_strength = I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED;
+	priv->settings.tp2367_io_strength = I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED;
+	priv->settings.hub_net_always_i3c = false;
+	priv->settings.tp_scl_h_ack_cycles = 0;
+	priv->settings.handshake_optimize = false;
+	priv->settings.fast_drv_h_add_cycles = 3;
+	priv->settings.fast_rson_en = false;
+	priv->settings.tp_od_vol_optimize = false;
+	priv->settings.tp_od_vref_optimize = false;
+
+	for (id = 0; id < I3C_HUB_TP_MAX_COUNT; ++id) {
+		priv->settings.tp[id].mode = I3C_HUB_DT_TP_MODE_NOT_DEFINED;
+		priv->settings.tp[id].pullup_en =
+			I3C_HUB_DT_TP_PULLUP_NOT_DEFINED;
+		priv->settings.tp[id].io_mode =
+			I3C_HUB_DT_TP_IO_MODE_NOT_DEFINED;
+		priv->settings.tp[id].poll_interval_ms = 0;
+		priv->settings.tp[id].clock_frequency = 400000;
+	}
+}
+
+static int i3c_hub_hw_configure_pullup(struct device *dev)
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+	u8 mask = 0, value = 0;
+
+	if (priv->settings.tp0145_pullup != I3C_HUB_DT_PULLUP_NOT_DEFINED) {
+		mask |= TP0145_PULLUP_CONF_MASK;
+		value |= TP0145_PULLUP_CONF(
+			i3c_hub_pullup_dt_to_reg(priv->settings.tp0145_pullup));
+	}
+
+	if (priv->settings.tp2367_pullup != I3C_HUB_DT_PULLUP_NOT_DEFINED) {
+		mask |= TP2367_PULLUP_CONF_MASK;
+		value |= TP2367_PULLUP_CONF(
+			i3c_hub_pullup_dt_to_reg(priv->settings.tp2367_pullup));
+	}
+
+	return regmap_update_bits(priv->regmap, I3C_HUB_LDO_AND_PULLUP_CONF,
+				  mask, value);
+}
+
+static int i3c_hub_hw_configure_ldo(struct device *dev)
+{
+	struct i3c_hub *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 == I3C_HUB_DT_LDO_ENABLED)
+		ldo_en_val |= CP0_LDO_EN;
+	if (priv->settings.cp1_ldo_en == I3C_HUB_DT_LDO_ENABLED)
+		ldo_en_val |= CP1_LDO_EN;
+	if (priv->settings.tp0145_ldo_en == I3C_HUB_DT_LDO_ENABLED)
+		ldo_en_val |= TP0145_LDO_EN;
+	if (priv->settings.tp2367_ldo_en == I3C_HUB_DT_LDO_ENABLED)
+		ldo_en_val |= TP2367_LDO_EN;
+
+	/* Get current LDOs configuration */
+	ret = regmap_read(priv->regmap, I3C_HUB_LDO_CONF, &reg_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 != I3C_HUB_DT_LDO_VOLT_NOT_DEFINED) {
+		val = CP0_LDO_VOLTAGE(
+			i3c_hub_ldo_dt_to_reg(priv->settings.cp0_ldo_volt));
+		if ((reg_val & CP0_LDO_VOLTAGE_MASK) != val) {
+			ldo_config_mask |= CP0_LDO_VOLTAGE_MASK;
+			ldo_disable_mask |= CP0_LDO_EN;
+			ldo_config_val |= val;
+		}
+	}
+	if (priv->settings.cp1_ldo_volt != I3C_HUB_DT_LDO_VOLT_NOT_DEFINED) {
+		val = CP1_LDO_VOLTAGE(
+			i3c_hub_ldo_dt_to_reg(priv->settings.cp1_ldo_volt));
+		if ((reg_val & CP1_LDO_VOLTAGE_MASK) != val) {
+			ldo_config_mask |= CP1_LDO_VOLTAGE_MASK;
+			ldo_disable_mask |= CP1_LDO_EN;
+			ldo_config_val |= val;
+		}
+	}
+	if (priv->settings.tp0145_ldo_volt != I3C_HUB_DT_LDO_VOLT_NOT_DEFINED) {
+		val = TP0145_LDO_VOLTAGE(
+			i3c_hub_ldo_dt_to_reg(priv->settings.tp0145_ldo_volt));
+		if ((reg_val & TP0145_LDO_VOLTAGE_MASK) != val) {
+			ldo_config_mask |= TP0145_LDO_VOLTAGE_MASK;
+			ldo_disable_mask |= TP0145_LDO_EN;
+			ldo_config_val |= val;
+		}
+	}
+	if (priv->settings.tp2367_ldo_volt != I3C_HUB_DT_LDO_VOLT_NOT_DEFINED) {
+		val = TP2367_LDO_VOLTAGE(
+			i3c_hub_ldo_dt_to_reg(priv->settings.tp2367_ldo_volt));
+		if ((reg_val & TP2367_LDO_VOLTAGE_MASK) != val) {
+			ldo_config_mask |= TP2367_LDO_VOLTAGE_MASK;
+			ldo_disable_mask |= TP2367_LDO_EN;
+			ldo_config_val |= val;
+		}
+	}
+
+	/*
+	 * 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,
+					 I3C_HUB_LDO_AND_PULLUP_CONF,
+					 ldo_disable_mask, 0);
+		if (ret)
+			return ret;
+
+		/* Update the LDOs configuration */
+		ret = regmap_update_bits(priv->regmap, I3C_HUB_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, I3C_HUB_LDO_AND_PULLUP_CONF,
+				  LDO_ENABLE_DISABLE_MASK, ldo_en_val);
+}
+
+static int i3c_hub_hw_configure_io_strength(struct device *dev)
+{
+	struct i3c_hub *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, I3C_HUB_IO_STRENGTH, &reg_val);
+	if (ret)
+		return ret;
+
+	tmp = priv->settings;
+	if (tmp.cp0_io_strength != I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED) {
+		val = CP0_IO_STRENGTH(
+			i3c_hub_io_strength_dt_to_reg(tmp.cp0_io_strength));
+		mask_all |= CP0_IO_STRENGTH_MASK;
+		val_all |= val;
+	}
+	if (tmp.cp1_io_strength != I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED) {
+		val = CP1_IO_STRENGTH(
+			i3c_hub_io_strength_dt_to_reg(tmp.cp1_io_strength));
+		mask_all |= CP1_IO_STRENGTH_MASK;
+		val_all |= val;
+	}
+	if (tmp.tp0145_io_strength != I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED) {
+		val = TP0145_IO_STRENGTH(
+			i3c_hub_io_strength_dt_to_reg(tmp.tp0145_io_strength));
+		mask_all |= TP0145_IO_STRENGTH_MASK;
+		val_all |= val;
+	}
+	if (tmp.tp2367_io_strength != I3C_HUB_DT_IO_STRENGTH_NOT_DEFINED) {
+		val = TP2367_IO_STRENGTH(
+			i3c_hub_io_strength_dt_to_reg(tmp.tp2367_io_strength));
+		mask_all |= TP2367_IO_STRENGTH_MASK;
+		val_all |= val;
+	}
+
+	/* Set IO strength if required */
+	return regmap_update_bits(priv->regmap, I3C_HUB_IO_STRENGTH, mask_all,
+				  val_all);
+}
+
+static int i3c_hub_hw_configure_tp(struct device *dev)
+{
+	struct i3c_hub *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 io_mode_mask = 0, io_mode_val = 0;
+	int ret;
+	int i, index;
+
+	memset(priv->gpio.port_to_index, -1, sizeof(priv->gpio.port_to_index));
+
+	for (i = 0; i < priv->dev_info->n_ports; ++i) {
+		if (priv->settings.tp[i].mode !=
+		    I3C_HUB_DT_TP_MODE_NOT_DEFINED) {
+			i3c_mask |= TPn_NET_CON(i);
+			smbus_mask |= TPn_SMBUS_MODE_EN(i);
+			gpio_mask |= TPn_GPIO_MODE_EN(i);
+			io_mode_mask |= TPn_IO_MODE_CON(i);
+
+			if (priv->settings.tp[i].mode ==
+			    I3C_HUB_DT_TP_MODE_I3C) {
+				i3c_val |= TPn_NET_CON(i);
+			} else if (priv->settings.tp[i].mode ==
+				   I3C_HUB_DT_TP_MODE_SMBUS) {
+				smbus_val |= TPn_SMBUS_MODE_EN(i);
+			} else if (priv->settings.tp[i].mode ==
+				   I3C_HUB_DT_TP_MODE_GPIO) {
+				gpio_val |= TPn_GPIO_MODE_EN(i);
+				priv->gpio.nums += GPIO_BANK_SZ;
+				index = priv->gpio.nums / GPIO_BANK_SZ - 1;
+				priv->gpio.tp[index] = i;
+				priv->gpio.port_to_index[i] = index;
+			}
+		}
+		if (priv->settings.tp[i].pullup_en !=
+		    I3C_HUB_DT_TP_PULLUP_NOT_DEFINED) {
+			pullup_mask |= TPn_PULLUP_EN(i);
+			if (priv->settings.tp[i].pullup_en ==
+			    I3C_HUB_DT_TP_PULLUP_ENABLED)
+				pullup_val |= TPn_PULLUP_EN(i);
+		}
+		if (priv->settings.tp[i].io_mode !=
+		    I3C_HUB_DT_TP_IO_MODE_NOT_DEFINED) {
+			if (priv->settings.tp[i].io_mode ==
+			    I3C_HUB_DT_TP_IO_MODE_OD)
+				io_mode_val |= TPn_IO_MODE_CON(i);
+		} else if (priv->settings.tp[i].mode ==
+			   I3C_HUB_DT_TP_MODE_SMBUS) {
+			io_mode_val |= TPn_IO_MODE_CON(i);
+		}
+	}
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_IO_MODE_CONF,
+				 io_mode_mask, io_mode_val);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_PULLUP_EN,
+				 pullup_mask, pullup_val);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_SMBUS_AGNT_EN,
+				 smbus_mask, smbus_val);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_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) {
+		ret = regmap_write(priv->regmap, I3C_HUB_CP_MUX_SET,
+				   CONTROLLER_PORT_MUX_REQ);
+		if (ret)
+			return ret;
+		/* TODO: verify if connection is done */
+	}
+
+	/* Enable TP here in case TP was configured */
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_ENABLE,
+				 i3c_mask | smbus_mask | gpio_mask,
+				 i3c_val | smbus_val | gpio_val);
+	if (ret)
+		return ret;
+
+	return regmap_write(priv->regmap, I3C_HUB_TP_NET_CON_CONF, i3c_val);
+}
+
+static int i3c_hub_hw_configure_misc(struct device *dev)
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+	int ret;
+	u8 reg = I3C_HUB_TARGET_DA_FLAG_BYTE_BASE;
+	u8 val[I3C_HUB_TARGET_DA_FLAG_BYTE_COUNT];
+
+	if (!priv->settings.hub_net_always_i3c)
+		return 0;
+
+	memset(val, 0xff, I3C_HUB_TARGET_DA_FLAG_BYTE_COUNT);
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_NET_OPER_MODE_CONF,
+				 I3C_HUB_NET_ALWAYS_I3C_EN,
+				 I3C_HUB_NET_ALWAYS_I3C_EN);
+	if (ret)
+		return ret;
+
+	ret = regmap_bulk_write(priv->regmap, reg, val,
+				I3C_HUB_TARGET_DA_FLAG_BYTE_COUNT);
+	return ret;
+}
+
+typedef int (*i3c_hub_cfg_fn)(struct i3c_hub *priv);
+
+static int i3c_hub_hw_cfg_with_page(struct i3c_hub *priv, u8 page,
+				    i3c_hub_cfg_fn op)
+{
+	int ret;
+
+	if (!op)
+		return -EINVAL;
+
+	mutex_lock(&priv->page_mutex);
+	ret = regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, page);
+	if (ret)
+		goto unlock;
+
+	ret = op(priv);
+unlock:
+	regmap_write(priv->regmap, I3C_HUB_PAGE_PTR, 0x00);
+	mutex_unlock(&priv->page_mutex);
+	return ret;
+}
+
+static int i3c_hub_cfg_op_fuse_latch(struct i3c_hub *priv)
+{
+	int ret;
+
+	if (priv->settings.handshake_optimize) {
+		ret = regmap_update_bits(priv->regmap, I3C_HUB_EFUSE_OFFSET_9E,
+					 I3C_HUB_IBI_ACK_RD_CYCLE_MASK,
+					 I3C_HUB_IBI_ACK_RD_CYCLE_VAL);
+		if (ret)
+			return ret;
+	}
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_EFUSE_OFFSET_A3,
+				 I3C_HUB_FAST_DRV_LOOP_DIS,
+				 I3C_HUB_FAST_DRV_LOOP_DIS);
+	if (ret)
+		return ret;
+
+	if (priv->settings.tp_od_vol_optimize) {
+		ret = regmap_update_bits(priv->regmap, I3C_HUB_EFUSE_OFFSET_9D,
+					 I3C_HUB_TP_OD_VOL_LEVEL,
+					 I3C_HUB_TP_OD_VOL_LEVEL);
+		if (ret)
+			return ret;
+	}
+
+	if (priv->settings.tp_od_vref_optimize) {
+		ret = regmap_update_bits(priv->regmap, I3C_HUB_EFUSE_OFFSET_9D,
+					 I3C_HUB_TP_OD_VREF,
+					 I3C_HUB_TP_OD_VREF);
+		if (ret)
+			return ret;
+	}
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_EFUSE_OFFSET_9E,
+				 I3C_HUB_FAST_DRV_H_ADD_CYCLE_MASK,
+				 I3C_HUB_FAST_DRV_H_ADD_CYCLE_VAL(
+					 priv->settings.fast_drv_h_add_cycles));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(
+		priv->regmap, I3C_HUB_EFUSE_OFFSET_A0, I3C_HUB_FAST_RSON_EN,
+		priv->settings.fast_rson_en ? I3C_HUB_FAST_RSON_EN : 0);
+	return ret;
+}
+
+static int i3c_hub_hw_configure_fuse_latch(struct device *dev)
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+
+	return i3c_hub_hw_cfg_with_page(priv, I3C_HUB_EFUSE_PAGE,
+					i3c_hub_cfg_op_fuse_latch);
+}
+
+static int i3c_hub_cfg_op_io(struct i3c_hub *priv)
+{
+	int ret;
+	u8 reg = I3C_HUB_CFG_TP_SCL_L_ACK_CLK;
+
+	/* cfg tp scl low ack clk */
+	ret = regmap_write(priv->regmap, reg,
+			   I3C_HUB_CFG_TP_SCL_L_ACK_CLK_EN |
+				   I3C_HUB_CFG_TP_SCL_L_ACK_CLK_COUNT_VAL);
+	if (ret)
+		return ret;
+
+	/* cfg tp scl high ack clk */
+	reg = I3C_HUB_CFG_TP_SCL_H_ACK_CLK;
+	if (priv->settings.tp_scl_h_ack_cycles == 0)
+		return 0;
+
+	ret = regmap_write(priv->regmap, reg,
+			   I3C_HUB_CFG_TP_SCL_H_ACK_CLK_EN |
+				   I3C_HUB_CFG_TP_SCL_H_ACK_CLK_COUNT_VAL(
+					   priv->settings.tp_scl_h_ack_cycles));
+
+	return ret;
+}
+
+static int i3c_hub_hw_configure_io(struct device *dev)
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+
+	return i3c_hub_hw_cfg_with_page(priv, I3C_HUB_IO_CTRL_PAGE,
+					i3c_hub_cfg_op_io);
+}
+
+static int i3c_hub_configure_hw(struct device *dev)
+{
+	int ret;
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+
+	ret = i3c_hub_hw_configure_ldo(dev);
+	if (ret)
+		return ret;
+
+	ret = i3c_hub_hw_configure_io_strength(dev);
+	if (ret)
+		return ret;
+
+	ret = i3c_hub_hw_configure_pullup(dev);
+	if (ret)
+		return ret;
+
+	if (priv->dev_info->part_id) {
+		ret = i3c_hub_hw_configure_misc(dev);
+		if (ret)
+			return ret;
+
+		ret = i3c_hub_hw_configure_fuse_latch(dev);
+		if (ret)
+			return ret;
+
+		ret = i3c_hub_hw_configure_io(dev);
+		if (ret)
+			return ret;
+	}
+
+	ret = i3c_hub_hw_configure_tp(dev);
+	return ret;
+}
+
+static void i3c_hub_of_get_conf_runtime(struct device *dev,
+					const struct device_node *node)
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+	struct device_node *i3c_node;
+	int i3c_id;
+	u8 tp_mask;
+
+	for_each_available_child_of_node(node, i3c_node) {
+		if (!i3c_node->full_name ||
+		    (sscanf(i3c_node->full_name, "i3c%i@%hhx", &i3c_id,
+			    &tp_mask) != 2))
+			continue;
+
+		if (i3c_id < priv->dev_info->n_ports) {
+			priv->logical_bus[i3c_id].available = true;
+			priv->logical_bus[i3c_id].of_node = i3c_node;
+			priv->logical_bus[i3c_id].tp_map = tp_mask;
+			priv->logical_bus[i3c_id].priv = priv;
+			priv->logical_bus[i3c_id].tp_id = i3c_id;
+		}
+	}
+}
+
+static const struct i3c_device_id i3c_hub_ids[] = {
+	I3C_CLASS(I3C_DCR_HUB, NULL),
+	{},
+};
+
+static int i3c_hub_read_id(struct device *dev)
+{
+	struct i3c_hub *priv = dev_get_drvdata(dev);
+	u32 reg_val;
+	int ret;
+
+	ret = regmap_read(priv->regmap, I3C_HUB_LDO_AND_CPSEL_STS, &reg_val);
+	if (ret) {
+		dev_err(dev, "Failed to read status register\n");
+		return -1;
+	}
+
+	priv->hub_pin_sel_id = CP_SEL_PIN_INPUT_CODE_GET(reg_val);
+	priv->hub_pin_cp1_id = CP_SDA1_SCL1_PINS_CODE_GET(reg_val);
+	return 0;
+}
+
+static struct device_node *i3c_hub_get_dt_hub_node(struct device_node *node,
+						   struct i3c_hub *priv)
+{
+	struct device_node *hub_node_no_id = NULL;
+	struct device_node *hub_node;
+	u32 hub_id;
+	u32 id_mask;
+	u32 dt_id;
+	u32 pin_id;
+	int found_id = 0;
+
+	for_each_available_child_of_node(node, hub_node) {
+		id_mask = 0;
+		priv->hub_dt_sel_id = -1;
+		priv->hub_dt_cp1_id = -1;
+
+		if (strstr(hub_node->name, "hub")) {
+			if (!of_property_read_u32(hub_node, "id", &hub_id)) {
+				id_mask |= 0x0f;
+				priv->hub_dt_sel_id = hub_id;
+			}
+
+			if (!of_property_read_u32(hub_node, "id-cp1",
+						  &hub_id)) {
+				id_mask |= 0xf0;
+				priv->hub_dt_cp1_id = hub_id;
+			}
+
+			dt_id = ((u32)priv->hub_dt_cp1_id & 0x0f) << 4 |
+				((u32)priv->hub_dt_sel_id & 0x0f);
+			pin_id = ((u32)priv->hub_pin_cp1_id & 0x0f) << 4 |
+				 ((u32)priv->hub_pin_sel_id & 0x0f);
+
+			if (id_mask != 0 &&
+			    (dt_id & id_mask) == (pin_id & id_mask))
+				found_id = 1;
+
+			if (!found_id) {
+				/*
+				 * Just keep reference to first HUB node with no ID in case no ID
+				 * matching
+				 */
+				if (!hub_node_no_id &&
+				    priv->hub_dt_sel_id == -1 &&
+				    priv->hub_dt_cp1_id == -1)
+					hub_node_no_id = hub_node;
+			} else {
+				return hub_node;
+			}
+		}
+	}
+
+	return hub_node_no_id;
+}
+
+static int fops_access_reg_get(void *ctx, u64 *val)
+{
+	struct i3c_hub *priv = ctx;
+	u32 reg_val;
+	int ret;
+
+	ret = regmap_read(priv->regmap, priv->reg_addr, &reg_val);
+	if (ret)
+		return ret;
+
+	*val = reg_val & 0xFF;
+	return 0;
+}
+
+static int fops_access_reg_set(void *ctx, u64 val)
+{
+	struct i3c_hub *priv = ctx;
+
+	return regmap_write(priv->regmap, priv->reg_addr, val & 0xFF);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_access_reg, fops_access_reg_get,
+			 fops_access_reg_set, "0x%llX\n");
+
+static int i3c_hub_debugfs_init(struct i3c_hub *priv, const char *hub_id)
+{
+	struct dentry *entry, *dt_conf_dir, *reg_dir;
+	struct dt_settings *settings = NULL;
+	int i;
+
+	entry = debugfs_create_dir(hub_id, NULL);
+	if (IS_ERR(entry))
+		return PTR_ERR(entry);
+
+	priv->debug_dir = entry;
+
+	if (priv->dev_info)
+		debugfs_create_str("model", 0400, priv->debug_dir,
+				   (char **)&priv->dev_info->model);
+
+	entry = debugfs_create_dir("dt-conf", priv->debug_dir);
+	if (IS_ERR(entry))
+		goto err_remove;
+
+	dt_conf_dir = entry;
+
+	settings = &priv->settings;
+	debugfs_create_u8("cp0-ldo-en", 0400, dt_conf_dir,
+			  &settings->cp0_ldo_en);
+	debugfs_create_u8("cp1-ldo-en", 0400, dt_conf_dir,
+			  &settings->cp1_ldo_en);
+	debugfs_create_u8("cp0-ldo-volt", 0400, dt_conf_dir,
+			  &settings->cp0_ldo_volt);
+	debugfs_create_u8("cp1-ldo-volt", 0400, dt_conf_dir,
+			  &settings->cp1_ldo_volt);
+	debugfs_create_u8("tp0145-ldo-en", 0400, dt_conf_dir,
+			  &settings->tp0145_ldo_en);
+	debugfs_create_u8("tp2367-ldo-en", 0400, dt_conf_dir,
+			  &settings->tp2367_ldo_en);
+	debugfs_create_u8("tp0145-ldo-volt", 0400, dt_conf_dir,
+			  &settings->tp0145_ldo_volt);
+	debugfs_create_u8("tp2367-ldo-volt", 0400, dt_conf_dir,
+			  &settings->tp2367_ldo_volt);
+	debugfs_create_u8("tp0145-pullup", 0400, dt_conf_dir,
+			  &settings->tp0145_pullup);
+	debugfs_create_u8("tp2367-pullup", 0400, dt_conf_dir,
+			  &settings->tp2367_pullup);
+	debugfs_create_bool("hub-net-always-i3c", 0400, dt_conf_dir,
+			    &settings->hub_net_always_i3c);
+	debugfs_create_u8("tp-scl-h-ack-cycles", 0400, dt_conf_dir,
+			  &settings->tp_scl_h_ack_cycles);
+	debugfs_create_bool("handshake-optimize", 0400, dt_conf_dir,
+			    &settings->handshake_optimize);
+	debugfs_create_u8("fast-drv-h-add-cycles", 0400, dt_conf_dir,
+			  &settings->fast_drv_h_add_cycles);
+	debugfs_create_bool("fast-rson-en", 0400, dt_conf_dir,
+			    &settings->fast_rson_en);
+	debugfs_create_bool("tp-od-vol-optimize", 0400, dt_conf_dir,
+			    &settings->tp_od_vol_optimize);
+	debugfs_create_bool("tp-od-vref-optimize", 0400, dt_conf_dir,
+			    &settings->tp_od_vref_optimize);
+
+	for (i = 0; i < priv->dev_info->n_ports; ++i) {
+		char file_name[32];
+
+		sprintf(file_name, "tp%i.mode", i);
+		debugfs_create_u8(file_name, 0400, dt_conf_dir,
+				  &settings->tp[i].mode);
+		sprintf(file_name, "tp%i.pullup_en", i);
+		debugfs_create_u8(file_name, 0400, dt_conf_dir,
+				  &settings->tp[i].pullup_en);
+		sprintf(file_name, "tp%i.io_mode", i);
+		debugfs_create_u8(file_name, 0400, dt_conf_dir,
+				  &settings->tp[i].io_mode);
+		sprintf(file_name, "tp%i.poll_interval_ms", i);
+		debugfs_create_u32(file_name, 0400, dt_conf_dir,
+				   &settings->tp[i].poll_interval_ms);
+		sprintf(file_name, "tp%i.clock_frequency", i);
+		debugfs_create_u32(file_name, 0400, dt_conf_dir,
+				   &settings->tp[i].clock_frequency);
+	}
+
+	entry = debugfs_create_dir("reg", priv->debug_dir);
+	if (IS_ERR(entry))
+		goto err_remove;
+
+	reg_dir = entry;
+
+	entry = debugfs_create_file_unsafe("access", 0600, reg_dir, priv,
+					   &fops_access_reg);
+	if (IS_ERR(entry))
+		goto err_remove;
+
+	debugfs_create_u8("offset", 0600, reg_dir, &priv->reg_addr);
+
+	return 0;
+
+err_remove:
+	debugfs_remove_recursive(priv->debug_dir);
+	return PTR_ERR(entry);
+}
+
+static void i3c_hub_trans_pre_cb(struct logical_bus *bus)
+{
+	struct i3c_hub *priv = bus->priv;
+	struct device *dev = i3cdev_to_dev(priv->i3cdev);
+	int ret;
+
+	if (priv->settings.tp[bus->tp_id].always_enable)
+		return;
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_NET_CON_CONF,
+				 GENMASK(bus->tp_id, bus->tp_id), bus->tp_map);
+	if (ret)
+		dev_warn(dev, "Failed to open Target Port(s)\n");
+}
+
+static void i3c_hub_trans_post_cb(struct logical_bus *bus)
+{
+	struct i3c_hub *priv = bus->priv;
+	struct device *dev = i3cdev_to_dev(priv->i3cdev);
+	int ret;
+
+	if (priv->settings.tp[bus->tp_id].always_enable)
+		return;
+
+	ret = regmap_update_bits(priv->regmap, I3C_HUB_TP_NET_CON_CONF,
+				 GENMASK(bus->tp_id, bus->tp_id), 0x00);
+	if (ret)
+		dev_warn(dev, "Failed to close Target Port(s)\n");
+}
+
+static struct logical_bus *bus_from_i3c_desc(struct i3c_dev_desc *desc)
+{
+	struct i3c_master_controller *controller = i3c_dev_get_master(desc);
+
+	return container_of(controller, struct logical_bus, controller);
+}
+
+static struct logical_bus *bus_from_i2c_desc(struct i2c_dev_desc *desc)
+{
+	struct i3c_master_controller *controller = i2c_dev_get_master(desc);
+
+	return container_of(controller, struct logical_bus, controller);
+}
+
+static struct i3c_master_controller *
+parent_from_controller(struct i3c_master_controller *controller)
+{
+	struct logical_bus *bus =
+		container_of(controller, struct logical_bus, controller);
+
+	return bus->priv->driving_master;
+}
+
+static struct i3c_master_controller *
+parent_controller_from_i3c_desc(struct i3c_dev_desc *desc)
+{
+	struct i3c_master_controller *controller = i3c_dev_get_master(desc);
+	struct logical_bus *bus =
+		container_of(controller, struct logical_bus, controller);
+
+	return bus->priv->driving_master;
+}
+
+static struct i3c_master_controller *
+parent_controller_from_i2c_desc(struct i2c_dev_desc *desc)
+{
+	struct i3c_master_controller *controller = desc->common.master;
+	struct logical_bus *bus =
+		container_of(controller, struct logical_bus, 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 i3c_hub_read_transaction_status(struct i3c_hub *priv, u8 target_port,
+					   u8 target_port_status, u32 data_len)
+{
+	unsigned int status_read;
+	int ret;
+	struct i2c_adapter_group *smbus =
+		&priv->smbus_port_adapter[target_port];
+	u32 smbus_clk = priv->settings.tp[target_port].clock_frequency / 1000;
+	u8 status;
+	u8 ret_code;
+
+	if (!priv->settings.tp[target_port].poll_interval_ms) {
+		ret = wait_for_completion_timeout(&smbus->completion,
+						  smbus->i2c.timeout);
+		if (!ret) {
+			dev_err(&priv->i3cdev->dev,
+				"Status read timeout reached on target port %d\n",
+				target_port);
+			return -ETIMEDOUT;
+		}
+
+		status = (u8)smbus->status &
+			 I3C_HUB_CONTROLLER_AGENT_STATUS_MASK;
+	} else {
+		ret = regmap_read_poll_timeout(
+			priv->regmap, target_port_status, status_read,
+			(u8)status_read & I3C_HUB_CONTROLLER_AGENT_FINISH_FLAG,
+			I3C_HUB_SMBUS_STATUS_READ_INTERVAL_US_CEIL(data_len,
+								   smbus_clk),
+			jiffies_to_usecs(smbus->i2c.timeout));
+
+		if (ret) {
+			dev_err(&priv->i3cdev->dev,
+				"Status read timeout reached on target port %d\n",
+				target_port);
+			return ret;
+		}
+
+		ret = regmap_write(priv->regmap, target_port_status,
+				   I3C_HUB_CONTROLLER_AGENT_FINISH_FLAG);
+		if (ret)
+			return ret;
+
+		status = (u8)status_read & I3C_HUB_CONTROLLER_AGENT_STATUS_MASK;
+	}
+
+	ret_code = status >> I3C_HUB_CONTROLLER_AGENT_RET_CODE_SHIFT;
+
+	switch (ret_code) {
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_SUCCESS:
+		return 0;
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_ADDRESS_NACK:
+		dev_dbg(&priv->i3cdev->dev,
+			"TP%u SMBus: Address NACK (device not present)\n",
+			target_port);
+		return -ENXIO;
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_DEVICE_BUSY:
+		dev_dbg(&priv->i3cdev->dev,
+			"TP%u SMBus: Device busy (data NACK after address ACK)\n",
+			target_port);
+		return -EREMOTEIO;
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_READ_NOT_READY:
+		dev_dbg(&priv->i3cdev->dev,
+			"TP%u SMBus: Device read not ready (read address NACK after write)\n",
+			target_port);
+		return -ENXIO;
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_SYNC_RECOVERED:
+		dev_dbg(&priv->i3cdev->dev,
+			"TP%u SMBus: Sync issue recovered (SDA stuck low, recovered by 9 SCL pulses)\n",
+			target_port);
+		return -EAGAIN;
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_SYNC_BUS_CLEAR:
+		dev_dbg(&priv->i3cdev->dev,
+			"TP%u SMBus: Sync issue bus clear (recovered by SCL low 35ms)\n",
+			target_port);
+		return -EAGAIN;
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_BUS_FAULT:
+		dev_err(&priv->i3cdev->dev,
+			"TP%u SMBus: Bus fault (SDA stuck low remains after recovery)\n",
+			target_port);
+		return -EIO;
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_ARBITRATION_LOST:
+		dev_dbg(&priv->i3cdev->dev, "TP%u SMBus: Arbitration lost\n",
+			target_port);
+		return -EAGAIN;
+	case I3C_HUB_CONTROLLER_AGENT_RET_CODE_SCL_TIMEOUT:
+		dev_err(&priv->i3cdev->dev, "TP%u SMBus: SCL timeout\n",
+			target_port);
+		return -ETIMEDOUT;
+	default:
+		dev_err(&priv->i3cdev->dev,
+			"TP%u SMBus: Reserved/unknown return code 0x%x\n",
+			target_port, ret_code);
+		return -EIO;
+	}
+}
+
+/*
+ * i3c_hub_smbus_msg() - This starts a smbus transaction by writing a descriptor
+ * and a message to the hub registers. Controller buffer page is determined by multiplying the
+ * target port index by four and adding the base page number to it.
+ * @hub: a pointer to the i3c hub main structure
+ * @target_port: a number of the port where the transaction will happen
+ * @xfers: i2c_msg struct received from the master_xfers callback
+ * @nxfers_i: the number of the current message
+ * @xfer_type: transfer type:
+ *   - I3C_HUB_SMBUS_XFER_WRITE (0): single write
+ *   - I3C_HUB_SMBUS_XFER_READ  (1): single read
+ *   - I3C_HUB_SMBUS_XFER_WR_RD (2): write followed by read
+ *     (uses xfers[nxfers_i] as write and xfers[nxfers_i+1] as read)
+ *
+ * Return: 0 on success, negative errno on failure from hub status or regmap ops.
+ * Note: for WR_RD the caller must ensure xfers[nxfers_i+1] exists, the address
+ * matches, and write_len + read_len <= I3C_HUB_SMBUS_PAYLOAD_SIZE.
+ */
+static int i3c_hub_smbus_msg(struct i3c_hub *hub, struct i2c_msg *xfers,
+			     u8 target_port, u8 nxfers_i, u8 xfer_type)
+{
+	u8 transaction_type = I3C_HUB_SMBUS_400kHz;
+	u8 controller_buffer_page =
+		I3C_HUB_CONTROLLER_BUFFER_PAGE + 4 * target_port;
+	int write_length = 0, read_length = 0;
+	u8 target_port_status = I3C_HUB_TP0_SMBUS_AGNT_STS + target_port;
+	u8 target_port_code = BIT(target_port);
+	u8 rw_address = xfers[nxfers_i].addr << 1;
+	u8 desc[I3C_HUB_SMBUS_DESCRIPTOR_SIZE] = { 0 };
+	int ret = 0;
+
+	transaction_type = i3c_hub_smbus_rate_bits_from_hz(
+		hub->settings.tp[target_port].clock_frequency);
+
+	switch (xfer_type) {
+	case I3C_HUB_SMBUS_XFER_WRITE:
+		write_length = xfers[nxfers_i].len;
+		break;
+	case I3C_HUB_SMBUS_XFER_READ:
+		read_length = xfers[nxfers_i].len;
+		rw_address |= BIT(0);
+		break;
+	case I3C_HUB_SMBUS_XFER_WR_RD:
+		write_length = xfers[nxfers_i].len;
+		read_length = xfers[nxfers_i + 1].len;
+		transaction_type |= BIT(0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Assemble descriptor */
+	desc[0] = rw_address;
+	desc[1] = transaction_type;
+	desc[2] = write_length;
+	desc[3] = read_length;
+
+	mutex_lock(&hub->page_mutex);
+	ret = regmap_write(hub->regmap, I3C_HUB_PAGE_PTR,
+			   controller_buffer_page);
+	if (ret)
+		goto unlock;
+
+	ret = regmap_bulk_write(hub->regmap, I3C_HUB_CONTROLLER_AGENT_BUFF,
+				desc, I3C_HUB_SMBUS_DESCRIPTOR_SIZE);
+	if (ret)
+		goto unlock;
+
+	if (write_length) {
+		ret = regmap_bulk_write(hub->regmap,
+					I3C_HUB_CONTROLLER_AGENT_BUFF_DATA,
+					xfers[nxfers_i].buf, write_length);
+		if (ret)
+			goto unlock;
+	}
+
+	ret = regmap_write(hub->regmap, I3C_HUB_PAGE_PTR, 0x00);
+	mutex_unlock(&hub->page_mutex);
+	if (ret)
+		return ret;
+
+	/* Start transaction */
+	ret = regmap_write(hub->regmap, I3C_HUB_TP_SMBUS_AGNT_TRANS_START,
+			   target_port_code);
+	if (ret)
+		return ret;
+
+	/* Get transaction status */
+	ret = i3c_hub_read_transaction_status(hub, target_port,
+					      target_port_status,
+					      write_length + read_length);
+	if (ret)
+		return ret;
+
+	/* if read_length is non-zero, read back the data */
+	if (read_length) {
+		mutex_lock(&hub->page_mutex);
+		ret = regmap_write(hub->regmap, I3C_HUB_PAGE_PTR,
+				   controller_buffer_page);
+		if (ret)
+			goto unlock;
+
+		if (xfer_type == I3C_HUB_SMBUS_XFER_READ) {
+			ret = regmap_bulk_read(
+				hub->regmap, I3C_HUB_CONTROLLER_AGENT_BUFF_DATA,
+				xfers[nxfers_i].buf, read_length);
+		} else {
+			ret = regmap_bulk_read(
+				hub->regmap,
+				I3C_HUB_CONTROLLER_AGENT_BUFF_DATA +
+					write_length,
+				xfers[nxfers_i + 1].buf, read_length);
+		}
+		if (ret)
+			goto unlock;
+
+		ret = regmap_write(hub->regmap, I3C_HUB_PAGE_PTR, 0x00);
+		mutex_unlock(&hub->page_mutex);
+	}
+
+	return ret;
+unlock:
+	regmap_write(hub->regmap, I3C_HUB_PAGE_PTR, 0x00);
+	mutex_unlock(&hub->page_mutex);
+	return ret;
+}
+
+static inline bool i3c_hub_can_combine_wr_rd(const struct i2c_msg *w,
+					     const struct i2c_msg *r)
+{
+	/* w: write, r: read; same addr; total length within payload */
+	return !(w->flags & I2C_M_RD) && (r->flags & I2C_M_RD) &&
+	       w->addr == r->addr &&
+	       (w->len + r->len) <= I3C_HUB_SMBUS_PAYLOAD_SIZE;
+}
+
+/**
+ * i3c_hub_smbus_port_adapter_xfer() - i3c hub smbus transfer logic
+ * @adap: i2c_adapter corresponding with single port in the i3c hub
+ * @xfers: all messages descriptors and data
+ * @nxfers: amount of single messages in a transfer
+ *
+ * Return: function returns the sum of correctly sent messages (only those with hub return
+ * status 0x01)
+ */
+static int i3c_hub_smbus_port_adapter_xfer(struct i2c_adapter *adap,
+					   struct i2c_msg *xfers, int nxfers)
+{
+	struct i2c_adapter_group *smbus = i2c_get_adapdata(adap);
+	struct i3c_hub *hub = smbus->priv;
+	int ret_sum = 0, ret, len, type, nxfers_i;
+	const struct i2c_msg *cur = NULL, *next = NULL;
+
+	for (nxfers_i = 0; nxfers_i < nxfers; nxfers_i++) {
+		cur = &xfers[nxfers_i];
+		len = cur->len;
+		type = cur->flags & I2C_M_RD ? I3C_HUB_SMBUS_XFER_READ :
+					       I3C_HUB_SMBUS_XFER_WRITE;
+
+		/* Per-message length limit check */
+		if (len > I3C_HUB_SMBUS_PAYLOAD_SIZE) {
+			dev_err(&adap->dev,
+				"Message nr. %d not sent - length over %d bytes.\n",
+				nxfers_i, I3C_HUB_SMBUS_PAYLOAD_SIZE);
+			continue;
+		}
+
+		/* Try to combine write followed by read to the same address */
+		if (type == I3C_HUB_SMBUS_XFER_WRITE &&
+		    (nxfers_i + 1) < nxfers) {
+			next = &xfers[nxfers_i + 1];
+			if (i3c_hub_can_combine_wr_rd(cur, next))
+				type = I3C_HUB_SMBUS_XFER_WR_RD;
+		}
+
+		ret = i3c_hub_smbus_msg(hub, xfers, smbus->tp_port, nxfers_i,
+					type);
+		if (ret)
+			return ret;
+
+		if (type == I3C_HUB_SMBUS_XFER_WR_RD) {
+			ret_sum += 2;
+			nxfers_i++; /* skip the next read message */
+
+		} else {
+			ret_sum++;
+		}
+	}
+	return ret_sum;
+}
+
+static int i3c_hub_bus_init(struct i3c_master_controller *controller)
+{
+	struct logical_bus *bus =
+		container_of(controller, struct logical_bus, controller);
+
+	controller->this = bus->priv->i3cdev->desc;
+	return 0;
+}
+
+static void i3c_hub_bus_cleanup(struct i3c_master_controller *controller)
+{
+	controller->this = NULL;
+}
+
+static int i3c_hub_attach_i3c_dev(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *parent =
+		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 i3c_hub_reattach_i3c_dev(struct i3c_dev_desc *dev, u8 old_dyn_addr)
+{
+	struct i3c_master_controller *parent =
+		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 i3c_hub_detach_i3c_dev(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *parent =
+		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 i3c_hub_do_daa(struct i3c_master_controller *controller)
+{
+	struct i3c_master_controller *parent =
+		parent_from_controller(controller);
+	int ret;
+
+	down_write(&parent->bus.lock);
+	ret = parent->ops->do_daa(parent);
+	up_write(&parent->bus.lock);
+	return ret;
+}
+
+static bool i3c_hub_supports_ccc_cmd(struct i3c_master_controller *controller,
+				     const struct i3c_ccc_cmd *cmd)
+{
+	struct i3c_master_controller *parent =
+		parent_from_controller(controller);
+
+	return parent->ops->supports_ccc_cmd(parent, cmd);
+}
+
+static int i3c_hub_send_ccc_cmd(struct i3c_master_controller *controller,
+				struct i3c_ccc_cmd *cmd)
+{
+	struct i3c_master_controller *parent =
+		parent_from_controller(controller);
+	struct logical_bus *bus =
+		container_of(controller, struct logical_bus, controller);
+	int ret;
+
+	if (cmd->id == I3C_CCC_RSTDAA(true))
+		return 0;
+
+	i3c_hub_trans_pre_cb(bus);
+	down_read(&parent->bus.lock);
+	ret = parent->ops->send_ccc_cmd(parent, cmd);
+	up_read(&parent->bus.lock);
+	i3c_hub_trans_post_cb(bus);
+
+	return ret;
+}
+
+static int i3c_hub_priv_xfers(struct i3c_dev_desc *dev,
+			      struct i3c_xfer *xfers, int nxfers,
+			      enum i3c_xfer_mode mode)
+{
+	struct i3c_master_controller *parent =
+		parent_controller_from_i3c_desc(dev);
+	struct i3c_master_controller *orig_parent;
+	struct logical_bus *bus = bus_from_i3c_desc(dev);
+	int res;
+
+	i3c_hub_trans_pre_cb(bus);
+	orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+	down_read(&parent->bus.lock);
+	res = parent->ops->i3c_xfers(dev, xfers, nxfers, mode);
+	up_read(&parent->bus.lock);
+	restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+	i3c_hub_trans_post_cb(bus);
+
+	return res;
+}
+
+static int i3c_hub_attach_i2c_dev(struct i2c_dev_desc *dev)
+{
+	struct i3c_master_controller *parent =
+		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 i3c_hub_detach_i2c_dev(struct i2c_dev_desc *dev)
+{
+	struct i3c_master_controller *parent =
+		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 i3c_hub_i2c_xfers(struct i2c_dev_desc *dev, struct i2c_msg *xfers,
+			     int nxfers)
+{
+	struct i3c_master_controller *parent =
+		parent_controller_from_i2c_desc(dev);
+	struct logical_bus *bus = bus_from_i2c_desc(dev);
+	struct i3c_master_controller *orig_parent;
+	int ret;
+
+	i3c_hub_trans_pre_cb(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);
+	i3c_hub_trans_post_cb(bus);
+	return ret;
+}
+
+static int i3c_hub_request_ibi(struct i3c_dev_desc *dev,
+			       const struct i3c_ibi_setup *req)
+{
+	struct i3c_master_controller *parent =
+		parent_controller_from_i3c_desc(dev);
+	struct logical_bus *bus = bus_from_i3c_desc(dev);
+	struct i3c_master_controller *orig_parent;
+	int ret;
+
+	i3c_hub_trans_pre_cb(bus);
+	orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+	down_read(&parent->bus.lock);
+	ret = parent->ops->request_ibi(dev, req);
+	up_read(&parent->bus.lock);
+	restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+	i3c_hub_trans_post_cb(bus);
+	return ret;
+}
+
+static void i3c_hub_free_ibi(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *parent =
+		parent_controller_from_i3c_desc(dev);
+	struct logical_bus *bus = bus_from_i3c_desc(dev);
+	struct i3c_master_controller *orig_parent;
+
+	i3c_hub_trans_pre_cb(bus);
+	orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+	down_read(&parent->bus.lock);
+	parent->ops->free_ibi(dev);
+	up_read(&parent->bus.lock);
+	restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+	i3c_hub_trans_post_cb(bus);
+}
+
+static int i3c_hub_enable_ibi(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *parent =
+		parent_controller_from_i3c_desc(dev);
+	struct logical_bus *bus = bus_from_i3c_desc(dev);
+	struct i3c_master_controller *orig_parent;
+	int ret;
+
+	i3c_hub_trans_pre_cb(bus);
+	orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+	down_read(&parent->bus.lock);
+	ret = parent->ops->enable_ibi(dev);
+	up_read(&parent->bus.lock);
+	restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+	i3c_hub_trans_post_cb(bus);
+	return ret;
+}
+
+static int i3c_hub_disable_ibi(struct i3c_dev_desc *dev)
+{
+	struct i3c_master_controller *parent =
+		parent_controller_from_i3c_desc(dev);
+	struct logical_bus *bus = bus_from_i3c_desc(dev);
+	struct i3c_master_controller *orig_parent;
+	int ret;
+
+	i3c_hub_trans_pre_cb(bus);
+	orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+	down_read(&parent->bus.lock);
+	ret = parent->ops->disable_ibi(dev);
+	up_read(&parent->bus.lock);
+	restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+	i3c_hub_trans_post_cb(bus);
+	return ret;
+}
+
+static void i3c_hub_recycle_ibi_slot(struct i3c_dev_desc *dev,
+				     struct i3c_ibi_slot *slot)
+{
+	struct i3c_master_controller *parent =
+		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 i3c_hub_i3c_ops = {
+	.bus_init = i3c_hub_bus_init,
+	.bus_cleanup = i3c_hub_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_do_daa,
+	.supports_ccc_cmd = i3c_hub_supports_ccc_cmd,
+	.send_ccc_cmd = i3c_hub_send_ccc_cmd,
+	.i3c_xfers = i3c_hub_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,
+};
+
+static int i3c_hub_logic_register(struct i3c_master_controller *controller,
+				  struct device *parent)
+{
+	return i3c_master_register(controller, parent, &i3c_hub_i3c_ops, false);
+}
+
+static u32 i3c_hub_smbus_funcs(struct i2c_adapter *adapter)
+{
+	return (I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C) & ~I2C_FUNC_SMBUS_QUICK;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int reg_i2c_target(struct i2c_client *client)
+{
+	struct i2c_adapter_group *smbus = i2c_get_adapdata(client->adapter);
+	struct smbus_backend *backend;
+	int ret = 0;
+
+	if (!smbus)
+		return -EINVAL;
+
+	mutex_lock(&smbus->mutex);
+
+	list_for_each_entry(backend, &smbus->backend_entry, list) {
+		if (backend->client->addr == client->addr) {
+			ret = -EBUSY;
+			goto out;
+		}
+	}
+
+	backend = kzalloc(sizeof(*backend), GFP_KERNEL);
+	if (!backend) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	backend->client = client;
+	list_add(&backend->list, &smbus->backend_entry);
+
+out:
+	mutex_unlock(&smbus->mutex);
+	return ret;
+}
+
+static int unreg_i2c_target(struct i2c_client *client)
+{
+	struct i2c_adapter_group *smbus = i2c_get_adapdata(client->adapter);
+	struct smbus_backend *backend;
+	bool found = false;
+
+	if (!smbus)
+		return -EINVAL;
+
+	mutex_lock(&smbus->mutex);
+
+	list_for_each_entry(backend, &smbus->backend_entry, list) {
+		if (backend->client->addr == client->addr) {
+			list_del(&backend->list);
+			kfree(backend);
+			found = true;
+			break;
+		}
+	}
+
+	mutex_unlock(&smbus->mutex);
+	return found ? 0 : -ENODEV;
+}
+#endif /* CONFIG_I2C_SLAVE */
+
+static const struct i2c_algorithm i3c_hub_smbus_algo = {
+	.master_xfer = i3c_hub_smbus_port_adapter_xfer,
+	.functionality = i3c_hub_smbus_funcs,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	.reg_slave = reg_i2c_target,
+	.unreg_slave = unreg_i2c_target,
+#endif
+};
+
+static void i3c_hub_delayed_work(struct work_struct *work)
+{
+	struct i3c_hub *priv =
+		container_of(work, typeof(*priv), delayed_work.work);
+	struct device *dev = i3cdev_to_dev(priv->i3cdev);
+	struct logical_bus *bus;
+	struct i2c_adapter_group *smbus;
+	int ret;
+	int i;
+	unsigned int reg_val = 0;
+
+	/* record reg 81: tp hubnetwork connection setting */
+	ret = regmap_read(priv->regmap, I3C_HUB_TP_NET_CON_CONF, &reg_val);
+	if (ret) {
+		dev_warn(dev, "Failed to read hubnetwork connection setting\n");
+		return;
+	}
+
+	ret = regmap_write(priv->regmap, I3C_HUB_TP_NET_CON_CONF, 0x00);
+	if (ret) {
+		dev_warn(dev, "Failed to close Target Port(s)\n");
+		return;
+	}
+
+	for (i = 0; i < priv->dev_info->n_ports; ++i) {
+		bus = &priv->logical_bus[i];
+		if (bus->available) {
+			ret = regmap_update_bits(
+				priv->regmap, I3C_HUB_TP_NET_CON_CONF,
+				GENMASK(bus->tp_id, bus->tp_id), bus->tp_map);
+			if (ret) {
+				dev_warn(dev,
+					 "Failed to open Target Port(s)\n");
+				return;
+			}
+
+			dev->of_node = bus->of_node;
+			ret = i3c_hub_logic_register(&bus->controller, dev);
+			if (ret) {
+				dev_warn(dev,
+					 "Failed to register i3c controller - bus id:%i\n",
+					 i);
+				return;
+			}
+			bus->registered = true;
+
+			ret = regmap_update_bits(
+				priv->regmap, I3C_HUB_TP_NET_CON_CONF,
+				GENMASK(bus->tp_id, bus->tp_id), 0x00);
+			if (ret) {
+				dev_warn(dev,
+					 "Failed to close Target Port(s)\n");
+				return;
+			}
+
+			if (!priv->settings.tp[i].always_enable)
+				reg_val &= ~GENMASK(bus->tp_id, bus->tp_id);
+		}
+	}
+
+	/* update tp hubnetwork connection setting */
+	ret = regmap_write(priv->regmap, I3C_HUB_TP_NET_CON_CONF, reg_val);
+	if (ret) {
+		dev_warn(dev, "Failed to open Target Port(s)\n");
+		return;
+	}
+
+	ret = i3c_master_do_daa(priv->driving_master);
+	if (ret) {
+		dev_warn(dev, "Failed to run DAA\n");
+		return;
+	}
+
+	for (i = 0; i < priv->dev_info->n_ports; i++) {
+		smbus = &priv->smbus_port_adapter[i];
+		if (!smbus->used)
+			continue;
+
+		if (!priv->settings.tp[i].poll_interval_ms)
+			continue;
+
+		schedule_delayed_work(
+			&smbus->delayed_work_polling,
+			msecs_to_jiffies(
+				priv->settings.tp[i].poll_interval_ms));
+	}
+}
+
+static int send_smbus_target_data_to_backend(struct i2c_adapter_group *smbus,
+					     u8 address, u8 *local_buffer,
+					     u8 len)
+{
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+	struct smbus_backend *backend;
+	struct i2c_client *client;
+	int i, ret;
+	u8 tmp;
+
+	mutex_lock(&smbus->mutex);
+
+	list_for_each_entry(backend, &smbus->backend_entry, list) {
+		client = backend->client;
+		if (client->addr == address >> 1) {
+			mutex_unlock(&smbus->mutex);
+			ret = i2c_slave_event(client, I2C_SLAVE_WRITE_REQUESTED,
+					      &address);
+			if (ret)
+				return ret;
+
+			for (i = 0; i < len; i++) {
+				ret = i2c_slave_event(client,
+						      I2C_SLAVE_WRITE_RECEIVED,
+						      &local_buffer[i]);
+				if (ret)
+					return ret;
+			}
+
+			return i2c_slave_event(client, I2C_SLAVE_STOP, &tmp);
+		}
+	}
+
+	mutex_unlock(&smbus->mutex);
+#endif /* CONFIG_I2C_SLAVE */
+	return -ENXIO;
+}
+
+static int read_smbus_target_buffer_page(struct i2c_adapter_group *smbus,
+					 u8 target_buffer_page, u8 *address,
+					 u8 *local_buffer, u8 *len)
+{
+	struct i3c_hub *hub = smbus->priv;
+	struct device *dev = i3cdev_to_dev(hub->i3cdev);
+	u32 status;
+	int ret;
+
+	mutex_lock(&hub->page_mutex);
+	regmap_write(hub->regmap, I3C_HUB_PAGE_PTR, target_buffer_page);
+
+	ret = regmap_read(hub->regmap, I3C_HUB_TARGET_BUFF_LENGTH, &status);
+	if (ret)
+		goto error;
+
+	*len = status - 1;
+	if (!*len)
+		goto error;
+
+	if (*len > I3C_HUB_SMBUS_TARGET_PAYLOAD_SIZE) {
+		dev_warn_ratelimited(
+			dev, "Received message too big for hub buffer\n");
+		ret = -EMSGSIZE;
+		*len = 0;
+		goto error;
+	}
+
+	ret = regmap_read(hub->regmap, I3C_HUB_TARGET_BUFF_ADDRESS, &status);
+	if (ret)
+		goto error;
+
+	*address = status;
+
+	ret = regmap_bulk_read(hub->regmap, I3C_HUB_TARGET_BUFF_DATA,
+			       local_buffer, *len);
+
+error:
+	regmap_write(hub->regmap, I3C_HUB_PAGE_PTR, 0x00);
+	mutex_unlock(&hub->page_mutex);
+	return ret;
+}
+
+static int process_smbus_controller_status(struct i2c_adapter_group *smbus,
+					   u8 reg, u32 status)
+{
+	struct i3c_hub *hub = smbus->priv;
+	int ret = 0;
+
+	if (status & I3C_HUB_CONTROLLER_AGENT_FINISH_FLAG) {
+		smbus->status = status;
+		ret = regmap_write(hub->regmap, reg,
+				   I3C_HUB_CONTROLLER_AGENT_FINISH_FLAG);
+		complete(&smbus->completion);
+	}
+
+	return ret;
+}
+
+/**
+ * Controller buffer page is determined by adding the first buffer page number to port
+ * index multiplied by four. The two target buffer page numbers are determined the same
+ * way but they are offset by 2 and 3 from the controller page.
+ */
+static int process_smbus_target_status(struct i2c_adapter_group *smbus, u8 reg,
+				       u32 status)
+{
+	struct i3c_hub *hub = smbus->priv;
+	struct device *dev = i3cdev_to_dev(hub->i3cdev);
+	u8 controller_buffer_page =
+		I3C_HUB_CONTROLLER_BUFFER_PAGE + 4 * smbus->tp_port;
+	u8 local_buffer[I3C_HUB_SMBUS_TARGET_PAYLOAD_SIZE] = { 0 };
+	u8 target_buffer_page, address = 0, len = 0, flag;
+	int ret;
+
+	if (smbus->last_processed_buf)
+		status &= ~smbus->last_processed_buf;
+
+	if (status & I3C_HUB_TARGET_BUF_0_RECEIVE) {
+		target_buffer_page = controller_buffer_page + 2;
+		flag = I3C_HUB_TARGET_BUF_0_RECEIVE;
+	} else if (status & I3C_HUB_TARGET_BUF_1_RECEIVE) {
+		target_buffer_page = controller_buffer_page + 3;
+		flag = I3C_HUB_TARGET_BUF_1_RECEIVE;
+	} else {
+		return -EINVAL;
+	}
+
+	ret = read_smbus_target_buffer_page(smbus, target_buffer_page, &address,
+					    local_buffer, &len);
+	if (ret && ret != -EMSGSIZE) {
+		dev_dbg(dev, "Failed to read target buffer page: %d\n", ret);
+		return ret;
+	}
+
+	smbus->last_processed_buf = flag;
+
+	if (status & I3C_HUB_TARGET_BUF_OVRFL)
+		flag |= I3C_HUB_TARGET_BUF_OVRFL;
+
+	ret = regmap_write(hub->regmap, reg, flag);
+	if (ret) {
+		dev_dbg(dev, "Failed to clear target port status\n");
+		return ret;
+	}
+
+	if (len) {
+		ret = send_smbus_target_data_to_backend(smbus, address,
+							local_buffer, len);
+		if (ret) {
+			dev_dbg(dev, "Failed to send data to backend: %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int i3c_hub_process_smbus_status(struct i2c_adapter_group *smbus)
+{
+	struct i3c_hub *hub = smbus->priv;
+	u8 target_port_status = I3C_HUB_TP0_SMBUS_AGNT_STS + smbus->tp_port;
+	struct device *dev = i3cdev_to_dev(hub->i3cdev);
+	u32 status;
+	int ret = 0;
+	u32 poll_interval_ms =
+		hub->settings.tp[smbus->tp_port].poll_interval_ms;
+
+	ret = regmap_read(hub->regmap, target_port_status, &status);
+	if (ret)
+		return ret;
+
+	/* smbus controller agent status */
+	if (!poll_interval_ms) {
+		ret = process_smbus_controller_status(smbus, target_port_status,
+						      status);
+		if (ret)
+			dev_warn_ratelimited(
+				dev,
+				"Failed to process smbus controller status\n");
+	}
+
+	/* smbus target agent status */
+	status &= I3C_HUB_TARGET_BUF_STATUS_MASK;
+
+	while (status) {
+		ret = process_smbus_target_status(smbus, target_port_status,
+						  status);
+		if (ret) {
+			dev_warn_ratelimited(
+				dev,
+				"Failed to process smbus target status: %d\n",
+				ret);
+			break;
+		}
+
+		if (!poll_interval_ms)
+			break;
+
+		ret = regmap_read(hub->regmap, target_port_status, &status);
+		if (ret)
+			break;
+		status &= I3C_HUB_TARGET_BUF_STATUS_MASK;
+	}
+
+	return ret;
+}
+
+/**
+ * i3c_hub_delayed_work_polling() - This delayed work is a polling mechanism to
+ * find if any transaction happened.
+ */
+static void i3c_hub_delayed_work_polling(struct work_struct *work)
+{
+	struct i2c_adapter_group *smbus =
+		container_of(work, typeof(*smbus), delayed_work_polling.work);
+	struct device *dev = i3cdev_to_dev(smbus->priv->i3cdev);
+	int ret;
+
+	if (!list_empty(&smbus->backend_entry)) {
+		ret = i3c_hub_process_smbus_status(smbus);
+		if (ret)
+			dev_warn_ratelimited(
+				dev,
+				"Failed to process TP %u smbus status: %d\n",
+				smbus->tp_port, ret);
+	}
+
+	schedule_delayed_work(
+		&smbus->delayed_work_polling,
+		msecs_to_jiffies(smbus->priv->settings.tp[smbus->tp_port]
+					 .poll_interval_ms));
+}
+
+static int i3c_hub_smbus_ibi_handler(struct i3c_hub *hub,
+				     const struct i3c_ibi_payload *payload)
+{
+	struct i2c_adapter_group *smbus;
+	u8 tp, tps;
+	int val, ret = 0, rc;
+	struct device *dev = i3cdev_to_dev(hub->i3cdev);
+
+	if (payload->len < 2) {
+		ret = regmap_read(hub->regmap, I3C_HUB_TP_SMBUS_AGNT_IBI_STS,
+				  &val);
+		if (ret)
+			return ret;
+
+		tps = (u8)val;
+	} else {
+		tps = ((const u8 *)payload->data)[1];
+	}
+
+	if (!tps)
+		return 0;
+
+	while (tps) {
+		tp = (u8)__ffs((unsigned long)tps);
+		tps &= (tps - 1);
+
+		if (hub->settings.tp[tp].poll_interval_ms)
+			continue;
+
+		smbus = &hub->smbus_port_adapter[tp];
+		rc = i3c_hub_process_smbus_status(smbus);
+		if (rc) {
+			dev_warn_ratelimited(
+				dev,
+				"Failed to process TP %u smbus status: %d\n",
+				tp, rc);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Sysfs attribute: clock_frequency
+ * Read/Write the SMBus clock frequency for this adapter's port.
+ */
+static ssize_t clock_frequency_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct i2c_adapter *adap = to_i2c_adapter(dev);
+	struct i2c_adapter_group *smbus = i2c_get_adapdata(adap);
+	struct i3c_hub *hub = smbus->priv;
+
+	return sprintf(buf, "%u\n",
+		       hub->settings.tp[smbus->tp_port].clock_frequency);
+}
+
+static ssize_t clock_frequency_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct i2c_adapter *adap = to_i2c_adapter(dev);
+	struct i2c_adapter_group *smbus = i2c_get_adapdata(adap);
+	struct i3c_hub *hub = smbus->priv;
+	u32 val;
+	int ret;
+
+	ret = kstrtou32(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (!i3c_hub_smbus_validate_clock_frequency(val))
+		return -EINVAL;
+
+	hub->settings.tp[smbus->tp_port].clock_frequency = val;
+
+	return count;
+}
+static DEVICE_ATTR_RW(clock_frequency);
+
+static struct attribute *i3c_hub_smbus_attrs[] = {
+	&dev_attr_clock_frequency.attr,
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(i3c_hub_smbus);
+
+static int i3c_hub_smbus_tp_algo(struct i3c_hub *priv, int i)
+{
+	struct device *dev = i3cdev_to_dev(priv->i3cdev);
+	int ret;
+	struct i2c_adapter_group *smbus = &priv->smbus_port_adapter[i];
+	struct i2c_adapter *i2c = &smbus->i2c;
+
+	mutex_init(&smbus->mutex);
+	INIT_LIST_HEAD(&smbus->backend_entry);
+	smbus->priv = priv;
+	smbus->tp_port = i;
+	smbus->tp_mask = BIT(i);
+
+	init_completion(&smbus->completion);
+	i2c->owner = THIS_MODULE;
+	i2c->algo = &i3c_hub_smbus_algo;
+	i2c->dev.parent = dev;
+	i2c->dev.of_node = smbus->of_node;
+	i2c->dev.groups = i3c_hub_smbus_groups;
+	i2c->timeout = HZ;
+	i2c->retries = 3;
+	snprintf(i2c->name, sizeof(i2c->name), "hub%s.port%d", dev_name(dev),
+		 smbus->tp_port);
+
+	if (priv->settings.tp[i].poll_interval_ms) {
+		ret = regmap_clear_bits(priv->regmap, I3C_HUB_TP_IBI_CONF,
+					smbus->tp_mask);
+		if (ret)
+			return ret;
+		INIT_DELAYED_WORK(&smbus->delayed_work_polling,
+				  i3c_hub_delayed_work_polling);
+		priv->smbus_ibi_en_mask &= ~smbus->tp_mask;
+	} else {
+		ret = regmap_set_bits(priv->regmap, I3C_HUB_TP_IBI_CONF,
+				      smbus->tp_mask);
+		if (ret)
+			return ret;
+		priv->smbus_ibi_en_mask |= smbus->tp_mask;
+	}
+
+	/* Enable SDA hold-low when both SMBus Target Agent buffers are full.
+	 * Used as a flow-control mechanism for MCTP to avoid upstream TX timeout
+	 * when target buffers are not serviced in time.
+	 */
+	ret = regmap_set_bits(priv->regmap,
+			      I3C_HUB_ONCHIP_TD_AND_SMBUS_AGNT_CONF,
+			      TARGET_AGENT_BUF_FULL_SDA_LOW_EN);
+	if (ret)
+		return ret;
+
+	i2c_set_adapdata(i2c, smbus);
+
+	ret = i2c_add_adapter(i2c);
+	if (ret)
+		return ret;
+
+	smbus->used = 1;
+	return ret;
+}
+
+static int i3c_hub_gpio_direction_input(struct gpio_chip *gc, unsigned int off)
+{
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+	int ret = 0;
+	u8 reg, mask = 0;
+
+	reg = off % GPIO_BANK_SZ ? I3C_HUB_TP_SDA_OUT_EN :
+				   I3C_HUB_TP_SCL_OUT_EN;
+	mask = BIT(gpio->tp[off / GPIO_BANK_SZ]);
+
+	ret = regmap_update_bits(hub->regmap, reg, mask, 0);
+	return ret;
+}
+
+static int i3c_hub_gpio_direction_output(struct gpio_chip *gc, unsigned int off,
+					 int val)
+{
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+	int ret = 0;
+	u8 reg, mask = 0;
+
+	reg = off % GPIO_BANK_SZ ? I3C_HUB_TP_SDA_OUT_EN :
+				   I3C_HUB_TP_SCL_OUT_EN;
+	mask = BIT(gpio->tp[off / GPIO_BANK_SZ]);
+
+	ret = regmap_update_bits(hub->regmap, reg, mask, mask);
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(hub->regmap, reg + 2, mask, val ? mask : 0);
+	return ret;
+}
+
+static int i3c_hub_gpio_get_value(struct gpio_chip *gc, unsigned int off)
+{
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+	int ret = 0, val = 0, dir;
+	u8 reg, shift = 0;
+
+	dir = gc->get_direction(gc, off);
+	if (dir)
+		reg = off % GPIO_BANK_SZ ? I3C_HUB_TP_SDA_IN_LEVEL_STS :
+					   I3C_HUB_TP_SCL_IN_LEVEL_STS;
+	else
+		reg = off % GPIO_BANK_SZ ? I3C_HUB_TP_SDA_OUT_LEVEL :
+					   I3C_HUB_TP_SCL_OUT_LEVEL;
+
+	shift = gpio->tp[off / GPIO_BANK_SZ];
+
+	ret = regmap_read(hub->regmap, reg, &val);
+	if (ret)
+		return ret;
+
+	ret = (val >> shift) & 0x01;
+	return ret;
+}
+
+static int i3c_hub_gpio_set_value(struct gpio_chip *gc, unsigned int off, int val)
+{
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+	u8 reg, mask = 0;
+
+	reg = off % GPIO_BANK_SZ ? I3C_HUB_TP_SDA_OUT_LEVEL :
+				   I3C_HUB_TP_SCL_OUT_LEVEL;
+	mask = BIT(gpio->tp[off / GPIO_BANK_SZ]);
+
+	return regmap_update_bits(hub->regmap, reg, mask, val ? mask : 0);
+}
+
+static int i3c_hub_gpio_get_direction(struct gpio_chip *gc, unsigned int off)
+{
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+	int ret = 0, dir = 0;
+	u8 reg, shift = 0;
+
+	reg = off % GPIO_BANK_SZ ? I3C_HUB_TP_SDA_OUT_EN :
+				   I3C_HUB_TP_SCL_OUT_EN;
+	shift = gpio->tp[off / GPIO_BANK_SZ];
+
+	ret = regmap_read(hub->regmap, reg, &dir);
+	if (ret)
+		return ret;
+
+	ret = ~(dir >> shift) & 0x01;
+	return ret;
+}
+
+static void i3c_hub_gpio_irq_mask(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+	u8 reg, hwirq = 0, mask = 0;
+
+	hwirq = irqd_to_hwirq(d);
+
+	reg = hwirq % GPIO_BANK_SZ ? I3C_HUB_TP_SDA_IN_DETECT_IBI_EN :
+				     I3C_HUB_TP_SCL_IN_DETECT_IBI_EN;
+	mask = BIT(gpio->tp[hwirq / GPIO_BANK_SZ]);
+
+	regmap_update_bits(hub->regmap, reg, mask, 0);
+}
+
+static void i3c_hub_gpio_irq_unmask(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+	u8 reg, hwirq = 0, mask = 0;
+
+	hwirq = irqd_to_hwirq(d);
+
+	reg = hwirq % GPIO_BANK_SZ ? I3C_HUB_TP_SDA_IN_DETECT_IBI_EN :
+				     I3C_HUB_TP_SCL_IN_DETECT_IBI_EN;
+	mask = BIT(gpio->tp[hwirq / GPIO_BANK_SZ]);
+
+	regmap_update_bits(hub->regmap, reg, mask, mask);
+}
+
+static int i3c_hub_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+	u8 hwirq = 0, mask = 0, val, tp, reg;
+	int ret;
+
+	if (!(flow_type & IRQ_TYPE_EDGE_BOTH)) {
+		dev_err(&hub->i3cdev->dev, "irq %d: unsupported type %d\n",
+			d->irq, flow_type);
+		return -EINVAL;
+	}
+
+	hwirq = irqd_to_hwirq(d);
+	tp = gpio->tp[hwirq / GPIO_BANK_SZ];
+
+	if (tp == 0 || tp == 1 || tp == 4 || tp == 5) {
+		if (hwirq % GPIO_BANK_SZ) {
+			mask = SDA0145_IO_IN_DET_CFG_MASK;
+			val = SDA0145_IO_IN_DET_CFG(flow_type);
+			reg = I3C_HUB_TP_SDA_IN_DETECT_FLG;
+		} else {
+			mask = SCL0145_IO_IN_DET_CFG_MASK;
+			val = SCL0145_IO_IN_DET_CFG(flow_type);
+			reg = I3C_HUB_TP_SCL_IN_DETECT_FLG;
+		}
+	} else {
+		if (hwirq % GPIO_BANK_SZ) {
+			mask = SDA2367_IO_IN_DET_CFG_MASK;
+			val = SDA2367_IO_IN_DET_CFG(flow_type);
+			reg = I3C_HUB_TP_SDA_IN_DETECT_FLG;
+		} else {
+			mask = SCL2367_IO_IN_DET_CFG_MASK;
+			val = SCL2367_IO_IN_DET_CFG(flow_type);
+			reg = I3C_HUB_TP_SCL_IN_DETECT_FLG;
+		}
+	}
+
+	ret = regmap_write(hub->regmap, reg, BIT(tp));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(hub->regmap, I3C_HUB_TP_IN_DETECT_MODE_CONF,
+				 mask, val);
+	return ret;
+}
+
+static void i3c_hub_gpio_irq_bus_lock(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+
+	mutex_lock(&gpio->irq_mutex);
+}
+
+static void i3c_hub_gpio_irq_bus_unlock(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct i3c_hub *hub = gpiochip_get_data(gc);
+	struct hub_gpio *gpio = &hub->gpio;
+
+	mutex_unlock(&gpio->irq_mutex);
+}
+
+static void i3c_hub_setup_gpio(struct i3c_hub *hub)
+{
+	struct hub_gpio *gpio = &hub->gpio;
+	struct gpio_chip *gc = &gpio->chip;
+	struct gpio_irq_chip *girq;
+
+	gc->direction_input = i3c_hub_gpio_direction_input;
+	gc->direction_output = i3c_hub_gpio_direction_output;
+	gc->get = i3c_hub_gpio_get_value;
+	gc->set = i3c_hub_gpio_set_value;
+	gc->get_direction = i3c_hub_gpio_get_direction;
+	gc->can_sleep = true;
+
+	gc->base = -1;
+	gc->ngpio = gpio->nums;
+	gc->label = dev_name(&hub->i3cdev->dev);
+	gc->parent = &hub->i3cdev->dev;
+	gc->owner = THIS_MODULE;
+
+	/* irq_chip support */
+	mutex_init(&gpio->irq_mutex);
+
+	gpio->irq_chip.name = dev_name(&hub->i3cdev->dev);
+	gpio->irq_chip.irq_mask = i3c_hub_gpio_irq_mask;
+	gpio->irq_chip.irq_unmask = i3c_hub_gpio_irq_unmask;
+	gpio->irq_chip.irq_set_type = i3c_hub_gpio_irq_set_type;
+	gpio->irq_chip.irq_bus_lock = i3c_hub_gpio_irq_bus_lock;
+	gpio->irq_chip.irq_bus_sync_unlock = i3c_hub_gpio_irq_bus_unlock;
+
+	girq = &gpio->chip.irq;
+
+	/* This will let us handle the parent IRQ in the driver */
+	girq->parent_handler = NULL;
+	girq->num_parents = 0;
+	girq->parents = NULL;
+	girq->default_type = IRQ_TYPE_NONE;
+	girq->handler = handle_simple_irq;
+	girq->threaded = true;
+	girq->first = 0;
+
+	girq->chip = &gpio->irq_chip;
+}
+
+static void i3c_hub_io_ibi_handler(struct i3c_hub *hub,
+				   const struct i3c_ibi_payload *payload)
+{
+	struct hub_gpio *gpio = &hub->gpio;
+	struct gpio_chip *gc = &gpio->chip;
+	u8 level, hwirq, tmp;
+	u8 pending[GPIO_BANK_SZ];
+	u8 tp[GPIO_BANK_SZ];
+	int i, irq, ret, index;
+
+	ret = regmap_bulk_read(hub->regmap, I3C_HUB_TP_SCL_OUT_EN, tp,
+			       GPIO_BANK_SZ);
+	if (ret) {
+		dev_err(&hub->i3cdev->dev, "Failed to read OUT_EN: %d\n", ret);
+		return;
+	}
+
+	ret = regmap_bulk_read(hub->regmap, I3C_HUB_TP_SCL_IN_DETECT_FLG,
+			       pending, GPIO_BANK_SZ);
+	if (ret) {
+		dev_err(&hub->i3cdev->dev, "Failed to read DETECT_FLG: %d\n",
+			ret);
+		return;
+	}
+
+	for (i = 0; i < GPIO_BANK_SZ; i++) {
+		tmp = ~tp[i] & pending[i];
+
+		while (tmp) {
+			level = __ffs(tmp);
+			tmp &= ~(1 << level);
+
+			/* Check if this port is in GPIO mode */
+			index = gpio->port_to_index[level];
+			if (index < 0) {
+				/* Non-GPIO mode port, skip without clearing.
+				 * This can happen because IN_DETECT IBI enable is
+				 * configured in groups (e.g., TP0145/TP2367), not
+				 * per individual port. Simply skip - the flag is
+				 * harmless and will be overwritten by next detection.
+				 */
+				dev_dbg(&hub->i3cdev->dev,
+					"IBI detect flag on non-GPIO port %d, skipping\n",
+					level);
+				continue;
+			}
+
+			hwirq = index * 2 + i;
+			irq = irq_find_mapping(gc->irq.domain, hwirq);
+
+			/* Clear the flag after processing */
+			regmap_write(hub->regmap,
+				     I3C_HUB_TP_SCL_IN_DETECT_FLG + i,
+				     BIT(level));
+
+			if (unlikely(irq <= 0)) {
+				dev_warn_ratelimited(gc->parent,
+						     "unmapped interrupt %d\n",
+						     hwirq);
+			} else {
+				handle_nested_irq(irq);
+			}
+		}
+	}
+}
+
+static void i3c_hub_ibi_handler(struct i3c_device *dev,
+				const struct i3c_ibi_payload *payload)
+{
+	struct i3c_hub *priv = i3cdev_get_drvdata(dev);
+	int ret, val = 0;
+	u8 status = 0;
+
+	if (!payload->len) {
+		dev_dbg(&dev->dev,
+			"Zero-length IBI payload, reading status register\n");
+		ret = regmap_read(priv->regmap, I3C_HUB_DEV_AND_IBI_STS, &val);
+		if (ret) {
+			dev_warn_ratelimited(&dev->dev,
+					     "Failed to read IBI status: %d\n",
+					     ret);
+			return;
+		}
+		status = (u8)val;
+	} else {
+		if (!payload->data) {
+			dev_warn_ratelimited(
+				&dev->dev,
+				"IBI payload data is NULL with len=%d\n",
+				payload->len);
+			return;
+		}
+		status = ((const u8 *)payload->data)[0];
+	}
+
+	if (status & TP_IO_FLAG_STATUS)
+		i3c_hub_io_ibi_handler(priv, payload);
+
+	if (status & SMBUS_AGENT_EVENT_FLAG_STATUS) {
+		ret = i3c_hub_smbus_ibi_handler(priv, payload);
+		if (ret) {
+			dev_warn_ratelimited(&dev->dev,
+					     "Failed to handle SMBus IBI: %d\n",
+					     ret);
+			return;
+		}
+	}
+}
+
+static inline void i3c_hub_regmap_lock(void *ctx)
+{
+	mutex_lock(&i3c_hub_regmap_mutex);
+}
+
+static inline void i3c_hub_regmap_unlock(void *ctx)
+{
+	mutex_unlock(&i3c_hub_regmap_mutex);
+}
+
+static int i3c_hub_probe(struct i3c_device *i3cdev)
+{
+	const struct regmap_config i3c_hub_regmap_config = {
+		.reg_bits = 8,
+		.val_bits = 8,
+		.lock = i3c_hub_regmap_lock,
+		.unlock = i3c_hub_regmap_unlock,
+		.lock_arg = NULL,
+	};
+	struct device *dev = &i3cdev->dev;
+	struct device_node *node = NULL;
+	struct regmap *regmap;
+	struct i3c_hub *priv;
+	char hub_id[32];
+	int ret;
+	int i;
+	struct i3c_ibi_setup ibireq = {};
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->i3cdev = i3cdev;
+	priv->driving_master = i3c_dev_get_master(i3cdev->desc);
+	mutex_init(&priv->page_mutex);
+	i3cdev_set_drvdata(i3cdev, priv);
+	INIT_DELAYED_WORK(&priv->delayed_work, i3c_hub_delayed_work);
+	i3c_hub_of_default_configuration(dev);
+
+	regmap = devm_regmap_init_i3c(i3cdev, &i3c_hub_regmap_config);
+	if (IS_ERR(regmap)) {
+		ret = PTR_ERR(regmap);
+		dev_err(dev, "Failed to register I3C HUB regmap\n");
+		return ret;
+	}
+	priv->regmap = regmap;
+
+	priv->dev_info = i3c_hub_lookup_dev_info(priv);
+	if (IS_ERR(priv->dev_info)) {
+		ret = PTR_ERR(priv->dev_info);
+		dev_err(dev, "Failed to lookup HUB dev info\n");
+		return ret;
+	}
+
+	sprintf(hub_id, "i3c-hub-%d-%llx",
+		i3c_dev_get_master(i3cdev->desc)->bus.id,
+		i3cdev->desc->info.pid);
+	ret = i3c_hub_debugfs_init(priv, hub_id);
+	if (ret)
+		dev_dbg(dev, "Failed to initialized DebugFS.\n");
+
+	ret = i3c_hub_read_id(dev);
+	if (ret)
+		goto error;
+
+	priv->hub_dt_sel_id = -1;
+	priv->hub_dt_cp1_id = -1;
+	if (priv->hub_pin_cp1_id >= 0 && priv->hub_pin_sel_id >= 0)
+		/* Find hub node in DT matching HW ID or just first without ID provided in DT */
+		node = i3c_hub_get_dt_hub_node(dev->parent->of_node, priv);
+
+	if (!node) {
+		dev_info(dev,
+			 "No DT entry - running with hardware defaults.\n");
+	} else {
+		of_node_get(node);
+		i3c_hub_of_get_conf_static(dev, node);
+		i3c_hub_of_get_conf_runtime(dev, node);
+		of_node_put(node);
+	}
+
+	/* Unlock access to protected registers */
+	ret = regmap_write(priv->regmap, I3C_HUB_PROTECTION_CODE,
+			   REGISTERS_UNLOCK_CODE);
+	if (ret) {
+		dev_err(dev, "Failed to unlock HUB's protected registers\n");
+		goto error;
+	}
+
+	/* Register logic for native smbus ports */
+	for (i = 0; i < priv->dev_info->n_ports; i++) {
+		priv->smbus_port_adapter[i].used = 0;
+		if (priv->settings.tp[i].mode == I3C_HUB_DT_TP_MODE_SMBUS) {
+			ret = i3c_hub_smbus_tp_algo(priv, i);
+			if (ret)
+				dev_warn(
+					dev,
+					"Failed to setup SMBus adapter, port: %d\n",
+					i);
+		}
+	}
+
+	ret = i3c_hub_configure_hw(dev);
+	if (ret) {
+		dev_err(dev, "Failed to configure the HUB\n");
+		goto error;
+	}
+
+	/* Lock access to protected registers */
+	ret = regmap_write(priv->regmap, I3C_HUB_PROTECTION_CODE,
+			   REGISTERS_LOCK_CODE);
+	if (ret) {
+		dev_err(dev, "Failed to lock HUB's protected registers\n");
+		goto error;
+	}
+
+	/* IBI */
+	ibireq.handler = i3c_hub_ibi_handler;
+	ibireq.max_payload_len = IBI_MAX_PAYLOAD_LEN;
+	ibireq.num_slots = IBI_SLOT_NUMS;
+
+	ret = i3c_device_request_ibi(i3cdev, &ibireq);
+	if (ret) {
+		dev_err(dev, "Failed to requeset ibi!\n");
+		goto error;
+	}
+
+	ret = i3c_device_enable_ibi(i3cdev);
+	if (ret) {
+		dev_err(dev, "Failed to enable ibi!\n");
+		goto err_free_ibi;
+	}
+
+	/* TBD: Apply special/security lock here using DEV_CMD register */
+
+	if (priv->gpio.nums > 0) {
+		i3c_hub_setup_gpio(priv);
+
+		ret = devm_gpiochip_add_data(dev, &priv->gpio.chip, priv);
+		if (ret) {
+			dev_err(dev, "gpiochip add data fail!\n");
+			goto err_dis_ibi;
+		}
+	}
+
+	schedule_delayed_work(&priv->delayed_work, msecs_to_jiffies(100));
+
+	return 0;
+
+err_dis_ibi:
+	i3c_device_disable_ibi(i3cdev);
+err_free_ibi:
+	i3c_device_free_ibi(i3cdev);
+error:
+	debugfs_remove_recursive(priv->debug_dir);
+	return ret;
+}
+
+static void i3c_hub_remove(struct i3c_device *i3cdev)
+{
+	struct i3c_hub *priv = i3cdev_get_drvdata(i3cdev);
+	int i;
+
+	i3c_device_disable_ibi(i3cdev);
+	i3c_device_free_ibi(i3cdev);
+
+	for (i = 0; i < priv->dev_info->n_ports; i++) {
+		if (priv->smbus_port_adapter[i].used) {
+			cancel_delayed_work_sync(
+				&priv->smbus_port_adapter[i]
+					 .delayed_work_polling);
+			i2c_del_adapter(&priv->smbus_port_adapter[i].i2c);
+		}
+
+		if (priv->logical_bus[i].registered)
+			i3c_master_unregister(&priv->logical_bus[i].controller);
+	}
+
+	cancel_delayed_work_sync(&priv->delayed_work);
+	debugfs_remove_recursive(priv->debug_dir);
+}
+
+static struct i3c_driver i3c_hub = {
+	.driver.name = "rts490xa-i3c-hub",
+	.id_table = i3c_hub_ids,
+	.probe = i3c_hub_probe,
+	.remove = i3c_hub_remove,
+};
+
+module_i3c_driver(i3c_hub);
+
+MODULE_DESCRIPTION("RTS490XA I3C HUB driver");
+MODULE_LICENSE("GPL");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB
  2026-04-30 12:13 [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB zain_zhou
  2026-04-30 12:13 ` [PATCH 2/2] staging: i3c: add Realtek RTS490x I3C HUB driver zain_zhou
@ 2026-04-30 13:41 ` Rob Herring (Arm)
  2026-05-01  9:37 ` Krzysztof Kozlowski
  2 siblings, 0 replies; 4+ messages in thread
From: Rob Herring (Arm) @ 2026-04-30 13:41 UTC (permalink / raw)
  To: zain_zhou
  Cc: linusw, gregkh, linux-i3c, brgl, conor+dt, linux-kernel,
	devicetree, linux-staging, alexandre.belloni, linux-gpio,
	Frank.Li, krzk+dt


On Thu, 30 Apr 2026 20:13:53 +0800, zain_zhou@realsil.com.cn wrote:
> From: zain_zhou <zain_zhou@realsil.com.cn>
> 
> Add DT binding schema for Realtek RTS490x series I3C HUB devices.
> 
> The binding describes configuration properties for:
>   - LDO enable/disable and voltage level per port group
>   - Pull-up resistance per port group
>   - IO driver strength per port
>   - Per target-port mode (I3C/SMBus/GPIO/disabled), pull-up,
>     IO mode, SMBus clock frequency and polling interval
>   - Hub network always-I3C mode
>   - Hardware identification via CSEL pin (id) and CP1 pins (id-cp1)
> 
> Signed-off-by: zain_zhou <zain_zhou@realsil.com.cn>
> ---
>  .../bindings/i3c/realtek,rts490x-i3c-hub.yaml | 410 ++++++++++++++++++
>  MAINTAINERS                                   |   6 +
>  2 files changed, 416 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml: ignoring, error in schema: patternProperties: @[0-9]$: properties: polling-interval-ms: type
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml: patternProperties:@[0-9]$:properties:polling-interval-ms: 'anyOf' conditional failed, one must be fixed:
	'maxItems' is a required property
		hint: Only "maxItems" is required for a single entry if there are no constraints defined for the values.
	'type' is not one of ['maxItems', 'description', 'deprecated']
		hint: Only "maxItems" is required for a single entry if there are no constraints defined for the values.
	Additional properties are not allowed ('type' was unexpected)
		hint: Arrays must be described with a combination of minItems/maxItems/items
	'type' is not one of ['description', 'deprecated', 'const', 'enum', 'minimum', 'maximum', 'multipleOf', 'default', '$ref', 'oneOf']
	hint: cell array properties must define how many entries and what the entries are when there is more than one entry.
	from schema $id: http://devicetree.org/meta-schemas/cell.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml: patternProperties:@[0-9]$:properties:polling-interval-ms:type: 'uint32' is not one of ['boolean', 'object']
	from schema $id: http://devicetree.org/meta-schemas/core.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml: patternProperties:@[0-9]$:properties:clock-frequency:type: 'uint32' is not one of ['boolean', 'object']
	from schema $id: http://devicetree.org/meta-schemas/core.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml: patternProperties:@[0-9]$:properties:clock-frequency: 'type' is not one of ['description', 'deprecated', 'const', 'enum', 'minimum', 'maximum', 'multipleOf', 'default', '$ref', 'oneOf']
	from schema $id: http://devicetree.org/meta-schemas/cell.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml: patternProperties:@[0-9]$:properties:clock-frequency:type: 'anyOf' conditional failed, one must be fixed:
	'uint32' is not one of ['array', 'boolean', 'integer', 'null', 'number', 'object', 'string']
	'uint32' is not of type 'array'
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml: patternProperties:@[0-9]$:properties:polling-interval-ms:type: 'anyOf' conditional failed, one must be fixed:
	'uint32' is not one of ['array', 'boolean', 'integer', 'null', 'number', 'object', 'string']
	'uint32' is not of type 'array'
Lexical error: Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.example.dts:50.34-55 Unexpected 'I2C_OWN_SLAVE_ADDRESS'
Lexical error: Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.example.dts:114.34-55 Unexpected 'I2C_OWN_SLAVE_ADDRESS'
FATAL ERROR: Syntax error parsing input tree
make[2]: *** [scripts/Makefile.dtbs:140: Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1637: dt_binding_check] Error 2
make: *** [Makefile:248: __sub-make] Error 2

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/20260430121354.6253-1-zain_zhou@realsil.com.cn

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB
  2026-04-30 12:13 [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB zain_zhou
  2026-04-30 12:13 ` [PATCH 2/2] staging: i3c: add Realtek RTS490x I3C HUB driver zain_zhou
  2026-04-30 13:41 ` [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB Rob Herring (Arm)
@ 2026-05-01  9:37 ` Krzysztof Kozlowski
  2 siblings, 0 replies; 4+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-01  9:37 UTC (permalink / raw)
  To: zain_zhou, linux-staging, linux-i3c, devicetree
  Cc: gregkh, alexandre.belloni, Frank.Li, robh, krzk+dt, conor+dt,
	linusw, brgl, linux-gpio, linux-kernel

On 30/04/2026 14:13, zain_zhou@realsil.com.cn wrote:
> From: zain_zhou <zain_zhou@realsil.com.cn>
> 
> Add DT binding schema for Realtek RTS490x series I3C HUB devices.

A nit, subject: drop second/last, redundant "bindings". The
"dt-bindings" prefix is already stating that these are bindings.
See also:
https://elixir.bootlin.com/linux/v6.17-rc3/source/Documentation/devicetree/bindings/submitting-patches.rst#L18

> 
> The binding describes configuration properties for:
>   - LDO enable/disable and voltage level per port group
>   - Pull-up resistance per port group
>   - IO driver strength per port
>   - Per target-port mode (I3C/SMBus/GPIO/disabled), pull-up,
>     IO mode, SMBus clock frequency and polling interval
>   - Hub network always-I3C mode
>   - Hardware identification via CSEL pin (id) and CP1 pins (id-cp1)
> 
> Signed-off-by: zain_zhou <zain_zhou@realsil.com.cn>

Don't use login name as full name, but rather latin transliteration.
Name in your native language would also be fine.

But login name is not fine.


> ---
>  .../bindings/i3c/realtek,rts490x-i3c-hub.yaml | 410 ++++++++++++++++++
>  MAINTAINERS                                   |   6 +
>  2 files changed, 416 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml
> 
> diff --git a/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml b/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml
> new file mode 100644
> index 000000000000..30295eefee89
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml
> @@ -0,0 +1,410 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/i3c/realtek,rts490x-i3c-hub.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: I3C HUB

Proper name here of your device


> +
> +maintainers:
> +  - zain_zhou <zain_zhou@realsil.com.cn>
> +
> +description: |
> +  I3C HUB is smart device which provides multiple functionality:


Are you describing particular device or I3C HUB?

> +  * enabling voltage compatibility across I3C Controller and Target devices,
> +  * bus capacitance isolation
> +  * address conflict isolation
> +  * I3C port expansion
> +  * two controllers in a single I3C bus
> +  * I3C and SMBus device compatibility
> +  * GPIO expansion
> +
> +  Having such big number of features, there is a need to have some DT knobs to tell the I3C HUB
> +  driver which features shall be enabled and how they shall be configured. I3C HUB driver read,

Please wrap code according to the preferred limit expressed in Kernel
coding style (checkpatch is not a coding style description, but only a
tool).  However don't wrap blindly (see Kernel coding style).

> +  validate DT knobs and set corresponding registers with the right way to satisfy user requests from
> +  DT.

Irrelevant. This is binding, so describe hardware. Not your drivers.

DT is irrelevant as well.

> +
> +  All the DT properties for I3C HUB are located under dedicated (for I3C HUB) DT entry. I3C HUB DT
> +  entry structure is aligned with regular I3C device DT entry described in i3c.yaml.
> +

Irrelevant. Describe your hardware.

> +allOf:
> +  - $ref: i3c.yaml#
> +
> +properties:
> +  $nodename:
> +    pattern: "^hub@0,0$"
> +
> +  cp0-ldo-en:
> +    enum:
> +      - disabled
> +      - enabled

Sorry, but what? Your binding does not look like accepted bindings AT
ALL. Please read writing bindings carefully or go via DTS101 slides.



> +    description: |
> +      I3C HUB Controller Port 0 LDO disabling/enabling setting. If enabled, voltage produced by
> +      on-die LDO will be available externally on dedicated pin. This option could be used to supply
> +      external pull-up resistors or for any other purpose which does not cross LDO capabilities.
> +
> +      This property is optional. If not provided, LDO will be disabled.
> +
> +  cp1-ldo-en:
> +    enum:
> +      - disabled
> +      - enabled
> +    description: |
> +      I3C HUB Controller Port 1 LDO disabling/enabling setting. If enabled, voltage produced by
> +      on-die LDO will be available externally on dedicated pin. This option could be used to supply
> +      external pull-up resistors or for any other purpose which does not cross LDO capabilities.
> +
> +      This property is optional. If not provided, LDO will be disabled.
> +
> +  tp0145-ldo-en:
> +    enum:
> +      - disabled
> +      - enabled
> +    description: |
> +      I3C HUB Target Ports 0/1/4/5 LDO disabling/enabling setting. If enabled, voltage produced by
> +      on-die LDO will be available externally on dedicated pin. This option could be used to supply
> +      external pull-up resistors or for any other purpose which does not cross LDO capabilities.
> +
> +      This property is optional. If not provided, LDO will be disabled.
> +
> +  tp2367-ldo-en:
> +    enum:
> +      - disabled
> +      - enabled
> +    description: |
> +      I3C HUB Target Ports 2/3/6/7 LDO disabling/enabling setting. If enabled, voltage produced by
> +      on-die LDO will be available externally on dedicated pin. This option could be used to supply
> +      external pull-up resistors or for any other purpose which does not cross LDO capabilities.
> +
> +      This property is optional. If not provided, LDO will be disabled.
> +
> +  cp0-ldo-volt:

Nope. There are no such properties. Missing vendor prefix, missing
proper unit suffix. Look at other bindings.

> +    enum:
> +      - 1.0V
> +      - 1.1V
> +      - 1.2V
> +      - 1.8V
> +    description: |
> +      I3C HUB Controller Port 0 LDO setting to control the Controller Port 1 voltage level. This
> +      property is optional.
> +

...


> +
> +additionalProperties: true

No, you cannot have it. Please read writing bindings, writing schema or
open ANY other binding.

> +
> +examples:
> +  - |
> +    i3c-master@d040000 {
> +      #address-cells = <1>;
> +      #size-cells = <0>;
> +
> +      hub@0,0 {
> +        cp0-ldo-en = "disabled";
> +        cp1-ldo-en = "enabled";
> +        cp0-ldo-volt = "1.0V";
> +        cp1-ldo-volt = "1.1V";
> +        tp0145-ldo-en = "enabled";
> +        tp2367-ldo-en = "disabled";
> +        tp0145-ldo-volt = "1.2V";
> +        tp2367-ldo-volt = "1.8V";
> +        tp0145-pullup = "2k";
> +        tp2367-pullup = "500R";
> +        tp0145-io-strength = "50Ohms";
> +        tp2367-io-strength = "30Ohms";
> +        cp0-io-strength = "20Ohms";
> +        cp1-io-strength = "40Ohms";
> +
> +        target-port@0 {
> +          mode = "i3c";
> +          pullup = "enabled";
> +          always_enable;
> +        };
> +        target-port@1 {
> +          mode = "smbus";
> +          pullup = "enabled";
> +          clock-frequency = <1000000>;
> +          polling-interval-ms = <10>;
> +          backend@10{
> +              compatible = "i2c-slave-mqueue";
> +              reg = <(0x10 | I2C_OWN_SLAVE_ADDRESS)>;
> +          };
> +        };
> +        target-port@2 {
> +          mode = "gpio";
> +          pullup = "disabled";
> +        };
> +        target-port@3 {
> +          mode = "disabled";
> +          pullup = "disabled";
> +        };
> +      };
> +    };
> +
> +  - |
> +    i3c-master@d040000 {
> +      #address-cells = <1>;
> +      #size-cells = <0>;
> +
> +      hub@70,3C000000100 {

No way it passes validation. You just said it CANNOT be anything else
than hub@0,0

> +        reg = <0x70 0x3C0 0x00000100>;

Lower case hex. Anyway, one example is enough.

> +        assigned-address = <0x70>;
> +        dcr = <0xC2>;
> +
> +        cp0-ldo-en = "disabled";
> +        cp1-ldo-en = "enabled";
> +        cp0-ldo-volt = "1.0V";
> +        cp1-ldo-volt = "1.1V";
> +        tp0145-ldo-en = "enabled";
> +        tp2367-ldo-en = "disabled";
> +        tp0145-ldo-volt = "1.2V";
> +        tp2367-ldo-volt = "1.8V";
> +        tp0145-pullup = "2k";
> +        tp2367-pullup = "500R";
> +        tp0145-io-strength = "50Ohms";
> +        tp2367-io-strength = "30Ohms";
> +        cp0-io-strength = "20Ohms";
> +        cp1-io-strength = "40Ohms";
> +
> +        target-port@0 {
> +          mode = "i3c";
> +          pullup = "enabled";
> +          always-enable;
> +        };
> +        target-port@1 {
> +          mode = "smbus";
> +          pullup = "enabled";
> +          backend@12{
> +              compatible = "i2c-slave-mqueue";
> +              reg = <(0x12 | I2C_OWN_SLAVE_ADDRESS)>;
> +          };
> +        };
> +        target-port@2 {
> +          mode = "gpio";
> +          pullup = "disabled";
> +        };
> +        target-port@3 {
> +          mode = "disabled";
> +          pullup = "disabled";
> +        };
> +      };
> +    };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2fb1c75afd16..71ee5071ac0f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -12214,6 +12214,12 @@ S:	Supported
>  F:	Documentation/devicetree/bindings/i3c/renesas,i3c.yaml
>  F:	drivers/i3c/master/renesas-i3c.c
>  
> +I3C HUB DRIVER FOR REALTEK RTS490X
> +M:	zain_zhou <zain_zhou@realsil.com.cn>
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/i3c/realtek,rts490x-i3c-hub.yaml
> +F:	drivers/staging/rts490x/

There is no such directory.



Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-05-01  9:37 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-30 12:13 [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB zain_zhou
2026-04-30 12:13 ` [PATCH 2/2] staging: i3c: add Realtek RTS490x I3C HUB driver zain_zhou
2026-04-30 13:41 ` [PATCH 1/2] dt-bindings: i3c: add binding for Realtek RTS490x I3C HUB Rob Herring (Arm)
2026-05-01  9:37 ` Krzysztof Kozlowski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox