* [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, ®_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, ®_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, ®_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, ®_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, ®_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