blob: 292172de983a00363c267bdb6aadd2a0bfdf4ada [file] [log] [blame]
From cf5677b3539a60a836c6b3216e1c59717a1a9cfc Mon Sep 17 00:00:00 2001
From: Steven Niu <steven.niu.uj@renesas.com>
Date: Wed, 22 Jan 2025 01:15:28 +0800
Subject: [PATCH 3/9] i3c: i3c-hub: Add I3C Hub driver
The I3C Hub driver supports:
- I3C transparent bridge mode
- SMBus Master/Slave Agent mode with IBI support
Signed-off-by: Steven Niu <steven.niu.uj@renesas.com>
---
drivers/i3c/Kconfig | 35 +
drivers/i3c/Makefile | 1 +
drivers/i3c/i3c-hub.c | 2312 ++++++++++++++++++++++++++++++++++++
include/linux/i3c/device.h | 1 +
4 files changed, 2349 insertions(+)
create mode 100644 drivers/i3c/i3c-hub.c
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index 2fd8deaf74f2..3175bac6c97d 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -44,4 +44,39 @@ config I3C_MUX_IMX3102
source "drivers/i3c/mctp/Kconfig"
source "drivers/i3c/master/Kconfig"
+
+config I3C_HUB
+ tristate "I3C HUB support"
+ depends on I3C
+ select REGMAP_I3C
+ help
+ This enables support for I3C HUB. Say Y here to use I3C HUB driver to
+ configure I3C HUB device.
+
+ I3C HUB drivers will be loaded automatically when I3C device with BCR
+ equals to 0xC2 (HUB device) is detected on the bus.
+
+config I3C_HUB_POLLING_MODE
+ bool "Enable polling mode for I3C Hub SMBus Agent"
+ default n
+ depends on I3C_HUB
+ help
+ This makes I3C HUB to get the SMBus Agent events with polling mode"
+
+config I3C_HUB_TP_IDENDIFY
+ bool "I3C Hub Identification by Target Port"
+ default n
+ depends on I3C_HUB
+ help
+ Iendify the I3C Hub instance by a target port
+
+config I3C_HUB_IDENTIFY_TP_NR
+ int "Target Port used for Hub Identification"
+ default 0
+ range 0 7
+ depends on I3C_HUB_TP_IDENDIFY
+ help
+ Select which target port is used for the identification.
+
+
endif # I3C
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index 4a8b82d942f9..6bd5c9d58da9 100644
--- a/drivers/i3c/Makefile
+++ b/drivers/i3c/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_I3CDEV) += i3cdev.o
obj-$(CONFIG_I3C) += master/
obj-$(CONFIG_I3C) += mctp/
obj-$(CONFIG_I3C_MUX_IMX3102) += i3c-mux-imx3102.o
+obj-$(CONFIG_I3C_HUB) += i3c-hub.o
diff --git a/drivers/i3c/i3c-hub.c b/drivers/i3c/i3c-hub.c
new file mode 100644
index 000000000000..607e69a00561
--- /dev/null
+++ b/drivers/i3c/i3c-hub.c
@@ -0,0 +1,2312 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2021 - 2023 Intel Corporation.*/
+
+#include "asm-generic/int-ll64.h"
+#include "linux/dev_printk.h"
+#include "linux/device.h"
+#include "linux/i2c.h"
+#include "linux/mutex.h"
+#include "linux/of.h"
+#include "linux/of_address.h"
+#include "linux/stddef.h"
+#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/i3c/device.h>
+#include <linux/i3c/master.h>
+
+#include "internals.h"
+
+#define I3C_HUB_TP_MAX_COUNT 0x08
+#define I3C_HUB_LOGICAL_BUS_MAX_COUNT 0x08
+
+/* 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 HUB_REG_DEV_INFO_0 0x00
+#define HUB_REG_DEV_INFO_1 0x01
+#define HUB_REG_PID_5 0x02
+#define HUB_REG_PID_4 0x03
+#define HUB_REG_PID_3 0x04
+#define HUB_REG_PID_2 0x05
+#define HUB_REG_PID_1 0x06
+#define HUB_REG_PID_0 0x07
+#define HUB_REG_BCR 0x08
+#define HUB_REG_DCR 0x09
+#define HUB_REG_DEV_CAPAB 0x0A
+#define HUB_REG_DEV_REVB 0x0B
+
+/* Device Configuration Registers */
+#define HUB_REG_PROTECTION 0x10
+#define REGISTERS_LOCK_CODE 0x00
+#define REGISTERS_UNLOCK_CODE 0x69
+#define CP1_REGISTERS_UNLOCK_CODE 0x6A
+
+#define HUB_REG_CP_CONF 0x11
+#define HUB_REG_TP_ENABLE 0x12
+#define TP_ENABLE(n) BIT(n)
+
+#define HUB_REG_DEV_CONF 0x13
+#define HUB_REG_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 HUB_REG_NET_OPER_MODE_CONF 0x15
+#define HUB_REG_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 HUB_REG_TP_IO_MODE_CONF 0x17
+#define HUB_REG_TP_SMBUS_AGNT_EN 0x18
+#define TP_SMBUS_MODE_EN(n) BIT(n)
+
+#define HUB_REG_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 HUB_REG_CP_IBI_CONF 0x1A
+#define HUB_REG_TP_IBI_CONF 0x1B
+#define HUB_REG_IBI_MDB_CUSTOM 0x1C
+#define HUB_REG_JEDEC_CONTEXT_ID 0x1D
+#define HUB_REG_TP_GPIO_MODE_EN 0x1E
+#define TP_GPIO_MODE_EN(n) BIT(n)
+
+/* Device Status and IBI Registers */
+#define HUB_REG_DEV_AND_PORT_IBI_STS 0x20
+#define HUB_REG_TP_SMBUS_AGNT_IBI_STS 0x21
+
+/* Controller Port Control/Status Registers */
+#define HUB_REG_CP_MUX_SET 0x38
+#define CONTROLLER_PORT_MUX_REQ BIT(0)
+#define HUB_REG_CP_MUX_STS 0x39
+#define CONTROLLER_PORT_MUX_CONNECTION_STATUS BIT(0)
+
+/* Target Ports Control Registers */
+#define HUB_REG_TP_SMBUS_AGNT_TRANS_START 0x50
+#define HUB_REG_TP_NET_CON_CONF 0x51
+#define TP_NET_CON(n) BIT(n)
+
+#define HUB_REG_TP_PULLUP_EN 0x53
+#define TP_PULLUP_EN(n) BIT(n)
+
+#define HUB_REG_TP_SCL_OUT_EN 0x54
+#define HUB_REG_TP_SDA_OUT_EN 0x55
+#define HUB_REG_TP_SCL_OUT_LEVEL 0x56
+#define HUB_REG_TP_SDA_OUT_LEVEL 0x57
+#define HUB_REG_TP_IN_DETECT_MODE_CONF 0x58
+#define HUB_REG_TP_SCL_IN_DETECT_IBI_EN 0x59
+#define HUB_REG_TP_SDA_IN_DETECT_IBI_EN 0x5A
+
+/* Target Ports Status Registers */
+#define HUB_REG_TP_SCL_IN_LEVEL_STS 0x60
+#define HUB_REG_TP_SDA_IN_LEVEL_STS 0x61
+#define HUB_REG_TP_SCL_IN_DETECT_FLG 0x62
+#define HUB_REG_TP_SDA_IN_DETECT_FLG 0x63
+
+/* SMBus Agent Configuration and Status Registers */
+#define HUB_REG_TP_SMBUS_AGNT_STS(p) (0x64 + (p))
+#define HUB_REG_TP0_SMBUS_AGNT_STS 0x64
+#define HUB_REG_TP1_SMBUS_AGNT_STS 0x65
+#define HUB_REG_TP2_SMBUS_AGNT_STS 0x66
+#define HUB_REG_TP3_SMBUS_AGNT_STS 0x67
+#define HUB_REG_TP4_SMBUS_AGNT_STS 0x68
+#define HUB_REG_TP5_SMBUS_AGNT_STS 0x69
+#define HUB_REG_TP6_SMBUS_AGNT_STS 0x6A
+#define HUB_REG_TP7_SMBUS_AGNT_STS 0x6B
+
+#define HUB_REG_AGENT_CNTRL_STATUS_FINISH 1
+#define HUB_REG_AGENT_CNTRL_STATUS_RX_BUF0 2
+#define HUB_REG_AGENT_CNTRL_STATUS_RX_BUF1 4
+#define HUB_REG_AGENT_CNTRL_STATUS_RX_BUF_OVF 8
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_SHIFT 4
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_OK 0
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_ADDR_NAK 1
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_DATA_NAK 2
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_WTR_NAK 3
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_SYNC_RCV 4
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_SYNC_RCVCLR 5
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_FAULT 6
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_ARB_LOSS 7
+#define HUB_REG_AGENT_CNTRL_STATUS_TXN_SCL_TO 8
+
+#define HUB_REG_ONCHIP_TD_AND_SMBUS_AGNT_CONF 0x6C
+
+/* Transaction status checking mask */
+#define I3C_HUB_XFER_SUCCESS 0x01
+#define I3C_HUB_TP_BUFFER_STATUS_MASK 0x0F
+#define I3C_HUB_TP_TRANSACTION_CODE_MASK 0xF0
+#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 HUB_REG_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 HUB_REG_BUS_RESET_SCL_TIMEOUT 0x7A
+#define HUB_REG_ONCHIP_TD_PROTO_ERR_FLG 0x7B
+#define HUB_REG_DEV_CMD 0x7C
+#define HUB_REG_ONCHIP_TD_STS 0x7D
+#define HUB_REG_ONCHIP_TD_ADDR_CONF 0x7E
+#define HUB_REG_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
+
+/* 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 polling */
+#ifdef CONFIG_I3C_HUB_POLLING_MODE
+#define I3C_HUB_POLLING_ROLL_PERIOD_MS 1
+#endif
+
+/* SMBus transaction types fields */
+#define I3C_HUB_SMBUS_400KHZ BIT(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 timeout time period in nanoseconds */
+#define I3C_HUB_SMBUS_400KHZ_TIMEOUT \
+ (10e9 * 8 * I3C_HUB_CONTROLLER_BUFFER_SIZE / 4e5)
+
+/* ID Extraction */
+#define I3C_HUB_ID_CP_SDA_SCL 0x00
+#define I3C_HUB_ID_CP_SEL 0x01
+
+/* page numbers, per port */
+#define HUB_PAGE_AGENT_TX(p) (16 + (4 * (p)) + 0)
+#define HUB_PAGE_AGENT_ADDRS(p) (16 + (4 * (p)) + 1)
+#define HUB_PAGE_AGENT_RX0(p) (16 + (4 * (p)) + 2)
+#define HUB_PAGE_AGENT_RX1(p) (16 + (4 * (p)) + 3)
+
+static const bool ibi_paranoia = true;
+static DEFINE_MUTEX(hub_lock);
+static struct device_node *last_node;
+
+/* mapping of part_id register to device-specific data */
+static const struct i3c_hub_devdata {
+ __u16 part_id;
+ unsigned int n_ports;
+} i3c_hub_devs[] = {
+ { 0x4712, 4 },
+ { 0x4812, 4 },
+ { 0x8712, 8 },
+ { 0x8812, 8 },
+};
+
+#define VIO_EXTERNAL 0x00u
+#define VIO_INTERNAL 0x01u
+
+struct i3c_hub_cp_port {
+ u32 id;
+ u32 io_microvolt;
+ u32 vio_source;
+ u32 io_strength_ohms;
+};
+
+struct i3c_hub_tp_group {
+ u32 id;
+ u32 io_microvolt;
+ u32 vio_source;
+ u32 io_strength_ohms;
+ u32 io_internal_pullups_ohms;
+};
+
+struct i3c_hub_target_port {
+ enum {
+ PORT_MODE_DISABLED = 0,
+ PORT_MODE_I3C,
+ PORT_MODE_AGENT,
+ } mode;
+ struct device_node *of_node;
+ u32 port_nr;
+ u32 port_mask;
+
+ bool pullups_disable;
+
+ struct i3c_hub_smbus_agent *agent;
+ struct i3c_hub_bridge *bridge;
+};
+
+struct i3c_hub {
+ struct i3c_device *i3cdev;
+ struct regmap *regmap;
+ const struct i3c_hub_devdata *devinfo;
+
+ struct device_node *of_node;
+
+ int hub_pin_sel_id;
+ int hub_pin_cp1_id;
+ int hub_pin_tpx_id;
+ int hub_dt_sel_id;
+ int hub_dt_cp1_id;
+ int hub_dt_tpx_id;
+
+ struct i3c_hub_cp_port cp_port;
+ struct i3c_hub_tp_group tp_groups[2];
+ struct i3c_hub_target_port ports[I3C_HUB_TP_MAX_COUNT];
+
+ unsigned int cur_page;
+
+ struct i3c_master_controller *driving_master;
+
+ /* Offset for reading HUB's register. */
+ u8 reg_addr;
+ struct dentry *debug_dir;
+
+ /* protects page access */
+ struct mutex lock;
+
+ struct delayed_work delayed_work;
+
+#ifdef CONFIG_I3C_HUB_POLLING_MODE
+ struct delayed_work smbus_agent_polling_work;
+ bool smbus_agent_polling_active;
+#endif
+};
+
+/* utils */
+#ifdef CONFIG_I3C_HUB_TP_IDENDIFY
+static inline int i3c_hub_port_enable(struct i3c_hub *hub, unsigned int port_nr)
+{
+ return regmap_set_bits(hub->regmap, HUB_REG_TP_ENABLE, 1u << port_nr);
+}
+#endif
+
+static inline int i3c_hub_port_disable(struct i3c_hub *hub, unsigned int port_nr)
+{
+ return regmap_clear_bits(hub->regmap, HUB_REG_TP_ENABLE, 1u << port_nr);
+}
+
+static inline int i3c_hub_unprotect_register(struct i3c_hub *hub)
+{
+ return regmap_write(hub->regmap, HUB_REG_PROTECTION, REGISTERS_UNLOCK_CODE);
+}
+
+static inline int i3c_hub_protect_register(struct i3c_hub *hub)
+{
+ return regmap_write(hub->regmap, HUB_REG_PROTECTION, REGISTERS_LOCK_CODE);
+}
+
+static int i3c_hub_disable_agent_ibi(struct i3c_hub *hub)
+{
+ int ret = 0;
+
+#ifndef CONFIG_I3C_HUB_POLLING_MODE
+ struct i3c_master_controller *master = hub->driving_master;
+
+ i3c_bus_normaluse_lock(&master->bus);
+ ret = i3c_master_disec_locked(hub->driving_master,
+ hub->i3cdev->desc->info.dyn_addr,
+ I3C_CCC_EVENT_SIR);
+ i3c_bus_normaluse_unlock(&master->bus);
+#endif
+ return ret;
+}
+
+static int i3c_hub_enable_agent_ibi(struct i3c_hub *hub)
+{
+ int ret = 0;
+
+#ifndef CONFIG_I3C_HUB_POLLING_MODE
+ struct i3c_master_controller *master = hub->driving_master;
+
+ i3c_bus_normaluse_lock(&master->bus);
+ ret = i3c_master_enec_locked(hub->driving_master,
+ hub->i3cdev->desc->info.dyn_addr,
+ I3C_CCC_EVENT_SIR);
+ i3c_bus_normaluse_unlock(&master->bus);
+#endif
+ return ret;
+}
+
+static int i3c_hub_write_paged(struct i3c_hub *hub, unsigned int page,
+ unsigned int addr, const void *data, size_t size)
+{
+ int ret;
+
+ mutex_lock(&hub->lock);
+
+ if (hub->cur_page != page) {
+ ret = regmap_write(hub->regmap, HUB_REG_PAGE_PTR, page);
+ if (ret)
+ goto exit_unlock;
+ hub->cur_page = page;
+ }
+
+ ret = regmap_bulk_write(hub->regmap, 128 + addr, data, size);
+
+exit_unlock:
+ mutex_unlock(&hub->lock);
+
+ return ret;
+}
+
+static int i3c_hub_read_paged(struct i3c_hub *hub, unsigned int page,
+ unsigned int addr, void *data, size_t size)
+{
+ int ret;
+
+ mutex_lock(&hub->lock);
+
+ if (hub->cur_page != page) {
+ ret = regmap_write(hub->regmap, HUB_REG_PAGE_PTR, page);
+ if (ret)
+ goto exit_unlock;
+ hub->cur_page = page;
+ }
+
+ ret = regmap_bulk_read(hub->regmap, 128 + addr, data, size);
+
+exit_unlock:
+ mutex_unlock(&hub->lock);
+
+ return ret;
+}
+
+/* SMBus Agent */
+struct i3c_hub_smbus_agent {
+ u32 port_nr;
+ u32 port_mask;
+ struct i2c_adapter i2c;
+
+ struct i3c_hub_target_port *port;
+ struct i3c_hub *hub;
+
+ /* agent attribute*/
+ u32 clk_freq;
+
+ /* target handling */
+ struct i2c_client *client;
+ u8 target_rx_buf[I3C_HUB_CONTROLLER_BUFFER_SIZE];
+ u32 next_buf_idx;
+
+ /* protects tx_res */
+ spinlock_t lock;
+ u8 tx_res;
+
+ struct completion completion;
+};
+
+struct i3c_hub_agent_tx_hdr {
+ u8 addr_rnw;
+ u8 type;
+ u8 wr_len;
+ u8 rd_len;
+};
+
+struct i3c_hub_agent_rx_hdr {
+ u8 len;
+ u8 addr;
+};
+
+static void i3c_hub_agent_target_rx(struct i3c_hub_smbus_agent *agent, unsigned int n)
+{
+ struct i3c_hub_agent_rx_hdr hdr;
+ struct i3c_hub *hub = agent->hub;
+ u8 tmp, len, addr;
+ unsigned int i, page;
+ int ret;
+
+ if (!agent->client)
+ goto ack;
+
+ page = n ? HUB_PAGE_AGENT_RX1(agent->port_nr) :
+ HUB_PAGE_AGENT_RX0(agent->port_nr);
+
+ /* We need the length to figure out the size of our read. But we also
+ * read the first byte of i2c data in the same read; the hardware has
+ * no facility for filtering on incoming local addresses, so we have a
+ * fast-path to aborting the transaction if it's not targeted to us.
+ */
+ ret = i3c_hub_read_paged(hub, page, 0, &hdr, sizeof(hdr));
+ if (ret)
+ goto ack;
+
+ len = min(hdr.len, I3C_HUB_TARGET_BUFFER_SIZE);
+ if (len == 0)
+ goto ack;
+
+ if (hdr.addr & 0x1) {
+ dev_dbg(&hub->i3cdev->dev, "unsupported read requested\n");
+ goto ack;
+ }
+
+ /* not for us? discard and ack */
+ addr = hdr.addr >> 1;
+ if (addr != (agent->client->addr & 0x7f))
+ goto ack;
+
+ memset(agent->target_rx_buf, 0, sizeof(agent->target_rx_buf));
+ ret = i3c_hub_read_paged(hub, page, 2, agent->target_rx_buf, len);
+ if (ret)
+ goto ack;
+
+ /* synthesize i2c target events from the target write */
+ tmp = 0;
+ ret = i2c_slave_event(agent->client, I2C_SLAVE_WRITE_REQUESTED, &tmp);
+ if (ret)
+ goto stop;
+
+ /* len includes the address byte, which we have already read */
+ for (i = 0; i < len - 1; i++) {
+ tmp = agent->target_rx_buf[i];
+ i2c_slave_event(agent->client, I2C_SLAVE_WRITE_RECEIVED, &tmp);
+ }
+
+stop:
+ tmp = 0;
+ i2c_slave_event(agent->client, I2C_SLAVE_STOP, &tmp);
+
+ack:
+ tmp = n ? HUB_REG_AGENT_CNTRL_STATUS_RX_BUF1 :
+ HUB_REG_AGENT_CNTRL_STATUS_RX_BUF0;
+
+ if (regmap_write(hub->regmap, HUB_REG_TP_SMBUS_AGNT_STS(agent->port_nr), tmp))
+ dev_warn(&hub->i3cdev->dev, "TP[%d]: Failed to clear RX status: %d\n",
+ agent->port_nr, ret);
+ agent->next_buf_idx = (agent->next_buf_idx + 1) % 2;
+}
+
+static void i3c_hub_agent_ibi(struct i3c_hub_smbus_agent *agent)
+{
+ struct i3c_hub *hub = agent->hub;
+ unsigned long flags;
+ unsigned int stat;
+ int ret;
+
+ ret = regmap_read(hub->regmap,
+ HUB_REG_TP_SMBUS_AGNT_STS(agent->port_nr), &stat);
+ if (ret) {
+ dev_err(&hub->i3cdev->dev,
+ "TP[%d] - failed to read agent status\n", agent->port_nr);
+ return;
+ }
+
+ /* Master Agent IBI */
+ if (stat & HUB_REG_AGENT_CNTRL_STATUS_FINISH) {
+ spin_lock_irqsave(&agent->lock, flags);
+ agent->tx_res = stat;
+ complete(&agent->completion);
+ spin_unlock_irqrestore(&agent->lock, flags);
+
+ ret = regmap_write(hub->regmap,
+ HUB_REG_TP_SMBUS_AGNT_STS(agent->port_nr),
+ HUB_REG_AGENT_CNTRL_STATUS_FINISH);
+ if (ret)
+ dev_warn(&hub->i3cdev->dev,
+ "TP[%d] - failed to clear finish status\n", agent->port_nr);
+ }
+
+ /* Slave Agent IBI */
+ if (stat & (HUB_REG_AGENT_CNTRL_STATUS_RX_BUF0 | HUB_REG_AGENT_CNTRL_STATUS_RX_BUF1)) {
+ if (agent->next_buf_idx == 0) {
+ if (stat & HUB_REG_AGENT_CNTRL_STATUS_RX_BUF0) {
+ i3c_hub_agent_target_rx(agent, 0);
+ /* Read the next transaction directly if it is there */
+ if (stat & HUB_REG_AGENT_CNTRL_STATUS_RX_BUF1)
+ i3c_hub_agent_target_rx(agent, 1);
+ } else {
+ dev_err(&hub->i3cdev->dev,
+ "expect rx buf 0 while buf 1 has data\n");
+ }
+ } else if (agent->next_buf_idx == 1) {
+ if (stat & HUB_REG_AGENT_CNTRL_STATUS_RX_BUF1) {
+ i3c_hub_agent_target_rx(agent, 1);
+ /* Read the next transaction directly if it is there */
+ if (stat & HUB_REG_AGENT_CNTRL_STATUS_RX_BUF0)
+ i3c_hub_agent_target_rx(agent, 0);
+ } else {
+ dev_err(&hub->i3cdev->dev,
+ "expect rx buf 1 while buf 0 has data\n");
+ }
+ }
+ }
+
+ if (stat & HUB_REG_AGENT_CNTRL_STATUS_RX_BUF_OVF) {
+ dev_dbg(&agent->i2c.dev, "rx overflow\n");
+ ret = regmap_write(hub->regmap,
+ HUB_REG_TP_SMBUS_AGNT_STS(agent->port_nr),
+ HUB_REG_AGENT_CNTRL_STATUS_RX_BUF_OVF);
+ if (ret)
+ dev_warn(&hub->i3cdev->dev,
+ "Port[%d] - failed to clear rx overflow status\n", agent->port_nr);
+ }
+}
+
+static u8 tx_clk_to_type(u32 clk)
+{
+ u8 type;
+
+ switch (clk) {
+ case 100000:
+ type = 0x0;
+ break;
+ case 200000:
+ type = 0x1;
+ break;
+ case 400000:
+ type = 0x2;
+ break;
+ case 1000000:
+ type = 0x3;
+ break;
+ default:
+ type = 0x0;
+ };
+
+ return type << 1;
+}
+
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int i3c_hub_agent_i2c_xfer_one(struct i3c_hub_smbus_agent *agent,
+ struct i2c_msg *wr_msg, struct i2c_msg *rd_msg)
+{
+ const unsigned int port_bit = 1u << agent->port_nr;
+ unsigned int page, port_stat, txn_stat;
+ struct i3c_hub_agent_tx_hdr hdr = { 0 };
+ struct device *dev = &agent->i2c.dev;
+ unsigned int port = agent->port_nr;
+ struct i3c_hub *hub = agent->hub;
+ unsigned long flags, wait_time;
+ unsigned int offset;
+ int ret;
+
+ ret = i3c_hub_disable_agent_ibi(hub);
+ if (ret) {
+ dev_err(dev, "Failed to disable smbus agent IBI:%d\n", ret);
+ goto exit;
+ }
+
+ hdr.type |= tx_clk_to_type(agent->clk_freq);
+ if (wr_msg && rd_msg) {
+ if (wr_msg->addr != rd_msg->addr) {
+ dev_err(&hub->i3cdev->dev, "different addr in i2c wr and rd msgs\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ hdr.addr_rnw = (wr_msg->addr << 1) | 0;
+ hdr.type |= 0x1;
+ hdr.wr_len = wr_msg->len;
+ hdr.rd_len = rd_msg->len;
+ } else if (wr_msg) {
+ hdr.addr_rnw = (wr_msg->addr << 1) | 0;
+ hdr.wr_len = wr_msg->len;
+ hdr.rd_len = 0;
+ } else if (rd_msg) {
+ hdr.addr_rnw = (rd_msg->addr << 1) | 1;
+ hdr.wr_len = 0;
+ hdr.rd_len = rd_msg->len;
+ }
+
+ if ((hdr.wr_len + hdr.rd_len) > 83) {
+ dev_err(dev, "SMBus Agent Tx Buffer Overflow: (%d, %d)\n", hdr.wr_len, hdr.rd_len);
+ ret = -EOVERFLOW;
+ goto exit;
+ }
+
+ page = HUB_PAGE_AGENT_TX(port);
+ ret = i3c_hub_write_paged(hub, page, 0, &hdr, sizeof(hdr));
+ if (ret) {
+ dev_err(dev, "Write header failed %d\n", ret);
+ goto exit;
+ }
+ if (wr_msg && wr_msg->len) {
+ ret = i3c_hub_write_paged(hub, page, 4, wr_msg->buf, wr_msg->len);
+ if (ret) {
+ dev_err(dev, "write data failed %d\n", ret);
+ goto exit;
+ }
+ }
+
+ /* start transfer */
+ ret = regmap_write(hub->regmap, HUB_REG_TP_SMBUS_AGNT_TRANS_START, port_bit);
+ if (ret) {
+ dev_err(dev, "write start failed %d\n", ret);
+ goto exit;
+ }
+
+ ret = i3c_hub_enable_agent_ibi(hub);
+ if (ret) {
+ dev_err(dev, "Failed to enable smbus agent IBI:%d\n", ret);
+ goto exit;
+ }
+
+ wait_time = wait_for_completion_timeout(&agent->completion,
+ agent->i2c.timeout);
+ if (!wait_time) {
+ dev_err(&hub->i3cdev->dev, "tx timeout!\n");
+ ret = -ETIMEDOUT;
+ goto exit;
+ }
+ spin_lock_irqsave(&agent->lock, flags);
+ port_stat = agent->tx_res;
+ spin_unlock_irqrestore(&agent->lock, flags);
+
+ txn_stat = port_stat >> HUB_REG_AGENT_CNTRL_STATUS_TXN_SHIFT;
+ switch (txn_stat) {
+ case HUB_REG_AGENT_CNTRL_STATUS_TXN_OK:
+ ret = 0;
+ break;
+ case HUB_REG_AGENT_CNTRL_STATUS_TXN_ADDR_NAK:
+ ret = -ENXIO;
+ break;
+ case HUB_REG_AGENT_CNTRL_STATUS_TXN_DATA_NAK:
+ ret = -EIO;
+ break;
+ case HUB_REG_AGENT_CNTRL_STATUS_TXN_ARB_LOSS:
+ ret = -EAGAIN;
+ break;
+ case HUB_REG_AGENT_CNTRL_STATUS_TXN_SCL_TO:
+ ret = -ETIMEDOUT;
+ break;
+ case HUB_REG_AGENT_CNTRL_STATUS_TXN_WTR_NAK:
+ ret = -ENXIO;
+ break;
+ default:
+ dev_err(&agent->i2c.dev, "unhandled transaction status 0x%0x\n", txn_stat);
+ ret = -EIO;
+ break;
+ }
+
+ if (!ret && rd_msg && rd_msg->len) {
+ offset = !wr_msg ? 0 : wr_msg->len;
+ ret = i3c_hub_read_paged(hub, page, sizeof(hdr) + offset, rd_msg->buf, rd_msg->len);
+ if (ret) {
+ dev_err(dev, "read data failed %d\n", ret);
+ ret = -EIO;
+ }
+ }
+
+exit:
+ if (i3c_hub_enable_agent_ibi(hub))
+ dev_err(dev, "Failed to enable smbus agent IBI:%d\n", ret);
+ return ret;
+}
+
+static int i3c_hub_agent_i2c_xfer(struct i2c_adapter *i2c, struct i2c_msg *msgs, int n_msgs)
+{
+ struct i3c_hub_smbus_agent *agent = i2c_get_adapdata(i2c);
+ struct i2c_msg *wr_msg, *rd_msg;
+ unsigned int i;
+ int ret;
+
+ i = 0;
+ while (i < n_msgs) {
+ if (!(msgs[i].flags & I2C_M_RD)) {
+ wr_msg = &msgs[i++];
+ rd_msg = NULL;
+ /* If a read msg followed by write msg is to the same address, combine it*/
+ if (i < n_msgs && msgs[i].addr == wr_msg->addr &&
+ (msgs[i].flags & I2C_M_RD)) {
+ rd_msg = &msgs[i++];
+ }
+ } else {
+ wr_msg = NULL;
+ rd_msg = &msgs[i++];
+ }
+
+ ret = i3c_hub_agent_i2c_xfer_one(agent, wr_msg, rd_msg);
+ if (ret)
+ return ret;
+ }
+
+ return n_msgs;
+}
+
+#ifdef CONFIG_I3C_HUB_POLLING_MODE
+static void smbus_agent_polling_work(struct work_struct *work);
+#endif
+static int i3c_hub_agent_i2c_reg_target(struct i2c_client *client)
+{
+ struct i3c_hub_smbus_agent *agent = i2c_get_adapdata(client->adapter);
+
+ if (agent->client)
+ return -EBUSY;
+
+ agent->client = client;
+
+ return 0;
+}
+
+static int i3c_hub_agent_i2c_unreg_target(struct i2c_client *client)
+{
+ struct i3c_hub_smbus_agent *agent = i2c_get_adapdata(client->adapter);
+
+ agent->client = NULL;
+
+ return 0;
+}
+#endif
+
+static u32 i3c_hub_agent_i2c_functionality(struct i2c_adapter *i2c)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm i3c_hub_smbus_agent_algo = {
+ .master_xfer = i3c_hub_agent_i2c_xfer,
+ .functionality = i3c_hub_agent_i2c_functionality,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+ .reg_slave = i3c_hub_agent_i2c_reg_target,
+ .unreg_slave = i3c_hub_agent_i2c_unreg_target,
+#endif
+};
+
+#ifdef CONFIG_I3C_HUB_POLLING_MODE
+static void i3c_hub_ibi(struct i3c_device *i3c,
+ const struct i3c_ibi_payload *payload);
+static void smbus_agent_polling_work(struct work_struct *work)
+{
+ struct i3c_hub *hub = container_of(work, typeof(*hub),
+ smbus_agent_polling_work.work);
+ struct i3c_ibi_payload ibi_payload = {0, NULL};
+ int ret;
+ u8 ibi_status[2];
+ int i;
+
+ ret = regmap_bulk_read(hub->regmap, HUB_REG_DEV_AND_PORT_IBI_STS, ibi_status, 2);
+ if (ret)
+ goto exit;
+
+ ibi_payload.len = sizeof(ibi_status);
+ ibi_payload.data = ibi_status;
+
+ if (ibi_status[0])
+ i3c_hub_ibi(hub->i3cdev, &ibi_payload);
+
+exit:
+ schedule_delayed_work(&hub->smbus_agent_polling_work,
+ msecs_to_jiffies(I3C_HUB_POLLING_ROLL_PERIOD_MS));
+}
+#endif
+
+static int smbus_agent_sync_next_buf_idx(struct i3c_hub_smbus_agent *agent, u32 *next_buf_idx)
+{
+ struct i3c_hub *hub = agent->hub;
+ struct i3c_hub_agent_tx_hdr hdr = { 0 };
+ u8 dev_addr = 0x70;
+ int page, stat_reg;
+ unsigned int stat;
+ int ret;
+ int i = 0;
+
+ ret = regmap_set_bits(hub->regmap, HUB_REG_ONCHIP_TD_AND_SMBUS_AGNT_CONF, 0x1);
+ if (ret)
+ return ret;
+
+ stat_reg = HUB_REG_TP_SMBUS_AGNT_STS(agent->port_nr);
+ page = HUB_PAGE_AGENT_TX(agent->port_nr);
+
+ ret = regmap_write(hub->regmap, stat_reg, 0x0f);
+ if (ret)
+ goto err_recover;
+
+ hdr.addr_rnw = dev_addr << 1;
+ ret = i3c_hub_write_paged(hub, page, 0, &hdr, sizeof(hdr));
+ if (ret)
+ goto err_recover;
+ ret = regmap_write(hub->regmap, HUB_REG_TP_SMBUS_AGNT_TRANS_START, 0x1 << agent->port_nr);
+ if (ret)
+ goto err_recover;
+
+ do {
+ ret = regmap_read(hub->regmap, stat_reg, &stat);
+ if (ret)
+ goto err_recover;
+ if (stat & HUB_REG_AGENT_CNTRL_STATUS_FINISH)
+ break;
+ } while (i++ < 100);
+
+ if (!(stat & HUB_REG_AGENT_CNTRL_STATUS_FINISH)) {
+ dev_err(&hub->i3cdev->dev, "port[%d] agent loopback unfinished:%02X\n",
+ agent->port_nr, stat);
+ ret = EIO;
+ goto err_recover;
+ }
+ stat = (stat >> 1) & 0xFF;
+ if (stat == 0x01) {
+ *next_buf_idx = 1;
+ } else if (stat == 0x02) {
+ *next_buf_idx = 0;
+ } else {
+ dev_err(&hub->i3cdev->dev, "port[%d] agent loopback error state:%02X\n",
+ agent->port_nr, stat);
+ ret = EIO;
+ goto err_recover;
+ }
+
+ ret = 0;
+
+err_recover:
+ regmap_write(hub->regmap, stat_reg, 0x0f);
+ regmap_clear_bits(hub->regmap, HUB_REG_ONCHIP_TD_AND_SMBUS_AGNT_CONF, 0x1);
+ return ret;
+}
+
+static int i3c_hub_port_init_smbus_agent(struct i3c_hub *hub,
+ struct i3c_hub_target_port *port,
+ unsigned int port_nr)
+{
+ struct i3c_hub_smbus_agent *agent;
+ u32 val;
+ int ret;
+
+ agent = devm_kzalloc(&hub->i3cdev->dev, sizeof(*agent), GFP_KERNEL);
+ if (!agent)
+ return -ENOMEM;
+
+ agent->hub = hub;
+ agent->port_nr = port_nr;
+ agent->port_mask = 1u << port_nr;
+ agent->port = port;
+
+ /* Get specific property from dt*/
+ if (!of_property_read_u32(agent->port->of_node, "clock-frequency", &val))
+ agent->clk_freq = val;
+ else
+ agent->clk_freq = 100000;
+
+ ret = regmap_clear_bits(hub->regmap, HUB_REG_TP_ENABLE, agent->port_mask);
+ if (ret)
+ return -EIO;
+
+ ret = regmap_clear_bits(hub->regmap, HUB_REG_TP_GPIO_MODE_EN, agent->port_mask);
+ if (ret)
+ return -EIO;
+
+ ret = regmap_set_bits(hub->regmap, HUB_REG_TP_IBI_CONF, agent->port_mask);
+ if (ret)
+ return -EIO;
+
+ /* clear pending events */
+ ret = regmap_write(hub->regmap, HUB_REG_TP_SMBUS_AGNT_STS(port_nr), 0x0f);
+ if (ret)
+ return -EIO;
+
+ ret = regmap_set_bits(hub->regmap, HUB_REG_TP_IO_MODE_CONF, agent->port_mask);
+ if (ret)
+ return -EIO;
+
+ ret = regmap_set_bits(hub->regmap, HUB_REG_TP_SMBUS_AGNT_EN, agent->port_mask);
+ if (ret)
+ return -EIO;
+
+ ret = regmap_set_bits(hub->regmap, HUB_REG_TP_ENABLE, agent->port_mask);
+ if (ret)
+ return -EIO;
+
+ /* Sync the rx_buf index */
+ ret = smbus_agent_sync_next_buf_idx(agent, &agent->next_buf_idx);
+ if (ret) {
+ dev_err(&hub->i3cdev->dev, "SMBus Agent Rx buf sync failed\n");
+ return ret;
+ }
+
+ dev_info(&hub->i3cdev->dev, "port[%d] - next buf idx: %d\n",
+ agent->port_nr, agent->next_buf_idx);
+
+ init_completion(&agent->completion);
+ spin_lock_init(&agent->lock);
+ agent->i2c.owner = THIS_MODULE;
+ agent->i2c.algo = &i3c_hub_smbus_agent_algo;
+ agent->i2c.dev.parent = &hub->i3cdev->dev;
+ agent->i2c.dev.of_node = of_node_get(port->of_node);
+ snprintf(agent->i2c.name, sizeof(agent->i2c.name), "hub%s.port%d",
+ dev_name(&hub->i3cdev->dev), port_nr);
+
+ i2c_set_adapdata(&agent->i2c, agent);
+
+ ret = i2c_add_adapter(&agent->i2c);
+ if (ret)
+ devm_kfree(&hub->i3cdev->dev, agent);
+
+ port->agent = agent;
+
+ return ret;
+}
+
+/* I3C Bridge */
+struct i3c_hub_bridge {
+ u32 port_nr;
+ u32 port_mask;
+ struct i3c_master_controller i3c;
+
+ struct i3c_hub_target_port *port;
+ struct i3c_hub *hub;
+
+ bool idle_disconnect;
+};
+
+/* i3c ops */
+static struct i3c_master_controller
+*parent_from_controller(struct i3c_master_controller *controller)
+{
+ struct i3c_hub_bridge *bridge = container_of(controller, struct i3c_hub_bridge, i3c);
+
+ return bridge->hub->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 i3c_hub_bridge *bridge = container_of(controller, struct i3c_hub_bridge, i3c);
+
+ return bridge->hub->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 i3c_hub_bridge *bridge = container_of(controller, struct i3c_hub_bridge, i3c);
+
+ return bridge->hub->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 inline int i3c_hub_bridge_connect(struct i3c_hub_bridge *bridge)
+{
+ return regmap_set_bits(bridge->hub->regmap, HUB_REG_TP_NET_CON_CONF, bridge->port_mask);
+}
+
+static inline int i3c_hub_bridge_disconnect(struct i3c_hub_bridge *bridge)
+{
+ return regmap_clear_bits(bridge->hub->regmap, HUB_REG_TP_NET_CON_CONF, bridge->port_mask);
+}
+
+static void i3c_hub_trans_pre_cb(struct i3c_hub_bridge *bridge)
+{
+ if (bridge->idle_disconnect)
+ i3c_hub_bridge_connect(bridge);
+}
+
+static void i3c_hub_trans_post_cb(struct i3c_hub_bridge *bridge)
+{
+ if (bridge->idle_disconnect)
+ i3c_hub_bridge_disconnect(bridge);
+}
+
+static struct i3c_hub_bridge *bus_from_i3c_desc(struct i3c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = i3c_dev_get_master(desc);
+
+ return container_of(controller, struct i3c_hub_bridge, i3c);
+}
+
+static struct i3c_hub_bridge *bus_from_i2c_desc(struct i2c_dev_desc *desc)
+{
+ struct i3c_master_controller *controller = i2c_dev_get_master(desc);
+
+ return container_of(controller, struct i3c_hub_bridge, i3c);
+}
+
+static int i3c_hub_bus_init(struct i3c_master_controller *controller)
+{
+ struct i3c_hub_bridge *bridge = container_of(controller, struct i3c_hub_bridge, i3c);
+
+ controller->this = bridge->hub->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;
+
+ if (!controller->init_done)
+ return 0;
+
+ 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);
+
+ if (parent->ops->supports_ccc_cmd)
+ return parent->ops->supports_ccc_cmd(parent, cmd);
+ else
+ return true;
+}
+
+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 i3c_hub_bridge *bridge = container_of(controller, struct i3c_hub_bridge, i3c);
+ int ret;
+
+ if (cmd->id == I3C_CCC_RSTDAA(true))
+ return 0;
+
+ i3c_hub_trans_pre_cb(bridge);
+ ret = parent->ops->send_ccc_cmd(parent, cmd);
+ i3c_hub_trans_post_cb(bridge);
+
+ return ret;
+}
+
+static int i3c_hub_priv_xfers(struct i3c_dev_desc *dev,
+ struct i3c_priv_xfer *xfers, int nxfers)
+{
+ struct i3c_master_controller *parent = parent_controller_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ struct i3c_hub_bridge *bridge = bus_from_i3c_desc(dev);
+ int res;
+
+ i3c_hub_trans_pre_cb(bridge);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ res = parent->ops->priv_xfers(dev, xfers, nxfers);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ i3c_hub_trans_post_cb(bridge);
+
+ 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,
+ const struct i2c_msg *xfers, int nxfers)
+{
+ struct i3c_master_controller *parent = parent_controller_from_i2c_desc(dev);
+ struct i3c_hub_bridge *bridge = bus_from_i2c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ i3c_hub_trans_pre_cb(bridge);
+ 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(bridge);
+ 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 i3c_hub_bridge *bridge = bus_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ i3c_hub_trans_pre_cb(bridge);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->request_ibi(dev, req);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ i3c_hub_trans_post_cb(bridge);
+ 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 i3c_hub_bridge *bridge = bus_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+
+ i3c_hub_trans_pre_cb(bridge);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ parent->ops->free_ibi(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ i3c_hub_trans_post_cb(bridge);
+}
+
+static int i3c_hub_enable_ibi(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *parent = parent_controller_from_i3c_desc(dev);
+ struct i3c_hub_bridge *bridge = bus_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ i3c_hub_trans_pre_cb(bridge);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->enable_ibi(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ i3c_hub_trans_post_cb(bridge);
+ 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 i3c_hub_bridge *bridge = bus_from_i3c_desc(dev);
+ struct i3c_master_controller *orig_parent;
+ int ret;
+
+ i3c_hub_trans_pre_cb(bridge);
+ orig_parent = update_i3c_i2c_desc_parent(&dev->common, parent);
+ ret = parent->ops->disable_ibi(dev);
+ restore_i3c_i2c_desc_parent(&dev->common, orig_parent);
+ i3c_hub_trans_post_cb(bridge);
+ 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,
+ .priv_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,
+};
+
+int i3c_hub_bridge_register(struct i3c_hub_bridge *bridge, struct device *parent)
+{
+ return i3c_master_register(&bridge->i3c, parent, &i3c_hub_i3c_ops, false);
+}
+
+static int i3c_hub_port_init_i3c_bridge(struct i3c_hub *hub,
+ struct i3c_hub_target_port *port,
+ unsigned int port_nr)
+{
+ struct i3c_hub_bridge *bridge;
+ int ret;
+
+ bridge = devm_kzalloc(&hub->i3cdev->dev, sizeof(*bridge), GFP_KERNEL);
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->hub = hub;
+ bridge->port = port;
+ bridge->port_nr = port_nr;
+ bridge->port_mask = 0x1u << port_nr;
+
+ if (of_property_read_bool(port->of_node, "idle-disconnect"))
+ bridge->idle_disconnect = true;
+ else
+ bridge->idle_disconnect = false;
+
+ port->bridge = bridge;
+
+ /* Port HW config */
+ ret = regmap_clear_bits(hub->regmap, HUB_REG_TP_IO_MODE_CONF, bridge->port_mask);
+ if (ret)
+ return ret;
+ ret = regmap_set_bits(hub->regmap, HUB_REG_TP_ENABLE, bridge->port_mask);
+ if (ret)
+ return ret;
+ ret = regmap_set_bits(hub->regmap, HUB_REG_TP_NET_CON_CONF, bridge->port_mask);
+ if (ret)
+ return ret;
+
+ hub->i3cdev->dev.of_node = port->of_node;
+
+ ret = i3c_hub_bridge_register(bridge, i3cdev_to_dev(hub->i3cdev));
+ if (ret) {
+ dev_warn(&hub->i3cdev->dev,
+ "Failed to register i3c controller - port:%i\n",
+ port->port_nr);
+ return ret;
+ }
+
+ ret = i3c_master_do_daa(hub->driving_master);
+ if (ret)
+ dev_warn(&hub->i3cdev->dev, "Failed to run DAA - port:%i\n", port->port_nr);
+
+ return ret;
+}
+
+/* Debug FS*/
+static int fops_access_reg_get(void *ctx, u64 *val)
+{
+ struct i3c_hub *priv = ctx;
+ u32 reg_val;
+ int ret;
+
+ ret = regmap_read(priv->regmap, priv->reg_addr, &reg_val);
+ if (ret)
+ return ret;
+
+ *val = reg_val & 0xFF;
+ return 0;
+}
+
+static int fops_access_reg_set(void *ctx, u64 val)
+{
+ struct i3c_hub *priv = ctx;
+
+ return regmap_write(priv->regmap, priv->reg_addr, val & 0xFF);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_access_reg, fops_access_reg_get,
+ fops_access_reg_set, "0x%llX\n");
+
+static int i3c_hub_debugfs_init(struct i3c_hub *hub, const char *hub_id)
+{
+ struct dentry *entry, *dt_conf_dir, *reg_dir;
+ struct dentry *target_grp_dir;
+ struct dentry *cp_dir, *tp_dir;
+ char file_name[32];
+ int i;
+
+ entry = debugfs_create_dir(hub_id, NULL);
+ if (IS_ERR(entry))
+ return PTR_ERR(entry);
+
+ hub->debug_dir = entry;
+
+ entry = debugfs_create_dir("dt-conf", hub->debug_dir);
+ if (IS_ERR(entry))
+ goto err_remove;
+
+ dt_conf_dir = entry;
+
+ cp_dir = debugfs_create_dir("control-port", dt_conf_dir);
+ if (IS_ERR(cp_dir))
+ goto err_remove;
+
+ /* for control ports */
+ debugfs_create_u32("id", 0400, cp_dir, &hub->cp_port.id);
+ debugfs_create_u32("io-microvolt", 0400, cp_dir, &hub->cp_port.io_microvolt);
+ debugfs_create_u32("io-strength-ohms", 0400, cp_dir, &hub->cp_port.io_strength_ohms);
+ debugfs_create_u32("vio-source", 0400, cp_dir, &hub->cp_port.vio_source);
+
+ /* for target groups */
+ for (i = 0; i < 2; ++i) {
+ sprintf(file_name, "targe-group-%d", i);
+ target_grp_dir = debugfs_create_dir(file_name, dt_conf_dir);
+ if (IS_ERR(target_grp_dir))
+ goto err_remove;
+
+ debugfs_create_u32("io-microvolt", 0400, target_grp_dir,
+ &hub->tp_groups[i].io_microvolt);
+ debugfs_create_u32("vio-source", 0400, target_grp_dir,
+ &hub->tp_groups[i].vio_source);
+ debugfs_create_u32("io-internal-pullups-ohms", 0400,
+ target_grp_dir,
+ &hub->tp_groups[i].io_internal_pullups_ohms);
+ debugfs_create_u32("io-strength-ohms", 0400, target_grp_dir,
+ &hub->tp_groups[i].io_strength_ohms);
+ }
+
+ /* for target ports*/
+ for (i = 0; i < hub->devinfo->n_ports; ++i) {
+ sprintf(file_name, "targe-port-%d", i);
+ tp_dir = debugfs_create_dir(file_name, dt_conf_dir);
+ if (IS_ERR(target_grp_dir))
+ goto err_remove;
+
+ debugfs_create_u32("mode", 0400, tp_dir, &hub->ports[i].mode);
+ debugfs_create_bool("io-internal-pullups-disble", 0400, tp_dir,
+ &hub->ports[i].pullups_disable);
+ if (hub->ports[i].mode == PORT_MODE_I3C)
+ debugfs_create_bool("idle-disconnect", 0400, tp_dir,
+ &hub->ports[i].bridge->idle_disconnect);
+ }
+
+ entry = debugfs_create_dir("reg", hub->debug_dir);
+ if (IS_ERR(entry))
+ goto err_remove;
+
+ reg_dir = entry;
+
+ entry = debugfs_create_file_unsafe("access", 0600, reg_dir, hub, &fops_access_reg);
+ if (IS_ERR(entry))
+ goto err_remove;
+
+ debugfs_create_u8("offset", 0600, reg_dir, &hub->reg_addr);
+
+ return 0;
+
+err_remove:
+ debugfs_remove_recursive(hub->debug_dir);
+ return PTR_ERR(entry);
+}
+
+static int i3c_hub_set_cp_ldo(struct i3c_hub *hub, u32 cp, u32 ldo_volt)
+{
+ u32 mask, val;
+
+ mask = cp == 0 ? GENMASK(1, 0) : GENMASK(3, 2);
+
+ switch (ldo_volt) {
+ case 1000000:
+ val = 0x00;
+ break;
+ case 1100000:
+ val = 0x01;
+ break;
+ case 1200000:
+ val = 0x02;
+ break;
+ case 1800000:
+ val = 0x03;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ val = cp == 0 ? val : val << 2;
+
+ return regmap_update_bits(hub->regmap, HUB_REG_LDO_CONF, mask, val);
+}
+
+static int i3c_hub_enable_cp_ldo(struct i3c_hub *hub, u32 cp, bool enable)
+{
+ u32 mask, val;
+
+ mask = cp == 0 ? GENMASK(0, 0) : GENMASK(1, 1);
+ val = enable ? 0x1 : 0x0;
+ val = cp == 0 ? val : val << 1;
+
+ return regmap_update_bits(hub->regmap, HUB_REG_LDO_AND_PULLUP_CONF, mask, val);
+}
+
+static int i3c_hub_set_cp_io_strength(struct i3c_hub *hub, u32 cp, u32 io_strength)
+{
+ u32 mask, val;
+
+ mask = cp == 0 ? GENMASK(5, 4) : GENMASK(7, 6);
+
+ switch (io_strength) {
+ case 20:
+ val = 0x00;
+ break;
+ case 30:
+ val = 0x01;
+ break;
+ case 40:
+ val = 0x02;
+ break;
+ case 50:
+ val = 0x03;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ val = cp == 0 ? val << 4 : val << 6;
+
+ return regmap_update_bits(hub->regmap, HUB_REG_IO_STRENGTH, mask, val);
+}
+
+static int i3c_hub_set_tp_group_ldo(struct i3c_hub *hub, u32 grp, u32 ldo_volt)
+{
+ u32 mask, val;
+
+ mask = grp == 0 ? GENMASK(5, 4) : GENMASK(7, 6);
+
+ switch (ldo_volt) {
+ case 1000000:
+ val = 0x00;
+ break;
+ case 1100000:
+ val = 0x01;
+ break;
+ case 1200000:
+ val = 0x02;
+ break;
+ case 1800000:
+ val = 0x03;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ val = grp == 0 ? val << 4 : val << 6;
+
+ return regmap_update_bits(hub->regmap, HUB_REG_LDO_CONF, mask, val);
+}
+
+static int i3c_hub_enable_tp_group_ldo(struct i3c_hub *hub, u32 grp, bool enable)
+{
+ u32 mask, val;
+
+ mask = grp == 0 ? GENMASK(2, 2) : GENMASK(3, 3);
+ val = enable ? 0x1 : 0x0;
+ val = grp == 0 ? val << 2 : val << 3;
+
+ return regmap_update_bits(hub->regmap, HUB_REG_LDO_AND_PULLUP_CONF, mask, val);
+}
+
+static int i3c_hub_set_tp_group_io_strength(struct i3c_hub *hub, u32 grp, u32 io_strength)
+{
+ u32 mask, val;
+
+ mask = grp == 0 ? GENMASK(1, 0) : GENMASK(3, 2);
+
+ switch (io_strength) {
+ case 20:
+ val = 0x00;
+ break;
+ case 30:
+ val = 0x01;
+ break;
+ case 40:
+ val = 0x02;
+ break;
+ case 50:
+ val = 0x03;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ val = grp == 0 ? val << 0 : val << 2;
+
+ return regmap_update_bits(hub->regmap, HUB_REG_IO_STRENGTH, mask, val);
+}
+
+static int i3c_hub_set_tp_group_pullup(struct i3c_hub *hub, u32 grp, u32 pullup)
+{
+ u32 mask, val;
+
+ mask = grp == 0 ? GENMASK(7, 6) : GENMASK(5, 4);
+
+ switch (pullup) {
+ case 250:
+ val = 0x00;
+ break;
+ case 500:
+ val = 0x01;
+ break;
+ case 1000:
+ val = 0x02;
+ break;
+ case 2000:
+ val = 0x03;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ val = grp == 0 ? val << 6 : val << 4;
+
+ return regmap_update_bits(hub->regmap, HUB_REG_LDO_AND_PULLUP_CONF, mask, val);
+}
+
+static int i3c_hub_populate_cp_settings(struct i3c_hub *hub)
+{
+ struct device_node *np;
+ const char *sval;
+ u32 val;
+ int ret;
+ bool ldo_en;
+
+ if (!hub->of_node)
+ return 0;
+
+ np = of_get_child_by_name(hub->of_node, "control-port");
+ if (!np)
+ return -EINVAL;
+
+ if (!of_property_read_string(np, "port-name", &sval)) {
+ if (!strcmp(sval, "CP0")) {
+ hub->cp_port.id = 0;
+ } else if (!strcmp(sval, "CP1")) {
+ hub->cp_port.id = 1;
+ } else {
+ dev_warn(&hub->i3cdev->dev, "Invalid control-port name:%s\n", sval);
+ return -EINVAL;
+ }
+ } else {
+ dev_err(&hub->i3cdev->dev, "no control-port name\n");
+ return -EINVAL;
+ }
+
+ if (!of_property_read_u32(np, "io-microvolt", &val)) {
+ hub->cp_port.io_microvolt = val;
+
+ ret = i3c_hub_set_cp_ldo(hub, hub->cp_port.id, hub->cp_port.io_microvolt);
+ if (ret)
+ return ret;
+ }
+
+ if (!of_property_read_u32(np, "io-strength-ohms", &val)) {
+ hub->cp_port.io_strength_ohms = val;
+
+ ret = i3c_hub_set_cp_io_strength(hub, hub->cp_port.id,
+ hub->cp_port.io_strength_ohms);
+ if (ret)
+ return ret;
+ }
+
+ if (!of_property_read_string(np, "vio-source", &sval)) {
+ if (!strcmp(sval, "external")) {
+ hub->cp_port.vio_source = VIO_EXTERNAL;
+ } else if (!strcmp(sval, "internal")) {
+ hub->cp_port.vio_source = VIO_INTERNAL;
+ } else {
+ dev_warn(&hub->i3cdev->dev, "Invalid vio-source:%s\n", sval);
+ return -EINVAL;
+ }
+
+ ldo_en = hub->cp_port.vio_source == VIO_INTERNAL ? true : false;
+ ret = i3c_hub_enable_cp_ldo(hub, hub->cp_port.id, ldo_en);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int i3c_hub_populate_tp_grp_settings(struct i3c_hub *hub, u32 grp)
+{
+ struct device_node *np;
+ struct i3c_hub_tp_group *tgrp;
+ const char *sval;
+ u32 val;
+ int ret;
+ bool ldo_en;
+
+ if (!hub->of_node)
+ return 0;
+
+ if (grp == 0)
+ np = of_get_child_by_name(hub->of_node, "target-group-0");
+ else
+ np = of_get_child_by_name(hub->of_node, "target-group-1");
+
+ if (!np)
+ return 0;
+
+ tgrp = &hub->tp_groups[grp];
+
+ if (!of_property_read_u32(np, "io-microvolt", &val)) {
+ tgrp->io_microvolt = val;
+
+ ret = i3c_hub_set_tp_group_ldo(hub, grp, tgrp->io_microvolt);
+ if (ret)
+ return ret;
+ }
+
+ if (!of_property_read_string(np, "vio-source", &sval)) {
+ if (!strcmp(sval, "external")) {
+ tgrp->vio_source = VIO_EXTERNAL;
+ } else if (!strcmp(sval, "internal")) {
+ tgrp->vio_source = VIO_INTERNAL;
+ } else {
+ dev_err(&hub->i3cdev->dev, "Invalid vio-source:%s\n", sval);
+ return -EINVAL;
+ }
+
+ ldo_en = tgrp->vio_source == VIO_INTERNAL ? true : false;
+ ret = i3c_hub_enable_tp_group_ldo(hub, grp, ldo_en);
+ if (ret)
+ return ret;
+ }
+
+ if (!of_property_read_u32(np, "io-strength-ohms", &val)) {
+ tgrp->io_strength_ohms = val;
+
+ ret = i3c_hub_set_tp_group_io_strength(hub, grp, tgrp->io_strength_ohms);
+ if (ret)
+ return ret;
+ }
+
+ if (!of_property_read_u32(np, "io-internal-pullups-ohms", &val)) {
+ tgrp->io_internal_pullups_ohms = val;
+
+ ret = i3c_hub_set_tp_group_pullup(hub, grp, tgrp->io_internal_pullups_ohms);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int i3c_hub_read_identify_tp(struct i3c_hub *hub)
+{
+ int tp_id = -2;
+
+#ifdef CONFIG_I3C_HUB_TP_IDENDIFY
+ int tp_nr = CONFIG_I3C_HUB_IDENTIFY_TP_NR;
+ unsigned int sscl_input, ssda_input, reg19;
+ int ret;
+
+ dev_info(&hub->i3cdev->dev, "Use TP[%d] for identification\n", tp_nr);
+
+ ret = i3c_hub_unprotect_register(hub);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(hub->regmap, HUB_REG_DEV_CONF, &reg19);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(hub->regmap, HUB_REG_DEV_CONF, reg19 & ~(BIT(3)));
+ if (ret)
+ goto recover;
+
+ ret = i3c_hub_port_enable(hub, tp_nr);
+ if (ret)
+ goto recover;
+
+ ret = regmap_read(hub->regmap, HUB_REG_TP_SCL_IN_LEVEL_STS, &sscl_input);
+ if (ret)
+ goto recover;
+
+ ret = regmap_read(hub->regmap, HUB_REG_TP_SDA_IN_LEVEL_STS, &ssda_input);
+ if (ret)
+ goto recover;
+
+ sscl_input = (sscl_input > tp_nr) & 0x01;
+ ssda_input = (ssda_input > tp_nr) & 0x01;
+
+ tp_id = (int)((ssda_input << 1) | (sscl_input));
+ dev_info(&hub->i3cdev->dev, "id-tpx from TP[%d]: %d\n", tp_nr, tp_id);
+
+recover:
+ regmap_write(hub->regmap, HUB_REG_DEV_CONF, reg19);
+
+out:
+ i3c_hub_port_disable(hub, tp_nr);
+ i3c_hub_protect_register(hub);
+
+ if (tp_id < 0)
+ dev_warn(&hub->i3cdev->dev, "tp_id read failed.\n");
+
+ return tp_id;
+#else
+ return tp_id;
+#endif
+}
+
+static int i3c_hub_read_id(struct i3c_hub *hub)
+{
+ u32 reg_val;
+ int ret;
+
+ ret = regmap_read(hub->regmap, HUB_REG_LDO_AND_CPSEL_STS, &reg_val);
+ if (ret) {
+ dev_err(&hub->i3cdev->dev, "Failed to read status register\n");
+ return -EIO;
+ }
+
+ hub->hub_pin_sel_id = CP_SEL_PIN_INPUT_CODE_GET(reg_val);
+ hub->hub_pin_cp1_id = CP_SDA1_SCL1_PINS_CODE_GET(reg_val);
+ hub->hub_pin_tpx_id = i3c_hub_read_identify_tp(hub);
+ return 0;
+}
+
+static struct device_node *i3c_hub_get_dt_hub_node(struct i3c_hub *hub)
+{
+ struct device_node *node = hub->i3cdev->dev.parent->of_node;
+ struct device_node *matched_node = NULL;
+ int max_ids_matched;
+ struct device_node *hub_node, *from;
+ int node_ids_matched;
+ u8 dcr;
+ u32 id_csel, id_cp1, id_tpx;
+ int ret;
+
+ max_ids_matched = 0;
+
+ from = last_node ? last_node : node;
+ hub_node = NULL;
+ while (1) {
+ hub_node = of_find_node_by_name(from, "hub");
+ if (!hub_node)
+ break;
+
+ ret = of_property_read_u8(hub_node, "dcr", &dcr);
+ if (ret || dcr != I3C_DCR_HUB)
+ continue;
+
+ from = hub_node;
+
+ node_ids_matched = 1;
+ ret = of_property_read_u32(hub_node, "id-csel", &id_csel);
+ if (ret == 0 && id_csel == (u32)hub->hub_pin_sel_id)
+ node_ids_matched += 1;
+
+ ret = of_property_read_u32(hub_node, "id-cp1", &id_cp1);
+ if (ret == 0 && id_cp1 == (u32)hub->hub_pin_cp1_id)
+ node_ids_matched += 1;
+
+ ret = of_property_read_u32(hub_node, "id-tpx", &id_tpx);
+ if (ret == 0 && id_tpx == (u32)hub->hub_pin_tpx_id)
+ node_ids_matched += 1;
+
+ if (node_ids_matched > max_ids_matched) {
+ matched_node = hub_node;
+ max_ids_matched = node_ids_matched;
+ }
+ }
+
+ if (!matched_node) {
+ dev_err(&hub->i3cdev->dev, "Node NOT matched\n");
+ return matched_node;
+ }
+
+ last_node = matched_node;
+ /* Find the proper node, update the id values in the node*/
+ ret = of_property_read_u32(matched_node, "id-csel", &id_csel);
+ hub->hub_dt_sel_id = ret == 0 ? id_csel : -1;
+ ret = of_property_read_u32(matched_node, "id-cp1", &id_cp1);
+ hub->hub_dt_cp1_id = ret == 0 ? id_cp1 : -1;
+ ret = of_property_read_u32(matched_node, "id-tpx", &id_tpx);
+ hub->hub_dt_tpx_id = ret == 0 ? id_tpx : -1;
+
+ dev_info(&hub->i3cdev->dev, "Node matching:pin:<%d, %d, %d>, dt:<%d, %d, %d>\n",
+ hub->hub_pin_sel_id, hub->hub_pin_cp1_id, hub->hub_pin_tpx_id,
+ hub->hub_dt_sel_id, hub->hub_dt_cp1_id, hub->hub_dt_tpx_id);
+ dev_info(&hub->i3cdev->dev, "Node matched:%s\n", matched_node->full_name);
+
+ return matched_node;
+}
+
+static const struct i3c_hub_devdata *i3c_hub_find_device(u16 part_id)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(i3c_hub_devs); i++) {
+ const struct i3c_hub_devdata *d = &i3c_hub_devs[i];
+
+ if (d->part_id == part_id)
+ return d;
+ }
+
+ return NULL;
+}
+
+static int i3c_hub_hw_init(struct i3c_hub *hub)
+{
+ unsigned int dev_info[2];
+ u16 part_id;
+ int ret;
+
+ ret = regmap_read(hub->regmap, HUB_REG_DEV_INFO_0, &dev_info[0]);
+ if (ret)
+ return ret;
+ ret = regmap_read(hub->regmap, HUB_REG_DEV_INFO_1, &dev_info[1]);
+ if (ret)
+ return ret;
+
+ part_id = dev_info[0] << 8 | dev_info[1];
+ part_id = dev_info[0] << 8 | dev_info[1];
+ dev_info(&hub->i3cdev->dev, "I3C Hub device %04x\n", part_id);
+
+ hub->devinfo = i3c_hub_find_device(part_id);
+ if (!hub->devinfo)
+ return -ENODEV;
+
+ /* Update CP & Target Group 0/1 Settings */
+ ret = i3c_hub_populate_cp_settings(hub);
+ if (ret)
+ return ret;
+ ret = i3c_hub_populate_tp_grp_settings(hub, 0);
+ if (ret)
+ return ret;
+ ret = i3c_hub_populate_tp_grp_settings(hub, 1);
+ if (ret)
+ return ret;
+ return 0;
+}
+
+static int i3c_hub_port_init(struct i3c_hub *hub, u32 port_nr)
+{
+ struct i3c_hub_target_port *port;
+ int ret;
+
+ if (port_nr >= I3C_HUB_TP_MAX_COUNT)
+ return -EINVAL;
+
+ port = &hub->ports[port_nr];
+ if (!port->of_node)
+ return 0;
+
+ port->port_nr = port_nr;
+ port->port_mask = 1u << port_nr;
+
+ if (of_property_read_bool(port->of_node, "io-internal-pullups-disable")) {
+ port->pullups_disable = true;
+ ret = regmap_clear_bits(hub->regmap, HUB_REG_TP_PULLUP_EN, port->port_mask);
+ if (ret)
+ return ret;
+ } else {
+ port->pullups_disable = false;
+ ret = regmap_set_bits(hub->regmap, HUB_REG_TP_PULLUP_EN, port->port_mask);
+ if (ret)
+ return ret;
+ }
+
+ switch (port->mode) {
+ case PORT_MODE_AGENT:
+ ret = i3c_hub_port_init_smbus_agent(hub, port, port_nr);
+ break;
+ case PORT_MODE_I3C:
+ ret = i3c_hub_port_init_i3c_bridge(hub, port, port_nr);
+ break;
+ default:
+ /* Disable the port*/
+ ret = i3c_hub_port_disable(hub, port_nr);
+ };
+
+ return ret;
+}
+
+static void i3c_hub_populate_target_ports(struct i3c_hub *hub)
+{
+ struct device_node *np;
+ struct i3c_hub_target_port *port;
+ u64 addr;
+ int ret;
+ int mode;
+
+ if (!hub->of_node)
+ return;
+
+ for_each_available_child_of_node(hub->of_node, np) {
+ if (of_device_is_compatible(np, "i3c-hub-smbus"))
+ mode = PORT_MODE_AGENT;
+ else if (of_device_is_compatible(np, "i3c-hub-i3c"))
+ mode = PORT_MODE_I3C;
+ else
+ continue;
+
+ ret = of_property_read_reg(np, 0, &addr, NULL);
+ if (ret)
+ continue;
+
+ if (addr >= hub->devinfo->n_ports) {
+ dev_warn(&hub->i3cdev->dev, "Invalid target-port addr:%llx\n", addr);
+ continue;
+ }
+
+ port = &hub->ports[addr];
+ port->of_node = np;
+ port->mode = mode;
+ }
+}
+
+/* I3C handling */
+struct i3c_hub_ibi_payload {
+ u8 dev_port_status;
+ u8 target_agent_status;
+} __packed;
+
+static void i3c_hub_ibi(struct i3c_device *i3c,
+ const struct i3c_ibi_payload *payload)
+{
+ struct i3c_hub *hub = i3cdev_get_drvdata(i3c);
+ const struct i3c_hub_ibi_payload *p = NULL;
+ unsigned int i, dev_stat, target_stat;
+ int ret;
+
+ ret = i3c_hub_disable_agent_ibi(hub);
+ if (ret)
+ dev_warn(&hub->i3cdev->dev, "Failed to disable smbus agent IBI:%d\n", ret);
+
+ if (payload->len == sizeof(*p))
+ p = payload->data;
+
+ if (!p || ibi_paranoia) {
+ unsigned char tmp[2];
+ int ret;
+
+ /* DEV_PORT_STATUS and TARGET_STATUS are contiguous,
+ * read as a bulk operation.
+ */
+ BUILD_BUG_ON(HUB_REG_TP_SMBUS_AGNT_IBI_STS !=
+ HUB_REG_DEV_AND_PORT_IBI_STS + 1);
+
+ ret = regmap_bulk_read(hub->regmap, HUB_REG_DEV_AND_PORT_IBI_STS, tmp, 2);
+ if (ret)
+ goto exit;
+
+ dev_stat = tmp[0];
+ target_stat = tmp[1];
+ } else {
+ dev_stat = p->dev_port_status;
+ target_stat = p->target_agent_status;
+ }
+
+ if (dev_stat & 0x07) {
+ dev_warn(&hub->i3cdev->dev, "I3C Hub device IBI [%02X] cleared\n", dev_stat & 0x07);
+ if (regmap_write(hub->regmap, HUB_REG_DEV_AND_PORT_IBI_STS, 0x07))
+ dev_warn(&hub->i3cdev->dev, "Failed to Clear IBI:%02x\n", dev_stat & 0x07);
+ }
+
+ if (dev_stat & 0x10) {
+ /* Pass SMBus agent events to each port's agent, if configured. */
+ for (i = 0; i < hub->devinfo->n_ports; i++) {
+ struct i3c_hub_target_port *port = &hub->ports[i];
+
+ if (!(target_stat & 1 << i))
+ continue;
+
+ if (port->mode != PORT_MODE_AGENT || !port->agent) {
+ dev_warn(&hub->i3cdev->dev, "IBI for invalid port %d\n", i);
+
+ continue;
+ }
+ i3c_hub_agent_ibi(port->agent);
+ }
+ }
+
+exit:
+ if (i3c_hub_enable_agent_ibi(hub))
+ dev_err(&hub->i3cdev->dev, "Failed to enable smbus agent IBI:%d\n", ret);
+}
+
+static const struct i3c_ibi_setup i3c_hub_ibi_setup = {
+ .max_payload_len = 2, /* no MDB, two status registers */
+ .num_slots = 6, /* two target buffers, one controller status */
+ .handler = i3c_hub_ibi,
+};
+
+static const struct regmap_config i3c_hub_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 255,
+};
+
+static const struct i3c_device_id i3c_hub_ids[] = {
+ I3C_CLASS(I3C_DCR_HUB, NULL),
+ { },
+};
+
+static void i3c_hub_delayed_work(struct work_struct *work)
+{
+ struct i3c_hub *hub = container_of(work, typeof(*hub), delayed_work.work);
+ struct device *dev = i3cdev_to_dev(hub->i3cdev);
+ int i;
+ int ret;
+
+ mutex_lock(&hub_lock);
+
+ i3c_hub_unprotect_register(hub);
+
+ for (i = 0; i < hub->devinfo->n_ports; ++i) {
+ dev_info(dev, "Init target port[%d] ...\n", i);
+ ret = i3c_hub_port_init(hub, i);
+ if (ret)
+ dev_err(dev, "ports init failed\n");
+ }
+ i3c_hub_protect_register(hub);
+
+ mutex_unlock(&hub_lock);
+}
+
+static int i3c_hub_probe(struct i3c_device *i3cdev)
+{
+ struct device *dev = &i3cdev->dev;
+ struct regmap *regmap;
+ struct i3c_hub *hub;
+ char hub_id[32];
+ int ret;
+
+ hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+
+ hub->i3cdev = i3cdev;
+ i3cdev_set_drvdata(i3cdev, hub);
+
+ INIT_DELAYED_WORK(&hub->delayed_work, i3c_hub_delayed_work);
+
+ regmap = devm_regmap_init_i3c(i3cdev, &i3c_hub_regmap_config);
+ if (IS_ERR_OR_NULL(regmap))
+ return PTR_ERR(regmap);
+ hub->regmap = regmap;
+
+ mutex_init(&hub->lock);
+
+ /* Disable all slave ports */
+ i3c_hub_unprotect_register(hub);
+ ret = regmap_write(hub->regmap, HUB_REG_TP_ENABLE, 0x00);
+ if (ret)
+ return ret;
+ i3c_hub_protect_register(hub);
+
+ hub->driving_master = i3c_dev_get_master(i3cdev->desc);
+
+ ret = i3c_hub_read_id(hub);
+ if (ret)
+ return ret;
+ hub->of_node = i3c_hub_get_dt_hub_node(hub);
+ if (!hub->of_node)
+ dev_info(dev, "No DT entry - running with hardware defaults.\n");
+
+ i3c_hub_unprotect_register(hub);
+
+ ret = i3c_hub_hw_init(hub);
+ if (ret) {
+ dev_err(dev, "device init failed\n");
+ return ret;
+ }
+
+ i3c_hub_populate_target_ports(hub);
+
+ i3c_hub_protect_register(hub);
+
+#ifndef CONFIG_I3C_HUB_POLLING_MODE
+ ret = i3c_device_request_ibi(hub->i3cdev, &i3c_hub_ibi_setup);
+ if (ret) {
+ dev_err(&hub->i3cdev->dev, "ibi init failed\n");
+ return ret;
+ }
+ ret = i3c_device_enable_ibi(hub->i3cdev);
+ if (ret) {
+ dev_err(&hub->i3cdev->dev, "ibi enable failed\n");
+ goto err_free_ibi;
+ }
+#endif
+
+#ifdef CONFIG_I3C_HUB_POLLING_MODE
+ i3c_device_disable_ibi(hub->i3cdev);
+
+ if (!hub->smbus_agent_polling_active) {
+ INIT_DELAYED_WORK(&hub->smbus_agent_polling_work, smbus_agent_polling_work);
+ schedule_delayed_work(&hub->smbus_agent_polling_work,
+ msecs_to_jiffies(I3C_HUB_POLLING_ROLL_PERIOD_MS));
+ hub->smbus_agent_polling_active = true;
+ }
+#endif
+
+ sprintf(hub_id, "i3c-hub-%d-%llx",
+ i3c_dev_get_master(i3cdev->desc)->bus.id,
+ i3cdev->desc->info.pid);
+ ret = i3c_hub_debugfs_init(hub, hub_id);
+ if (ret) {
+ dev_err(dev, "Failed to create I3C HUB debugfs\n");
+ goto err_free_ibi;
+ }
+
+ schedule_delayed_work(&hub->delayed_work, msecs_to_jiffies(100));
+
+ return 0;
+
+err_free_ibi:
+ i3c_device_free_ibi(hub->i3cdev);
+ return ret;
+}
+
+static void i3c_hub_remove(struct i3c_device *i3cdev)
+{
+ struct i3c_hub *hub = i3cdev_get_drvdata(i3cdev);
+ int i;
+
+ i3c_device_disable_ibi(i3cdev);
+ i3c_device_free_ibi(i3cdev);
+
+ debugfs_remove_recursive(hub->debug_dir);
+
+ for (i = 0; i < I3C_HUB_TP_MAX_COUNT; ++i) {
+ if (hub->ports[i].bridge)
+ i3c_master_unregister(&hub->ports[i].bridge->i3c);
+ }
+}
+
+static struct i3c_driver i3c_hub = {
+ .driver.name = "i3c-hub",
+ .id_table = i3c_hub_ids,
+ .probe = i3c_hub_probe,
+ .remove = i3c_hub_remove,
+};
+
+module_i3c_driver(i3c_hub);
+
+MODULE_AUTHOR("Zbigniew Lukwinski <zbigniew.lukwinski@linux.intel.com>");
+MODULE_AUTHOR("Steven Niu <steven.niu.uj@renesas.com>");
+MODULE_DESCRIPTION("I3C HUB driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
index c23de58e234f..03e46efbf5bb 100644
--- a/include/linux/i3c/device.h
+++ b/include/linux/i3c/device.h
@@ -95,6 +95,7 @@ struct i3c_priv_xfer {
/**
* enum i3c_dcr - I3C DCR values
* @I3C_DCR_GENERIC_DEVICE: generic I3C device
+ * @I3C_DCR_HUB: I3C HUB device
*/
enum i3c_dcr {
I3C_DCR_GENERIC_DEVICE = 0,
--
2.49.0.906.g1f30a19c02-goog